✨(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:
committed by
aleb_the_flash
parent
f03ef6a9e1
commit
e1489b7fe0
@@ -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");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user