(react) internationalize calendar system in DatePicker

Component was lacking some customization capabilities.
Using the browser calendar system wasn't enough modular.
This introduces a `locale` props, that allows a higher
customization of the component. By default, the calendar
system of the DatePicker is synchronized with the Cunningham
Provider.
This commit is contained in:
Lebaud Antoine
2023-06-26 18:55:29 +02:00
committed by aleb_the_flash
parent f03ef6a9e1
commit e1489b7fe0
2 changed files with 237 additions and 74 deletions

View File

@@ -1131,4 +1131,160 @@ describe("<DatePicker/>", () => {
await user.keyboard("{ArrowRight}"); await user.keyboard("{ArrowRight}");
expectFocusedMonthToBeEqual(nextMonthValue); expectFocusedMonthToBeEqual(nextMonthValue);
}); });
it("uses the locale props calendar system", async () => {
const user = userEvent.setup();
render(
<CunninghamProvider>
<DatePicker
label="Pick a date"
locale="hi-IN-u-ca-indian"
defaultValue="2023-06-25"
/>
</CunninghamProvider>
);
const input = (await screen.findAllByRole("button"))[0];
// Toggle button opens the calendar.
await user.click(input);
expectCalendarToBeOpen();
expectDateFieldToBeDisplayed();
// Make sure dateField is in the right locale
const dateFieldContent = screen.getByRole("presentation").textContent;
expect(dateFieldContent).eq("4/4/1945 शक");
// Make sure month is in the right locale
const focusedMonth = screen
.getByRole("combobox", {
name: "Select a month",
})!
.textContent?.replace("arrow_drop_down", "");
expect(focusedMonth).eq("आषाढ़");
// Make sure year is in the right locale
const focusedYear = screen
.getByRole("combobox", {
name: "Select a year",
})!
.textContent?.replace("arrow_drop_down", "");
expect(focusedYear).eq("1945 शक");
// Make sure weekdays are in the right locale
screen.getByText("रवि");
screen.getByText("सोम");
screen.getByText("मंगल");
screen.getByText("बुध");
screen.getByText("गुरु");
screen.getByText("शुक्र");
screen.getByText("शनि");
});
it("uses the cunningham provider props calendar systems", async () => {
const user = userEvent.setup();
render(
<CunninghamProvider currentLocale="fr-FR">
<DatePicker label="Pick a date" defaultValue="2023-06-25" />
</CunninghamProvider>
);
const input = (await screen.findAllByRole("button"))[0];
// Toggle button opens the calendar.
await user.click(input);
expectCalendarToBeOpen();
expectDateFieldToBeDisplayed();
// Make sure dateField is in the right locale
const dateFieldContent = screen.getByRole("presentation").textContent;
expect(dateFieldContent).eq("25/06/2023");
// Make sure month is in the right locale
const focusedMonth = screen
.getByRole("combobox", {
name: "Sélectionner un mois",
})!
.textContent?.replace("arrow_drop_down", "");
expect(focusedMonth).eq("juin");
// Make sure year is in the right locale
const focusedYear = screen
.getByRole("combobox", {
name: "Sélectionner une année",
})!
.textContent?.replace("arrow_drop_down", "");
expect(focusedYear).eq("2023");
// Make sure weekdays are in the right locale
screen.getByText("lun.");
screen.getByText("mar.");
screen.getByText("mer.");
screen.getByText("jeu.");
screen.getByText("ven.");
screen.getByText("sam.");
screen.getByText("dim.");
});
it("makes sure the locale props override the cunningham provider calendar system", async () => {
const user = userEvent.setup();
render(
<CunninghamProvider currentLocale="fr-FR">
<DatePicker
label="Pick a date"
defaultValue="2023-06-25"
locale="hi-IN-u-ca-indian"
/>
</CunninghamProvider>
);
const input = (await screen.findAllByRole("button"))[0];
// Toggle button opens the calendar.
await user.click(input);
expectCalendarToBeOpen();
expectDateFieldToBeDisplayed();
// Make sure dateField is in the right locale
const dateFieldContent = screen.getByRole("presentation").textContent;
expect(dateFieldContent).eq("4/4/1945 शक");
// Make sure month is in the right locale
// And aria-label uses the right translation.
const focusedMonth = screen
.getByRole("combobox", {
name: "Sélectionner un mois",
})!
.textContent?.replace("arrow_drop_down", "");
expect(focusedMonth).eq("आषाढ़");
// Make sure year is in the right locale
// And aria-label uses the right translation.
const focusedYear = screen
.getByRole("combobox", {
name: "Sélectionner une année",
})!
.textContent?.replace("arrow_drop_down", "");
expect(focusedYear).eq("1945 शक");
// Make sure weekdays are in the right locale
screen.getByText("रवि");
screen.getByText("सोम");
screen.getByText("मंगल");
screen.getByText("बुध");
screen.getByText("गुरु");
screen.getByText("शुक्र");
screen.getByText("शनि");
});
it("has a wrong locale props", async () => {
vi.spyOn(console, "error").mockImplementation(() => undefined);
expect(() =>
render(
<CunninghamProvider>
<DatePicker label="Pick a date" locale="111" />
</CunninghamProvider>
)
).toThrow("Incorrect locale information provided");
});
}); });

