@boxcustodia/library 2.0.0-alpha.10 → 2.0.0-alpha.12

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 (352) hide show
  1. package/dist/index.cjs.js +70 -70
  2. package/dist/index.css +2 -0
  3. package/dist/index.d.ts +420 -272
  4. package/dist/index.es.js +34448 -27816
  5. package/dist/theme.css +1 -1
  6. package/package.json +11 -6
  7. package/src/__doc__/Changelog.mdx +6 -0
  8. package/src/__doc__/Components.mdx +73 -0
  9. package/src/__doc__/Examples.tsx +69 -0
  10. package/src/__doc__/Icons.mdx +41 -0
  11. package/src/__doc__/Intro.mdx +138 -0
  12. package/src/__doc__/MCP.mdx +71 -0
  13. package/src/__doc__/Migration.mdx +475 -0
  14. package/src/__doc__/Theme.mdx +132 -0
  15. package/src/__doc__/Types.mdx +252 -0
  16. package/src/components/alert/alert.stories.tsx +142 -0
  17. package/src/components/alert/alert.tsx +109 -0
  18. package/src/components/alert/index.ts +7 -0
  19. package/src/components/alert-dialog/alert-dialog.stories.tsx +173 -0
  20. package/src/components/alert-dialog/alert-dialog.test.tsx +49 -0
  21. package/src/components/alert-dialog/alert-dialog.tsx +265 -0
  22. package/src/components/alert-dialog/index.ts +1 -0
  23. package/src/components/auto-complete/auto-complete-primitives.tsx +155 -0
  24. package/src/components/auto-complete/auto-complete.stories.tsx +241 -0
  25. package/src/components/auto-complete/auto-complete.tsx +82 -0
  26. package/src/components/auto-complete/index.ts +2 -0
  27. package/src/components/avatar/avatar.stories.tsx +84 -0
  28. package/src/components/avatar/avatar.test.tsx +61 -0
  29. package/src/components/avatar/avatar.tsx +104 -0
  30. package/src/components/avatar/index.ts +1 -0
  31. package/src/components/background-image/background-image.stories.tsx +21 -0
  32. package/src/components/background-image/background-image.test.tsx +29 -0
  33. package/src/components/background-image/background-image.tsx +23 -0
  34. package/src/components/background-image/index.ts +1 -0
  35. package/src/components/button/button.stories.tsx +396 -0
  36. package/src/components/button/button.test.tsx +58 -0
  37. package/src/components/button/button.tsx +31 -0
  38. package/src/components/button/button.variants.ts +44 -0
  39. package/src/components/button/components/base-button.tsx +86 -0
  40. package/src/components/button/components/loader-overlay.tsx +21 -0
  41. package/src/components/button/components/loading-icon.tsx +47 -0
  42. package/src/components/button/index.ts +3 -0
  43. package/src/components/calendar/calendar.model.ts +86 -0
  44. package/src/components/calendar/calendar.stories.tsx +155 -0
  45. package/src/components/calendar/calendar.test.tsx +12 -0
  46. package/src/components/calendar/calendar.tsx +185 -0
  47. package/src/components/calendar/components/calendar-navigation.tsx +141 -0
  48. package/src/components/calendar/components/day.tsx +61 -0
  49. package/src/components/calendar/components/decade-view.tsx +45 -0
  50. package/src/components/calendar/components/index.ts +6 -0
  51. package/src/components/calendar/components/month-view.tsx +58 -0
  52. package/src/components/calendar/components/week-days.tsx +27 -0
  53. package/src/components/calendar/components/year-view.tsx +29 -0
  54. package/src/components/calendar/hooks/index.ts +4 -0
  55. package/src/components/calendar/hooks/use-calendar-navigation.ts +79 -0
  56. package/src/components/calendar/hooks/use-calendar.ts +90 -0
  57. package/src/components/calendar/hooks/use-multiple-calendar.ts +34 -0
  58. package/src/components/calendar/hooks/use-range-calendar.ts +91 -0
  59. package/src/components/calendar/hooks/use-single-calendar.ts +18 -0
  60. package/src/components/calendar/index.ts +1 -0
  61. package/src/components/calendar/utils/typeguards.ts +7 -0
  62. package/src/components/card/card.stories.tsx +116 -0
  63. package/src/components/card/card.tsx +74 -0
  64. package/src/components/card/index.ts +1 -0
  65. package/src/components/center/center.stories.tsx +81 -0
  66. package/src/components/center/center.tsx +24 -0
  67. package/src/components/center/index.ts +1 -0
  68. package/src/components/checkbox/checkbox.stories.tsx +307 -0
  69. package/src/components/checkbox/checkbox.tsx +273 -0
  70. package/src/components/checkbox/index.ts +1 -0
  71. package/src/components/checkbox-group/checkbox-group.stories.tsx +104 -0
  72. package/src/components/checkbox-group/checkbox-group.tsx +16 -0
  73. package/src/components/checkbox-group/index.ts +1 -0
  74. package/src/components/combobox/combobox.stories.tsx +339 -0
  75. package/src/components/combobox/combobox.tsx +892 -0
  76. package/src/components/combobox/index.ts +1 -0
  77. package/src/components/date-picker/date-input.stories.tsx +158 -0
  78. package/src/components/date-picker/date-input.tsx +163 -0
  79. package/src/components/date-picker/date-picker.model.ts +90 -0
  80. package/src/components/date-picker/date-picker.stories.tsx +200 -0
  81. package/src/components/date-picker/date-picker.test.tsx +23 -0
  82. package/src/components/date-picker/date-picker.tsx +298 -0
  83. package/src/components/date-picker/date-picker.utils.ts +260 -0
  84. package/src/components/date-picker/index.ts +3 -0
  85. package/src/components/date-picker/use-date-input-popover.ts +48 -0
  86. package/src/components/date-picker/use-date-input.ts +125 -0
  87. package/src/components/dialog/dialog.stories.tsx +171 -0
  88. package/src/components/dialog/dialog.test.tsx +68 -0
  89. package/src/components/dialog/dialog.tsx +277 -0
  90. package/src/components/dialog/index.ts +1 -0
  91. package/src/components/divider/divider.stories.tsx +70 -0
  92. package/src/components/divider/divider.test.tsx +22 -0
  93. package/src/components/divider/divider.tsx +23 -0
  94. package/src/components/divider/index.ts +1 -0
  95. package/src/components/dropzone/dropzone.stories.tsx +210 -0
  96. package/src/components/dropzone/dropzone.tsx +154 -0
  97. package/src/components/dropzone/file-types.ts +64 -0
  98. package/src/components/dropzone/index.ts +3 -0
  99. package/src/components/dropzone/upload-primitives.tsx +310 -0
  100. package/src/components/dropzone/use-dropzone.ts +122 -0
  101. package/src/components/empty-state/empty-state.stories.tsx +56 -0
  102. package/src/components/empty-state/empty-state.tsx +39 -0
  103. package/src/components/empty-state/index.ts +1 -0
  104. package/src/components/field/field.stories.tsx +223 -0
  105. package/src/components/field/field.tsx +229 -0
  106. package/src/components/field/index.ts +1 -0
  107. package/src/components/form/form.stories.tsx +594 -0
  108. package/src/components/form/form.tsx +30 -0
  109. package/src/components/form/index.ts +1 -0
  110. package/src/components/heading/heading.stories.tsx +74 -0
  111. package/src/components/heading/heading.tsx +28 -0
  112. package/src/components/heading/heading.variants.ts +27 -0
  113. package/src/components/heading/index.ts +1 -0
  114. package/src/components/index.ts +46 -0
  115. package/src/components/input/index.ts +1 -0
  116. package/src/components/input/input.stories.tsx +104 -0
  117. package/src/components/input/input.tsx +75 -0
  118. package/src/components/kbd/index.ts +1 -0
  119. package/src/components/kbd/kbd.stories.tsx +40 -0
  120. package/src/components/kbd/kbd.tsx +31 -0
  121. package/src/components/kbd/kbd.variants.ts +26 -0
  122. package/src/components/label/index.ts +1 -0
  123. package/src/components/label/label.stories.tsx +68 -0
  124. package/src/components/label/label.test.tsx +61 -0
  125. package/src/components/label/label.tsx +62 -0
  126. package/src/components/loader/index.ts +1 -0
  127. package/src/components/loader/loader.stories.tsx +60 -0
  128. package/src/components/loader/loader.test.tsx +26 -0
  129. package/src/components/loader/loader.tsx +60 -0
  130. package/src/components/menu/index.ts +2 -0
  131. package/src/components/menu/menu-primitives.tsx +248 -0
  132. package/src/components/menu/menu.stories.tsx +203 -0
  133. package/src/components/menu/menu.tsx +100 -0
  134. package/src/components/menu/util/render-menu-item.tsx +54 -0
  135. package/src/components/multi-select/hooks/use-multi-select.ts +66 -0
  136. package/src/components/multi-select/index.ts +1 -0
  137. package/src/components/multi-select/multi-select.stories.tsx +294 -0
  138. package/src/components/multi-select/multi-select.tsx +300 -0
  139. package/src/components/multi-select/multi-select.variants.ts +22 -0
  140. package/src/components/number-input/index.ts +1 -0
  141. package/src/components/number-input/number-input.stories.tsx +209 -0
  142. package/src/components/number-input/number-input.test.tsx +87 -0
  143. package/src/components/number-input/number-input.tsx +230 -0
  144. package/src/components/pagination/components/pagination-option.tsx +27 -0
  145. package/src/components/pagination/index.ts +1 -0
  146. package/src/components/pagination/pagination.stories.tsx +80 -0
  147. package/src/components/pagination/pagination.test.tsx +76 -0
  148. package/src/components/pagination/pagination.tsx +102 -0
  149. package/src/components/password/index.ts +1 -0
  150. package/src/components/password/password.stories.tsx +104 -0
  151. package/src/components/password/password.tsx +71 -0
  152. package/src/components/popover/index.ts +1 -0
  153. package/src/components/popover/popover.stories.tsx +213 -0
  154. package/src/components/popover/popover.tsx +203 -0
  155. package/src/components/progress/index.ts +1 -0
  156. package/src/components/progress/progress.stories.tsx +124 -0
  157. package/src/components/progress/progress.test.tsx +25 -0
  158. package/src/components/progress/progress.tsx +124 -0
  159. package/src/components/scroll-area/index.ts +1 -0
  160. package/src/components/scroll-area/scroll-area.stories.tsx +166 -0
  161. package/src/components/scroll-area/scroll-area.tsx +64 -0
  162. package/src/components/select/index.ts +1 -0
  163. package/src/components/select/select.stories.tsx +253 -0
  164. package/src/components/select/select.tsx +430 -0
  165. package/src/components/show/index.ts +1 -0
  166. package/src/components/show/show.stories.tsx +197 -0
  167. package/src/components/show/show.test.tsx +41 -0
  168. package/src/components/show/show.tsx +16 -0
  169. package/src/components/skeleton/index.ts +1 -0
  170. package/src/components/skeleton/skeleton.stories.tsx +36 -0
  171. package/src/components/skeleton/skeleton.test.tsx +14 -0
  172. package/src/components/skeleton/skeleton.tsx +15 -0
  173. package/src/components/stack/index.ts +1 -0
  174. package/src/components/stack/stack.stories.tsx +194 -0
  175. package/src/components/stack/stack.tsx +52 -0
  176. package/src/components/stepper/Stepper.tsx +190 -0
  177. package/src/components/stepper/context/stepper-context.tsx +11 -0
  178. package/src/components/stepper/index.ts +1 -0
  179. package/src/components/stepper/stepper.stories.tsx +130 -0
  180. package/src/components/stepper/stepper.test.tsx +91 -0
  181. package/src/components/switch/index.ts +1 -0
  182. package/src/components/switch/switch.stories.tsx +122 -0
  183. package/src/components/switch/switch.test.tsx +30 -0
  184. package/src/components/switch/switch.tsx +86 -0
  185. package/src/components/table/index.ts +3 -0
  186. package/src/components/table/table-primitives.tsx +122 -0
  187. package/src/components/table/table.model.ts +20 -0
  188. package/src/components/table/table.stories.tsx +169 -0
  189. package/src/components/table/table.test.tsx +91 -0
  190. package/src/components/table/table.tsx +109 -0
  191. package/src/components/table-pagination/index.ts +2 -0
  192. package/src/components/table-pagination/table-pagination.model.ts +2 -0
  193. package/src/components/table-pagination/table-pagination.stories.tsx +23 -0
  194. package/src/components/table-pagination/table-pagination.test.tsx +32 -0
  195. package/src/components/table-pagination/table-pagination.tsx +108 -0
  196. package/src/components/tabs/context/tabs-context.tsx +14 -0
  197. package/src/components/tabs/index.ts +1 -0
  198. package/src/components/tabs/tabs.stories.tsx +182 -0
  199. package/src/components/tabs/tabs.test.tsx +61 -0
  200. package/src/components/tabs/tabs.tsx +175 -0
  201. package/src/components/tag/index.ts +2 -0
  202. package/src/components/tag/tag.stories.tsx +170 -0
  203. package/src/components/tag/tag.test.tsx +18 -0
  204. package/src/components/tag/tag.tsx +99 -0
  205. package/src/components/tag/tag.variants.ts +31 -0
  206. package/src/components/textarea/index.ts +1 -0
  207. package/src/components/textarea/textarea.stories.tsx +73 -0
  208. package/src/components/textarea/textarea.tsx +105 -0
  209. package/src/components/timeline/index.ts +1 -0
  210. package/src/components/timeline/timeline-status.ts +5 -0
  211. package/src/components/timeline/timeline.stories.tsx +84 -0
  212. package/src/components/timeline/timeline.tsx +147 -0
  213. package/src/components/toast/index.ts +1 -0
  214. package/src/components/toast/toast.stories.tsx +392 -0
  215. package/src/components/toast/toast.test.tsx +50 -0
  216. package/src/components/toast/toast.tsx +411 -0
  217. package/src/components/tooltip/index.ts +1 -0
  218. package/src/components/tooltip/tooltip.stories.tsx +226 -0
  219. package/src/components/tooltip/tooltip.test.tsx +46 -0
  220. package/src/components/tooltip/tooltip.tsx +171 -0
  221. package/src/components/tree/hooks/use-controllable-tree-state.ts +80 -0
  222. package/src/components/tree/index.ts +2 -0
  223. package/src/components/tree/tree-primitives.tsx +126 -0
  224. package/src/components/tree/tree.stories.tsx +468 -0
  225. package/src/components/tree/tree.tsx +42 -0
  226. package/src/hooks/index.ts +26 -0
  227. package/src/hooks/useArray/__doc__/useArray.stories.tsx +100 -0
  228. package/src/hooks/useArray/__test__/useArray.test.tsx +88 -0
  229. package/src/hooks/useArray/index.ts +1 -0
  230. package/src/hooks/useArray/useArray.ts +76 -0
  231. package/src/hooks/useAsync/__doc__/useAsync.stories.tsx +149 -0
  232. package/src/hooks/useAsync/__test__/useAsync.test.tsx +68 -0
  233. package/src/hooks/useAsync/index.ts +1 -0
  234. package/src/hooks/useAsync/useAsync.ts +58 -0
  235. package/src/hooks/useClickOutside/__doc__/useClickOutside.stories.tsx +40 -0
  236. package/src/hooks/useClickOutside/__test__/useClickOutside.test.tsx +33 -0
  237. package/src/hooks/useClickOutside/index.ts +1 -0
  238. package/src/hooks/useClickOutside/useClickOutside.ts +26 -0
  239. package/src/hooks/useClipboard/__doc__/useClipboard.stories.tsx +45 -0
  240. package/src/hooks/useClipboard/__test__/useClipboard.test.tsx +19 -0
  241. package/src/hooks/useClipboard/index.ts +1 -0
  242. package/src/hooks/useClipboard/useClipboard.tsx +28 -0
  243. package/src/hooks/useDebounceCallback/__doc__/useDebouncedCallback.stories.tsx +84 -0
  244. package/src/hooks/useDebounceCallback/index.ts +1 -0
  245. package/src/hooks/useDebounceCallback/useDebouncedCallback.ts +23 -0
  246. package/src/hooks/useDebounceValue/__doc__/useDebouncedValue.stories.tsx +75 -0
  247. package/src/hooks/useDebounceValue/index.ts +1 -0
  248. package/src/hooks/useDebounceValue/useDebouncedValue.ts +17 -0
  249. package/src/hooks/useDisclosure/__doc__/useDisclosure.stories.tsx +39 -0
  250. package/src/hooks/useDisclosure/__test__/useDisclosure.test.ts +43 -0
  251. package/src/hooks/useDisclosure/index.ts +1 -0
  252. package/src/hooks/useDisclosure/useDisclosure.ts +37 -0
  253. package/src/hooks/useDocumentTitle/__doc__/useDocumentTitle.stories.tsx +26 -0
  254. package/src/hooks/useDocumentTitle/index.ts +1 -0
  255. package/src/hooks/useDocumentTitle/useDocumentTitle.tsx +11 -0
  256. package/src/hooks/useEventListener/__doc__/useEventListener.stories.tsx +28 -0
  257. package/src/hooks/useEventListener/__test__/useEventListener.test.tsx +26 -0
  258. package/src/hooks/useEventListener/index.ts +1 -0
  259. package/src/hooks/useEventListener/useEventListener.ts +25 -0
  260. package/src/hooks/useFocusTrap/__doc__/useFocusTrap.stories.tsx +37 -0
  261. package/src/hooks/useFocusTrap/index.ts +1 -0
  262. package/src/hooks/useFocusTrap/scopeTab.ts +38 -0
  263. package/src/hooks/useFocusTrap/tabbable.ts +70 -0
  264. package/src/hooks/useFocusTrap/useFocusTrap.ts +78 -0
  265. package/src/hooks/useHotkey/__docs__/useHotkey.stories.tsx +116 -0
  266. package/src/hooks/useHotkey/__test__/useHotkey.test.tsx +105 -0
  267. package/src/hooks/useHotkey/__utils__/create-hotkey-listener.ts +25 -0
  268. package/src/hooks/useHotkey/__utils__/index.ts +3 -0
  269. package/src/hooks/useHotkey/__utils__/is-input-field.ts +14 -0
  270. package/src/hooks/useHotkey/__utils__/match-key-modifiers.ts +25 -0
  271. package/src/hooks/useHotkey/index.ts +1 -0
  272. package/src/hooks/useHotkey/useHotkey.ts +34 -0
  273. package/src/hooks/useHover/__doc__/useHover.stories.tsx +41 -0
  274. package/src/hooks/useHover/__test__/useHover.test.tsx +45 -0
  275. package/src/hooks/useHover/index.ts +1 -0
  276. package/src/hooks/useHover/useHover.tsx +40 -0
  277. package/src/hooks/useIsVisible/__doc__/useIsVisible.stories.tsx +60 -0
  278. package/src/hooks/useIsVisible/index.ts +1 -0
  279. package/src/hooks/useIsVisible/useIsVisible.tsx +50 -0
  280. package/src/hooks/useLocalStorage/__doc__/useLocalStorage.stories.tsx +86 -0
  281. package/src/hooks/useLocalStorage/__test__/useLocalStorage.test.ts +85 -0
  282. package/src/hooks/useLocalStorage/index.ts +1 -0
  283. package/src/hooks/useLocalStorage/useLocalStorage.ts +57 -0
  284. package/src/hooks/useMediaQuery/__doc__/useMediaQuery.stories.tsx +39 -0
  285. package/src/hooks/useMediaQuery/index.ts +1 -0
  286. package/src/hooks/useMediaQuery/useMediaQuery.ts +22 -0
  287. package/src/hooks/useMemoizedFn/index.ts +1 -0
  288. package/src/hooks/useMemoizedFn/useMemoizedFn.ts +32 -0
  289. package/src/hooks/useMutation/__doc__/useMutation.stories.tsx +111 -0
  290. package/src/hooks/useMutation/__test__/useMutation.test.tsx +83 -0
  291. package/src/hooks/useMutation/index.ts +1 -0
  292. package/src/hooks/useMutation/useMutation.tsx +60 -0
  293. package/src/hooks/useObject/__doc__/useObject.stories.tsx +119 -0
  294. package/src/hooks/useObject/__test__/useObject.test.tsx +87 -0
  295. package/src/hooks/useObject/index.ts +1 -0
  296. package/src/hooks/useObject/useObject.tsx +48 -0
  297. package/src/hooks/usePagination/__doc__/usePagination.stories.tsx +72 -0
  298. package/src/hooks/usePagination/__test__/usePagination.test.tsx +98 -0
  299. package/src/hooks/usePagination/index.ts +2 -0
  300. package/src/hooks/usePagination/usePagination.tsx +74 -0
  301. package/src/hooks/usePortal/__doc__/usePortal.stories.tsx +19 -0
  302. package/src/hooks/usePortal/__test__/usePortal.test.tsx +20 -0
  303. package/src/hooks/usePortal/index.ts +1 -0
  304. package/src/hooks/usePortal/usePortal.ts +40 -0
  305. package/src/hooks/usePreventCloseWindow/__doc__/usePreventCloseWindow.stories.tsx +32 -0
  306. package/src/hooks/usePreventCloseWindow/index.ts +1 -0
  307. package/src/hooks/usePreventCloseWindow/usePreventCloseWindow.ts +33 -0
  308. package/src/hooks/useRangePagination/__test__/useRangePagination.test.tsx +63 -0
  309. package/src/hooks/useRangePagination/index.ts +2 -0
  310. package/src/hooks/useRangePagination/useRangePagination.tsx +72 -0
  311. package/src/hooks/useSelection/__doc__/useSelection.stories.tsx +140 -0
  312. package/src/hooks/useSelection/__test__/useSelection.test.tsx +57 -0
  313. package/src/hooks/useSelection/index.ts +1 -0
  314. package/src/hooks/useSelection/useSelection.ts +121 -0
  315. package/src/hooks/useStep/__doc__/useStep.stories.tsx +98 -0
  316. package/src/hooks/useStep/__test__/useStep.test.ts +51 -0
  317. package/src/hooks/useStep/index.ts +1 -0
  318. package/src/hooks/useStep/useStep.ts +57 -0
  319. package/src/hooks/useToggle/__doc__/useToggle.stories.tsx +25 -0
  320. package/src/hooks/useToggle/__test__/useToggle.test.tsx +43 -0
  321. package/src/hooks/useToggle/index.ts +1 -0
  322. package/src/hooks/useToggle/useToggle.ts +16 -0
  323. package/src/index.ts +6 -0
  324. package/src/lib/cn.ts +8 -0
  325. package/src/lib/index.ts +1 -0
  326. package/src/models/Generic.model.ts +67 -0
  327. package/src/models/index.ts +1 -0
  328. package/src/providers/index.ts +2 -0
  329. package/src/providers/library-provider.tsx +44 -0
  330. package/src/providers/theme/ThemeProvider.tsx +25 -0
  331. package/src/providers/theme/index.ts +3 -0
  332. package/src/providers/theme/types.ts +11 -0
  333. package/src/providers/theme/useThemeProps.ts +25 -0
  334. package/src/stores/theme.store.ts +31 -0
  335. package/src/styles/components.css +4 -0
  336. package/src/styles/index.css +2 -0
  337. package/src/styles/library.css +2 -0
  338. package/src/styles/theme.css +232 -0
  339. package/src/utils/dates/parseDateRange.utility.ts +39 -0
  340. package/src/utils/form.tsx +91 -0
  341. package/src/utils/functions/createSafeContext.ts +17 -0
  342. package/src/utils/functions/ensureReactElement.tsx +30 -0
  343. package/src/utils/functions/getFormData.ts +19 -0
  344. package/src/utils/functions/index.ts +4 -0
  345. package/src/utils/functions/mergeRefs.ts +18 -0
  346. package/src/utils/index.ts +3 -0
  347. package/src/utils/strings/extractInitials.utility.ts +10 -0
  348. package/src/utils/strings/index.ts +1 -0
  349. package/src/utils/tests/click.ts +3 -0
  350. package/src/utils/tests/index.ts +2 -0
  351. package/src/utils/tests/keyboard.ts +21 -0
  352. package/src/utils/tests/type.ts +6 -0
