Skip to content

Commit 25bb353

Browse files
wheresdiasdDias, Diego
andauthored
Feat/date value datepicker (#1190)
* 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]>
1 parent 38913e5 commit 25bb353

File tree

10 files changed

+179
-46
lines changed

10 files changed

+179
-46
lines changed

.changeset/old-jobs-decide.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
"flowbite-react": patch
3+
---
4+
5+
### Datepicker Component Updates
6+
7+
The Datepicker has been enhanced with several improvements:
8+
9+
1. **Controlled Inputs**: Supports controlled inputs via `value` and `defaultValue` props, enabling programmatic date updates without manual clicks.
10+
2. **State Management**: Optimized internal state management using `useMemo` and `useEffect`.
11+
3. **Documentation**: Added sections in documentation for controlled usage and handling `null` values.
12+
4. **Test Cases**: Comprehensive unit tests added for date handling.
13+
5. **Storybook**: Improved stories, showcasing different states (controlled/uncontrolled).
14+
15+
### Files Updated:
16+
17+
- `apps/web/content/docs/components/datepicker.mdx`: Added controlled usage section.
18+
- `Datepicker.spec.tsx`: Added unit tests.
19+
- `Datepicker.stories.tsx`: Enhanced story variants.
20+
- `Datepicker.tsx`: Expanded `DatepickerProps`.
21+
- `DatepickerContext.tsx`: Adjusted `selectedDate` type.
22+
- `Decades.tsx`, `Months.tsx`, `Years.tsx`: Updated logic to check for `selectedDate`.

apps/web/content/docs/components/datepicker.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ Use the `inline` prop to show the datepicker component without having to click i
6969

7070
<Example name="datepicker.inline" />
7171

72+
## Controlled Date/Datepicker.
73+
74+
Use `<Datepicker value={}` to create a controlled `<Datepicker />`. Pass `null` to clear the input.
75+
76+
<Example name="datepicker.value" />
77+
7278
## Theme
7379

7480
To learn more about how to customize the appearance of components, please see the [Theme docs](/docs/customize/theme).

packages/ui/src/components/Datepicker/Datepicker.spec.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,17 +57,17 @@ describe("Components / Datepicker", () => {
5757
expect(screen.getByDisplayValue(todaysDateInDefaultLanguage)).toBeInTheDocument();
5858
});
5959

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

65-
render(<Datepicker onSelectedDateChanged={onSelectedDateChange} />);
65+
render(<Datepicker onChange={onChange} />);
6666

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

70-
expect(onSelectedDateChange).toHaveBeenCalledOnce();
70+
expect(onChange).toHaveBeenCalledOnce();
7171
});
7272

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

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

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

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

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

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

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

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

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

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

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

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

172172
const textBox = screen.getByRole("textbox");
173173
await userEvent.click(textBox);

packages/ui/src/components/Datepicker/Datepicker.stories.tsx

Lines changed: 92 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Meta, StoryFn } from "@storybook/react";
2+
import { useEffect, useState } from "react";
23
import type { DatepickerProps } from "./Datepicker";
34
import { Datepicker } from "./Datepicker";
45
import { getFirstDateInRange, WeekStart } from "./helpers";
@@ -13,6 +14,9 @@ export default {
1314
options: ["en", "pt-BR"],
1415
},
1516
},
17+
value: { control: { type: "date", format: "MM/DD/YYYY" } },
18+
defaultValue: { control: { type: "date", format: "MM/DD/YYYY" } },
19+
label: { control: { type: "text" } },
1620
weekStart: {
1721
options: Object.values(WeekStart).filter((x) => typeof x === "string"),
1822
mapping: Object.entries(WeekStart)
@@ -28,6 +32,36 @@ export default {
2832
},
2933
} as Meta;
3034

