@lumx/react 3.9.2-alpha.9 → 3.9.3
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 +2 -0
- package/index.js +77 -48
- package/index.js.map +1 -1
- package/package.json +4 -3
- package/src/components/date-picker/DatePickerControlled.test.tsx +3 -2
- package/src/components/date-picker/DatePickerControlled.tsx +55 -32
- package/src/components/image-lightbox/ImageLightbox.tsx +2 -0
- package/src/components/image-lightbox/useImageLightbox.tsx +1 -1
- package/src/components/text-field/TextField.tsx +3 -1
- package/src/components/tooltip/Tooltip.test.tsx +4 -3
- package/src/components/tooltip/Tooltip.tsx +3 -2
- package/src/constants.ts +5 -0
- package/src/utils/DOM/startViewTransition.ts +23 -11
package/package.json
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
"url": "https://github.com/lumapps/design-system/issues"
|
|
7
7
|
},
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@lumx/core": "^3.9.
|
|
10
|
-
"@lumx/icons": "^3.9.
|
|
9
|
+
"@lumx/core": "^3.9.3",
|
|
10
|
+
"@lumx/icons": "^3.9.3",
|
|
11
11
|
"@popperjs/core": "^2.5.4",
|
|
12
12
|
"body-scroll-lock": "^3.1.5",
|
|
13
13
|
"classnames": "^2.3.2",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"@testing-library/user-event": "^14.4.3",
|
|
40
40
|
"@types/body-scroll-lock": "^2.6.1",
|
|
41
41
|
"@types/classnames": "^2.2.9",
|
|
42
|
+
"@types/dom-view-transitions": "^1.0.5",
|
|
42
43
|
"@types/jest": "^29.2.1",
|
|
43
44
|
"@types/lodash": "^4.14.149",
|
|
44
45
|
"@types/react": "^17.0.2",
|
|
@@ -110,5 +111,5 @@
|
|
|
110
111
|
"build:storybook": "storybook build"
|
|
111
112
|
},
|
|
112
113
|
"sideEffects": false,
|
|
113
|
-
"version": "3.9.
|
|
114
|
+
"version": "3.9.3"
|
|
114
115
|
}
|
|
@@ -3,8 +3,9 @@ import React from 'react';
|
|
|
3
3
|
import { render, screen, waitFor } from '@testing-library/react';
|
|
4
4
|
import { getByClassName, queryByClassName } from '@lumx/react/testing/utils/queries';
|
|
5
5
|
import { commonTestsSuiteRTL } from '@lumx/react/testing/utils';
|
|
6
|
-
|
|
7
6
|
import userEvent from '@testing-library/user-event';
|
|
7
|
+
import { VISUALLY_HIDDEN } from '@lumx/react/constants';
|
|
8
|
+
|
|
8
9
|
import { DatePickerControlled, DatePickerControlledProps } from './DatePickerControlled';
|
|
9
10
|
import { CLASSNAME } from './constants';
|
|
10
11
|
|
|
@@ -39,7 +40,7 @@ const queries = {
|
|
|
39
40
|
screen.getByRole('spinbutton', {
|
|
40
41
|
name: /année/i,
|
|
41
42
|
}),
|
|
42
|
-
getAccessibleMonthYear: (container: HTMLElement) => getByClassName(container,
|
|
43
|
+
getAccessibleMonthYear: (container: HTMLElement) => getByClassName(container, VISUALLY_HIDDEN),
|
|
43
44
|
};
|
|
44
45
|
|
|
45
46
|
describe(`<${DatePickerControlled.displayName}>`, () => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { KeyboardEventHandler, forwardRef } from 'react';
|
|
2
2
|
import classNames from 'classnames';
|
|
3
|
-
import { DatePickerProps, Emphasis, FlexBox, IconButton, Text, TextField, Toolbar } from '@lumx/react';
|
|
3
|
+
import { DatePickerProps, Emphasis, FlexBox, IconButton, Text, TextField, TextFieldProps, Toolbar } from '@lumx/react';
|
|
4
4
|
import { mdiChevronLeft, mdiChevronRight } from '@lumx/icons';
|
|
5
5
|
import { Comp } from '@lumx/react/utils/type';
|
|
6
6
|
import { getMonthCalendar } from '@lumx/react/utils/date/getMonthCalendar';
|
|
@@ -13,6 +13,7 @@ import { getYearDisplayName } from '@lumx/react/utils/date/getYearDisplayName';
|
|
|
13
13
|
import { onEnterPressed } from '@lumx/react/utils/event';
|
|
14
14
|
import { addMonthResetDay } from '@lumx/react/utils/date/addMonthResetDay';
|
|
15
15
|
import { formatDayNumber } from '@lumx/react/utils/date/formatDayNumber';
|
|
16
|
+
import { VISUALLY_HIDDEN } from '@lumx/react/constants';
|
|
16
17
|
import { CLASSNAME } from './constants';
|
|
17
18
|
|
|
18
19
|
/**
|
|
@@ -61,36 +62,58 @@ export const DatePickerControlled: Comp<DatePickerControlledProps, HTMLDivElemen
|
|
|
61
62
|
return getMonthCalendar(localeObj, selectedMonth, minDate, maxDate);
|
|
62
63
|
}, [locale, minDate, maxDate, selectedMonth]);
|
|
63
64
|
|
|
64
|
-
const selectedYear = selectedMonth.
|
|
65
|
-
const
|
|
66
|
-
const
|
|
65
|
+
const selectedYear = selectedMonth.getFullYear();
|
|
66
|
+
const formattedYear = selectedMonth.toLocaleDateString(locale, { year: 'numeric' }).slice(0, 4);
|
|
67
|
+
const [currentYear, setCurrentYear] = React.useState(String(selectedYear));
|
|
67
68
|
|
|
68
69
|
// Updates month offset when validating year. Adds or removes 12 months per year when updating year value.
|
|
69
|
-
const updateMonthOffset = React.useCallback(
|
|
70
|
-
|
|
71
|
-
const yearNumber =
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
onMonthChange(addMonthResetDay(selectedMonth, offset));
|
|
70
|
+
const updateMonthOffset = React.useCallback(
|
|
71
|
+
(newYearValue: string) => {
|
|
72
|
+
const yearNumber = Number(newYearValue);
|
|
73
|
+
if (yearNumber < 0 && yearNumber >= 9999) {
|
|
74
|
+
return;
|
|
75
75
|
}
|
|
76
|
-
}
|
|
77
|
-
}, [isYearValid, selectedMonth, textFieldYearValue, onMonthChange]);
|
|
78
76
|
|
|
79
|
-
|
|
77
|
+
const previousYearNumber = selectedMonth.getFullYear();
|
|
78
|
+
const offset = (yearNumber - previousYearNumber) * 12;
|
|
79
|
+
onMonthChange?.(addMonthResetDay(selectedMonth, offset));
|
|
80
|
+
},
|
|
81
|
+
[selectedMonth, onMonthChange],
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const onYearChange = React.useCallback<TextFieldProps['onChange']>(
|
|
85
|
+
(newYearValue, _, event) => {
|
|
86
|
+
setCurrentYear(newYearValue);
|
|
80
87
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
88
|
+
// Detect if change is coming from the spin up/down arrows
|
|
89
|
+
const inputType = (event?.nativeEvent as any)?.inputType;
|
|
90
|
+
if (
|
|
91
|
+
// Chrome/Safari
|
|
92
|
+
!inputType ||
|
|
93
|
+
// Firefox
|
|
94
|
+
inputType === 'insertReplacementText'
|
|
95
|
+
) {
|
|
96
|
+
updateMonthOffset(newYearValue);
|
|
97
|
+
}
|
|
98
|
+
},
|
|
84
99
|
[updateMonthOffset],
|
|
85
100
|
);
|
|
86
101
|
|
|
87
|
-
|
|
102
|
+
const updateYear = React.useCallback(() => {
|
|
103
|
+
updateMonthOffset(currentYear);
|
|
104
|
+
}, [updateMonthOffset, currentYear]);
|
|
105
|
+
|
|
106
|
+
const updateYearOnEnterPressed: KeyboardEventHandler = React.useMemo(
|
|
107
|
+
() => onEnterPressed(updateYear),
|
|
108
|
+
[updateYear],
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const monthYear = selectedMonth.toLocaleDateString(locale, { year: 'numeric', month: 'long' });
|
|
112
|
+
|
|
113
|
+
// Update current year when selected year changes
|
|
88
114
|
React.useEffect(() => {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
93
|
-
}, [selectedMonth]);
|
|
115
|
+
setCurrentYear(String(selectedYear));
|
|
116
|
+
}, [selectedYear]);
|
|
94
117
|
|
|
95
118
|
const prevSelectedMonth = usePreviousValue(selectedMonth);
|
|
96
119
|
const monthHasChanged = prevSelectedMonth && !isSameDay(selectedMonth, prevSelectedMonth);
|
|
@@ -101,7 +124,7 @@ export const DatePickerControlled: Comp<DatePickerControlledProps, HTMLDivElemen
|
|
|
101
124
|
if (monthHasChanged) setLabelAriaLive('polite');
|
|
102
125
|
}, [monthHasChanged]);
|
|
103
126
|
|
|
104
|
-
const
|
|
127
|
+
const yearLabel = getYearDisplayName(locale);
|
|
105
128
|
|
|
106
129
|
return (
|
|
107
130
|
<div ref={ref} className={`${CLASSNAME}`}>
|
|
@@ -125,7 +148,7 @@ export const DatePickerControlled: Comp<DatePickerControlledProps, HTMLDivElemen
|
|
|
125
148
|
}
|
|
126
149
|
label={
|
|
127
150
|
<>
|
|
128
|
-
<span aria-live={labelAriaLive} className={onMonthChange ?
|
|
151
|
+
<span aria-live={labelAriaLive} className={onMonthChange ? VISUALLY_HIDDEN : ''} dir="auto">
|
|
129
152
|
{monthYear}
|
|
130
153
|
</span>
|
|
131
154
|
{onMonthChange && (
|
|
@@ -137,21 +160,21 @@ export const DatePickerControlled: Comp<DatePickerControlledProps, HTMLDivElemen
|
|
|
137
160
|
vAlign="center"
|
|
138
161
|
dir="auto"
|
|
139
162
|
>
|
|
140
|
-
{RegExp(`(.*)(${
|
|
163
|
+
{RegExp(`(.*)(${formattedYear})(.*)`)
|
|
141
164
|
.exec(monthYear)
|
|
142
165
|
?.slice(1)
|
|
143
166
|
.filter((part) => part !== '')
|
|
144
167
|
.map((part) =>
|
|
145
|
-
part ===
|
|
168
|
+
part === formattedYear ? (
|
|
146
169
|
<TextField
|
|
147
|
-
value={
|
|
148
|
-
aria-label={
|
|
149
|
-
onChange={
|
|
170
|
+
value={currentYear}
|
|
171
|
+
aria-label={yearLabel}
|
|
172
|
+
onChange={onYearChange}
|
|
150
173
|
type="number"
|
|
151
174
|
max={9999}
|
|
152
175
|
min={0}
|
|
153
|
-
onBlur={
|
|
154
|
-
onKeyPress={
|
|
176
|
+
onBlur={updateYear}
|
|
177
|
+
onKeyPress={updateYearOnEnterPressed}
|
|
155
178
|
key="year"
|
|
156
179
|
className={`${CLASSNAME}__year`}
|
|
157
180
|
/>
|
|
@@ -200,7 +223,7 @@ export const DatePickerControlled: Comp<DatePickerControlledProps, HTMLDivElemen
|
|
|
200
223
|
onClick={() => onChange(date)}
|
|
201
224
|
>
|
|
202
225
|
<span aria-hidden>{formatDayNumber(locale, date)}</span>
|
|
203
|
-
<span className=
|
|
226
|
+
<span className={VISUALLY_HIDDEN}>
|
|
204
227
|
{date.toLocaleDateString(locale, {
|
|
205
228
|
day: 'numeric',
|
|
206
229
|
month: 'long',
|
|
@@ -55,6 +55,8 @@ const Inner: Comp<ImageLightboxProps, HTMLDivElement> = forwardRef((props, ref)
|
|
|
55
55
|
closeButtonProps={closeButtonProps}
|
|
56
56
|
focusElement={currentPaginationItemRef}
|
|
57
57
|
{...forwardedProps}
|
|
58
|
+
// Disable the close on click away as we want a custom one here
|
|
59
|
+
preventAutoClose
|
|
58
60
|
>
|
|
59
61
|
<ClickAwayProvider childrenRefs={clickAwayChildrenRefs} callback={onClickAway}>
|
|
60
62
|
<ImageSlideshow
|
|
@@ -68,6 +68,8 @@ export interface TextFieldProps extends GenericProps, HasTheme {
|
|
|
68
68
|
placeholder?: string;
|
|
69
69
|
/** Reference to the wrapper. */
|
|
70
70
|
textFieldRef?: Ref<HTMLDivElement>;
|
|
71
|
+
/** Native input type (only when `multiline` is disabled). */
|
|
72
|
+
type?: React.ComponentProps<'input'>['type'];
|
|
71
73
|
/** Value. */
|
|
72
74
|
value?: string;
|
|
73
75
|
/** On blur callback. */
|
|
@@ -160,7 +162,7 @@ interface InputNativeProps {
|
|
|
160
162
|
maxLength?: number;
|
|
161
163
|
placeholder?: string;
|
|
162
164
|
rows: number;
|
|
163
|
-
type:
|
|
165
|
+
type: TextFieldProps['type'];
|
|
164
166
|
name?: string;
|
|
165
167
|
value?: string;
|
|
166
168
|
setFocus(focus: boolean): void;
|
|
@@ -5,8 +5,9 @@ import { screen, render } from '@testing-library/react';
|
|
|
5
5
|
import { queryAllByTagName, queryByClassName } from '@lumx/react/testing/utils/queries';
|
|
6
6
|
import { commonTestsSuiteRTL } from '@lumx/react/testing/utils';
|
|
7
7
|
import userEvent from '@testing-library/user-event';
|
|
8
|
-
|
|
9
8
|
import { isFocusVisible } from '@lumx/react/utils/isFocusVisible';
|
|
9
|
+
import { VISUALLY_HIDDEN } from '@lumx/react/constants';
|
|
10
|
+
|
|
10
11
|
import { Tooltip, TooltipProps } from './Tooltip';
|
|
11
12
|
|
|
12
13
|
const CLASSNAME = Tooltip.className as string;
|
|
@@ -142,11 +143,11 @@ describe(`<${Tooltip.displayName}>`, () => {
|
|
|
142
143
|
forceOpen: false,
|
|
143
144
|
});
|
|
144
145
|
expect(tooltip).toBeInTheDocument();
|
|
145
|
-
expect(tooltip).toHaveClass(
|
|
146
|
+
expect(tooltip).toHaveClass(VISUALLY_HIDDEN);
|
|
146
147
|
|
|
147
148
|
const anchor = screen.getByRole('button', { name: 'Anchor' });
|
|
148
149
|
await userEvent.hover(anchor);
|
|
149
|
-
expect(tooltip).not.toHaveClass(
|
|
150
|
+
expect(tooltip).not.toHaveClass(VISUALLY_HIDDEN);
|
|
150
151
|
});
|
|
151
152
|
});
|
|
152
153
|
|
|
@@ -4,7 +4,7 @@ import { createPortal } from 'react-dom';
|
|
|
4
4
|
|
|
5
5
|
import classNames from 'classnames';
|
|
6
6
|
|
|
7
|
-
import { DOCUMENT } from '@lumx/react/constants';
|
|
7
|
+
import { DOCUMENT, VISUALLY_HIDDEN } from '@lumx/react/constants';
|
|
8
8
|
import { Comp, GenericProps, HasCloseMode } from '@lumx/react/utils/type';
|
|
9
9
|
import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
|
|
10
10
|
import { useMergeRefs } from '@lumx/react/utils/mergeRefs';
|
|
@@ -106,6 +106,7 @@ export const Tooltip: Comp<TooltipProps, HTMLDivElement> = forwardRef((props, re
|
|
|
106
106
|
const { isOpen: isActivated, onPopperMount } = useTooltipOpen(delay, anchorElement);
|
|
107
107
|
const isOpen = (isActivated || forceOpen) && !!label;
|
|
108
108
|
const isMounted = !!label && (isOpen || closeMode === 'hide');
|
|
109
|
+
const isHidden = !isOpen && closeMode === 'hide';
|
|
109
110
|
const wrappedChildren = useInjectTooltipRef({
|
|
110
111
|
children,
|
|
111
112
|
setAnchorElement,
|
|
@@ -139,8 +140,8 @@ export const Tooltip: Comp<TooltipProps, HTMLDivElement> = forwardRef((props, re
|
|
|
139
140
|
handleBasicClasses({
|
|
140
141
|
prefix: CLASSNAME,
|
|
141
142
|
position,
|
|
142
|
-
hidden: !isOpen && closeMode === 'hide',
|
|
143
143
|
}),
|
|
144
|
+
isHidden && VISUALLY_HIDDEN,
|
|
144
145
|
)}
|
|
145
146
|
style={{ ...styles.popper, zIndex }}
|
|
146
147
|
{...attributes.popper}
|
package/src/constants.ts
CHANGED
|
@@ -20,3 +20,8 @@ export const DOCUMENT = typeof document !== 'undefined' ? document : undefined;
|
|
|
20
20
|
* Check if we are running in a true browser
|
|
21
21
|
*/
|
|
22
22
|
export const IS_BROWSER = typeof navigator !== 'undefined' && !navigator.userAgent.includes('jsdom');
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Visually hidden a11y utility class name
|
|
26
|
+
*/
|
|
27
|
+
export const VISUALLY_HIDDEN = 'visually-hidden';
|
|
@@ -5,9 +5,21 @@ import { MaybeElementOrRef } from '@lumx/react/utils/type';
|
|
|
5
5
|
import { unref } from '../react/unref';
|
|
6
6
|
import { getPrefersReducedMotion } from '../browser/getPrefersReducedMotion';
|
|
7
7
|
|
|
8
|
-
function
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
function setupViewTransitionName(elementRef: MaybeElementOrRef<HTMLElement>, name: string) {
|
|
9
|
+
let originalName: string | null = null;
|
|
10
|
+
return {
|
|
11
|
+
set() {
|
|
12
|
+
const element = unref(elementRef);
|
|
13
|
+
if (!element) return;
|
|
14
|
+
originalName = element.style.viewTransitionName;
|
|
15
|
+
element.style.viewTransitionName = name;
|
|
16
|
+
},
|
|
17
|
+
unset() {
|
|
18
|
+
const element = unref(elementRef);
|
|
19
|
+
if (!element || originalName === null) return;
|
|
20
|
+
element.style.viewTransitionName = originalName;
|
|
21
|
+
},
|
|
22
|
+
};
|
|
11
23
|
}
|
|
12
24
|
|
|
13
25
|
/**
|
|
@@ -37,20 +49,20 @@ export async function startViewTransition({
|
|
|
37
49
|
return;
|
|
38
50
|
}
|
|
39
51
|
|
|
40
|
-
//
|
|
41
|
-
|
|
52
|
+
// Setup set/unset transition name on source & target
|
|
53
|
+
const sourceTransitionName = setupViewTransitionName(viewTransitionName.source, viewTransitionName.name);
|
|
54
|
+
const targetTransitionName = setupViewTransitionName(viewTransitionName.target, viewTransitionName.name);
|
|
55
|
+
|
|
56
|
+
sourceTransitionName.set();
|
|
42
57
|
|
|
43
58
|
// Start view transition, apply changes & flush to DOM
|
|
44
59
|
await start(() => {
|
|
45
|
-
|
|
46
|
-
setTransitionViewName(viewTransitionName.source, null);
|
|
60
|
+
sourceTransitionName.unset();
|
|
47
61
|
|
|
48
62
|
flushSync(changes);
|
|
49
63
|
|
|
50
|
-
|
|
51
|
-
setTransitionViewName(viewTransitionName.target, viewTransitionName.name);
|
|
64
|
+
targetTransitionName.set();
|
|
52
65
|
}).updateCallbackDone;
|
|
53
66
|
|
|
54
|
-
|
|
55
|
-
setTransitionViewName(viewTransitionName.target, null);
|
|
67
|
+
targetTransitionName.unset();
|
|
56
68
|
}
|