@lumx/react 3.2.1 → 3.3.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.
|
|
11
|
-
"@lumx/icons": "^3.
|
|
10
|
+
"@lumx/core": "^3.3.0",
|
|
11
|
+
"@lumx/icons": "^3.3.0",
|
|
12
12
|
"@popperjs/core": "^2.5.4",
|
|
13
13
|
"body-scroll-lock": "^3.1.5",
|
|
14
14
|
"classnames": "^2.2.6",
|
|
@@ -113,5 +113,5 @@
|
|
|
113
113
|
"build:storybook": "cd storybook && ./build"
|
|
114
114
|
},
|
|
115
115
|
"sideEffects": false,
|
|
116
|
-
"version": "3.
|
|
116
|
+
"version": "3.3.0"
|
|
117
117
|
}
|
|
@@ -5,8 +5,14 @@ import camelCase from 'lodash/camelCase';
|
|
|
5
5
|
import { commonTestsSuiteRTL } from '@lumx/react/testing/utils';
|
|
6
6
|
import { getBasicClass } from '@lumx/react/utils/className';
|
|
7
7
|
|
|
8
|
-
import { render } from '@testing-library/react';
|
|
9
|
-
import {
|
|
8
|
+
import { render, screen } from '@testing-library/react';
|
|
9
|
+
import {
|
|
10
|
+
getByClassName,
|
|
11
|
+
getByTagName,
|
|
12
|
+
queryAllByClassName,
|
|
13
|
+
queryByClassName,
|
|
14
|
+
queryByTagName,
|
|
15
|
+
} from '@lumx/react/testing/utils/queries';
|
|
10
16
|
import partition from 'lodash/partition';
|
|
11
17
|
import userEvent from '@testing-library/user-event';
|
|
12
18
|
|
|
@@ -27,9 +33,11 @@ const setup = (propsOverride: Partial<TextFieldProps> = {}) => {
|
|
|
27
33
|
| HTMLInputElement;
|
|
28
34
|
const helpers = queryAllByClassName(container, 'lumx-text-field__helper');
|
|
29
35
|
const [[helper], [error]] = partition(helpers, (h) => !h.className.includes('lumx-input-helper--color-red'));
|
|
36
|
+
const clearButton = queryByClassName(container, 'lumx-text-field__input-clear');
|
|
30
37
|
|
|
31
38
|
return {
|
|
32
39
|
props,
|
|
40
|
+
clearButton,
|
|
33
41
|
container,
|
|
34
42
|
element,
|
|
35
43
|
inputNative,
|
|
@@ -106,22 +114,27 @@ describe(`<${TextField.displayName}>`, () => {
|
|
|
106
114
|
});
|
|
107
115
|
|
|
108
116
|
it('should have helper text', () => {
|
|
109
|
-
const { helper } = setup({
|
|
117
|
+
const { helper, inputNative } = setup({
|
|
110
118
|
helper: 'helper',
|
|
111
119
|
label: 'test',
|
|
112
120
|
placeholder: 'test',
|
|
113
121
|
});
|
|
122
|
+
|
|
114
123
|
expect(helper).toHaveTextContent('helper');
|
|
124
|
+
expect(inputNative).toHaveAttribute('aria-describedby');
|
|
115
125
|
});
|
|
116
126
|
|
|
117
127
|
it('should have error text', () => {
|
|
118
|
-
const { error } = setup({
|
|
128
|
+
const { error, inputNative } = setup({
|
|
119
129
|
error: 'error',
|
|
120
130
|
hasError: true,
|
|
121
131
|
label: 'test',
|
|
122
132
|
placeholder: 'test',
|
|
123
133
|
});
|
|
134
|
+
|
|
124
135
|
expect(error).toHaveTextContent('error');
|
|
136
|
+
expect(inputNative).toHaveAttribute('aria-invalid', 'true');
|
|
137
|
+
expect(inputNative).toHaveAttribute('aria-describedby');
|
|
125
138
|
});
|
|
126
139
|
|
|
127
140
|
it('should not have error text', () => {
|
|
@@ -160,6 +173,41 @@ describe(`<${TextField.displayName}>`, () => {
|
|
|
160
173
|
|
|
161
174
|
expect(onChange).toHaveBeenCalledWith('a', 'name', expect.objectContaining({}));
|
|
162
175
|
});
|
|
176
|
+
|
|
177
|
+
it('should trigger `onChange` with empty value when text field is cleared', async () => {
|
|
178
|
+
const onChange = jest.fn();
|
|
179
|
+
const { clearButton } = setup({
|
|
180
|
+
value: 'initial value',
|
|
181
|
+
name: 'name',
|
|
182
|
+
clearButtonProps: { label: 'Clear' },
|
|
183
|
+
onChange,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
expect(clearButton).toBeInTheDocument();
|
|
187
|
+
|
|
188
|
+
await userEvent.click(clearButton as HTMLElement);
|
|
189
|
+
|
|
190
|
+
expect(onChange).toHaveBeenCalledWith('');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should trigger `onChange` with empty value and `onClear` when text field is cleared', async () => {
|
|
194
|
+
const onChange = jest.fn();
|
|
195
|
+
const onClear = jest.fn();
|
|
196
|
+
const { clearButton } = setup({
|
|
197
|
+
value: 'initial value',
|
|
198
|
+
name: 'name',
|
|
199
|
+
clearButtonProps: { label: 'Clear' },
|
|
200
|
+
onChange,
|
|
201
|
+
onClear,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
expect(clearButton).toBeInTheDocument();
|
|
205
|
+
|
|
206
|
+
await userEvent.click(clearButton as HTMLElement);
|
|
207
|
+
|
|
208
|
+
expect(onChange).toHaveBeenCalledWith('');
|
|
209
|
+
expect(onClear).toHaveBeenCalled();
|
|
210
|
+
});
|
|
163
211
|
});
|
|
164
212
|
|
|
165
213
|
// Common tests suite.
|
|
@@ -61,6 +61,8 @@ export interface TextFieldProps extends GenericProps, HasTheme {
|
|
|
61
61
|
onBlur?(event: React.FocusEvent): void;
|
|
62
62
|
/** On change callback. */
|
|
63
63
|
onChange(value: string, name?: string, event?: SyntheticEvent): void;
|
|
64
|
+
/** On clear callback. */
|
|
65
|
+
onClear?(event?: SyntheticEvent): void;
|
|
64
66
|
/** On focus callback. */
|
|
65
67
|
onFocus?(event: React.FocusEvent): void;
|
|
66
68
|
}
|
|
@@ -153,6 +155,8 @@ interface InputNativeProps {
|
|
|
153
155
|
onChange(value: string, name?: string, event?: SyntheticEvent): void;
|
|
154
156
|
onFocus?(value: React.FocusEvent): void;
|
|
155
157
|
onBlur?(value: React.FocusEvent): void;
|
|
158
|
+
hasError?: boolean;
|
|
159
|
+
describedById?: string;
|
|
156
160
|
}
|
|
157
161
|
|
|
158
162
|
const renderInputNative: React.FC<InputNativeProps> = (props) => {
|
|
@@ -172,6 +176,8 @@ const renderInputNative: React.FC<InputNativeProps> = (props) => {
|
|
|
172
176
|
recomputeNumberOfRows,
|
|
173
177
|
type,
|
|
174
178
|
name,
|
|
179
|
+
hasError,
|
|
180
|
+
describedById,
|
|
175
181
|
...forwardedProps
|
|
176
182
|
} = props;
|
|
177
183
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
@@ -214,6 +220,8 @@ const renderInputNative: React.FC<InputNativeProps> = (props) => {
|
|
|
214
220
|
onFocus: onTextFieldFocus,
|
|
215
221
|
onBlur: onTextFieldBlur,
|
|
216
222
|
onChange: handleChange,
|
|
223
|
+
'aria-invalid': hasError ? 'true' : undefined,
|
|
224
|
+
'aria-describedby': describedById,
|
|
217
225
|
ref: mergeRefs(inputRef as any, ref) as any,
|
|
218
226
|
};
|
|
219
227
|
if (multiline) {
|
|
@@ -254,6 +262,7 @@ export const TextField: Comp<TextFieldProps, HTMLDivElement> = forwardRef((props
|
|
|
254
262
|
name,
|
|
255
263
|
onBlur,
|
|
256
264
|
onChange,
|
|
265
|
+
onClear,
|
|
257
266
|
onFocus,
|
|
258
267
|
placeholder,
|
|
259
268
|
textFieldRef,
|
|
@@ -264,6 +273,17 @@ export const TextField: Comp<TextFieldProps, HTMLDivElement> = forwardRef((props
|
|
|
264
273
|
...forwardedProps
|
|
265
274
|
} = props;
|
|
266
275
|
const textFieldId = useMemo(() => id || `text-field-${uid()}`, [id]);
|
|
276
|
+
/**
|
|
277
|
+
* Generate unique ids for both the helper and error texts, in order to
|
|
278
|
+
* later on add them to the input native as aria-describedby. If both the error and the helper are present,
|
|
279
|
+
* we want to first use the most important one, which is the errorId. That way, screen readers will read first
|
|
280
|
+
* the error and then the helper
|
|
281
|
+
*/
|
|
282
|
+
const helperId = helper ? `text-field-helper-${uid()}` : undefined;
|
|
283
|
+
const errorId = error ? `text-field-error-${uid()}` : undefined;
|
|
284
|
+
const describedByIds = [errorId, helperId].filter(Boolean);
|
|
285
|
+
const describedById = describedByIds.length === 0 ? undefined : describedByIds.join(' ');
|
|
286
|
+
|
|
267
287
|
const [isFocus, setFocus] = useState(false);
|
|
268
288
|
const { rows, recomputeNumberOfRows } = useComputeNumberOfRows(multiline ? minimumRows || DEFAULT_MIN_ROWS : 0);
|
|
269
289
|
const valueLength = (value || '').length;
|
|
@@ -275,12 +295,16 @@ export const TextField: Comp<TextFieldProps, HTMLDivElement> = forwardRef((props
|
|
|
275
295
|
* and remove focus from the clear button.
|
|
276
296
|
* @param evt On clear event.
|
|
277
297
|
*/
|
|
278
|
-
const
|
|
298
|
+
const handleClear = (evt: React.ChangeEvent) => {
|
|
279
299
|
evt.nativeEvent.preventDefault();
|
|
280
300
|
evt.nativeEvent.stopPropagation();
|
|
281
301
|
(evt.currentTarget as HTMLElement).blur();
|
|
282
302
|
|
|
283
303
|
onChange('');
|
|
304
|
+
|
|
305
|
+
if (onClear) {
|
|
306
|
+
onClear(evt);
|
|
307
|
+
}
|
|
284
308
|
};
|
|
285
309
|
|
|
286
310
|
return (
|
|
@@ -359,6 +383,8 @@ export const TextField: Comp<TextFieldProps, HTMLDivElement> = forwardRef((props
|
|
|
359
383
|
type,
|
|
360
384
|
value,
|
|
361
385
|
name,
|
|
386
|
+
hasError,
|
|
387
|
+
describedById,
|
|
362
388
|
...forwardedProps,
|
|
363
389
|
})}
|
|
364
390
|
</div>
|
|
@@ -383,6 +409,8 @@ export const TextField: Comp<TextFieldProps, HTMLDivElement> = forwardRef((props
|
|
|
383
409
|
type,
|
|
384
410
|
value,
|
|
385
411
|
name,
|
|
412
|
+
hasError,
|
|
413
|
+
describedById,
|
|
386
414
|
...forwardedProps,
|
|
387
415
|
})}
|
|
388
416
|
</div>
|
|
@@ -405,7 +433,7 @@ export const TextField: Comp<TextFieldProps, HTMLDivElement> = forwardRef((props
|
|
|
405
433
|
emphasis={Emphasis.low}
|
|
406
434
|
size={Size.s}
|
|
407
435
|
theme={theme}
|
|
408
|
-
onClick={
|
|
436
|
+
onClick={handleClear}
|
|
409
437
|
type="button"
|
|
410
438
|
/>
|
|
411
439
|
)}
|
|
@@ -414,13 +442,13 @@ export const TextField: Comp<TextFieldProps, HTMLDivElement> = forwardRef((props
|
|
|
414
442
|
</div>
|
|
415
443
|
|
|
416
444
|
{hasError && error && (
|
|
417
|
-
<InputHelper className={`${CLASSNAME}__helper`} kind={Kind.error} theme={theme}>
|
|
445
|
+
<InputHelper className={`${CLASSNAME}__helper`} kind={Kind.error} theme={theme} id={errorId}>
|
|
418
446
|
{error}
|
|
419
447
|
</InputHelper>
|
|
420
448
|
)}
|
|
421
449
|
|
|
422
450
|
{helper && (
|
|
423
|
-
<InputHelper className={`${CLASSNAME}__helper`} theme={theme}>
|
|
451
|
+
<InputHelper className={`${CLASSNAME}__helper`} theme={theme} id={helperId}>
|
|
424
452
|
{helper}
|
|
425
453
|
</InputHelper>
|
|
426
454
|
)}
|