@buerokratt-ria/common-gui-components 0.0.57 → 0.0.59

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 (26) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/package.json +1 -1
  3. package/templates/history-page/src/History.scss +33 -3
  4. package/templates/history-page/src/components/ChatMetadataPanel/ChatMetadataPanel.scss +18 -0
  5. package/templates/history-page/src/components/ChatMetadataPanel/index.tsx +206 -0
  6. package/templates/history-page/src/components/ChatMetadataPanelItem/index.tsx +17 -0
  7. package/templates/history-page/src/components/FilterTag/FilterTag.scss +42 -0
  8. package/templates/history-page/src/components/FilterTag/index.tsx +16 -0
  9. package/templates/history-page/src/components/HeaderCombobox/index.tsx +66 -0
  10. package/templates/history-page/src/components/QualitySettings/QualitySettings.scss +19 -0
  11. package/templates/history-page/src/components/QualitySettings/index.tsx +115 -0
  12. package/templates/history-page/src/components/SelectedFilterTags/SelectedFilterTags.scss +36 -0
  13. package/templates/history-page/src/components/SelectedFilterTags/index.tsx +224 -0
  14. package/templates/history-page/src/components/index.tsx +6 -0
  15. package/templates/history-page/src/index.tsx +943 -209
  16. package/templates/history-page/src/types/index.ts +17 -0
  17. package/translations/en/common.json +22 -2
  18. package/translations/et/common.json +22 -2
  19. package/types/chat.ts +3 -0
  20. package/ui-components/FormElements/FormCombobox/FormCombobox.scss +252 -0
  21. package/ui-components/FormElements/FormCombobox/index.tsx +334 -0
  22. package/ui-components/FormElements/index.tsx +1 -0
  23. package/ui-components/HistoricalChat/ChatMessage.tsx +24 -9
  24. package/ui-components/HistoricalChat/index.tsx +16 -6
  25. package/ui-components/Icon/index.tsx +1 -0
  26. package/ui-components/index.tsx +2 -0
@@ -0,0 +1,17 @@
1
+ const MEASUREMENT_TYPES = {
2
+ THEME: 'THEME',
3
+ QUALITY: 'QUALITY',
4
+ FOLLOW_UP_ACTION: 'FOLLOW_UP_ACTION',
5
+ } as const;
6
+
7
+ export type MeasurementType =
8
+ (typeof MEASUREMENT_TYPES)[keyof typeof MEASUREMENT_TYPES];
9
+
10
+ export type CharMeasurementType = {
11
+ readonly chatUuid: string;
12
+ readonly authorId: number;
13
+ readonly authorDisplayName: string;
14
+ readonly type: MeasurementType;
15
+ readonly value: string;
16
+ readonly createdAt: string;
17
+ }
@@ -163,6 +163,7 @@
163
163
  "endTime": "End time",
164
164
  "csaName": "Customer support name",
165
165
  "contact": "Contact",
166
+ "www": "Webpage",
166
167
  "authenticatedPerson": "Authenticated person",
167
168
  "comment": "Comment",
168
169
  "label": "Label",
@@ -177,7 +178,17 @@
177
178
  "statusAddedDate": "Status added date",
178
179
  "chosenCsa": "Chosen csa(s)",
179
180
  "chosenColumn": "Chosen column(s)",
180
- "chooseAll": "Choose all"
181
+ "chooseAll": "Choose all",
182
+ "conversationResponseQuality": "Response quality",
183
+ "followUpAction": "Follow-up action",
184
+ "chooseFollowUpAction": "- Select follow-up action -",
185
+ "chooseConversationResponseQuality": "- Select response quality -",
186
+ "chooseConversationTheme": "- Select chat theme -",
187
+ "conversationTheme": "Chat theme",
188
+ "theme": "Theme",
189
+ "responseQuality": "Bürokratt Response Quality",
190
+ "followUpStatus": "Follow-up Status",
191
+ "analysis": "Analysis"
181
192
  },
