Skip to content

Commit

Permalink
Merge pull request #21 from moroshko/date-picker-day-prop
Browse files Browse the repository at this point in the history
Add day prop to DatePicker
  • Loading branch information
moroshko authored Feb 28, 2020
2 parents eeb3eda + 0b3277f commit 3dc0058
Show file tree
Hide file tree
Showing 13 changed files with 140 additions and 50 deletions.
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"@testing-library/jest-dom": "5.1.1",
"@testing-library/react": "9.4.1",
"@testing-library/react-hooks": "3.2.1",
"@testing-library/user-event": "^10.0.0",
"babel-eslint": "10.0.3",
"babel-loader": "8.0.6",
"cross-env": "7.0.0",
Expand Down
2 changes: 1 addition & 1 deletion src/components/Checkbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ function Checkbox(props) {
const data = useMemo(
() => ({
isEmpty,
data: validateData
...(validateData && { data: validateData })
}),
[isEmpty, validateData]
);
Expand Down
92 changes: 54 additions & 38 deletions src/components/DatePicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,22 @@ const YEAR_REGEX = /^\d{1,4}$/;

const DEFAULT_PROPS = {
color: InternalInput.DEFAULT_PROPS.color,
day: true,
disabled: false,
optional: false,
validate: ({ day, month, year }) => {
validate: ({ day, month, year }, data) => {
const errors = [];

if (DAY_REGEX.test(day)) {
const dayInt = parseInt(day, 10);
if (data.day) {
if (DAY_REGEX.test(day)) {
const dayInt = parseInt(day, 10);

if (dayInt < 1 || dayInt > 31) {
if (dayInt < 1 || dayInt > 31) {
errors.push("Day must be within 1-31.");
}
} else {
errors.push("Day must be within 1-31.");
}
} else {
errors.push("Day must be within 1-31.");
}

if (MONTH_REGEX.test(month)) {
Expand All @@ -56,7 +59,7 @@ const DEFAULT_PROPS = {
errors.push("Year must be within 1800-2200.");
}

if (errors.length === 0) {
if (data.day && errors.length === 0) {
const twoDigitsDay = day.length === 1 ? `0${day}` : day;
const twoDigitsMonth = month.length === 1 ? `0${month}` : month;

Expand All @@ -75,16 +78,19 @@ const DEFAULT_PROPS = {
DatePicker.COLORS = COLORS;
DatePicker.DEFAULT_PROPS = DEFAULT_PROPS;

function getHelpText(day, month, year, defaultHelpText) {
const dayInt = parseInt(day || "0", 10);
const monthInt = parseInt(month || "0", 10);
const yearInt = parseInt(year || "0", 10);
function getHelpText(value, day, defaultHelpText) {
const dayInt = day ? parseInt(value.day || "0", 10) : 1;
const monthInt = parseInt(value.month || "0", 10);
const yearInt = parseInt(value.year || "0", 10);

if (dayInt === 0 || monthInt === 0 || yearInt === 0) {
if ((day && dayInt === 0) || monthInt === 0 || yearInt === 0) {
return defaultHelpText;
}

return formatDate(new Date(yearInt, monthInt - 1, dayInt), "d MMMM, yyyy");
return formatDate(
new Date(yearInt, monthInt - 1, dayInt),
day ? "d MMMM, yyyy" : "MMMM, yyyy"
);
}

function DatePicker(props) {
Expand All @@ -94,13 +100,15 @@ function DatePicker(props) {
};
const mergedProps = mergeProps(props, DEFAULT_PROPS, inheritedProps, {
color: color => COLORS.includes(color),
day: day => typeof day === "boolean",
disabled: disabled => typeof disabled === "boolean",
optional: optional => typeof optional === "boolean"
});
const {
name,
color,
label,
day,
helpText: helpTextProp,
disabled,
optional,
Expand All @@ -111,15 +119,19 @@ function DatePicker(props) {
const [labelId] = useState(() => `date-picker-${nanoid()}`);
const [auxId] = useState(() => `date-picker-aux-${nanoid()}`);
const isEmpty = useCallback(
value => value.day === "" && value.month === "" && value.year === "",
[]
value =>
(day === false || value.day === "") &&
value.month === "" &&
value.year === "",
[day]
);
const data = useMemo(
() => ({
isEmpty,
data: validateData
day,
...(validateData && { data: validateData })
}),
[isEmpty, validateData]
[isEmpty, day, validateData]
);
const { value, errors, hasErrors, onFocus, onBlur, onChange } = useField({
name,
Expand All @@ -128,10 +140,11 @@ function DatePicker(props) {
validate,
data
});
const helpText = useMemo(
() => getHelpText(value.day, value.month, value.year, helpTextProp),
[value.day, value.month, value.year, helpTextProp]
);
const helpText = useMemo(() => getHelpText(value, day, helpTextProp), [
value,
day,
helpTextProp
]);

return (
<Field
Expand All @@ -149,22 +162,24 @@ function DatePicker(props) {
aria-labelledby={labelId}
aria-describedby={helpText || hasErrors ? auxId : null}
>
<Grid cols={4} colsGutter={1}>
<Grid.Item colSpan={0}>
<InternalInput
name={`${name}.day`}
parentName={name}
color={color}
type="number"
placeholder="DD"
disabled={disabled}
onFocus={onFocus}
onBlur={onBlur}
value={value.day}
onChange={onChange}
/>
</Grid.Item>
<Grid.Item colSpan={1}>
<Grid cols={day ? 4 : 3} colsGutter={1}>
{day && (
<Grid.Item colSpan={0}>
<InternalInput
name={`${name}.day`}
parentName={name}
color={color}
type="number"
placeholder="DD"
disabled={disabled}
onFocus={onFocus}
onBlur={onBlur}
value={value.day}
onChange={onChange}
/>
</Grid.Item>
)}
<Grid.Item colSpan={day ? 1 : 0}>
<InternalInput
name={`${name}.month`}
parentName={name}
Expand All @@ -178,7 +193,7 @@ function DatePicker(props) {
onChange={onChange}
/>
</Grid.Item>
<Grid.Item colSpan="2-3">
<Grid.Item colSpan={day ? "2-3" : "1-2"}>
<InternalInput
name={`${name}.year`}
parentName={name}
Expand All @@ -202,6 +217,7 @@ DatePicker.propTypes = {
name: PropTypes.string.isRequired,
color: PropTypes.oneOf(COLORS),
label: PropTypes.string.isRequired,
day: PropTypes.bool,
helpText: PropTypes.string,
disabled: PropTypes.bool,
optional: PropTypes.bool,
Expand Down
45 changes: 40 additions & 5 deletions src/components/DatePicker.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import { render, wait } from "../utils/test";
import { render, wait, userEvent } from "../utils/test";
import "@testing-library/jest-dom/extend-expect";
import Form from "./Form";
import DatePicker from "./DatePicker";
Expand All @@ -21,6 +21,13 @@ function FormWithDatePicker(props) {
);
}

function getHelpText(container) {
const inputsContainer = container.querySelector("[aria-labelledby]");
const describedBy = inputsContainer.getAttribute("aria-describedby");

return container.querySelector(`[id="${describedBy}"]`).textContent;
}

describe("DatePicker", () => {
it("renders label and 3 fields", () => {
const { container, getByText, getByPlaceholderText } = render(
Expand All @@ -45,15 +52,43 @@ describe("DatePicker", () => {
expect(yearInput.getAttribute("type")).toBe("number");
});

it("doesn't render the day field when day={false}", () => {
const { queryByPlaceholderText } = render(
<FormWithDatePicker label="Expiry date" day={false} />
);

expect(queryByPlaceholderText("DD")).not.toBeInTheDocument();
});

it("renders the date as help text", async () => {
const { container, getByPlaceholderText } = render(
<FormWithDatePicker label="Expiry date" />
);

await userEvent.type(getByPlaceholderText("DD"), "6");
await userEvent.type(getByPlaceholderText("MM"), "4");
await userEvent.type(getByPlaceholderText("YYYY"), "2017");

expect(getHelpText(container)).toBe("6 April, 2017");
});

it("renders the date as help text when day={false}", async () => {
const { container, getByPlaceholderText } = render(
<FormWithDatePicker label="Expiry date" day={false} />
);

await userEvent.type(getByPlaceholderText("MM"), "4");
await userEvent.type(getByPlaceholderText("YYYY"), "2017");

expect(getHelpText(container)).toBe("April, 2017");
});

it("renders help text", () => {
const { container } = render(
<FormWithDatePicker label="Expiry date" helpText="Some help text" />
);
const inputsContainer = container.querySelector("[aria-labelledby]");
const describedBy = inputsContainer.getAttribute("aria-describedby");
const errorMessage = container.querySelector(`[id="${describedBy}"]`);

expect(errorMessage).toHaveTextContent("Some help text");
expect(getHelpText(container)).toBe("Some help text");
});

it("renders error messages", async () => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/Frequency.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ function Frequency(props) {
isInputEmpty,
isFrequencyEmpty,
isEmpty,
data: validateData
...(validateData && { data: validateData })
}),
[isInputEmpty, isFrequencyEmpty, isEmpty, validateData]
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/Input.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ function Input(props) {
const data = useMemo(
() => ({
isEmpty,
data: validateData
...(validateData && { data: validateData })
}),
[isEmpty, validateData]
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/RadioGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ function RadioGroup(props) {
const data = useMemo(
() => ({
isEmpty,
data: validateData
...(validateData && { data: validateData })
}),
[isEmpty, validateData]
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/Select.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ function Select(props) {
const data = useMemo(
() => ({
isEmpty,
data: validateData
...(validateData && { data: validateData })
}),
[isEmpty, validateData]
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/TimeSpan.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ function TimeSpan(props) {
const data = useMemo(
() => ({
isEmpty,
data: validateData
...(validateData && { data: validateData })
}),
[isEmpty, validateData]
);
Expand Down
3 changes: 3 additions & 0 deletions src/utils/test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from "react";
import PropTypes from "prop-types";
import { render } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import "regenerator-runtime/runtime";
import matchMediaPolyfill from "mq-polyfill";
import { BasisProvider, defaultTheme } from "..";
Expand Down Expand Up @@ -29,3 +30,5 @@ export * from "@testing-library/react";

// override render method
export { customRender as render };

export { userEvent };
15 changes: 14 additions & 1 deletion website/src/components/kitchen-sink/DatePicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import KitchenSinkForm from "./KitchenSinkForm";

function FormWithDatePicker({
label,
day,
initialValue = {
day: "",
month: "",
Expand All @@ -18,13 +19,14 @@ function FormWithDatePicker({
initialValues={{ birthDate: initialValue }}
submitOnMount={submitOnMount}
>
<DatePicker name="birthDate" label={label} />
<DatePicker name="birthDate" label={label} day={day} />
</KitchenSinkForm>
);
}

FormWithDatePicker.propTypes = {
label: PropTypes.string,
day: PropTypes.bool,
initialValue: PropTypes.shape({
day: PropTypes.string.isRequired,
month: PropTypes.string.isRequired,
Expand All @@ -40,6 +42,8 @@ function KitchenSinkDatePicker() {
<Grid rowsGutter="8">
<FormWithDatePicker label="Grey" />

<FormWithDatePicker label="Without day" day={false} />

<FormWithDatePicker
label="With error"
initialValue={{
Expand All @@ -63,6 +67,15 @@ function KitchenSinkDatePicker() {
}}
/>

<FormWithDatePicker
label="Without day"
day={false}
initialValue={{
month: "2",
year: "1999"
}}
/>

<FormWithDatePicker label="Multiple errors" submitOnMount />
</Grid>
</Container>
Expand Down
Loading

1 comment on commit 3dc0058

@vercel
Copy link

@vercel vercel bot commented on 3dc0058 Feb 28, 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.