Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(date-input): modifying validation error containing date #3694

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/calm-kids-visit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nextui-org/date-input": patch
---

The Date mentioned in the Error message of the date-picker is not according to the Locale. This PR adds an layer to the validationError to ensure correct formatting date in error messages.
55 changes: 54 additions & 1 deletion packages/components/date-input/__tests__/date-input.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
/* eslint-disable jsx-a11y/no-autofocus */
import * as React from "react";
import {act, fireEvent, render} from "@testing-library/react";
import {CalendarDate, CalendarDateTime, DateValue, ZonedDateTime} from "@internationalized/date";
import {
CalendarDate,
CalendarDateTime,
DateValue,
ZonedDateTime,
parseDate,
} from "@internationalized/date";
import {pointerMap, triggerPress} from "@nextui-org/test-utils";
import userEvent from "@testing-library/user-event";
import {I18nProvider} from "@react-aria/i18n";

import {DateInput as DateInputBase, DateInputProps} from "../src";

Expand Down Expand Up @@ -174,6 +181,52 @@ describe("DateInput", () => {
}
}
});

it("should support error message with MinDateValue according to locale", function () {
const minValue = "2024-04-04";
const locale = "hi-IN-u-ca-indian";
let {getByRole} = render(
<I18nProvider locale={locale}>
<DateInput
defaultValue={parseDate("2024-04-03")}
label="Date"
minValue={parseDate(minValue)}
/>
</I18nProvider>,
);

let group = getByRole("group");

expect(group).toHaveAttribute("aria-describedby");

const errorComponent = document.querySelector("[data-slot=error-message]");
const localeBasedDate = new Date(minValue).toLocaleDateString(locale);

expect(errorComponent).toHaveTextContent(localeBasedDate);
});

it("should support error message with MaxDateValue according to locale", function () {
const maxValue = "2024-04-04";
const locale = "hi-IN-u-ca-indian";
let {getByRole} = render(
<I18nProvider locale={locale}>
<DateInput
defaultValue={parseDate("2024-04-05")}
label="Date"
maxValue={parseDate(maxValue)}
/>
</I18nProvider>,
);

let group = getByRole("group");

expect(group).toHaveAttribute("aria-describedby");

const errorComponent = document.querySelector("[data-slot=error-message]");
const localeBasedDate = new Date(maxValue).toLocaleDateString(locale);

expect(errorComponent).toHaveTextContent(localeBasedDate);
});
});

