@stack-spot/citric-react 0.36.0 → 0.37.1-beta.1
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/dist/citric.css +2844 -2832
- 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/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.js +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/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 +17 -3
- package/dist/components/Rating.d.ts.map +1 -1
- package/dist/components/Rating.js +11 -3
- package/dist/components/Rating.js.map +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/overlay.js +1 -1
- package/dist/theme.css +415 -415
- package/package.json +2 -2
- 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 +70 -70
- 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 +51 -51
- package/src/components/CheckboxGroup.tsx +152 -152
- 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 +8 -8
- 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 +164 -164
- package/src/components/Overlay/types.ts +70 -70
- package/src/components/Pagination.tsx +113 -113
- package/src/components/ProgressBar.tsx +45 -45
- package/src/components/ProgressCircular.tsx +45 -45
- package/src/components/RadioGroup.tsx +146 -146
- package/src/components/Rating.tsx +98 -35
- package/src/components/Select/MultiSelect.tsx +346 -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 +87 -87
- package/src/components/Tabs/types.ts +54 -54
- 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 +72 -72
- 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 +58 -58
- package/src/overlay.ts +341 -341
- package/src/types.ts +216 -216
- 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/tsconfig.json +10 -10
|
@@ -1,217 +1,346 @@
|
|
|
1
|
-
import { listToClass } from '@stack-spot/portal-theme'
|
|
2
|
-
import { useTranslate } from '@stack-spot/portal-translate'
|
|
3
|
-
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
4
|
-
import { useCheckboxGroupControls } from '../../utils/checkbox'
|
|
5
|
-
import { applyCSSVariable } from '../../utils/css'
|
|
6
|
-
import { defaultRenderKey, defaultRenderLabel } from '../../utils/options'
|
|
7
|
-
import { withRef } from '../../utils/react'
|
|
8
|
-
import { Checkbox } from '../Checkbox'
|
|
9
|
-
import { CheckboxGroup } from '../CheckboxGroup'
|
|
10
|
-
import { CitricComponent } from '../CitricComponent'
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
value
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* @
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* @
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* @
|
|
43
|
-
* @
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
)
|
|
201
|
-
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
1
|
+
import { listToClass } from '@stack-spot/portal-theme'
|
|
2
|
+
import { useTranslate } from '@stack-spot/portal-translate'
|
|
3
|
+
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
4
|
+
import { useCheckboxGroupControls } from '../../utils/checkbox'
|
|
5
|
+
import { applyCSSVariable } from '../../utils/css'
|
|
6
|
+
import { defaultRenderKey, defaultRenderLabel } from '../../utils/options'
|
|
7
|
+
import { withRef } from '../../utils/react'
|
|
8
|
+
import { Checkbox } from '../Checkbox'
|
|
9
|
+
import { CheckboxGroup } from '../CheckboxGroup'
|
|
10
|
+
import { CitricComponent } from '../CitricComponent'
|
|
11
|
+
import { IconButton } from '../IconBox'
|
|
12
|
+
import { Input } from '../Input'
|
|
13
|
+
import { Row } from '../layout'
|
|
14
|
+
import { ProgressCircular } from '../ProgressCircular'
|
|
15
|
+
import { useDisabledEffect, useFocusEffect, useOpenPanelEffect } from './hooks'
|
|
16
|
+
import { RichSelectProps } from './types'
|
|
17
|
+
|
|
18
|
+
export interface BaseMultiSelectProps<T> extends
|
|
19
|
+
Omit<RichSelectProps<T>, 'value' | 'onChange' | 'renderHeader' | 'renderLabel' | 'renderOption' | 'required' | 'onFocus' | 'onBlur'> {
|
|
20
|
+
value: T[],
|
|
21
|
+
onChange: (value: T[]) => void,
|
|
22
|
+
/**
|
|
23
|
+
* A function to render the option in the selectable list.
|
|
24
|
+
*
|
|
25
|
+
* The `renderLabel` function is used if this is not provided.
|
|
26
|
+
* @param value the option.
|
|
27
|
+
* @returns the React Node.
|
|
28
|
+
*/
|
|
29
|
+
renderOption?: (option: T) => React.ReactNode,
|
|
30
|
+
/**
|
|
31
|
+
* A function to render the selected options in the header.
|
|
32
|
+
*
|
|
33
|
+
* The `renderOption` function is used if this is not provided.
|
|
34
|
+
* @param value the option.
|
|
35
|
+
* @returns the React Node.
|
|
36
|
+
*/
|
|
37
|
+
renderHeader?: (value: T[]) => React.ReactNode,
|
|
38
|
+
/**
|
|
39
|
+
* A function to render the item label.
|
|
40
|
+
* @example
|
|
41
|
+
* `(option) => option.name`
|
|
42
|
+
* @default "the item's toString() result."
|
|
43
|
+
* @param option the item to render.
|
|
44
|
+
* @returns a React Node to render.
|
|
45
|
+
*/
|
|
46
|
+
renderLabel?: (option: T) => string,
|
|
47
|
+
/**
|
|
48
|
+
* Whether or not to show a checkbox to select all or remove the selection.
|
|
49
|
+
*
|
|
50
|
+
* @default false
|
|
51
|
+
*/
|
|
52
|
+
showSelectAll?: boolean,
|
|
53
|
+
/**
|
|
54
|
+
* Whether to render selected values as removable chips/tags.
|
|
55
|
+
*
|
|
56
|
+
* @default false
|
|
57
|
+
*/
|
|
58
|
+
showAsChips?: boolean,
|
|
59
|
+
/**
|
|
60
|
+
* Whether to allow adding custom values that don't exist in options.
|
|
61
|
+
* When enabled, typing in the search and pressing Enter will add the value.
|
|
62
|
+
* The value will be added as a string to the list.
|
|
63
|
+
*
|
|
64
|
+
* @default false
|
|
65
|
+
*/
|
|
66
|
+
allowCustomOptions?: boolean,
|
|
67
|
+
/**
|
|
68
|
+
* Function to create a new option from a string input.
|
|
69
|
+
* Required when `allowCustomOptions` is true.
|
|
70
|
+
*
|
|
71
|
+
* @param input the string input from the user
|
|
72
|
+
* @returns the new option of type T
|
|
73
|
+
*/
|
|
74
|
+
createOption?: (inputValue: string) => T,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export type MultiSelectProps<T> = Omit<React.JSX.IntrinsicElements['div'], 'ref' | 'onChange' | 'onFocus' | 'onBlur'> &
|
|
78
|
+
BaseMultiSelectProps<T>
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* A component that looks like a Select and behaves like a CheckboxGroup. This is a component that lets the user select multiple options
|
|
82
|
+
* in a list.
|
|
83
|
+
*
|
|
84
|
+
* Differently than then the component Select, this does not render the native select of the browser. Instead, it renders a series of
|
|
85
|
+
* checkboxes.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
*
|
|
89
|
+
* ```
|
|
90
|
+
* const options = useMemo(() => [
|
|
91
|
+
* { id: 1, name: 'Option 1' },
|
|
92
|
+
* { id: 2, name: 'Option 2' },
|
|
93
|
+
* { id: 3, name: 'Option 3' },
|
|
94
|
+
* ], [])
|
|
95
|
+
*
|
|
96
|
+
* const [value, setValue] = useState<typeof options>([])
|
|
97
|
+
*
|
|
98
|
+
* return <MultiSelect options={options} renderLabel={o => o.name} renderKey={o => o.id} value={value} setValue={setValue} />
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export const MultiSelect = withRef(
|
|
102
|
+
function MultiSelect<T>({
|
|
103
|
+
ref,
|
|
104
|
+
options,
|
|
105
|
+
value = [],
|
|
106
|
+
onChange,
|
|
107
|
+
renderLabel = defaultRenderLabel,
|
|
108
|
+
renderKey = defaultRenderKey,
|
|
109
|
+
disabled,
|
|
110
|
+
loading,
|
|
111
|
+
renderOption,
|
|
112
|
+
renderHeader,
|
|
113
|
+
searchable,
|
|
114
|
+
maxHeight,
|
|
115
|
+
style,
|
|
116
|
+
className,
|
|
117
|
+
showArrow,
|
|
118
|
+
placeholder,
|
|
119
|
+
showSelectAll,
|
|
120
|
+
showAsChips = false,
|
|
121
|
+
allowCustomOptions = false,
|
|
122
|
+
createOption,
|
|
123
|
+
...props
|
|
124
|
+
}: MultiSelectProps<T>,
|
|
125
|
+
) {
|
|
126
|
+
const t = useTranslate(dictionary)
|
|
127
|
+
const _element = useRef<HTMLDivElement | null>(null)
|
|
128
|
+
const element = ref ?? _element
|
|
129
|
+
const [open, setOpen] = useState(false)
|
|
130
|
+
const [focused, setFocused] = useState(false)
|
|
131
|
+
|
|
132
|
+
// Merge options with selected values that are not in the original options
|
|
133
|
+
const mergedOptions = useMemo(() => {
|
|
134
|
+
const optionKeys = new Set(options.map(renderKey))
|
|
135
|
+
const extraValues = value.filter(v => !optionKeys.has(renderKey(v)))
|
|
136
|
+
return [...options, ...extraValues]
|
|
137
|
+
}, [options, value, renderKey])
|
|
138
|
+
|
|
139
|
+
const controls = useCheckboxGroupControls({
|
|
140
|
+
options: mergedOptions,
|
|
141
|
+
renderKey,
|
|
142
|
+
initialValue: value,
|
|
143
|
+
onChange,
|
|
144
|
+
applyFilter: (filter, option) => {
|
|
145
|
+
const label = renderLabel(option)
|
|
146
|
+
if (!label) return false
|
|
147
|
+
return label.toLocaleLowerCase().includes(filter.toLocaleLowerCase())
|
|
148
|
+
},
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
useOpenPanelEffect({ open, setOpen, setSearch: controls.setFilter, element, searchable })
|
|
152
|
+
useFocusEffect({ element, focused, setFocused, setOpen })
|
|
153
|
+
useDisabledEffect({ disabled, setOpen, setFocused })
|
|
154
|
+
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
if (value !== controls.value) controls.setValue(value)
|
|
157
|
+
}, [value.map(renderKey).join(',')])
|
|
158
|
+
|
|
159
|
+
const handleRemoveChip = (option: T) => {
|
|
160
|
+
const newValue = value.filter(v => renderKey(v) !== renderKey(option))
|
|
161
|
+
controls.setValue(newValue)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const handleAddCustomValue = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
165
|
+
if (!allowCustomOptions || !createOption || !controls.filter) return
|
|
166
|
+
const filterValue = String(controls.filter).trim()
|
|
167
|
+
if (e.key === 'Enter' && filterValue && filterValue.length > 0) {
|
|
168
|
+
e.preventDefault()
|
|
169
|
+
const newOption = createOption(filterValue)
|
|
170
|
+
const exists = value.some(v => {
|
|
171
|
+
const key1 = renderKey(v)
|
|
172
|
+
const key2 = renderKey(newOption)
|
|
173
|
+
if (typeof key1 === 'string' && typeof key2 === 'string') {
|
|
174
|
+
return key1.toLowerCase() === key2.toLowerCase()
|
|
175
|
+
}
|
|
176
|
+
return key1 === key2
|
|
177
|
+
})
|
|
178
|
+
if (!exists) {
|
|
179
|
+
controls.setValue([...value, newOption])
|
|
180
|
+
}
|
|
181
|
+
controls.setFilter('' as any)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Check if the current filter does not match any existing option
|
|
186
|
+
const hasTemporaryOption = useMemo(() => {
|
|
187
|
+
if (!allowCustomOptions || !controls.filter) return false
|
|
188
|
+
const filterValue = String(controls.filter).trim()
|
|
189
|
+
if (!filterValue || filterValue.length === 0) return false
|
|
190
|
+
|
|
191
|
+
const matchesExisting = mergedOptions.some(option => {
|
|
192
|
+
const label = renderLabel(option)
|
|
193
|
+
if (!label) return false
|
|
194
|
+
return label.toLowerCase() === filterValue.toLowerCase()
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
return !matchesExisting
|
|
198
|
+
}, [allowCustomOptions, controls.filter, mergedOptions, renderLabel])
|
|
199
|
+
|
|
200
|
+
const header = useMemo(() => {
|
|
201
|
+
if (value.length === 0) return <span className="placeholder header-text">{placeholder}</span>
|
|
202
|
+
const reversed = [...value].reverse()
|
|
203
|
+
|
|
204
|
+
if (showAsChips) {
|
|
205
|
+
return (
|
|
206
|
+
<Row className="header-chips" gap="4px">
|
|
207
|
+
{reversed.map(option => (
|
|
208
|
+
<span
|
|
209
|
+
key={renderKey(option)}
|
|
210
|
+
data-citric="badge"
|
|
211
|
+
className="chip"
|
|
212
|
+
>
|
|
213
|
+
<span>{renderLabel(option)}</span>
|
|
214
|
+
{!disabled && (
|
|
215
|
+
<IconButton
|
|
216
|
+
icon="Times"
|
|
217
|
+
type="button"
|
|
218
|
+
className="remove-button"
|
|
219
|
+
size="xs"
|
|
220
|
+
disabled={disabled}
|
|
221
|
+
onClick={(e) => {
|
|
222
|
+
e.stopPropagation()
|
|
223
|
+
handleRemoveChip(option)
|
|
224
|
+
}}
|
|
225
|
+
aria-label={`${t.remove} ${renderLabel(option)}`}
|
|
226
|
+
/>
|
|
227
|
+
)}
|
|
228
|
+
</span>
|
|
229
|
+
))}
|
|
230
|
+
</Row>
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
(renderHeader?.(reversed)
|
|
236
|
+
?? (renderOption
|
|
237
|
+
? <Row className="header-text">{reversed.map(renderOption)}</Row>
|
|
238
|
+
: <span className="header-text">{reversed.map(renderLabel).join(', ')}</span>
|
|
239
|
+
)
|
|
240
|
+
) || <span></span>
|
|
241
|
+
)}, [value, placeholder, showAsChips, disabled])
|
|
242
|
+
|
|
243
|
+
return (
|
|
244
|
+
<CitricComponent
|
|
245
|
+
tag="div"
|
|
246
|
+
component="multi-select"
|
|
247
|
+
style={maxHeight ? applyCSSVariable(style, 'max-height', `${maxHeight}px`) : style}
|
|
248
|
+
className={listToClass([
|
|
249
|
+
className,
|
|
250
|
+
showArrow === false && 'hide-arrow',
|
|
251
|
+
open && 'open',
|
|
252
|
+
focused && 'focused',
|
|
253
|
+
disabled && 'disabled',
|
|
254
|
+
showAsChips && 'with-chips',
|
|
255
|
+
])}
|
|
256
|
+
ref={element}
|
|
257
|
+
aria-busy={loading}
|
|
258
|
+
{...props}
|
|
259
|
+
>
|
|
260
|
+
<header
|
|
261
|
+
onClick={() => {
|
|
262
|
+
if (disabled) return
|
|
263
|
+
setFocused(true)
|
|
264
|
+
setOpen(true)
|
|
265
|
+
}}
|
|
266
|
+
onFocus={() => setFocused(true)}
|
|
267
|
+
aria-label={t.accessibilityHelp}
|
|
268
|
+
tabIndex={disabled ? undefined : 0}
|
|
269
|
+
className={renderHeader ? 'custom' : undefined}
|
|
270
|
+
>
|
|
271
|
+
{header}
|
|
272
|
+
{loading && <ProgressCircular size="xs" className="loader" />}
|
|
273
|
+
</header>
|
|
274
|
+
<div className="selection-panel" aria-hidden={!open} {...(open ? {} : { inert: 'true' })}>
|
|
275
|
+
{searchable && <div className="search-bar">
|
|
276
|
+
<div data-citric="field-group" className="auto">
|
|
277
|
+
<i data-citric="icon-box" className="citric-icon outline Search"></i>
|
|
278
|
+
<Input
|
|
279
|
+
type="search"
|
|
280
|
+
value={controls.filter}
|
|
281
|
+
onChange={controls.setFilter}
|
|
282
|
+
onKeyDown={handleAddCustomValue}
|
|
283
|
+
aria-label={t.searchAccessibility}
|
|
284
|
+
placeholder={allowCustomOptions ? t.searchOrAddPlaceholder : undefined}
|
|
285
|
+
/>
|
|
286
|
+
</div>
|
|
287
|
+
</div>}
|
|
288
|
+
{showSelectAll && (
|
|
289
|
+
<Checkbox
|
|
290
|
+
className="select-all"
|
|
291
|
+
onChange={checked => checked ? controls.selectAll() : controls.removeSelection()}
|
|
292
|
+
value={controls.isAllSelected}
|
|
293
|
+
>
|
|
294
|
+
{controls.isAllSelected ? t.removeSelection : t.selectAll}
|
|
295
|
+
</Checkbox>
|
|
296
|
+
)}
|
|
297
|
+
<CheckboxGroup
|
|
298
|
+
className="options"
|
|
299
|
+
gap="0"
|
|
300
|
+
options={controls.options}
|
|
301
|
+
onChange={controls.setValue}
|
|
302
|
+
value={controls.value}
|
|
303
|
+
renderKey={controls.renderKey}
|
|
304
|
+
focusable={false}
|
|
305
|
+
renderItem={(checkbox, option) => (
|
|
306
|
+
<CitricComponent
|
|
307
|
+
component="checkbox-row"
|
|
308
|
+
tag="label"
|
|
309
|
+
className={listToClass(['option', controls.isUnfilteredButChecked(option) && 'unfiltered'])}
|
|
310
|
+
>
|
|
311
|
+
{checkbox}
|
|
312
|
+
{renderOption?.(option) ?? renderLabel(option)}
|
|
313
|
+
</CitricComponent>
|
|
314
|
+
)}
|
|
315
|
+
/>
|
|
316
|
+
{hasTemporaryOption && (
|
|
317
|
+
<div className="temporary-option" style={{ fontStyle: 'italic', padding: '8px 16px', opacity: 0.7 }}>
|
|
318
|
+
{String(controls.filter).trim()} ({t.pressEnterToAdd})
|
|
319
|
+
</div>
|
|
320
|
+
)}
|
|
321
|
+
</div>
|
|
322
|
+
</CitricComponent>
|
|
323
|
+
)
|
|
324
|
+
},
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
const dictionary = {
|
|
328
|
+
en: {
|
|
329
|
+
accessibilityHelp: 'Press the arrow down to select multiple options',
|
|
330
|
+
searchAccessibility: 'Filter the options',
|
|
331
|
+
removeSelection: 'Remove selection',
|
|
332
|
+
selectAll: 'Select all',
|
|
333
|
+
remove: 'Remove',
|
|
334
|
+
searchOrAddPlaceholder: 'Search or press Enter to add',
|
|
335
|
+
pressEnterToAdd: 'press Enter to add',
|
|
336
|
+
},
|
|
337
|
+
pt: {
|
|
338
|
+
accessibilityHelp: 'Pressione a seta para baixo para selecionar múltiplas opções',
|
|
339
|
+
searchAccessibility: 'Filtre as opções',
|
|
340
|
+
removeSelection: 'Remover seleção',
|
|
341
|
+
selectAll: 'Selecionar todos',
|
|
342
|
+
remove: 'Remover',
|
|
343
|
+
searchOrAddPlaceholder: 'Busque ou pressione Enter para adicionar',
|
|
344
|
+
pressEnterToAdd: 'pressione Enter para adicionar',
|
|
345
|
+
},
|
|
346
|
+
}
|