@lumx/react 3.6.5 → 3.6.6-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -7,8 +7,8 @@
7
7
  },
8
8
  "dependencies": {
9
9
  "@juggle/resize-observer": "^3.2.0",
10
- "@lumx/core": "^3.6.5",
11
- "@lumx/icons": "^3.6.5",
10
+ "@lumx/core": "^3.6.6-alpha.0",
11
+ "@lumx/icons": "^3.6.6-alpha.0",
12
12
  "@popperjs/core": "^2.5.4",
13
13
  "body-scroll-lock": "^3.1.5",
14
14
  "classnames": "^2.3.2",
@@ -112,5 +112,5 @@
112
112
  "build:storybook": "storybook build"
113
113
  },
114
114
  "sideEffects": false,
115
- "version": "3.6.5"
115
+ "version": "3.6.6-alpha.0"
116
116
  }
@@ -0,0 +1,49 @@
1
+ import React from 'react';
2
+
3
+ import { Chip, ChipGroup, Size } from '@lumx/react';
4
+
5
+ export default {
6
+ title: 'LumX components/chip/ChipGroup',
7
+ component: ChipGroup,
8
+ args: ChipGroup.defaultProps,
9
+ argTypes: {},
10
+ };
11
+
12
+ const chips = (
13
+ <>
14
+ <Chip size={Size.s}>Apricot</Chip>
15
+ <Chip size={Size.s}>Apple</Chip>
16
+ <Chip size={Size.s}>Banana</Chip>
17
+ <Chip size={Size.s}>Blueberry</Chip>
18
+ <Chip size={Size.s}>Lemon</Chip>
19
+ <Chip size={Size.s}>Orange</Chip>
20
+ <Chip size={Size.s}>Peach</Chip>
21
+ <Chip size={Size.s}>Pear</Chip>
22
+ <Chip size={Size.s}>Pineapple</Chip>
23
+ <Chip size={Size.s}>Melon</Chip>
24
+ <Chip size={Size.s}>Raspberry</Chip>
25
+ <Chip size={Size.s}>Strawberry</Chip>
26
+ <Chip size={Size.s}>Watermelon</Chip>
27
+ </>
28
+ );
29
+
30
+ /**
31
+ * Default chip group
32
+ */
33
+ export const Default = {
34
+ args: {
35
+ children: chips,
36
+ },
37
+ };
38
+
39
+ /**
40
+ * Small chip group
41
+ */
42
+ export const Small = {
43
+ args: {
44
+ children: chips,
45
+ style: {
46
+ width: '200px',
47
+ },
48
+ },
49
+ };
@@ -3,7 +3,6 @@ import React from 'react';
3
3
  import { render } from '@testing-library/react';
4
4
  import { getByClassName } from '@lumx/react/testing/utils/queries';
5
5
  import { commonTestsSuiteRTL } from '@lumx/react/testing/utils';
6
- import { Alignment } from '@lumx/react';
7
6
  import { ChipGroup, ChipGroupProps } from './ChipGroup';
8
7
  import { Chip } from './Chip';
9
8
 
@@ -29,12 +28,6 @@ describe('<ChipGroup />', () => {
29
28
  const { chipGroup } = setup();
30
29
  expect(chipGroup).toBeInTheDocument();
31
30
  expect(chipGroup).toHaveClass(CLASSNAME);
32
- expect(chipGroup).toHaveClass(`${CLASSNAME}--align-left`);
33
- });
34
-
35
- it('should render with align', () => {
36
- const { chipGroup } = setup({ align: Alignment.right });
37
- expect(chipGroup).toHaveClass(`${CLASSNAME}--align-right`);
38
31
  });
39
32
  });
40
33
 
@@ -1,4 +1,4 @@
1
- import { Alignment, HorizontalAlignment } from '@lumx/react/components';
1
+ import { HorizontalAlignment } from '@lumx/react/components';
2
2
  import React, { forwardRef, ReactNode } from 'react';
3
3
 
4
4
  import classNames from 'classnames';
