Skip to content

Commit 3028f83

Browse files
authored
feat: Add 'filterDate' prop function on Datepicker (#1587)
* feat: Implement a filter function for the Datepicker component * feat: Update the Datepicker documentation * fix: update filter function test to disable today's date * feat: enhance Datepicker documentation and example to include 'Decades' in Views enum * fix: update filter function test to disable tomorrow's date instead of today's * fix: use "use client" directive in Datepicker filter component web doc * fix: update filter function to compare dates by string representation for tomorrow's date * chore: add changeset * fix: minor version to patch
1 parent 557d233 commit 3028f83

File tree

13 files changed

+169
-10
lines changed

13 files changed

+169
-10
lines changed

.changeset/hip-pants-run.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"flowbite-react": patch
3+
---
4+
5+
feat(Datepicker): Implemented a filter function prop

apps/storybook/src/Datepicker.stories.tsx

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Meta, StoryFn } from "@storybook/react";
22
import type { DatepickerProps } from "flowbite-react";
3-
import { Datepicker, getFirstDateInRange, WeekStart } from "flowbite-react";
3+
import { Datepicker, getFirstDateInRange, Views, WeekStart } from "flowbite-react";
44
import { useEffect, useState } from "react";
55

66
export default {
@@ -147,3 +147,48 @@ EmptyDates.args = {
147147
theme: {},
148148
label: "No date selected",
149149
};
150+
151+
export const FilterWeekdaysOnly = Template.bind({});
152+
FilterWeekdaysOnly.args = {
153+
open: true,
154+
autoHide: false,
155+
showClearButton: true,
156+
showTodayButton: true,
157+
defaultValue: undefined,
158+
value: undefined,
159+
minDate: undefined,
160+
maxDate: undefined,
161+
filterDate: (date: Date, view: Views) => {
162+
if (view === Views.Days) {
163+
const day = date.getDay();
164+
return day >= 1 && day <= 5;
165+
}
166+
return true;
167+
},
168+
language: "en",
169+
weekStart: WeekStart.Sunday,
170+
theme: {},
171+
label: "Filter: Weekdays only",
172+
};
173+
174+
export const FilterEvenDatesOnly = Template.bind({});
175+
FilterEvenDatesOnly.args = {
176+
open: true,
177+
autoHide: false,
178+
showClearButton: true,
179+
showTodayButton: true,
180+
defaultValue: undefined,
181+
value: undefined,
182+
minDate: undefined,
183+
maxDate: undefined,
184+
filterDate: (date: Date, view: Views) => {
185+
if (view === Views.Days) {
186+
return date.getDate() % 2 === 0;
187+
}
188+
return true;
189+
},
190+
language: "en",
191+
weekStart: WeekStart.Sunday,
192+
theme: {},
193+
label: "Filter: Even dates only",
194+
};

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ The `labelTodayButton` and `labelClearButton` can also be used to update the tex
2727

2828
<Example name="datepicker.localization" />
2929

30+
## Filter dates
31+
32+
You can use the `filterDate` prop to filter out specific dates from the datepicker component. This is useful if you want to disable certain dates or only allow selection of weekdays, for example.
33+
34+
The `Views` enum can be used to determine the current view of the datepicker, such as `Days`, `Months`, `Years` or `Decades`.
35+
36+
<Example name="datepicker.filter" />
37+
3038
## Limit the date
3139

3240
By using the `minDate` and `maxDate` props you can limit the date range that the user can select.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"use client";
2+
3+
import { Datepicker, Views } from "flowbite-react";
4+
import type { CodeData } from "~/components/code-demo";
5+
6+
const code = `
7+
"use client";
8+
9+
import { Datepicker, Views } from "flowbite-react";
10+
11+
export function Component() {
12+
const filterFn = (date: Date, view: Views) => {
13+
if (view === Views.Days) {
14+
const day = date.getDay();
15+
return day >= 1 && day <= 5;
16+
}
17+
return true;
18+
};
19+
20+
return <Datepicker filterDate={filterFn} />;
21+
}
22+
`;
23+
24+
export function Component() {
25+
const filterFn = (date: Date, view: Views) => {
26+
if (view === Views.Days) {
27+
const day = date.getDay();
28+
return day >= 1 && day <= 5;
29+
}
30+
return true;
31+
};
32+
33+
return <Datepicker filterDate={filterFn} />;
34+
}
35+
36+
export const filter: CodeData = {
37+
type: "single",
38+
code: {
39+
fileName: "index",
40+
language: "tsx",
41+
code,
42+
},
43+
githubSlug: "datepicker/datepicker.filter.tsx",
44+
component: <Component />,
45+
};

apps/web/examples/datepicker/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export { autoHide } from "./datepicker.autoHide";
22
export { inline } from "./datepicker.inline";
33
export { localization } from "./datepicker.localization";
4+
export { filter } from "./datepicker.filter";
45
export { range } from "./datepicker.range";
56
export { root } from "./datepicker.root";
67
export { title } from "./datepicker.title";

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useRef } from "react";
44
import { describe, expect, it, vi } from "vitest";
55
import type { DatepickerRef } from "./Datepicker";
66
import { Datepicker } from "./Datepicker";
7-
import { getFormattedDate } from "./helpers";
7+
import { getFormattedDate, Views } from "./helpers";
88

