@coreui/react-pro 4.11.3 → 4.12.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.
Files changed (95) hide show
  1. package/README.md +1 -1
  2. package/dist/components/accordion/index.d.ts +1 -1
  3. package/dist/components/calendar/CCalendar.d.ts +4 -4
  4. package/dist/components/date-range-picker/CDateRangePicker copy.d.ts +114 -0
  5. package/dist/components/date-range-picker/CDateRangePicker.d.ts +49 -1
  6. package/dist/components/multi-select/CMultiSelect.d.ts +34 -3
  7. package/dist/components/multi-select/CMultiSelectNativeSelect.d.ts +4 -5
  8. package/dist/components/multi-select/CMultiSelectOptions.d.ts +4 -2
  9. package/dist/components/multi-select/CMultiSelectSelection.d.ts +1 -0
  10. package/dist/components/multi-select/types.d.ts +7 -3
  11. package/dist/components/multi-select/utils.d.ts +5 -5
  12. package/dist/components/multi-select-new/CMultiSelect.d.ts +133 -0
  13. package/dist/components/multi-select-new/CMultiSelectNativeSelect.d.ts +7 -0
  14. package/dist/components/multi-select-new/CMultiSelectNew.d.ts +121 -0
  15. package/dist/components/multi-select-new/CMultiSelectOptions.d.ts +16 -0
  16. package/dist/components/multi-select-new/CMultiSelectSelection.d.ts +11 -0
  17. package/dist/components/multi-select-new/index.d.ts +2 -0
  18. package/dist/components/multi-select-new/types.d.ts +15 -0
  19. package/dist/components/multi-select-new/utils.d.ts +11 -0
  20. package/dist/components/picker/CPicker copy.d.ts +82 -0
  21. package/dist/components/picker/CPicker.d.ts +14 -56
  22. package/dist/components/picker copy/CPicker.d.ts +82 -0
  23. package/dist/components/popover/CPopover.d.ts +1 -1
  24. package/dist/components/time-picker/CTimePicker.d.ts +41 -1
  25. package/dist/components/tooltip/CTooltip.d.ts +3 -3
  26. package/dist/hooks/index.d.ts +2 -1
  27. package/dist/hooks/useColorModes.d.ts +6 -0
  28. package/dist/index.d.ts +1 -0
  29. package/dist/index.es.js +2385 -2403
  30. package/dist/index.es.js.map +1 -1
  31. package/dist/index.js +2386 -2401
  32. package/dist/index.js.map +1 -1
  33. package/dist/props.d.ts +1 -1
  34. package/dist/utils/index.d.ts +2 -1
  35. package/dist/utils/isRTL.d.ts +2 -0
  36. package/package.json +15 -15
  37. package/src/components/accordion/index.ts +1 -7
  38. package/src/components/calendar/CCalendar.tsx +49 -64
  39. package/src/components/calendar/__tests__/__snapshots__/CCalendar.spec.tsx.snap +601 -555
  40. package/src/components/calendar/utils.ts +1 -1
  41. package/src/components/date-picker/__tests__/__snapshots__/CDatePicker.spec.tsx.snap +599 -561
  42. package/src/components/date-range-picker/CDateRangePicker.tsx +208 -107
  43. package/src/components/date-range-picker/__tests__/__snapshots__/CDateRangePicker.spec.tsx.snap +1197 -1121
  44. package/src/components/date-range-picker/utils.ts +2 -2
  45. package/src/components/dropdown/CDropdownMenu.tsx +5 -4
  46. package/src/components/dropdown/__tests__/__snapshots__/CDropdown.spec.tsx.snap +1 -1
  47. package/src/components/element-cover/CElementCover.tsx +1 -0
  48. package/src/components/form/CFormSelect.tsx +3 -1
  49. package/src/components/modal/CModal.tsx +2 -0
  50. package/src/components/multi-select/CMultiSelect.tsx +200 -79
  51. package/src/components/multi-select/CMultiSelectNativeSelect.tsx +34 -24
  52. package/src/components/multi-select/CMultiSelectOptions.tsx +25 -23
  53. package/src/components/multi-select/CMultiSelectSelection.tsx +36 -7
  54. package/src/components/multi-select/__tests__/CMultiSelect.spec.tsx +25 -14
  55. package/src/components/multi-select/__tests__/CMultiSelectNativeSelect.spec.tsx +1 -1
  56. package/src/components/multi-select/__tests__/CMultiSelectOptions.spec.tsx +4 -4
  57. package/src/components/multi-select/__tests__/CMultiSelectSelection.spec.tsx +0 -1
  58. package/src/components/multi-select/__tests__/__snapshots__/CMultiSelect.spec.tsx.snap +136 -30
  59. package/src/components/multi-select/__tests__/__snapshots__/CMultiSelectNativeSelect.spec.tsx.snap +2 -0
  60. package/src/components/multi-select/__tests__/__snapshots__/CMultiSelectOptions.spec.tsx.snap +3 -1
  61. package/src/components/multi-select/__tests__/__snapshots__/CMultiSelectSelection.spec.tsx.snap +2 -2
  62. package/src/components/multi-select/types.ts +9 -4
  63. package/src/components/multi-select/utils.ts +56 -39
  64. package/src/components/picker/CPicker.tsx +113 -138
  65. package/src/components/popover/CPopover.tsx +61 -26
  66. package/src/components/popover/__tests__/__snapshots__/CPopover.spec.tsx.snap +2 -1
  67. package/src/components/progress/CProgress.tsx +1 -0
  68. package/src/components/sidebar/CSidebar.tsx +2 -2
  69. package/src/components/smart-table/CSmartTable.tsx +22 -10
  70. package/src/components/smart-table/CSmartTableBody.tsx +3 -5
  71. package/src/components/smart-table/CSmartTableHead.tsx +5 -6
  72. package/src/components/smart-table/__tests__/CSmartTable.spec.tsx +1 -1
  73. package/src/components/smart-table/__tests__/__snapshots__/CSmartTable.spec.tsx.snap +580 -0
  74. package/src/components/spinner/CSpinner.tsx +4 -2
  75. package/src/components/spinner/__tests__/__snapshots__/CSpinner.spec.tsx.snap +2 -2
  76. package/src/components/table/CTable.tsx +2 -1
  77. package/src/components/time-picker/CTimePicker.tsx +182 -82
  78. package/src/components/time-picker/__tests__/__snapshots__/CTimePicker.spec.tsx.snap +1078 -0
  79. package/src/components/tooltip/CTooltip.tsx +65 -31
  80. package/src/components/tooltip/__tests__/CTooltip.spec.tsx +1 -1
  81. package/src/components/virtual-scroller/CVirtualScroller.tsx +0 -1
  82. package/src/hooks/index.ts +2 -1
  83. package/src/hooks/useColorModes.ts +55 -0
  84. package/src/hooks/useForkedRef.ts +1 -1
  85. package/src/index.ts +1 -0
  86. package/src/props.ts +4 -1
  87. package/src/utils/index.ts +2 -1
  88. package/src/utils/isRTL.ts +13 -0
  89. package/src/components/multi-select/__tests__/CMultiSelectCleaner.spec.tsx +0 -15
  90. package/src/components/multi-select/__tests__/CMultiSelectSearchInput.spec.tsx +0 -17
  91. package/src/components/multi-select/__tests__/CMultiSelectSelectAllButton.spec.tsx +0 -21
  92. package/src/components/multi-select/__tests__/__snapshots__/CMultiSelectCleaner.spec.tsx.snap +0 -19
  93. package/src/components/multi-select/__tests__/__snapshots__/CMultiSelectSearchInput.spec.tsx.snap +0 -20
  94. package/src/components/multi-select/__tests__/__snapshots__/CMultiSelectSelectAllButton.spec.tsx.snap +0 -24
  95. package/src/components/smart-table/CSmartTableCleaner.tsx +0 -37
