@coreui/react-pro 5.25.0 → 5.26.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 (291) hide show
  1. package/dist/cjs/components/card/CCardImage.js.map +1 -1
  2. package/dist/cjs/components/carousel/CCarousel.js +44 -33
  3. package/dist/cjs/components/carousel/CCarousel.js.map +1 -1
  4. package/dist/cjs/components/carousel/CCarouselItem.js +19 -28
  5. package/dist/cjs/components/carousel/CCarouselItem.js.map +1 -1
  6. package/dist/cjs/components/chip/CChip.d.ts +12 -0
  7. package/dist/cjs/components/chip/CChip.js +16 -63
  8. package/dist/cjs/components/chip/CChip.js.map +1 -1
  9. package/dist/cjs/components/chip/const.d.ts +1 -0
  10. package/dist/cjs/components/chip/const.js +8 -0
  11. package/dist/cjs/components/chip/const.js.map +1 -0
  12. package/dist/cjs/components/chip-set/CChipSet.d.ts +73 -0
  13. package/dist/cjs/components/chip-set/CChipSet.js +66 -0
  14. package/dist/cjs/components/chip-set/CChipSet.js.map +1 -0
  15. package/dist/cjs/components/chip-set/index.d.ts +2 -0
  16. package/dist/cjs/components/chip-set/useChipSet.d.ts +48 -0
  17. package/dist/cjs/components/chip-set/useChipSet.js +163 -0
  18. package/dist/cjs/components/chip-set/useChipSet.js.map +1 -0
  19. package/dist/cjs/components/chip-set/utils.d.ts +7 -0
  20. package/dist/cjs/components/chip-set/utils.js +18 -0
  21. package/dist/cjs/components/chip-set/utils.js.map +1 -0
  22. package/dist/cjs/components/collapse/CCollapse.js +14 -6
  23. package/dist/cjs/components/collapse/CCollapse.js.map +1 -1
  24. package/dist/cjs/components/conditional-portal/CConditionalPortal.js +3 -1
  25. package/dist/cjs/components/conditional-portal/CConditionalPortal.js.map +1 -1
  26. package/dist/cjs/components/dropdown/CDropdown.js +22 -29
  27. package/dist/cjs/components/dropdown/CDropdown.js.map +1 -1
  28. package/dist/cjs/components/dropdown/CDropdownToggle.js.map +1 -1
  29. package/dist/cjs/components/form/CChipInput.d.ts +14 -2
  30. package/dist/cjs/components/form/CChipInput.js +77 -49
  31. package/dist/cjs/components/form/CChipInput.js.map +1 -1
  32. package/dist/cjs/components/form/CFormCheck.js.map +1 -1
  33. package/dist/cjs/components/form/CFormInput.js.map +1 -1
  34. package/dist/cjs/components/form/CFormSelect.js.map +1 -1
  35. package/dist/cjs/components/form/CFormTextarea.js.map +1 -1
  36. package/dist/cjs/components/form/CInputGroupText.js.map +1 -1
  37. package/dist/cjs/components/grid/CContainer.js +3 -1
  38. package/dist/cjs/components/grid/CContainer.js.map +1 -1
  39. package/dist/cjs/components/header/CHeaderBrand.js.map +1 -1
  40. package/dist/cjs/components/index.d.ts +1 -0
  41. package/dist/cjs/components/link/CLink.js +4 -2
  42. package/dist/cjs/components/link/CLink.js.map +1 -1
  43. package/dist/cjs/components/list-group/CListGroupItem.js.map +1 -1
  44. package/dist/cjs/components/modal/CModal.js +14 -2
  45. package/dist/cjs/components/modal/CModal.js.map +1 -1
  46. package/dist/cjs/components/multi-select/CMultiSelect.d.ts +73 -1
  47. package/dist/cjs/components/multi-select/CMultiSelect.js +157 -18
  48. package/dist/cjs/components/multi-select/CMultiSelect.js.map +1 -1
  49. package/dist/cjs/components/multi-select/CMultiSelectNativeSelect.js +1 -1
  50. package/dist/cjs/components/multi-select/CMultiSelectNativeSelect.js.map +1 -1
  51. package/dist/cjs/components/multi-select/CMultiSelectOptions.d.ts +3 -0
  52. package/dist/cjs/components/multi-select/CMultiSelectOptions.js +45 -2
  53. package/dist/cjs/components/multi-select/CMultiSelectOptions.js.map +1 -1
  54. package/dist/cjs/components/multi-select/CMultiSelectSelection.d.ts +1 -0
  55. package/dist/cjs/components/multi-select/CMultiSelectSelection.js +4 -3
  56. package/dist/cjs/components/multi-select/CMultiSelectSelection.js.map +1 -1
  57. package/dist/cjs/components/multi-select/types.d.ts +12 -0
  58. package/dist/cjs/components/multi-select/utils.js +1 -4
  59. package/dist/cjs/components/multi-select/utils.js.map +1 -1
  60. package/dist/cjs/components/nav/CNav.js.map +1 -1
  61. package/dist/cjs/components/nav/CNavGroup.d.ts +10 -5
  62. package/dist/cjs/components/nav/CNavGroup.js +83 -24
  63. package/dist/cjs/components/nav/CNavGroup.js.map +1 -1
  64. package/dist/cjs/components/nav/CNavGroupContext.d.ts +25 -0
  65. package/dist/cjs/components/nav/CNavGroupContext.js +13 -0
  66. package/dist/cjs/components/nav/CNavGroupContext.js.map +1 -0
  67. package/dist/cjs/components/nav/CNavLink.d.ts +1 -5
  68. package/dist/cjs/components/nav/CNavLink.js +21 -8
  69. package/dist/cjs/components/nav/CNavLink.js.map +1 -1
  70. package/dist/cjs/components/popover/CPopover.js +57 -9
  71. package/dist/cjs/components/popover/CPopover.js.map +1 -1
  72. package/dist/cjs/components/progress/CProgress.js.map +1 -1
  73. package/dist/cjs/components/rating/CRating.d.ts +1 -1
  74. package/dist/cjs/components/rating/CRating.js.map +1 -1
  75. package/dist/cjs/components/sidebar/CSidebar.js +40 -23
  76. package/dist/cjs/components/sidebar/CSidebar.js.map +1 -1
  77. package/dist/cjs/components/sidebar/CSidebarNav.js +6 -35
  78. package/dist/cjs/components/sidebar/CSidebarNav.js.map +1 -1
  79. package/dist/cjs/components/table/CTableDataCell.js.map +1 -1
  80. package/dist/cjs/components/table/types.d.ts +2 -1
  81. package/dist/cjs/components/table/utils.js +4 -4
  82. package/dist/cjs/components/table/utils.js.map +1 -1
  83. package/dist/cjs/components/tabs/CTabPanel.js +3 -1
  84. package/dist/cjs/components/tabs/CTabPanel.js.map +1 -1
  85. package/dist/cjs/components/tooltip/CTooltip.js +57 -9
  86. package/dist/cjs/components/tooltip/CTooltip.js.map +1 -1
  87. package/dist/cjs/helpers/polymorphicComponent.d.ts +1 -2
  88. package/dist/cjs/hooks/useForkedRef.d.ts +1 -1
  89. package/dist/cjs/hooks/useForkedRef.js +6 -3
  90. package/dist/cjs/hooks/useForkedRef.js.map +1 -1
  91. package/dist/cjs/hooks/usePopper.js +8 -6
  92. package/dist/cjs/hooks/usePopper.js.map +1 -1
  93. package/dist/cjs/index.js +2 -0
  94. package/dist/cjs/index.js.map +1 -1
  95. package/dist/cjs/node_modules/@babel/runtime/helpers/esm/objectWithoutPropertiesLoose.js +1 -1
  96. package/dist/cjs/node_modules/@babel/runtime/helpers/esm/objectWithoutPropertiesLoose.js.map +1 -1
  97. package/dist/cjs/node_modules/ua-parser-js/dist/ua-parser.min.js +3 -3
  98. package/dist/cjs/node_modules/ua-parser-js/dist/ua-parser.min.js.map +1 -1
  99. package/dist/cjs/utils/executeAfterTransition.js.map +1 -1
  100. package/dist/cjs/utils/index.d.ts +2 -1
  101. package/dist/cjs/utils/swipe.d.ts +26 -0
  102. package/dist/cjs/utils/swipe.js +94 -0
  103. package/dist/cjs/utils/swipe.js.map +1 -0
  104. package/dist/esm/components/card/CCardImage.js.map +1 -1
  105. package/dist/esm/components/carousel/CCarousel.js +44 -33
  106. package/dist/esm/components/carousel/CCarousel.js.map +1 -1
  107. package/dist/esm/components/carousel/CCarouselItem.js +19 -28
  108. package/dist/esm/components/carousel/CCarouselItem.js.map +1 -1
  109. package/dist/esm/components/chip/CChip.d.ts +12 -0
  110. package/dist/esm/components/chip/CChip.js +16 -63
  111. package/dist/esm/components/chip/CChip.js.map +1 -1
  112. package/dist/esm/components/chip/const.d.ts +1 -0
  113. package/dist/esm/components/chip/const.js +6 -0
  114. package/dist/esm/components/chip/const.js.map +1 -0
  115. package/dist/esm/components/chip-set/CChipSet.d.ts +73 -0
  116. package/dist/esm/components/chip-set/CChipSet.js +64 -0
  117. package/dist/esm/components/chip-set/CChipSet.js.map +1 -0
  118. package/dist/esm/components/chip-set/index.d.ts +2 -0
  119. package/dist/esm/components/chip-set/useChipSet.d.ts +48 -0
  120. package/dist/esm/components/chip-set/useChipSet.js +161 -0
  121. package/dist/esm/components/chip-set/useChipSet.js.map +1 -0
  122. package/dist/esm/components/chip-set/utils.d.ts +7 -0
  123. package/dist/esm/components/chip-set/utils.js +16 -0
  124. package/dist/esm/components/chip-set/utils.js.map +1 -0
  125. package/dist/esm/components/collapse/CCollapse.js +14 -6
  126. package/dist/esm/components/collapse/CCollapse.js.map +1 -1
  127. package/dist/esm/components/conditional-portal/CConditionalPortal.js +3 -1
  128. package/dist/esm/components/conditional-portal/CConditionalPortal.js.map +1 -1
  129. package/dist/esm/components/dropdown/CDropdown.js +22 -29
  130. package/dist/esm/components/dropdown/CDropdown.js.map +1 -1
  131. package/dist/esm/components/dropdown/CDropdownToggle.js.map +1 -1
  132. package/dist/esm/components/form/CChipInput.d.ts +14 -2
  133. package/dist/esm/components/form/CChipInput.js +78 -50
  134. package/dist/esm/components/form/CChipInput.js.map +1 -1
  135. package/dist/esm/components/form/CFormCheck.js.map +1 -1
  136. package/dist/esm/components/form/CFormInput.js.map +1 -1
  137. package/dist/esm/components/form/CFormSelect.js.map +1 -1
  138. package/dist/esm/components/form/CFormTextarea.js.map +1 -1
  139. package/dist/esm/components/form/CInputGroupText.js.map +1 -1
  140. package/dist/esm/components/grid/CContainer.js +3 -1
  141. package/dist/esm/components/grid/CContainer.js.map +1 -1
  142. package/dist/esm/components/header/CHeaderBrand.js.map +1 -1
  143. package/dist/esm/components/index.d.ts +1 -0
  144. package/dist/esm/components/link/CLink.js +4 -2
  145. package/dist/esm/components/link/CLink.js.map +1 -1
  146. package/dist/esm/components/list-group/CListGroupItem.js.map +1 -1
  147. package/dist/esm/components/modal/CModal.js +14 -2
  148. package/dist/esm/components/modal/CModal.js.map +1 -1
  149. package/dist/esm/components/multi-select/CMultiSelect.d.ts +73 -1
  150. package/dist/esm/components/multi-select/CMultiSelect.js +158 -19
  151. package/dist/esm/components/multi-select/CMultiSelect.js.map +1 -1
  152. package/dist/esm/components/multi-select/CMultiSelectNativeSelect.js +1 -1
  153. package/dist/esm/components/multi-select/CMultiSelectNativeSelect.js.map +1 -1
  154. package/dist/esm/components/multi-select/CMultiSelectOptions.d.ts +3 -0
  155. package/dist/esm/components/multi-select/CMultiSelectOptions.js +45 -2
  156. package/dist/esm/components/multi-select/CMultiSelectOptions.js.map +1 -1
  157. package/dist/esm/components/multi-select/CMultiSelectSelection.d.ts +1 -0
  158. package/dist/esm/components/multi-select/CMultiSelectSelection.js +4 -3
  159. package/dist/esm/components/multi-select/CMultiSelectSelection.js.map +1 -1
  160. package/dist/esm/components/multi-select/types.d.ts +12 -0
  161. package/dist/esm/components/multi-select/utils.js +1 -4
  162. package/dist/esm/components/multi-select/utils.js.map +1 -1
  163. package/dist/esm/components/nav/CNav.js.map +1 -1
  164. package/dist/esm/components/nav/CNavGroup.d.ts +10 -5
  165. package/dist/esm/components/nav/CNavGroup.js +84 -25
  166. package/dist/esm/components/nav/CNavGroup.js.map +1 -1
  167. package/dist/esm/components/nav/CNavGroupContext.d.ts +25 -0
  168. package/dist/esm/components/nav/CNavGroupContext.js +11 -0
  169. package/dist/esm/components/nav/CNavGroupContext.js.map +1 -0
  170. package/dist/esm/components/nav/CNavLink.d.ts +1 -5
  171. package/dist/esm/components/nav/CNavLink.js +21 -8
  172. package/dist/esm/components/nav/CNavLink.js.map +1 -1
  173. package/dist/esm/components/popover/CPopover.js +57 -9
  174. package/dist/esm/components/popover/CPopover.js.map +1 -1
  175. package/dist/esm/components/progress/CProgress.js.map +1 -1
  176. package/dist/esm/components/rating/CRating.d.ts +1 -1
  177. package/dist/esm/components/rating/CRating.js.map +1 -1
  178. package/dist/esm/components/sidebar/CSidebar.js +40 -23
  179. package/dist/esm/components/sidebar/CSidebar.js.map +1 -1
  180. package/dist/esm/components/sidebar/CSidebarNav.js +7 -36
  181. package/dist/esm/components/sidebar/CSidebarNav.js.map +1 -1
  182. package/dist/esm/components/table/CTableDataCell.js.map +1 -1
  183. package/dist/esm/components/table/types.d.ts +2 -1
  184. package/dist/esm/components/table/utils.js +4 -4
  185. package/dist/esm/components/table/utils.js.map +1 -1
  186. package/dist/esm/components/tabs/CTabPanel.js +3 -1
  187. package/dist/esm/components/tabs/CTabPanel.js.map +1 -1
  188. package/dist/esm/components/tooltip/CTooltip.js +57 -9
  189. package/dist/esm/components/tooltip/CTooltip.js.map +1 -1
  190. package/dist/esm/helpers/polymorphicComponent.d.ts +1 -2
  191. package/dist/esm/hooks/useForkedRef.d.ts +1 -1
  192. package/dist/esm/hooks/useForkedRef.js +6 -3
  193. package/dist/esm/hooks/useForkedRef.js.map +1 -1
  194. package/dist/esm/hooks/usePopper.js +9 -7
  195. package/dist/esm/hooks/usePopper.js.map +1 -1
  196. package/dist/esm/index.js +1 -0
  197. package/dist/esm/index.js.map +1 -1
  198. package/dist/esm/node_modules/@babel/runtime/helpers/esm/objectWithoutPropertiesLoose.js +1 -1
  199. package/dist/esm/node_modules/@babel/runtime/helpers/esm/objectWithoutPropertiesLoose.js.map +1 -1
  200. package/dist/esm/node_modules/ua-parser-js/dist/ua-parser.min.js +3 -3
  201. package/dist/esm/node_modules/ua-parser-js/dist/ua-parser.min.js.map +1 -1
  202. package/dist/esm/utils/executeAfterTransition.js.map +1 -1
  203. package/dist/esm/utils/index.d.ts +2 -1
  204. package/dist/esm/utils/swipe.d.ts +26 -0
  205. package/dist/esm/utils/swipe.js +90 -0
  206. package/dist/esm/utils/swipe.js.map +1 -0
  207. package/package.json +6 -6
  208. package/src/components/autocomplete/CAutocompleteOptions.tsx +1 -1
  209. package/src/components/card/CCardImage.tsx +3 -2
  210. package/src/components/carousel/CCarousel.tsx +48 -51
  211. package/src/components/carousel/CCarouselContext.ts +1 -1
  212. package/src/components/carousel/CCarouselItem.tsx +25 -26
  213. package/src/components/carousel/__tests__/CCarousel.spec.tsx +47 -4
  214. package/src/components/carousel/__tests__/CCarouselItem.spec.tsx +37 -0
  215. package/src/components/chip/CChip.tsx +45 -78
  216. package/src/components/chip/__tests__/CChip.spec.tsx +26 -19
  217. package/src/components/chip/const.ts +3 -0
  218. package/src/components/chip-set/CChipSet.tsx +183 -0
  219. package/src/components/chip-set/__tests__/CChipSet.spec.tsx +215 -0
  220. package/src/components/chip-set/__tests__/__snapshots__/CChipSet.spec.tsx.snap +22 -0
  221. package/src/components/chip-set/index.ts +3 -0
  222. package/src/components/chip-set/useChipSet.ts +243 -0
  223. package/src/components/chip-set/utils.ts +19 -0
  224. package/src/components/collapse/CCollapse.tsx +18 -6
  225. package/src/components/conditional-portal/CConditionalPortal.tsx +4 -2
  226. package/src/components/dropdown/CDropdown.tsx +28 -33
  227. package/src/components/dropdown/CDropdownToggle.tsx +1 -1
  228. package/src/components/dropdown/__tests__/CDropdown.spec.tsx +55 -0
  229. package/src/components/focus-trap/__tests__/CFocusTrap.spec.tsx +1 -1
  230. package/src/components/form/CChipInput.tsx +105 -67
  231. package/src/components/form/CFormCheck.tsx +1 -2
  232. package/src/components/form/CFormInput.tsx +1 -2
  233. package/src/components/form/CFormSelect.tsx +1 -2
  234. package/src/components/form/CFormTextarea.tsx +1 -2
  235. package/src/components/form/CInputGroupText.tsx +3 -2
  236. package/src/components/form/__tests__/CChipInput.spec.tsx +67 -1
  237. package/src/components/form/__tests__/__snapshots__/CChipInput.spec.tsx.snap +7 -0
  238. package/src/components/grid/CContainer.tsx +3 -1
  239. package/src/components/header/CHeaderBrand.tsx +3 -2
  240. package/src/components/index.ts +1 -0
  241. package/src/components/link/CLink.tsx +7 -4
  242. package/src/components/list-group/CListGroupItem.tsx +3 -2
  243. package/src/components/modal/CModal.tsx +19 -2
  244. package/src/components/modal/__tests__/CModal.spec.tsx +43 -0
  245. package/src/components/multi-select/CMultiSelect.tsx +341 -46
  246. package/src/components/multi-select/CMultiSelectNativeSelect.tsx +1 -1
  247. package/src/components/multi-select/CMultiSelectOptions.tsx +74 -3
  248. package/src/components/multi-select/CMultiSelectSelection.tsx +5 -1
  249. package/src/components/multi-select/__tests__/CMultiSelect.spec.tsx +12 -8
  250. package/src/components/multi-select/__tests__/__snapshots__/CMultiSelect.spec.tsx.snap +62 -45
  251. package/src/components/multi-select/__tests__/__snapshots__/CMultiSelectNativeSelect.spec.tsx.snap +2 -2
  252. package/src/components/multi-select/__tests__/__snapshots__/CMultiSelectOptions.spec.tsx.snap +1 -0
  253. package/src/components/multi-select/__tests__/__snapshots__/CMultiSelectSelection.spec.tsx.snap +5 -1
  254. package/src/components/multi-select/types.ts +14 -0
  255. package/src/components/nav/CNav.tsx +3 -2
  256. package/src/components/nav/CNavGroup.tsx +193 -107
  257. package/src/components/nav/CNavGroupContext.ts +27 -0
  258. package/src/components/nav/CNavLink.tsx +26 -12
  259. package/src/components/nav/__tests__/CNavGroup.spec.tsx +195 -20
  260. package/src/components/nav/__tests__/__snapshots__/CNavGroup.spec.tsx.snap +2 -0
  261. package/src/components/popover/CPopover.tsx +59 -9
  262. package/src/components/popover/__tests__/CPopover.spec.tsx +76 -1
  263. package/src/components/progress/CProgress.tsx +1 -2
  264. package/src/components/rating/CRating.tsx +1 -0
  265. package/src/components/search-button/__tests__/CSearchButton.spec.tsx +2 -2
  266. package/src/components/sidebar/CSidebar.tsx +51 -26
  267. package/src/components/sidebar/CSidebarNav.tsx +12 -51
  268. package/src/components/sidebar/__tests__/CSidebar.spec.tsx +26 -0
  269. package/src/components/table/CTableDataCell.tsx +2 -1
  270. package/src/components/table/types.ts +3 -1
  271. package/src/components/table/utils.ts +4 -4
  272. package/src/components/tabs/CTabPanel.tsx +3 -1
  273. package/src/components/tabs/CTabsContext.ts +1 -1
  274. package/src/components/tooltip/CTooltip.tsx +59 -9
  275. package/src/components/tooltip/__tests__/CTooltip.spec.tsx +96 -1
  276. package/src/helpers/polymorphicComponent.ts +1 -1
  277. package/src/hooks/useColorModes.ts +2 -2
  278. package/src/hooks/useForkedRef.ts +20 -15
  279. package/src/hooks/usePopper.ts +13 -9
  280. package/src/utils/__tests__/swipe.spec.ts +82 -0
  281. package/src/utils/executeAfterTransition.ts +1 -1
  282. package/src/utils/index.ts +2 -0
  283. package/src/utils/mergeClassNames.ts +1 -1
  284. package/src/utils/swipe.ts +114 -0
  285. package/dist/cjs/components/sidebar/CSidebarNavContext.d.ts +0 -5
  286. package/dist/cjs/components/sidebar/CSidebarNavContext.js +0 -8
  287. package/dist/cjs/components/sidebar/CSidebarNavContext.js.map +0 -1
  288. package/dist/esm/components/sidebar/CSidebarNavContext.d.ts +0 -5
  289. package/dist/esm/components/sidebar/CSidebarNavContext.js +0 -6
  290. package/dist/esm/components/sidebar/CSidebarNavContext.js.map +0 -1
  291. package/src/components/sidebar/CSidebarNavContext.ts +0 -8
