@ssa-ui-kit/core 1.0.7 → 1.0.8
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 +1 -1
- package/src/components/Typeahead/Typeahead.spec.tsx +21 -7
- package/src/components/Typeahead/Typeahead.stories.tsx +37 -3
- package/src/components/Typeahead/components/MultipleTrigger.tsx +4 -3
- package/src/components/Typeahead/components/SingleTrigger.tsx +4 -3
- package/src/components/Typeahead/useTypeahead.tsx +16 -10
package/package.json
CHANGED
|
@@ -60,7 +60,9 @@ describe('Typeahead', () => {
|
|
|
60
60
|
it('Renders without a selected item', async () => {
|
|
61
61
|
const { user, mockOnChange, getByRole, queryByRole, getByTestId } = setup();
|
|
62
62
|
|
|
63
|
-
expect(mockOnChange).toBeCalledWith('typeahead-dropdown', undefined
|
|
63
|
+
expect(mockOnChange).toBeCalledWith('typeahead-dropdown', undefined, {
|
|
64
|
+
shouldDirty: false,
|
|
65
|
+
});
|
|
64
66
|
|
|
65
67
|
const mainElement = getByTestId('typeahead');
|
|
66
68
|
|
|
@@ -104,7 +106,9 @@ describe('Typeahead', () => {
|
|
|
104
106
|
label: 'Label',
|
|
105
107
|
});
|
|
106
108
|
|
|
107
|
-
expect(mockOnChange).toBeCalledWith('typeahead-dropdown', selectedIDs
|
|
109
|
+
expect(mockOnChange).toBeCalledWith('typeahead-dropdown', selectedIDs, {
|
|
110
|
+
shouldDirty: false,
|
|
111
|
+
});
|
|
108
112
|
|
|
109
113
|
let mainElement = getByTestId('typeahead');
|
|
110
114
|
|
|
@@ -164,7 +168,9 @@ describe('Typeahead', () => {
|
|
|
164
168
|
/>,
|
|
165
169
|
);
|
|
166
170
|
|
|
167
|
-
expect(mockOnChange).toBeCalledWith('typeahead-dropdown', []
|
|
171
|
+
expect(mockOnChange).toBeCalledWith('typeahead-dropdown', [], {
|
|
172
|
+
shouldDirty: false,
|
|
173
|
+
});
|
|
168
174
|
|
|
169
175
|
let mainElement = getByTestId('typeahead');
|
|
170
176
|
|
|
@@ -203,7 +209,9 @@ describe('Typeahead', () => {
|
|
|
203
209
|
label: 'Label',
|
|
204
210
|
});
|
|
205
211
|
|
|
206
|
-
expect(mockOnChange).toBeCalledWith('typeahead-dropdown', selectedIDs
|
|
212
|
+
expect(mockOnChange).toBeCalledWith('typeahead-dropdown', selectedIDs, {
|
|
213
|
+
shouldDirty: false,
|
|
214
|
+
});
|
|
207
215
|
|
|
208
216
|
const mainElement = getByTestId('typeahead');
|
|
209
217
|
|
|
@@ -243,7 +251,9 @@ describe('Typeahead', () => {
|
|
|
243
251
|
label: 'Label',
|
|
244
252
|
});
|
|
245
253
|
|
|
246
|
-
expect(mockOnChange).lastCalledWith('typeahead-dropdown', selectedIDs[0]
|
|
254
|
+
expect(mockOnChange).lastCalledWith('typeahead-dropdown', selectedIDs[0], {
|
|
255
|
+
shouldDirty: true,
|
|
256
|
+
});
|
|
247
257
|
|
|
248
258
|
let inputEl = screen.queryByTestId('typeahead-input');
|
|
249
259
|
expect(inputEl).toHaveValue('First');
|
|
@@ -345,7 +355,9 @@ describe('Typeahead', () => {
|
|
|
345
355
|
label: 'Label',
|
|
346
356
|
});
|
|
347
357
|
|
|
348
|
-
expect(mockOnChange).toBeCalledWith('typeahead-dropdown', selectedIDs
|
|
358
|
+
expect(mockOnChange).toBeCalledWith('typeahead-dropdown', selectedIDs, {
|
|
359
|
+
shouldDirty: false,
|
|
360
|
+
});
|
|
349
361
|
|
|
350
362
|
let mainElement = getByTestId('typeahead');
|
|
351
363
|
|
|
@@ -448,7 +460,9 @@ describe('Typeahead', () => {
|
|
|
448
460
|
label: 'Label',
|
|
449
461
|
});
|
|
450
462
|
|
|
451
|
-
expect(mockOnChange).toBeCalledWith('typeahead-dropdown', selectedIDs
|
|
463
|
+
expect(mockOnChange).toBeCalledWith('typeahead-dropdown', selectedIDs, {
|
|
464
|
+
shouldDirty: false,
|
|
465
|
+
});
|
|
452
466
|
|
|
453
467
|
let mainElement = getByTestId('typeahead');
|
|
454
468
|
let toggleElement = within(mainElement).getByRole('combobox');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
FieldError,
|
|
4
4
|
FieldValues,
|
|
@@ -86,8 +86,38 @@ Basic.args = { isDisabled: false };
|
|
|
86
86
|
|
|
87
87
|
export const Multiple: StoryObj = (args: TypeaheadProps) => {
|
|
88
88
|
const useFormResult = useForm<FieldValues>();
|
|
89
|
-
const {
|
|
89
|
+
const {
|
|
90
|
+
handleSubmit,
|
|
91
|
+
register,
|
|
92
|
+
setValue,
|
|
93
|
+
setError,
|
|
94
|
+
clearErrors,
|
|
95
|
+
watch,
|
|
96
|
+
formState: { errors, isDirty },
|
|
97
|
+
} = useFormResult;
|
|
98
|
+
const fieldName = 'typeahead-dropdown';
|
|
99
|
+
const error = errors[fieldName]
|
|
100
|
+
? {
|
|
101
|
+
type: errors[fieldName].type,
|
|
102
|
+
message: errors[fieldName].message,
|
|
103
|
+
}
|
|
104
|
+
: undefined;
|
|
105
|
+
|
|
90
106
|
const onSubmit: SubmitHandler<FieldValues> = (data) => console.log(data);
|
|
107
|
+
const fieldWatch = watch(fieldName);
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (isDirty) {
|
|
110
|
+
if (Array.isArray(fieldWatch) && !fieldWatch.length) {
|
|
111
|
+
setError(fieldName, {
|
|
112
|
+
message: 'Required field',
|
|
113
|
+
type: 'required',
|
|
114
|
+
});
|
|
115
|
+
} else {
|
|
116
|
+
clearErrors(fieldName);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}, [fieldWatch, isDirty]);
|
|
120
|
+
|
|
91
121
|
return (
|
|
92
122
|
<form onSubmit={handleSubmit(onSubmit)}>
|
|
93
123
|
<Typeahead
|
|
@@ -98,7 +128,11 @@ export const Multiple: StoryObj = (args: TypeaheadProps) => {
|
|
|
98
128
|
helperText="Helper Text"
|
|
99
129
|
register={register}
|
|
100
130
|
setValue={setValue}
|
|
101
|
-
|
|
131
|
+
validationSchema={{
|
|
132
|
+
required: 'Required',
|
|
133
|
+
}}
|
|
134
|
+
name={fieldName}
|
|
135
|
+
error={error as FieldError}
|
|
102
136
|
renderOption={({ label, input }) => highlightInputMatch(label, input)}>
|
|
103
137
|
{items.map(({ label, value, id }) => (
|
|
104
138
|
<TypeaheadOption key={id} value={id} label={label || value}>
|
|
@@ -78,9 +78,10 @@ export const MultipleTrigger = () => {
|
|
|
78
78
|
<input
|
|
79
79
|
type="text"
|
|
80
80
|
data-testid="typeahead-input"
|
|
81
|
-
aria-hidden
|
|
81
|
+
aria-hidden={context.isOpen}
|
|
82
82
|
readOnly
|
|
83
83
|
value={context.firstSuggestion}
|
|
84
|
+
tabIndex={-1}
|
|
84
85
|
disabled={context.isDisabled}
|
|
85
86
|
className={[
|
|
86
87
|
'typeahead-input',
|
|
@@ -91,7 +92,6 @@ export const MultipleTrigger = () => {
|
|
|
91
92
|
/>
|
|
92
93
|
<input
|
|
93
94
|
type="hidden"
|
|
94
|
-
aria-hidden
|
|
95
95
|
readOnly
|
|
96
96
|
value={context.selectedItems as string[]}
|
|
97
97
|
{...context.register?.(context.name, context.validationSchema)}
|
|
@@ -103,7 +103,8 @@ export const MultipleTrigger = () => {
|
|
|
103
103
|
data-testid="remove-all-button"
|
|
104
104
|
endIcon={<Icon name="cross" size={8} tooltip="Remove all" />}
|
|
105
105
|
css={{
|
|
106
|
-
padding: '0
|
|
106
|
+
padding: '0 10px',
|
|
107
|
+
marginRight: 4,
|
|
107
108
|
position: 'absolute',
|
|
108
109
|
right: 0,
|
|
109
110
|
zIndex: 10,
|
|
@@ -37,9 +37,10 @@ export const SingleTrigger = () => {
|
|
|
37
37
|
<input
|
|
38
38
|
type="text"
|
|
39
39
|
data-testid="typeahead-input"
|
|
40
|
-
aria-hidden
|
|
40
|
+
aria-hidden={context.isOpen}
|
|
41
41
|
readOnly
|
|
42
42
|
value={context.firstSuggestion}
|
|
43
|
+
tabIndex={-1}
|
|
43
44
|
className={[
|
|
44
45
|
'typeahead-input',
|
|
45
46
|
S.TypeaheadInput(theme),
|
|
@@ -49,7 +50,6 @@ export const SingleTrigger = () => {
|
|
|
49
50
|
/>
|
|
50
51
|
<input
|
|
51
52
|
type="hidden"
|
|
52
|
-
aria-hidden
|
|
53
53
|
readOnly
|
|
54
54
|
value={(context.selectedItems[0] || '') as string | undefined}
|
|
55
55
|
{...context.register?.(context.name, context.validationSchema)}
|
|
@@ -60,7 +60,8 @@ export const SingleTrigger = () => {
|
|
|
60
60
|
data-testid="remove-all-button"
|
|
61
61
|
endIcon={<Icon name="cross" size={8} tooltip="Remove" />}
|
|
62
62
|
css={{
|
|
63
|
-
padding: '0
|
|
63
|
+
padding: '0 10px',
|
|
64
|
+
marginRight: 4,
|
|
64
65
|
position: 'absolute',
|
|
65
66
|
right: -28,
|
|
66
67
|
zIndex: 10,
|
|
@@ -39,6 +39,7 @@ export const useTypeahead = ({
|
|
|
39
39
|
const [optionsWithKey, setOptionsWithKey] = useState<
|
|
40
40
|
Record<number | string, Record<string, string | number>>
|
|
41
41
|
>({});
|
|
42
|
+
const [isFirstRender, setFirstRender] = useState<boolean>(true);
|
|
42
43
|
const [items, setItems] = useState<Array<React.ReactElement> | undefined>();
|
|
43
44
|
const [inputValue, setInputValue] = useState<string>('');
|
|
44
45
|
const [status, setStatus] = useState<'basic' | 'success' | 'error'>('basic');
|
|
@@ -58,11 +59,15 @@ export const useTypeahead = ({
|
|
|
58
59
|
|
|
59
60
|
useEffect(() => {
|
|
60
61
|
if (isMultiple) {
|
|
61
|
-
setValue?.(name, selected
|
|
62
|
+
setValue?.(name, selected, {
|
|
63
|
+
shouldDirty: !isFirstRender,
|
|
64
|
+
});
|
|
62
65
|
setInputValue('');
|
|
63
66
|
setFirstSuggestion('');
|
|
64
67
|
} else {
|
|
65
|
-
setValue?.(name, selected.length ? selected[0] : undefined
|
|
68
|
+
setValue?.(name, selected.length ? selected[0] : undefined, {
|
|
69
|
+
shouldDirty: !isFirstRender,
|
|
70
|
+
});
|
|
66
71
|
}
|
|
67
72
|
}, [selected]);
|
|
68
73
|
|
|
@@ -85,6 +90,7 @@ export const useTypeahead = ({
|
|
|
85
90
|
if (error) {
|
|
86
91
|
useFormResult.setError(name, error);
|
|
87
92
|
} else {
|
|
93
|
+
setStatus('basic');
|
|
88
94
|
useFormResult.resetField(name);
|
|
89
95
|
}
|
|
90
96
|
}, [error]);
|
|
@@ -108,6 +114,7 @@ export const useTypeahead = ({
|
|
|
108
114
|
});
|
|
109
115
|
setOptionsWithKey(keyedOptions);
|
|
110
116
|
setItems(childItems);
|
|
117
|
+
setFirstRender(false);
|
|
111
118
|
}, [initialSelectedItems, children]);
|
|
112
119
|
|
|
113
120
|
useEffect(() => {
|
|
@@ -216,7 +223,8 @@ export const useTypeahead = ({
|
|
|
216
223
|
setFirstSuggestion('');
|
|
217
224
|
inputRef.current?.focus();
|
|
218
225
|
setStatus('basic');
|
|
219
|
-
useFormResult.clearErrors();
|
|
226
|
+
useFormResult.clearErrors(name);
|
|
227
|
+
useFormResult.trigger(name);
|
|
220
228
|
onChange && onChange(changingValue, isNewSelected);
|
|
221
229
|
};
|
|
222
230
|
|
|
@@ -230,6 +238,7 @@ export const useTypeahead = ({
|
|
|
230
238
|
setInputValue('');
|
|
231
239
|
setIsOpen(false);
|
|
232
240
|
setFirstSuggestion('');
|
|
241
|
+
useFormResult.trigger(name);
|
|
233
242
|
inputRef.current?.focus();
|
|
234
243
|
};
|
|
235
244
|
|
|
@@ -247,13 +256,12 @@ export const useTypeahead = ({
|
|
|
247
256
|
const handleInputKeyDown: React.KeyboardEventHandler<HTMLInputElement> = (
|
|
248
257
|
event,
|
|
249
258
|
) => {
|
|
250
|
-
if (['
|
|
259
|
+
if (['Space'].includes(event.code) && !firstSuggestion) {
|
|
251
260
|
setIsOpen(true);
|
|
252
261
|
inputRef.current?.focus();
|
|
253
262
|
event.stopPropagation();
|
|
254
263
|
event.preventDefault();
|
|
255
|
-
}
|
|
256
|
-
if (['Tab', 'Enter'].includes(event.code) && firstSuggestion) {
|
|
264
|
+
} else if (['Tab', 'Enter'].includes(event.code) && firstSuggestion) {
|
|
257
265
|
const foundItem = Object.values(optionsWithKey).find(
|
|
258
266
|
(item) =>
|
|
259
267
|
`${item.label}`.toLowerCase() === firstSuggestion.toLowerCase(),
|
|
@@ -264,8 +272,7 @@ export const useTypeahead = ({
|
|
|
264
272
|
}
|
|
265
273
|
event.preventDefault();
|
|
266
274
|
return false;
|
|
267
|
-
}
|
|
268
|
-
if (
|
|
275
|
+
} else if (
|
|
269
276
|
isMultiple &&
|
|
270
277
|
event.code === 'Backspace' &&
|
|
271
278
|
selected.length > 0 &&
|
|
@@ -274,8 +281,7 @@ export const useTypeahead = ({
|
|
|
274
281
|
handleChange(selected[selected.length - 1]);
|
|
275
282
|
event.preventDefault();
|
|
276
283
|
return false;
|
|
277
|
-
}
|
|
278
|
-
if (!isOpen) {
|
|
284
|
+
} else if (!isOpen) {
|
|
279
285
|
setIsOpen(true);
|
|
280
286
|
}
|
|
281
287
|
};
|