@ssa-ui-kit/core 1.0.2 → 1.0.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.
Files changed (57) hide show
  1. package/dist/components/Button/fixtures.d.ts +8 -0
  2. package/dist/components/Button/types.d.ts +2 -0
  3. package/dist/components/FormHelperText/FormHelperText.d.ts +1 -1
  4. package/dist/components/Input/types.d.ts +1 -0
  5. package/dist/components/Label/Label.d.ts +1 -1
  6. package/dist/components/Label/LabelBase.d.ts +2 -0
  7. package/dist/components/Label/types.d.ts +1 -0
  8. package/dist/components/Typeahead/Typeahead.context.d.ts +36 -0
  9. package/dist/components/Typeahead/Typeahead.d.ts +11 -0
  10. package/dist/components/Typeahead/components/MultipleTrigger.d.ts +1 -0
  11. package/dist/components/Typeahead/components/NoOptions.d.ts +1 -0
  12. package/dist/components/Typeahead/components/SingleTrigger.d.ts +1 -0
  13. package/dist/components/Typeahead/components/TypeaheadItem.d.ts +8 -0
  14. package/dist/components/Typeahead/components/TypeaheadOption.d.ts +2 -0
  15. package/dist/components/Typeahead/components/TypeaheadOptions.d.ts +2 -0
  16. package/dist/components/Typeahead/components/TypeaheadTrigger.d.ts +1 -0
  17. package/dist/components/Typeahead/components/index.d.ts +7 -0
  18. package/dist/components/Typeahead/index.d.ts +5 -0
  19. package/dist/components/Typeahead/styles.d.ts +47 -0
  20. package/dist/components/Typeahead/types.d.ts +46 -0
  21. package/dist/components/Typeahead/useTypeahead.d.ts +36 -0
  22. package/dist/components/Typeahead/utils.d.ts +1 -0
  23. package/dist/components/index.d.ts +1 -0
  24. package/dist/index.js +1 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/types/emotion.d.ts +4 -1
  27. package/package.json +3 -3
  28. package/src/components/Button/Button.tsx +10 -2
  29. package/src/components/Button/types.ts +2 -0
  30. package/src/components/FormHelperText/FormHelperText.tsx +2 -1
  31. package/src/components/Input/Input.spec.tsx +6 -8
  32. package/src/components/Input/Input.tsx +14 -8
  33. package/src/components/Input/types.ts +1 -0
  34. package/src/components/Label/Label.tsx +2 -0
  35. package/src/components/Label/LabelBase.tsx +3 -2
  36. package/src/components/Label/types.ts +1 -0
  37. package/src/components/Typeahead/Typeahead.context.ts +59 -0
  38. package/src/components/Typeahead/Typeahead.spec.tsx +506 -0
  39. package/src/components/Typeahead/Typeahead.stories.tsx +372 -0
  40. package/src/components/Typeahead/Typeahead.tsx +120 -0
  41. package/src/components/Typeahead/components/MultipleTrigger.tsx +116 -0
  42. package/src/components/Typeahead/components/NoOptions.tsx +7 -0
  43. package/src/components/Typeahead/components/SingleTrigger.tsx +71 -0
  44. package/src/components/Typeahead/components/TypeaheadItem.ts +14 -0
  45. package/src/components/Typeahead/components/TypeaheadOption.tsx +12 -0
  46. package/src/components/Typeahead/components/TypeaheadOptions.tsx +25 -0
  47. package/src/components/Typeahead/components/TypeaheadTrigger.tsx +26 -0
  48. package/src/components/Typeahead/components/index.ts +7 -0
  49. package/src/components/Typeahead/index.ts +5 -0
  50. package/src/components/Typeahead/styles.ts +193 -0
  51. package/src/components/Typeahead/types.ts +77 -0
  52. package/src/components/Typeahead/useTypeahead.tsx +321 -0
  53. package/src/components/Typeahead/utils.tsx +22 -0
  54. package/src/components/index.ts +1 -0
  55. package/src/themes/main.ts +3 -0
  56. package/src/types/emotion.ts +3 -0
  57. package/tsbuildcache +1 -1