@@ -0,0 +1,892 @@
1
+ import { Combobox as ComboboxPrimitive } from "@base-ui/react/combobox";
2
+ import { ChevronsUpDownIcon, XIcon } from "lucide-react";
3
+ import * as React from "react";
4
+ import { cn } from "../../lib";
5
+ import { Input, inputBaseClasses } from "../input";
6
+ import { ScrollArea } from "../scroll-area";
7
+
8
+ // ─── Context ─────────────────────────────────────────────────────────────────
9
+
10
+ export const ComboboxContext: React.Context<{
11
+ chipsRef: React.RefObject<Element | null> | null;
12
+ multiple: boolean;
13
+ }> = React.createContext<{
14
+ chipsRef: React.RefObject<Element | null> | null;
15
+ multiple: boolean;
16
+ }>({
17
+ chipsRef: null,
18
+ multiple: false,
19
+ });
20
+
21
+ // ─── Primitives ───────────────────────────────────────────────────────────────
22
+
23
+ export function ComboboxRoot<
24
+ Value,
25
+ Multiple extends boolean | undefined = false,
26
+ >({
27
+ onChange,
28
+ ...props
29
+ }: Omit<ComboboxPrimitive.Root.Props<Value, Multiple>, "onValueChange"> & {
30
+ onChange?: ComboboxPrimitive.Root.Props<Value, Multiple>["onValueChange"];
31
+ }): React.ReactElement {
32
+ const chipsRef = React.useRef<Element | null>(null);
33
+ return (
34
+ <ComboboxContext.Provider value={{ chipsRef, multiple: !!props.multiple }}>
35
+ <ComboboxPrimitive.Root onValueChange={onChange} {...props} />
36
+ </ComboboxContext.Provider>
37
+ );
38
+ }
39
+
40
+ export function ComboboxChipsInput({
41
+ className,
42
+ ...props
43
+ }: Omit<ComboboxPrimitive.Input.Props, "size"> & {
44
+ ref?: React.Ref<HTMLInputElement>;
45
+ }): React.ReactElement {
46
+ return (
47
+ <ComboboxPrimitive.Input
48
+ className={cn(
49
+ "min-w-12 flex-1 text-base outline-none placeholder:text-muted-foreground sm:text-sm [[data-slot=combobox-chip]+&]:ps-0.5",
50
+ className,
51
+ )}
52
+ data-slot="combobox-chips-input"
53
+ {...props}
54
+ />
55
+ );
56
+ }
57
+
58
+ export function ComboboxInput({
59
+ className,
60
+ showTrigger = true,
61
+ showClear = false,
62
+ startAddon,
63
+ triggerProps,
64
+ clearProps,
65
+ ...props
66
+ }: Omit<ComboboxPrimitive.Input.Props, "size"> & {
67
+ showTrigger?: boolean;
68
+ showClear?: boolean;
69
+ startAddon?: React.ReactNode;
70
+ ref?: React.Ref<HTMLInputElement>;
71
+ triggerProps?: ComboboxPrimitive.Trigger.Props;
72
+ clearProps?: ComboboxPrimitive.Clear.Props;
73
+ }): React.ReactElement {
74
+ return (
75
+ <ComboboxPrimitive.InputGroup
76
+ className="relative not-has-[>*.w-full]:w-fit w-full text-foreground has-disabled:opacity-64"
77
+ data-slot="combobox-input-group"
78
+ >
79
+ {startAddon && (
80
+ <div
81
+ aria-hidden="true"
82
+ className="pointer-events-none absolute inset-y-0 start-px z-10 flex items-center ps-[calc(--spacing(3)-1px)] opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:-mx-0.5"
83
+ data-slot="combobox-start-addon"
84
+ >
85
+ {startAddon}
86
+ </div>
87
+ )}
88
+ <ComboboxPrimitive.Input
89
+ className={cn(
90
+ startAddon &&
91
+ "*:data-[slot=combobox-input]:ps-[calc(--spacing(8.5)-1px)] sm:*:data-[slot=combobox-input]:ps-[calc(--spacing(8)-1px)]",
92
+ "pr-6",
93
+ className,
94
+ )}
95
+ data-slot="combobox-input"
96
+ render={(props) => (
97
+ <Input
98
+ className="has-disabled:opacity-100"
99
+ nativeInput
100
+ {...props}
101
+ onChange={(_, event) => props?.onChange?.(event)}
102
+ />
103
+ )}
104
+ {...props}
105
+ />
106
+ <ComboboxEndAdornment
107
+ showTrigger={showTrigger}
108
+ showClear={showClear}
109
+ triggerProps={triggerProps}
110
+ clearProps={clearProps}
111
+ />
112
+ </ComboboxPrimitive.InputGroup>
113
+ );
114
+ }
115
+
116
+ export function ComboboxTrigger({
117
+ className,
118
+ children,
119
+ ...props
120
+ }: ComboboxPrimitive.Trigger.Props): React.ReactElement {
121
+ return (
122
+ <ComboboxPrimitive.Trigger
123
+ className={className}
124
+ data-slot="combobox-trigger"
125
+ {...props}
126
+ >
127
+ {children}
128
+ </ComboboxPrimitive.Trigger>
129
+ );
130
+ }
131
+
132
+ export function ComboboxSelectTrigger({
133
+ className,
134
+ placeholder,
135
+ children,
136
+ showClear,
137
+ clearProps,
138
+ ...props
139
+ }: ComboboxPrimitive.Trigger.Props & {
140
+ placeholder?: React.ReactNode;
141
+ showClear?: boolean;
142
+ clearProps?: ComboboxPrimitive.Clear.Props;
143
+ }): React.ReactElement {
144
+ const trigger = (
145
+ <ComboboxPrimitive.Trigger
146
+ className={cn(
147
+ "relative flex w-full items-center rounded-md border border-input bg-background px-3 py-2 pe-9 text-sm outline-none transition-shadow",
148
+ "focus-visible:border-ring",
149
+ "aria-invalid:border-error focus-visible:aria-invalid:ring-error/20",
150
+ "data-disabled:cursor-not-allowed data-disabled:opacity-50",
151
+ "data-placeholder:text-muted-foreground",
152
+ className,
153
+ )}
154
+ data-slot="combobox-select-trigger"
155
+ {...props}
156
+ >
157
+ {children ?? <ComboboxPrimitive.Value placeholder={placeholder} />}
158
+ </ComboboxPrimitive.Trigger>
159
+ );
160
+
161
+ if (!showClear) {
162
+ return (
163
+ <ComboboxPrimitive.Trigger
164
+ className={cn(
165
+ "relative flex w-full items-center rounded-md border border-input bg-background px-3 py-2 pe-9 text-sm outline-none transition-shadow",
166
+ "focus-visible:border-ring",
167
+ "aria-invalid:border-error focus-visible:aria-invalid:ring-error/20",
168
+ "data-disabled:cursor-not-allowed data-disabled:opacity-50",
169
+ "data-placeholder:text-muted-foreground",
170
+ className,
171
+ )}
172
+ data-slot="combobox-select-trigger"
173
+ {...props}
174
+ >
175
+ {children ?? (
176
+ <>
177
+ <ComboboxPrimitive.Value placeholder={placeholder} />
178
+ <div
179
+ aria-hidden="true"
180
+ className="pointer-events-none absolute end-0.5 top-1/2 inline-flex size-8 -translate-y-1/2 items-center justify-center text-muted-foreground opacity-80 sm:size-7 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4"
181
+ >
182
+ <ChevronsUpDownIcon />
183
+ </div>
184
+ </>
185
+ )}
186
+ </ComboboxPrimitive.Trigger>
187
+ );
188
+ }
189
+
190
+ return (
191
+ <div
192
+ className="relative w-full text-foreground has-disabled:opacity-64"
193
+ data-slot="combobox-select-trigger-group"
194
+ >
195
+ {trigger}
196
+ <ComboboxEndAdornment showTrigger showClear clearProps={clearProps} />
197
+ </div>
198
+ );
199
+ }
200
+
201
+ export function ComboboxSearchInput({
202
+ className,
203
+ ...props
204
+ }: Omit<ComboboxPrimitive.Input.Props, "size"> & {
205
+ ref?: React.Ref<HTMLInputElement>;
206
+ }): React.ReactElement {
207
+ return (
208
+ <div className="border-b p-2" data-slot="combobox-search-input-wrapper">
209
+ <ComboboxPrimitive.Input
210
+ className={cn(
211
+ "flex h-9 w-full rounded-md bg-transparent text-base placeholder:text-muted-foreground outline-none disabled:cursor-not-allowed disabled:opacity-64 sm:h-8 sm:text-sm",
212
+ className,
213
+ )}
214
+ autoComplete="off"
215
+ data-slot="combobox-search-input"
216
+ {...props}
217
+ />
218
+ </div>
219
+ );
220
+ }
221
+
222
+ export function ComboboxPopup({
223
+ className,
224
+ children,
225
+ side = "bottom",
226
+ sideOffset = 4,
227
+ alignOffset,
228
+ align = "start",
229
+ anchor: anchorProp,
230
+ portalProps,
231
+ ...props
232
+ }: ComboboxPrimitive.Popup.Props & {
233
+ align?: ComboboxPrimitive.Positioner.Props["align"];
234
+ sideOffset?: ComboboxPrimitive.Positioner.Props["sideOffset"];
235
+ alignOffset?: ComboboxPrimitive.Positioner.Props["alignOffset"];
236
+ side?: ComboboxPrimitive.Positioner.Props["side"];
237
+ anchor?: ComboboxPrimitive.Positioner.Props["anchor"];
238
+ portalProps?: ComboboxPrimitive.Portal.Props;
239
+ }): React.ReactElement {
240
+ const { chipsRef } = React.useContext(ComboboxContext);
241
+ const anchor = anchorProp ?? chipsRef;
242
+
243
+ return (
244
+ <ComboboxPrimitive.Portal {...portalProps}>
245
+ <ComboboxPrimitive.Positioner
246
+ align={align}
247
+ alignOffset={alignOffset}
248
+ anchor={anchor}
249
+ className="z-50 select-none"
250
+ data-slot="combobox-positioner"
251
+ side={side}
252
+ sideOffset={sideOffset}
253
+ >
254
+ <span
255
+ className={cn(
256
+ "relative flex max-h-full min-w-(--anchor-width) max-w-(--available-width) origin-(--transform-origin) rounded-lg border bg-popover not-dark:bg-clip-padding shadow-lg/5 transition-[scale,opacity] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
257
+ className,
258
+ )}
259
+ >
260
+ <ComboboxPrimitive.Popup
261
+ className="flex max-h-[min(var(--available-height),23rem)] flex-1 flex-col text-foreground"
262
+ data-slot="combobox-popup"
263
+ {...props}
264
+ >
265
+ {children}
266
+ </ComboboxPrimitive.Popup>
267
+ </span>
268
+ </ComboboxPrimitive.Positioner>
269
+ </ComboboxPrimitive.Portal>
270
+ );
271
+ }
272
+
273
+ export function ComboboxItem({
274
+ className,
275
+ children,
276
+ ...props
277
+ }: ComboboxPrimitive.Item.Props): React.ReactElement {
278
+ return (
279
+ <ComboboxPrimitive.Item
280
+ className={cn(
281
+ "flex justify-between min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-default grid-cols-[1rem_1fr] items-center gap-2 rounded-sm py-1 px-2 text-base outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
282
+ className,
283
+ )}
284
+ data-slot="combobox-item"
285
+ {...props}
286
+ >
287
+ {children}
288
+ <ComboboxPrimitive.ItemIndicator className="col-end-1">
289
+ <svg
290
+ aria-hidden="true"
291
+ fill="none"
292
+ height="24"
293
+ stroke="currentColor"
294
+ strokeLinecap="round"
295
+ strokeLinejoin="round"
296
+ strokeWidth="2"
297
+ viewBox="0 0 24 24"
298
+ width="24"
299
+ xmlns="http://www.w3.org/2000/svg"
300
+ >
301
+ <path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
302
+ </svg>
303
+ </ComboboxPrimitive.ItemIndicator>
304
+ </ComboboxPrimitive.Item>
305
+ );
306
+ }
307
+
308
+ export function ComboboxSeparator({
309
+ className,
310
+ ...props
311
+ }: ComboboxPrimitive.Separator.Props): React.ReactElement {
312
+ return (
313
+ <ComboboxPrimitive.Separator
314
+ className={cn("mx-2 my-1 h-px bg-border last:hidden", className)}
315
+ data-slot="combobox-separator"
316
+ {...props}
317
+ />
318
+ );
319
+ }
320
+
321
+ export function ComboboxGroup({
322
+ className,
323
+ ...props
324
+ }: ComboboxPrimitive.Group.Props): React.ReactElement {
325
+ return (
326
+ <ComboboxPrimitive.Group
327
+ className={cn("[[role=group]+&]:mt-1.5", className)}
328
+ data-slot="combobox-group"
329
+ {...props}
330
+ />
331
+ );
332
+ }
333
+
334
+ export function ComboboxGroupLabel({
335
+ className,
336
+ ...props
337
+ }: ComboboxPrimitive.GroupLabel.Props): React.ReactElement {
338
+ return (
339
+ <ComboboxPrimitive.GroupLabel
340
+ className={cn(
341
+ "px-2 py-1.5 font-medium text-muted-foreground text-xs",
342
+ className,
343
+ )}
344
+ data-slot="combobox-group-label"
345
+ {...props}
346
+ />
347
+ );
348
+ }
349
+
350
+ export function ComboboxEmpty({
351
+ className,
352
+ ...props
353
+ }: ComboboxPrimitive.Empty.Props): React.ReactElement {
354
+ return (
355
+ <ComboboxPrimitive.Empty
356
+ className={cn(
357
+ "not-empty:p-2 text-center text-base text-muted-foreground sm:text-sm",
358
+ className,
359
+ )}
360
+ data-slot="combobox-empty"
361
+ {...props}
362
+ />
363
+ );
364
+ }
365
+
366
+ export function ComboboxRow({
367
+ className,
368
+ ...props
369
+ }: ComboboxPrimitive.Row.Props): React.ReactElement {
370
+ return (
371
+ <ComboboxPrimitive.Row
372
+ className={className}
373
+ data-slot="combobox-row"
374
+ {...props}
375
+ />
376
+ );
377
+ }
378
+
379
+ export function ComboboxValue({
380
+ ...props
381
+ }: ComboboxPrimitive.Value.Props): React.ReactElement {
382
+ return <ComboboxPrimitive.Value data-slot="combobox-value" {...props} />;
383
+ }
384
+
385
+ export function ComboboxList({
386
+ className,
387
+ ...props
388
+ }: ComboboxPrimitive.List.Props): React.ReactElement {
389
+ return (
390
+ <ScrollArea scrollbarGutter scrollFade>
391
+ <ComboboxPrimitive.List
392
+ className={cn(
393
+ "not-empty:scroll-py-1 not-empty:px-1 not-empty:py-1 in-data-has-overflow-y:pe-3",
394
+ className,
395
+ )}
396
+ data-slot="combobox-list"
397
+ {...props}
398
+ />
399
+ </ScrollArea>
400
+ );
401
+ }
402
+
403
+ export function ComboboxClear({
404
+ className,
405
+ ...props
406
+ }: ComboboxPrimitive.Clear.Props): React.ReactElement {
407
+ return (
408
+ <ComboboxPrimitive.Clear
409
+ className={className}
410
+ data-slot="combobox-clear"
411
+ {...props}
412
+ />
413
+ );
414
+ }
415
+
416
+ const endBtnClass =
417
+ "absolute end-0.5 top-1/2 inline-flex size-8 shrink-0 -translate-y-1/2 cursor-pointer items-center justify-center rounded-md border border-transparent opacity-80 outline-none transition-opacity pointer-coarse:after:absolute pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:opacity-100 sm:size-7 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0";
418
+
419
+ function ComboboxEndAdornment({
420
+ showTrigger = true,
421
+ showClear = false,
422
+ triggerProps,
423
+ clearProps,
424
+ }: {
425
+ showTrigger?: boolean;
426
+ showClear?: boolean;
427
+ triggerProps?: ComboboxPrimitive.Trigger.Props;
428
+ clearProps?: ComboboxPrimitive.Clear.Props;
429
+ }): React.ReactElement | null {
430
+ if (!showTrigger && !showClear) return null;
431
+ return (
432
+ <div className="text-muted-foreground hover:text-foreground transition-colors">
433
+ {showTrigger && (
434
+ <ComboboxTrigger
435
+ className={cn(
436
+ endBtnClass,
437
+ "text-muted-foreground has-[+[data-slot=combobox-clear]]:hidden",
438
+ )}
439
+ {...triggerProps}
440
+ >
441
+ <ComboboxPrimitive.Icon data-slot="combobox-icon">
442
+ <ChevronsUpDownIcon />
443
+ </ComboboxPrimitive.Icon>
444
+ </ComboboxTrigger>
445
+ )}
446
+ {showClear && (
447
+ <ComboboxClear className={endBtnClass} {...clearProps}>
448
+ <XIcon />
449
+ </ComboboxClear>
450
+ )}
451
+ </div>
452
+ );
453
+ }
454
+
455
+ export function ComboboxStatus({
456
+ className,
457
+ ...props
458
+ }: ComboboxPrimitive.Status.Props): React.ReactElement {
459
+ return (
460
+ <ComboboxPrimitive.Status
461
+ className={cn(
462
+ "px-3 py-2 font-medium text-muted-foreground text-xs empty:m-0 empty:p-0",
463
+ className,
464
+ )}
465
+ data-slot="combobox-status"
466
+ {...props}
467
+ />
468
+ );
469
+ }
470
+
471
+ export function ComboboxCollection(
472
+ props: ComboboxPrimitive.Collection.Props,
473
+ ): React.ReactElement {
474
+ return (
475
+ <ComboboxPrimitive.Collection data-slot="combobox-collection" {...props} />
476
+ );
477
+ }
478
+
479
+ export function ComboboxChips({
480
+ className,
481
+ children,
482
+ startAddon,
483
+ ...props
484
+ }: ComboboxPrimitive.Chips.Props & {
485
+ startAddon?: React.ReactNode;
486
+ }): React.ReactElement {
487
+ const { chipsRef } = React.useContext(ComboboxContext);
488
+
489
+ return (
490
+ <ComboboxPrimitive.Chips
491
+ className={cn(
492
+ inputBaseClasses,
493
+ "relative inline-flex pr-6 w-full flex-wrap gap-1",
494
+ "placeholder:text-muted-foreground",
495
+ "focus-within:border-ring",
496
+ "has-aria-invalid:border-error focus-within:has-aria-invalid:ring-error/20",
497
+ "has-disabled:cursor-not-allowed has-disabled:opacity-50",
498
+ className,
499
+ )}
500
+ data-slot="combobox-chips"
501
+ ref={chipsRef as React.Ref<HTMLDivElement> | null}
502
+ {...props}
503
+ >
504
+ {startAddon && (
505
+ <div
506
+ aria-hidden="true"
507
+ className="flex shrink-0 items-center ps-2 opacity-80 has-[+[data-slot=combobox-chip]]:pe-2 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:-ms-0.5 [&_svg]:-me-1.5"
508
+ data-slot="combobox-start-addon"
509
+ >
510
+ {startAddon}
511
+ </div>
512
+ )}
513
+ {children}
514
+ </ComboboxPrimitive.Chips>
515
+ );
516
+ }
517
+
518
+ export function ComboboxChip({
519
+ children,
520
+ removeProps,
521
+ ...props
522
+ }: ComboboxPrimitive.Chip.Props & {
523
+ removeProps?: ComboboxPrimitive.ChipRemove.Props;
524
+ }): React.ReactElement {
525
+ return (
526
+ <ComboboxPrimitive.Chip
527
+ className="flex items-center pl-1 rounded-[calc(var(--radius-md)-1px)] bg-accent font-medium text-accent-foreground text-sm outline-none sm:text-xs/(--text-xs--line-height) [&_svg:not([class*='size-'])]:size-4 sm:[&_svg:not([class*='size-'])]:size-3.5"
528
+ data-slot="combobox-chip"
529
+ {...props}
530
+ >
531
+ {children}
532
+ <ComboboxChipRemove {...removeProps} />
533
+ </ComboboxPrimitive.Chip>
534
+ );
535
+ }
536
+
537
+ export function ComboboxChipRemove(
538
+ props: ComboboxPrimitive.ChipRemove.Props,
539
+ ): React.ReactElement {
540
+ return (
541
+ <ComboboxPrimitive.ChipRemove
542
+ aria-label="Remove"
543
+ className="h-full shrink-0 cursor-pointer px-1 opacity-80 hover:opacity-100 [&_svg:not([class*='size-'])]:size-4 sm:[&_svg:not([class*='size-'])]:size-3.5"
544
+ data-slot="combobox-chip-remove"
545
+ {...props}
546
+ >
547
+ <XIcon />
548
+ </ComboboxPrimitive.ChipRemove>
549
+ );
550
+ }
551
+
552
+ // ─── Chip Overflow ────────────────────────────────────────────────────────────
553
+
554
+ const CHIP_GAP = 4; // gap-1
555
+ const CHIPS_PADDING_X = 24; // px-3 × 2
556
+ const END_ADORNMENT = 36; // clear button + end-0.5 offset
557
+ const OVERFLOW_WIDTH = 68; // max "+99 más" at text-sm + px-1
558
+
559
+ function calculateChipVisible(
560
+ containerWidth: number,
561
+ chipWidths: number[],
562
+ ): number {
563
+ const available = containerWidth - CHIPS_PADDING_X - END_ADORNMENT;
564
+ let usedWidth = 0;
565
+ let count = 0;
566
+
567
+ for (let i = 0; i < chipWidths.length; i++) {
568
+ const chipWidth = chipWidths[i];
569
+ const willHaveOverflow = i < chipWidths.length - 1;
570
+
571
+ const projected =
572
+ usedWidth +
573
+ (i > 0 ? CHIP_GAP : 0) +
574
+ chipWidth +
575
+ (willHaveOverflow ? CHIP_GAP + OVERFLOW_WIDTH : 0);
576
+
577
+ if (projected > available) break;
578
+
579
+ usedWidth += (i > 0 ? CHIP_GAP : 0) + chipWidth;
580
+ count++;
581
+ }
582
+
583
+ return count;
584
+ }
585
+
586
+ function useChipOverflow(
587
+ itemCount: number,
588
+ containerRef: React.RefObject<Element | null> | null,
589
+ ): { visibleCount: number; isMeasuring: boolean } {
590
+ const [stableCount, setStableCount] = React.useState<{
591
+ items: number;
592
+ visible: number;
593
+ } | null>(null);
594
+ const chipWidthsRef = React.useRef<number[]>([]);
595
+
596
+ const isMeasuring = stableCount === null || stableCount.items !== itemCount;
597
+
598
+ React.useLayoutEffect(() => {
599
+ if (!isMeasuring) return;
600
+
601
+ const container = containerRef?.current as HTMLElement | null;
602
+ if (!container) return;
603
+
604
+ const chipEls = container.querySelectorAll('[data-slot="combobox-chip"]');
605
+ chipWidthsRef.current = Array.from(chipEls).map(
606
+ (el) => (el as HTMLElement).offsetWidth,
607
+ );
608
+
609
+ const visible = calculateChipVisible(
610
+ container.clientWidth,
611
+ chipWidthsRef.current,
612
+ );
613
+ setStableCount({ items: itemCount, visible });
614
+ }, [isMeasuring, itemCount, containerRef]);
615
+
616
+ React.useEffect(() => {
617
+ const container = containerRef?.current as HTMLElement | null;
618
+ if (!container) return;
619
+
620
+ const observer = new ResizeObserver(() => {
621
+ if (!chipWidthsRef.current.length) return;
622
+ const visible = calculateChipVisible(
623
+ container.clientWidth,
624
+ chipWidthsRef.current,
625
+ );
626
+ setStableCount((prev) => (prev ? { ...prev, visible } : null));
627
+ });
628
+
629
+ observer.observe(container);
630
+ return () => observer.disconnect();
631
+ }, [containerRef]);
632
+
633
+ return { visibleCount: stableCount?.visible ?? itemCount, isMeasuring };
634
+ }
635
+
636
+ function ComboboxMultipleChipsContent({
637
+ value,
638
+ getLabel,
639
+ getValue,
640
+ }: {
641
+ value: unknown[];
642
+ getLabel: (item: unknown) => string;
643
+ getValue: (item: unknown) => string;
644
+ }): React.ReactElement {
645
+ const { chipsRef } = React.useContext(ComboboxContext);
646
+ const { visibleCount, isMeasuring } = useChipOverflow(value.length, chipsRef);
647
+
648
+ const visibleItems = isMeasuring ? value : value.slice(0, visibleCount);
649
+ const hiddenCount = isMeasuring ? 0 : value.length - visibleCount;
650
+
651
+ return (
652
+ <>
653
+ {visibleItems.map((item) => (
654
+ <ComboboxChip key={getValue(item)} aria-label={getLabel(item)}>
655
+ {getLabel(item)}
656
+ </ComboboxChip>
657
+ ))}
658
+ {hiddenCount > 0 && (
659
+ <span className="flex items-center whitespace-nowrap px-1 text-sm text-muted-foreground">
660
+ +{hiddenCount} más
661
+ </span>
662
+ )}
663
+ </>
664
+ );
665
+ }
666
+
667
+ // ─── Composite ────────────────────────────────────────────────────────────────
668
+
669
+ function defaultGetLabel(item: unknown): string {
670
+ if (item == null) return "";
671
+ if (typeof item === "string" || typeof item === "number") return String(item);
672
+ if (typeof item === "object" && "label" in item)
673
+ return String((item as Record<string, unknown>).label);
674
+ return String(item);
675
+ }
676
+
677
+ function defaultGetValue(item: unknown): string {
678
+ if (item == null) return "";
679
+ if (typeof item === "string" || typeof item === "number") return String(item);
680
+ if (typeof item === "object" && "value" in item)
681
+ return String((item as Record<string, unknown>).value);
682
+ return String(item);
683
+ }
684
+
685
+ function ComboboxSingleTrigger({
686
+ placeholder,
687
+ showClear,
688
+ inputProps,
689
+ }: {
690
+ placeholder?: string;
691
+ showClear?: boolean;
692
+ inputProps?: React.ComponentProps<typeof ComboboxInput>;
693
+ }): React.ReactElement {
694
+ return (
695
+ <ComboboxInput
696
+ placeholder={placeholder}
697
+ showClear={showClear}
698
+ {...inputProps}
699
+ />
700
+ );
701
+ }
702
+
703
+ function ComboboxMultipleTrigger({
704
+ getLabel,
705
+ getValue,
706
+ chipsProps,
707
+ showClear,
708
+ clearProps,
709
+ }: {
710
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
711
+ getLabel: (item: any) => string;
712
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
713
+ getValue: (item: any) => string;
714
+ chipsProps?: React.ComponentProps<typeof ComboboxChips>;
715
+ showClear?: boolean;
716
+ clearProps?: ComboboxPrimitive.Clear.Props;
717
+ }): React.ReactElement {
718
+ return (
719
+ <ComboboxChips {...chipsProps}>
720
+ <ComboboxValue>
721
+ {(value: unknown[]) => (
722
+ <ComboboxMultipleChipsContent
723
+ value={value}
724
+ getLabel={getLabel}
725
+ getValue={getValue}
726
+ />
727
+ )}
728
+ </ComboboxValue>
729
+ <ComboboxEndAdornment
730
+ showTrigger={!showClear}
731
+ showClear={showClear}
732
+ clearProps={clearProps}
733
+ />
734
+ </ComboboxChips>
735
+ );
736
+ }
737
+
738
+ type ComboboxRootPropsAlias<V, M extends boolean | undefined = false> = Omit<
739
+ ComboboxPrimitive.Root.Props<V, M>,
740
+ "onValueChange"
741
+ > & {
742
+ onChange?: ComboboxPrimitive.Root.Props<V, M>["onValueChange"];
743
+ };
744
+
745
+ type ComboboxBaseProps<TItem = unknown> = Omit<
746
+ ComboboxRootPropsAlias<TItem, boolean>,
747
+ | "items"
748
+ | "itemToStringLabel"
749
+ | "itemToStringValue"
750
+ | "children"
751
+ | "multiple"
752
+ | "onChange"
753
+ | "value"
754
+ | "defaultValue"
755
+ > & {
756
+ items: readonly TItem[];
757
+ getLabel?: (item: TItem) => string;
758
+ getValue?: (item: TItem) => string;
759
+ renderItem?: (item: TItem) => React.ReactNode;
760
+ placeholder?: string;
761
+ emptyText?: string;
762
+ inputProps?: React.ComponentProps<typeof ComboboxInput>;
763
+ chipsProps?: React.ComponentProps<typeof ComboboxChips>;
764
+ chipsInputProps?: React.ComponentProps<typeof ComboboxChipsInput>;
765
+ popupProps?: React.ComponentProps<typeof ComboboxPopup>;
766
+ itemProps?: ComboboxPrimitive.Item.Props;
767
+ listProps?: Omit<ComboboxPrimitive.List.Props, "children">;
768
+ showClear?: boolean;
769
+ };
770
+
771
+ export type ComboboxProps<TItem = unknown> =
772
+ | (ComboboxBaseProps<TItem> & {
773
+ multiple?: false;
774
+ value?: TItem | null;
775
+ defaultValue?: TItem | null;
776
+ onChange?: (value: TItem | null) => void;
777
+ })
778
+ | (ComboboxBaseProps<TItem> & {
779
+ multiple: true;
780
+ value?: TItem[];
781
+ defaultValue?: TItem[];
782
+ onChange?: (value: TItem[]) => void;
783
+ });
784
+
785
+ /**
786
+ * Composite combobox for single and multiple selection.
787
+ *
788
+ * Items shaped as `{ label, value }` or plain strings/numbers work with no
789
+ * extra props. For other shapes, provide `getLabel` and/or `getValue`.
790
+ *
791
+ * In multiple mode, chips are fit dynamically based on the container width —
792
+ * overflowing items show as "+N más". The popup anchors to the chips container automatically.
793
+ *
794
+ * `renderItem` customizes the content **inside** each `ComboboxItem` (the
795
+ * checkmark indicator is always rendered by the item wrapper).
796
+ *
797
+ * Use `ComboboxRoot` + primitives directly when you need full structural
798
+ * control beyond what escape-hatch props offer.
799
+ */
800
+ export function Combobox<TItem = unknown>(
801
+ allProps: ComboboxProps<TItem>,
802
+ ): React.ReactElement {
803
+ // Cast internally — ComboboxProps discriminated union enforces correctness for consumers.
804
+ // The union can't be spread directly into ComboboxRoot because TypeScript can't resolve
805
+ // which branch is active at the call site.
806
+ const {
807
+ items,
808
+ getLabel: getLabelProp,
809
+ getValue: getValueProp,
810
+ renderItem,
811
+ placeholder,
812
+ emptyText = "Sin resultados.",
813
+ multiple,
814
+ onChange,
815
+ value,
816
+ defaultValue,
817
+ inputProps,
818
+ chipsProps,
819
+ chipsInputProps,
820
+ popupProps,
821
+ itemProps,
822
+ listProps,
823
+ showClear = true,
824
+ ...rest
825
+ } = allProps as ComboboxBaseProps<TItem> & {
826
+ multiple?: boolean;
827
+ onChange?: (value: any, eventDetails?: any) => void;
828
+ value?: any;
829
+ defaultValue?: any;
830
+ };
831
+
832
+ const getLabel: (item: TItem) => string = getLabelProp ?? defaultGetLabel;
833
+ const getValue: (item: TItem) => string = getValueProp ?? defaultGetValue;
834
+
835
+ return (
836
+ <ComboboxRoot
837
+ items={items}
838
+ itemToStringLabel={getLabel}
839
+ itemToStringValue={getValue}
840
+ multiple={multiple as boolean}
841
+ onChange={onChange}
842
+ value={value}
843
+ defaultValue={defaultValue}
844
+ autoHighlight
845
+ loopFocus
846
+ {...rest}
847
+ >
848
+ {multiple ? (
849
+ <ComboboxMultipleTrigger
850
+ getLabel={getLabel}
851
+ getValue={getValue}
852
+ chipsProps={chipsProps}
853
+ showClear={showClear}
854
+ />
855
+ ) : (
856
+ <ComboboxSingleTrigger
857
+ placeholder={placeholder}
858
+ showClear={showClear}
859
+ inputProps={inputProps}
860
+ />
861
+ )}
862
+ <ComboboxPopup {...popupProps}>
863
+ {multiple && (
864
+ <div
865
+ className="border-b p-2"
866
+ data-slot="combobox-chips-input-wrapper"
867
+ >
868
+ <ComboboxChipsInput
869
+ placeholder={placeholder}
870
+ {...chipsInputProps}
871
+ />
872
+ </div>
873
+ )}
874
+ <ComboboxEmpty>{emptyText}</ComboboxEmpty>
875
+ <ComboboxList {...listProps}>
876
+ {(item: TItem) => (
877
+ <ComboboxItem key={getValue(item)} value={item} {...itemProps}>
878
+ {renderItem ? renderItem(item) : getLabel(item)}
879
+ </ComboboxItem>
880
+ )}
881
+ </ComboboxList>
882
+ </ComboboxPopup>
883
+ </ComboboxRoot>
884
+ );
885
+ }
886
+
887
+ // ─── Exports ──────────────────────────────────────────────────────────────────
888
+
889
+ export const useComboboxFilter: typeof ComboboxPrimitive.useFilter =
890
+ ComboboxPrimitive.useFilter;
891
+
892
+ export { ComboboxPrimitive };