Skip to content

Commit

Permalink
Add variant='numeric' to Input (#119)
Browse files Browse the repository at this point in the history
  • Loading branch information
moroshko authored Jun 30, 2020
1 parent e9049a2 commit 553de26
Show file tree
Hide file tree
Showing 15 changed files with 299 additions and 128 deletions.
15 changes: 9 additions & 6 deletions src/components/DatePicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,9 @@ function DatePicker(props) {
const [auxId] = useState(() => `date-picker-aux-${nanoid()}`);
const isEmpty = useCallback(
(value) =>
(day === false || value.day === "") &&
value.month === "" &&
value.year === "",
(day === false || value.day.trim() === "") &&
value.month.trim() === "" &&
value.year.trim() === "",
[day]
);
const data = useMemo(
Expand Down Expand Up @@ -173,9 +173,10 @@ function DatePicker(props) {
<InternalInput
name={`${name}.day`}
parentName={name}
variant="numeric"
color={props.color}
type="number"
placeholder="DD"
maxLength="2"
disabled={disabled}
onFocus={onFocus}
onBlur={onBlur}
Expand All @@ -188,9 +189,10 @@ function DatePicker(props) {
<InternalInput
name={`${name}.month`}
parentName={name}
variant="numeric"
color={props.color}
type="number"
placeholder="MM"
maxLength="2"
disabled={disabled}
onFocus={onFocus}
onBlur={onBlur}
Expand All @@ -202,9 +204,10 @@ function DatePicker(props) {
<InternalInput
name={`${name}.year`}
parentName={name}
variant="numeric"
color={props.color}
type="number"
placeholder="YYYY"
maxLength="4"
disabled={disabled}
onFocus={onFocus}
onBlur={onBlur}
Expand Down
11 changes: 8 additions & 3 deletions src/components/DatePicker.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,14 @@ describe("DatePicker", () => {
const monthInput = screen.getByPlaceholderText("MM");
const yearInput = screen.getByPlaceholderText("YYYY");

expect(dayInput).toHaveAttribute("type", "number");
expect(monthInput).toHaveAttribute("type", "number");
expect(yearInput).toHaveAttribute("type", "number");
expect(dayInput).toHaveAttribute("inputmode", "numeric");
expect(dayInput).toHaveAttribute("maxlength", "2");

expect(monthInput).toHaveAttribute("inputmode", "numeric");
expect(monthInput).toHaveAttribute("maxlength", "2");

expect(yearInput).toHaveAttribute("inputmode", "numeric");
expect(yearInput).toHaveAttribute("maxlength", "4");
});

it("doesn't render the day field when day={false}", () => {
Expand Down
5 changes: 1 addition & 4 deletions src/components/Form.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,7 @@ describe("Form", () => {
likeIceCream: ["Must be checked"],
name: ["Required"],
relationshipStatus: ["Please make a selection."],
salary: [
"Please enter a valid amount.",
"Please select a frequency.",
],
salary: ["Please enter an amount.", "Please select a frequency."],
birthDate: [
"Day must be within 1-31.",
"Month must be within 1-12.",
Expand Down
19 changes: 14 additions & 5 deletions src/components/Frequency.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const ALL_FREQUENCY_OPTIONS = [
},
];

const { COLORS } = InternalInput;
const { COLORS, NUMERIC_REGEX } = InternalInput;
const MODES = ["radio-group", "select"];

function isFrequencySelected(frequency, frequencyPropsMap) {
Expand All @@ -57,7 +57,11 @@ const DEFAULT_PROPS = {
const errors = [];

if (isInputEmpty(value.amount)) {
errors.push("Please enter a valid amount.");
errors.push("Please enter an amount.");
}

if (NUMERIC_REGEX.test(value.amount) === false) {
errors.push("Amount can contain only digits.");
}

if (isFrequencyEmpty(value.frequency)) {
Expand Down Expand Up @@ -86,6 +90,8 @@ function Frequency(props) {
monthly: (monthly) => typeof monthly === "boolean",
fortnightly: (fortnightly) => typeof fortnightly === "boolean",
weekly: (weekly) => typeof weekly === "boolean",
amountPrefix: (amountPrefix) =>
typeof amountPrefix === "string" && amountPrefix.length > 0,
disabled: (disabled) => typeof disabled === "boolean",
optional: (optional) => typeof optional === "boolean",
}
Expand All @@ -99,11 +105,12 @@ function Frequency(props) {
monthly,
fortnightly,
weekly,
optional,
amountPrefix,
amountPlaceholder,
selectPlaceholder,
helpText,
disabled,
optional,
validate,
validateData,
testId,
Expand Down Expand Up @@ -132,7 +139,7 @@ function Frequency(props) {
[frequencyPropsMap]
);
const isInputEmpty = useCallback((amount) => {
return amount === "";
return amount.trim() === "";
}, []);
const isFrequencyEmpty = useCallback(
(frequency) => {
Expand Down Expand Up @@ -174,8 +181,9 @@ function Frequency(props) {
<InternalInput
name={`${name}.amount`}
parentName={name}
variant="numeric"
numericPrefix={amountPrefix}
color={props.color}
type="number"
placeholder={amountPlaceholder}
disabled={disabled}
onFocus={onFocus}
Expand Down Expand Up @@ -256,6 +264,7 @@ Frequency.propTypes = {
monthly: PropTypes.bool,
fortnightly: PropTypes.bool,
weekly: PropTypes.bool,
amountPrefix: PropTypes.string,
amountPlaceholder: PropTypes.string,
selectPlaceholder: PropTypes.string,
helpText: PropTypes.node,
Expand Down
4 changes: 2 additions & 2 deletions src/components/Frequency.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe("Frequency", () => {

const amountInput = screen.getByPlaceholderText("0.00");

expect(amountInput).toHaveAttribute("type", "number");
expect(amountInput).toHaveAttribute("inputmode", "numeric");

expect(screen.getByText("Annually")).toBeInTheDocument();
expect(screen.getByText("Quarterly")).toBeInTheDocument();
Expand Down Expand Up @@ -82,7 +82,7 @@ describe("Frequency", () => {
amountInput.focus();
amountInput.blur();

await screen.findByText("Please enter a valid amount.");
await screen.findByText("Please enter an amount.");
await screen.findByText("Please select a frequency.");
await waitFor(() => {
expect(screen.queryByText("Some help text")).not.toBeInTheDocument();
Expand Down
51 changes: 24 additions & 27 deletions src/components/Input.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,28 @@ import { mergeProps } from "../utils/component";
import Field from "./internal/Field";
import InternalInput from "./internal/InternalInput";

const { TYPES, COLORS } = InternalInput;
const { VARIANTS, COLORS, NUMERIC_REGEX } = InternalInput;

const DEFAULT_PROPS = {
variant: InternalInput.DEFAULT_PROPS.variant,
color: InternalInput.DEFAULT_PROPS.color,
type: InternalInput.DEFAULT_PROPS.type,
disabled: false,
pasteAllowed: true,
optional: false,
validate: (value, { isEmpty }) => {
validate: (value, { isEmpty, variant }) => {
if (isEmpty(value)) {
return "Required";
}

if (variant === "numeric" && NUMERIC_REGEX.test(value) === false) {
return "Only 0-9 are allowed";
}

return null;
},
};

Input.TYPES = TYPES;
Input.VARIANTS = VARIANTS;
Input.COLORS = COLORS;
Input.DEFAULT_PROPS = DEFAULT_PROPS;

Expand All @@ -33,28 +37,22 @@ function Input(props) {
DEFAULT_PROPS,
{},
{
variant: (variant) => VARIANTS.includes(variant),
numericPrefix: (numericPrefix) =>
typeof numericPrefix === "string" && numericPrefix.length > 0,
numericSuffix: (numericSuffix) =>
typeof numericSuffix === "string" && numericSuffix.length > 0,
color: (color) => COLORS.includes(color),
type: (type) => TYPES.includes(type),
min: (min) =>
props.type === "number" &&
(typeof min === "number" || typeof min === "string"),
max: (max) =>
props.type === "number" &&
(typeof max === "number" || typeof max === "string"),
step: (step) =>
props.type === "number" &&
(typeof step === "number" || typeof step === "string"),
disabled: (disabled) => typeof disabled === "boolean",
pasteAllowed: (pasteAllowed) => typeof pasteAllowed === "boolean",
optional: (optional) => typeof optional === "boolean",
}
);
const {
name,
type,
min,
max,
step,
variant,
numericPrefix,
numericSuffix,
label,
placeholder,
helpText,
Expand All @@ -72,9 +70,10 @@ function Input(props) {
const data = useMemo(
() => ({
isEmpty,
variant,
...(validateData && { data: validateData }),
}),
[isEmpty, validateData]
[isEmpty, variant, validateData]
);
const { value, errors, hasErrors, onFocus, onBlur, onChange } = useField(
"Input",
Expand All @@ -101,10 +100,9 @@ function Input(props) {
<InternalInput
id={label ? inputId : null}
name={name}
type={type}
min={min}
max={max}
step={step}
variant={variant}
numericPrefix={numericPrefix}
numericSuffix={numericSuffix}
placeholder={placeholder}
color={props.color}
disabled={disabled}
Expand All @@ -123,11 +121,10 @@ function Input(props) {

Input.propTypes = {
name: PropTypes.string.isRequired,
variant: PropTypes.oneOf(VARIANTS),
numericPrefix: PropTypes.string,
numericSuffix: PropTypes.string,
color: PropTypes.oneOf(COLORS),
type: PropTypes.oneOf(TYPES),
min: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
max: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
step: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
label: PropTypes.string.isRequired,
placeholder: PropTypes.string,
helpText: PropTypes.node,
Expand Down
80 changes: 69 additions & 11 deletions src/components/Input.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,38 +23,96 @@ describe("Input", () => {

const label = screen.getByText("First name");
const input = screen.getByLabelText("First name");
const inputContainer = input.parentElement;

expect(label.tagName).toBe("LABEL");
expect(input.tagName).toBe("INPUT");

const inputId = input.getAttribute("id");

expect(inputId).toBeTruthy();
expect(input).toHaveAttribute("type", "text");
expect(label).toHaveAttribute("for", inputId);

expect(input).toHaveStyle({
boxSizing: "border-box",
expect(label).toHaveStyle({
display: "flex",
fontFamily: "'Roboto',sans-serif",
fontSize: "16px",
fontWeight: "500",
lineHeight: "24px",
color: "#414141",
marginBottom: "8px",
});

expect(inputContainer).toHaveStyle({
fontSize: "16px",
fontWeight: "300",
lineHeight: "24px",
fontFamily: "'Roboto',sans-serif",
padding: "0 16px",
color: "#000000",
});

expect(input).toHaveStyle({
boxSizing: "border-box",
fontSize: "inherit",
fontWeight: "inherit",
lineHeight: "inherit",
fontFamily: "inherit",
color: "inherit",
padding: "0px 16px 0px 16px",
width: "100%",
height: "48px",
border: "0",
margin: "0",
backgroundColor: "#f2f2f2",
});
});

expect(label).toHaveStyle({
display: "flex",
fontFamily: "'Roboto',sans-serif",
fontSize: "16px",
fontWeight: "500",
lineHeight: "24px",
color: "#414141",
marginBottom: "8px",
it("numeric variant", () => {
render(<FormWithInput label="New credit limit" variant="numeric" />);

const input = screen.getByLabelText("New credit limit");

expect(input).toHaveAttribute("type", "text");
expect(input).toHaveAttribute("inputmode", "numeric");
expect(input).toHaveAttribute("pattern", "[0-9]*");
});

it("with numericPrefix", () => {
render(
<FormWithInput
label="New credit limit"
variant="numeric"
numericPrefix="$"
/>
);

const input = screen.getByLabelText("New credit limit");

expect(input).toHaveStyle({
paddingTop: 0,
paddingBottom: 0,
paddingLeft: "calc(16px + 2ch)",
paddingRight: "16px",
});
});

it("with numericSuffix", () => {
render(
<FormWithInput
label="New credit limit"
variant="numeric"
numericSuffix="mysuffix"
/>
);

const input = screen.getByLabelText("New credit limit");

expect(input).toHaveStyle({
paddingTop: 0,
paddingBottom: 0,
paddingLeft: "16px",
paddingRight: "calc(16px + 9ch)",
});
});

Expand Down
Loading

1 comment on commit 553de26

@vercel
Copy link

@vercel vercel bot commented on 553de26 Jun 30, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.