Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/date value datepicker #1190

Merged
merged 39 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
6b779a5
feat(datepicker): accepts dateValue as value
ddiasfront Dec 14, 2023
c114f6e
chore(lint/prettier): cleaning code
ddiasfront Dec 14, 2023
9812a4c
removed unecessary debug
ddiasfront Dec 14, 2023
ef8d49a
test(fixing tests): finding why tests are not running
ddiasfront Dec 14, 2023
f33d2c6
chore: fixed testing for the use case scenario
ddiasfront Dec 14, 2023
1e84357
fix: removing commments
ddiasfront Dec 14, 2023
c48f026
chore(reducing complexity for datevalue): assertive state/context ma…
ddiasfront Dec 15, 2023
2f8c02c
chore: linting
ddiasfront Dec 15, 2023
e043936
feat: added necessary documentation
ddiasfront Dec 16, 2023
02cd518
Update Datepicker.spec.tsx
ddiasfront Dec 18, 2023
25478f0
chore: pipeline build fixes
ddiasfront Dec 18, 2023
df5968b
fix(clear button): fixed datepicker clear button to not displaying an…
ddiasfront Dec 19, 2023
f25bb18
Update Datepicker.spec.tsx
ddiasfront Dec 19, 2023
a0b94ba
test(datepicker): clear
ddiasfront Jan 1, 2024
3dd087b
docs(datepicker): it should have right type for the new empty date ca…
ddiasfront Dec 20, 2023
d8f572c
chore: removing unecessary console
ddiasfront Dec 28, 2023
7a3a85d
docs(datepicker): label naming
ddiasfront Jan 1, 2024
6c06037
feat(datepicker): datevalue expects null to clear date
ddiasfront Jan 1, 2024
ceb4ccf
feat(datepicker): datepicker should expect value and defaultValue
ddiasfront Jan 2, 2024
82d5f6a
feat(datepicker): datepicker default value and sideeffects
ddiasfront Jan 4, 2024
8f62e4c
feat: adding datevalue property
ddiasfront May 16, 2024
a1e915b
feat: handling date value with propper test cases
ddiasfront May 16, 2024
f66442d
feat: handling date value with propper test cases
ddiasfront May 16, 2024
799f456
feat: removed default value and renamed onchange prop
Aug 13, 2024
3583b13
feat: solved comments | updated tests and parameters
Aug 21, 2024
ac1d098
feat: ts compiling
Aug 21, 2024
6f7f6c6
feat: added null as a type reference for selectedDate
Aug 21, 2024
628dc14
feat: prettier
Aug 21, 2024
1e5a610
feat: added value as a modifier
Aug 27, 2024
92a826a
feat: updated storybook to reflect controlled and uncontrolled model
Aug 27, 2024
c9fb6c1
feat: removed debugger and renamed defaultDate to defaultValue
Aug 28, 2024
4459adf
feat: removed debugger and renamed defaultDate to defaultValue
Aug 28, 2024
6e477a5
feat: format check
Aug 28, 2024
3a62843
feat: addressing hook dependencies
Aug 28, 2024
10f0f56
feat: controlled component setup | clearn functionality
Aug 29, 2024
7ca4295
feat: remove debugger statement
Aug 29, 2024
4548a0c
feat: defaultDate empty and side-effects addressed
Sep 2, 2024
dfef19d
feat: added changeset
Sep 13, 2024
f082ca7
feat - changset to patch
Sep 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
ddiasfront marked this conversation as resolved.
Show resolved Hide resolved
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
Loading