@@ -0,0 +1,372 @@
1
+ import React, { useState } from 'react';
2
+ import {
3
+ FieldError,
4
+ FieldValues,
5
+ SubmitHandler,
6
+ useForm,
7
+ } from 'react-hook-form';
8
+ import { useTheme } from '@emotion/react';
9
+ import { css } from '@emotion/css';
10
+ import type { Meta, StoryObj } from '@storybook/react';
11
+ import Icon from '@components/Icon';
12
+ import { IconProps } from '@components/Icon/types';
13
+ import Button from '@components/Button';
14
+ import Wrapper from '@components/Wrapper';
15
+ import { Typeahead } from '.';
16
+ import { TypeaheadProps } from './types';
17
+ import { highlightInputMatch } from './utils';
18
+ import { TypeaheadItemIcon, TypeaheadOption } from './components';
19
+
20
+ const items = [
21
+ { id: 1, value: 'First' },
22
+ { id: 2, value: 'Second' },
23
+ { id: 3, value: 'Third' },
24
+ { id: 4, label: 'Fourth', value: 4 },
25
+ { id: 5, value: 'Fifth' },
26
+ { id: 6, value: 'Sixth' },
27
+ ];
28
+
29
+ export default {
30
+ title: 'Components/Typeahead',
31
+ component: Typeahead,
32
+ argTypes: {
33
+ onChange: {
34
+ control: {
35
+ disable: true,
36
+ },
37
+ },
38
+ className: {
39
+ description: 'Used in order to overwrite the default style',
40
+ table: {
41
+ type: {
42
+ summary: 'StyledComponent',
43
+ },
44
+ },
45
+ control: {
46
+ disable: true,
47
+ },
48
+ },
49
+ },
50
+ decorators: [
51
+ (Story, { args }) => {
52
+ return <div style={{ paddingBottom: 200 }}>{Story({ ...args })}</div>;
53
+ },
54
+ ],
55
+ } as Meta<typeof Typeahead>;
56
+
57
+ export const Basic: StoryObj = (args: TypeaheadProps) => {
58
+ const useFormResult = useForm<FieldValues>();
59
+ const { handleSubmit, register, setValue } = useFormResult;
60
+ const onSubmit: SubmitHandler<FieldValues> = (data) => console.log(data);
61
+ return (
62
+ <form onSubmit={handleSubmit(onSubmit)}>
63
+ <Typeahead
64
+ initialSelectedItems={[items[2].id]}
65
+ isDisabled={args.isDisabled}
66
+ name={'typeahead-dropdown'}
67
+ label="Label"
68
+ helperText="Helper Text"
69
+ register={register}
70
+ setValue={setValue}
71
+ renderOption={({ label, input }) => highlightInputMatch(label, input)}>
72
+ {items.map(({ label, value, id }) => (
73
+ <TypeaheadOption key={id} value={id} label={label || value}>
74
+ {label || value}
75
+ </TypeaheadOption>
76
+ ))}
77
+ </Typeahead>
78
+ <Button type="submit" css={{ marginTop: 5 }}>
79
+ Submit
80
+ </Button>
81
+ </form>
82
+ );
83
+ };
84
+
85
+ Basic.args = { isDisabled: false };
86
+
87
+ export const Multiple: StoryObj = (args: TypeaheadProps) => {
88
+ const useFormResult = useForm<FieldValues>();
89
+ const { handleSubmit, register, setValue } = useFormResult;
90
+ const onSubmit: SubmitHandler<FieldValues> = (data) => console.log(data);
91
+ return (
92
+ <form onSubmit={handleSubmit(onSubmit)}>
93
+ <Typeahead
94
+ initialSelectedItems={[items[2].id, items[1].id]}
95
+ isMultiple
96
+ isDisabled={args.isDisabled}
97
+ label="Label"
98
+ helperText="Helper Text"
99
+ register={register}
100
+ setValue={setValue}
101
+ name={'typeahead-dropdown'}
102
+ renderOption={({ label, input }) => highlightInputMatch(label, input)}>
103
+ {items.map(({ label, value, id }) => (
104
+ <TypeaheadOption key={id} value={id} label={label || value}>
105
+ {label || value}
106
+ </TypeaheadOption>
107
+ ))}
108
+ </Typeahead>
109
+ <Button type="submit" css={{ marginTop: 5 }}>
110
+ Submit
111
+ </Button>
112
+ </form>
113
+ );
114
+ };
115
+
116
+ Multiple.args = { isDisabled: false };
117
+
118
+ const imageItems = [
119
+ { id: 1, label: 'First', value: 1, iconName: 'cogwheel' },
120
+ { id: 2, label: 'Second', value: 2, iconName: 'information' },
121
+ { id: 3, label: 'Third', value: 3, iconName: 'notification' },
122
+ { id: 4, label: 'Fourth', value: 4, iconName: 'roles' },
123
+ { id: 5, label: 'Fifth', value: 5, iconName: 'settings' },
124
+ { id: 6, label: 'Sixth', value: 6, iconName: 'user' },
125
+ ];
126
+
127
+ const getIconNameByValue = (value: number) =>
128
+ (imageItems.find((item) => item.value === value)?.iconName ||
129
+ 'user') as IconProps['name'];
130
+
131
+ export const WithImageAndStartIcon: StoryObj = (args: TypeaheadProps) => {
132
+ const useFormResult = useForm<FieldValues>();
133
+ const { register, setValue } = useFormResult;
134
+ return (
135
+ <Typeahead
136
+ initialSelectedItems={[imageItems[2].id, imageItems[1].id]}
137
+ isMultiple
138
+ isDisabled={args.isDisabled}
139
+ name={'typeahead-dropdown'}
140
+ label="Label"
141
+ startIcon={<Icon name="user" size={16} />}
142
+ startIconClassName={css`
143
+ position: absolute;
144
+ left: 14px;
145
+ `}
146
+ css={{
147
+ width: 500,
148
+ paddingLeft: 38,
149
+ }}
150
+ register={register}
151
+ setValue={setValue}
152
+ renderOption={({ label, input, value }) => (
153
+ <React.Fragment>
154
+ <TypeaheadItemIcon
155
+ name={getIconNameByValue(Number(value))}
156
+ size={18}
157
+ />
158
+ {highlightInputMatch(label, input)}
159
+ </React.Fragment>
160
+ )}>
161
+ {imageItems.map(({ label, value, id, iconName }) => (
162
+ <TypeaheadOption key={id} value={id} label={label || value}>
163
+ <TypeaheadItemIcon name={iconName as IconProps['name']} size={18} />
164
+ {label || value}
165
+ </TypeaheadOption>
166
+ ))}
167
+ </Typeahead>
168
+ );
169
+ };
170
+
171
+ WithImageAndStartIcon.args = { isDisabled: false };
172
+
173
+ const mockError: FieldError = {
174
+ type: 'required',
175
+ message: 'Required field',
176
+ };
177
+
178
+ export const WithError: StoryObj = (args: TypeaheadProps) => {
179
+ const useFormResult = useForm<FieldValues>();
180
+ const { register, setValue } = useFormResult;
181
+ return (
182
+ <Typeahead
183
+ initialSelectedItems={[]}
184
+ isDisabled={args.isDisabled}
185
+ name={'typeahead-dropdown'}
186
+ label="Label"
187
+ register={register}
188
+ setValue={setValue}
189
+ validationSchema={{
190
+ required: 'Required',
191
+ }}
192
+ errors={mockError}
193
+ renderOption={({ label, input }) => highlightInputMatch(label, input)}>
194
+ {items.map(({ label, value, id }) => (
195
+ <TypeaheadOption key={id} value={id} label={label || value}>
196
+ {label || value}
197
+ </TypeaheadOption>
198
+ ))}
199
+ </Typeahead>
200
+ );
201
+ };
202
+
203
+ WithError.args = { isDisabled: false };
204
+
205
+ export const WithSuccess: StoryObj = (args: TypeaheadProps) => {
206
+ const useFormResult = useForm<FieldValues>();
207
+ const { register, setValue } = useFormResult;
208
+ return (
209
+ <Typeahead
210
+ initialSelectedItems={[items[2].id]}
211
+ isDisabled={args.isDisabled}
212
+ name={'typeahead-dropdown'}
213
+ label="Label"
214
+ register={register}
215
+ setValue={setValue}
216
+ validationSchema={{
217
+ required: 'Required',
218
+ }}
219
+ success
220
+ helperText="Helper text"
221
+ renderOption={({ label, input }) => highlightInputMatch(label, input)}>
222
+ {items.map(({ label, value, id }) => (
223
+ <TypeaheadOption key={id} value={id} label={label || value}>
224
+ {label || value}
225
+ </TypeaheadOption>
226
+ ))}
227
+ </Typeahead>
228
+ );
229
+ };
230
+
231
+ WithSuccess.args = { isDisabled: false };
232
+
233
+ export const Opened: StoryObj = (args: TypeaheadProps) => {
234
+ const useFormResult = useForm<FieldValues>();
235
+ const { register, setValue } = useFormResult;
236
+ return (
237
+ <Typeahead
238
+ isDisabled={args.isDisabled}
239
+ name={'typeahead-dropdown'}
240
+ label="Label"
241
+ isOpen
242
+ register={register}
243
+ setValue={setValue}
244
+ renderOption={({ label, input }) => highlightInputMatch(label, input)}>
245
+ {items.map(({ label, value, id }) => (
246
+ <TypeaheadOption key={id} value={id} label={label || value}>
247
+ {label || value}
248
+ </TypeaheadOption>
249
+ ))}
250
+ </Typeahead>
251
+ );
252
+ };
253
+
254
+ Opened.args = { isDisabled: false };
255
+
256
+ export const DynamicallyChangedItems = (args: TypeaheadProps) => {
257
+ const [localItems, setLocalItems] = useState(items);
258
+
259
+ const handleUpdate = () => {
260
+ setLocalItems((state) => [
261
+ ...state,
262
+ {
263
+ id: state[state.length - 1].id + 1,
264
+ label: `New item #${state[state.length - 1].id + 1}`,
265
+ value: state[state.length - 1].id + 1,
266
+ },
267
+ ]);
268
+ };
269
+
270
+ const useFormResult = useForm<FieldValues>();
271
+ const { handleSubmit, register, setValue } = useFormResult;
272
+ const onSubmit: SubmitHandler<FieldValues> = (data) => console.log(data);
273
+ return (
274
+ <form onSubmit={handleSubmit(onSubmit)}>
275
+ <Wrapper
276
+ css={{ flexDirection: 'column', alignItems: 'flex-start', gap: 10 }}>
277
+ <Typeahead
278
+ initialSelectedItems={[localItems[2].id]}
279
+ isMultiple
280
+ name={'typeahead-dropdown'}
281
+ label="Label"
282
+ register={register}
283
+ setValue={setValue}
284
+ validationSchema={{
285
+ required: 'Required',
286
+ }}
287
+ renderOption={({ label, input }) => highlightInputMatch(label, input)}
288
+ {...args}>
289
+ {localItems.map(({ label, value, id }) => (
290
+ <TypeaheadOption key={id} value={id} label={label || value}>
291
+ {label || value}
292
+ </TypeaheadOption>
293
+ ))}
294
+ </Typeahead>
295
+ <Button variant="primary" onClick={handleUpdate}>
296
+ Update items
297
+ </Button>
298
+ <Button type="submit" variant="info">
299
+ Submit
300
+ </Button>
301
+ </Wrapper>
302
+ </form>
303
+ );
304
+ };
305
+
306
+ DynamicallyChangedItems.args = { isDisabled: false };
307
+
308
+ export const Disabled: StoryObj = (args: TypeaheadProps) => {
309
+ const theme = useTheme();
310
+ const useFormResult = useForm<FieldValues>();
311
+ const { register, setValue } = useFormResult;
312
+ return (
313
+ <Typeahead
314
+ initialSelectedItems={[items[2].id, items[1].id]}
315
+ isMultiple
316
+ isDisabled={args.isDisabled}
317
+ name={'typeahead-dropdown'}
318
+ label="Label"
319
+ startIcon={<Icon name="user" size={16} color={theme.colors.grey} />}
320
+ css={{
321
+ width: 500,
322
+ }}
323
+ register={register}
324
+ setValue={setValue}
325
+ helperText="Helper text"
326
+ renderOption={({ label, input, value }) => (
327
+ <React.Fragment>
328
+ <TypeaheadItemIcon
329
+ name={getIconNameByValue(Number(value))}
330
+ color={args.isDisabled ? theme.colors.grey : '#000'}
331
+ size={18}
332
+ />
333
+ {highlightInputMatch(label, input)}
334
+ </React.Fragment>
335
+ )}>
336
+ {imageItems.map(({ label, value, id, iconName }) => (
337
+ <TypeaheadOption key={id} value={id} label={label || value}>
338
+ <TypeaheadItemIcon
339
+ name={iconName as IconProps['name']}
340
+ size={18}
341
+ color={args.isDisabled ? theme.colors.grey : '#000'}
342
+ />
343
+ {label || value}
344
+ </TypeaheadOption>
345
+ ))}
346
+ </Typeahead>
347
+ );
348
+ };
349
+
350
+ Disabled.args = { isDisabled: true };
351
+
352
+ export const NoItems: StoryObj = (args: TypeaheadProps) => {
353
+ const useFormResult = useForm<FieldValues>();
354
+ const { register, setValue } = useFormResult;
355
+ return (
356
+ <Typeahead
357
+ isMultiple
358
+ isDisabled={args.isDisabled}
359
+ name={'typeahead-dropdown'}
360
+ label="Label"
361
+ css={{
362
+ width: 500,
363
+ }}
364
+ register={register}
365
+ setValue={setValue}
366
+ helperText="Helper text">
367
+ {null}
368
+ </Typeahead>
369
+ );
370
+ };
371
+
372
+ NoItems.args = { isDisabled: false };
@@ -0,0 +1,120 @@
1
+ import { useTheme } from '@emotion/react';
2
+ import {
3
+ Popover,
4
+ PopoverContent,
5
+ PopoverDescription,
6
+ } from '@components/Popover';
7
+ import FormHelperText from '@components/FormHelperText';
8
+ import Wrapper from '@components/Wrapper';
9
+ import Label from '@components/Label';
10
+ import { TypeaheadContext } from './Typeahead.context';
11
+ import { useTypeahead } from './useTypeahead';
12
+ import { TypeaheadOptions, TypeaheadTrigger } from './components';
13
+ import { TypeaheadProps } from './types';
14
+
15
+ /**
16
+ * The structure of the component:
17
+ * - TypeaheadTrigger
18
+ * - TypeaheadOptions
19
+ * - FormHelperText
20
+ *
21
+ * Aria attributes are set according to
22
+ * https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-select-only/
23
+ **/
24
+ export const Typeahead = ({
25
+ name = 'typeahead-search',
26
+ label,
27
+ initialSelectedItems,
28
+ isOpen,
29
+ isDisabled,
30
+ isMultiple,
31
+ children,
32
+ className,
33
+ startIcon,
34
+ endIcon,
35
+ errors,
36
+ success,
37
+ helperText,
38
+ validationSchema,
39
+ placeholder = 'Select something',
40
+ startIconClassName,
41
+ endIconClassName,
42
+ optionsClassName,
43
+ setValue,
44
+ register,
45
+ onChange,
46
+ renderOption,
47
+ }: TypeaheadProps) => {
48
+ const theme = useTheme();
49
+ const hookResult = useTypeahead({
50
+ name,
51
+ initialSelectedItems,
52
+ isOpen,
53
+ isDisabled,
54
+ isMultiple,
55
+ children,
56
+ className,
57
+ startIcon,
58
+ endIcon,
59
+ startIconClassName,
60
+ endIconClassName,
61
+ errors,
62
+ success,
63
+ validationSchema,
64
+ placeholder,
65
+ setValue,
66
+ register,
67
+ onChange,
68
+ renderOption,
69
+ });
70
+
71
+ return (
72
+ <TypeaheadContext.Provider value={hookResult}>
73
+ <Wrapper
74
+ css={{
75
+ flexDirection: 'column',
76
+ alignItems: 'flex-start',
77
+ }}
78
+ data-testid="typeahead">
79
+ {label && (
80
+ <Label
81
+ htmlFor={hookResult.inputName}
82
+ isDisabled={isDisabled}
83
+ data-testid="typeahead-label">
84
+ {label}
85
+ </Label>
86
+ )}
87
+ <Popover
88
+ floatingOptions={{
89
+ onOpenChange: hookResult.handleOpenChange,
90
+ open: hookResult.isOpen,
91
+ }}>
92
+ <TypeaheadTrigger />
93
+ <PopoverContent
94
+ css={{
95
+ width: hookResult.triggerRef.current?.clientWidth,
96
+ boxShadow: `-4px 4px 14px 0px ${theme.colors.greyDarker14}`,
97
+ }}
98
+ isFocusManagerDisabled>
99
+ <PopoverDescription css={{ width: '100%' }}>
100
+ {hookResult.isOpen ? (
101
+ <TypeaheadOptions className={optionsClassName}>
102
+ {children}
103
+ </TypeaheadOptions>
104
+ ) : null}
105
+ </PopoverDescription>
106
+ </PopoverContent>
107
+ </Popover>
108
+ {(errors?.message || helperText) && (
109
+ <FormHelperText
110
+ role="status"
111
+ status={hookResult.status}
112
+ disabled={isDisabled}
113
+ data-testid="helper-text">
114
+ {errors ? errors?.message : helperText}
115
+ </FormHelperText>
116
+ )}
117
+ </Wrapper>
118
+ </TypeaheadContext.Provider>
119
+ );
120
+ };
@@ -0,0 +1,116 @@
1
+ import React, { InputHTMLAttributes } from 'react';
2
+ import { useTheme } from '@emotion/react';
3
+ import Icon from '@components/Icon';
4
+ import Input from '@components/Input';
5
+ import Button from '@components/Button';
6
+ import * as S from '../styles';
7
+ import { useTypeaheadContext } from '../Typeahead.context';
8
+
9
+ export const MultipleTrigger = () => {
10
+ const theme = useTheme();
11
+ const context = useTypeaheadContext();
12
+ const typeaheadInputAdditionalProps: InputHTMLAttributes<HTMLInputElement> =
13
+ {};
14
+ if (!context.selectedItems.length && !!context.placeholder) {
15
+ typeaheadInputAdditionalProps.placeholder = context.placeholder;
16
+ }
17
+ return (
18
+ <React.Fragment>
19
+ {Object.values(context.optionsWithKey).length > 0 &&
20
+ context.selectedItems.map((selectedItem, index) => {
21
+ const currentOption = context.optionsWithKey[selectedItem];
22
+ const optionText = currentOption
23
+ ? currentOption.children ||
24
+ currentOption.label ||
25
+ currentOption.value
26
+ : '';
27
+
28
+ return (
29
+ <S.TypeaheadItem
30
+ key={`typeahead-selected-selectedItem-${index}`}
31
+ onClick={context.handleSelectedClick}
32
+ isDisabled={context.isDisabled}>
33
+ <S.TypeaheadItemLabel isDisabled={context.isDisabled}>
34
+ {optionText}
35
+ </S.TypeaheadItemLabel>
36
+ <S.TypeaheadItemCross
37
+ isDisabled={context.isDisabled}
38
+ endIcon={
39
+ <Icon
40
+ name="cross"
41
+ tooltip="Remove"
42
+ size={14}
43
+ color={
44
+ context.isDisabled
45
+ ? theme.colors.grey
46
+ : theme.colors.greyDarker
47
+ }
48
+ css={{
49
+ '& path': {
50
+ strokeWidth: 1,
51
+ },
52
+ }}
53
+ />
54
+ }
55
+ onClick={context.handleRemoveSelectedClick(selectedItem)}
56
+ />
57
+ </S.TypeaheadItem>
58
+ );
59
+ })}
60
+ <S.TypeaheadInputsGroupWrapper isOpen={context.isOpen}>
61
+ {!context.isDisabled && (
62
+ <Input
63
+ name={context.inputName}
64
+ status={'custom'}
65
+ disabled={context.isDisabled}
66
+ inputProps={{
67
+ onClick: context.handleInputClick,
68
+ onKeyDown: context.handleInputKeyDown,
69
+ onChange: context.handleInputChange,
70
+ value: context.inputValue,
71
+ autoComplete: 'off',
72
+ className: ['typeahead-input', S.TypeaheadInput].join(' '),
73
+ }}
74
+ wrapperClassName={S.TypeaheadInputWrapper}
75
+ ref={context.inputRef}
76
+ />
77
+ )}
78
+ <input
79
+ type="text"
80
+ data-testid="typeahead-input"
81
+ aria-hidden
82
+ readOnly
83
+ value={context.firstSuggestion}
84
+ disabled={context.isDisabled}
85
+ className={[
86
+ 'typeahead-input',
87
+ S.TypeaheadInput,
88
+ S.TypeaheadInputPlaceholder,
89
+ ].join(' ')}
90
+ {...typeaheadInputAdditionalProps}
91
+ />
92
+ <input
93
+ type="hidden"
94
+ aria-hidden
95
+ readOnly
96
+ value={context.selectedItems as string[]}
97
+ {...context.register?.(context.name, context.validationSchema)}
98
+ />
99
+ </S.TypeaheadInputsGroupWrapper>
100
+ {!context.isDisabled && context.selectedItems.length ? (
101
+ <Button
102
+ variant="tertiary"
103
+ data-testid="remove-all-button"
104
+ endIcon={<Icon name="cross" size={8} tooltip="Remove all" />}
105
+ css={{
106
+ padding: '0 14px 0 10px',
107
+ position: 'absolute',
108
+ right: 0,
109
+ zIndex: 10,
110
+ }}
111
+ onClick={context.handleClearAll}
112
+ />
113
+ ) : null}
114
+ </React.Fragment>
115
+ );
116
+ };
@@ -0,0 +1,7 @@
1
+ import * as S from '../styles';
2
+
3
+ export const NoOptions = ({ children, ...rest }: React.PropsWithChildren) => (
4
+ <S.TypeaheadOption {...rest} role="option">
5
+ {children}
6
+ </S.TypeaheadOption>
7
+ );
@@ -0,0 +1,71 @@
1
+ import { InputHTMLAttributes } from 'react';
2
+ import Input from '@components/Input';
3
+ import Button from '@components/Button';
4
+ import Icon from '@components/Icon';
5
+ import { useTypeaheadContext } from '../Typeahead.context';
6
+ import * as S from '../styles';
7
+
8
+ export const SingleTrigger = () => {
9
+ const context = useTypeaheadContext();
10
+ const typeaheadInputAdditionalProps: InputHTMLAttributes<HTMLInputElement> =
11
+ {};
12
+ if (!context.selectedItems.length && !!context.placeholder) {
13
+ typeaheadInputAdditionalProps.placeholder = context.placeholder;
14
+ }
15
+
16
+ return (
17
+ <S.TypeaheadInputsGroupWrapper isOpen={context.isOpen}>
18
+ {!context.isDisabled && (
19
+ <Input
20
+ name={context.inputName}
21
+ status={'custom'}
22
+ disabled={context.isDisabled}
23
+ inputProps={{
24
+ onClick: context.handleInputClick,
25
+ onKeyDown: context.handleInputKeyDown,
26
+ onChange: context.handleInputChange,
27
+ value: context.inputValue,
28
+ autoComplete: 'off',
29
+ className: ['typeahead-input', S.TypeaheadInput].join(' '),
30
+ }}
31
+ wrapperClassName={S.TypeaheadInputWrapper}
32
+ ref={context.inputRef}
33
+ />
34
+ )}
35
+ <input
36
+ type="text"
37
+ data-testid="typeahead-input"
38
+ aria-hidden
39
+ readOnly
40
+ value={context.firstSuggestion}
41
+ className={[
42
+ 'typeahead-input',
43
+ S.TypeaheadInput,
44
+ S.TypeaheadInputPlaceholder,
45
+ ].join(' ')}
46
+ {...typeaheadInputAdditionalProps}
47
+ />
48
+ <input
49
+ type="hidden"
50
+ aria-hidden
51
+ readOnly
52
+ value={(context.selectedItems[0] || '') as string | undefined}
53
+ {...context.register?.(context.name, context.validationSchema)}
54
+ />
55
+ {!context.isDisabled && context.selectedItems.length ? (
56
+ <Button
57
+ variant="tertiary"
58
+ data-testid="remove-all-button"
59
+ endIcon={<Icon name="cross" size={8} tooltip="Remove" />}
60
+ css={{
61
+ padding: '0 14px 0 10px',
62
+ position: 'absolute',
63
+ right: -28,
64
+ zIndex: 10,
65
+ }}
66
+ onClick={context.handleClearAll}
67
+ />
68
+ ) : null}
69
+ </S.TypeaheadInputsGroupWrapper>
70
+ );
71
+ };
@@ -0,0 +1,14 @@
1
+ import Icon from '@components/Icon';
2
+ import styled from '@emotion/styled';
3
+
4
+ export const TypeaheadItemImage = styled.img`
5
+ width: 24px;
6
+ height: 24px;
7
+ border-radius: 50%;
8
+ `;
9
+
10
+ export const TypeaheadItemIcon = styled(Icon)`
11
+ width: 18px;
12
+ height: 18px;
13
+ border-radius: 50%;
14
+ `;