(react) introduce a reusable common DatePicker component

For both mono date and range date selection, create a versatile
DatePickerAux component that shares common logics, between both
selection mode.
This commit is contained in:
Lebaud Antoine
2023-06-16 16:57:51 +02:00
committed by aleb_the_flash
parent 0dae71aa92
commit 0775490a60
2 changed files with 178 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
---
"@openfun/cunningham-react": minor
---
Introduce a common DatePickerAux component

View File

@@ -0,0 +1,173 @@
import React, {
forwardRef,
PropsWithChildren,
Ref,
useMemo,
useRef,
} from "react";
import {
DateRangePickerState,
DatePickerState,
} from "@react-stately/datepicker";
import { DateRangePickerAria, DatePickerAria } from "@react-aria/datepicker";
import classNames from "classnames";
import { Button } from ":/components/Button";
import { Popover } from ":/components/Popover";
import { Field, FieldProps } from ":/components/Forms/Field";
import { useCunningham } from ":/components/Provider";
import { StringOrDate } from ":/components/Forms/DatePicker/types";
import {
Calendar,
CalendarRange,
} from ":/components/Forms/DatePicker/Calendar";
export type DatePickerAuxSubProps = FieldProps & {
label?: string;
minValue?: StringOrDate;
maxValue?: StringOrDate;
disabled?: boolean;
name?: string;
};
export type DatePickerAuxProps = PropsWithChildren &
DatePickerAuxSubProps & {
pickerState: DateRangePickerState | DatePickerState;
pickerProps: Pick<
DateRangePickerAria | DatePickerAria,
"buttonProps" | "groupProps"
>;
calendar: React.ReactElement<typeof Calendar | typeof CalendarRange>;
isFocused: boolean;
labelAsPlaceholder: boolean;
optionalClassName?: string;
onClear: () => void;
};
/**
* This component is used by date and date range picker components.
* It contains the common logic between the two.
*/
const DatePickerAux = forwardRef(
(
{
pickerState,
pickerProps,
onClear,
isFocused,
labelAsPlaceholder,
calendar,
children,
name,
disabled = false,
optionalClassName,
...props
}: DatePickerAuxProps,
ref: Ref<HTMLDivElement>
) => {
const { t } = useCunningham();
const pickerRef = useRef<HTMLDivElement>(null);
const isDateInvalid = useMemo(
() =>
pickerState.validationState === "invalid" || props.state === "error",
[pickerState.validationState, props.state]
);
// onPress props don't exist on the <Button /> component.
// Remove it to avoid any warning.
const { onPress: onPressToggleButton, ...otherButtonProps } =
pickerProps.buttonProps;
return (
<Field {...props}>
<div
ref={pickerRef}
className={classNames(["c__date-picker", optionalClassName], {
"c__date-picker--disabled": disabled,
"c__date-picker--invalid": isDateInvalid,
"c__date-picker--success": props.state === "success",
"c__date-picker--focused":
!isDateInvalid && !disabled && (pickerState.isOpen || isFocused),
})}
>
<div
className={classNames("c__date-picker__wrapper", {
"c__date-picker__wrapper--clickable": labelAsPlaceholder,
})}
ref={ref}
{...pickerProps.groupProps}
role="button"
tabIndex={0}
onClick={() => !pickerState.isOpen && pickerState.open()}
>
{"dateRange" in pickerState ? (
<>
<input
type="hidden"
name={name && `${name}_start`}
value={pickerState.value?.start?.toString() || ""}
/>
<input
type="hidden"
name={name && `${name}_end`}
value={pickerState.value?.end?.toString() || ""}
/>
</>
) : (
<input
type="hidden"
name={name}
value={pickerState.value?.toString() || ""}
/>
)}
<div className="c__date-picker__wrapper__icon">
<Button
{...{
...otherButtonProps,
"aria-label": t(
pickerState.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={pickerState.toggle}
icon={<span className="material-icons">calendar_today</span>}
disabled={disabled}
/>
</div>
{children}
<Button
className={classNames("c__date-picker__inner__action", {
"c__date-picker__inner__action--empty": !pickerState.value,
"c__date-picker__inner__action--hidden":
labelAsPlaceholder || disabled,
})}
color="tertiary"
size="small"
icon={<span className="material-icons">cancel</span>}
onClick={onClear}
aria-label={t(
"components.forms.date_picker.clear_button_aria_label"
)}
disabled={disabled}
/>
</div>
{pickerState.isOpen && (
<Popover
parentRef={pickerRef}
onClickOutside={pickerState.close}
borderless
>
{calendar}
</Popover>
)}
</div>
</Field>
);
}
);
export default DatePickerAux;