Skip to content

Commit

Permalink
✨ - feat: add DatePicker component
Browse files Browse the repository at this point in the history
  • Loading branch information
svenvandescheur committed Jun 10, 2024
1 parent 022fa73 commit a337d08
Show file tree
Hide file tree
Showing 16 changed files with 909 additions and 23 deletions.
68 changes: 63 additions & 5 deletions package-lock.json

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

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
"@storybook/test-runner": "^0.17.0",
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/react": "^18.0.0",
"@types/react-datepicker": "^6.2.0",
"@typescript-eslint/eslint-plugin": "^6.17.0",
"@typescript-eslint/parser": "^6.17.0",
"commitlint": "^19.0.0",
Expand Down Expand Up @@ -121,6 +122,7 @@
"dependencies": {
"@floating-ui/react": "^0.26.6",
"@heroicons/react": "^2.1.1",
"clsx": "^2.1.0"
"clsx": "^2.1.0",
"react-datepicker": "^6.9.0"
}
}
23 changes: 11 additions & 12 deletions src/components/form/choicefield/choicefield.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,25 @@ import { Select, SelectProps } from "../select";
export type ChoiceFieldProps<
Element = HTMLDivElement,
FormElement = HTMLSelectElement,
> = Omit<React.HTMLAttributes<Element>, "onChange"> & {
> = Omit<React.HTMLAttributes<Element>, "onChange" | "value"> & {
/** Can be used to generate `SelectOption` components from an array of objects. */
options: Option[];

/** Form element name. */
name?: string;

/** Form element type. */
type?: string;

/** Gets called when the input is blurred. */
onBlur?: React.FormEventHandler<FormElement>;

/** Value of the form element */
value?: Option["value"] | null;

/** The variant (style) of the form element. */
variant?: "normal" | "transparent";

/**
*
* A custom "change" event created with `detail` set to the selected option.
Expand All @@ -24,11 +33,6 @@ export type ChoiceFieldProps<
* the use of events).
*/
onChange?: React.ChangeEventHandler<FormElement>;

value?: Option["value"] | null;

/** The variant (style) of the form element. */
variant?: "normal" | "transparent";
};
/**
* A single (select) option, can be passed to `Select as array.
Expand All @@ -45,12 +49,7 @@ export type Option<L = number | string, V = string | number | undefined> = {
* @param props
* @constructor
*/
export const ChoiceField: React.FC<
ChoiceFieldProps & {
/** Can be set to `checkbox` to render as checkbox group. */
type?: "checkbox" | "select";
}
> = ({ type, ...props }) => {
export const ChoiceField: React.FC<ChoiceFieldProps> = ({ type, ...props }) => {
if (type === "checkbox") {
return <CheckboxGroup {...(props as CheckboxGroupProps)}></CheckboxGroup>;
}
Expand Down
5 changes: 5 additions & 0 deletions src/components/form/datepicker/datepicker.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.mykn-datepicker {
+ .react-datepicker__close-icon::after {
background-color: var(--typography-color-body);
}
}
162 changes: 162 additions & 0 deletions src/components/form/datepicker/datepicker.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import type { Meta, StoryObj } from "@storybook/react";
import { expect, fn, userEvent, waitFor, within } from "@storybook/test";

import { FORM_TEST_DECORATOR } from "../.storybook/decorators";
import { DatePicker } from "./datepicker";

const meta = {
title: "Form/Datepicker",
component: DatePicker,
argTypes: {
onChange: {
action: "onChange",
},
},
} satisfies Meta<typeof DatePicker>;

export default meta;
type Story = StoryObj<typeof meta>;

// Date

export const DatepickerComponent: Story = {
args: {
type: "date",
name: "date",
value: "2023-09-15",
},
decorators: [FORM_TEST_DECORATOR],
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const input: HTMLInputElement = canvas.getByRole("textbox");
let latestEventListenerEvent: Event | null = null;
const spy = fn(
(customEvent: Event) => (latestEventListenerEvent = customEvent),
);
input.addEventListener("change", spy);

// Test click date.
await userEvent.click(input, { delay: 10 });
const startDate = canvas.getByText("14");
await userEvent.click(startDate, { delay: 10 });

await waitFor(testFormDataSerialization.bind(this, "2023-09-14"));
await waitFor(testEventListener.bind(this, "2023-09-14"));

// Test clear date.
const clearButton = canvas.getByRole("button");
await userEvent.click(clearButton, { delay: 10 });

await waitFor(testFormDataSerialization.bind(this, ""));
await waitFor(testEventListener.bind(this, ""));

// Test type date.
await userEvent.type(input, "2023-09-15", { delay: 10 });

async function testFormDataSerialization(expectedDate: string) {
const pre = await canvas.findByRole("log");
const data = JSON.parse(pre?.textContent || "{}");
await expect(data.date).toBe(expectedDate);
}

async function testEventListener(expectedDate: string) {
await expect(spy).toHaveBeenCalled();
const input = latestEventListenerEvent?.target as HTMLInputElement;
await expect(input.value).toBe(expectedDate);
}
},
};

export const DatePickerWithoutValue: Story = {
...DatepickerComponent,
args: {
...DatepickerComponent.args,
type: "date",
},
};

export const DatePickerWithDateAsValue: Story = {
...DatepickerComponent,
args: {
...DatepickerComponent.args,
type: "date",
value: new Date("2023-09-15"),
},
};

export const DatePickerWithNumberAsValue: Story = {
...DatepickerComponent,
args: {
...DatepickerComponent.args,
type: "date",
value: 1694736000000,
},
};

// Date Range

export const DateRangePicker: Story = {
args: {
name: "daterange",
type: "daterange",
value: "2023-09-14/2023-09-15",
},
decorators: [FORM_TEST_DECORATOR],
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const input: HTMLInputElement = canvas.getByRole("textbox");
let latestEventListenerEvent: Event | null = null;
const spy = fn(
(customEvent: Event) => (latestEventListenerEvent = customEvent),
);
input.addEventListener("change", spy);

// Test click date.
await userEvent.click(input, { delay: 10 });
const startDate = canvas.getByText("14");
const endDate = canvas.getByText("15");
await userEvent.click(startDate, { delay: 10 });
await userEvent.click(endDate, { delay: 10 });

await waitFor(
testFormDataSerialization.bind(this, "2023-09-14/2023-09-15"),
);
await waitFor(testEventListener.bind(this, "2023-09-14/2023-09-15"));

// Test clear date.
const clearButton = canvas.getByRole("button");
await userEvent.click(clearButton, { delay: 10 });

await waitFor(testFormDataSerialization.bind(this, ""));
await waitFor(testEventListener.bind(this, ""));

async function testFormDataSerialization(expectedDate: string) {
const pre = await canvas.findByRole("log");
const data = JSON.parse(pre?.textContent || "{}");
await expect(data.daterange).toBe(expectedDate);
}

async function testEventListener(expectedDate: string) {
await expect(spy).toHaveBeenCalled();
const input = latestEventListenerEvent?.target as HTMLInputElement;
await expect(input.value).toBe(expectedDate);
}
},
};

export const DateRangePickerWithoutValue: Story = {
...DateRangePicker,
args: {
...DateRangePicker.args,
type: "daterange",
},
};

export const DateRangePickerWithDatesAsValue: Story = {
...DateRangePicker,
args: {
...DateRangePicker.args,
type: "daterange",
value: [new Date("2023-09-14"), new Date("2023-09-15")],
},
};
Loading

0 comments on commit a337d08

Please sign in to comment.