@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ssa-ui-kit/core",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "private": false,
@@ -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 { handleSubmit, register, setValue } = useFormResult;
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
- name={'typeahead-dropdown'}
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 14px 0 10px',
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 14px 0 10px',
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 (['Tab', 'Space'].includes(event.code) && !firstSuggestion) {
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
  };