View File

@@ -11,6 +11,7 @@ import {
} from "@react-stately/datepicker"; } from "@react-stately/datepicker";
import { DateRangePickerAria, DatePickerAria } from "@react-aria/datepicker"; import { DateRangePickerAria, DatePickerAria } from "@react-aria/datepicker";
import classNames from "classnames"; import classNames from "classnames";
import { I18nProvider } from "@react-aria/i18n";
import { Button } from ":/components/Button"; import { Button } from ":/components/Button";
import { Popover } from ":/components/Popover"; import { Popover } from ":/components/Popover";
import { Field, FieldProps } from ":/components/Forms/Field"; import { Field, FieldProps } from ":/components/Forms/Field";
@@ -27,6 +28,7 @@ export type DatePickerAuxSubProps = FieldProps & {
maxValue?: StringOrDate; maxValue?: StringOrDate;
disabled?: boolean; disabled?: boolean;
name?: string; name?: string;
locale?: string;
}; };
export type DatePickerAuxProps = PropsWithChildren & export type DatePickerAuxProps = PropsWithChildren &
@@ -58,13 +60,14 @@ const DatePickerAux = forwardRef(
calendar, calendar,
children, children,
name, name,
locale,
disabled = false, disabled = false,
optionalClassName, optionalClassName,
...props ...props
}: DatePickerAuxProps, }: DatePickerAuxProps,
ref: Ref<HTMLDivElement> ref: Ref<HTMLDivElement>
) => { ) => {
const { t } = useCunningham(); const { t, currentLocale } = useCunningham();
const pickerRef = useRef<HTMLDivElement>(null); const pickerRef = useRef<HTMLDivElement>(null);
const isDateInvalid = useMemo( const isDateInvalid = useMemo(
@@ -79,93 +82,97 @@ const DatePickerAux = forwardRef(
pickerProps.buttonProps; pickerProps.buttonProps;
return ( return (
<Field {...props}> <I18nProvider locale={locale || currentLocale}>
<div <Field {...props}>
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 <div
className={classNames("c__date-picker__wrapper", { ref={pickerRef}
"c__date-picker__wrapper--clickable": labelAsPlaceholder, 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),
})} })}
ref={ref}
{...pickerProps.groupProps}
role="button"
tabIndex={0}
onClick={() => !pickerState.isOpen && pickerState.open()}
> >
{"dateRange" in pickerState ? ( <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 <input
type="hidden" type="hidden"
name={name && `${name}_start`} name={name}
value={pickerState.value?.start?.toString() || ""} value={pickerState.value?.toString() || ""}
/> />
<input )}
type="hidden" <div className="c__date-picker__wrapper__icon">
name={name && `${name}_end`} <Button
value={pickerState.value?.end?.toString() || ""} {...{
...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}
<input
type="hidden"
name={name}
value={pickerState.value?.toString() || ""}
/>
)}
<div className="c__date-picker__wrapper__icon">
<Button <Button
{...{ className={classNames("c__date-picker__inner__action", {
...otherButtonProps, "c__date-picker__inner__action--empty": !pickerState.value,
"aria-label": t( "c__date-picker__inner__action--hidden":
pickerState.isOpen labelAsPlaceholder || disabled,
? "components.forms.date_picker.toggle_button_aria_label_close" })}
: "components.forms.date_picker.toggle_button_aria_label_open"
),
}}
color="tertiary" color="tertiary"
size="small" size="small"
className="c__date-picker__wrapper__toggle" icon={<span className="material-icons">cancel</span>}
onClick={pickerState.toggle} onClick={onClear}
icon={<span className="material-icons">calendar_today</span>} aria-label={t(
"components.forms.date_picker.clear_button_aria_label"
)}
disabled={disabled} disabled={disabled}
/> />
</div> </div>
{children} {pickerState.isOpen && (
<Button <Popover
className={classNames("c__date-picker__inner__action", { parentRef={pickerRef}
"c__date-picker__inner__action--empty": !pickerState.value, onClickOutside={pickerState.close}
"c__date-picker__inner__action--hidden": borderless
labelAsPlaceholder || disabled, >
})} {calendar}
color="tertiary" </Popover>
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> </div>
{pickerState.isOpen && ( </Field>
<Popover </I18nProvider>
parentRef={pickerRef}
onClickOutside={pickerState.close}
borderless
>
{calendar}
</Popover>
)}
</div>
</Field>
); );
} }
); );