Skip to content

Commit 36d1951

Browse files
committed
Merge branch 'main' into enhancement/on-right-icon-click-prop
2 parents 5c1ffa7 + bed3c41 commit 36d1951

File tree

12 files changed

+183
-52
lines changed

12 files changed

+183
-52
lines changed

.changeset/plenty-lemons-bow.md

-5
This file was deleted.

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

+6
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/CHANGELOG.md

+25
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,30 @@
11
# Changelog
22

3+
## 0.10.2
4+
5+
### Patch Changes
6+
7+
- [#1190](https://github.com/themesberg/flowbite-react/pull/1190) [`25bb353`](https://github.com/themesberg/flowbite-react/commit/25bb353685c595c2b05f1a355a381c28fd57526a) Thanks [@ddiasfront](https://github.com/ddiasfront)! - ### Datepicker Component Updates
8+
9+
The Datepicker has been enhanced with several improvements:
10+
11+
1. **Controlled Inputs**: Supports controlled inputs via `value` and `defaultValue` props, enabling programmatic date updates without manual clicks.
12+
2. **State Management**: Optimized internal state management using `useMemo` and `useEffect`.
13+
3. **Documentation**: Added sections in documentation for controlled usage and handling `null` values.
14+
4. **Test Cases**: Comprehensive unit tests added for date handling.
15+
5. **Storybook**: Improved stories, showcasing different states (controlled/uncontrolled).
16+
17+
### Files Updated:
18+
19+
- `apps/web/content/docs/components/datepicker.mdx`: Added controlled usage section.
20+
- `Datepicker.spec.tsx`: Added unit tests.
21+
- `Datepicker.stories.tsx`: Enhanced story variants.
22+
- `Datepicker.tsx`: Expanded `DatepickerProps`.
23+
- `DatepickerContext.tsx`: Adjusted `selectedDate` type.
24+
- `Decades.tsx`, `Months.tsx`, `Years.tsx`: Updated logic to check for `selectedDate`.
25+
26+
- [#1484](https://github.com/themesberg/flowbite-react/pull/1484) [`38913e5`](https://github.com/themesberg/flowbite-react/commit/38913e51536ecf1c8e924a6de571880cb91d2ea0) Thanks [@KRTirtho](https://github.com/KRTirtho)! - fix: autocomplete for string enums with dynamic value not working
27+
328
## 0.10.1
429

530
### Patch Changes

packages/ui/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "flowbite-react",
3-
"version": "0.10.1",
3+
"version": "0.10.2",
44
"description": "Official React components built for Flowbite and Tailwind CSS",
55
"keywords": [
66
"design-system",

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

+10-10
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

+92-5
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)