Skip to content

Commit

Permalink
feat(DatepickerField): integrate dateField with the new Datepicker fl…
Browse files Browse the repository at this point in the history
…owbite component (#11)

* feat(DatepickerField): 🎇Datepicker support

* feat(DatepickerField): control placeholder
  • Loading branch information
MiroslavPetrik authored Mar 14, 2024
1 parent 9be3147 commit 26de38f
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 9 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ jobs:

- name: 💾 Cache node_modules
id: cache-node-modules
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}

- name: 🏗️ Install
uses: borales/actions-yarn@v4
uses: borales/actions-yarn@v5
with:
cmd: install

Expand All @@ -48,7 +48,7 @@ jobs:
run: yarn build

- name: 🚢 Release
uses: borales/actions-yarn@v4
uses: borales/actions-yarn@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
},
"devDependencies": {
"@emotion/react": "^11.11.1",
"@form-atoms/field": "^5.1.0",
"@form-atoms/field": "^5.1.1",
"@form-atoms/list-atom": "^1.0.11",
"@mdx-js/react": "^2.3.0",
"@semantic-release/changelog": "^6.0.3",
Expand Down
54 changes: 54 additions & 0 deletions src/components/datepicker-field/DatepickerField.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { dateField } from "@form-atoms/field";

import { DatepickerField } from "./DatepickerField";
import { FormStory, meta, optionalField } from "../../stories/story-form";

export default {
title: "DatepickerField",
...meta,
};

const dueDate = dateField({
schema: (s) => {
return s.min(new Date());
},
});

export const Required: FormStory = {
args: {
fields: { dueDate },
children: ({ required }) => (
<DatepickerField
field={dueDate}
label="Due Date"
required={required}
helperText="Event must be in the future"
/>
),
},
};

const optional = dateField().optional();

export const Optional: FormStory = {
...optionalField,
args: {
fields: { optional },
children: () => <DatepickerField field={optional} label="Birthday" />,
},
};

const initialized = dateField();

export const Initialized: FormStory = {
args: {
fields: { initialized },
children: () => (
<DatepickerField
field={initialized}
initialValue={new Date()}
label="Birthday"
/>
),
},
};
113 changes: 113 additions & 0 deletions src/components/datepicker-field/DatepickerField.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { dateField } from "@form-atoms/field";
import { act, render, renderHook, screen } from "@testing-library/react";
import { userEvent } from "@testing-library/user-event";
import { formAtom, useFieldActions, useFormSubmit } from "form-atoms";
import { describe, expect, it } from "vitest";

import { DatepickerField } from "./DatepickerField";

describe("<DatepickerField />", () => {
it("focuses input when clicked on label", async () => {
const field = dateField();

render(<DatepickerField role="dialog" field={field} label="label" />);

await act(() =>
userEvent.click(screen.getByLabelText("label", { exact: false })),
);

expect(screen.getByRole("dialog")).toHaveFocus();
});

describe("with required field", () => {
it("renders error message when submitting empty", async () => {
const field = dateField();

const form = formAtom({
field,
});
const { result } = renderHook(() => useFormSubmit(form));

const onSubmit = vi.fn();
await act(async () => {
result.current(onSubmit)();
});

render(<DatepickerField role="dialog" field={field} />);

expect(screen.getByRole("dialog")).toBeInvalid();
expect(screen.getByText("This field is required")).toBeInTheDocument();
expect(onSubmit).not.toBeCalled();
});

it("submits without error when valid", async () => {
const value = new Date();
const field = dateField();
const form = formAtom({ field });
const { result } = renderHook(() => useFormSubmit(form));

render(
<DatepickerField field={field} initialValue={value} role="dialog" />,
);

const input = screen.getByRole("dialog");

expect(input).toBeValid();

const onSubmit = vi.fn();
await act(async () => {
result.current(onSubmit)();
});

expect(onSubmit).toHaveBeenCalledWith({ field: value });
});
});

describe("with optional field", () => {
it("submits with undefined", async () => {
const field = dateField().optional();
const form = formAtom({ field });
const { result } = renderHook(() => useFormSubmit(form));

render(<DatepickerField field={field} role="dialog" />);

const dateInput = screen.getByRole("dialog");

expect(dateInput).toBeValid();

const onSubmit = vi.fn();
await act(async () => {
result.current(onSubmit)();
});

expect(onSubmit).toHaveBeenCalledWith({ field: undefined });
});
});

describe("placeholder", () => {
it("renders", () => {
const field = dateField();

render(<DatepickerField field={field} placeholder="Pick a date" />);

expect(screen.getByPlaceholderText("Pick a date")).toBeInTheDocument();
});

it("appears when the field is cleared", async () => {
const field = dateField({ value: new Date() });
const { result: fieldActions } = renderHook(() => useFieldActions(field));

render(<DatepickerField field={field} placeholder="Pick a date" />);

expect(
screen.queryByPlaceholderText("Pick a date"),
).not.toBeInTheDocument();

await act(async () => {
fieldActions.current.setValue(undefined);
});

expect(screen.queryByPlaceholderText("Pick a date")).toBeInTheDocument();
});
});
});
60 changes: 60 additions & 0 deletions src/components/datepicker-field/DatepickerField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { DateFieldProps, useDateFieldProps } from "@form-atoms/field";
import { Datepicker, DatepickerProps } from "flowbite-react";

import { FlowbiteField } from "../field";

type DatepickerFIeldProps = DateFieldProps &
Omit<DatepickerProps, "onSelectedDateChanged">;

export const DatepickerField = ({
field,
label,
helperText,
required,
initialValue,
placeholder = "Please select a date",
...uiProps
}: DatepickerFIeldProps) => {
const {
// TODO(flowbite-react/Datepicker): support forwardRef
// eslint-disable-next-line @typescript-eslint/no-unused-vars
ref,
value,
onChange,
...dateFieldProps
} = useDateFieldProps(field, {
initialValue,
});

const emptyProps = !value
? {
value: "",
placeholder,
}
: {};

return (
<FlowbiteField
field={field}
label={label}
required={required}
helperText={helperText}
>
{(fieldProps) => (
<Datepicker
{...dateFieldProps}
{...uiProps}
{...fieldProps}
{...emptyProps}
defaultDate={value}
onSelectedDateChanged={(valueAsDate) => {
onChange({
// @ts-expect-error fake event
currentTarget: { valueAsDate },
});
}}
/>
)}
</FlowbiteField>
);
};
10 changes: 5 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2726,9 +2726,9 @@ __metadata:
languageName: node
linkType: hard

"@form-atoms/field@npm:^5.1.0":
version: 5.1.0
resolution: "@form-atoms/field@npm:5.1.0"
"@form-atoms/field@npm:^5.1.1":
version: 5.1.1
resolution: "@form-atoms/field@npm:5.1.1"
dependencies:
react-render-prop-type: "npm:0.1.0"
peerDependencies:
Expand All @@ -2738,7 +2738,7 @@ __metadata:
jotai-effect: ^0
react: ">=16.8"
zod: ^3
checksum: 556d1366b731cb8a995bd37b9057f9877ebcf0406dbed9c0e1a175d4d0edd3d439528b3f4b27c758f2364d938c35c1fbccd397664aad2c28232605038aad2b9a
checksum: af504b7abfc3352919d5882d9e40ebe4c504a94dcced380c9bbd139b55074518fbd7a4b7256dbdabc1c650094865142496890f647cb52cd145471422097d53f6
languageName: node
linkType: hard

Expand All @@ -2747,7 +2747,7 @@ __metadata:
resolution: "@form-atoms/flowbite@workspace:."
dependencies:
"@emotion/react": "npm:^11.11.1"
"@form-atoms/field": "npm:^5.1.0"
"@form-atoms/field": "npm:^5.1.1"
"@form-atoms/list-atom": "npm:^1.0.11"
"@mdx-js/react": "npm:^2.3.0"
"@semantic-release/changelog": "npm:^6.0.3"
Expand Down

0 comments on commit 26de38f

Please sign in to comment.