@@ -12,7 +12,10 @@ import { useChipGroupNavigation } from '@lumx/react/hooks/useChipGroupNavigation
12
12
  * Defines the props of the component.
13
13
  */
14
14
  export interface ChipGroupProps extends GenericProps {
15
- /** Chip horizontal alignment. */
15
+ /**
16
+ * Chip horizontal alignment.
17
+ * @deprecated
18
+ */
16
19
  align?: HorizontalAlignment;
17
20
  /** List of Chip. */
18
21
  children: ReactNode;
@@ -21,9 +24,7 @@ export interface ChipGroupProps extends GenericProps {
21
24
  /**
22
25
  * Component default props.
23
26
  */
24
- const DEFAULT_PROPS: Partial<ChipGroupProps> = {
25
- align: Alignment.left,
26
- };
27
+ const DEFAULT_PROPS: Partial<ChipGroupProps> = {};
27
28
 
28
29
  /**
29
30
  * Component display name.
@@ -45,7 +46,6 @@ const CLASSNAME = getRootClassName(COMPONENT_NAME);
45
46
  const InternalChipGroup: Comp<ChipGroupProps, HTMLDivElement> = forwardRef((props, ref) => {
46
47
  const { align, children, className, ...forwardedProps } = props;
47
48
  const chipGroupClassName = handleBasicClasses({
48
- align,
49
49
  prefix: CLASSNAME,
50
50
  });
51
51
 
@@ -38,7 +38,7 @@ describe(`<${DatePickerControlled.displayName}>`, () => {
38
38
  expect(month).toHaveTextContent('février 2017');
39
39
 
40
40
  const selected = queryByClassName(datePickerControlled, `${CLASSNAME}__month-day--is-selected`);
41
- expect(selected).toBe(screen.queryByRole('button', { name: '22' }));
41
+ expect(selected).toBe(screen.queryByRole('button', { name: /22 février 2017/i }));
42
42
  });
43
43
 
44
44
  commonTestsSuiteRTL(setup, {
@@ -8,6 +8,7 @@ import { isSameDay } from '@lumx/react/utils/date/isSameDay';
8
8
  import { getCurrentLocale } from '@lumx/react/utils/locale/getCurrentLocale';
9
9
  import { parseLocale } from '@lumx/react/utils/locale/parseLocale';
10
10
  import { Locale } from '@lumx/react/utils/locale/types';
11
+ import { usePreviousValue } from '@lumx/react/hooks/usePreviousValue';
11
12
  import { CLASSNAME } from './constants';
12
13
 
13
14
  /**
@@ -53,6 +54,15 @@ export const DatePickerControlled: Comp<DatePickerControlledProps, HTMLDivElemen
53
54
  return getMonthCalendar(localeObj, selectedMonth, minDate, maxDate);
54
55
  }, [locale, minDate, maxDate, selectedMonth]);
55
56
 
57
+ const prevSelectedMonth = usePreviousValue(selectedMonth);
58
+ const monthHasChanged = prevSelectedMonth && !isSameDay(selectedMonth, prevSelectedMonth);
59
+
60
+ // Only set the aria-live after the month has changed otherwise it can get announced on mount when used in the popover dialog
61
+ const [labelAriaLive, setLabelAriaLive] = React.useState<'polite'>();
62
+ React.useEffect(() => {
63
+ if (monthHasChanged) setLabelAriaLive('polite');
64
+ }, [monthHasChanged]);
65
+
56
66
  return (
57
67
  <div ref={ref} className={`${CLASSNAME}`}>
58
68
  <Toolbar
@@ -74,16 +84,19 @@ export const DatePickerControlled: Comp<DatePickerControlledProps, HTMLDivElemen
74
84
  />
75
85
  }
76
86
  label={
77
- <span className={`${CLASSNAME}__month`}>
87
+ <span className={`${CLASSNAME}__month`} aria-live={labelAriaLive}>
78
88
  {selectedMonth.toLocaleDateString(locale, { year: 'numeric', month: 'long' })}
79
89
  </span>
80
90
  }
81
91
  />
82
92
  <div className={`${CLASSNAME}__calendar`}>
83
93
  <div className={`${CLASSNAME}__week-days ${CLASSNAME}__days-wrapper`}>
84
- {weekDays.map(({ letter, number }) => (
94
+ {weekDays.map(({ letter, number, long }) => (
85
95
  <div key={number} className={`${CLASSNAME}__day-wrapper`}>
86
- <span className={`${CLASSNAME}__week-day`}>{letter.toLocaleUpperCase()}</span>
96
+ <span className={`${CLASSNAME}__week-day`} aria-hidden>
97
+ {letter.toLocaleUpperCase()}
98
+ </span>
99
+ <span className="visually-hidden">{long}</span>
87
100
  </div>
88
101
  ))}
89
102
  </div>
@@ -109,7 +122,16 @@ export const DatePickerControlled: Comp<DatePickerControlledProps, HTMLDivElemen
109
122
  type="button"
110
123
  onClick={() => onChange(date)}
111
124
  >
112
- <span>{date.toLocaleDateString(locale, { day: 'numeric' })}</span>
125
+ <span aria-hidden>
126
+ {date.toLocaleDateString(locale, { day: 'numeric' })}
127
+ </span>
128
+ <span className="visually-hidden">
129
+ {date.toLocaleDateString(locale, {
130
+ day: 'numeric',
131
+ month: 'long',
132
+ year: 'numeric',
133
+ })}
134
+ </span>
113
135
  </button>
114
136
  )}
115
137
  </div>
@@ -1,27 +1,22 @@
1
- import React, { forwardRef, SyntheticEvent, useCallback, useRef, useState } from 'react';
1
+ import React, { forwardRef, SyntheticEvent, useCallback, useRef } from 'react';
2
2
 
3
- import { DatePicker, IconButtonProps, Placement, Popover, TextField } from '@lumx/react';
4
- import { useFocusTrap } from '@lumx/react/hooks/useFocusTrap';
5
- import { useFocus } from '@lumx/react/hooks/useFocus';
3
+ import { DatePicker, IconButtonProps, Placement, PopoverDialog, TextField, TextFieldProps } from '@lumx/react';
6
4
  import { Comp, GenericProps } from '@lumx/react/utils/type';
7
5
  import { getCurrentLocale } from '@lumx/react/utils/locale/getCurrentLocale';
6
+ import { useBooleanState } from '@lumx/react/hooks/useBooleanState';
8
7
 
9
8
  /**
10
9
  * Defines the props of the component.
11
10
  */
12
- export interface DatePickerFieldProps extends GenericProps {
11
+ export interface DatePickerFieldProps extends Omit<TextFieldProps, 'value' | 'onChange'>, GenericProps {
13
12
  /** Default month. */
14
13
  defaultMonth?: Date;
15
- /** Whether the component is disabled or not. */
16
- isDisabled?: boolean;
17
14
  /** Locale (language or region) to use. */
18
15
  locale?: string;
19
16
  /** Date after which dates can't be selected. */
20
17
  maxDate?: Date;
21
18
  /** Date before which dates can't be selected. */
22
19
  minDate?: Date;
23
- /** Native input name property. */
24
- name?: string;
25
20
  /** Props to pass to the next month button (minus those already set by the DatePickerControlled props). */
26
21
  nextButtonProps: Pick<IconButtonProps, 'label'> & Omit<IconButtonProps, 'label' | 'onClick' | 'icon' | 'emphasis'>;
27
22
  /** Props to pass to the previous month button (minus those already set by the DatePickerControlled props). */
@@ -60,44 +55,39 @@ export const DatePickerField: Comp<DatePickerFieldProps, HTMLDivElement> = forwa
60
55
  value,
61
56
  ...forwardedProps
62
57
  } = props;
63
- const [wrapperElement, setWrapperElement] = useState<HTMLDivElement | null>(null);
64
58
  const anchorRef = useRef(null);
65
59
 
66
- const [isOpen, setIsOpen] = useState(false);
60
+ const [isOpen, close, , toggleOpen] = useBooleanState(false);
67
61
 
68
- const toggleSimpleMenu = () => {
69
- setIsOpen(!isOpen);
70
- };
71
-
72
- const onClose = useCallback(() => {
73
- setIsOpen(false);
74
- }, []);
75
-
76
- useFocus(anchorRef.current, isOpen);
77
- const handleKeyboardNav = (evt: React.KeyboardEvent) => {
78
- if ((evt.key === 'Enter' || evt.key === ' ') && toggleSimpleMenu) {
79
- toggleSimpleMenu();
80
- }
81
- };
82
-
83
- // Handle focus trap.
84
- const [todayOrSelectedDate, setTodayOrSelectedDate] = useState<HTMLButtonElement | null>(null);
85
- useFocusTrap(isOpen && wrapperElement, todayOrSelectedDate);
86
-
87
- const onTextFieldChange = (textFieldValue: string, textFieldName?: string, event?: SyntheticEvent) => {
88
- if (!textFieldValue) {
89
- onChange(undefined, textFieldName, event);
90
- }
91
- };
92
-
93
- const onDatePickerChange = (newDate?: Date) => {
94
- onChange(newDate, name);
95
- onClose();
96
- };
62
+ const handleKeyboardNav = useCallback(
63
+ (evt: React.KeyboardEvent) => {
64
+ if (evt.key === 'Enter' || evt.key === ' ') {
65
+ toggleOpen();
66
+ }
67
+ },
68
+ [toggleOpen],
69
+ );
70
+ const onTextFieldChange = useCallback(
71
+ (textFieldValue: string, textFieldName?: string, event?: SyntheticEvent) => {
72
+ if (!textFieldValue) {
73
+ onChange(undefined, textFieldName, event);
74
+ }
75
+ },
76
+ [onChange],
77
+ );
78
+ const onDatePickerChange = useCallback(
79
+ (newDate?: Date) => {
80
+ onChange(newDate, name);
81
+ close();
82
+ },
83
+ [name, onChange, close],
84
+ );
97
85
 
98
86
  // Format date for text field
99
87
  const textFieldValue = value?.toLocaleDateString(locale, { year: 'numeric', month: 'long', day: 'numeric' }) || '';
100
88
 
89
+ const todayOrSelectedDateRef = React.useRef(null);
90
+
101
91
  return (
102
92
  <>
103
93
  <TextField
@@ -107,34 +97,36 @@ export const DatePickerField: Comp<DatePickerFieldProps, HTMLDivElement> = forwa
107
97
  forceFocusStyle={isOpen}
108
98
  textFieldRef={anchorRef}
109
99
  value={textFieldValue}
110
- onClick={toggleSimpleMenu}
100
+ onClick={toggleOpen}
111
101
  onChange={onTextFieldChange}
112
102
  onKeyPress={handleKeyboardNav}
113
103
  isDisabled={isDisabled}
114
104
  readOnly
105
+ aria-haspopup="dialog"
115
106
  />
116
107
  {isOpen ? (
117
- <Popover
108
+ <PopoverDialog
109
+ // Can't use `aria-labelledby` targeting the text field label element (not correctly read on some screen readers)
110
+ aria-label={forwardedProps.label}
118
111
  anchorRef={anchorRef}
119
112
  placement={Placement.BOTTOM_START}
120
113
  isOpen={isOpen}
121
- onClose={onClose}
122
- closeOnClickAway
123
- closeOnEscape
114
+ onClose={close}
115
+ // On open, focus the selected day or today
116
+ focusElement={todayOrSelectedDateRef}
124
117
  >
125
118
  <DatePicker
126
- ref={setWrapperElement}
127
119
  locale={locale}
128
120
  maxDate={maxDate}
129
121
  minDate={minDate}
130
122
  value={value}
123
+ todayOrSelectedDateRef={todayOrSelectedDateRef}
131
124
  onChange={onDatePickerChange}
132
- todayOrSelectedDateRef={setTodayOrSelectedDate}
133
125
  defaultMonth={defaultMonth}
134
126
  nextButtonProps={nextButtonProps}
135
127
  previousButtonProps={previousButtonProps}
136
128
  />
137
- </Popover>
129
+ </PopoverDialog>
138
130
  ) : null}
139
131
  </>
140
132
  );
@@ -22,6 +22,17 @@ describe(`<${PopoverDialog.displayName}>`, () => {
22
22
  expect(within(dialog).getAllByRole('button')[0]).toHaveFocus();
23
23
  });
24
24
 
25
+ it('should work with aria-label', async () => {
26
+ const label = 'Test Label';
27
+ render(<WithButtonTrigger aria-label={label} />);
28
+
29
+ // Open popover
30
+ const triggerElement = screen.getByRole('button', { name: 'Open popover' });
31
+ await userEvent.click(triggerElement);
32
+
33
+ expect(await screen.findByRole('dialog', { name: label })).toBeInTheDocument();
34
+ });
35
+
25
36
  it('should trap focus', async () => {
26
37
  const label = 'Test Label';
27
38
  render(<WithButtonTrigger label={label} />);
@@ -35,7 +35,15 @@ const DEFAULT_PROPS: Partial<PopoverDialogProps> = {};
35
35
  * * Closes on click away and escape.
36
36
  */
37
37
  export const PopoverDialog: Comp<PopoverDialogProps, HTMLDivElement> = forwardRef((props, ref) => {
38
- const { children, isOpen, focusElement, label, className, ...forwardedProps } = props;
38
+ const {
39
+ children,
40
+ isOpen,
41
+ focusElement,
42
+ 'aria-label': ariaLabel,
43
+ label = ariaLabel,
44
+ className,
45
+ ...forwardedProps
46
+ } = props;
39
47
 
40
48
  return (
41
49
  <Popover
@@ -15,7 +15,18 @@ import get from 'lodash/get';
15
15
  import { uid } from 'uid';
16
16
 
17
17
  import { mdiAlertCircle, mdiCheckCircle, mdiCloseCircle } from '@lumx/icons';
18
- import { Emphasis, Icon, IconButton, IconButtonProps, InputHelper, InputLabel, Kind, Size, Theme } from '@lumx/react';
18
+ import {
19
+ Emphasis,
20
+ Icon,
21
+ IconButton,
22
+ IconButtonProps,
23
+ InputHelper,
24
+ InputLabel,
25
+ InputLabelProps,
26
+ Kind,
27
+ Size,
28
+ Theme,
29
+ } from '@lumx/react';
19
30
  import { Comp, GenericProps, HasTheme } from '@lumx/react/utils/type';
20
31
  import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
21
32
  import { mergeRefs } from '@lumx/react/utils/mergeRefs';
@@ -53,6 +64,8 @@ export interface TextFieldProps extends GenericProps, HasTheme {
53
64
  isValid?: boolean;
54
65
  /** Label text. */
55
66
  label?: string;
67
+ /** Additional label props. */
68
+ labelProps?: InputLabelProps;
56
69
  /** Max string length the input accepts (constrains the input and displays a character counter). */
57
70
  maxLength?: number;
58
71
  /** Minimum number of rows displayed in multiline mode (requires `multiline` to be enabled). */
@@ -266,6 +279,7 @@ export const TextField: Comp<TextFieldProps, HTMLDivElement> = forwardRef((props
266
279
  isRequired,
267
280
  isValid,
268
281
  label,
282
+ labelProps,
269
283
  maxLength,
270
284
  minimumRows,
271
285
  multiline,
@@ -355,6 +369,7 @@ export const TextField: Comp<TextFieldProps, HTMLDivElement> = forwardRef((props
355
369
  <div className={`${CLASSNAME}__header`}>
356
370
  {label && (
357
371
  <InputLabel
372
+ {...labelProps}
358
373
  htmlFor={textFieldId}
359
374
  className={`${CLASSNAME}__label`}
360
375
  isRequired={isRequired}
@@ -180,7 +180,7 @@ export const Thumbnail: Comp<ThumbnailProps> = forwardRef((props, ref) => {
180
180
  fillHeight && `${CLASSNAME}--fill-height`,
181
181
  )}
182
182
  >
183
- <div className={`${CLASSNAME}__background`}>
183
+ <span className={`${CLASSNAME}__background`}>
184
184
  <img
185
185
  {...imgProps}
186
186
  style={{
@@ -203,15 +203,15 @@ export const Thumbnail: Comp<ThumbnailProps> = forwardRef((props, ref) => {
203
203
  loading={loading}
204
204
  />
205
205
  {!isLoading && hasError && (
206
- <div className={`${CLASSNAME}__fallback`}>
206
+ <span className={`${CLASSNAME}__fallback`}>
207
207
  {hasIconErrorFallback ? (
208
208
  <Icon icon={fallback as string} size={Size.xxs} theme={theme} />
209
209
  ) : (
210
210
  fallback
211
211
  )}
212
- </div>
212
+ </span>
213
213
  )}
214
- </div>
214
+ </span>
215
215
  {badge &&
216
216
  React.cloneElement(badge, { className: classNames(`${CLASSNAME}__badge`, badge.props.className) })}
217
217
  </Wrapper>
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+
3
+ /**
4
+ * Returns the value from the previous render.
5
+ */
6
+ export function usePreviousValue<V>(value: V): V | undefined {
7
+ const prevValueRef = React.useRef<V>();
8
+ const prevValue = prevValueRef.current;
9
+ React.useEffect(() => {
10
+ prevValueRef.current = value;
11
+ }, [value]);
12
+ return prevValue;
13
+ }
@@ -10,13 +10,13 @@ describe(getMonthCalendar.name, () => {
10
10
 
11
11
  expect(month).toEqual({
12
12
  weekDays: [
13
- { letter: 'L', number: 1 },
14
- { letter: 'M', number: 2 },
15
- { letter: 'M', number: 3 },
16
- { letter: 'J', number: 4 },
17
- { letter: 'V', number: 5 },
18
- { letter: 'S', number: 6 },
19
- { letter: 'D', number: 0 },
13
+ { long: 'lundi', letter: 'L', number: 1 },
14
+ { long: 'mardi', letter: 'M', number: 2 },
15
+ { long: 'mercredi', letter: 'M', number: 3 },
16
+ { long: 'jeudi', letter: 'J', number: 4 },
17
+ { long: 'vendredi', letter: 'V', number: 5 },
18
+ { long: 'samedi', letter: 'S', number: 6 },
19
+ { long: 'dimanche', letter: 'D', number: 0 },
20
20
  ],
21
21
  weeks: [
22
22
  {
@@ -70,13 +70,13 @@ describe(getMonthCalendar.name, () => {
70
70
 
71
71
  expect(month).toEqual({
72
72
  weekDays: [
73
- { letter: 'S', number: 0 },
74
- { letter: 'M', number: 1 },
75
- { letter: 'T', number: 2 },
76
- { letter: 'W', number: 3 },
77
- { letter: 'T', number: 4 },
78
- { letter: 'F', number: 5 },
79
- { letter: 'S', number: 6 },
73
+ { long: 'Sunday', letter: 'S', number: 0 },
74
+ { long: 'Monday', letter: 'M', number: 1 },
75
+ { long: 'Tuesday', letter: 'T', number: 2 },
76
+ { long: 'Wednesday', letter: 'W', number: 3 },
77
+ { long: 'Thursday', letter: 'T', number: 4 },
78
+ { long: 'Friday', letter: 'F', number: 5 },
79
+ { long: 'Saturday', letter: 'S', number: 6 },
80
80
  ],
81
81
  weeks: [
82
82
  {
@@ -10,39 +10,39 @@ describe(getWeekDays.name, () => {
10
10
  it('should list french week days', () => {
11
11
  const weekDays = getWeekDays(french);
12
12
  expect(weekDays).toEqual([
13
- { letter: 'L', number: 1 },
14
- { letter: 'M', number: 2 },
15
- { letter: 'M', number: 3 },
16
- { letter: 'J', number: 4 },
17
- { letter: 'V', number: 5 },
18
- { letter: 'S', number: 6 },
19
- { letter: 'D', number: 0 },
13
+ { long: 'lundi', letter: 'L', number: 1 },
14
+ { long: 'mardi', letter: 'M', number: 2 },
15
+ { long: 'mercredi', letter: 'M', number: 3 },
16
+ { long: 'jeudi', letter: 'J', number: 4 },
17
+ { long: 'vendredi', letter: 'V', number: 5 },
18
+ { long: 'samedi', letter: 'S', number: 6 },
19
+ { long: 'dimanche', letter: 'D', number: 0 },
20
20
  ]);
21
21
  });
22
22
 
23
23
  it('should list US week days', () => {
24
24
  const weekDays = getWeekDays(englishUS);
25
25
  expect(weekDays).toEqual([
26
- { letter: 'S', number: 0 },
27
- { letter: 'M', number: 1 },
28
- { letter: 'T', number: 2 },
29
- { letter: 'W', number: 3 },
30
- { letter: 'T', number: 4 },
31
- { letter: 'F', number: 5 },
32
- { letter: 'S', number: 6 },
26
+ { long: 'Sunday', letter: 'S', number: 0 },
27
+ { long: 'Monday', letter: 'M', number: 1 },
28
+ { long: 'Tuesday', letter: 'T', number: 2 },
29
+ { long: 'Wednesday', letter: 'W', number: 3 },
30
+ { long: 'Thursday', letter: 'T', number: 4 },
31
+ { long: 'Friday', letter: 'F', number: 5 },
32
+ { long: 'Saturday', letter: 'S', number: 6 },
33
33
  ]);
34
34
  });
35
35
 
36
36
  it('should list fa-ir week days', () => {
37
37
  const weekDays = getWeekDays(farsi);
38
38
  expect(weekDays).toEqual([
39
- { letter: 'ش', number: 6 },
40
- { letter: 'ی', number: 0 },
41
- { letter: 'د', number: 1 },
42
- { letter: 'س', number: 2 },
43
- { letter: 'چ', number: 3 },
44
- { letter: 'پ', number: 4 },
45
- { letter: 'ج', number: 5 },
39
+ { long: 'شنبه', letter: 'ش', number: 6 },
40
+ { long: 'یکشنبه', letter: 'ی', number: 0 },
41
+ { long: 'دوشنبه', letter: 'د', number: 1 },
42
+ { long: 'سه‌شنبه', letter: 'س', number: 2 },
43
+ { long: 'چهارشنبه', letter: 'چ', number: 3 },
44
+ { long: 'پنجشنبه', letter: 'پ', number: 4 },
45
+ { long: 'جمعه', letter: 'ج', number: 5 },
46
46
  ]);
47
47
  });
48
48
  });
@@ -1,7 +1,7 @@
1
1
  import { Locale } from '@lumx/react/utils/locale/types';
2
2
  import { getFirstDayOfWeek } from './getFirstDayOfWeek';
3
3
 
4
- export type WeekDayInfo = { letter: string; number: number };
4
+ export type WeekDayInfo = { letter: string; number: number; long: string };
5
5
 
6
6
  export const DAYS_PER_WEEK = 7;
7
7
 
@@ -22,10 +22,12 @@ export const getWeekDays = (locale: Locale): Array<WeekDayInfo> => {
22
22
  for (let i = 0; i < DAYS_PER_WEEK; i++) {
23
23
  // Single letter week day (ex: "M" for "Monday", "L" for "Lundi", etc.)
24
24
  const letter = iterDate.toLocaleDateString(locale.code, { weekday: 'narrow' });
25
+ // Weed day long notation
26
+ const long = iterDate.toLocaleDateString(locale.code, { weekday: 'long' });
25
27
  // Day number (1-based index starting on Monday)
26
28
  const number = iterDate.getDay();
27
29
 
28
- weekDays.push({ letter, number });
30
+ weekDays.push({ letter, number, long });
29
31
  iterDate.setDate(iterDate.getDate() + 1);
30
32
  }
31
33
  return weekDays;