describe("Events", function () {
Expand Down
172 changes: 172 additions & 0 deletions packages/components/date-input/intl/messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
export default {
"ar-AE": {
minValidationMessage: "يجب أن تكون القيمة {date} أو بعد ذلك.",
maxValidationMessage: "يجب أن تكون القيمة {date} أو قبل ذلك.",
badInputMessage: "التاريخ المحدد غير متوفر.",
},
"bg-BG": {
minValidationMessage: "Стойността трябва да бъде {date} или по-късно.",
maxValidationMessage: "Стойността трябва да бъде {date} или по-рано.",
badInputMessage: "Избраното дата не е налична.",
},
"cs-CZ": {
minValidationMessage: "Hodnota musí být {date} nebo později.",
maxValidationMessage: "Hodnota musí být {date} nebo dříve.",
badInputMessage: "Vybraný datum není k dispozici.",
},
"da-DK": {
minValidationMessage: "Værdien skal være {date} eller senere.",
maxValidationMessage: "Værdien skal være {date} eller tidligere.",
badInputMessage: "Valgt dato er ikke tilgængelig.",
},
"de-DE": {
minValidationMessage: "Der Wert muss {date} oder später sein.",
maxValidationMessage: "Der Wert muss {date} oder früher sein.",
badInputMessage: "Ausgewähltes Datum nicht verfügbar.",
},
"el-GR": {
minValidationMessage: "Η τιμή πρέπει να είναι {date} ή αργότερα.",
maxValidationMessage: "Η τιμή πρέπει να είναι {date} ή νωρίτερα.",
badInputMessage: "Επιλεγμένη ημερομηνία μη διαθέσιμη.",
},
"en-US": {
minValidationMessage: "Value must be {date} or later.",
maxValidationMessage: "Value must be {date} or earlier.",
badInputMessage: "Selected date unavailable.",
},
"es-ES": {
minValidationMessage: "El valor debe ser {date} o posterior.",
maxValidationMessage: "El valor debe ser {date} o anterior.",
badInputMessage: "Fecha seleccionada no disponible.",
},
"et-EE": {
minValidationMessage: "Väärtus peab olema {date} või hiljem.",
maxValidationMessage: "Väärtus peab olema {date} või varem.",
badInputMessage: "Valitud kuupäev ei ole saadaval.",
},
"fi-FI": {
minValidationMessage: "Arvon on oltava {date} tai myöhemmin.",
maxValidationMessage: "Arvon on oltava {date} tai aikaisemmin.",
badInputMessage: "Valittu päivämäärä ei ole saatavilla.",
},
"fr-FR": {
minValidationMessage: "La valeur doit être {date} ou ultérieure.",
maxValidationMessage: "La valeur doit être {date} ou antérieure.",
badInputMessage: "Date sélectionnée non disponible.",
},
"he-IL": {
minValidationMessage: "הערך חייב להיות {date} או מאוחר יותר.",
maxValidationMessage: "הערך חייב להיות {date} או קודם.",
badInputMessage: "התאריך שנבחר אינו זמין.",
},
"hr-HR": {
minValidationMessage: "Vrijednost mora biti {date} ili kasnije.",
maxValidationMessage: "Vrijednost mora biti {date} ili ranije.",
badInputMessage: "Odabrani datum nije dostupan.",
},
"hu-HU": {
minValidationMessage: "Az értéknek {date} vagy későbbi kell lennie.",
maxValidationMessage: "Az értéknek {date} vagy korábbi kell lennie.",
badInputMessage: "Kiválasztott dátum nem elérhető.",
},
"it-IT": {
minValidationMessage: "Il valore deve essere {date} o successivo.",
maxValidationMessage: "Il valore deve essere {date} o precedente.",
badInputMessage: "Data selezionata non disponibile.",
},
"ja-JP": {
minValidationMessage: "値は{date}以降でなければなりません。",
maxValidationMessage: "{date}以前でなければなりません。",
badInputMessage: "選択された日付は利用できません。",
},
"ko-KR": {
minValidationMessage: "{date} 이후여야 합니다.",
maxValidationMessage: "{date} 이전이어야 합니다.",
badInputMessage: "선택한 날짜는 사용 불가능합니다.",
},
"lt-LT": {
minValidationMessage: "Vertė turi būti {date} arba vėliau.",
maxValidationMessage: "Vertė turi būti {date} arba ankstesnė.",
badInputMessage: "Pasirinkta data nėra prieinama.",
},
"lv-LV": {
minValidationMessage: "Vērtībai jābūt {date} vai vēlāk.",
maxValidationMessage: "Vērtībai jābūt {date} vai agrāk.",
badInputMessage: "Izvēlētā datums nav pieejams.",
},
"nb-NO": {
minValidationMessage: "Verdien må være {date} eller senere.",
maxValidationMessage: "Verdien må være {date} eller tidligere.",
badInputMessage: "Valgt dato er ikke tilgjengelig.",
},
"nl-NL": {
minValidationMessage: "De waarde moet {date} of later zijn.",
maxValidationMessage: "De waarde moet {date} of eerder zijn.",
badInputMessage: "Geselecteerde datum niet beschikbaar.",
},
"pl-PL": {
minValidationMessage: "Wartość musi być {date} lub późniejsza.",
maxValidationMessage: "Wartość musi być {date} lub wcześniejsza.",
badInputMessage: "Wybrana data jest niedostępna.",
},
"pt-BR": {
minValidationMessage: "O valor deve ser {date} ou posterior.",
maxValidationMessage: "O valor deve ser {date} ou anterior.",
badInputMessage: "Data selecionada não disponível.",
},
"pt-PT": {
minValidationMessage: "O valor deve ser {date} ou mais tarde.",
maxValidationMessage: "O valor deve ser {date} ou antes.",
badInputMessage: "Data selecionada não disponível.",
},
"ro-RO": {
minValidationMessage: "Valoarea trebuie să fie {date} sau mai târziu.",
maxValidationMessage: "Valoarea trebuie să fie {date} sau mai devreme.",
badInputMessage: "Data selectată nu este disponibilă.",
},
"ru-RU": {
minValidationMessage: "Значение должно быть {date} или позже.",
maxValidationMessage: "Значение должно быть {date} или раньше.",
badInputMessage: "Выбранная дата недоступна.",
},
"sk-SK": {
minValidationMessage: "Hodnota musí byť {date} alebo neskôr.",
maxValidationMessage: "Hodnota musí byť {date} alebo skôr.",
badInputMessage: "Vybraný dátum nie je k dispozícii.",
},
"sl-SI": {
minValidationMessage: "Vrednost mora biti {date} ali kasneje.",
maxValidationMessage: "Vrednost mora biti {date} ali prej.",
badInputMessage: "Izbrani datum ni na voljo.",
},
"sr-SP": {
minValidationMessage: "Vrednost mora biti {date} ili kasnije.",
maxValidationMessage: "Vrednost mora biti {date} ili ranije.",
badInputMessage: "Izabrani datum nije dostupan.",
},
"sv-SE": {
minValidationMessage: "Värdet måste vara {date} eller senare.",
maxValidationMessage: "Värdet måste vara {date} eller tidigare.",
badInputMessage: "Vald datum är inte tillgänglig.",
},
"tr-TR": {
minValidationMessage: "Değer {date} veya sonrasında olmalıdır.",
maxValidationMessage: "Değer {date} veya daha önce olmalıdır.",
badInputMessage: "Seçilen tarih kullanılamıyor.",
},
"uk-UA": {
minValidationMessage: "Значення повинно бути {date} або пізніше.",
maxValidationMessage: "Значення повинно бути {date} або раніше.",
badInputMessage: "Вибрана дата недоступна.",
},
"zh-CN": {
minValidationMessage: "值必须为 {date} 或更晚。",
maxValidationMessage: "值必须为 {date} 或更早。",
badInputMessage: "选择的日期不可用。",
},
"zh-TW": {
minValidationMessage: "值必須為 {date} 或更晚。",
maxValidationMessage: "值必須為 {date} 或更早。",
badInputMessage: "選擇的日期不可用。",
},
};
44 changes: 43 additions & 1 deletion packages/components/date-input/src/use-date-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type {ReactRef} from "@nextui-org/react-utils";
import type {DOMAttributes, GroupDOMAttributes} from "@react-types/shared";
import type {DateInputGroupProps} from "./date-input-group";

import {useLocale} from "@react-aria/i18n";
import {useLocale, useLocalizedStringFormatter} from "@react-aria/i18n";
import {createCalendar, CalendarDate, DateFormatter} from "@internationalized/date";
import {mergeProps} from "@react-aria/utils";
import {PropGetter, useProviderContext} from "@nextui-org/system";
Expand All @@ -18,6 +18,8 @@ import {objectToDeps, clsx, dataAttr, getGregorianYearOffset} from "@nextui-org/
import {dateInput, cn} from "@nextui-org/theme";
import {useMemo} from "react";

import intlMessages from "../intl/messages";

type NextUIBaseProps<T extends DateValue> = Omit<
HTMLNextUIProps<"div">,
keyof AriaDateFieldProps<T> | "onChange"
Expand Down Expand Up @@ -184,6 +186,46 @@ export function useDateInput<T extends DateValue>(originalProps: UseDateInputPro
isInvalid: ariaIsInvalid,
} = useAriaDateField({...originalProps, label, validationBehavior, inputRef}, state, domRef);

const stringFormatter = useLocalizedStringFormatter(intlMessages);

if (props.minValue != undefined && validationDetails.rangeUnderflow) {
const minValueDate = new Date(
minValue.year,
minValue.month - 1,
minValue.day,
).toLocaleDateString(locale);
const timeZone =
state.segments.filter((segment) => segment.type === "timeZoneName")[0]?.text ?? "";
const rangeUnderflow = stringFormatter
.format("minValidationMessage")
.replace("{date}", `${minValueDate} ${timeZone}`);

validationErrors.splice(0, 1, rangeUnderflow);
}

if (props.maxValue != undefined && validationDetails.rangeOverflow) {
const maxValueDate = new Date(
maxValue.year,
maxValue.month - 1,
maxValue.day,
).toLocaleDateString(locale);
const timeZone =
state.segments.filter((segment) => segment.type === "timeZoneName")[0]?.text ?? "";

const rangeOverflow = stringFormatter
.format("maxValidationMessage")
.replace("{date}", `${maxValueDate} ${timeZone}`);

validationErrors.splice(0, 1, rangeOverflow);
}

if (validationDetails.badInput) {
const badInputMessage = stringFormatter.format("badInputMessage");
const indexInValidationErrors = validationErrors.length - 1;

validationErrors.splice(indexInValidationErrors, 1, badInputMessage);
}

const baseStyles = clsx(classNames?.base, className);

const isInvalid = isInvalidProp || ariaIsInvalid;
Expand Down