99
describe("Components / Datepicker", () => {
1010
it("should display today's date by default", () => {
@@ -185,6 +185,24 @@ describe("Components / Datepicker", () => {
185185
expect(outsideRange).toBeDisabled();
186186
});
187187

188+
it("the filter function should allow to disable a certain date", async () => {
189+
const tomorrow = new Date();
190+
tomorrow.setDate(new Date().getDate() + 1);
191+
192+
const filter = (date: Date, view: Views) => {
193+
if (view === Views.Days) {
194+
return date.toDateString() !== tomorrow.toDateString();
195+
}
196+
return true;
197+
};
198+
199+
render(<Datepicker filterDate={filter} />);
200+
201+
await userEvent.click(screen.getByRole("textbox"));
202+
203+
expect(screen.getByText(tomorrow.getDate())).toBeDisabled();
204+
});
205+
188206
it("should focus the input when ref.current.focus is called", () => {
189207
const {
190208
result: { current: ref },

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ export interface DatepickerProps
100100
labelTodayButton?: string;
101101
minDate?: Date;
102102
maxDate?: Date;
103+
filterDate?: (date: Date, view: Views) => boolean;
103104
language?: string;
104105
weekStart?: WeekStart;
105106
onChange?: (date: Date | null) => void;
@@ -127,6 +128,7 @@ export const Datepicker = forwardRef<DatepickerRef, DatepickerProps>((props, ref
127128
defaultValue,
128129
minDate,
129130
maxDate,
131+
filterDate,
130132
language = "en",
131133
weekStart = WeekStart.Sunday,
132134
className,
@@ -277,6 +279,7 @@ export const Datepicker = forwardRef<DatepickerRef, DatepickerProps>((props, ref
277279
language,
278280
minDate,
279281
maxDate,
282+
filterDate,
280283
weekStart,
281284
isOpen,
282285
setIsOpen,

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export interface DatepickerContextValue {
1010
weekStart: WeekStart;
1111
minDate?: Date;
1212
maxDate?: Date;
13+
filterDate?: (date: Date, view: Views) => boolean;
1314
isOpen?: boolean;
1415
setIsOpen: (isOpen: boolean) => void;
1516
view: Views;

packages/ui/src/components/Datepicker/Views/Days.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,15 @@
22

33
import { twMerge } from "../../../helpers/tailwind-merge";
44
import { useDatePickerContext } from "../DatepickerContext";
5-
import { addDays, getFirstDayOfTheMonth, getFormattedDate, getWeekDays, isDateEqual, isDateInRange } from "../helpers";
5+
import {
6+
addDays,
7+
getFirstDayOfTheMonth,
8+
getFormattedDate,
9+
getWeekDays,
10+
isDateEqual,
11+
isDateInRange,
12+
Views,
13+
} from "../helpers";
614

715
export interface DatepickerViewsDaysTheme {
816
header: {
@@ -25,6 +33,7 @@ export function DatepickerViewsDays() {
2533
weekStart,
2634
minDate,
2735
maxDate,
36+
filterDate,
2837
viewDate,
2938
selectedDate,
3039
changeSelectedDate,
@@ -51,7 +60,8 @@ export function DatepickerViewsDays() {
5160
const day = getFormattedDate(language, currentDate, { day: "numeric" });
5261

5362
const isSelected = selectedDate && isDateEqual(selectedDate, currentDate);
54-
const isDisabled = !isDateInRange(currentDate, minDate, maxDate);
63+
const isDisabled =
64+
!isDateInRange(currentDate, minDate, maxDate) || (filterDate && !filterDate(currentDate, Views.Days));
5565

5666
return (
5767
<button

packages/ui/src/components/Datepicker/Views/Decades.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,16 @@ export interface DatepickerViewsDecadesTheme {
1616
}
1717

1818
export function DatepickerViewsDecades() {
19-
const { theme: rootTheme, viewDate, selectedDate, minDate, maxDate, setViewDate, setView } = useDatePickerContext();
19+
const {
20+
theme: rootTheme,
21+
viewDate,
22+
selectedDate,
23+
minDate,
24+
maxDate,
25+
filterDate,
26+
setViewDate,
27+
setView,
28+
} = useDatePickerContext();
2029

2130
const theme = rootTheme.views.decades;
2231
const first = startOfYearPeriod(viewDate, 100);
@@ -31,7 +40,9 @@ export function DatepickerViewsDecades() {
3140
const lastDate = addYears(firstDate, 9);
3241

3342
const isSelected = selectedDate && isDateInDecade(selectedDate, year);
34-
const isDisabled = !isDateInRange(firstDate, minDate, maxDate) && !isDateInRange(lastDate, minDate, maxDate);
43+
const isDisabled =
44+
(!isDateInRange(firstDate, minDate, maxDate) && !isDateInRange(lastDate, minDate, maxDate)) ||
45+
(filterDate && !filterDate(newDate, Views.Decades));
3546

3647
return (
3748
<button

0 commit comments

Comments
 (0)