35+
const ControlledTemplate: StoryFn<DatepickerProps> = (args) => {
36+
const [selectedDate, setSelectedDate] = useState<Date | null>(args.value ?? null);
37+
38+
const handleChange = (date: Date | null) => {
39+
setSelectedDate(date);
40+
};
41+
42+
useEffect(() => {
43+
const date = args.value && new Date(args.value);
44+
setSelectedDate(date ?? null);
45+
}, [args.value]);
46+
47+
// https://github.com/storybookjs/storybook/issues/11822
48+
if (args.minDate) {
49+
args.minDate = new Date(args.minDate);
50+
}
51+
if (args.maxDate) {
52+
args.maxDate = new Date(args.maxDate);
53+
}
54+
55+
// update defaultValue based on the range
56+
if (args.minDate && args.maxDate) {
57+
if (args.defaultValue) {
58+
args.defaultValue = getFirstDateInRange(args.defaultValue, args.minDate, args.maxDate);
59+
}
60+
}
61+
62+
return <Datepicker {...args} value={selectedDate} onChange={handleChange} />;
63+
};
64+
3165
const Template: StoryFn<DatepickerProps> = (args) => {
3266
// https://github.com/storybookjs/storybook/issues/11822
3367
if (args.minDate) {
@@ -37,27 +71,80 @@ const Template: StoryFn<DatepickerProps> = (args) => {
3771
args.maxDate = new Date(args.maxDate);
3872
}
3973

40-
// update defaultDate based on the range
74+
// update defaultValue based on the range
4175
if (args.minDate && args.maxDate) {
42-
if (args.defaultDate) {
43-
// https://github.com/storybookjs/storybook/issues/11822
44-
args.defaultDate = getFirstDateInRange(new Date(args.defaultDate), args.minDate, args.maxDate);
76+
if (args.defaultValue) {
77+
args.defaultValue = getFirstDateInRange(args.defaultValue, args.minDate, args.maxDate);
4578
}
4679
}
4780

4881
return <Datepicker {...args} />;
4982
};
5083

84+
export const ControlledDefaultEmpty = ControlledTemplate.bind({});
85+
ControlledDefaultEmpty.args = {
86+
open: false,
87+
autoHide: true,
88+
showClearButton: true,
89+
showTodayButton: true,
90+
value: null,
91+
minDate: undefined,
92+
maxDate: undefined,
93+
language: "en",
94+
theme: {},
95+
label: "No date selected",
96+
};
97+
5198
export const Default = Template.bind({});
5299
Default.args = {
53100
open: false,
54101
autoHide: true,
55102
showClearButton: true,
56103
showTodayButton: true,
57-
defaultDate: new Date(),
104+
value: undefined,
105+
minDate: undefined,
106+
maxDate: undefined,
107+
language: "en",
108+
theme: {},
109+
};
110+
111+
export const NullDateValue = Template.bind({});
112+
NullDateValue.args = {
113+
open: false,
114+
autoHide: true,
115+
showClearButton: true,
116+
showTodayButton: true,
117+
minDate: undefined,
118+
maxDate: undefined,
119+
language: "en",
120+
theme: {},
121+
};
122+
123+
export const DateValueSet = Template.bind({});
124+
DateValueSet.args = {
125+
open: false,
126+
autoHide: true,
127+
showClearButton: true,
128+
showTodayButton: true,
129+
minDate: undefined,
130+
maxDate: undefined,
131+
language: "en",
132+
defaultValue: new Date(),
133+
theme: {},
134+
};
135+
136+
export const EmptyDates = Template.bind({});
137+
EmptyDates.args = {
138+
open: false,
139+
autoHide: true,
140+
showClearButton: true,
141+
showTodayButton: true,
142+
defaultValue: undefined,
143+
value: undefined,
58144
minDate: undefined,
59145
maxDate: undefined,
60146
language: "en",
61147
weekStart: WeekStart.Sunday,
62148
theme: {},
149+
label: "No date selected",
63150
};

0 commit comments

Comments
 (0)