Skip to content

Commit 05eb954

Browse files
committed
rework DateField
1 parent 84eb9f4 commit 05eb954

File tree

5 files changed

+95
-222
lines changed

5 files changed

+95
-222
lines changed

packages/bento-design-system/src/DateField/BaseDateInput.tsx

+8-10
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,23 @@ import { Calendar } from "./Calendar";
77
import { Box } from "../Box/Box";
88
import { Inline } from "../Layout/Inline";
99
import { Button, FieldProps } from "..";
10-
import { ShortcutProps } from "./types";
10+
import { SingleDateProps, RangeDateProps } from "./types";
1111

12-
type SingleDateProps = {
13-
type: "single";
14-
shortcuts?: ShortcutProps<Date | null>[];
12+
type BaseSingleDateProps = SingleDateProps & {
1513
datePickerAria: DatePickerAria;
1614
datePickerState: DatePickerState;
1715
} & Pick<FieldProps<Date | null>, "onChange" | "value">;
1816

19-
type RangeDateProps = {
20-
type: "range";
21-
shortcuts?: ShortcutProps<[Date, Date] | null>[];
17+
type BaseRangeDateProps = RangeDateProps & {
2218
dateRangePickerAria: DateRangePickerAria;
2319
dateRangePickerState: DateRangePickerState;
2420
} & Pick<FieldProps<[Date, Date] | null>, "onChange" | "value">;
2521

26-
type Props = (SingleDateProps | RangeDateProps) & { inputRef: React.RefObject<HTMLInputElement> };
22+
type Props = (BaseSingleDateProps | BaseRangeDateProps) & {
23+
inputRef: React.RefObject<HTMLInputElement>;
24+
};
2725

28-
function BaseSingleDateInput(props: Extract<Props, { type: "single" }>) {
26+
function BaseSingleDateInput(props: Extract<Props, { type?: "single" }>) {
2927
const shortcuts = props.shortcuts && (
3028
<Inline space={4}>
3129
{props.shortcuts.map((shortcut) => (
@@ -114,7 +112,7 @@ function BaseRangeDateInput(props: Extract<Props, { type: "range" }>) {
114112

115113
export function BaseDateInput(props: Props) {
116114
return match(props)
117-
.with({ type: "single" }, (props) => <BaseSingleDateInput {...props} />)
115+
.with({ type: "single" }, { type: undefined }, (props) => <BaseSingleDateInput {...props} />)
118116
.with({ type: "range" }, (props) => <BaseRangeDateInput {...props} />)
119117
.exhaustive();
120118
}
Original file line numberDiff line numberDiff line change
@@ -1,163 +1,71 @@
11
import { useDatePicker, useDateRangePicker } from "@react-aria/datepicker";
22
import { useDatePickerState, useDateRangePickerState } from "@react-stately/datepicker";
33
import { useRef } from "react";
4+
import { match } from "ts-pattern";
45
import { FieldProps } from "../Field/FieldProps";
56
import { CalendarDate, DateValue, getLocalTimeZone } from "@internationalized/date";
6-
import { Input } from "./Input";
7-
import { Calendar } from "./Calendar";
8-
import { Box } from "../Box/Box";
7+
import { BaseDateInput } from "./BaseDateInput";
98
import { Field } from "../Field/Field";
10-
import { LocalizedString } from "../util/ConfigurableTypes";
119
import { RangeValue } from "@react-types/shared";
12-
import { Inline } from "../Layout/Inline";
13-
import { Button } from "..";
10+
import { DateProps, SingleDateProps, RangeDateProps } from "./types";
11+
import { dateToCalendarDate } from "./utils";
1412

15-
export type ShortcutProps<Value> = {
16-
label: LocalizedString;
17-
value: Value;
18-
};
13+
type SingleDateFieldProps = SingleDateProps & FieldProps<Date | null> & DateProps;
14+
type RangeDateFieldProps = RangeDateProps & FieldProps<[Date, Date] | null> & DateProps;
1915

20-
type StandaloneProps<T> = Pick<
21-
FieldProps<T>,
22-
"autoFocus" | "disabled" | "name" | "onBlur" | "onChange" | "value"
23-
>;
24-
25-
type SingleDateProps = { type?: "single"; shortcuts?: ShortcutProps<Date | null>[] };
26-
type RangeDateProps = { type: "range"; shortcuts?: ShortcutProps<[Date, Date] | null>[] };
27-
28-
type SingleDateFieldProps = SingleDateProps & FieldProps<Date | null>;
29-
type SingleDateStandaloneProps = SingleDateProps & StandaloneProps<Date | null>;
30-
31-
type RangeDateFieldProps = RangeDateProps & FieldProps<[Date, Date] | null>;
32-
type RangeDateStandaloneProps = RangeDateProps & StandaloneProps<[Date, Date] | null>;
33-
34-
type DateProps = {
35-
minDate?: Date;
36-
maxDate?: Date;
37-
shouldDisableDate?: (date: Date) => boolean;
38-
readOnly?: boolean;
39-
};
40-
41-
type PublicDateFieldProps = (SingleDateFieldProps | RangeDateFieldProps) & DateProps;
42-
type PublicDateInputProps = (SingleDateStandaloneProps | RangeDateStandaloneProps) & DateProps;
43-
44-
type InternalDateProps =
45-
| ({
46-
isStandalone: true;
47-
} & PublicDateInputProps)
48-
| ({
49-
isStandalone?: false;
50-
} & PublicDateFieldProps);
51-
52-
function dateToCalendarDate(date: Date): CalendarDate {
53-
return new CalendarDate(date.getFullYear(), date.getMonth() + 1, date.getDate());
54-
}
55-
56-
function SingleDate({
57-
disabled,
58-
readOnly,
59-
...props
60-
}: Extract<InternalDateProps, { type?: "single" }>) {
16+
function SingleDateField(props: SingleDateFieldProps) {
6117
const localTimeZone = getLocalTimeZone();
18+
const ref = useRef(null);
19+
6220
const internalProps = {
6321
...props,
6422
value: props.value ? dateToCalendarDate(props.value) : props.value,
6523
onChange: (date: CalendarDate | null) => {
6624
props.onChange(date?.toDate(localTimeZone) ?? null);
6725
},
68-
isDisabled: disabled,
69-
isReadOnly: readOnly,
70-
validationState: !props.isStandalone && props.issues ? "invalid" : "valid",
26+
isDisabled: props.disabled,
27+
isReadOnly: props.isReadOnly ?? props.readOnly,
28+
validationState: props.issues ? "invalid" : "valid",
7129
minValue: props.minDate ? dateToCalendarDate(props.minDate) : undefined,
7230
maxValue: props.maxDate ? dateToCalendarDate(props.maxDate) : undefined,
7331
isDateUnavailable: props.shouldDisableDate
7432
? (date: DateValue) => props.shouldDisableDate!(date.toDate(localTimeZone))
7533
: undefined,
7634
shouldForceLeadingZeros: true,
35+
onBlur: props.onBlur,
36+
autoFocus: props.autoFocus,
7737
} as const;
78-
const state = useDatePickerState(internalProps);
79-
const ref = useRef(null);
80-
const {
81-
groupProps,
82-
labelProps,
83-
fieldProps,
84-
buttonProps,
85-
descriptionProps,
86-
errorMessageProps,
87-
calendarProps,
88-
} = useDatePicker(internalProps, state, ref);
8938

90-
const shortcuts = props.shortcuts && (
91-
<Inline space={4}>
92-
{props.shortcuts.map((shortcut) => (
93-
<Button
94-
key={shortcut.label}
95-
kind="transparent"
96-
hierarchy="secondary"
97-
size="small"
98-
label={shortcut.label}
99-
onPress={() => {
100-
props.onChange(shortcut.value);
101-
state.close();
102-
}}
103-
/>
104-
))}
105-
</Inline>
106-
);
39+
const datePickerState = useDatePickerState(internalProps);
40+
const datePickerAria = useDatePicker(internalProps, datePickerState, ref);
10741

108-
const datePicker = (
109-
<>
110-
<Box {...groupProps} ref={ref}>
111-
<Input
112-
type="single"
113-
fieldProps={fieldProps}
114-
buttonProps={buttonProps}
115-
isCalendarOpen={state.isOpen}
116-
/>
117-
</Box>
118-
{state.isOpen && (
119-
<Calendar
120-
type="single"
121-
{...calendarProps}
122-
inputRef={ref}
123-
onClose={state.close}
124-
shortcuts={shortcuts}
125-
/>
126-
)}
127-
</>
128-
);
129-
130-
return props.isStandalone ? (
131-
datePicker
132-
) : (
42+
return (
13343
<Field
13444
{...props}
135-
disabled={disabled}
136-
labelProps={labelProps}
137-
assistiveTextProps={descriptionProps}
138-
errorMessageProps={errorMessageProps}
45+
disabled={props.disabled}
46+
labelProps={datePickerAria.labelProps}
47+
assistiveTextProps={datePickerAria.descriptionProps}
48+
errorMessageProps={datePickerAria.errorMessageProps}
13949
>
140-
{datePicker}
50+
<BaseDateInput
51+
type="single"
52+
value={props.value}
53+
onChange={props.onChange}
54+
shortcuts={props.shortcuts}
55+
datePickerAria={datePickerAria}
56+
datePickerState={datePickerState}
57+
inputRef={ref}
58+
/>
14159
</Field>
14260
);
14361
}
14462

145-
function RangeDateField({
146-
disabled,
147-
readOnly,
148-
...props
149-
}: Extract<InternalDateProps, { type: "range" }>) {
63+
function RangeDateField(props: RangeDateFieldProps) {
15064
const localTimeZone = getLocalTimeZone();
65+
const ref = useRef(null);
66+
15167
const internalProps = {
15268
...props,
153-
isDisabled: disabled,
154-
isReadOnly: readOnly,
155-
validationState: !props.isStandalone && props.issues ? "invalid" : "valid",
156-
minValue: props.minDate ? dateToCalendarDate(props.minDate) : undefined,
157-
maxValue: props.maxDate ? dateToCalendarDate(props.maxDate) : undefined,
158-
isDateUnavailable: props.shouldDisableDate
159-
? (date: DateValue) => props.shouldDisableDate!(date.toDate(localTimeZone))
160-
: undefined,
16169
value: props.value
16270
? {
16371
start: dateToCalendarDate(props.value[0]),
@@ -171,86 +79,47 @@ function RangeDateField({
17179
props.onChange([range.start.toDate(localTimeZone), range.end.toDate(localTimeZone)]);
17280
}
17381
},
82+
isDisabled: props.disabled,
83+
isReadOnly: props.isReadOnly ?? props.readOnly,
84+
validationState: props.issues ? "invalid" : "valid",
85+
minValue: props.minDate ? dateToCalendarDate(props.minDate) : undefined,
86+
maxValue: props.maxDate ? dateToCalendarDate(props.maxDate) : undefined,
87+
isDateUnavailable: props.shouldDisableDate
88+
? (date: DateValue) => props.shouldDisableDate!(date.toDate(localTimeZone))
89+
: undefined,
17490
shouldForceLeadingZeros: true,
91+
onBlur: props.onBlur,
92+
autoFocus: props.autoFocus,
17593
} as const;
176-
const state = useDateRangePickerState(internalProps);
177-
const ref = useRef(null);
178-
const {
179-
groupProps,
180-
labelProps,
181-
buttonProps,
182-
descriptionProps,
183-
errorMessageProps,
184-
calendarProps,
185-
startFieldProps,
186-
endFieldProps,
187-
} = useDateRangePicker(internalProps, state, ref);
188-
189-
const shortcuts = props.shortcuts && (
190-
<Inline space={4}>
191-
{props.shortcuts.map((shortcut) => (
192-
<Button
193-
key={shortcut.label}
194-
kind="transparent"
195-
hierarchy="secondary"
196-
size="small"
197-
label={shortcut.label}
198-
onPress={() => {
199-
props.onChange(shortcut.value);
200-
state.close();
201-
}}
202-
/>
203-
))}
204-
</Inline>
205-
);
20694

207-
const datePicker = (
208-
<>
209-
<Box {...groupProps} ref={ref}>
210-
<Input
211-
type="range"
212-
fieldProps={{ start: startFieldProps, end: endFieldProps }}
213-
buttonProps={buttonProps}
214-
isCalendarOpen={state.isOpen}
215-
/>
216-
</Box>
217-
{state.isOpen && (
218-
<Calendar
219-
type="range"
220-
{...calendarProps}
221-
inputRef={ref}
222-
onClose={state.close}
223-
shortcuts={shortcuts}
224-
/>
225-
)}
226-
</>
227-
);
95+
const rangeDatePickerState = useDateRangePickerState(internalProps);
96+
const rangeDatePickerAria = useDateRangePicker(internalProps, rangeDatePickerState, ref);
22897

229-
return props.isStandalone ? (
230-
datePicker
231-
) : (
98+
return (
23299
<Field
233100
{...props}
234-
disabled={disabled}
235-
labelProps={labelProps}
236-
assistiveTextProps={descriptionProps}
237-
errorMessageProps={errorMessageProps}
101+
disabled={props.disabled}
102+
labelProps={rangeDatePickerAria.labelProps}
103+
assistiveTextProps={rangeDatePickerAria.descriptionProps}
104+
errorMessageProps={rangeDatePickerAria.errorMessageProps}
238105
>
239-
{datePicker}
106+
<BaseDateInput
107+
type="range"
108+
value={props.value}
109+
onChange={props.onChange}
110+
shortcuts={props.shortcuts}
111+
dateRangePickerAria={rangeDatePickerAria}
112+
dateRangePickerState={rangeDatePickerState}
113+
inputRef={ref}
114+
/>
240115
</Field>
241116
);
242117
}
243118

244-
export function DateField(props: PublicDateFieldProps) {
245-
return props.type === "range" ? <RangeDateField {...props} /> : <SingleDate {...props} />;
119+
export function DateField(props: SingleDateFieldProps | RangeDateFieldProps) {
120+
// @ts-ignore
121+
return match(props)
122+
.with({ type: "single" }, { type: undefined }, (props) => <SingleDateField {...props} />)
123+
.with({ type: "range" }, (props) => <RangeDateField {...props} />)
124+
.exhaustive();
246125
}
247-
248-
export function DateInput(props: PublicDateInputProps) {
249-
return props.type === "range" ? (
250-
<RangeDateField {...props} isStandalone />
251-
) : (
252-
<SingleDate {...props} isStandalone />
253-
);
254-
}
255-
256-
export type { PublicDateFieldProps as DateFieldProps, PublicDateInputProps as DateInputProps };

0 commit comments

Comments
 (0)