@@ -32,9 +32,9 @@ export const getLocalDateFromString = (string: string, locale: string, time?: bo
32
32
  Number(partials.groups['month']) - 1,
33
33
  Number(partials.groups['day']),
34
34
  partials.groups['ampm']
35
- ? partials.groups['ampm'] === 'PM'
35
+ ? (partials.groups['ampm'] === 'PM'
36
36
  ? Number(partials.groups['hour']) + 12
37
- : Number(partials.groups['hour'])
37
+ : Number(partials.groups['hour']))
38
38
  : Number(partials.groups['hour']),
39
39
  Number(partials.groups['minute']),
40
40
  Number(partials.groups['second']),
@@ -7,6 +7,7 @@ import { Alignments, CDropdownContext } from './CDropdown'
7
7
  import { CConditionalPortal } from '../conditional-portal'
8
8
 
9
9
  import type { Placements } from '../../types'
10
+ import { isRTL } from '../../utils'
10
11
 
11
12
  export interface CDropdownMenuProps
12
13
  extends HTMLAttributes<HTMLDivElement | HTMLUListElement>,
@@ -109,7 +110,7 @@ export const CDropdownMenu: FC<CDropdownMenuProps> = ({
109
110
  }
110
111
 
111
112
  if (direction === 'dropup') {
112
- _placement = 'top-start'
113
+ _placement = isRTL(dropdownMenuRef.current) ? 'top-end' : 'top-start'
113
114
  }
114
115
 
115
116
  if (direction === 'dropup-center') {
@@ -117,15 +118,15 @@ export const CDropdownMenu: FC<CDropdownMenuProps> = ({
117
118
  }
118
119
 
119
120
  if (direction === 'dropend') {
120
- _placement = 'right-start'
121
+ _placement = isRTL(dropdownMenuRef.current) ? 'left-start' : 'right-start'
121
122
  }
122
123
 
123
124
  if (direction === 'dropstart') {
124
- _placement = 'left-start'
125
+ _placement = isRTL(dropdownMenuRef.current) ? 'right-start' : 'left-start'
125
126
  }
126
127
 
127
128
  if (alignment === 'end') {
128
- _placement = 'bottom-end'
129
+ _placement = isRTL(dropdownMenuRef.current) ? 'bottom-start' : 'bottom-end'
129
130
  }
130
131
 
131
132
  const dropdownMenuComponent = (style?: React.CSSProperties, ref?: React.Ref<HTMLDivElement>) => (
@@ -3,7 +3,7 @@
3
3
  exports[`CDropdown customize 1`] = `
4
4
  <div>
5
5
  <li
6
- class="nav-item dropdown dropstart show bazinga"
6
+ class="nav-item dropdown direction show bazinga"
7
7
  >
8
8
  Test
9
9
  </li>
@@ -78,6 +78,7 @@ export const CElementCover = forwardRef<HTMLDivElement, CElementCoverProps>(
78
78
  const coverStyles: CSSProperties = {
79
79
  ...containerCoords,
80
80
  position: 'absolute',
81
+ zIndex: 2,
81
82
  backgroundColor: `rgba(255,255,255,${opacity})`,
82
83
  }
83
84
 
@@ -9,6 +9,7 @@ type Option = {
9
9
  label?: string
10
10
  value?: string
11
11
  }
12
+
12
13
  export interface CFormSelectProps
13
14
  extends CFormControlWrapperProps,
14
15
  Omit<InputHTMLAttributes<HTMLSelectElement>, 'size'> {
@@ -102,7 +103,8 @@ export const CFormSelect = forwardRef<HTMLSelectElement, CFormSelectProps>(
102
103
  <option
103
104
  {...(typeof option === 'object' &&
104
105
  option.disabled && { disabled: option.disabled })}
105
- {...(typeof option === 'object' && option.value && { value: option.value })}
106
+ {...(typeof option === 'object' &&
107
+ option.value !== undefined && { value: option.value })}
106
108
  key={index}
107
109
  >
108
110
  {typeof option === 'string' ? option : option.label}
@@ -142,6 +142,7 @@ export const CModal = forwardRef<HTMLDivElement, CModalProps>(
142
142
  return setStaticBackdrop(true)
143
143
  }
144
144
 
145
+ setVisible(false)
145
146
  return onClose && onClose()
146
147
  }
147
148
 
@@ -174,6 +175,7 @@ export const CModal = forwardRef<HTMLDivElement, CModalProps>(
174
175
  document.body.style.removeProperty('padding-right')
175
176
  }
176
177
  }
178
+
177
179
  return () => {
178
180
  document.body.classList.remove('modal-open')
179
181
  if (backdrop) {
@@ -2,7 +2,6 @@ import React, {
2
2
  FormEvent,
3
3
  forwardRef,
4
4
  HTMLAttributes,
5
- KeyboardEvent,
6
5
  ReactNode,
7
6
  useEffect,
8
7
  useState,
@@ -12,16 +11,18 @@ import React, {
12
11
 
13
12
  import classNames from 'classnames'
14
13
  import PropTypes from 'prop-types'
14
+ import { createPopper, Instance } from '@popperjs/core'
15
15
 
16
- import { CFormControlWrapper, CFormControlWrapperProps } from './../form/CFormControlWrapper'
17
- import { CPicker } from '../picker/CPicker'
16
+ import { CFormControlWrapper, CFormControlWrapperProps } from '../form/CFormControlWrapper'
18
17
 
19
18
  import { CMultiSelectNativeSelect } from './CMultiSelectNativeSelect'
20
19
  import { CMultiSelectOptions } from './CMultiSelectOptions'
21
20
  import { CMultiSelectSelection } from './CMultiSelectSelection'
22
21
 
23
- import { createOption, filterOptionsList, flattenArray, selectOptions } from './utils'
24
- import type { Option, SelectedOption } from './types'
22
+ import { useForkedRef } from '../../hooks'
23
+ import { isRTL } from '../../utils'
24
+ import { createOption, filterOptionsList, flattenOptionsArray, selectOptions } from './utils'
25
+ import type { Option, OptionsGroup, SelectedOption } from './types'
25
26
 
26
27
  export interface CMultiSelectProps
27
28
  extends Omit<CFormControlWrapperProps, 'floatingClassName' | 'floatingLabel'>,
@@ -50,6 +51,13 @@ export interface CMultiSelectProps
50
51
  * Toggle the disabled state for the component.
51
52
  */
52
53
  disabled?: boolean
54
+ /**
55
+ * The id global attribute defines an identifier (ID) that must be unique in the whole document.
56
+ *
57
+ * The name and id attributes for the native select element are generated based on the `id` property:
58
+ * - <select id="\{id\}-multi-select" name="\{id\}-multi-select" />
59
+ */
60
+ id?: string
53
61
  /**
54
62
  * When set, the options list will have a loading style: loading spinner and reduced opacity.
55
63
  *
@@ -70,10 +78,18 @@ export interface CMultiSelectProps
70
78
  * @since 4.8.0
71
79
  */
72
80
  onFilterChange?: (value: string) => void
81
+ /**
82
+ * The callback is fired when the Multi Select component requests to be hidden.
83
+ */
84
+ onHide?: () => void
85
+ /**
86
+ * The callback is fired when the Multi Select component requests to be shown.
87
+ */
88
+ onShow?: () => void
73
89
  /**
74
90
  * List of option elements.
75
91
  */
76
- options: Option[]
92
+ options: (Option | OptionsGroup)[]
77
93
  /**
78
94
  * Sets maxHeight of options list.
79
95
  */
@@ -82,10 +98,26 @@ export interface CMultiSelectProps
82
98
  * Sets option style.
83
99
  */
84
100
  optionsStyle?: 'checkbox' | 'text'
101
+ /**
102
+ * Custom template for options.
103
+ *
104
+ * @since 4.12.0-beta.0
105
+ */
106
+ optionsTemplate?: (option: Option) => ReactNode
107
+ /**
108
+ * Custom template for options groups.
109
+ *
110
+ * @since 4.12.0-beta.0
111
+ */
112
+ optionsGroupsTemplate?: (option: OptionsGroup) => ReactNode
85
113
  /**
86
114
  * Specifies a short hint that is visible in the search input.
87
115
  */
88
116
  placeholder?: string
117
+ /**
118
+ * When it is present, it indicates that the user must choose a value before submitting the form.
119
+ */
120
+ required?: boolean
89
121
  /**
90
122
  * Enables search input element.
91
123
  */
@@ -150,10 +182,15 @@ export const CMultiSelect = forwardRef<HTMLDivElement, CMultiSelectProps>(
150
182
  label,
151
183
  onChange,
152
184
  onFilterChange,
185
+ onHide,
186
+ onShow,
153
187
  options,
154
188
  optionsMaxHeight = 'auto',
155
189
  optionsStyle = 'checkbox',
190
+ optionsTemplate,
191
+ optionsGroupsTemplate,
156
192
  placeholder = 'Select...',
193
+ required,
157
194
  search = true,
158
195
  searchNoResultsLabel = 'No results found',
159
196
  selectAll = true,
@@ -171,11 +208,18 @@ export const CMultiSelect = forwardRef<HTMLDivElement, CMultiSelectProps>(
171
208
  },
172
209
  ref,
173
210
  ) => {
174
- const isInitialMount = useRef(true)
211
+ const multiSelectRef = useRef<HTMLDivElement>(null)
212
+ const multiSelectForkedRef = useForkedRef(ref, multiSelectRef)
213
+
214
+ const dropdownRef = useRef<HTMLDivElement>(null)
175
215
  const nativeSelectRef = useRef<HTMLSelectElement>(null)
216
+ const togglerRef = useRef<HTMLDivElement>(null)
176
217
  const searchRef = useRef<HTMLInputElement>(null)
177
218
 
178
- const [_options, setOptions] = useState<Option[]>(options)
219
+ const isInitialMount = useRef(true)
220
+ const popper = useRef<Instance>()
221
+
222
+ const [_options, setOptions] = useState<(Option | OptionsGroup)[]>(options)
179
223
  const [_visible, setVisible] = useState(visible)
180
224
  const [searchValue, setSearchValue] = useState('')
181
225
  const [selected, setSelected] = useState<SelectedOption[]>([])
@@ -183,47 +227,37 @@ export const CMultiSelect = forwardRef<HTMLDivElement, CMultiSelectProps>(
183
227
 
184
228
  const filteredOptions = useMemo(
185
229
  () =>
186
- search === 'external'
187
- ? [..._options, ...filterOptionsList(searchValue, userOptions)]
188
- : filterOptionsList(searchValue, [..._options, ...userOptions]),
230
+ flattenOptionsArray(
231
+ search === 'external'
232
+ ? [..._options, ...filterOptionsList(searchValue, userOptions)]
233
+ : filterOptionsList(searchValue, [..._options, ...userOptions]),
234
+ true,
235
+ ),
189
236
  [_options, searchValue, userOptions],
190
237
  )
191
238
 
239
+ const flattenedOptions = useMemo(() => flattenOptionsArray(options), [JSON.stringify(options)])
240
+
192
241
  const userOption = useMemo(() => {
193
242
  if (
194
243
  allowCreateOptions &&
195
- filteredOptions.length > 0 &&
196
- flattenArray(filteredOptions).filter(
197
- (option) => option.text.toLowerCase() === searchValue.toLowerCase(),
198
- ).length > 0
244
+ filteredOptions.some(
245
+ (option) => option.text && option.text.toLowerCase() === searchValue.toLowerCase(),
246
+ )
199
247
  ) {
200
248
  return false
201
249
  }
202
250
 
203
- return searchRef.current && createOption(String(searchValue), flattenArray(_options))
251
+ return searchRef.current && createOption(String(searchValue), flattenedOptions)
204
252
  }, [filteredOptions, searchValue])
205
253
 
206
- useMemo(() => {
254
+ useEffect(() => {
207
255
  setOptions(options)
208
- const _selected =
209
- options &&
210
- flattenArray(options).filter((option: Option) => {
211
- if (option.selected) {
212
- return option
213
- }
214
-
215
- return
216
- })
217
256
 
218
- const deselected =
219
- options &&
220
- flattenArray(options).filter((option: Option) => {
221
- if (option.selected === false) {
222
- return option
223
- }
224
-
225
- return
226
- })
257
+ const _selected = flattenedOptions.filter((option: Option) => option.selected === true)
258
+ const deselected = flattenedOptions.filter(
259
+ (option: Option) => option.selected === false,
260
+ ) as Option[]
227
261
 
228
262
  _selected && setSelected(selectOptions(_selected, selected, deselected))
229
263
  }, [JSON.stringify(options)])
@@ -233,21 +267,90 @@ export const CMultiSelect = forwardRef<HTMLDivElement, CMultiSelectProps>(
233
267
  }, [searchValue])
234
268
 
235
269
  useEffect(() => {
236
- !isInitialMount.current &&
237
- nativeSelectRef.current &&
270
+ if (!isInitialMount.current && nativeSelectRef.current) {
238
271
  nativeSelectRef.current.dispatchEvent(new Event('change', { bubbles: true }))
272
+ }
273
+
274
+ if (popper.current) {
275
+ popper.current.update()
276
+ }
239
277
  }, [JSON.stringify(selected)])
240
278
 
279
+ useEffect(() => {
280
+ if (_visible) {
281
+ onShow && onShow()
282
+
283
+ window.addEventListener('mouseup', handleMouseUp)
284
+ window.addEventListener('keyup', handleKeyUp)
285
+
286
+ initPopper()
287
+ searchRef.current && searchRef.current.focus()
288
+ }
289
+
290
+ return () => {
291
+ onHide && onHide()
292
+
293
+ window.removeEventListener('mouseup', handleMouseUp)
294
+ window.removeEventListener('keyup', handleKeyUp)
295
+
296
+ destroyPopper()
297
+ }
298
+ }, [_visible])
299
+
241
300
  useEffect(() => {
242
301
  isInitialMount.current = false
243
302
  }, [])
244
303
 
304
+ const initPopper = () => {
305
+ if (togglerRef.current && dropdownRef.current) {
306
+ popper.current = createPopper(togglerRef.current, dropdownRef.current, {
307
+ placement: isRTL(multiSelectRef.current) ? 'bottom-end' : 'bottom-start',
308
+ modifiers: [
309
+ {
310
+ name: 'preventOverflow',
311
+ options: {
312
+ boundary: 'clippingParents',
313
+ },
314
+ },
315
+ {
316
+ name: 'offset',
317
+ options: {
318
+ offset: [0, 2],
319
+ },
320
+ },
321
+ ],
322
+ })
323
+ }
324
+ }
325
+
326
+ const destroyPopper = () => {
327
+ if (popper.current) {
328
+ popper.current.destroy()
329
+ }
330
+
331
+ popper.current = undefined
332
+ }
333
+
334
+ const handleKeyUp = (event: KeyboardEvent) => {
335
+ if (event.key === 'Escape') {
336
+ setVisible(false)
337
+ }
338
+ }
339
+
340
+ const handleMouseUp = (event: Event) => {
341
+ if (multiSelectRef.current && multiSelectRef.current.contains(event.target as HTMLElement)) {
342
+ return
343
+ }
344
+
345
+ setVisible(false)
346
+ }
347
+
245
348
  const handleSearchChange = (event: FormEvent<HTMLInputElement>) => {
246
349
  const value = (event.target as HTMLInputElement).value
247
350
  setSearchValue(value)
248
351
  }
249
352
 
250
- const handleSearchKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
353
+ const handleSearchKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
251
354
  if (event.key === 'Enter' && searchValue && allowCreateOptions) {
252
355
  event.preventDefault()
253
356
 
@@ -259,9 +362,9 @@ export const CMultiSelect = forwardRef<HTMLDivElement, CMultiSelectProps>(
259
362
  if (!userOption) {
260
363
  setSelected([
261
364
  ...selected,
262
- flattenArray(filteredOptions).filter(
365
+ filteredOptions.find(
263
366
  (option) => String(option.text).toLowerCase() === searchValue.toLowerCase(),
264
- )[0],
367
+ ) as Option,
265
368
  ])
266
369
  }
267
370
 
@@ -285,7 +388,7 @@ export const CMultiSelect = forwardRef<HTMLDivElement, CMultiSelectProps>(
285
388
 
286
389
  const handleOptionOnClick = (option: Option) => {
287
390
  if (!multiple) {
288
- setSelected([{ value: option.value, text: option.text }] as SelectedOption[])
391
+ setSelected([option] as SelectedOption[])
289
392
  setVisible(false)
290
393
  setSearchValue('')
291
394
  if (searchRef.current) {
@@ -310,14 +413,14 @@ export const CMultiSelect = forwardRef<HTMLDivElement, CMultiSelectProps>(
310
413
  if (selected.some((_option) => _option.value === option.value)) {
311
414
  setSelected(selected.filter((_option) => _option.value !== option.value))
312
415
  } else {
313
- setSelected([...selected, { value: option.value, text: option.text }] as SelectedOption[])
416
+ setSelected([...selected, option] as SelectedOption[])
314
417
  }
315
418
  }
316
419
 
317
420
  const handleSelectAll = () => {
318
421
  setSelected(
319
422
  selectOptions(
320
- [...flattenArray(options).filter((option: Option) => !option.disabled), ...userOptions],
423
+ [...flattenedOptions.filter((option: Option) => !option.disabled), ...userOptions],
321
424
  selected,
322
425
  ),
323
426
  )
@@ -341,8 +444,10 @@ export const CMultiSelect = forwardRef<HTMLDivElement, CMultiSelectProps>(
341
444
  valid={valid}
342
445
  >
343
446
  <CMultiSelectNativeSelect
447
+ id={id}
344
448
  multiple={multiple}
345
449
  options={selected}
450
+ required={required}
346
451
  value={
347
452
  multiple
348
453
  ? selected.map((option: SelectedOption) => option.value.toString())
@@ -351,51 +456,44 @@ export const CMultiSelect = forwardRef<HTMLDivElement, CMultiSelectProps>(
351
456
  onChange={() => onChange && onChange(selected)}
352
457
  ref={nativeSelectRef}
353
458
  />
354
- <CPicker
459
+ <div
355
460
  className={classNames(
356
461
  'form-multi-select',
357
462
  {
358
- 'form-multi-select-with-cleaner': cleaner,
359
463
  [`form-multi-select-${size}`]: size,
360
- show: _visible,
361
- 'form-multi-select-selection-tags': multiple && selectionType === 'tags',
362
464
  disabled,
363
465
  'is-invalid': invalid,
364
466
  'is-valid': valid,
467
+ show: _visible,
365
468
  },
366
469
  className,
367
470
  )}
368
- disabled={disabled}
471
+ aria-expanded={_visible}
369
472
  id={id}
370
- onHide={() => setVisible(false)}
371
- onShow={() => {
372
- searchRef.current && searchRef.current.focus()
373
- setVisible(true)
374
- }}
375
- toggler={
376
- <div>
377
- <CMultiSelectSelection
378
- multiple={multiple}
379
- onRemove={(option) => !disabled && handleOptionOnClick(option)}
380
- search={search}
381
- selected={selected}
382
- selectionType={selectionType}
383
- selectionTypeCounterText={selectionTypeCounterText}
384
- />
385
- {multiple && cleaner && selected.length > 0 && (
386
- <button
387
- type="button"
388
- className="form-multi-select-selection-cleaner"
389
- onClick={() => handleDeselectAll()}
390
- ></button>
391
- )}
473
+ ref={multiSelectForkedRef}
474
+ >
475
+ <div
476
+ className="form-multi-select-input-group"
477
+ onClick={() => setVisible(true)}
478
+ ref={togglerRef}
479
+ >
480
+ <CMultiSelectSelection
481
+ multiple={multiple}
482
+ onRemove={(option) => !disabled && handleOptionOnClick(option)}
483
+ placeholder={placeholder}
484
+ search={search}
485
+ selected={selected}
486
+ selectionType={selectionType}
487
+ selectionTypeCounterText={selectionTypeCounterText}
488
+ >
392
489
  {search && (
393
490
  <input
394
491
  type="text"
395
492
  className="form-multi-select-search"
396
493
  disabled={disabled}
397
- onChange={(event) => handleSearchChange(event)}
398
- onKeyDown={(event) => handleSearchKeyDown(event)}
494
+ autoComplete="off"
495
+ onChange={handleSearchChange}
496
+ onKeyDown={handleSearchKeyDown}
399
497
  {...(selected.length === 0 && { placeholder: placeholder })}
400
498
  {...(selected.length > 0 &&
401
499
  selectionType === 'counter' && {
@@ -409,12 +507,27 @@ export const CMultiSelect = forwardRef<HTMLDivElement, CMultiSelectProps>(
409
507
  ref={searchRef}
410
508
  ></input>
411
509
  )}
510
+ </CMultiSelectSelection>
511
+ <div className="form-multi-select-buttons">
512
+ {!disabled && cleaner && selected.length > 0 && (
513
+ <button
514
+ type="button"
515
+ className="form-multi-select-cleaner"
516
+ onClick={() => handleDeselectAll()}
517
+ ></button>
518
+ )}
519
+ <button
520
+ type="button"
521
+ className="form-multi-select-indicator"
522
+ onClick={(event) => {
523
+ event.preventDefault()
524
+ event.stopPropagation()
525
+ setVisible(!_visible)
526
+ }}
527
+ ></button>
412
528
  </div>
413
- }
414
- visible={_visible}
415
- ref={ref}
416
- >
417
- <>
529
+ </div>
530
+ <div className="form-multi-select-dropdown" role="menu" ref={dropdownRef}>
418
531
  {multiple && selectAll && (
419
532
  <button
420
533
  type="button"
@@ -434,13 +547,15 @@ export const CMultiSelect = forwardRef<HTMLDivElement, CMultiSelectProps>(
434
547
  }
435
548
  optionsMaxHeight={optionsMaxHeight}
436
549
  optionsStyle={optionsStyle}
550
+ optionsTemplate={optionsTemplate}
551
+ optionsGroupsTemplate={optionsGroupsTemplate}
437
552
  searchNoResultsLabel={searchNoResultsLabel}
438
553
  selected={selected}
439
554
  virtualScroller={virtualScroller}
440
555
  visibleItems={visibleItems}
441
556
  />
442
- </>
443
- </CPicker>
557
+ </div>
558
+ </div>
444
559
  </CFormControlWrapper>
445
560
  )
446
561
  },
@@ -454,10 +569,16 @@ CMultiSelect.propTypes = {
454
569
  loading: PropTypes.bool,
455
570
  multiple: PropTypes.bool,
456
571
  onChange: PropTypes.func,
572
+ onFilterChange: PropTypes.func,
573
+ onHide: PropTypes.func,
574
+ onShow: PropTypes.func,
457
575
  options: PropTypes.array.isRequired,
458
576
  optionsMaxHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
459
577
  optionsStyle: PropTypes.oneOf(['checkbox', 'text']),
578
+ optionsTemplate: PropTypes.func,
579
+ optionsGroupsTemplate: PropTypes.func,
460
580
  placeholder: PropTypes.string,
581
+ required: PropTypes.bool,
461
582
  search: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf<'external'>(['external'])]),
462
583
  searchNoResultsLabel: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
463
584
  selectAll: PropTypes.bool,
@@ -1,44 +1,54 @@
1
- import React, { forwardRef, HTMLAttributes } from 'react'
1
+ import React, { forwardRef, InputHTMLAttributes } from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
 
4
4
  import type { Option } from './types'
5
5
 
6
6
  export interface CMultiSelectNativeSelectProps
7
- extends Omit<HTMLAttributes<HTMLSelectElement>, 'options'> {
8
- multiple?: boolean
9
- options: Option[] | undefined
10
- value?: string | number | readonly string[]
7
+ extends Omit<InputHTMLAttributes<HTMLSelectElement>, 'options'> {
8
+ options?: Option[]
9
+ value?: string | number | string[]
11
10
  }
12
11
 
12
+ const createNativeOptions = (options: Option[]) =>
13
+ options &&
14
+ options.map((option: Option, index: number) =>
15
+ option.options ? (
16
+ <optgroup label={option.label} key={index}>
17
+ {createNativeOptions(option.options)}
18
+ </optgroup>
19
+ ) : (
20
+ <option value={option.value} key={index} disabled={option.disabled}>
21
+ {option.text}
22
+ </option>
23
+ ),
24
+ )
25
+
13
26
  export const CMultiSelectNativeSelect = forwardRef<
14
27
  HTMLSelectElement,
15
28
  CMultiSelectNativeSelectProps
16
- >(({ multiple, options, ...rest }, ref) => {
17
- const createNativeOptions = (options: Option[]) => {
18
- return options.map((option: Option, index: number) => {
19
- return option.options ? (
20
- <optgroup label={option.label} key={index}>
21
- {createNativeOptions(option.options)}
22
- </optgroup>
23
- ) : (
24
- <option value={option.value} key={index} disabled={option.disabled}>
25
- {option.text}
26
- </option>
27
- )
28
- })
29
- }
30
-
29
+ >(({ id, options, ...rest }, ref) => {
31
30
  return (
32
- <select multiple={multiple} tabIndex={-1} style={{ display: 'none' }} {...rest} ref={ref}>
31
+ <select
32
+ className="multi-select-new"
33
+ {...(id && { id: `${id}-multi-select` })}
34
+ {...(id && { name: `${id}-multi-select` })}
35
+ tabIndex={-1}
36
+ style={{ display: 'none' }}
37
+ {...rest}
38
+ ref={ref}
39
+ >
33
40
  {options && createNativeOptions(options)}
34
41
  </select>
35
42
  )
36
43
  })
37
44
 
38
45
  CMultiSelectNativeSelect.propTypes = {
39
- multiple: PropTypes.any, // TODO: update
40
- options: PropTypes.array.isRequired,
41
- value: PropTypes.any, // TODO: update
46
+ options: PropTypes.array,
47
+ value: PropTypes.oneOfType([
48
+ PropTypes.number,
49
+ PropTypes.string,
50
+ PropTypes.arrayOf(PropTypes.string.isRequired),
51
+ ]),
42
52
  }
43
53
 
44
54
  CMultiSelectNativeSelect.displayName = 'CMultiSelectNativeSelect'