Skip to content

Commit

Permalink
Feat/date value datepicker (#1190)
Browse files Browse the repository at this point in the history
* feat(datepicker): accepts dateValue as value

This issue was found in a scenario where I needed to update the selectedDate without clicking into
the component, just by passing a value to the Datepicker, and actually it just accepts changes by
clicking which I don't believe covers the use cases.

* chore(lint/prettier): cleaning code

Cleaning code

* removed unecessary debug

* test(fixing tests): finding why tests are not running

Tests stopped running locally, running it on the pipe

* chore: fixed testing for the use case scenario

* fix: removing commments

Comments removal

* chore(reducing complexity  for datevalue): assertive state/context management according view/date

* chore: linting

* feat: added necessary documentation

* Update Datepicker.spec.tsx

chore: removing screen.debug

* chore: pipeline build fixes

* fix(clear button): fixed datepicker clear button to not displaying any selected date by using null"

Attributed null as a acceptable value to the selectedDate, also removed default args for weekstart
which was crashing the app due to an overlap with the component props

1039

* Update Datepicker.spec.tsx

* test(datepicker): clear

Clear should affect dateValue

* docs(datepicker): it should have right type for the new empty date case scenario texting property

Updated datepicker component storybook file controls, added the new property and its type so the dev
can simulate scenarios on storybook

* chore: removing unecessary console

* docs(datepicker): label naming

Changed the name from labelempty to label

* feat(datepicker): datevalue expects null to clear date

Now datevalue expects null to clear date, also a diff mechanism was added on the dateValue useffect
to prevent mass re-renders.

g

* feat(datepicker): datepicker should expect value and defaultValue

Now datepicker component accepts value and defaultValue as props comes from this is the most
intuitive pattern.

BREAKING CHANGE: A breaking change which affects the old property defaultDate being named now
defaultValue.

* feat(datepicker): datepicker default value and sideeffects

Setting defaultvalue to empty when the component loads, therefore updating its sideffects on
mounting considering dateview initialization | Added test cases to these scenarios | Added propper
naming and default values according flowbite patterns

BREAKING CHANGE: New defaultValue initialization value | Migrating from defaultDate to defaultValue

* feat: adding datevalue property

* feat: handling date value with propper test cases

* feat: handling date value with propper test cases

* feat: removed default value and renamed onchange prop

* feat: solved comments | updated tests and parameters

* feat: ts compiling

* feat: added null as a type reference for selectedDate

* feat: prettier

* feat: added value as a modifier

* feat: updated storybook to reflect controlled and uncontrolled model

* feat: removed debugger and renamed defaultDate to defaultValue

* feat: removed debugger and renamed defaultDate to defaultValue

* feat: format check

* feat: addressing hook dependencies

* feat: controlled component setup | clearn functionality

* feat: remove debugger statement

* feat: defaultDate empty and side-effects addressed

* feat: added changeset

* feat - changset to patch

---------

Co-authored-by: Dias, Diego <[email protected]>
  • Loading branch information
ddiasfront and Dias, Diego authored Sep 30, 2024
1 parent 38913e5 commit 25bb353
Show file tree
Hide file tree
Showing 10 changed files with 179 additions and 46 deletions.
22 changes: 22 additions & 0 deletions .changeset/old-jobs-decide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
"flowbite-react": patch
---

### Datepicker Component Updates

The Datepicker has been enhanced with several improvements:

1. **Controlled Inputs**: Supports controlled inputs via `value` and `defaultValue` props, enabling programmatic date updates without manual clicks.
2. **State Management**: Optimized internal state management using `useMemo` and `useEffect`.
3. **Documentation**: Added sections in documentation for controlled usage and handling `null` values.
4. **Test Cases**: Comprehensive unit tests added for date handling.
5. **Storybook**: Improved stories, showcasing different states (controlled/uncontrolled).

### Files Updated:

- `apps/web/content/docs/components/datepicker.mdx`: Added controlled usage section.
- `Datepicker.spec.tsx`: Added unit tests.
- `Datepicker.stories.tsx`: Enhanced story variants.
- `Datepicker.tsx`: Expanded `DatepickerProps`.
- `DatepickerContext.tsx`: Adjusted `selectedDate` type.
- `Decades.tsx`, `Months.tsx`, `Years.tsx`: Updated logic to check for `selectedDate`.
6 changes: 6 additions & 0 deletions apps/web/content/docs/components/datepicker.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ Use the `inline` prop to show the datepicker component without having to click i

<Example name="datepicker.inline" />

## Controlled Date/Datepicker.

Use `<Datepicker value={}` to create a controlled `<Datepicker />`. Pass `null` to clear the input.

<Example name="datepicker.value" />

## Theme

To learn more about how to customize the appearance of components, please see the [Theme docs](/docs/customize/theme).
Expand Down
20 changes: 10 additions & 10 deletions packages/ui/src/components/Datepicker/Datepicker.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,17 @@ describe("Components / Datepicker", () => {
expect(screen.getByDisplayValue(todaysDateInDefaultLanguage)).toBeInTheDocument();
});

it("should call `onSelectedDateChange` when a new date is selected", async () => {
const onSelectedDateChange = vi.fn();
it("should call `onChange` when a new date is selected", async () => {
const onChange = vi.fn();
const todaysDayOfMonth = new Date().getDate();
const anotherDay = todaysDayOfMonth === 1 ? 2 : 1;

render(<Datepicker onSelectedDateChanged={onSelectedDateChange} />);
render(<Datepicker onChange={onChange} />);

await userEvent.click(screen.getByRole("textbox"));
await userEvent.click(screen.getAllByText(anotherDay)[0]);

expect(onSelectedDateChange).toHaveBeenCalledOnce();
expect(onChange).toHaveBeenCalledOnce();
});

// TODO: fix
Expand All @@ -80,7 +80,7 @@ describe("Components / Datepicker", () => {

it("should render 1990 - 2100 year range when selecting decade", async () => {
const testDate = new Date(2024, 6, 20);
render(<Datepicker defaultDate={testDate} />);
render(<Datepicker defaultValue={testDate} />);

const textBox = screen.getByRole("textbox");
await userEvent.click(textBox);
Expand All @@ -96,7 +96,7 @@ describe("Components / Datepicker", () => {

it("should allow selecting earlier decades when setting max date", async () => {
const testDate = new Date(2024, 6, 20);
render(<Datepicker defaultDate={testDate} maxDate={testDate} />);
render(<Datepicker defaultValue={testDate} maxDate={testDate} />);

const textBox = screen.getByRole("textbox");
await userEvent.click(textBox);
Expand All @@ -113,7 +113,7 @@ describe("Components / Datepicker", () => {

it("should disallow selecting later decades when setting max date", async () => {
const testDate = new Date(2024, 6, 20);
render(<Datepicker defaultDate={testDate} maxDate={testDate} />);
render(<Datepicker defaultValue={testDate} maxDate={testDate} />);

const textBox = screen.getByRole("textbox");
await userEvent.click(textBox);
Expand All @@ -130,7 +130,7 @@ describe("Components / Datepicker", () => {

it("should disallow selecting earlier decades when setting min date", async () => {
const testDate = new Date(2024, 6, 20);
render(<Datepicker defaultDate={testDate} minDate={testDate} />);
render(<Datepicker defaultValue={testDate} minDate={testDate} />);

const textBox = screen.getByRole("textbox");
await userEvent.click(textBox);
Expand All @@ -147,7 +147,7 @@ describe("Components / Datepicker", () => {

it("should allow selecting later decades when setting min date", async () => {
const testDate = new Date(2024, 6, 20);
render(<Datepicker defaultDate={testDate} minDate={testDate} />);
render(<Datepicker defaultValue={testDate} minDate={testDate} />);

const textBox = screen.getByRole("textbox");
await userEvent.click(textBox);
Expand All @@ -167,7 +167,7 @@ describe("Components / Datepicker", () => {
const maxDate = new Date(2030, 1, 1);
const testDate = new Date(2024, 6, 1);

render(<Datepicker defaultDate={testDate} minDate={minDate} maxDate={maxDate} />);
render(<Datepicker defaultValue={testDate} minDate={minDate} maxDate={maxDate} />);

const textBox = screen.getByRole("textbox");
await userEvent.click(textBox);
Expand Down
97 changes: 92 additions & 5 deletions packages/ui/src/components/Datepicker/Datepicker.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Meta, StoryFn } from "@storybook/react";
import { useEffect, useState } from "react";
import type { DatepickerProps } from "./Datepicker";
import { Datepicker } from "./Datepicker";
import { getFirstDateInRange, WeekStart } from "./helpers";
Expand All @@ -13,6 +14,9 @@ export default {
options: ["en", "pt-BR"],
},
},
value: { control: { type: "date", format: "MM/DD/YYYY" } },
defaultValue: { control: { type: "date", format: "MM/DD/YYYY" } },
label: { control: { type: "text" } },
weekStart: {
options: Object.values(WeekStart).filter((x) => typeof x === "string"),
mapping: Object.entries(WeekStart)
Expand All @@ -28,6 +32,36 @@ export default {
},
} as Meta;

const ControlledTemplate: StoryFn<DatepickerProps> = (args) => {
const [selectedDate, setSelectedDate] = useState<Date | null>(args.value ?? null);

const handleChange = (date: Date | null) => {
setSelectedDate(date);
};

useEffect(() => {
const date = args.value && new Date(args.value);
setSelectedDate(date ?? null);
}, [args.value]);

// https://github.com/storybookjs/storybook/issues/11822
if (args.minDate) {
args.minDate = new Date(args.minDate);
}
if (args.maxDate) {
args.maxDate = new Date(args.maxDate);
}

// update defaultValue based on the range
if (args.minDate && args.maxDate) {
if (args.defaultValue) {
args.defaultValue = getFirstDateInRange(args.defaultValue, args.minDate, args.maxDate);
}
}

return <Datepicker {...args} value={selectedDate} onChange={handleChange} />;
};

const Template: StoryFn<DatepickerProps> = (args) => {
// https://github.com/storybookjs/storybook/issues/11822
if (args.minDate) {
Expand All @@ -37,27 +71,80 @@ const Template: StoryFn<DatepickerProps> = (args) => {
args.maxDate = new Date(args.maxDate);
}

// update defaultDate based on the range
// update defaultValue based on the range
if (args.minDate && args.maxDate) {
if (args.defaultDate) {
// https://github.com/storybookjs/storybook/issues/11822
args.defaultDate = getFirstDateInRange(new Date(args.defaultDate), args.minDate, args.maxDate);
if (args.defaultValue) {
args.defaultValue = getFirstDateInRange(args.defaultValue, args.minDate, args.maxDate);
}
}

return <Datepicker {...args} />;
};

export const ControlledDefaultEmpty = ControlledTemplate.bind({});
ControlledDefaultEmpty.args = {
open: false,
autoHide: true,
showClearButton: true,
showTodayButton: true,
value: null,
minDate: undefined,
maxDate: undefined,
language: "en",
theme: {},
label: "No date selected",
};

export const Default = Template.bind({});
Default.args = {
open: false,
autoHide: true,
showClearButton: true,
showTodayButton: true,
defaultDate: new Date(),
value: undefined,
minDate: undefined,
maxDate: undefined,
language: "en",
theme: {},
};

export const NullDateValue = Template.bind({});
NullDateValue.args = {
open: false,
autoHide: true,
showClearButton: true,
showTodayButton: true,
minDate: undefined,
maxDate: undefined,
language: "en",
theme: {},
};

export const DateValueSet = Template.bind({});
DateValueSet.args = {
open: false,
autoHide: true,
showClearButton: true,
showTodayButton: true,
minDate: undefined,
maxDate: undefined,
language: "en",
defaultValue: new Date(),
theme: {},
};

export const EmptyDates = Template.bind({});
EmptyDates.args = {
open: false,
autoHide: true,
showClearButton: true,
showTodayButton: true,
defaultValue: undefined,
value: undefined,
minDate: undefined,
maxDate: undefined,
language: "en",
weekStart: WeekStart.Sunday,
theme: {},
label: "No date selected",
};
Loading

0 comments on commit 25bb353

Please sign in to comment.