182
193
  "plainEvents": {
183
194
  "answered": "Answered",
@@ -282,6 +293,12 @@
282
293
  "requestedAt": "Requested At",
283
294
  "approve": "Approve"
284
295
  }
296
+ },
297
+ "quality": {
298
+ "theme": "Chat theme",
299
+ "responseQuality": "Chat response quality",
300
+ "followUpAction": "Follow-up action",
301
+ "selectionEmptied": "Selection emptied"
285
302
  }
286
303
  },
287
304
  "deleteConversation": {
@@ -466,7 +483,10 @@
466
483
  "copied": "Copied",
467
484
  "userDeleted": "User deleted",
468
485
  "newUserAdded": "New user added",
469
- "userUpdated": "User updated"
486
+ "userUpdated": "User updated",
487
+ "conversationThemeSaved": "Theme selection has been saved.",
488
+ "conversationQualitySaved": "Response quality selection has been saved.",
489
+ "conversationFollowUpActionSaved": "Follow-up action selection has been saved."
470
490
  }
471
491
  },
472
492
  "notification": {
@@ -163,6 +163,7 @@
163
163
  "endTime": "Lõppaeg",
164
164
  "csaName": "Nõustaja nimi",
165
165
  "contact": "Kontaktandmed",
166
+ "www": "Veebileht",
166
167
  "authenticatedPerson": "Autenditud isik",
167
168
  "comment": "Kommentaar",
168
169
  "label": "Märksõna",
@@ -177,7 +178,17 @@
177
178
  "statusAddedDate": "Staatuse lisamise kuupäev",
178
179
  "chosenCsa": "Valitud nõustaja(d)",
179
180
  "chosenColumn": "Valitud tulba(d)",
180
- "chooseAll": "Vali kõik"
181
+ "chooseAll": "Vali kõik",
182
+ "conversationResponseQuality": "Vastuse kvaliteet",
183
+ "followUpAction": "Järeltegevus",
184
+ "chooseFollowUpAction": "- Vali järeltegevus -",
185
+ "chooseConversationResponseQuality": "- Vali vastuse kvaliteet -",
186
+ "chooseConversationTheme": "- Vali vestluse valdkond -",
187
+ "conversationTheme": "Vestluse valdkond",
188
+ "theme": "Valdkond",
189
+ "responseQuality": "Bürokrati vastuse kvaliteet",
190
+ "followUpStatus": "Järeltegevuste staatus",
191
+ "analysis": "Analüüs"
181
192
  },
182
193
  "plainEvents": {
183
194
  "answered": "Vastatud",
@@ -282,6 +293,12 @@
282
293
  "requestedAt": "Taotletud Kell",
283
294
  "approve": "Kinnita"
284
295
  }
296
+ },
297
+ "quality": {
298
+ "theme": "Vestluse valdkond",
299
+ "responseQuality": "Vastuse kvaliteet",
300
+ "followUpAction": "Järeltegevus",
301
+ "selectionEmptied": "Valik tühjendatud"
285
302
  }
286
303
  },
287
304
  "deleteConversation": {
@@ -466,7 +483,10 @@
466
483
  "copied": "Kopeeritud",
467
484
  "userDeleted": "Kasutaja kustutatud",
468
485
  "newUserAdded": "Uus kasutaja lisatud",
469
- "userUpdated": "Kasutaja uuendatud"
486
+ "userUpdated": "Kasutaja uuendatud",
487
+ "conversationThemeSaved": "Valdkonna valik on salvestatud.",
488
+ "conversationQualitySaved": "Vastuse kvaliteedi valik on salvestatud.",
489
+ "conversationFollowUpActionSaved": "Järeltegevuse valik on salvestatud."
470
490
  }
471
491
  },