@@ -0,0 +1,3 @@
1
+ // Chips advertise their own focusability via `data-coreui-chip-focusable`,
2
+ // so containers (CChipSet, CChipInput) read it instead of re-deriving it.
3
+ export const SELECTOR_CHIP_FOCUSABLE = '[data-coreui-chip-focusable="true"]:not(.disabled)'
@@ -0,0 +1,183 @@
1
+ import React, {
2
+ ElementType,
3
+ HTMLAttributes,
4
+ KeyboardEvent,
5
+ ReactNode,
6
+ forwardRef,
7
+ useState,
8
+ } from 'react'
9
+ import PropTypes from 'prop-types'
10
+ import classNames from 'classnames'
11
+
12
+ import { useChipSet, type CChipSetItem } from './useChipSet'
13
+ import { PolymorphicRefForwardingComponent } from '../../helpers'
14
+ import { useForkedRef } from '../../hooks'
15
+
16
+ export type { CChipSetItem }
17
+
18
+ const itemValue = (item: string | CChipSetItem): string =>
19
+ typeof item === 'string' ? item : item.value
20
+
21
+ export interface CChipSetProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onSelect'> {
22
+ /**
23
+ * Provides an accessible label for the remove button of every chip rendered by the React Chip Set component.
24
+ */
25
+ ariaRemoveLabel?: string
26
+ /**
27
+ * Specifies the root element or custom component used by the React Chip Set component.
28
+ */
29
+ as?: ElementType
30
+ /**
31
+ * Renders chips from data instead of children. Each item is a string or an object with a `value`, an optional `label`, and any `CChip` props. Children are used when this is omitted.
32
+ */
33
+ chips?: (string | CChipSetItem)[]
34
+ /**
35
+ * Adds custom classes to the React Chip Set root element.
36
+ */
37
+ className?: string
38
+ /**
39
+ * Sets the initial uncontrolled chips. In this mode the React Chip Set component owns the list and removes chips itself; use `chips` (with `onRemove`) for a controlled list.
40
+ */
41
+ defaultChips?: (string | CChipSetItem)[]
42
+ /**
43
+ * Sets the initial uncontrolled selection of the React Chip Set component.
44
+ */
45
+ defaultSelected?: string[]
46
+ /**
47
+ * Disables every chip rendered by the React Chip Set component.
48
+ */
49
+ disabled?: boolean
50
+ /**
51
+ * Turns the chips into filter chips, each showing a leading check icon while selected.
52
+ */
53
+ filter?: boolean
54
+ /**
55
+ * Callback fired when a chip requests removal. The chips are controlled by their rendered children, so remove the chip from your data in response.
56
+ */
57
+ onRemove?: (value: string) => void
58
+ /**
59
+ * Callback fired when the selected chip values of the React Chip Set component change.
60
+ */
61
+ onSelect?: (selected: string[]) => void
62
+ /**
63
+ * Displays a remove button on every chip rendered by the React Chip Set component.
64
+ */
65
+ removable?: boolean
66
+ /**
67
+ * Replaces the default remove icon on every chip with a custom icon node.
68
+ */
69
+ removeIcon?: ReactNode
70
+ /**
71
+ * Enables selection behavior for the chips rendered by the React Chip Set component.
72
+ */
73
+ selectable?: boolean
74
+ /**
75
+ * Controls the selected chip values of the React Chip Set component.
76
+ *
77
+ * @controllable onSelect
78
+ */
79
+ selected?: string[]
80
+ /**
81
+ * Replaces the default selected icon shown by filter chips with a custom icon node.
82
+ */
83
+ selectedIcon?: ReactNode
84
+ /**
85
+ * Sets how many chips can be selected at once in the React Chip Set component.
86
+ */
87
+ selectionMode?: 'single' | 'multiple'
88
+ }
89
+
90
+ export const CChipSet: PolymorphicRefForwardingComponent<'div', CChipSetProps> = forwardRef<
91
+ HTMLDivElement,
92
+ CChipSetProps
93
+ >(
94
+ (
95
+ {
96
+ ariaRemoveLabel,
97
+ as: Component = 'div',
98
+ children,
99
+ chips,
100
+ className,
101
+ defaultChips,
102
+ defaultSelected,
103
+ disabled,
104
+ filter,
105
+ onKeyDown,
106
+ onRemove,
107
+ onSelect,
108
+ removable,
109
+ removeIcon,
110
+ selectable,
111
+ selected,
112
+ selectedIcon,
113
+ selectionMode,
114
+ ...rest
115
+ },
116
+ ref
117
+ ) => {
118
+ // The chip list is controlled by `chips` (or children) or, with `defaultChips`,
119
+ // uncontrolled — then the set owns the list and removes chips itself.
120
+ const isUncontrolledList = chips === undefined && defaultChips !== undefined
121
+ const [_chips, setChips] = useState<(string | CChipSetItem)[]>(defaultChips ?? [])
122
+
123
+ const { rootRef, handleKeyDown, renderChips, renderChipsFromData } = useChipSet({
124
+ ariaRemoveLabel,
125
+ defaultSelected,
126
+ disabled,
127
+ filter,
128
+ removable,
129
+ removeIcon,
130
+ selectable,
131
+ selected,
132
+ selectedIcon,
133
+ selectionMode,
134
+ onRemoveChip: (value) => {
135
+ if (isUncontrolledList) {
136
+ setChips((prev) => prev.filter((item) => itemValue(item) !== value))
137
+ }
138
+ onRemove?.(value)
139
+ },
140
+ onSelectionChange: onSelect,
141
+ })
142
+ const forkedRef = useForkedRef(ref, rootRef)
143
+
144
+ const items = chips ?? (isUncontrolledList ? _chips : undefined)
145
+
146
+ return (
147
+ <Component
148
+ className={classNames('chip-set', { disabled }, className)}
149
+ {...(disabled && { 'aria-disabled': true })}
150
+ onKeyDown={(event: KeyboardEvent<HTMLDivElement>) => {
151
+ handleKeyDown(event)
152
+ onKeyDown?.(event)
153
+ }}
154
+ {...rest}
155
+ ref={forkedRef}
156
+ >
157
+ {items ? renderChipsFromData(items) : renderChips(children)}
158
+ </Component>
159
+ )
160
+ }
161
+ )
162
+
163
+ CChipSet.propTypes = {
164
+ ariaRemoveLabel: PropTypes.string,
165
+ as: PropTypes.elementType,
166
+ children: PropTypes.node,
167
+ chips: PropTypes.array,
168
+ className: PropTypes.string,
169
+ defaultChips: PropTypes.array,
170
+ defaultSelected: PropTypes.array,
171
+ disabled: PropTypes.bool,
172
+ filter: PropTypes.bool,
173
+ onRemove: PropTypes.func,
174
+ onSelect: PropTypes.func,
175
+ removable: PropTypes.bool,
176
+ removeIcon: PropTypes.node,
177
+ selectable: PropTypes.bool,
178
+ selected: PropTypes.array,
179
+ selectedIcon: PropTypes.node,
180
+ selectionMode: PropTypes.oneOf(['single', 'multiple']),
181
+ }
182
+
183
+ CChipSet.displayName = 'CChipSet'
@@ -0,0 +1,215 @@
1
+ import * as React from 'react'
2
+ import { fireEvent, render } from '@testing-library/react'
3
+ import '@testing-library/jest-dom'
4
+ import { CChip } from '../../chip'
5
+ import { CChipSet } from '../index'
6
+
7
+ test('loads and displays CChipSet component', async () => {
8
+ const { container } = render(
9
+ <CChipSet>
10
+ <CChip value="a">A</CChip>
11
+ <CChip value="b">B</CChip>
12
+ </CChipSet>
13
+ )
14
+ expect(container).toMatchSnapshot()
15
+ expect(container.firstChild).toHaveClass('chip-set')
16
+ })
17
+
18
+ test('CChipSet passes selectable down to its chips', async () => {
19
+ const { getByText } = render(
20
+ <CChipSet selectable={true}>
21
+ <CChip value="a">A</CChip>
22
+ <CChip value="b">B</CChip>
23
+ </CChipSet>
24
+ )
25
+
26
+ expect(getByText('A')).toHaveAttribute('aria-selected', 'false')
27
+ fireEvent.click(getByText('A'))
28
+ expect(getByText('A')).toHaveClass('active')
29
+ })
30
+
31
+ test('CChipSet allows multiple selected chips by default', async () => {
32
+ const onSelect = jest.fn()
33
+ const { getByText } = render(
34
+ <CChipSet selectable={true} onSelect={onSelect}>
35
+ <CChip value="a">A</CChip>
36
+ <CChip value="b">B</CChip>
37
+ </CChipSet>
38
+ )
39
+
40
+ fireEvent.click(getByText('A'))
41
+ fireEvent.click(getByText('B'))
42
+
43
+ expect(getByText('A')).toHaveClass('active')
44
+ expect(getByText('B')).toHaveClass('active')
45
+ expect(onSelect).toHaveBeenLastCalledWith(['a', 'b'])
46
+ })
47
+
48
+ test('CChipSet deselects siblings in single selection mode', async () => {
49
+ const onSelect = jest.fn()
50
+ const { getByText } = render(
51
+ <CChipSet selectable={true} selectionMode="single" onSelect={onSelect}>
52
+ <CChip value="a">A</CChip>
53
+ <CChip value="b">B</CChip>
54
+ </CChipSet>
55
+ )
56
+
57
+ fireEvent.click(getByText('A'))
58
+ fireEvent.click(getByText('B'))
59
+
60
+ expect(getByText('A')).not.toHaveClass('active')
61
+ expect(getByText('B')).toHaveClass('active')
62
+ expect(onSelect).toHaveBeenLastCalledWith(['b'])
63
+ })
64
+
65
+ test('CChipSet honors a controlled selected', async () => {
66
+ const { getByText } = render(
67
+ <CChipSet selectable={true} selected={['b']}>
68
+ <CChip value="a">A</CChip>
69
+ <CChip value="b">B</CChip>
70
+ </CChipSet>
71
+ )
72
+
73
+ expect(getByText('A')).not.toHaveClass('active')
74
+ expect(getByText('B')).toHaveClass('active')
75
+ })
76
+
77
+ test('CChipSet forwards filter so a selected chip shows a check icon', async () => {
78
+ const { getByText, container } = render(
79
+ <CChipSet filter={true} selected={['a']}>
80
+ <CChip value="a">A</CChip>
81
+ </CChipSet>
82
+ )
83
+
84
+ expect(getByText('A')).toHaveClass('active')
85
+ expect(container.querySelector('.chip-check')).toBeInTheDocument()
86
+ })
87
+
88
+ test('CChipSet moves focus between chips with the keyboard', async () => {
89
+ const { getByText } = render(
90
+ <CChipSet selectable>
91
+ <CChip value="first">First</CChip>
92
+ <CChip value="second">Second</CChip>
93
+ <CChip value="third">Third</CChip>
94
+ </CChipSet>
95
+ )
96
+
97
+ const first = getByText('First')
98
+ const second = getByText('Second')
99
+ const third = getByText('Third')
100
+
101
+ first.focus()
102
+ fireEvent.keyDown(first, { key: 'ArrowRight' })
103
+ expect(second).toHaveFocus()
104
+
105
+ fireEvent.keyDown(second, { key: 'End' })
106
+ expect(third).toHaveFocus()
107
+
108
+ fireEvent.keyDown(third, { key: 'Home' })
109
+ expect(first).toHaveFocus()
110
+
111
+ // No cycling past the first chip.
112
+ fireEvent.keyDown(first, { key: 'ArrowLeft' })
113
+ expect(first).toHaveFocus()
114
+ })
115
+
116
+ test('CChipSet mirrors arrow keys in RTL', async () => {
117
+ document.documentElement.dir = 'rtl'
118
+
119
+ const { getByText } = render(
120
+ <CChipSet selectable>
121
+ <CChip value="first">First</CChip>
122
+ <CChip value="second">Second</CChip>
123
+ </CChipSet>
124
+ )
125
+
126
+ const first = getByText('First')
127
+ const second = getByText('Second')
128
+
129
+ first.focus()
130
+ // In RTL, ArrowLeft moves to the next chip.
131
+ fireEvent.keyDown(first, { key: 'ArrowLeft' })
132
+ expect(second).toHaveFocus()
133
+
134
+ fireEvent.keyDown(second, { key: 'ArrowRight' })
135
+ expect(first).toHaveFocus()
136
+
137
+ document.documentElement.dir = ''
138
+ })
139
+
140
+ test('CChipSet fires onRemove so the parent can drop the chip', async () => {
141
+ const onRemove = jest.fn()
142
+
143
+ const Example = () => {
144
+ const [values, setValues] = React.useState(['a', 'b'])
145
+ return (
146
+ <CChipSet
147
+ removable
148
+ onRemove={(value) => {
149
+ onRemove(value)
150
+ setValues((prev) => prev.filter((item) => item !== value))
151
+ }}
152
+ >
153
+ {values.map((value) => (
154
+ <CChip key={value} value={value}>
155
+ {value.toUpperCase()}
156
+ </CChip>
157
+ ))}
158
+ </CChipSet>
159
+ )
160
+ }
161
+
162
+ const { getByText, queryByText } = render(<Example />)
163
+
164
+ const removeButton = getByText('A').querySelector('.chip-remove')
165
+ fireEvent.click(removeButton as Element)
166
+
167
+ expect(onRemove).toHaveBeenCalledWith('a')
168
+ expect(queryByText('A')).not.toBeInTheDocument()
169
+ expect(getByText('B')).toBeInTheDocument()
170
+ })
171
+
172
+ test('CChipSet with defaultChips removes the chip itself (uncontrolled list)', async () => {
173
+ const onRemove = jest.fn()
174
+ const { getByText, queryByText } = render(
175
+ <CChipSet removable defaultChips={['a', { value: 'b', label: 'B' }]} onRemove={onRemove} />
176
+ )
177
+
178
+ fireEvent.click(getByText('a').querySelector('.chip-remove') as Element)
179
+
180
+ expect(onRemove).toHaveBeenCalledWith('a')
181
+ expect(queryByText('a')).not.toBeInTheDocument()
182
+ expect(getByText('B')).toBeInTheDocument()
183
+ })
184
+
185
+ test('CChipSet renders chips from the chips prop (strings and objects)', async () => {
186
+ const { getByText, container } = render(
187
+ <CChipSet
188
+ selectable
189
+ chips={[
190
+ 'react',
191
+ { value: 'vue', label: 'Vue' },
192
+ { value: 'ng', label: 'Angular', selectable: false },
193
+ ]}
194
+ />
195
+ )
196
+
197
+ expect(container.querySelectorAll('.chip')).toHaveLength(3)
198
+ expect(getByText('react')).toBeInTheDocument()
199
+ expect(getByText('Vue')).toBeInTheDocument()
200
+
201
+ // Per-item override still works with the data-driven API.
202
+ fireEvent.click(getByText('Angular'))
203
+ expect(getByText('Angular')).not.toHaveClass('active')
204
+ })
205
+
206
+ test('CChipSet disables every chip', async () => {
207
+ const { getByText } = render(
208
+ <CChipSet disabled={true} removable={true}>
209
+ <CChip value="a">A</CChip>
210
+ </CChipSet>
211
+ )
212
+
213
+ expect(getByText('A')).toHaveClass('disabled')
214
+ expect(getByText('A').querySelector('.chip-remove')).toBeNull()
215
+ })
@@ -0,0 +1,22 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`loads and displays CChipSet component 1`] = `
4
+ <div>
5
+ <div
6
+ class="chip-set"
7
+ >
8
+ <span
9
+ class="chip"
10
+ data-coreui-chip-value="a"
11
+ >
12
+ A
13
+ </span>
14
+ <span
15
+ class="chip"
16
+ data-coreui-chip-value="b"
17
+ >
18
+ B
19
+ </span>
20
+ </div>
21
+ </div>
22
+ `;
@@ -0,0 +1,3 @@
1
+ import { CChipSet } from './CChipSet'
2
+
3
+ export { CChipSet }
@@ -0,0 +1,243 @@
1
+ import React, {
2
+ KeyboardEvent,
3
+ MouseEvent,
4
+ ReactNode,
5
+ isValidElement,
6
+ useEffect,
7
+ useRef,
8
+ useState,
9
+ } from 'react'
10
+
11
+ import { CChip, type CChipProps } from '../chip/CChip'
12
+ import { SELECTOR_CHIP_FOCUSABLE } from '../chip/const'
13
+ import { resolveChipValue } from './utils'
14
+ import { isRTL } from '../../utils'
15
+
16
+ export interface CChipSetItem extends Omit<CChipProps, 'value' | 'children'> {
17
+ /**
18
+ * The value that identifies the chip and tracks its selection.
19
+ */
20
+ value: string
21
+ /**
22
+ * The chip content. Falls back to `value` when omitted.
23
+ */
24
+ label?: ReactNode
25
+ }
26
+
27
+ // Build a CChip element from a data item (string or descriptor).
28
+ const buildChip = (item: string | CChipSetItem) => {
29
+ if (typeof item === 'string') {
30
+ return React.createElement(CChip, { key: item, value: item }, item)
31
+ }
32
+
33
+ const { label, value, ...chipProps } = item
34
+ return React.createElement(CChip, { key: value, value, ...chipProps }, label ?? value)
35
+ }
36
+
37
+ export interface UseChipSetOptions {
38
+ ariaRemoveLabel?: string
39
+ defaultSelected?: string[]
40
+ disabled?: boolean
41
+ filter?: boolean
42
+ removable?: boolean
43
+ removeIcon?: ReactNode
44
+ /**
45
+ * Move focus to a neighboring chip after one is removed. Containers that own a
46
+ * different focus target after removal (e.g. CChipInput refocuses its input)
47
+ * set this to `false`.
48
+ */
49
+ restoreFocusOnRemove?: boolean
50
+ selectable?: boolean
51
+ selected?: string[]
52
+ selectedIcon?: ReactNode
53
+ selectionMode?: 'single' | 'multiple'
54
+ onRemoveChip?: (value: string) => void
55
+ onSelectionChange?: (selected: string[]) => void
56
+ }
57
+
58
+ /**
59
+ * The shared chip-set engine. Both `CChipSet` and `CChipInput` build on it — the
60
+ * React equivalent of the vanilla `ChipInput extends ChipSet`. It owns selection
61
+ * coordination (single/multiple, controlled-or-uncontrolled), roving focus, the
62
+ * focus-a-neighbor-after-removal behavior, and the per-chip prop injection.
63
+ * Existence of the chips stays with the caller (its children / value list).
64
+ */
65
+ export const useChipSet = (options: UseChipSetOptions) => {
66
+ const {
67
+ ariaRemoveLabel,
68
+ defaultSelected = [],
69
+ disabled,
70
+ filter,
71
+ removable,
72
+ removeIcon,
73
+ restoreFocusOnRemove = true,
74
+ selectable,
75
+ selected,
76
+ selectedIcon,
77
+ selectionMode = 'multiple',
78
+ onRemoveChip,
79
+ onSelectionChange,
80
+ } = options
81
+
82
+ const rootRef = useRef<HTMLDivElement>(null)
83
+ const pendingFocusValue = useRef<string | null>(null)
84
+ const isControlled = selected !== undefined
85
+ const [_selected, setSelected] = useState<string[]>(defaultSelected)
86
+ // In controlled mode the selected prop drives selection directly; _selected is
87
+ // only read when uncontrolled, so no syncing effect is needed.
88
+ const selectedValues = isControlled ? (selected as string[]) : _selected
89
+
90
+ // Focus the saved neighbor once the removed chip has left the DOM.
91
+ useEffect(() => {
92
+ if (pendingFocusValue.current === null) {
93
+ return
94
+ }
95
+
96
+ const selector = `[data-coreui-chip-value="${pendingFocusValue.current}"]`
97
+ rootRef.current?.querySelector<HTMLElement>(selector)?.focus()
98
+ pendingFocusValue.current = null
99
+ })
100
+
101
+ const getFocusableChips = (): HTMLElement[] => [
102
+ ...(rootRef.current?.querySelectorAll<HTMLElement>(SELECTOR_CHIP_FOCUSABLE) ?? []),
103
+ ]
104
+
105
+ const emitSelection = (next: string[]) => {
106
+ if (!isControlled) {
107
+ setSelected(next)
108
+ }
109
+ onSelectionChange?.(next)
110
+ }
111
+
112
+ const updateSelection = (chipValue: string, nextSelected: boolean) => {
113
+ const isSelected = selectedValues.includes(chipValue)
114
+ if (nextSelected === isSelected) {
115
+ return
116
+ }
117
+
118
+ if (nextSelected) {
119
+ // Single selection mode keeps only the freshly selected chip.
120
+ emitSelection(selectionMode === 'single' ? [chipValue] : [...selectedValues, chipValue])
121
+ return
122
+ }
123
+
124
+ emitSelection(selectedValues.filter((item) => item !== chipValue))
125
+ }
126
+
127
+ const clearSelection = () => {
128
+ if (selectedValues.length > 0) {
129
+ emitSelection([])
130
+ }
131
+ }
132
+
133
+ const removeChip = (chipValue: string) => {
134
+ if (restoreFocusOnRemove) {
135
+ const chips = getFocusableChips()
136
+ const index = chips.findIndex((chip) => chip.dataset.coreuiChipValue === chipValue)
137
+ // Prefer the next chip, fall back to the previous one at the end.
138
+ const neighbor = chips[index + 1] ?? chips[index - 1] ?? null
139
+ pendingFocusValue.current = neighbor?.dataset.coreuiChipValue ?? null
140
+ }
141
+
142
+ emitSelection(selectedValues.filter((item) => item !== chipValue))
143
+ onRemoveChip?.(chipValue)
144
+ }
145
+
146
+ // Roving focus: arrow keys move between chips and Home/End jump to the edges,
147
+ // with no cycling. Returns true when the key was handled.
148
+ const handleKeyDown = (event: KeyboardEvent<HTMLElement>): boolean => {
149
+ const chip = (event.target as HTMLElement).closest<HTMLElement>(SELECTOR_CHIP_FOCUSABLE)
150
+ if (!chip) {
151
+ return false
152
+ }
153
+
154
+ const chips = getFocusableChips()
155
+ const index = chips.indexOf(chip)
156
+ // In RTL the visual direction is mirrored, so left/right swap.
157
+ const rtl = isRTL(rootRef.current)
158
+
159
+ switch (event.key) {
160
+ case 'ArrowLeft': {
161
+ event.preventDefault()
162
+ chips[rtl ? index + 1 : index - 1]?.focus()
163
+ return true
164
+ }
165
+
166
+ case 'ArrowRight': {
167
+ event.preventDefault()
168
+ chips[rtl ? index - 1 : index + 1]?.focus()
169
+ return true
170
+ }
171
+
172
+ case 'Home': {
173
+ event.preventDefault()
174
+ chips[0]?.focus()
175
+ return true
176
+ }
177
+
178
+ case 'End': {
179
+ event.preventDefault()
180
+ chips[chips.length - 1]?.focus()
181
+ return true
182
+ }
183
+
184
+ default: {
185
+ return false
186
+ }
187
+ }
188
+ }
189
+
190
+ const renderChips = (children: ReactNode): ReactNode =>
191
+ React.Children.map(children, (child, index) => {
192
+ if (!isValidElement<CChipProps>(child)) {
193
+ return child
194
+ }
195
+
196
+ const chipValue = resolveChipValue(child, index)
197
+ const childSelectable = child.props.selectable ?? selectable
198
+ const childFilter = child.props.filter ?? filter
199
+ const childRemovable = child.props.removable ?? removable
200
+ const isCoordinated = Boolean(childSelectable || childFilter)
201
+
202
+ return React.cloneElement(child, {
203
+ ariaRemoveLabel: child.props.ariaRemoveLabel ?? ariaRemoveLabel,
204
+ disabled: child.props.disabled ?? disabled,
205
+ filter: childFilter,
206
+ removable: childRemovable,
207
+ removeIcon: child.props.removeIcon ?? removeIcon,
208
+ selectable: childSelectable,
209
+ selectedIcon: child.props.selectedIcon ?? selectedIcon,
210
+ ...(isCoordinated && {
211
+ selected: selectedValues.includes(chipValue),
212
+ onSelectedChange: (
213
+ nextSelected: boolean,
214
+ event: MouseEvent<HTMLElement> | KeyboardEvent<HTMLElement>
215
+ ) => {
216
+ updateSelection(chipValue, nextSelected)
217
+ child.props.onSelectedChange?.(nextSelected, event)
218
+ },
219
+ }),
220
+ ...(childRemovable && {
221
+ onRemove: (event: MouseEvent<HTMLButtonElement> | KeyboardEvent<HTMLElement>) => {
222
+ removeChip(chipValue)
223
+ child.props.onRemove?.(event)
224
+ },
225
+ }),
226
+ })
227
+ })
228
+
229
+ // Build CChip elements from data, then run them through the same transform —
230
+ // used by CChipSet's `chips` prop and by CChipInput's value list.
231
+ const renderChipsFromData = (items: (string | CChipSetItem)[]): ReactNode =>
232
+ renderChips(items.map((item) => buildChip(item)))
233
+
234
+ return {
235
+ rootRef,
236
+ selectedValues,
237
+ clearSelection,
238
+ getFocusableChips,
239
+ handleKeyDown,
240
+ renderChips,
241
+ renderChipsFromData,
242
+ }
243
+ }
@@ -0,0 +1,19 @@
1
+ import { ReactElement } from 'react'
2
+
3
+ import type { CChipProps } from '../chip/CChip'
4
+
5
+ /**
6
+ * Resolves the value used to identify a chip in a `CChipSet`: its explicit
7
+ * `value` prop, its text children, or the render index as a last resort.
8
+ */
9
+ export const resolveChipValue = (chip: ReactElement<CChipProps>, index: number): string => {
10
+ if (chip.props.value !== undefined) {
11
+ return chip.props.value
12
+ }
13
+
14
+ if (typeof chip.props.children === 'string') {
15
+ return chip.props.children
16
+ }
17
+
18
+ return String(index)
19
+ }