@stack-spot/citric-react 0.41.2 → 0.42.0-beta.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/CHANGELOG.md +13 -13
- package/dist/citric.css +3090 -2846
- package/dist/components/Accordion.d.ts +1 -1
- package/dist/components/Accordion.js +1 -1
- package/dist/components/Alert.d.ts +1 -1
- package/dist/components/Alert.js +1 -1
- package/dist/components/AsyncContent.d.ts +1 -1
- package/dist/components/AsyncContent.js +1 -1
- package/dist/components/Autocomplete/Autocomplete.d.ts +211 -0
- package/dist/components/Autocomplete/Autocomplete.d.ts.map +1 -0
- package/dist/components/Autocomplete/Autocomplete.js +409 -0
- package/dist/components/Autocomplete/Autocomplete.js.map +1 -0
- package/dist/components/Autocomplete/index.d.ts +3 -0
- package/dist/components/Autocomplete/index.d.ts.map +1 -0
- package/dist/components/Autocomplete/index.js +2 -0
- package/dist/components/Autocomplete/index.js.map +1 -0
- package/dist/components/Avatar.d.ts +1 -1
- package/dist/components/Avatar.js +1 -1
- package/dist/components/AvatarGroup.d.ts +1 -1
- package/dist/components/AvatarGroup.js +1 -1
- package/dist/components/Badge.d.ts +1 -1
- package/dist/components/Badge.js +1 -1
- package/dist/components/Blockquote.d.ts +1 -1
- package/dist/components/Blockquote.js +1 -1
- package/dist/components/Breadcrumb.d.ts +1 -1
- package/dist/components/Breadcrumb.js +1 -1
- package/dist/components/Button.d.ts +1 -1
- package/dist/components/Button.js +1 -1
- package/dist/components/ButtonLink.d.ts +1 -1
- package/dist/components/ButtonLink.js +1 -1
- package/dist/components/Card.d.ts +1 -1
- package/dist/components/Card.js +1 -1
- package/dist/components/Checkbox.d.ts +1 -1
- package/dist/components/Checkbox.d.ts.map +1 -1
- package/dist/components/Checkbox.js +2 -2
- package/dist/components/Checkbox.js.map +1 -1
- package/dist/components/CheckboxGroup.d.ts +1 -1
- package/dist/components/CheckboxGroup.js +1 -1
- package/dist/components/Circle.d.ts +1 -1
- package/dist/components/Circle.js +1 -1
- package/dist/components/CitricComponent.d.ts +1 -1
- package/dist/components/CitricComponent.d.ts.map +1 -1
- package/dist/components/Divider.d.ts +1 -1
- package/dist/components/Divider.js +1 -1
- package/dist/components/ErrorBoundary.d.ts +1 -1
- package/dist/components/ErrorBoundary.js +1 -1
- package/dist/components/ErrorMessage.d.ts +1 -1
- package/dist/components/ErrorMessage.js +1 -1
- package/dist/components/FallbackBoundary.d.ts +1 -1
- package/dist/components/FallbackBoundary.js +1 -1
- package/dist/components/Favorite.d.ts +1 -1
- package/dist/components/Favorite.js +1 -1
- package/dist/components/FieldGroup.d.ts +1 -1
- package/dist/components/FieldGroup.js +1 -1
- package/dist/components/Form.d.ts +2 -2
- package/dist/components/Form.js +1 -1
- package/dist/components/FormGroup.d.ts +1 -1
- package/dist/components/FormGroup.js +1 -1
- package/dist/components/Icon.d.ts +1 -1
- package/dist/components/Icon.js +1 -1
- package/dist/components/IconBox.d.ts +3 -3
- package/dist/components/IconBox.js +1 -1
- package/dist/components/ImageBox.d.ts +3 -3
- package/dist/components/ImageBox.js +1 -1
- package/dist/components/ImageWithFallback.d.ts +1 -1
- package/dist/components/ImageWithFallback.js +1 -1
- package/dist/components/Input.d.ts +1 -1
- package/dist/components/Input.js +1 -1
- package/dist/components/Link.d.ts +1 -1
- package/dist/components/Link.js +1 -1
- package/dist/components/LoadingPanel.d.ts +1 -1
- package/dist/components/LoadingPanel.js +1 -1
- package/dist/components/MenuOverlay/Menu.d.ts +1 -1
- package/dist/components/MenuOverlay/Menu.js +1 -1
- package/dist/components/MenuOverlay/index.d.ts +1 -1
- package/dist/components/MenuOverlay/index.js +1 -1
- package/dist/components/Overlay/index.d.ts +1 -1
- package/dist/components/Overlay/index.js +1 -1
- package/dist/components/Pagination.d.ts +1 -1
- package/dist/components/Pagination.js +1 -1
- package/dist/components/ProgressBar.d.ts +1 -1
- package/dist/components/ProgressBar.js +1 -1
- package/dist/components/ProgressCircular.d.ts +1 -1
- package/dist/components/ProgressCircular.js +1 -1
- package/dist/components/RadioGroup.d.ts +1 -1
- package/dist/components/RadioGroup.js +1 -1
- package/dist/components/Rating.d.ts +1 -1
- package/dist/components/Rating.js +1 -1
- package/dist/components/Select/MultiSelect.d.ts +1 -1
- package/dist/components/Select/MultiSelect.js +1 -1
- package/dist/components/Select/RichSelect.d.ts +1 -1
- package/dist/components/Select/RichSelect.js +1 -1
- package/dist/components/Select/SimpleSelect.d.ts +1 -1
- package/dist/components/Select/SimpleSelect.js +1 -1
- package/dist/components/Select/index.d.ts +1 -1
- package/dist/components/Select/index.js +1 -1
- package/dist/components/SelectBox.d.ts +1 -1
- package/dist/components/SelectBox.js +1 -1
- package/dist/components/Skeleton.d.ts +1 -1
- package/dist/components/Skeleton.js +1 -1
- package/dist/components/Slider.d.ts +1 -1
- package/dist/components/Slider.js +1 -1
- package/dist/components/SmartTable.d.ts +1 -1
- package/dist/components/SmartTable.js +1 -1
- package/dist/components/Stepper.d.ts +1 -1
- package/dist/components/Stepper.js +1 -1
- package/dist/components/Table.d.ts +3 -3
- package/dist/components/Table.js +1 -1
- package/dist/components/Tabs/index.d.ts +1 -1
- package/dist/components/Tabs/index.js +1 -1
- package/dist/components/Textarea.d.ts +1 -1
- package/dist/components/Textarea.js +1 -1
- package/dist/components/Tooltip.d.ts +1 -1
- package/dist/components/Tooltip.js +1 -1
- package/dist/context/CitricProvider.d.ts +1 -1
- package/dist/context/CitricProvider.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/overlay.js +1 -1
- package/dist/theme.css +415 -415
- package/package.json +1 -1
- package/scripts/build-css.ts +49 -49
- package/src/components/Accordion.tsx +130 -130
- package/src/components/Alert.tsx +24 -24
- package/src/components/AsyncContent.tsx +75 -75
- package/src/components/Autocomplete/Autocomplete.tsx +794 -0
- package/src/components/Autocomplete/index.ts +3 -0
- package/src/components/Avatar.tsx +45 -45
- package/src/components/AvatarGroup.tsx +49 -49
- package/src/components/Badge.tsx +47 -47
- package/src/components/Blockquote.tsx +18 -18
- package/src/components/Breadcrumb.tsx +33 -33
- package/src/components/Button.tsx +105 -105
- package/src/components/ButtonLink.tsx +45 -45
- package/src/components/Card.tsx +68 -68
- package/src/components/Checkbox.tsx +52 -51
- package/src/components/CheckboxGroup.tsx +153 -153
- package/src/components/Circle.tsx +43 -43
- package/src/components/CitricComponent.ts +47 -47
- package/src/components/Divider.tsx +24 -24
- package/src/components/ErrorBoundary.tsx +75 -75
- package/src/components/ErrorMessage.tsx +11 -11
- package/src/components/FallbackBoundary.tsx +40 -40
- package/src/components/Favorite.tsx +57 -57
- package/src/components/FieldGroup.tsx +46 -46
- package/src/components/Form.tsx +36 -36
- package/src/components/FormGroup.tsx +57 -57
- package/src/components/Icon.tsx +35 -35
- package/src/components/IconBox.tsx +134 -134
- package/src/components/ImageBox.tsx +125 -125
- package/src/components/ImageWithFallback.tsx +65 -65
- package/src/components/Input.tsx +49 -49
- package/src/components/Link.tsx +55 -55
- package/src/components/LoadingPanel.tsx +12 -12
- package/src/components/MenuOverlay/Menu.tsx +158 -158
- package/src/components/MenuOverlay/context.ts +20 -20
- package/src/components/MenuOverlay/index.tsx +55 -55
- package/src/components/MenuOverlay/keyboard.ts +60 -60
- package/src/components/MenuOverlay/types.ts +171 -171
- package/src/components/Overlay/context.ts +10 -10
- package/src/components/Overlay/index.tsx +182 -182
- package/src/components/Overlay/types.ts +75 -75
- package/src/components/Pagination.tsx +133 -133
- package/src/components/ProgressBar.tsx +45 -45
- package/src/components/ProgressCircular.tsx +45 -45
- package/src/components/RadioGroup.tsx +147 -147
- package/src/components/Rating.tsx +98 -98
- package/src/components/Select/MultiSelect.tsx +217 -217
- package/src/components/Select/RichSelect.tsx +128 -128
- package/src/components/Select/SimpleSelect.tsx +73 -73
- package/src/components/Select/hooks.ts +133 -133
- package/src/components/Select/index.tsx +35 -35
- package/src/components/Select/types.ts +134 -134
- package/src/components/SelectBox.tsx +167 -167
- package/src/components/Skeleton.tsx +53 -53
- package/src/components/Slider.tsx +89 -89
- package/src/components/SmartTable.tsx +227 -227
- package/src/components/Stepper.tsx +163 -163
- package/src/components/Table.tsx +234 -234
- package/src/components/Tabs/TabController.ts +54 -54
- package/src/components/Tabs/index.tsx +106 -106
- package/src/components/Tabs/types.ts +67 -67
- package/src/components/Tabs/utils.ts +6 -6
- package/src/components/Text.ts +111 -111
- package/src/components/Textarea.tsx +27 -27
- package/src/components/Tooltip.tsx +83 -83
- package/src/components/layout.tsx +101 -101
- package/src/context/CitricContext.tsx +4 -4
- package/src/context/CitricProvider.tsx +14 -14
- package/src/context/hooks.ts +6 -6
- package/src/index.ts +59 -58
- package/src/overlay.ts +348 -348
- package/src/types.ts +235 -235
- package/src/utils/ValueController.ts +28 -28
- package/src/utils/acessibility.ts +92 -92
- package/src/utils/checkbox.ts +121 -121
- package/src/utils/css.ts +119 -119
- package/src/utils/options.ts +9 -9
- package/src/utils/radio.ts +93 -93
- package/src/utils/react.ts +6 -6
- package/src/utils/time.ts +5 -5
- package/tsconfig.json +10 -10
|
@@ -0,0 +1,794 @@
|
|
|
1
|
+
import { ColorPaletteName, ColorSchemeName, listToClass } from '@stack-spot/portal-theme'
|
|
2
|
+
import { useTranslate } from '@stack-spot/portal-translate'
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
4
|
+
import { applyCSSVariable } from '../../utils/css'
|
|
5
|
+
import { defaultRenderKey, defaultRenderLabel } from '../../utils/options'
|
|
6
|
+
import { withRef } from '../../utils/react'
|
|
7
|
+
import { Badge } from '../Badge'
|
|
8
|
+
import { Checkbox } from '../Checkbox'
|
|
9
|
+
import { CitricComponent } from '../CitricComponent'
|
|
10
|
+
import { IconButton } from '../IconBox'
|
|
11
|
+
import { ProgressCircular } from '../ProgressCircular'
|
|
12
|
+
import { useDisabledEffect, useFocusEffect } from '../Select/hooks'
|
|
13
|
+
import { Row } from '../layout'
|
|
14
|
+
|
|
15
|
+
export interface BaseAutocompleteProps<T, Multiple extends boolean = false> {
|
|
16
|
+
/**
|
|
17
|
+
* The list of options available for selection.
|
|
18
|
+
*/
|
|
19
|
+
options: T[],
|
|
20
|
+
/**
|
|
21
|
+
* The current value(s) selected.
|
|
22
|
+
* - Single selection: T | null
|
|
23
|
+
* - Multiple selection: T[]
|
|
24
|
+
*/
|
|
25
|
+
value: Multiple extends true ? T[] : (T | null),
|
|
26
|
+
/**
|
|
27
|
+
* Callback fired when the value changes.
|
|
28
|
+
*/
|
|
29
|
+
onChange: Multiple extends true ? (value: T[]) => void : (value: T | null) => void,
|
|
30
|
+
/**
|
|
31
|
+
* If true, enables multiple selection mode.
|
|
32
|
+
* @default false
|
|
33
|
+
*/
|
|
34
|
+
multiple?: Multiple,
|
|
35
|
+
/**
|
|
36
|
+
* If true, allows the user to enter values that are not in the options list.
|
|
37
|
+
* @default false
|
|
38
|
+
*/
|
|
39
|
+
freeSolo?: boolean,
|
|
40
|
+
/**
|
|
41
|
+
* If true, allows creating new options when no match is found.
|
|
42
|
+
* Shows an "Add [value]" option at the top of the list.
|
|
43
|
+
* @default false
|
|
44
|
+
*/
|
|
45
|
+
creatable?: boolean,
|
|
46
|
+
/**
|
|
47
|
+
* Callback fired when a new option is created.
|
|
48
|
+
* Required when creatable is true and you want manual control.
|
|
49
|
+
*/
|
|
50
|
+
onCreate?: (inputValue: string) => void,
|
|
51
|
+
/**
|
|
52
|
+
* Function to create a new option object from the input value.
|
|
53
|
+
* Required when creatable is true without onCreate and working with objects.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```tsx
|
|
57
|
+
* getOptionFromInput={(inputValue) => ({ id: Date.now(), name: inputValue })}
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
getOptionFromInput?: (inputValue: string) => T,
|
|
61
|
+
/**
|
|
62
|
+
* The input value (controlled).
|
|
63
|
+
*/
|
|
64
|
+
inputValue?: string,
|
|
65
|
+
/**
|
|
66
|
+
* Callback fired when the input value changes.
|
|
67
|
+
*/
|
|
68
|
+
onInputChange?: (value: string) => void,
|
|
69
|
+
/**
|
|
70
|
+
* A function to render the item label.
|
|
71
|
+
* @default "the item's toString() result."
|
|
72
|
+
*/
|
|
73
|
+
renderLabel?: (option: T) => string,
|
|
74
|
+
/**
|
|
75
|
+
* A function to generate a unique key for each option.
|
|
76
|
+
*/
|
|
77
|
+
renderKey?: (option: T) => string | number,
|
|
78
|
+
/**
|
|
79
|
+
* A function to render an option in the dropdown.
|
|
80
|
+
*/
|
|
81
|
+
renderOption?: (option: T) => React.ReactNode,
|
|
82
|
+
/**
|
|
83
|
+
* A function to render a tag in multiple mode.
|
|
84
|
+
*/
|
|
85
|
+
renderTag?: (option: T, onRemove: () => void) => React.ReactNode,
|
|
86
|
+
/**
|
|
87
|
+
* Custom filter function for options.
|
|
88
|
+
* @default filters by label includes input (case insensitive)
|
|
89
|
+
*/
|
|
90
|
+
filterOptions?: (options: T[], inputValue: string) => T[],
|
|
91
|
+
/**
|
|
92
|
+
* If true, shows a loading indicator.
|
|
93
|
+
* @default false
|
|
94
|
+
*/
|
|
95
|
+
loading?: boolean,
|
|
96
|
+
/**
|
|
97
|
+
* If true, the component is disabled.
|
|
98
|
+
* @default false
|
|
99
|
+
*/
|
|
100
|
+
disabled?: boolean,
|
|
101
|
+
/**
|
|
102
|
+
* Placeholder text for the input.
|
|
103
|
+
*/
|
|
104
|
+
placeholder?: string,
|
|
105
|
+
/**
|
|
106
|
+
* Maximum height for the dropdown panel in pixels.
|
|
107
|
+
*/
|
|
108
|
+
maxHeight?: number,
|
|
109
|
+
/**
|
|
110
|
+
* Maximum number of tags to show before truncating.
|
|
111
|
+
* Only applies when multiple is true.
|
|
112
|
+
*/
|
|
113
|
+
maxTagsToShow?: number,
|
|
114
|
+
/**
|
|
115
|
+
* If true, automatically highlights the first option.
|
|
116
|
+
* @default false
|
|
117
|
+
*/
|
|
118
|
+
autoHighlight?: boolean,
|
|
119
|
+
/**
|
|
120
|
+
* If true, clears the input value when an option is selected.
|
|
121
|
+
* Only applies when multiple is true.
|
|
122
|
+
* @default true (for multiple)
|
|
123
|
+
*/
|
|
124
|
+
clearOnSelect?: boolean,
|
|
125
|
+
/**
|
|
126
|
+
* If true, the popup will open on input focus.
|
|
127
|
+
* @default true
|
|
128
|
+
*/
|
|
129
|
+
openOnFocus?: boolean,
|
|
130
|
+
/**
|
|
131
|
+
* Callback fired when an option is selected (before onChange).
|
|
132
|
+
*/
|
|
133
|
+
onSelect?: (option: T | null) => void,
|
|
134
|
+
/**
|
|
135
|
+
* Text to display when no options are available.
|
|
136
|
+
*/
|
|
137
|
+
noOptionsText?: string,
|
|
138
|
+
/**
|
|
139
|
+
* Text to display when loading.
|
|
140
|
+
*/
|
|
141
|
+
loadingText?: string,
|
|
142
|
+
/**
|
|
143
|
+
* Callback fired when the user scrolls to the end of the options list.
|
|
144
|
+
* Useful for implementing infinite scroll/pagination.
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```tsx
|
|
148
|
+
* <Autocomplete
|
|
149
|
+
* options={options}
|
|
150
|
+
* onScrollEnd={() => fetchMoreOptions()}
|
|
151
|
+
* loading={isFetchingMore}
|
|
152
|
+
* />
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
onScrollEnd?: () => void,
|
|
156
|
+
/**
|
|
157
|
+
* Margin in pixels before the end of the list to trigger onScrollEnd.
|
|
158
|
+
* @default 200
|
|
159
|
+
*/
|
|
160
|
+
scrollEndMargin?: number,
|
|
161
|
+
/**
|
|
162
|
+
* Color scheme for the tags (badges) in multiple mode.
|
|
163
|
+
* @example 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info'
|
|
164
|
+
*/
|
|
165
|
+
tagColorScheme?: ColorSchemeName,
|
|
166
|
+
/**
|
|
167
|
+
* Color palette for the tags (badges) in multiple mode.
|
|
168
|
+
* @example 'blue' | 'green' | 'red' | 'yellow' | 'purple'
|
|
169
|
+
*/
|
|
170
|
+
tagColorPalette?: ColorPaletteName,
|
|
171
|
+
/**
|
|
172
|
+
* Appearance of the tags (badges) in multiple mode.
|
|
173
|
+
* @default 'circle'
|
|
174
|
+
*/
|
|
175
|
+
tagAppearance?: 'square' | 'circle',
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export type AutocompleteProps<T, Multiple extends boolean = false> =
|
|
179
|
+
Omit<React.JSX.IntrinsicElements['div'], 'ref' | 'onChange' | 'onSelect'> &
|
|
180
|
+
BaseAutocompleteProps<T, Multiple>
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* A combination of a text input and a dropdown that suggests options as the user types.
|
|
184
|
+
* Supports both single and multiple selection modes, similar to Material-UI Autocomplete.
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* Basic usage (single selection):
|
|
188
|
+
* ```tsx
|
|
189
|
+
* const [value, setValue] = useState<Option | null>(null)
|
|
190
|
+
*
|
|
191
|
+
* <Autocomplete
|
|
192
|
+
* options={options}
|
|
193
|
+
* value={value}
|
|
194
|
+
* onChange={setValue}
|
|
195
|
+
* renderLabel={o => o.name}
|
|
196
|
+
* renderKey={o => o.id}
|
|
197
|
+
* />
|
|
198
|
+
* ```
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* Multiple selection with tags:
|
|
202
|
+
* ```tsx
|
|
203
|
+
* const [value, setValue] = useState<Option[]>([])
|
|
204
|
+
*
|
|
205
|
+
* <Autocomplete
|
|
206
|
+
* multiple
|
|
207
|
+
* options={options}
|
|
208
|
+
* value={value}
|
|
209
|
+
* onChange={setValue}
|
|
210
|
+
* renderLabel={o => o.name}
|
|
211
|
+
* renderKey={o => o.id}
|
|
212
|
+
* />
|
|
213
|
+
* ```
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* Free solo (allow custom values):
|
|
217
|
+
* ```tsx
|
|
218
|
+
* <Autocomplete
|
|
219
|
+
* freeSolo
|
|
220
|
+
* options={options}
|
|
221
|
+
* value={value}
|
|
222
|
+
* onChange={setValue}
|
|
223
|
+
* renderLabel={o => o.name}
|
|
224
|
+
* />
|
|
225
|
+
* ```
|
|
226
|
+
*/
|
|
227
|
+
export const Autocomplete = withRef(
|
|
228
|
+
function Autocomplete<T, Multiple extends boolean = false>({
|
|
229
|
+
options,
|
|
230
|
+
value,
|
|
231
|
+
onChange,
|
|
232
|
+
multiple = false as Multiple,
|
|
233
|
+
freeSolo = false,
|
|
234
|
+
creatable = false,
|
|
235
|
+
onCreate,
|
|
236
|
+
getOptionFromInput,
|
|
237
|
+
inputValue: controlledInputValue,
|
|
238
|
+
onInputChange,
|
|
239
|
+
renderLabel = defaultRenderLabel,
|
|
240
|
+
renderKey = defaultRenderKey as (option: T) => string | number,
|
|
241
|
+
renderOption,
|
|
242
|
+
renderTag,
|
|
243
|
+
filterOptions,
|
|
244
|
+
loading = false,
|
|
245
|
+
disabled = false,
|
|
246
|
+
placeholder,
|
|
247
|
+
maxHeight,
|
|
248
|
+
maxTagsToShow,
|
|
249
|
+
autoHighlight = false,
|
|
250
|
+
clearOnSelect = multiple,
|
|
251
|
+
openOnFocus = true,
|
|
252
|
+
onSelect,
|
|
253
|
+
noOptionsText,
|
|
254
|
+
loadingText,
|
|
255
|
+
onScrollEnd,
|
|
256
|
+
scrollEndMargin = 200,
|
|
257
|
+
tagColorScheme,
|
|
258
|
+
tagColorPalette,
|
|
259
|
+
tagAppearance = 'circle',
|
|
260
|
+
style,
|
|
261
|
+
className,
|
|
262
|
+
...props
|
|
263
|
+
}: AutocompleteProps<T, Multiple>, ref: React.Ref<HTMLDivElement>) {
|
|
264
|
+
const t = useTranslate(dictionary)
|
|
265
|
+
const _element = useRef<HTMLDivElement | null>(null)
|
|
266
|
+
const inputRef = useRef<HTMLInputElement | null>(null)
|
|
267
|
+
const dropdownRef = useRef<HTMLDivElement | null>(null)
|
|
268
|
+
const element = (ref as React.RefObject<HTMLDivElement>) ?? _element
|
|
269
|
+
|
|
270
|
+
const [open, setOpen] = useState(false)
|
|
271
|
+
const [focused, setFocused] = useState(false)
|
|
272
|
+
const [internalInputValue, setInternalInputValue] = useState('')
|
|
273
|
+
const [highlightedIndex, setHighlightedIndex] = useState<number>(-1)
|
|
274
|
+
|
|
275
|
+
useFocusEffect({ element, focused, setFocused, setOpen })
|
|
276
|
+
useDisabledEffect({ disabled, setOpen, setFocused })
|
|
277
|
+
|
|
278
|
+
useEffect(() => {
|
|
279
|
+
if (!open) return
|
|
280
|
+
|
|
281
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
282
|
+
if (element.current && !element.current.contains(event.target as Node)) {
|
|
283
|
+
setOpen(false)
|
|
284
|
+
setFocused(false)
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
setTimeout(() => {
|
|
289
|
+
document.addEventListener('click', handleClickOutside)
|
|
290
|
+
}, 10)
|
|
291
|
+
|
|
292
|
+
return () => {
|
|
293
|
+
document.removeEventListener('click', handleClickOutside)
|
|
294
|
+
}
|
|
295
|
+
}, [open, element])
|
|
296
|
+
|
|
297
|
+
const inputValue = controlledInputValue ?? internalInputValue
|
|
298
|
+
const setInputValue = useCallback((newValue: string) => {
|
|
299
|
+
if (onInputChange) {
|
|
300
|
+
onInputChange(newValue)
|
|
301
|
+
} else {
|
|
302
|
+
setInternalInputValue(newValue)
|
|
303
|
+
}
|
|
304
|
+
}, [onInputChange])
|
|
305
|
+
|
|
306
|
+
const defaultFilter = useCallback((opts: T[], input: string) => {
|
|
307
|
+
if (!input) return opts
|
|
308
|
+
return opts.filter(option =>
|
|
309
|
+
renderLabel(option).toLowerCase().includes(input.toLowerCase()),
|
|
310
|
+
)
|
|
311
|
+
}, [renderLabel])
|
|
312
|
+
|
|
313
|
+
const filter = filterOptions ?? defaultFilter
|
|
314
|
+
|
|
315
|
+
const filteredOptions = useMemo(() => filter(options, inputValue), [options, inputValue, filter])
|
|
316
|
+
|
|
317
|
+
const showCreateOption = useMemo(() => {
|
|
318
|
+
if (!creatable || !onCreate || !inputValue.trim()) return false
|
|
319
|
+
|
|
320
|
+
const hasExactMatch = filteredOptions.some(option =>
|
|
321
|
+
renderLabel(option).toLowerCase() === inputValue.toLowerCase(),
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
return !hasExactMatch
|
|
325
|
+
}, [creatable, onCreate, inputValue, filteredOptions, renderLabel])
|
|
326
|
+
|
|
327
|
+
const handleCreate = useCallback(() => {
|
|
328
|
+
if (!onCreate || !inputValue.trim()) return
|
|
329
|
+
|
|
330
|
+
onCreate(inputValue.trim())
|
|
331
|
+
setInputValue('')
|
|
332
|
+
|
|
333
|
+
if (inputRef.current) {
|
|
334
|
+
inputRef.current.focus()
|
|
335
|
+
}
|
|
336
|
+
}, [onCreate, inputValue, setInputValue])
|
|
337
|
+
|
|
338
|
+
const isSelected = useCallback((option: T) => {
|
|
339
|
+
if (multiple) {
|
|
340
|
+
return (value as T[]).some(v => renderKey(v) === renderKey(option))
|
|
341
|
+
}
|
|
342
|
+
return value !== null && renderKey(value as T) === renderKey(option)
|
|
343
|
+
}, [value, multiple, renderKey])
|
|
344
|
+
|
|
345
|
+
const handleSelect = useCallback((option: T) => {
|
|
346
|
+
if (onSelect) onSelect(option)
|
|
347
|
+
|
|
348
|
+
if (multiple) {
|
|
349
|
+
const currentValue = value as T[]
|
|
350
|
+
const isAlreadySelected = currentValue.some(v => renderKey(v) === renderKey(option))
|
|
351
|
+
|
|
352
|
+
if (isAlreadySelected) {
|
|
353
|
+
const newValue = currentValue.filter(v => renderKey(v) !== renderKey(option));
|
|
354
|
+
(onChange as (value: T[]) => void)(newValue)
|
|
355
|
+
} else {
|
|
356
|
+
(onChange as (value: T[]) => void)([...currentValue, option])
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (clearOnSelect) {
|
|
360
|
+
setInputValue('')
|
|
361
|
+
}
|
|
362
|
+
} else {
|
|
363
|
+
(onChange as (value: T | null) => void)(option)
|
|
364
|
+
setInputValue(renderLabel(option))
|
|
365
|
+
setOpen(false)
|
|
366
|
+
}
|
|
367
|
+
}, [multiple, value, onChange, renderKey, renderLabel, clearOnSelect, setInputValue, onSelect])
|
|
368
|
+
|
|
369
|
+
const handleRemoveTag = useCallback((optionToRemove: T) => {
|
|
370
|
+
if (!multiple) return
|
|
371
|
+
const newValue = (value as T[]).filter(v => renderKey(v) !== renderKey(optionToRemove));
|
|
372
|
+
(onChange as (value: T[]) => void)(newValue)
|
|
373
|
+
}, [multiple, value, onChange, renderKey])
|
|
374
|
+
|
|
375
|
+
const handleInputChange = (newValue: string) => {
|
|
376
|
+
setInputValue(newValue)
|
|
377
|
+
if (!open && newValue) {
|
|
378
|
+
setOpen(true)
|
|
379
|
+
}
|
|
380
|
+
setHighlightedIndex(autoHighlight ? 0 : -1)
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const handleFocus = () => {
|
|
384
|
+
setFocused(true)
|
|
385
|
+
if (openOnFocus) {
|
|
386
|
+
setOpen(true)
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const handleBlur = (e: React.FocusEvent) => {
|
|
391
|
+
if (element.current?.contains(e.relatedTarget as Node)) {
|
|
392
|
+
return
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
setFocused(false)
|
|
396
|
+
setOpen(false)
|
|
397
|
+
|
|
398
|
+
if (freeSolo && inputValue && !multiple) {
|
|
399
|
+
if (creatable && !onCreate) {
|
|
400
|
+
if (getOptionFromInput) {
|
|
401
|
+
const newOption = getOptionFromInput(inputValue.trim());
|
|
402
|
+
(onChange as (value: T | null) => void)(newOption)
|
|
403
|
+
} else {
|
|
404
|
+
(onChange as (value: T | null) => void)(inputValue as unknown as T)
|
|
405
|
+
}
|
|
406
|
+
} else {
|
|
407
|
+
const exactMatch = options.find(o =>
|
|
408
|
+
renderLabel(o).toLowerCase() === inputValue.toLowerCase(),
|
|
409
|
+
)
|
|
410
|
+
if (exactMatch) {
|
|
411
|
+
handleSelect(exactMatch)
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
418
|
+
if (disabled) return
|
|
419
|
+
|
|
420
|
+
switch (e.key) {
|
|
421
|
+
case 'ArrowDown':
|
|
422
|
+
e.preventDefault()
|
|
423
|
+
if (!open) {
|
|
424
|
+
setOpen(true)
|
|
425
|
+
} else {
|
|
426
|
+
setHighlightedIndex(prev =>
|
|
427
|
+
prev < filteredOptions.length - 1 ? prev + 1 : prev,
|
|
428
|
+
)
|
|
429
|
+
}
|
|
430
|
+
break
|
|
431
|
+
|
|
432
|
+
case 'ArrowUp':
|
|
433
|
+
e.preventDefault()
|
|
434
|
+
if (open) {
|
|
435
|
+
setHighlightedIndex(prev => prev > 0 ? prev - 1 : 0)
|
|
436
|
+
}
|
|
437
|
+
break
|
|
438
|
+
|
|
439
|
+
case 'Enter':
|
|
440
|
+
e.preventDefault()
|
|
441
|
+
if (open && highlightedIndex >= 0 && filteredOptions[highlightedIndex]) {
|
|
442
|
+
handleSelect(filteredOptions[highlightedIndex])
|
|
443
|
+
} else if (creatable && inputValue.trim()) {
|
|
444
|
+
if (onCreate) {
|
|
445
|
+
handleCreate()
|
|
446
|
+
} else if (freeSolo && getOptionFromInput) {
|
|
447
|
+
const newOption = getOptionFromInput(inputValue.trim())
|
|
448
|
+
if (multiple) {
|
|
449
|
+
const currentValue = value as T[]
|
|
450
|
+
const isDuplicate = currentValue.some(v => renderKey(v) === renderKey(newOption))
|
|
451
|
+
if (!isDuplicate) {
|
|
452
|
+
(onChange as (value: T[]) => void)([...currentValue, newOption])
|
|
453
|
+
}
|
|
454
|
+
setInputValue('')
|
|
455
|
+
} else {
|
|
456
|
+
(onChange as (value: T | null) => void)(newOption)
|
|
457
|
+
setInputValue(renderLabel(newOption))
|
|
458
|
+
setOpen(false)
|
|
459
|
+
}
|
|
460
|
+
} else if (freeSolo) {
|
|
461
|
+
if (multiple) {
|
|
462
|
+
const currentValue = value as T[]
|
|
463
|
+
const inputAsOption = inputValue as unknown as T
|
|
464
|
+
const isDuplicate = currentValue.some(v => renderLabel(v).toLowerCase() === inputValue.toLowerCase())
|
|
465
|
+
if (!isDuplicate) {
|
|
466
|
+
(onChange as (value: T[]) => void)([...currentValue, inputAsOption])
|
|
467
|
+
}
|
|
468
|
+
setInputValue('')
|
|
469
|
+
} else {
|
|
470
|
+
(onChange as (value: T | null) => void)(inputValue as unknown as T)
|
|
471
|
+
setOpen(false)
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
} else if (freeSolo && inputValue && !multiple) {
|
|
475
|
+
const exactMatch = options.find(o =>
|
|
476
|
+
renderLabel(o).toLowerCase() === inputValue.toLowerCase(),
|
|
477
|
+
)
|
|
478
|
+
if (exactMatch) {
|
|
479
|
+
handleSelect(exactMatch)
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
break
|
|
483
|
+
|
|
484
|
+
case 'Escape':
|
|
485
|
+
e.preventDefault()
|
|
486
|
+
setOpen(false)
|
|
487
|
+
if (inputRef.current) {
|
|
488
|
+
inputRef.current.blur()
|
|
489
|
+
}
|
|
490
|
+
break
|
|
491
|
+
|
|
492
|
+
case 'Backspace':
|
|
493
|
+
if (multiple && !inputValue && (value as T[]).length > 0) {
|
|
494
|
+
const lastTag = (value as T[])[(value as T[]).length - 1]
|
|
495
|
+
handleRemoveTag(lastTag)
|
|
496
|
+
}
|
|
497
|
+
break
|
|
498
|
+
|
|
499
|
+
default:
|
|
500
|
+
break
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const handleClear = () => {
|
|
505
|
+
if (multiple) {
|
|
506
|
+
(onChange as (value: T[]) => void)([])
|
|
507
|
+
} else {
|
|
508
|
+
(onChange as (value: T | null) => void)(null)
|
|
509
|
+
}
|
|
510
|
+
setInputValue('')
|
|
511
|
+
if (inputRef.current) {
|
|
512
|
+
inputRef.current.focus()
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
useEffect(() => {
|
|
517
|
+
if (!multiple && value && !focused) {
|
|
518
|
+
setInternalInputValue(renderLabel(value as T))
|
|
519
|
+
}
|
|
520
|
+
}, [value, multiple, renderLabel, focused])
|
|
521
|
+
|
|
522
|
+
useEffect(() => {
|
|
523
|
+
if (autoHighlight && open && filteredOptions.length > 0) {
|
|
524
|
+
setHighlightedIndex(0)
|
|
525
|
+
}
|
|
526
|
+
}, [autoHighlight, open, filteredOptions.length])
|
|
527
|
+
|
|
528
|
+
useEffect(() => {
|
|
529
|
+
if (highlightedIndex < 0 || !open) return
|
|
530
|
+
|
|
531
|
+
const optionsContainer = dropdownRef.current?.querySelector('.options') as HTMLElement
|
|
532
|
+
if (!optionsContainer) return
|
|
533
|
+
|
|
534
|
+
const highlightedOption = optionsContainer.children[highlightedIndex] as HTMLElement
|
|
535
|
+
if (!highlightedOption) return
|
|
536
|
+
|
|
537
|
+
const containerRect = optionsContainer.getBoundingClientRect()
|
|
538
|
+
const optionRect = highlightedOption.getBoundingClientRect()
|
|
539
|
+
|
|
540
|
+
if (optionRect.bottom > containerRect.bottom) {
|
|
541
|
+
highlightedOption.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
|
|
542
|
+
} else if (optionRect.top < containerRect.top) {
|
|
543
|
+
highlightedOption.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
|
|
544
|
+
}
|
|
545
|
+
}, [highlightedIndex, open])
|
|
546
|
+
|
|
547
|
+
useEffect(() => {
|
|
548
|
+
if (!onScrollEnd || !open) return
|
|
549
|
+
|
|
550
|
+
const optionsContainer = dropdownRef.current?.querySelector('.options') as HTMLElement
|
|
551
|
+
if (!optionsContainer) return
|
|
552
|
+
|
|
553
|
+
const handleScroll = () => {
|
|
554
|
+
if (loading) return
|
|
555
|
+
|
|
556
|
+
const { scrollTop, scrollHeight, clientHeight } = optionsContainer
|
|
557
|
+
const scrollBottom = scrollHeight - scrollTop - clientHeight
|
|
558
|
+
|
|
559
|
+
if (scrollBottom <= scrollEndMargin) {
|
|
560
|
+
onScrollEnd()
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
optionsContainer.addEventListener('scroll', handleScroll)
|
|
565
|
+
|
|
566
|
+
handleScroll()
|
|
567
|
+
|
|
568
|
+
return () => {
|
|
569
|
+
optionsContainer.removeEventListener('scroll', handleScroll)
|
|
570
|
+
}
|
|
571
|
+
}, [onScrollEnd, open, filteredOptions.length, loading, scrollEndMargin])
|
|
572
|
+
|
|
573
|
+
const renderTags = () => {
|
|
574
|
+
if (!multiple || (value as T[]).length === 0) return null
|
|
575
|
+
|
|
576
|
+
const tags = value as T[]
|
|
577
|
+
const visibleTags = maxTagsToShow && tags.length > maxTagsToShow
|
|
578
|
+
? tags.slice(0, maxTagsToShow)
|
|
579
|
+
: tags
|
|
580
|
+
const remainingCount = maxTagsToShow && tags.length > maxTagsToShow
|
|
581
|
+
? tags.length - maxTagsToShow
|
|
582
|
+
: 0
|
|
583
|
+
|
|
584
|
+
return (
|
|
585
|
+
<>
|
|
586
|
+
{visibleTags.map(tag => {
|
|
587
|
+
if (renderTag) {
|
|
588
|
+
return renderTag(tag, () => handleRemoveTag(tag))
|
|
589
|
+
}
|
|
590
|
+
return (
|
|
591
|
+
<Badge
|
|
592
|
+
key={renderKey(tag)}
|
|
593
|
+
colorScheme={tagColorScheme}
|
|
594
|
+
colorPalette={tagColorPalette}
|
|
595
|
+
appearance={tagAppearance}
|
|
596
|
+
>
|
|
597
|
+
{renderLabel(tag)}
|
|
598
|
+
{!disabled && (
|
|
599
|
+
<IconButton
|
|
600
|
+
icon="Times"
|
|
601
|
+
type="button"
|
|
602
|
+
appearance="none"
|
|
603
|
+
size="xs"
|
|
604
|
+
style={{ color: 'inherit' }}
|
|
605
|
+
onClick={(e) => {
|
|
606
|
+
e.stopPropagation()
|
|
607
|
+
if (!disabled) handleRemoveTag(tag)
|
|
608
|
+
}}
|
|
609
|
+
aria-label={`${t.removeTag} ${renderLabel(tag)}`}
|
|
610
|
+
disabled={disabled}
|
|
611
|
+
tabIndex={0}
|
|
612
|
+
/>)}
|
|
613
|
+
</Badge>
|
|
614
|
+
)
|
|
615
|
+
})}
|
|
616
|
+
{remainingCount > 0 && (
|
|
617
|
+
<Badge
|
|
618
|
+
colorScheme={tagColorScheme}
|
|
619
|
+
colorPalette={tagColorPalette}
|
|
620
|
+
appearance={tagAppearance}
|
|
621
|
+
>
|
|
622
|
+
+{remainingCount}
|
|
623
|
+
</Badge>
|
|
624
|
+
)}
|
|
625
|
+
</>
|
|
626
|
+
)
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const showClearButton = !disabled && (
|
|
630
|
+
(!multiple && value !== null) ||
|
|
631
|
+
(multiple && (value as T[]).length > 0)
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
return (
|
|
635
|
+
<CitricComponent
|
|
636
|
+
tag="div"
|
|
637
|
+
component="autocomplete"
|
|
638
|
+
data-citric="autocomplete"
|
|
639
|
+
style={maxHeight ? applyCSSVariable(style, 'max-height', `${maxHeight}px`) : style}
|
|
640
|
+
className={listToClass([
|
|
641
|
+
className,
|
|
642
|
+
open && 'open',
|
|
643
|
+
focused && 'focused',
|
|
644
|
+
disabled && 'disabled',
|
|
645
|
+
multiple && 'multiple',
|
|
646
|
+
])}
|
|
647
|
+
ref={element}
|
|
648
|
+
aria-busy={loading}
|
|
649
|
+
{...props}
|
|
650
|
+
>
|
|
651
|
+
<header
|
|
652
|
+
onClick={() => {
|
|
653
|
+
if (disabled) return
|
|
654
|
+
setFocused(true)
|
|
655
|
+
setOpen(true)
|
|
656
|
+
if (inputRef.current) {
|
|
657
|
+
inputRef.current.focus()
|
|
658
|
+
}
|
|
659
|
+
}}
|
|
660
|
+
onFocus={() => setFocused(true)}
|
|
661
|
+
tabIndex={disabled ? undefined : 0}
|
|
662
|
+
>
|
|
663
|
+
<Row gap="4px" className="input-container">
|
|
664
|
+
{multiple && renderTags()}
|
|
665
|
+
<input
|
|
666
|
+
ref={inputRef}
|
|
667
|
+
type="text"
|
|
668
|
+
value={inputValue}
|
|
669
|
+
onChange={(e) => handleInputChange(e.target.value)}
|
|
670
|
+
onFocus={handleFocus}
|
|
671
|
+
onBlur={handleBlur}
|
|
672
|
+
onKeyDown={handleKeyDown}
|
|
673
|
+
disabled={disabled}
|
|
674
|
+
placeholder={multiple && (value as T[]).length > 0 ? '' : placeholder}
|
|
675
|
+
autoComplete="off"
|
|
676
|
+
aria-autocomplete="list"
|
|
677
|
+
aria-expanded={open}
|
|
678
|
+
aria-controls="autocomplete-listbox"
|
|
679
|
+
/>
|
|
680
|
+
</Row>
|
|
681
|
+
<div className="end-adornment">
|
|
682
|
+
{loading && <ProgressCircular size="xs" className="loader" />}
|
|
683
|
+
{showClearButton && (
|
|
684
|
+
<IconButton
|
|
685
|
+
icon="Times"
|
|
686
|
+
appearance="none"
|
|
687
|
+
size="sm"
|
|
688
|
+
type="button"
|
|
689
|
+
onClick={(e) => {
|
|
690
|
+
e.stopPropagation()
|
|
691
|
+
handleClear()
|
|
692
|
+
}}
|
|
693
|
+
disabled={disabled}
|
|
694
|
+
aria-label={t.clear}
|
|
695
|
+
tabIndex={1}
|
|
696
|
+
style={{ width: '12px', height: '12px' }}
|
|
697
|
+
/>
|
|
698
|
+
)}
|
|
699
|
+
<IconButton
|
|
700
|
+
icon={open ? 'ChevronUp' : 'ChevronDown'}
|
|
701
|
+
appearance="none"
|
|
702
|
+
size="md"
|
|
703
|
+
type="button"
|
|
704
|
+
onClick={(e) => {
|
|
705
|
+
e.stopPropagation()
|
|
706
|
+
setOpen((prev) => !prev)
|
|
707
|
+
}}
|
|
708
|
+
disabled={disabled}
|
|
709
|
+
aria-label={open ? t.collapse : t.expand}
|
|
710
|
+
tabIndex={1}
|
|
711
|
+
style={{ width: '12px', height: '12px' }}
|
|
712
|
+
/>
|
|
713
|
+
</div>
|
|
714
|
+
</header>
|
|
715
|
+
|
|
716
|
+
<div
|
|
717
|
+
className="dropdown-panel"
|
|
718
|
+
ref={dropdownRef}
|
|
719
|
+
id="autocomplete-listbox"
|
|
720
|
+
role="listbox"
|
|
721
|
+
aria-hidden={!open}
|
|
722
|
+
{...(open ? {} : { inert: 'true' })}
|
|
723
|
+
>
|
|
724
|
+
{loading && !filteredOptions.length ? (
|
|
725
|
+
<div className="message">{loadingText || t.loading}</div>
|
|
726
|
+
) : filteredOptions.length === 0 && !showCreateOption ? (
|
|
727
|
+
<div className="message">{noOptionsText || t.noOptions}</div>
|
|
728
|
+
) : (
|
|
729
|
+
<div className="options">
|
|
730
|
+
{showCreateOption && (
|
|
731
|
+
<div
|
|
732
|
+
key="create-option"
|
|
733
|
+
role="option"
|
|
734
|
+
className="option create-option"
|
|
735
|
+
onMouseDown={(e) => {
|
|
736
|
+
e.preventDefault()
|
|
737
|
+
}}
|
|
738
|
+
onClick={handleCreate}
|
|
739
|
+
onMouseEnter={() => setHighlightedIndex(-1)}
|
|
740
|
+
>
|
|
741
|
+
<i data-citric="icon" className="citric-icon outline Plus" />
|
|
742
|
+
{t.addOption.replace('{value}', inputValue)}
|
|
743
|
+
</div>
|
|
744
|
+
)}
|
|
745
|
+
{filteredOptions.map((option, index) => (
|
|
746
|
+
<div
|
|
747
|
+
key={renderKey(option)}
|
|
748
|
+
role="option"
|
|
749
|
+
aria-selected={isSelected(option)}
|
|
750
|
+
className={listToClass([
|
|
751
|
+
'option',
|
|
752
|
+
isSelected(option) && 'selected',
|
|
753
|
+
highlightedIndex === index && 'highlighted',
|
|
754
|
+
])}
|
|
755
|
+
onMouseDown={(e) => {
|
|
756
|
+
e.preventDefault()
|
|
757
|
+
}}
|
|
758
|
+
onClick={() => handleSelect(option)}
|
|
759
|
+
onMouseEnter={() => setHighlightedIndex(index)}
|
|
760
|
+
>
|
|
761
|
+
{multiple && <Checkbox value={isSelected(option)} readOnly />}
|
|
762
|
+
{renderOption ? renderOption(option) : renderLabel(option)}
|
|
763
|
+
</div>
|
|
764
|
+
))}
|
|
765
|
+
</div>
|
|
766
|
+
)}
|
|
767
|
+
</div>
|
|
768
|
+
</CitricComponent>
|
|
769
|
+
)
|
|
770
|
+
},
|
|
771
|
+
) as <T, Multiple extends boolean = false>(
|
|
772
|
+
props: AutocompleteProps<T, Multiple>,
|
|
773
|
+
) => React.ReactElement
|
|
774
|
+
|
|
775
|
+
const dictionary = {
|
|
776
|
+
en: {
|
|
777
|
+
removeTag: 'Remove',
|
|
778
|
+
clear: 'Clear',
|
|
779
|
+
loading: 'Loading...',
|
|
780
|
+
noOptions: 'No options',
|
|
781
|
+
collapse: 'Collapse',
|
|
782
|
+
expand: 'Expand',
|
|
783
|
+
addOption: 'Add "{value}"',
|
|
784
|
+
},
|
|
785
|
+
pt: {
|
|
786
|
+
removeTag: 'Remover',
|
|
787
|
+
clear: 'Limpar',
|
|
788
|
+
loading: 'Carregando...',
|
|
789
|
+
noOptions: 'Sem opções',
|
|
790
|
+
collapse: 'Recolher',
|
|
791
|
+
expand: 'Expandir',
|
|
792
|
+
addOption: 'Adicionar "{value}"',
|
|
793
|
+
},
|
|
794
|
+
}
|