Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(DatepickerField): integrate dateField with the new Datepicker flowbite component #11

Merged
merged 2 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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