@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/index.d.ts +7 -6
- package/index.js +211 -181
- package/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/chip/ChipGroup.stories.tsx +49 -0
- package/src/components/chip/ChipGroup.test.tsx +0 -7
- package/src/components/chip/ChipGroup.tsx +6 -6
- package/src/components/date-picker/DatePickerControlled.test.tsx +1 -1
- package/src/components/date-picker/DatePickerControlled.tsx +26 -4
- package/src/components/date-picker/DatePickerField.tsx +40 -48
- package/src/components/popover-dialog/PopoverDialog.test.tsx +11 -0
- package/src/components/popover-dialog/PopoverDialog.tsx +9 -1
- package/src/components/text-field/TextField.tsx +16 -1
- package/src/components/thumbnail/Thumbnail.tsx +4 -4
- package/src/hooks/usePreviousValue.ts +13 -0
- package/src/utils/date/getMonthCalendar.test.ts +14 -14
- package/src/utils/date/getWeekDays.test.ts +21 -21
- package/src/utils/date/getWeekDays.ts +4 -2
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.
|
|
11
|
-
"@lumx/icons": "^3.6.
|
|
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.
|
|
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 {
|
|
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
|
-
/**
|
|
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:
|
|
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`}>
|
|
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
|
|
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
|
|
1
|
+
import React, { forwardRef, SyntheticEvent, useCallback, useRef } from 'react';
|
|
2
2
|
|
|
3
|
-
import { DatePicker, IconButtonProps, Placement,
|
|
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,
|
|
60
|
+
const [isOpen, close, , toggleOpen] = useBooleanState(false);
|
|
67
61
|
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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={
|
|
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
|
-
<
|
|
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={
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
</
|
|
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 {
|
|
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 {
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
</
|
|
212
|
+
</span>
|
|
213
213
|
)}
|
|
214
|
-
</
|
|
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;
|