diff --git a/packages/bento-design-system/src/DateField/DateField.tsx b/packages/bento-design-system/src/DateField/DateField.tsx index a033c3688..12c0c8cd0 100644 --- a/packages/bento-design-system/src/DateField/DateField.tsx +++ b/packages/bento-design-system/src/DateField/DateField.tsx @@ -2,7 +2,7 @@ import { useDatePicker, useDateRangePicker } from "@react-aria/datepicker"; import { useDatePickerState, useDateRangePickerState } from "@react-stately/datepicker"; import { useRef } from "react"; import { FieldProps } from "../Field/FieldProps"; -import { CalendarDate, DateValue } from "@internationalized/date"; +import { CalendarDate, DateValue, getLocalTimeZone } from "@internationalized/date"; import { Input } from "./Input"; import { Calendar } from "./Calendar"; import { Box } from "../Box/Box"; @@ -18,27 +18,36 @@ export type ShortcutProps = { }; type SingleDateFieldProps = { type?: "single"; - shortcuts?: ShortcutProps[]; -} & FieldProps; + shortcuts?: ShortcutProps[]; +} & FieldProps; type RangeDateFieldProps = { type: "range"; - shortcuts?: ShortcutProps<[CalendarDate, CalendarDate]>[]; -} & FieldProps<[CalendarDate, CalendarDate] | null>; + shortcuts?: ShortcutProps<[Date, Date]>[]; +} & FieldProps<[Date, Date] | null>; type Props = (SingleDateFieldProps | RangeDateFieldProps) & { - minDate?: CalendarDate; - maxDate?: CalendarDate; + minDate?: Date; + maxDate?: Date; shouldDisableDate?: (date: DateValue) => boolean; readOnly?: boolean; }; +function dateToCalendarDate(date: Date): CalendarDate { + if (!date) return date; + return new CalendarDate(date.getFullYear(), date.getMonth() + 1, date.getDate()); +} + function SingleDateField({ disabled, readOnly, ...props }: Extract) { const internalProps = { ...props, + value: props.value ? dateToCalendarDate(props.value) : props.value, + onChange: (date: CalendarDate | null) => { + props.onChange(date?.toDate(getLocalTimeZone()) ?? null); + }, isDisabled: disabled, isReadOnly: readOnly, validationState: props.issues ? "invalid" : "valid", - minValue: props.minDate, - maxValue: props.maxDate, + minValue: props.minDate ? dateToCalendarDate(props.minDate) : undefined, + maxValue: props.maxDate ? dateToCalendarDate(props.maxDate) : undefined, isDateUnavailable: props.shouldDisableDate, shouldForceLeadingZeros: true, } as const; @@ -85,7 +94,6 @@ function SingleDateField({ disabled, readOnly, ...props }: Extract @@ -108,17 +116,18 @@ function RangeDateField({ disabled, readOnly, ...props }: Extract) => { - props.onChange([range.start, range.end]); + const localTimeZone = getLocalTimeZone(); + props.onChange([range.start.toDate(localTimeZone), range.end.toDate(localTimeZone)]); }, } as const; const state = useDateRangePickerState(internalProps); @@ -165,7 +174,6 @@ function RangeDateField({ disabled, readOnly, ...props }: Extract diff --git a/packages/bento-design-system/src/DateField/Input.tsx b/packages/bento-design-system/src/DateField/Input.tsx index de12104e8..e3ab6c1fd 100644 --- a/packages/bento-design-system/src/DateField/Input.tsx +++ b/packages/bento-design-system/src/DateField/Input.tsx @@ -6,7 +6,7 @@ import { DateSegment as DateSegmentType, useDateFieldState, } from "@react-stately/datepicker"; -import { RefObject, useRef } from "react"; +import { useRef } from "react"; import { Box } from "../Box/Box"; import { inputRecipe } from "../Field/Field.css"; import { bodyRecipe } from "../Typography/Body/Body.css"; @@ -36,7 +36,6 @@ type Props = ( ) & { buttonProps: AriaButtonProps<"button">; isCalendarOpen: boolean; - ref: RefObject; }; function DateSegment({ segment, state }: { segment: DateSegmentType; state: DateFieldState }) { diff --git a/packages/bento-design-system/stories/Components/DateField.stories.tsx b/packages/bento-design-system/stories/Components/DateField.stories.tsx index 9b4087f82..999c29ccc 100644 --- a/packages/bento-design-system/stories/Components/DateField.stories.tsx +++ b/packages/bento-design-system/stories/Components/DateField.stories.tsx @@ -1,38 +1,23 @@ -import { DateField, DateFieldProps } from "../../src/DateField/DateField"; -import { Meta, StoryObj } from "@storybook/react"; -import { useState } from "react"; +import { DateField, DateFieldProps } from ".."; import { - CalendarDate, - getLocalTimeZone, - today as _today, - getDayOfWeek, startOfWeek, endOfWeek, startOfMonth, endOfMonth, startOfYear, endOfYear, -} from "@internationalized/date"; -import isChromatic from "chromatic"; + addMonths, + startOfToday, + addWeeks, + addDays, +} from "date-fns"; import { screen, waitFor } from "@storybook/testing-library"; - -const ControlledDateField = (props: Omit) => { - const [value, setValue] = useState(props.value); - return ( - { - setValue(value); - }} - /> - ); -}; +import isChromatic from "chromatic/isChromatic"; +import { Meta, StoryObj } from "@storybook/react"; const meta = { - component: ControlledDateField, + component: DateField, args: { - type: "single", name: "date", label: "Date", assistiveText: "This is your favorite date", @@ -44,40 +29,45 @@ export default meta; type Story = StoryObj; -const today = _today(getLocalTimeZone()); +const today = startOfToday(); +const value = new Date(2022, 1, 4); -export const SingleDate = {} satisfies Story; +export const SingleDate = { + value, +} satisfies Story; export const Disabled = { args: { + value: null, disabled: true, }, } satisfies Story; export const ReadOnly = { args: { - value: new CalendarDate(2022, 2, 4), + value, readOnly: true, }, } satisfies Story; -const inOneWeek = today.add({ weeks: 1 }); +const inOneWeek = addWeeks(today, 1); export const SingleWithMinMax = { args: { + value: null, minDate: today, maxDate: inOneWeek, assistiveText: "You can select a date between today and one week from now", }, } satisfies Story; -const inOneMonth = today.add({ months: 1 }); +const inOneMonth = addMonths(today, 1); export const SingleWithShortcuts = { args: { value: null, shortcuts: [ { label: "Today", - value: today, + value: new Date(), }, { label: "In a week", @@ -92,18 +82,16 @@ export const SingleWithShortcuts = { } satisfies Story; export const DisabledDates = { args: { - shouldDisableDate: (date: CalendarDate) => getDayOfWeek(date, "en-EN") === 0, + shouldDisableDate: (date: Date) => date.getDay() === 0, }, }; export const CalendarOpen = { args: { - value: new CalendarDate(2022, 2, 4), + value, }, play: async () => { - const button = screen.getByRole("button"); - await waitFor(async () => { - await button.click(); - }); + const input = screen.getByRole("textbox"); + await waitFor(() => input.click()); // wait a bit to see if it solves Chromatic snapshot flakiness await new Promise((resolve) => setTimeout(resolve, 1000)); }, @@ -120,10 +108,11 @@ export const CalendarOpen = { export const Range = { args: { - value: [today, today.add({ days: 2 })], + value: [value, addDays(value, 2)], type: "range", }, } satisfies Story; + export const RangeWithMinMax = { args: { value: [null, null], @@ -133,6 +122,7 @@ export const RangeWithMinMax = { assistiveText: "You can select a date between today and one month from now", }, } satisfies Story; + export const RangeWithShortcuts = { args: { value: [null, null], @@ -140,7 +130,7 @@ export const RangeWithShortcuts = { shortcuts: [ { label: "This Week", - value: [startOfWeek(today, "en-EN"), endOfWeek(today, "en-EN")], + value: [startOfWeek(today), endOfWeek(today)], }, { label: "This Month",