-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(DatepickerField): integrate dateField with the new Datepicker fl…
…owbite component (#11) * feat(DatepickerField): 🎇Datepicker support * feat(DatepickerField): control placeholder
- Loading branch information
1 parent
9be3147
commit 26de38f
Showing
6 changed files
with
236 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
54 changes: 54 additions & 0 deletions
54
src/components/datepicker-field/DatepickerField.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
113
src/components/datepicker-field/DatepickerField.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters