@@ -72,7 +74,12 @@ const sections = [
content: (
<>
- Halstack Provider can be used to translate all the labels that cannot be changed by the component properties.
+ Using Halstack Provider localeTag property, we can set the locale for the components. The localeTag property
+ accepts a string that represents the locale, such as "en-US" for English (United States) or "es-ES" for
+ Spanish (Spain). By setting this property, some components like the DateInput component will automatically
+ format the date according to the specified locale. For example, if we set localeTag="de-DE", the DateInput
+ component will display the date in the German format (DD.MM.YYYY). Halstack Provider can also be used to
+ translate all the labels that cannot be changed by the component properties.
- Let's imagine that we want to translate the '(Optional)' label of a DxcTextInput, as well as the
- error messages of our DxcFileInput component. To do so, we need to create an object with the
- translations. In this object, you will have as many objects as components you want to translate with the
- respective translation for their labels.
+ Let's imagine that we want to translate the '(Optional)' label of a DxcTextInput. To do so, we
+ need to create an object with the translations. In this object, you will have as many objects as components
+ you want to translate with the respective translation for their labels.
+
+
+ To change the format in a DxcDateInput component, we can use the localeTag property
+ of the Halstack Provider or the format prop. The localeTag property accepts a string
+ that represents the locale, such as "en-US" for English (United States) or "es-ES" for Spanish (Spain). By
+ setting this property, the DateInput component will automatically format the date according to the specified
+ locale and also change the first day of the week. For example, if we set localeTag="fr-CH", the{" "}
+ DxcDateInput component will display the date in the French but using Swiss format (DD.MM.YYYY)
+ and set Monday as the first day of the week.
-
-
-
- *(to see the translated error message you should try to add any file on the DxcFileInput).
-
-
+
>
),
},
diff --git a/apps/website/screens/utilities/halstack-provider/examples/customLocalization.tsx b/apps/website/screens/utilities/halstack-provider/examples/customLocalization.tsx
new file mode 100644
index 0000000000..e6700fb36e
--- /dev/null
+++ b/apps/website/screens/utilities/halstack-provider/examples/customLocalization.tsx
@@ -0,0 +1,39 @@
+import { DxcTextInput, HalstackProvider, DxcDateInput, DxcInset, DxcFlex } from "@dxc-technology/halstack-react";
+
+const code = `() => {
+ const labels = {
+ formFields: {
+ optionalLabel: "(Optionnel)",
+ },
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ );
+}`;
+
+const scope = {
+ DxcTextInput,
+ DxcDateInput,
+ HalstackProvider,
+ DxcInset,
+ DxcFlex,
+};
+
+export default { code, scope };
diff --git a/apps/website/screens/utilities/halstack-provider/examples/customTranslations.tsx b/apps/website/screens/utilities/halstack-provider/examples/customTranslations.tsx
deleted file mode 100644
index 6829639103..0000000000
--- a/apps/website/screens/utilities/halstack-provider/examples/customTranslations.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import { DxcTextInput, DxcFileInput, HalstackProvider, DxcInset, DxcFlex } from "@dxc-technology/halstack-react";
-import { useState } from "react";
-
-const code = `() => {
- const [items, changeItems] = useState(10);
- const [files, setFiles] = useState([]);
-
- const itemsPerPageClick = (value) => {
- changeItems(value);
- };
- const callbackFile = (files) => {
- const updatedFiles = files.map((file) => {
- return { ...file };
- });
- setFiles(updatedFiles);
- };
-
- const labels = {
- formFields: {
- optionalLabel: "(Optionnel)",
- },
- fileInput: {
- fileSizeLessThanErrorMessage:
- "La taille du fichier doit être inférieure à la taille maximale.",
- },
- };
-
- return (
-
-
-
-
-
-
-
-
- );
-}`;
-
-const scope = {
- DxcTextInput,
- DxcFileInput,
- HalstackProvider,
- DxcInset,
- DxcFlex,
- useState,
-};
-
-export default { code, scope };
diff --git a/apps/website/tsconfig.json b/apps/website/tsconfig.json
index aa9c8211b2..bc92b780d2 100644
--- a/apps/website/tsconfig.json
+++ b/apps/website/tsconfig.json
@@ -9,6 +9,6 @@
"@/common/*": ["screens/common/*"]
}
},
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "next.config.ts"],
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "next.config.ts", "../../packages/lib/src/**/*.d.ts"],
"exclude": ["node_modules", "out", ".turbo", ".next"]
}
diff --git a/packages/lib/src/HalstackContext.tsx b/packages/lib/src/HalstackContext.tsx
index cecbe49d4b..5b61dd31b7 100644
--- a/packages/lib/src/HalstackContext.tsx
+++ b/packages/lib/src/HalstackContext.tsx
@@ -2,7 +2,12 @@ import { createContext, ReactNode, useMemo } from "react";
import styled from "@emotion/styled";
import { css } from "@emotion/react";
import { coreTokens, aliasTokens } from "./styles/tokens";
-import { defaultThemedLogos, TranslatedLabels, defaultTranslatedComponentLabels } from "./common/variables";
+import {
+ defaultThemedLogos,
+ TranslatedLabels,
+ defaultTranslatedComponentLabels,
+ LocalizedContext,
+} from "./common/variables";
/**
* This type is used to allow labels objects to be passed to the HalstackProvider.
@@ -11,7 +16,7 @@ import { defaultThemedLogos, TranslatedLabels, defaultTranslatedComponentLabels
export type DeepPartial = {
[P in keyof T]?: Partial;
};
-const HalstackLanguageContext = createContext(defaultTranslatedComponentLabels);
+const HalstackLanguageContext = createContext({ labels: defaultTranslatedComponentLabels });
const HalstackLogosContext = createContext>(defaultThemedLogos);
const parseLabels = (labels: DeepPartial): TranslatedLabels => {
@@ -30,12 +35,14 @@ const parseLabels = (labels: DeepPartial): TranslatedLabels =>
});
return parsedLabels;
};
+
type ThemeType = { tokens?: Record; logos?: Record };
type HalstackProviderPropsType = {
labels?: DeepPartial;
children: ReactNode;
opinionatedTheme?: ThemeType;
+ localeTag?: string;
};
const HalstackThemed = styled.div<{ coreTheme?: ThemeType["tokens"] }>`
@@ -69,7 +76,12 @@ const createCoreTheme = (opinionatedTheme: ThemeType["tokens"] | undefined = {})
return newTheme;
};
-const HalstackProvider = ({ labels, children, opinionatedTheme }: HalstackProviderPropsType): JSX.Element => {
+const HalstackProvider = ({
+ labels,
+ children,
+ opinionatedTheme,
+ localeTag,
+}: HalstackProviderPropsType): JSX.Element => {
const parsedLabels = useMemo(() => (labels ? parseLabels(labels) : null), [labels]);
const parsedCoreTheme = useMemo(() => {
const theme = createCoreTheme(opinionatedTheme?.tokens);
@@ -79,8 +91,12 @@ const HalstackProvider = ({ labels, children, opinionatedTheme }: HalstackProvid
return (
- {parsedLabels ? (
- {children}
+ {parsedLabels || localeTag ? (
+
+ {children}
+
) : (
children
)}
diff --git a/packages/lib/src/alert/Alert.tsx b/packages/lib/src/alert/Alert.tsx
index 0c78ef5707..050de59d1c 100644
--- a/packages/lib/src/alert/Alert.tsx
+++ b/packages/lib/src/alert/Alert.tsx
@@ -105,7 +105,7 @@ const DxcAlert = ({
const [currentIndex, setCurrentIndex] = useState(0);
const id = useId();
- const translatedLabels = useContext(HalstackLanguageContext);
+ const translatedLabels = useContext(HalstackLanguageContext).labels;
const handleNextOnClick = () => {
setCurrentIndex((prevIndex) => (prevIndex < messages.length ? prevIndex + 1 : prevIndex));
diff --git a/packages/lib/src/card/Card.tsx b/packages/lib/src/card/Card.tsx
index 2ce95681b2..8591392ccc 100644
--- a/packages/lib/src/card/Card.tsx
+++ b/packages/lib/src/card/Card.tsx
@@ -112,7 +112,7 @@ const DxcCard = forwardRef(
const [internalSelected, setInternalSelected] = useState(
selected !== undefined ? selected : defaultSelected || false
);
- const translatedLabels = useContext(HalstackLanguageContext);
+ const translatedLabels = useContext(HalstackLanguageContext).labels;
useEffect(() => {
if (selected !== undefined) {
diff --git a/packages/lib/src/checkbox/Checkbox.tsx b/packages/lib/src/checkbox/Checkbox.tsx
index 06badf4663..ddc4f0bb06 100644
--- a/packages/lib/src/checkbox/Checkbox.tsx
+++ b/packages/lib/src/checkbox/Checkbox.tsx
@@ -93,7 +93,7 @@ const DxcCheckbox = forwardRef(
const labelId = `label-checkbox-${useId()}`;
const [innerChecked, setInnerChecked] = useState(defaultChecked);
const checkboxRef = useRef(null);
- const translatedLabels = useContext(HalstackLanguageContext);
+ const translatedLabels = useContext(HalstackLanguageContext).labels;
const { partial } = useContext(CheckboxContext) ?? {};
const handleOnChange = () => {
diff --git a/packages/lib/src/common/variables.ts b/packages/lib/src/common/variables.ts
index 5cd2bedddf..f5a58b3784 100644
--- a/packages/lib/src/common/variables.ts
+++ b/packages/lib/src/common/variables.ts
@@ -140,6 +140,11 @@ export const defaultTranslatedComponentLabels = {
export type TranslatedLabels = typeof defaultTranslatedComponentLabels;
+export type LocalizedContext = {
+ labels: TranslatedLabels;
+ locale?: string;
+};
+
export const defaultThemedLogos = {
mainLogo: undefined,
footerLogo: undefined,
diff --git a/packages/lib/src/date-input/Calendar.tsx b/packages/lib/src/date-input/Calendar.tsx
index 3e2b61b387..7f0a600731 100644
--- a/packages/lib/src/date-input/Calendar.tsx
+++ b/packages/lib/src/date-input/Calendar.tsx
@@ -3,6 +3,7 @@ import { useContext, useState, useMemo, useEffect, useId, memo, KeyboardEvent, F
import styled from "@emotion/styled";
import { CalendarPropsType, DateType } from "./types";
import { HalstackLanguageContext } from "../HalstackContext";
+import { divideDaysIntoWeeks, getCalendarDays, getDateToFocus, isDaySelected, validateLocale } from "./utils";
const CalendarContainer = styled.div`
box-sizing: border-box;
@@ -91,53 +92,6 @@ const DayCellButton = styled.button<{
}
`;
-const getDays = (innerDate: Dayjs) => {
- const monthDayCells: DateType[] = [];
- const lastMonthNumberOfDays = innerDate.set("month", innerDate.get("month") - 1).endOf("month");
- const firstDayOfMonth = innerDate.startOf("month").day() === 0 ? 6 : innerDate.startOf("month").day() - 1;
- const daysInMonth = firstDayOfMonth + innerDate.daysInMonth();
-
- for (let i = 0; i < 42; i++) {
- if (i < firstDayOfMonth) {
- monthDayCells.push({
- day: lastMonthNumberOfDays.get("date") - firstDayOfMonth + i + 1,
- month: innerDate.get("month") ? innerDate.get("month") - 1 : 11,
- year: innerDate.set("month", innerDate.get("month") - 1).get("year"),
- });
- } else if (i < daysInMonth) {
- monthDayCells.push({
- day: i - firstDayOfMonth + 1,
- month: innerDate.get("month"),
- year: innerDate.get("year"),
- });
- } else {
- monthDayCells.push({
- day: i - daysInMonth + 1,
- month: innerDate.get("month") === 11 ? 0 : innerDate.get("month") + 1,
- year: innerDate.set("month", innerDate.get("month") + 1).get("year"),
- });
- }
- }
- return monthDayCells;
-};
-
-const getDateToFocus = (selectedDate: Dayjs, innerDate: Dayjs, today: Dayjs) =>
- selectedDate?.get("month") === innerDate.get("month") && selectedDate?.get("year") === innerDate.get("year")
- ? selectedDate
- : today.get("month") === innerDate.get("month") && today.get("year") === innerDate.get("year")
- ? today
- : innerDate.set("date", 1);
-
-const isDaySelected = (date: DateType, selectedDate: Dayjs) =>
- selectedDate?.get("month") === date.month &&
- selectedDate?.get("year") === date.year &&
- selectedDate?.get("date") === date.day;
-
-const divideDaysIntoWeeks = (data: DateType[], weekSize: number) =>
- Array.from({ length: Math.ceil(data.length / weekSize) }, (_, rowIndex) =>
- data.slice(rowIndex * weekSize, (rowIndex + 1) * weekSize)
- );
-
const Calendar = ({
selectedDate,
innerDate,
@@ -148,8 +102,12 @@ const Calendar = ({
const [dateToFocus, setDateToFocus] = useState(getDateToFocus(selectedDate, innerDate, today));
const [isFocusable, setIsFocusable] = useState(false);
const id = useId();
- const translatedLabels = useContext(HalstackLanguageContext);
- const dayCells = useMemo(() => getDays(innerDate), [innerDate]);
+ const languageContext = useContext(HalstackLanguageContext);
+ const translatedLabels = languageContext?.labels;
+ const localeTag = languageContext?.locale || navigator.language;
+ const locale = new Intl.Locale(validateLocale(localeTag) ? localeTag : "en");
+ const firstDayOfWeek = (locale ? (locale.getWeekInfo?.()?.firstDay ?? 1) : 1) % 7;
+ const dayCells = useMemo(() => getCalendarDays(innerDate, firstDayOfWeek), [innerDate, firstDayOfWeek]);
const onDateClickHandler = (date: DateType) => {
const newDate = innerDate.set("month", date.month).set("date", date.day);
@@ -184,6 +142,11 @@ const Calendar = ({
}
}, [innerDate, dateToFocus, selectedDate, today]);
+ const orderedWeekDays = useMemo(() => {
+ const weekDays = translatedLabels.calendar.daysShort;
+ return [...weekDays.slice(firstDayOfWeek - 1), ...weekDays.slice(0, firstDayOfWeek - 1)];
+ }, [translatedLabels.calendar.daysShort]);
+
const handleDayKeyboardEvent = (event: KeyboardEvent, date: DateType) => {
let dateToFocusTemp =
date.month === innerDate.get("month")
@@ -253,10 +216,12 @@ const Calendar = ({
break;
}
};
+
return (
- {translatedLabels.calendar.daysShort.map((weekDay) => (
+ {/* array needs to be changed based on firstDayOfWeek or the array itself */}
+ {orderedWeekDays.map((weekDay) => (
{weekDay}
diff --git a/packages/lib/src/date-input/DateInput.stories.tsx b/packages/lib/src/date-input/DateInput.stories.tsx
index c98da33b31..23a5f373b3 100644
--- a/packages/lib/src/date-input/DateInput.stories.tsx
+++ b/packages/lib/src/date-input/DateInput.stories.tsx
@@ -10,6 +10,7 @@ import DxcDateInput from "./DateInput";
import DxcDatePicker from "./DatePicker";
import YearPicker from "./YearPicker";
import { fireEvent, screen, userEvent, within } from "storybook/internal/test";
+import { HalstackProvider } from "../HalstackContext";
export default {
title: "Date Input",
@@ -156,6 +157,12 @@ const DatePickerButtonStates = () => (
{}} id="test-calendar3" />
+
+
+
+ {}} id="test-calendar3" />
+
+
>
);
diff --git a/packages/lib/src/date-input/DateInput.test.tsx b/packages/lib/src/date-input/DateInput.test.tsx
index 27655dcfa6..b954489a2a 100644
--- a/packages/lib/src/date-input/DateInput.test.tsx
+++ b/packages/lib/src/date-input/DateInput.test.tsx
@@ -3,6 +3,7 @@ import userEvent from "@testing-library/user-event";
import dayjs from "dayjs";
import DxcDateInput from "./DateInput";
import MockDOMRect from "../../test/mocks/domRectMock";
+import { HalstackProvider } from "../HalstackContext";
// Mocking DOMRect for Radix Primitive Popover
global.DOMRect = MockDOMRect;
@@ -497,6 +498,22 @@ describe("DateInput component tests", () => {
userEvent.click(calendarAction);
expect(getByText("October 2080")).toBeTruthy();
});
+ test("German locale date input", () => {
+ const { getByRole, getByText } = render(
+
+
+
+ );
+ const input = getByRole("textbox") as HTMLInputElement;
+ const calendarAction = getByRole("combobox");
+ expect(input.value).toBe("03.12.1995");
+ userEvent.click(calendarAction);
+ const day31 = getByText("31");
+ if (day31 != null) {
+ userEvent.click(day31);
+ }
+ expect(input.value).toBe("31.12.1995");
+ });
test("Form onSubmit is not called when interacting with the calendar and pressing enter", () => {
const onSubmit = jest.fn();
const { getByRole, getAllByText } = render(
diff --git a/packages/lib/src/date-input/DateInput.tsx b/packages/lib/src/date-input/DateInput.tsx
index e05ee8cec4..6af1c10ea4 100644
--- a/packages/lib/src/date-input/DateInput.tsx
+++ b/packages/lib/src/date-input/DateInput.tsx
@@ -6,10 +6,9 @@ import {
useCallback,
useContext,
forwardRef,
- Dispatch,
- SetStateAction,
FocusEvent,
KeyboardEvent,
+ useMemo,
} from "react";
import dayjs, { Dayjs } from "dayjs";
import styled from "@emotion/styled";
@@ -21,6 +20,7 @@ import DatePicker from "./DatePicker";
import { getMargin } from "../common/utils";
import { spaces } from "../common/variables";
import DxcTextInput from "../text-input/TextInput";
+import { getDate, getFormatFromLocale, getValueForPicker } from "./utils";
dayjs.extend(customParseFormat);
@@ -87,32 +87,6 @@ const StyledPopoverContent = styled(Popover.Content)`
}
`;
-const getValueForPicker = (value: string, format: string) => dayjs(value, format.toUpperCase(), true);
-
-const getDate = (
- value: string,
- format: string,
- lastValidYear: number | null,
- setLastValidYear: Dispatch>
-) => {
- if ((value || value === "") && format.toUpperCase().includes("YYYY")) {
- return getValueForPicker(value, format);
- }
- let newDate = getValueForPicker(value, format);
- if (lastValidYear == null) {
- if (+newDate.format("YY") < 68) {
- setLastValidYear(2000 + +newDate.format("YY"));
- newDate = newDate.set("year", 2000 + +newDate.format("YY"));
- } else {
- setLastValidYear(1900 + +newDate.format("YY"));
- newDate = newDate.set("year", 1900 + +newDate.format("YY"));
- }
- } else {
- newDate = newDate.set("year", (lastValidYear <= 1999 ? 1900 : 2000) + +newDate.format("YY"));
- }
- return newDate;
-};
-
const DxcDateInput = forwardRef(
(
{
@@ -120,7 +94,7 @@ const DxcDateInput = forwardRef(
name,
defaultValue = "",
value,
- format = "dd-MM-yyyy",
+ format,
helperText,
placeholder = false,
clearable,
@@ -141,10 +115,16 @@ const DxcDateInput = forwardRef(
const [innerValue, setInnerValue] = useState(defaultValue);
const [isOpen, setIsOpen] = useState(false);
const calendarId = `date-picker-${useId()}`;
- const [dayjsDate, setDayjsDate] = useState(getValueForPicker(value ?? defaultValue ?? "", format));
+ const languageContext = useContext(HalstackLanguageContext);
+ const locale = languageContext.locale ? languageContext.locale : undefined;
+ const formatter = useMemo(() => {
+ return format ? format : locale ? getFormatFromLocale(locale) : "dd-MM-yyyy";
+ }, [format, locale]);
+ const [dayjsDate, setDayjsDate] = useState(getValueForPicker(value ?? defaultValue ?? "", formatter));
const [lastValidYear, setLastValidYear] = useState(
innerValue || value
- ? !format.toUpperCase().includes("YYYY") && +getValueForPicker(value ?? innerValue, format).format("YY") < 68
+ ? !formatter.toUpperCase().includes("YYYY") &&
+ +getValueForPicker(value ?? innerValue, formatter).format("YY") < 68
? 2000
: 1900
: null
@@ -152,12 +132,12 @@ const DxcDateInput = forwardRef(
const [sideOffset, setSideOffset] = useState(SIDEOFFSET);
const [portalContainer, setPortalContainer] = useState(null);
- const translatedLabels = useContext(HalstackLanguageContext);
+ const translatedLabels = languageContext.labels;
const dateRef = useRef(null);
const popoverContentRef = useRef(null);
const handleCalendarOnClick = (newDate: Dayjs) => {
- const newValue = newDate.format(format.toUpperCase());
+ const newValue = newDate.format(formatter.toUpperCase());
if (!value) {
setDayjsDate(newDate);
setInnerValue(newValue);
@@ -179,7 +159,7 @@ const DxcDateInput = forwardRef(
if (value == null) {
setInnerValue(newValue);
}
- const newDate = getDate(newValue, format, lastValidYear, setLastValidYear);
+ const newDate = getDate(newValue, formatter, lastValidYear, setLastValidYear);
const invalidDateMessage =
newValue !== "" && !newDate.isValid() && translatedLabels.dateInput.invalidDateErrorMessage;
const callbackParams = {
@@ -199,7 +179,7 @@ const DxcDateInput = forwardRef(
}
};
const handleOnBlur = ({ value: blurValue, error: inputError }: { value: string; error?: string }) => {
- const date = getDate(blurValue, format, lastValidYear, setLastValidYear);
+ const date = getDate(blurValue, formatter, lastValidYear, setLastValidYear);
const invalidDateMessage =
blurValue !== "" && !date.isValid() && translatedLabels.dateInput.invalidDateErrorMessage;
const callbackParams = {
@@ -273,9 +253,9 @@ const DxcDateInput = forwardRef(
useEffect(() => {
if (value || value === "") {
- setDayjsDate(getDate(value, format, lastValidYear, setLastValidYear));
+ setDayjsDate(getDate(value, formatter, lastValidYear, setLastValidYear));
}
- }, [value, format, lastValidYear]);
+ }, [value, formatter, lastValidYear]);
useEffect(() => {
if (!disabled) {
@@ -312,7 +292,7 @@ const DxcDateInput = forwardRef(
name={name}
defaultValue={defaultValue}
value={value ?? innerValue}
- placeholder={placeholder ? format.toUpperCase() : undefined}
+ placeholder={placeholder ? formatter.toUpperCase() : undefined}
action={{
onClick: openCalendar,
icon: "filled_calendar_today",
diff --git a/packages/lib/src/date-input/DatePicker.tsx b/packages/lib/src/date-input/DatePicker.tsx
index 723d8b4bd9..af68c49ff5 100644
--- a/packages/lib/src/date-input/DatePicker.tsx
+++ b/packages/lib/src/date-input/DatePicker.tsx
@@ -84,7 +84,7 @@ const DatePicker = ({ date, onDateSelect, id }: DatePickerPropsType): JSX.Elemen
const [innerDate, setInnerDate] = useState(date?.isValid() ? date : dayjs());
const [content, setContent] = useState("calendar");
const selectedDate = date?.isValid() ? date : dayjs(null);
- const translatedLabels = useContext(HalstackLanguageContext);
+ const translatedLabels = useContext(HalstackLanguageContext).labels;
const handleDateSelect = (chosenDate: Dayjs) => {
setInnerDate(chosenDate);
diff --git a/packages/lib/src/date-input/utils.tsx b/packages/lib/src/date-input/utils.tsx
new file mode 100644
index 0000000000..09d426a90b
--- /dev/null
+++ b/packages/lib/src/date-input/utils.tsx
@@ -0,0 +1,111 @@
+import { Dispatch, SetStateAction } from "react";
+import dayjs, { Dayjs } from "dayjs";
+import { DateType } from "./types";
+
+const INVIS_CHARS = /[\u200E\u200F\u061C]/g;
+
+export const getValueForPicker = (value: string, format: string) => dayjs(value, format.toUpperCase(), true);
+
+export const validateLocale = (locale: string): boolean => {
+ let valid = false;
+ try {
+ Intl.DateTimeFormat.supportedLocalesOf(locale);
+ valid = true;
+ } catch {
+ valid = false;
+ }
+ return valid;
+};
+
+export const getDate = (
+ value: string,
+ format: string,
+ lastValidYear: number | null,
+ setLastValidYear: Dispatch>
+) => {
+ if ((value || value === "") && format.toUpperCase().includes("YYYY")) {
+ return getValueForPicker(value, format);
+ }
+ let newDate = getValueForPicker(value, format);
+ if (lastValidYear == null) {
+ if (+newDate.format("YY") < 68) {
+ setLastValidYear(2000 + +newDate.format("YY"));
+ newDate = newDate.set("year", 2000 + +newDate.format("YY"));
+ } else {
+ setLastValidYear(1900 + +newDate.format("YY"));
+ newDate = newDate.set("year", 1900 + +newDate.format("YY"));
+ }
+ } else {
+ newDate = newDate.set("year", (lastValidYear <= 1999 ? 1900 : 2000) + +newDate.format("YY"));
+ }
+ return newDate;
+};
+
+export const getFormatFromLocale = (locale: string): string => {
+ const formatter = new Intl.DateTimeFormat(locale, {
+ year: "numeric",
+ month: "2-digit",
+ day: "2-digit",
+ });
+
+ const parts = formatter.formatToParts(new Date(1995, 11, 3));
+
+ const dateValues: Record = {
+ day: "dd",
+ month: "MM",
+ year: "yyyy",
+ };
+
+ // if dateValues[part.type] is undefined, it means that the part is a literal (like a separator), so we return the part.value without invisible characters
+ return parts.map((part) => dateValues[part.type] ?? part.value.replace(INVIS_CHARS, "")).join("");
+};
+
+export const getCalendarDays = (innerDate: Dayjs, firstDayOfWeek: number) => {
+ const monthDayCells: DateType[] = [];
+ const lastMonthNumberOfDays = innerDate.set("month", innerDate.get("month") - 1).endOf("month");
+ const firstDayOfMonth = (innerDate.startOf("month").day() - firstDayOfWeek + 7) % 7;
+ const daysInMonth = firstDayOfMonth + innerDate.daysInMonth();
+
+ for (let i = 0; i < 42; i++) {
+ if (i < firstDayOfMonth) {
+ // previous month days
+ monthDayCells.push({
+ day: lastMonthNumberOfDays.get("date") - firstDayOfMonth + i + 1,
+ month: innerDate.get("month") ? innerDate.get("month") - 1 : 11,
+ year: innerDate.set("month", innerDate.get("month") - 1).get("year"),
+ });
+ } else if (i < daysInMonth) {
+ // this month days
+ monthDayCells.push({
+ day: i - firstDayOfMonth + 1,
+ month: innerDate.get("month"),
+ year: innerDate.get("year"),
+ });
+ } else {
+ // next month days
+ monthDayCells.push({
+ day: i - daysInMonth + 1,
+ month: innerDate.get("month") === 11 ? 0 : innerDate.get("month") + 1,
+ year: innerDate.set("month", innerDate.get("month") + 1).get("year"),
+ });
+ }
+ }
+ return monthDayCells;
+};
+
+export const getDateToFocus = (selectedDate: Dayjs, innerDate: Dayjs, today: Dayjs) =>
+ selectedDate?.get("month") === innerDate.get("month") && selectedDate?.get("year") === innerDate.get("year")
+ ? selectedDate
+ : today.get("month") === innerDate.get("month") && today.get("year") === innerDate.get("year")
+ ? today
+ : innerDate.set("date", 1);
+
+export const isDaySelected = (date: DateType, selectedDate: Dayjs) =>
+ selectedDate?.get("month") === date.month &&
+ selectedDate?.get("year") === date.year &&
+ selectedDate?.get("date") === date.day;
+
+export const divideDaysIntoWeeks = (data: DateType[], weekSize: number) =>
+ Array.from({ length: Math.ceil(data.length / weekSize) }, (_, rowIndex) =>
+ data.slice(rowIndex * weekSize, (rowIndex + 1) * weekSize)
+ );
diff --git a/packages/lib/src/dialog/Dialog.tsx b/packages/lib/src/dialog/Dialog.tsx
index c739f9501c..9380331cee 100644
--- a/packages/lib/src/dialog/Dialog.tsx
+++ b/packages/lib/src/dialog/Dialog.tsx
@@ -68,7 +68,7 @@ const DxcDialog = ({
disableFocusLock = false,
}: DialogPropsType): JSX.Element => {
const id = useId();
- const translatedLabels = useContext(HalstackLanguageContext);
+ const translatedLabels = useContext(HalstackLanguageContext).labels;
const [portalContainer, setPortalContainer] = useState(null);
useEffect(() => {
diff --git a/packages/lib/src/file-input/FileInput.tsx b/packages/lib/src/file-input/FileInput.tsx
index d60bfc083d..c27d948078 100644
--- a/packages/lib/src/file-input/FileInput.tsx
+++ b/packages/lib/src/file-input/FileInput.tsx
@@ -133,7 +133,7 @@ const DxcFileInput = forwardRef(
const [isDragging, setIsDragging] = useState(false);
const [files, setFiles] = useState([]);
const fileInputId = `file-input-${useId()}`;
- const translatedLabels = useContext(HalstackLanguageContext);
+ const translatedLabels = useContext(HalstackLanguageContext).labels;
const checkFileSize = (file: File) => {
if (minSize && file.size < minSize) {
diff --git a/packages/lib/src/file-input/FileItem.tsx b/packages/lib/src/file-input/FileItem.tsx
index 5f3a577883..a857d9e43e 100644
--- a/packages/lib/src/file-input/FileItem.tsx
+++ b/packages/lib/src/file-input/FileItem.tsx
@@ -120,7 +120,7 @@ const FileItem = ({
tabIndex,
size = "medium",
}: FileItemProps): JSX.Element => {
- const translatedLabels = useContext(HalstackLanguageContext);
+ const translatedLabels = useContext(HalstackLanguageContext).labels;
const [hasTooltip, setHasTooltip] = useState(false);
const fileNameId = useId();
diff --git a/packages/lib/src/footer/Footer.tsx b/packages/lib/src/footer/Footer.tsx
index 41852c6e9c..15c5bfbada 100644
--- a/packages/lib/src/footer/Footer.tsx
+++ b/packages/lib/src/footer/Footer.tsx
@@ -215,7 +215,7 @@ const DxcFooter = ({
socialLinks,
tabIndex = 0,
}: FooterPropsType): JSX.Element => {
- const translatedLabels = useContext(HalstackLanguageContext);
+ const translatedLabels = useContext(HalstackLanguageContext).labels;
const themedLogos = useContext(HalstackLogosContext);
const footerLogo = useMemo(() => {
diff --git a/packages/lib/src/intl.d.ts b/packages/lib/src/intl.d.ts
new file mode 100644
index 0000000000..226338ac94
--- /dev/null
+++ b/packages/lib/src/intl.d.ts
@@ -0,0 +1,14 @@
+export {};
+declare global {
+ namespace Intl {
+ interface WeekInfo {
+ firstDay: number;
+ weekend: number[];
+ minimalDays: number;
+ }
+
+ interface Locale {
+ getWeekInfo(): WeekInfo;
+ }
+ }
+}
diff --git a/packages/lib/src/paginator/Paginator.tsx b/packages/lib/src/paginator/Paginator.tsx
index c691a766c9..fb536ae5a3 100644
--- a/packages/lib/src/paginator/Paginator.tsx
+++ b/packages/lib/src/paginator/Paginator.tsx
@@ -80,7 +80,7 @@ const DxcPaginator = ({
const maxItemsPerPage =
minItemsPerPage - 1 + itemsPerPage > totalItems ? totalItems : minItemsPerPage - 1 + itemsPerPage;
- const translatedLabels = useContext(HalstackLanguageContext);
+ const translatedLabels = useContext(HalstackLanguageContext).labels;
const containerRef = useRef(null);
const width = useWidth(containerRef);
diff --git a/packages/lib/src/password-input/PasswordInput.tsx b/packages/lib/src/password-input/PasswordInput.tsx
index cd3a040fea..4f1388023b 100644
--- a/packages/lib/src/password-input/PasswordInput.tsx
+++ b/packages/lib/src/password-input/PasswordInput.tsx
@@ -44,7 +44,7 @@ const DxcPasswordInput = forwardRef(
) => {
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
const inputRef = useRef(null);
- const { passwordInput } = useContext(HalstackLanguageContext);
+ const { passwordInput } = useContext(HalstackLanguageContext).labels;
useEffect(() => {
(() => {
diff --git a/packages/lib/src/quick-nav/QuickNav.tsx b/packages/lib/src/quick-nav/QuickNav.tsx
index 4131314a82..4f482da4a2 100644
--- a/packages/lib/src/quick-nav/QuickNav.tsx
+++ b/packages/lib/src/quick-nav/QuickNav.tsx
@@ -60,7 +60,7 @@ const Link = styled.a`
`;
export default function DxcQuickNav({ links, title }: QuickNavTypes) {
- const translatedLabels = useContext(HalstackLanguageContext);
+ const translatedLabels = useContext(HalstackLanguageContext).labels;
const isHashRouter = (): boolean => {
if (typeof window === "undefined") return false;
return window.location.href.includes("/#/");
diff --git a/packages/lib/src/radio-group/RadioGroup.tsx b/packages/lib/src/radio-group/RadioGroup.tsx
index 357a276ca6..2768304537 100644
--- a/packages/lib/src/radio-group/RadioGroup.tsx
+++ b/packages/lib/src/radio-group/RadioGroup.tsx
@@ -46,7 +46,7 @@ const DxcRadioGroup = forwardRef(
const id = `radio-group-${useId()}`;
const labelId = `label-${id}`;
const errorId = `error-${id}`;
- const translatedLabels = useContext(HalstackLanguageContext);
+ const translatedLabels = useContext(HalstackLanguageContext).labels;
const innerOptions = useMemo(
() =>
optional
diff --git a/packages/lib/src/search-bar/SearchBar.tsx b/packages/lib/src/search-bar/SearchBar.tsx
index 42440567c4..dd7bfe5736 100644
--- a/packages/lib/src/search-bar/SearchBar.tsx
+++ b/packages/lib/src/search-bar/SearchBar.tsx
@@ -68,7 +68,7 @@ const DxcSearchBar = ({
onEnter,
placeholder = "Search...",
}: SearchBarProps) => {
- const translatedLabels = useContext(HalstackLanguageContext);
+ const translatedLabels = useContext(HalstackLanguageContext).labels;
const inputRef = useRef(null);
const [innerValue, setInnerValue] = useState("");
diff --git a/packages/lib/src/search-bar/SearchBarTrigger.tsx b/packages/lib/src/search-bar/SearchBarTrigger.tsx
index e0b49d0028..f1e5c1bace 100644
--- a/packages/lib/src/search-bar/SearchBarTrigger.tsx
+++ b/packages/lib/src/search-bar/SearchBarTrigger.tsx
@@ -4,7 +4,7 @@ import { HalstackLanguageContext } from "../HalstackContext";
import { SearchBarTriggerProps } from "./types";
const DxcSearchBarTrigger = ({ onTriggerClick }: SearchBarTriggerProps) => {
- const translatedLabels = useContext(HalstackLanguageContext);
+ const translatedLabels = useContext(HalstackLanguageContext).labels;
return (
{
- const translatedLabels = useContext(HalstackLanguageContext);
+ const translatedLabels = useContext(HalstackLanguageContext).labels;
const virtuosoRef = useRef(null);
const isSearchEmpty = searchable && (options.length === 0 || !groupsHaveOptions(options));
@@ -288,7 +288,7 @@ const NonVirtualizedListbox = ({
styles,
visualFocusIndex,
}: ListboxProps) => {
- const translatedLabels = useContext(HalstackLanguageContext);
+ const translatedLabels = useContext(HalstackLanguageContext).labels;
const listboxRef = useRef(null);
let globalMappingIndex = (multiple ? enableSelectAll : optional) ? 0 : -1;
diff --git a/packages/lib/src/select/Select.tsx b/packages/lib/src/select/Select.tsx
index 07b982e4db..0544cfb84e 100644
--- a/packages/lib/src/select/Select.tsx
+++ b/packages/lib/src/select/Select.tsx
@@ -220,7 +220,7 @@ const DxcSelect = forwardRef(
const selectSearchInputRef = useRef(null);
const width = useWidth(selectRef);
- const translatedLabels = useContext(HalstackLanguageContext);
+ const translatedLabels = useContext(HalstackLanguageContext).labels;
const optionalItem = useMemo(() => ({ label: placeholder, value: "" }), [placeholder]);
const filteredOptions = useMemo(
diff --git a/packages/lib/src/switch/Switch.tsx b/packages/lib/src/switch/Switch.tsx
index 704db66dcc..9c7a41fd32 100644
--- a/packages/lib/src/switch/Switch.tsx
+++ b/packages/lib/src/switch/Switch.tsx
@@ -130,7 +130,7 @@ const DxcSwitch = forwardRef(
ref
) => {
const [innerChecked, setInnerChecked] = useState(defaultChecked);
- const translatedLabels = useContext(HalstackLanguageContext);
+ const translatedLabels = useContext(HalstackLanguageContext).labels;
const handleOnChange = () => {
if (checked == null) setInnerChecked((currentInnerChecked) => !currentInnerChecked);
diff --git a/packages/lib/src/tabs/Tabs.tsx b/packages/lib/src/tabs/Tabs.tsx
index 4d96e056ba..213c99da5e 100644
--- a/packages/lib/src/tabs/Tabs.tsx
+++ b/packages/lib/src/tabs/Tabs.tsx
@@ -104,7 +104,7 @@ const DxcTabs = ({ children, iconPosition = "left", margin, tabIndex = 0 }: Tabs
const [totalTabsWidth, setTotalTabsWidth] = useState(0);
const refTabListContainer = useRef(null);
const refTabList = useRef(null);
- const translatedLabels = useContext(HalstackLanguageContext);
+ const translatedLabels = useContext(HalstackLanguageContext).labels;
const viewWidth = useWidth(refTabList);
const contextValue = useMemo(() => {
const focusedChild = innerFocusIndex != null ? childrenArray[innerFocusIndex] : null;
diff --git a/packages/lib/src/text-input/Suggestions.tsx b/packages/lib/src/text-input/Suggestions.tsx
index 7757d106b0..7355ada6f3 100644
--- a/packages/lib/src/text-input/Suggestions.tsx
+++ b/packages/lib/src/text-input/Suggestions.tsx
@@ -54,7 +54,7 @@ const Suggestions = ({
value,
visualFocusIndex,
}: SuggestionsProps) => {
- const translatedLabels = useContext(HalstackLanguageContext);
+ const translatedLabels = useContext(HalstackLanguageContext).labels;
const listboxRef = useRef(null);
useEffect(() => {
diff --git a/packages/lib/src/text-input/TextInput.tsx b/packages/lib/src/text-input/TextInput.tsx
index ab40af511e..2b29115741 100644
--- a/packages/lib/src/text-input/TextInput.tsx
+++ b/packages/lib/src/text-input/TextInput.tsx
@@ -143,7 +143,7 @@ const DxcTextInput = forwardRef(
const inputId = `input-${useId()}`;
const autosuggestId = `suggestions-${inputId}`;
const errorId = `error-${inputId}`;
- const translatedLabels = useContext(HalstackLanguageContext);
+ const translatedLabels = useContext(HalstackLanguageContext).labels;
const numberInputContext = useContext(NumberInputContext);
const inputRef = useRef(null);
const inputContainerRef = useRef(null);
diff --git a/packages/lib/src/textarea/Textarea.tsx b/packages/lib/src/textarea/Textarea.tsx
index c2013092fc..b180dcb387 100644
--- a/packages/lib/src/textarea/Textarea.tsx
+++ b/packages/lib/src/textarea/Textarea.tsx
@@ -95,7 +95,7 @@ const DxcTextarea = forwardRef(
const [innerValue, setInnerValue] = useState(defaultValue);
const textareaId = `textarea-${useId()}`;
const errorId = `error-${textareaId}`;
- const translatedLabels = useContext(HalstackLanguageContext);
+ const translatedLabels = useContext(HalstackLanguageContext).labels;
const textareaRef = useRef(null);
const prevValueRef = useRef(null);
diff --git a/packages/lib/src/time-input/TimeInput.tsx b/packages/lib/src/time-input/TimeInput.tsx
index d18a4310fa..cb0eb0c235 100644
--- a/packages/lib/src/time-input/TimeInput.tsx
+++ b/packages/lib/src/time-input/TimeInput.tsx
@@ -90,7 +90,7 @@ const DxcTimeInput = forwardRef(
const secondRef = useRef(null);
const dayPeriodRef = useRef(null);
const isControlled = value !== undefined;
- const translatedLabels = useContext(HalstackLanguageContext);
+ const translatedLabels = useContext(HalstackLanguageContext).labels;
useEffect(() => {
const time = value || defaultValue || undefined;
diff --git a/packages/lib/src/toast/Toast.tsx b/packages/lib/src/toast/Toast.tsx
index 8f73a1ef15..ca7eccca31 100644
--- a/packages/lib/src/toast/Toast.tsx
+++ b/packages/lib/src/toast/Toast.tsx
@@ -93,7 +93,7 @@ const DxcToast = ({
const [isClosing, setIsClosing] = useState(false);
const toastRef = useRef(null);
const previouslyFocusedElement = useRef(null);
- const translatedLabels = useContext(HalstackLanguageContext);
+ const translatedLabels = useContext(HalstackLanguageContext).labels;
// Timeouts
const clearClosingAnimationTimer = useTimeout(() => setIsClosing(true), loading ? undefined : duration - 300);
diff --git a/packages/lib/tsconfig.json b/packages/lib/tsconfig.json
index 5330ab9f7f..e0794eabca 100644
--- a/packages/lib/tsconfig.json
+++ b/packages/lib/tsconfig.json
@@ -3,8 +3,9 @@
"compilerOptions": {
"jsx": "react-jsx",
"outDir": "dist",
- "forceConsistentCasingInFileNames": true
+ "forceConsistentCasingInFileNames": true,
+ "lib": ["DOM", "DOM.Iterable", "ES2022", "ESNext"]
},
- "include": ["src", "test", ".", ".storybook/**/*", "src/global.d.ts"],
+ "include": ["src", "test", ".", ".storybook/**/*", "src/**/*.d.ts"],
"exclude": ["node_modules", "dist", "coverage", ".turbo"]
}
|