♻️(react) refactor DatePicker component
Refactor the original DatePicker component to utilize the newly created shared common DatePickerAux component. This modification enhances code consistency, and promotes the reuse of the DatePickerAux across date inputs variants.
This commit is contained in:
committed by
aleb_the_flash
parent
0d6b98ee1f
commit
114d0b5f2e
@@ -1,192 +1 @@
|
||||
import React, { PropsWithChildren, useMemo, useRef, useState } from "react";
|
||||
import {
|
||||
CalendarDate,
|
||||
DateValue,
|
||||
parseAbsoluteToLocal,
|
||||
toCalendarDate,
|
||||
} from "@internationalized/date";
|
||||
import {
|
||||
DatePickerStateOptions,
|
||||
useDatePickerState,
|
||||
} from "@react-stately/datepicker";
|
||||
import { useDatePicker } from "@react-aria/datepicker";
|
||||
import classNames from "classnames";
|
||||
import { Button } from ":/components/Button";
|
||||
import { Field, FieldProps } from ":/components/Forms/Field";
|
||||
import { LabelledBox } from ":/components/Forms/LabelledBox";
|
||||
import { DateField } from ":/components/Forms/DatePicker/DateField";
|
||||
import { Calendar } from ":/components/Forms/DatePicker/Calendar";
|
||||
import { Popover } from ":/components/Popover";
|
||||
import { useCunningham } from ":/components/Provider";
|
||||
|
||||
type DatePickerProps = PropsWithChildren &
|
||||
FieldProps & {
|
||||
label: string;
|
||||
name?: string;
|
||||
value?: null | Date | string;
|
||||
defaultValue?: Date | string;
|
||||
minValue?: Date | string;
|
||||
maxValue?: Date | string;
|
||||
onChange?: (value: string) => void | undefined;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export const DatePicker = ({
|
||||
label,
|
||||
name,
|
||||
disabled = false,
|
||||
...props
|
||||
}: DatePickerProps) => {
|
||||
if (props.defaultValue && props.value) {
|
||||
throw new Error(
|
||||
"You cannot use both defaultValue and value props on DatePicker component"
|
||||
);
|
||||
}
|
||||
|
||||
const parseCalendarDate = (
|
||||
rawDate: Date | string | undefined
|
||||
): undefined | CalendarDate => {
|
||||
if (!rawDate) {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
const ISODateString = new Date(rawDate).toISOString();
|
||||
return toCalendarDate(parseAbsoluteToLocal(ISODateString));
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
"Invalid date format when initializing props on DatePicker component"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const datePickerOptions: DatePickerStateOptions<DateValue> = {
|
||||
value:
|
||||
// Force clear the component's value when passing null or an empty string.
|
||||
props.value === "" || props.value === null
|
||||
? null
|
||||
: parseCalendarDate(props.value),
|
||||
defaultValue: parseCalendarDate(props.defaultValue),
|
||||
minValue: parseCalendarDate(props.minValue),
|
||||
maxValue: parseCalendarDate(props.maxValue),
|
||||
onChange: (value: DateValue | null) => {
|
||||
props.onChange?.(value?.toString() || "");
|
||||
},
|
||||
shouldCloseOnSelect: true,
|
||||
granularity: "day",
|
||||
isDisabled: disabled,
|
||||
label,
|
||||
};
|
||||
|
||||
const state = useDatePickerState(datePickerOptions);
|
||||
const pickerRef = useRef<HTMLDivElement>(null);
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
const { t } = useCunningham();
|
||||
|
||||
const { fieldProps, buttonProps, groupProps, calendarProps } = useDatePicker(
|
||||
datePickerOptions,
|
||||
state,
|
||||
wrapperRef
|
||||
);
|
||||
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
|
||||
const labelAsPlaceholder = useMemo(
|
||||
() => !state.isOpen && !state.value,
|
||||
[state.value, state.isOpen]
|
||||
);
|
||||
|
||||
const isDateInvalid = useMemo(
|
||||
() => state.validationState === "invalid" || props.state === "error",
|
||||
[state.validationState, props.state]
|
||||
);
|
||||
|
||||
// onPress props don't exist on the <Button /> component.
|
||||
// Remove it to avoid any warning.
|
||||
const { onPress: onPressToggleButton, ...otherButtonProps } = buttonProps;
|
||||
|
||||
return (
|
||||
<Field {...props}>
|
||||
<div
|
||||
ref={pickerRef}
|
||||
className={classNames("c__date-picker", {
|
||||
"c__date-picker--disabled": disabled,
|
||||
"c__date-picker--invalid": isDateInvalid,
|
||||
"c__date-picker--success": props.state === "success",
|
||||
"c__date-picker--focused":
|
||||
!isDateInvalid && !disabled && (state.isOpen || isFocused),
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={classNames("c__date-picker__wrapper", {
|
||||
"c__date-picker__wrapper--clickable": labelAsPlaceholder,
|
||||
})}
|
||||
ref={wrapperRef}
|
||||
{...groupProps}
|
||||
aria-label={label}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => !state.isOpen && state.open()}
|
||||
>
|
||||
{state.value && (
|
||||
<input type="hidden" name={name} value={state.value?.toString()} />
|
||||
)}
|
||||
<div className="c__date-picker__wrapper__icon">
|
||||
<Button
|
||||
{...{
|
||||
...otherButtonProps,
|
||||
"aria-label": t(
|
||||
state.isOpen
|
||||
? "components.forms.date_picker.toggle_button_aria_label_close"
|
||||
: "components.forms.date_picker.toggle_button_aria_label_open"
|
||||
),
|
||||
}}
|
||||
color="tertiary"
|
||||
size="small"
|
||||
className="c__date-picker__wrapper__toggle"
|
||||
onClick={state.toggle}
|
||||
icon={<span className="material-icons">calendar_today</span>}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
<LabelledBox label={label} labelAsPlaceholder={labelAsPlaceholder}>
|
||||
<div className="c__date-picker__inner">
|
||||
{!labelAsPlaceholder && (
|
||||
<DateField
|
||||
{...{
|
||||
...fieldProps,
|
||||
onFocusChange: (focus) => setIsFocused(focus),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</LabelledBox>
|
||||
<Button
|
||||
className={classNames("c__date-picker__inner__action", {
|
||||
"c__date-picker__inner__action--empty": !state.value,
|
||||
"c__date-picker__inner__action--hidden":
|
||||
labelAsPlaceholder || disabled,
|
||||
})}
|
||||
color="tertiary"
|
||||
size="small"
|
||||
icon={<span className="material-icons">cancel</span>}
|
||||
// Intentionally pass a null value to reset date picker state
|
||||
onClick={() => state.setValue(null as unknown as DateValue)}
|
||||
aria-label={t(
|
||||
"components.forms.date_picker.clear_button_aria_label"
|
||||
)}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
{state.isOpen && (
|
||||
<Popover
|
||||
parentRef={pickerRef}
|
||||
onClickOutside={state.close}
|
||||
borderless
|
||||
>
|
||||
<Calendar {...calendarProps} />
|
||||
</Popover>
|
||||
)}
|
||||
</div>
|
||||
</Field>
|
||||
);
|
||||
};
|
||||
// todo : what do we export ?
|
||||
|
||||
Reference in New Issue
Block a user