472
492
  "notification": {
package/types/chat.ts CHANGED
@@ -91,6 +91,9 @@ export interface Chat {
91
91
  isPreserve?: boolean;
92
92
  isFiveRatingScale?: string;
93
93
  allCsa?: string[];
94
+ theme?: string[];
95
+ responseQuality?: string[];
96
+ followUpStatus?: string[];
94
97
  totalPages?: number;
95
98
  totalCount?: number;
96
99
  }
@@ -0,0 +1,252 @@
1
+ @import 'src/styles/tools/spacing';
2
+ @import 'src/styles/tools/color';
3
+ @import 'src/styles/settings/variables/other';
4
+ @import 'src/styles/settings/variables/typography';
5
+
6
+ .select {
7
+ $self: &;
8
+ display: flex;
9
+ align-items: center;
10
+ gap: get-spacing(paldiski);
11
+ width: 100%;
12
+
13
+ &__label {
14
+ flex: 0 0 185px;
15
+ font-size: $veera-font-size-100;
16
+ line-height: 24px;
17
+ }
18
+
19
+ &__wrapper {
20
+ width: 100%;
21
+ position: relative;
22
+ }
23
+
24
+ &__trigger {
25
+ width: 100%;
26
+ display: flex;
27
+ align-items: center;
28
+ justify-content: space-between;
29
+ appearance: none;
30
+ background-color: get-color(white);
31
+ border: 1px solid get-color(black-coral-6);
32
+ border-radius: $veera-radius-s;
33
+ color: get-color(black);
34
+ font-size: $veera-font-size-100;
35
+ height: 40px;
36
+ line-height: 24px;
37
+ padding: get-spacing(paldiski);
38
+ text-align: left;
39
+
40
+ .icon {
41
+ font-size: $veera-font-size-250;
42
+ }
43
+
44
+ &[aria-expanded=true] {
45
+ border-color: get-color(sapphire-blue-10);
46
+ border-radius: 3px;
47
+
48
+ + #{$self}__menu {
49
+ display: block;
50
+ }
51
+
52
+ +#{$self}__menu_up {
53
+ display: block;
54
+ }
55
+
56
+ .icon {
57
+ transform: rotate(180deg);
58
+ }
59
+ }
60
+ }
61
+
62
+ &--plain {
63
+ width: auto;
64
+
65
+ #{$self}__wrapper {
66
+ width: auto;
67
+ }
68
+
69
+ #{$self}__trigger {
70
+ width: auto;
71
+ height: 24px;
72
+ padding: 0;
73
+ gap: get-spacing(paldiski);
74
+ justify-content: flex-start;
75
+ background-color: transparent;
76
+ border-color: transparent;
77
+ color: get-color(black-coral-10);
78
+
79
+ &[aria-expanded=true] {
80
+ border-color: transparent;
81
+ border-radius: 0;
82
+ }
83
+
84
+ .icon {
85
+ align-items: center;
86
+ background-color: #F0F0F2;
87
+ border-radius: 50%;
88
+ display: inline-flex;
89
+ flex: 0 0 24px;
90
+ height: 24px;
91
+ justify-content: center;
92
+ width: 24px;
93
+ }
94
+
95
+ &:hover,
96
+ &:focus,
97
+ &[aria-expanded=true] {
98
+ .icon {
99
+ background-color: #E9F5FF;
100
+ }
101
+ }
102
+ }
103
+
104
+ #{$self}__trigger-text {
105
+ color: get-color(black-coral-18);
106
+ display: inline;
107
+ overflow: visible;
108
+ }
109
+
110
+ #{$self}__menu--combobox {
111
+ background-color: get-color(white);
112
+ top: 100%;
113
+ bottom: auto;
114
+ min-width: 296px;
115
+ margin-top: 3px;
116
+ margin-bottom: 0;
117
+ z-index: 10000;
118
+ }
119
+
120
+ #{$self}__trigger[aria-expanded=true] {
121
+ .icon {
122
+ transform: none;
123
+ }
124
+ }
125
+
126
+ #{$self}__search,
127
+ #{$self}__options {
128
+ background-color: get-color(white);
129
+ }
130
+ }
131
+
132
+ &__trigger-text {
133
+ overflow: hidden;
134
+ text-overflow: ellipsis;
135
+ white-space: nowrap;
136
+ }
137
+
138
+ &__menu {
139
+ display: none;
140
+ position: absolute;
141
+ top: 100%;
142
+ left: 0;
143
+ right: 0;
144
+ background-color: get-color(white);
145
+ border-radius: 4px;
146
+ border: 1px solid get-color(black-coral-2);
147
+ border-top: 1;
148
+ z-index: 9998;
149
+ max-height: 320px;
150
+ overflow: auto;
151
+ margin-top: 3px;
152
+ }
153
+
154
+ &__menu--combobox {
155
+ top: auto;
156
+ bottom: 100%;
157
+ overflow: hidden;
158
+ margin-top: 0;
159
+ margin-bottom: 3px;
160
+ }
161
+
162
+ &__options {
163
+ max-height: 224px;
164
+ overflow: auto;
165
+ list-style: none;
166
+ margin: 0;
167
+ padding: 0;
168
+ }
169
+
170
+ &__search {
171
+ padding: get-spacing(paldiski);
172
+ position: relative;
173
+ }
174
+
175
+ &__search-icon {
176
+ left: 16px;
177
+ position: absolute;
178
+ top: 50%;
179
+ transform: translateY(-50%);
180
+ }
181
+
182
+ &__search-input {
183
+ width: 100%;
184
+ appearance: none;
185
+ background-color: get-color(white);
186
+ border: 1px solid get-color(black-coral-6);
187
+ border-radius: $veera-radius-s;
188
+ color: get-color(black);
189
+ font-size: $veera-font-size-100;
190
+ height: 40px;
191
+ line-height: 24px;
192
+ padding: get-spacing(paldiski);
193
+ padding-left: 36px;
194
+
195
+ &:focus {
196
+ border-color: get-color(sapphire-blue-10);
197
+ outline: none;
198
+ }
199
+ }
200
+
201
+ &__option {
202
+ display: flex;
203
+ align-items: center;
204
+ gap: 8px;
205
+ padding: 8px 15px;
206
+
207
+ span {
208
+ display: block;
209
+ }
210
+
211
+ &[aria-selected=true] {
212
+ background-color: get-color(sapphire-blue-10);
213
+ color: get-color(white);
214
+
215
+ &:hover,
216
+ &:focus {
217
+ background-color: get-color(sapphire-blue-10);
218
+ color: get-color(white);
219
+ }
220
+ }
221
+
222
+ &:hover,
223
+ &:focus {
224
+ background-color: get-color(black-coral-0);
225
+ color: get-color(black-coral-20);
226
+ }
227
+ }
228
+
229
+ &__option--combobox {
230
+ cursor: pointer;
231
+
232
+ &[aria-selected=true] {
233
+ background-color: get-color(white);
234
+ color: get-color(black);
235
+
236
+ &:hover,
237
+ &:focus {
238
+ background-color: get-color(black-coral-0);
239
+ color: get-color(black-coral-20);
240
+ }
241
+ }
242
+ }
243
+
244
+ &__option--combobox input[type='checkbox'] {
245
+ margin: 0;
246
+ }
247
+
248
+ .search__icon-size {
249
+ width: get-spacing(kuressaare);
250
+ height: get-spacing(kuressaare);
251
+ }
252
+ }
@@ -0,0 +1,334 @@
1
+ import {
2
+ CSSProperties,
3
+ FC,
4
+ FocusEvent as ReactFocusEvent,
5
+ ReactNode,
6
+ useEffect,
7
+ useId,
8
+ useMemo,
9
+ useRef,
10
+ useState
11
+ } from 'react';
12
+ import clsx from 'clsx';
13
+ import { useTranslation } from 'react-i18next';
14
+ import { MdArrowDropDown, MdExpandMore, MdSearch } from 'react-icons/md';
15
+
16
+ import { Icon } from '../..';
17
+ import './FormCombobox.scss';
18
+
19
+ type FormComboboxOption = {
20
+ readonly label: string;
21
+ readonly value: string;
22
+ };
23
+
24
+ type FormComboboxBaseProps = {
25
+ readonly label?: ReactNode;
26
+ readonly name?: string;
27
+ readonly placeholder?: string;
28
+ readonly searchPlaceholder?: string;
29
+ readonly options: FormComboboxOption[];
30
+ readonly hideLabel?: boolean;
31
+ readonly disabled?: boolean;
32
+ readonly style?: CSSProperties;
33
+ readonly isSearchEnabled?: boolean;
34
+ readonly hideInputStyle?: boolean;
35
+ };
36
+
37
+ type FormComboboxSingleProps = FormComboboxBaseProps & {
38
+ readonly multiple?: false;
39
+ readonly value?: string;
40
+ readonly defaultValue?: string;
41
+ readonly onChange?: (value: string) => void;
42
+ readonly onSelectionChange?: (selection: FormComboboxOption | null) => void;
43
+ };
44
+
45
+ type FormComboboxMultipleProps = FormComboboxBaseProps & {
46
+ readonly multiple: true;
47
+ readonly value?: string[];
48
+ readonly defaultValue?: string[];
49
+ readonly onChange?: (value: string[]) => void;
50
+ readonly onSelectionChange?: (selection: FormComboboxOption[] | null) => void;
51
+ };
52
+
53
+ type FormComboboxProps = FormComboboxSingleProps | FormComboboxMultipleProps;
54
+
55
+ type FormComboboxValueProps =
56
+ | {
57
+ readonly multiple?: false;
58
+ readonly value?: string;
59
+ readonly defaultValue?: string;
60
+ }
61
+ | {
62
+ readonly multiple: true;
63
+ readonly value?: string[];
64
+ readonly defaultValue?: string[];
65
+ };
66
+
67
+ const getInitialSingleValue = (props: FormComboboxValueProps): string => {
68
+ if (props.multiple === true) {
69
+ return '';
70
+ }
71
+
72
+ return props.defaultValue ?? '';
73
+ };
74
+
75
+ const getInitialMultipleValue = (props: FormComboboxValueProps): string[] => {
76
+ if (!props.multiple) {
77
+ return [];
78
+ }
79
+
80
+ return props.defaultValue ?? [];
81
+ };
82
+
83
+ const getSelectedValues = (
84
+ props: FormComboboxValueProps,
85
+ internalSingleValue: string,
86
+ internalMultipleValue: string[]
87
+ ): string[] => {
88
+ if (props.multiple === true) {
89
+ return props.value ?? internalMultipleValue;
90
+ }
91
+
92
+ if (props.value) {
93
+ return [props.value];
94
+ }
95
+
96
+ if (internalSingleValue) {
97
+ return [internalSingleValue];
98
+ }
99
+
100
+ return [];
101
+ };
102
+
103
+ const orderSelectedOptionsFirst = (
104
+ options: FormComboboxOption[],
105
+ selectedValues: string[]
106
+ ): FormComboboxOption[] => {
107
+ if (selectedValues.length === 0) return options;
108
+
109
+ const selectedValueSet = new Set(selectedValues);
110
+ const selectedOptions: FormComboboxOption[] = [];
111
+ const unselectedOptions: FormComboboxOption[] = [];
112
+
113
+ options.forEach((option) => {
114
+ if (selectedValueSet.has(option.value)) {
115
+ selectedOptions.push(option);
116
+ return;
117
+ }
118
+
119
+ unselectedOptions.push(option);
120
+ });
121
+
122
+ return [...selectedOptions, ...unselectedOptions];
123
+ };
124
+
125
+ export const FormCombobox: FC<FormComboboxProps> = ({
126
+ label,
127
+ hideLabel,
128
+ placeholder,
129
+ searchPlaceholder,
130
+ options,
131
+ disabled,
132
+ style,
133
+ isSearchEnabled = false,
134
+ hideInputStyle = false,
135
+ ...props
136
+ }) => {
137
+ const id = useId();
138
+ const { t } = useTranslation();
139
+ const [isOpen, setIsOpen] = useState(false);
140
+ const [query, setQuery] = useState('');
141
+ const [internalSingleValue, setInternalSingleValue] = useState<string>(
142
+ getInitialSingleValue(props)
143
+ );
144
+ const [internalMultipleValue, setInternalMultipleValue] = useState<string[]>(
145
+ getInitialMultipleValue(props)
146
+ );
147
+ const wrapperRef = useRef<HTMLDivElement | null>(null);
148
+ const searchInputRef = useRef<HTMLInputElement | null>(null);
149
+
150
+ const isMultiple = props.multiple === true;
151
+ const selectedValues = getSelectedValues(props, internalSingleValue, internalMultipleValue);
152
+
153
+ const orderedOptions = useMemo(() => (
154
+ orderSelectedOptionsFirst(options, selectedValues)
155
+ ), [options, selectedValues]);
156
+
157
+ const filteredOptions = useMemo(() => {
158
+ const normalizedQuery = query.trim().toLowerCase();
159
+ if (!normalizedQuery) return orderedOptions;
160
+
161
+ return orderedOptions.filter((option) => (
162
+ option.label.toLowerCase().includes(normalizedQuery)
163
+ ));
164
+ }, [orderedOptions, query]);
165
+
166
+ const selectedOptions = useMemo(() => (
167
+ options.filter((option) => selectedValues.includes(option.value))
168
+ ), [options, selectedValues]);
169
+
170
+ useEffect(() => {
171
+ if (isOpen) {
172
+ searchInputRef.current?.focus();
173
+ }
174
+ }, [isOpen]);
175
+
176
+ useEffect(() => {
177
+ const handleDocumentClick = (event: MouseEvent) => {
178
+ if (!wrapperRef.current?.contains(event.target as Node)) {
179
+ setIsOpen(false);
180
+ setQuery('');
181
+ }
182
+ };
183
+
184
+ document.addEventListener('mousedown', handleDocumentClick);
185
+
186
+ return () => {
187
+ document.removeEventListener('mousedown', handleDocumentClick);
188
+ };
189
+ }, []);
190
+
191
+ const closeOnFocusOutside = (event: ReactFocusEvent<HTMLDivElement>) => {
192
+ if (!event.currentTarget.contains(event.relatedTarget)) {
193
+ setIsOpen(false);
194
+ setQuery('');
195
+ }
196
+ };
197
+
198
+ const setSingleValue = (option: FormComboboxOption) => {
199
+ if (props.multiple) return;
200
+ const singleProps = props as FormComboboxSingleProps;
201
+ const nextValue = selectedValues.includes(option.value) ? '' : option.value;
202
+ const nextSelection = nextValue ? option : null;
203
+
204
+ if (singleProps.value === undefined) {
205
+ setInternalSingleValue(nextValue);
206
+ }
207
+
208
+ singleProps.onChange?.(nextValue);
209
+ singleProps.onSelectionChange?.(nextSelection);
210
+ setIsOpen(false);
211
+ setQuery('');
212
+ };
213
+
214
+ const setMultipleValue = (option: FormComboboxOption) => {
215
+ if (!props.multiple) return;
216
+ const multipleProps = props as FormComboboxMultipleProps;
217
+
218
+ const nextValues = selectedValues.includes(option.value)
219
+ ? selectedValues.filter((value) => value !== option.value)
220
+ : [...selectedValues, option.value];
221
+ const nextSelection = options.filter((item) => nextValues.includes(item.value));
222
+
223
+ if (multipleProps.value === undefined) {
224
+ setInternalMultipleValue(nextValues);
225
+ }
226
+
227
+ multipleProps.onChange?.(nextValues);
228
+ multipleProps.onSelectionChange?.(nextSelection.length ? nextSelection : null);
229
+ };
230
+
231
+ const selectOption = (option: FormComboboxOption) => {
232
+ if (disabled) return;
233
+
234
+ if (props.multiple) {
235
+ setMultipleValue(option);
236
+ return;
237
+ }
238
+
239
+ setSingleValue(option);
240
+ };
241
+
242
+ const placeholderValue = placeholder || t('global.choose');
243
+ const triggerLabel = isMultiple
244
+ ? selectedOptions.length > 0
245
+ ? selectedOptions.map((option) => option.label).join(', ')
246
+ : placeholderValue
247
+ : selectedOptions[0]?.label ?? placeholderValue;
248
+ const triggerContent = hideInputStyle ? label ?? triggerLabel : triggerLabel;
249
+
250
+ const selectClasses = clsx(
251
+ 'select',
252
+ disabled && 'select--disabled',
253
+ isOpen && 'select--open',
254
+ hideInputStyle && 'select--plain',
255
+ );
256
+
257
+ return (
258
+ <div ref={wrapperRef} className={selectClasses} style={style} onBlur={closeOnFocusOutside}>
259
+ {label && !hideLabel && <label htmlFor={id} className='select__label'>{label}</label>}
260
+ <div className='select__wrapper'>
261
+ <button
262
+ id={id}
263
+ type='button'
264
+ className='select__trigger'
265
+ aria-label={hideInputStyle && typeof label === 'string' ? label : undefined}
266
+ aria-haspopup='listbox'
267
+ aria-expanded={isOpen}
268
+ disabled={disabled}
269
+ onClick={() => setIsOpen((open) => !open)}
270
+ >
271
+ <span className='select__trigger-text'>{triggerContent}</span>
272
+ <Icon
273
+ label='Dropdown icon'
274
+ size='medium'
275
+ icon={hideInputStyle ? <MdExpandMore color='#5D6071' /> : <MdArrowDropDown color='#5D6071' />}
276
+ />
277
+ </button>
278
+
279
+ {isOpen && (
280
+ <div className='select__menu select__menu--combobox'>
281
+ {
282
+ isSearchEnabled && <div className='select__search'>
283
+ <Icon
284
+ label='Search icon'
285
+ size='medium'
286
+ className='select__search-icon'
287
+ icon={<MdSearch className='search__icon-size' color='#5D6071' />}
288
+ />
289
+ <input
290
+ ref={searchInputRef}
291
+ className='select__search-input'
292
+ value={query}
293
+ onChange={(event) => setQuery(event.target.value)}
294
+ placeholder={searchPlaceholder ?? t('global.search')}
295
+ disabled={disabled}
296
+ />
297
+ </div>
298
+ }
299
+
300
+ <ul className='select__options' role='listbox' aria-label={searchPlaceholder ?? t('global.search')}>
301
+ {filteredOptions.length > 0 ? (
302
+ filteredOptions.map((option) => {
303
+ const isSelected = selectedValues.includes(option.value);
304
+
305
+ return (
306
+ <li
307
+ key={option.value}
308
+ role='option'
309
+ aria-selected={isSelected}
310
+ className='select__option select__option--combobox'
311
+ onMouseDown={(event) => event.preventDefault()}
312
+ onClick={() => selectOption(option)}
313
+ >
314
+ <input
315
+ type={isMultiple ? 'checkbox' : 'radio'}
316
+ checked={isSelected}
317
+ value={option.value}
318
+ onChange={() => null}
319
+ onClick={(event) => event.preventDefault()}
320
+ />
321
+ <span>{option.label}</span>
322
+ </li>
323
+ );
324
+ })
325
+ ) : null}
326
+ </ul>
327
+ </div>
328
+ )}
329
+ </div>
330
+ </div>
331
+ );
332
+ };
333
+
334
+ export default FormCombobox;