@k3-universe/react-kit 0.0.21 → 0.0.23

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 (427) hide show
  1. package/dist/index.js +10823 -8449
  2. package/dist/kit/builder/form/components/FormBuilder.d.ts.map +1 -1
  3. package/dist/kit/builder/form/components/fields/AutocompleteField.d.ts.map +1 -1
  4. package/dist/kit/builder/form/types.d.ts +1 -2
  5. package/dist/kit/builder/form/types.d.ts.map +1 -1
  6. package/dist/kit/components/autocomplete/Autocomplete.d.ts +6 -33
  7. package/dist/kit/components/autocomplete/Autocomplete.d.ts.map +1 -1
  8. package/dist/kit/themes/clean-slate.css +41 -47
  9. package/dist/kit/themes/default.css +42 -48
  10. package/dist/kit/themes/minimal-modern.css +41 -47
  11. package/dist/kit/themes/spotify.css +41 -47
  12. package/package.json +9 -7
  13. package/src/kit/builder/data-table/components/DataTable.tsx +4 -4
  14. package/src/kit/builder/form/components/FormBuilder.tsx +7 -1
  15. package/src/kit/builder/form/components/fields/AutocompleteField.tsx +1 -2
  16. package/src/kit/builder/form/types.ts +1 -2
  17. package/src/kit/components/autocomplete/Autocomplete.tsx +433 -686
  18. package/src/kit/themes/default.css +1 -1
  19. package/src/shadcn/ui/card.tsx +1 -1
  20. package/src/shadcn/ui/table.tsx +4 -4
  21. package/src/stories/kit/builder/Form.Autocomplete.stories.tsx +2 -4
  22. package/src/stories/kit/components/Autocomplete.stories.tsx +159 -4
  23. package/storybook-static/assets/Accordion.stories-KU4JBR8U.js +52 -0
  24. package/storybook-static/assets/AdminLayout-CPvVCwfY.js +53 -0
  25. package/storybook-static/assets/AdminLayout.Basic.stories-DkP2UVXe.js +4 -0
  26. package/storybook-static/assets/AdminLayout.Collapsible.stories-BuuVjtpW.js +4 -0
  27. package/storybook-static/assets/AdminLayout.Complex.stories-D-k4H0hJ.js +29 -0
  28. package/storybook-static/assets/AdminLayout.CustomSidebarHeaderComponent.stories-B_0IEDd4.js +9 -0
  29. package/storybook-static/assets/AdminLayout.CustomSidebarTitleAndIcon.stories-CvAeXUyA.js +4 -0
  30. package/storybook-static/assets/AdminLayout.HeaderSlots.stories-RBFHoSZK.js +7 -0
  31. package/storybook-static/assets/Alert.stories-DKxKtIc0.js +27 -0
  32. package/storybook-static/assets/AlertDialog.stories-BqTpZ_nG.js +43 -0
  33. package/storybook-static/assets/AspectRatio.stories-DPO9QQ5F.js +22 -0
  34. package/storybook-static/assets/Autocomplete-Cpg4CaJe.js +56 -0
  35. package/storybook-static/assets/Autocomplete.stories-CWj4G5fh.js +56 -0
  36. package/storybook-static/assets/Avatar.stories-DPhov_2g.js +12 -0
  37. package/storybook-static/assets/Badge.stories-DFKrRdXq.js +12 -0
  38. package/storybook-static/assets/Breadcrumb.stories-CTE6CZUC.js +25 -0
  39. package/storybook-static/assets/Button.stories-cbt2InL-.js +26 -0
  40. package/storybook-static/assets/Calendar.stories-DRhTw_43.js +3 -0
  41. package/storybook-static/assets/Card.stories-Isf6n_K3.js +15 -0
  42. package/storybook-static/assets/Carousel.stories-Cmg0I3fR.js +15 -0
  43. package/storybook-static/assets/Chart.stories-aQ-fNijT.js +126 -0
  44. package/storybook-static/assets/Checkbox.stories-B7YMXPDc.js +12 -0
  45. package/storybook-static/assets/Collapsible.stories-BUzl17ZZ.js +18 -0
  46. package/storybook-static/assets/Combination-BdQWAuko.js +41 -0
  47. package/storybook-static/assets/Command.stories-DzBlWQs0.js +30 -0
  48. package/storybook-static/assets/ContextMenu.stories-CJlBQyXc.js +31 -0
  49. package/storybook-static/assets/DataTable.Basic.stories-BWYKFDmK.js +6 -0
  50. package/storybook-static/assets/DataTable.Filters.stories-uZdtJk8t.js +21 -0
  51. package/storybook-static/assets/DataTable.Pagination.stories-C5N1khkp.js +24 -0
  52. package/storybook-static/assets/DataTable.SelectionAndActions.stories-FhCqZKvO.js +26 -0
  53. package/storybook-static/assets/DataTable.Sorting.stories-D-k7EtRj.js +6 -0
  54. package/storybook-static/assets/Dialog.stories-C62AF-Gx.js +54 -0
  55. package/storybook-static/assets/Dialog.stories-lrjRwOus.js +18 -0
  56. package/storybook-static/assets/Drawer.stories-CGjkdJeV.js +24 -0
  57. package/storybook-static/assets/DropdownMenu.stories-DkGClRAA.js +35 -0
  58. package/storybook-static/assets/Form.ArrayLayouts.stories-C5d_062d.js +130 -0
  59. package/storybook-static/assets/Form.Autocomplete.stories-CPZPkk4o.js +142 -0
  60. package/storybook-static/assets/Form.Basic.stories-Bhcu3-3n.js +58 -0
  61. package/storybook-static/assets/Form.Complex.stories-QnXh5a7Q.js +361 -0
  62. package/storybook-static/assets/Form.Dynamic.stories-DFW6wIuT.js +502 -0
  63. package/storybook-static/assets/Form.Simple.stories-BhJcyhbE.js +53 -0
  64. package/storybook-static/assets/Form.stories-PFNsMYxO.js +3 -0
  65. package/storybook-static/assets/FormBuilder-BQBBxo_k.js +5 -0
  66. package/storybook-static/assets/HoverCard.stories-CAlQEVn8.js +21 -0
  67. package/storybook-static/assets/Input.stories-CEhODt0V.js +16 -0
  68. package/storybook-static/assets/InputOtp.stories-DSvNP4dS.js +42 -0
  69. package/storybook-static/assets/Label.stories-B3pa8ZLY.js +14 -0
  70. package/storybook-static/assets/Login.stories-C7KQkmR_.js +37 -0
  71. package/storybook-static/assets/Menubar.stories-CHXhSHxc.js +44 -0
  72. package/storybook-static/assets/MonthPicker.stories-BnrOc4fm.js +99 -0
  73. package/storybook-static/assets/MonthRangePicker.stories-55Gk1t-7.js +134 -0
  74. package/storybook-static/assets/NavigationMenu.stories-CXoS080P.js +30 -0
  75. package/storybook-static/assets/Page.stories-GdSJgZ6-.js +91 -0
  76. package/storybook-static/assets/Pagination.stories-BEBwqH4N.js +29 -0
  77. package/storybook-static/assets/Popover.stories-BICy98Cw.js +15 -0
  78. package/storybook-static/assets/Progress.stories-DECHNOME.js +8 -0
  79. package/storybook-static/assets/RadioGroup.stories-DA7-uKfV.js +16 -0
  80. package/storybook-static/assets/Resizable.stories-B99kWkH7.js +25 -0
  81. package/storybook-static/assets/ScrollArea.stories-BqvUAXqU.js +12 -0
  82. package/storybook-static/assets/Section.stories-lFMlFBQn.js +277 -0
  83. package/storybook-static/assets/SectionBuilder-BQW705x0.js +1 -0
  84. package/storybook-static/assets/Select.stories-BsKyZ6Io.js +17 -0
  85. package/storybook-static/assets/Separator.stories-BTDOaOM2.js +17 -0
  86. package/storybook-static/assets/Sheet.stories-Cam1gR6G.js +24 -0
  87. package/storybook-static/assets/Sidebar.stories-DnOa6G7y.js +106 -0
  88. package/storybook-static/assets/Skeleton.stories-BQNIuIe5.js +9 -0
  89. package/storybook-static/assets/Slider.stories-Bslq7hjq.js +6 -0
  90. package/storybook-static/assets/Sonner.stories-D34pBBtI.js +18 -0
  91. package/storybook-static/assets/Switch.stories-Puyb1-Bx.js +3 -0
  92. package/storybook-static/assets/Table.stories-ClZxAhut.js +35 -0
  93. package/storybook-static/assets/Tabs.stories-CURNTETB.js +10 -0
  94. package/storybook-static/assets/Textarea.stories-Cf1ZBrWw.js +17 -0
  95. package/storybook-static/assets/Toggle.stories-CdMHY_bi.js +3 -0
  96. package/storybook-static/assets/ToggleGroup.stories-BM68m1dX.js +13 -0
  97. package/storybook-static/assets/Tooltip.stories-DiQv64dM.js +10 -0
  98. package/storybook-static/assets/accordion-DVgwQcnw.js +1 -0
  99. package/storybook-static/assets/alert-dialog-DCUEwpqm.js +7 -0
  100. package/storybook-static/assets/avatar-BzwOE-mi.js +1 -0
  101. package/storybook-static/assets/axe-HmUsR1st.js +30 -0
  102. package/storybook-static/assets/badge-BnQWua6u.js +1 -0
  103. package/storybook-static/assets/button-0oMkiryo.js +1 -0
  104. package/storybook-static/assets/card-BJpPOzP8.js +1 -0
  105. package/storybook-static/assets/chart-column-DZGb4ZZS.js +6 -0
  106. package/storybook-static/assets/check-B9hBGj6o.js +6 -0
  107. package/storybook-static/assets/checkbox-CyIeaWHX.js +1 -0
  108. package/storybook-static/assets/chevron-down-D_37S6il.js +6 -0
  109. package/storybook-static/assets/chevron-left-BBoN0vbI.js +6 -0
  110. package/storybook-static/assets/chevron-right-B5vIMLxK.js +6 -0
  111. package/storybook-static/assets/circle-C5Lzx6Nx.js +6 -0
  112. package/storybook-static/assets/clean-slate-D1HmMFJM.css +1 -0
  113. package/storybook-static/assets/command-Csa9p8_a.js +6 -0
  114. package/storybook-static/assets/createLucideIcon-BrHXro7t.js +21 -0
  115. package/storybook-static/assets/default-CN_Fo1GY.css +1 -0
  116. package/storybook-static/assets/dependencies-ctrV69dx.js +1 -0
  117. package/storybook-static/assets/dialog-CsnqITTn.js +1 -0
  118. package/storybook-static/assets/dropdown-menu-BWxxwPHL.js +1 -0
  119. package/storybook-static/assets/ellipsis-BRS038RR.js +6 -0
  120. package/storybook-static/assets/grip-vertical-BxXG8KNA.js +6 -0
  121. package/storybook-static/assets/iframe-C9bogcIc.css +1 -0
  122. package/storybook-static/assets/iframe-v7iAhKit.js +1555 -0
  123. package/storybook-static/assets/index-0-qMRXou.js +1 -0
  124. package/storybook-static/assets/index-AvwFFKJc.js +1 -0
  125. package/storybook-static/assets/index-BVDb4dFc.js +1 -0
  126. package/storybook-static/assets/index-B_qx7A5T.js +1 -0
  127. package/storybook-static/assets/index-BdQq_4o_.js +1 -0
  128. package/storybook-static/assets/index-BfiCOk42.js +1 -0
  129. package/storybook-static/assets/index-Bv9yk470.js +1 -0
  130. package/storybook-static/assets/index-Bw1A27Kp.js +1 -0
  131. package/storybook-static/assets/index-ByqivBWx.js +1 -0
  132. package/storybook-static/assets/index-C9Ta0ZTH.js +1 -0
  133. package/storybook-static/assets/index-CDY5kTx5.js +1 -0
  134. package/storybook-static/assets/index-CGnyVRgB.js +1 -0
  135. package/storybook-static/assets/index-CGrAONsN.js +1 -0
  136. package/storybook-static/assets/index-CWjrGFAQ.js +1 -0
  137. package/storybook-static/assets/index-CwBdPBFz.js +9 -0
  138. package/storybook-static/assets/index-D8RXF03I.js +1 -0
  139. package/storybook-static/assets/index-DLIxT4Z7.js +1 -0
  140. package/storybook-static/assets/index-DW48STyt.js +1 -0
  141. package/storybook-static/assets/index-DbaA6-o1.js +1 -0
  142. package/storybook-static/assets/index-Dph_5COR.js +1 -0
  143. package/storybook-static/assets/index-DrN5n71E.js +1 -0
  144. package/storybook-static/assets/index-Tp9IdbR8.js +1 -0
  145. package/storybook-static/assets/index-WyF3-wTE.js +9 -0
  146. package/storybook-static/assets/index-XSmPROEP.js +1 -0
  147. package/storybook-static/assets/index-Z6wF44KX.js +5 -0
  148. package/storybook-static/assets/index-_RPqOjlQ.js +1 -0
  149. package/storybook-static/assets/index-jrimW4QO.js +1 -0
  150. package/storybook-static/assets/index-nqc17SX4.js +1 -0
  151. package/storybook-static/assets/input-11YRd9gD.js +1 -0
  152. package/storybook-static/assets/jsx-runtime-D_zvdyIk.js +9 -0
  153. package/storybook-static/assets/label-Do8ODIVk.js +1 -0
  154. package/storybook-static/assets/lodash-DDwpuhPG.js +73 -0
  155. package/storybook-static/assets/matchers-7Z3WT2CE-T3xScrR7.js +14 -0
  156. package/storybook-static/assets/minimal-modern-BlYVzfQU.css +1 -0
  157. package/storybook-static/assets/popover-CcciSWAw.js +1 -0
  158. package/storybook-static/assets/preload-helper-Dp1pzeXC.js +1 -0
  159. package/storybook-static/assets/radio-group-DiJ0Y_KQ.js +1 -0
  160. package/storybook-static/assets/react-18-Cr9fq_Ip.js +25 -0
  161. package/storybook-static/assets/react-icons.esm-B_ULMmNU.js +1 -0
  162. package/storybook-static/assets/refresh-cw-BmRDhIV_.js +6 -0
  163. package/storybook-static/assets/schemas-CGNYCiJ6.js +18 -0
  164. package/storybook-static/assets/section-factories-DCCY9R35.js +1 -0
  165. package/storybook-static/assets/select-DDrkxaOg.js +6 -0
  166. package/storybook-static/assets/separator-o5SAUnaJ.js +1 -0
  167. package/storybook-static/assets/settings-2-xWGvvbG6.js +6 -0
  168. package/storybook-static/assets/sheet-C7jhU3XE.js +1 -0
  169. package/storybook-static/assets/shopping-cart-BFlrufvo.js +11 -0
  170. package/storybook-static/assets/sidebar-C8hU1Mxy.js +6 -0
  171. package/storybook-static/assets/skeleton-CjDnQs43.js +1 -0
  172. package/storybook-static/assets/spotify-CUDj7g8m.css +1 -0
  173. package/storybook-static/assets/switch-CKGRuk3u.js +1 -0
  174. package/storybook-static/assets/table-CP3vMqFn.js +1 -0
  175. package/storybook-static/assets/tabs-CopK2m3j.js +1 -0
  176. package/storybook-static/assets/textarea-Dw2vruMl.js +1 -0
  177. package/storybook-static/assets/toggle-DmHbWetf.js +16 -0
  178. package/storybook-static/assets/tooltip-_LqYEYFw.js +1 -0
  179. package/storybook-static/assets/trash-2-xdbApPby.js +11 -0
  180. package/storybook-static/assets/utils-D-KgF5mV.js +1 -0
  181. package/storybook-static/assets/x-B1a4fyWM.js +6 -0
  182. package/storybook-static/favicon-wrapper.svg +46 -0
  183. package/storybook-static/favicon.svg +1 -0
  184. package/storybook-static/iframe.html +687 -0
  185. package/storybook-static/index.d.ts +64 -0
  186. package/storybook-static/index.d.ts.map +1 -0
  187. package/storybook-static/index.html +166 -0
  188. package/storybook-static/index.json +1 -0
  189. package/storybook-static/kit/builder/data-table/components/DataTable.d.ts +37 -0
  190. package/storybook-static/kit/builder/data-table/components/DataTable.d.ts.map +1 -0
  191. package/storybook-static/kit/builder/data-table/components/DataTableColumnHeader.d.ts +8 -0
  192. package/storybook-static/kit/builder/data-table/components/DataTableColumnHeader.d.ts.map +1 -0
  193. package/storybook-static/kit/builder/data-table/components/DataTablePagination.d.ts +6 -0
  194. package/storybook-static/kit/builder/data-table/components/DataTablePagination.d.ts.map +1 -0
  195. package/storybook-static/kit/builder/data-table/components/DataTableViewOptions.d.ts +5 -0
  196. package/storybook-static/kit/builder/data-table/components/DataTableViewOptions.d.ts.map +1 -0
  197. package/storybook-static/kit/builder/data-table/index.d.ts +7 -0
  198. package/storybook-static/kit/builder/data-table/index.d.ts.map +1 -0
  199. package/storybook-static/kit/builder/data-table/types.d.ts +27 -0
  200. package/storybook-static/kit/builder/data-table/types.d.ts.map +1 -0
  201. package/storybook-static/kit/builder/data-table/utils/dotAccessor.d.ts +2 -0
  202. package/storybook-static/kit/builder/data-table/utils/dotAccessor.d.ts.map +1 -0
  203. package/storybook-static/kit/builder/dialog/index.d.ts +3 -0
  204. package/storybook-static/kit/builder/dialog/index.d.ts.map +1 -0
  205. package/storybook-static/kit/builder/dialog/provider.d.ts +26 -0
  206. package/storybook-static/kit/builder/dialog/provider.d.ts.map +1 -0
  207. package/storybook-static/kit/builder/form/components/FormBuilder.d.ts +137 -0
  208. package/storybook-static/kit/builder/form/components/FormBuilder.d.ts.map +1 -0
  209. package/storybook-static/kit/builder/form/components/FormBuilderActions.d.ts +20 -0
  210. package/storybook-static/kit/builder/form/components/FormBuilderActions.d.ts.map +1 -0
  211. package/storybook-static/kit/builder/form/components/FormBuilderField.d.ts +12 -0
  212. package/storybook-static/kit/builder/form/components/FormBuilderField.d.ts.map +1 -0
  213. package/storybook-static/kit/builder/form/components/fields/ArrayField.d.ts +3 -0
  214. package/storybook-static/kit/builder/form/components/fields/ArrayField.d.ts.map +1 -0
  215. package/storybook-static/kit/builder/form/components/fields/AutocompleteField.d.ts +3 -0
  216. package/storybook-static/kit/builder/form/components/fields/AutocompleteField.d.ts.map +1 -0
  217. package/storybook-static/kit/builder/form/components/fields/CheckboxField.d.ts +3 -0
  218. package/storybook-static/kit/builder/form/components/fields/CheckboxField.d.ts.map +1 -0
  219. package/storybook-static/kit/builder/form/components/fields/DateField.d.ts +3 -0
  220. package/storybook-static/kit/builder/form/components/fields/DateField.d.ts.map +1 -0
  221. package/storybook-static/kit/builder/form/components/fields/FileField.d.ts +3 -0
  222. package/storybook-static/kit/builder/form/components/fields/FileField.d.ts.map +1 -0
  223. package/storybook-static/kit/builder/form/components/fields/NumberField.d.ts +3 -0
  224. package/storybook-static/kit/builder/form/components/fields/NumberField.d.ts.map +1 -0
  225. package/storybook-static/kit/builder/form/components/fields/ObjectField.d.ts +3 -0
  226. package/storybook-static/kit/builder/form/components/fields/ObjectField.d.ts.map +1 -0
  227. package/storybook-static/kit/builder/form/components/fields/RadioField.d.ts +3 -0
  228. package/storybook-static/kit/builder/form/components/fields/RadioField.d.ts.map +1 -0
  229. package/storybook-static/kit/builder/form/components/fields/SelectField.d.ts +3 -0
  230. package/storybook-static/kit/builder/form/components/fields/SelectField.d.ts.map +1 -0
  231. package/storybook-static/kit/builder/form/components/fields/SwitchField.d.ts +3 -0
  232. package/storybook-static/kit/builder/form/components/fields/SwitchField.d.ts.map +1 -0
  233. package/storybook-static/kit/builder/form/components/fields/TextField.d.ts +3 -0
  234. package/storybook-static/kit/builder/form/components/fields/TextField.d.ts.map +1 -0
  235. package/storybook-static/kit/builder/form/components/fields/TextareaField.d.ts +3 -0
  236. package/storybook-static/kit/builder/form/components/fields/TextareaField.d.ts.map +1 -0
  237. package/storybook-static/kit/builder/form/components/fields/index.d.ts +14 -0
  238. package/storybook-static/kit/builder/form/components/fields/index.d.ts.map +1 -0
  239. package/storybook-static/kit/builder/form/components/fields/types.d.ts +14 -0
  240. package/storybook-static/kit/builder/form/components/fields/types.d.ts.map +1 -0
  241. package/storybook-static/kit/builder/form/components/index.d.ts +4 -0
  242. package/storybook-static/kit/builder/form/components/index.d.ts.map +1 -0
  243. package/storybook-static/kit/builder/form/index.d.ts +3 -0
  244. package/storybook-static/kit/builder/form/index.d.ts.map +1 -0
  245. package/storybook-static/kit/builder/form/utils/common-forms.d.ts +7 -0
  246. package/storybook-static/kit/builder/form/utils/common-forms.d.ts.map +1 -0
  247. package/storybook-static/kit/builder/form/utils/dependencies.d.ts +41 -0
  248. package/storybook-static/kit/builder/form/utils/dependencies.d.ts.map +1 -0
  249. package/storybook-static/kit/builder/form/utils/field-factories.d.ts +23 -0
  250. package/storybook-static/kit/builder/form/utils/field-factories.d.ts.map +1 -0
  251. package/storybook-static/kit/builder/form/utils/index.d.ts +15 -0
  252. package/storybook-static/kit/builder/form/utils/index.d.ts.map +1 -0
  253. package/storybook-static/kit/builder/form/utils/section-factories.d.ts +7 -0
  254. package/storybook-static/kit/builder/form/utils/section-factories.d.ts.map +1 -0
  255. package/storybook-static/kit/builder/form/utils/transformers.d.ts +6 -0
  256. package/storybook-static/kit/builder/form/utils/transformers.d.ts.map +1 -0
  257. package/storybook-static/kit/builder/form/utils/validations.d.ts +13 -0
  258. package/storybook-static/kit/builder/form/utils/validations.d.ts.map +1 -0
  259. package/storybook-static/kit/builder/form/utils/validators.d.ts +8 -0
  260. package/storybook-static/kit/builder/form/utils/validators.d.ts.map +1 -0
  261. package/storybook-static/kit/builder/page/Page.d.ts +48 -0
  262. package/storybook-static/kit/builder/page/Page.d.ts.map +1 -0
  263. package/storybook-static/kit/builder/page/index.d.ts +2 -0
  264. package/storybook-static/kit/builder/page/index.d.ts.map +1 -0
  265. package/storybook-static/kit/builder/section/SectionBuilder.d.ts +3 -0
  266. package/storybook-static/kit/builder/section/SectionBuilder.d.ts.map +1 -0
  267. package/storybook-static/kit/builder/section/index.d.ts +3 -0
  268. package/storybook-static/kit/builder/section/index.d.ts.map +1 -0
  269. package/storybook-static/kit/builder/section/types.d.ts +70 -0
  270. package/storybook-static/kit/builder/section/types.d.ts.map +1 -0
  271. package/storybook-static/kit/builder/stack-dialog/context.d.ts +3 -0
  272. package/storybook-static/kit/builder/stack-dialog/context.d.ts.map +1 -0
  273. package/storybook-static/kit/builder/stack-dialog/hooks.d.ts +6 -0
  274. package/storybook-static/kit/builder/stack-dialog/hooks.d.ts.map +1 -0
  275. package/storybook-static/kit/builder/stack-dialog/index.d.ts +5 -0
  276. package/storybook-static/kit/builder/stack-dialog/index.d.ts.map +1 -0
  277. package/storybook-static/kit/builder/stack-dialog/provider.d.ts +3 -0
  278. package/storybook-static/kit/builder/stack-dialog/provider.d.ts.map +1 -0
  279. package/storybook-static/kit/builder/stack-dialog/renderer.d.ts +6 -0
  280. package/storybook-static/kit/builder/stack-dialog/renderer.d.ts.map +1 -0
  281. package/storybook-static/kit/builder/stack-dialog/types.d.ts +20 -0
  282. package/storybook-static/kit/builder/stack-dialog/types.d.ts.map +1 -0
  283. package/storybook-static/kit/components/autocomplete/Autocomplete.d.ts +53 -0
  284. package/storybook-static/kit/components/autocomplete/Autocomplete.d.ts.map +1 -0
  285. package/storybook-static/kit/components/autocomplete/index.d.ts +4 -0
  286. package/storybook-static/kit/components/autocomplete/index.d.ts.map +1 -0
  287. package/storybook-static/kit/components/autocomplete/types.d.ts +19 -0
  288. package/storybook-static/kit/components/autocomplete/types.d.ts.map +1 -0
  289. package/storybook-static/kit/components/login/Login.d.ts +29 -0
  290. package/storybook-static/kit/components/login/Login.d.ts.map +1 -0
  291. package/storybook-static/kit/components/login/index.d.ts +2 -0
  292. package/storybook-static/kit/components/login/index.d.ts.map +1 -0
  293. package/storybook-static/kit/components/monthpicker/MonthPicker.d.ts +32 -0
  294. package/storybook-static/kit/components/monthpicker/MonthPicker.d.ts.map +1 -0
  295. package/storybook-static/kit/components/monthpicker/MonthRangePicker.d.ts +48 -0
  296. package/storybook-static/kit/components/monthpicker/MonthRangePicker.d.ts.map +1 -0
  297. package/storybook-static/kit/layouts/admin/components/AdminLayout.d.ts +17 -0
  298. package/storybook-static/kit/layouts/admin/components/AdminLayout.d.ts.map +1 -0
  299. package/storybook-static/kit/layouts/admin/components/ThemeToggle.d.ts +5 -0
  300. package/storybook-static/kit/layouts/admin/components/ThemeToggle.d.ts.map +1 -0
  301. package/storybook-static/kit/layouts/admin/hooks/menu.d.ts +13 -0
  302. package/storybook-static/kit/layouts/admin/hooks/menu.d.ts.map +1 -0
  303. package/storybook-static/kit/layouts/admin/providers/AdminMenuProvider.d.ts +7 -0
  304. package/storybook-static/kit/layouts/admin/providers/AdminMenuProvider.d.ts.map +1 -0
  305. package/storybook-static/kit/layouts/admin/types/index.d.ts +27 -0
  306. package/storybook-static/kit/layouts/admin/types/index.d.ts.map +1 -0
  307. package/storybook-static/kit/providers/ThemeProvider.d.ts +14 -0
  308. package/storybook-static/kit/providers/ThemeProvider.d.ts.map +1 -0
  309. package/storybook-static/nunito-sans-bold-italic.woff2 +0 -0
  310. package/storybook-static/nunito-sans-bold.woff2 +0 -0
  311. package/storybook-static/nunito-sans-italic.woff2 +0 -0
  312. package/storybook-static/nunito-sans-regular.woff2 +0 -0
  313. package/storybook-static/project.json +1 -0
  314. package/storybook-static/sb-addons/a11y-5/manager-bundle.js +5 -0
  315. package/storybook-static/sb-addons/essentials-backgrounds-1/manager-bundle.js +3 -0
  316. package/storybook-static/sb-addons/essentials-measure-2/manager-bundle.js +3 -0
  317. package/storybook-static/sb-addons/essentials-outline-3/manager-bundle.js +3 -0
  318. package/storybook-static/sb-addons/interactions-4/manager-bundle.js +57 -0
  319. package/storybook-static/sb-addons/storybook-core-server-presets-0/common-manager-bundle.js +971 -0
  320. package/storybook-static/sb-addons/storysource-6/manager-bundle.js +3 -0
  321. package/storybook-static/sb-common-assets/favicon-wrapper.svg +46 -0
  322. package/storybook-static/sb-common-assets/favicon.svg +1 -0
  323. package/storybook-static/sb-common-assets/nunito-sans-bold-italic.woff2 +0 -0
  324. package/storybook-static/sb-common-assets/nunito-sans-bold.woff2 +0 -0
  325. package/storybook-static/sb-common-assets/nunito-sans-italic.woff2 +0 -0
  326. package/storybook-static/sb-common-assets/nunito-sans-regular.woff2 +0 -0
  327. package/storybook-static/sb-manager/globals-module-info.js +797 -0
  328. package/storybook-static/sb-manager/globals-runtime.js +69653 -0
  329. package/storybook-static/sb-manager/globals.js +34 -0
  330. package/storybook-static/sb-manager/runtime.js +13181 -0
  331. package/storybook-static/shadcn/hooks/use-mobile.d.ts +2 -0
  332. package/storybook-static/shadcn/hooks/use-mobile.d.ts.map +1 -0
  333. package/storybook-static/shadcn/lib/utils.d.ts +3 -0
  334. package/storybook-static/shadcn/lib/utils.d.ts.map +1 -0
  335. package/storybook-static/shadcn/ui/accordion.d.ts +8 -0
  336. package/storybook-static/shadcn/ui/accordion.d.ts.map +1 -0
  337. package/storybook-static/shadcn/ui/alert-dialog.d.ts +15 -0
  338. package/storybook-static/shadcn/ui/alert-dialog.d.ts.map +1 -0
  339. package/storybook-static/shadcn/ui/alert.d.ts +10 -0
  340. package/storybook-static/shadcn/ui/alert.d.ts.map +1 -0
  341. package/storybook-static/shadcn/ui/aspect-ratio.d.ts +4 -0
  342. package/storybook-static/shadcn/ui/aspect-ratio.d.ts.map +1 -0
  343. package/storybook-static/shadcn/ui/avatar.d.ts +7 -0
  344. package/storybook-static/shadcn/ui/avatar.d.ts.map +1 -0
  345. package/storybook-static/shadcn/ui/badge.d.ts +10 -0
  346. package/storybook-static/shadcn/ui/badge.d.ts.map +1 -0
  347. package/storybook-static/shadcn/ui/breadcrumb.d.ts +12 -0
  348. package/storybook-static/shadcn/ui/breadcrumb.d.ts.map +1 -0
  349. package/storybook-static/shadcn/ui/button.d.ts +11 -0
  350. package/storybook-static/shadcn/ui/button.d.ts.map +1 -0
  351. package/storybook-static/shadcn/ui/calendar.d.ts +9 -0
  352. package/storybook-static/shadcn/ui/calendar.d.ts.map +1 -0
  353. package/storybook-static/shadcn/ui/card.d.ts +10 -0
  354. package/storybook-static/shadcn/ui/card.d.ts.map +1 -0
  355. package/storybook-static/shadcn/ui/carousel.d.ts +20 -0
  356. package/storybook-static/shadcn/ui/carousel.d.ts.map +1 -0
  357. package/storybook-static/shadcn/ui/chart.d.ts +41 -0
  358. package/storybook-static/shadcn/ui/chart.d.ts.map +1 -0
  359. package/storybook-static/shadcn/ui/checkbox.d.ts +5 -0
  360. package/storybook-static/shadcn/ui/checkbox.d.ts.map +1 -0
  361. package/storybook-static/shadcn/ui/collapsible.d.ts +6 -0
  362. package/storybook-static/shadcn/ui/collapsible.d.ts.map +1 -0
  363. package/storybook-static/shadcn/ui/command.d.ts +19 -0
  364. package/storybook-static/shadcn/ui/command.d.ts.map +1 -0
  365. package/storybook-static/shadcn/ui/context-menu.d.ts +26 -0
  366. package/storybook-static/shadcn/ui/context-menu.d.ts.map +1 -0
  367. package/storybook-static/shadcn/ui/dialog.d.ts +16 -0
  368. package/storybook-static/shadcn/ui/dialog.d.ts.map +1 -0
  369. package/storybook-static/shadcn/ui/drawer.d.ts +14 -0
  370. package/storybook-static/shadcn/ui/drawer.d.ts.map +1 -0
  371. package/storybook-static/shadcn/ui/dropdown-menu.d.ts +26 -0
  372. package/storybook-static/shadcn/ui/dropdown-menu.d.ts.map +1 -0
  373. package/storybook-static/shadcn/ui/form.d.ts +25 -0
  374. package/storybook-static/shadcn/ui/form.d.ts.map +1 -0
  375. package/storybook-static/shadcn/ui/hover-card.d.ts +7 -0
  376. package/storybook-static/shadcn/ui/hover-card.d.ts.map +1 -0
  377. package/storybook-static/shadcn/ui/input-otp.d.ts +12 -0
  378. package/storybook-static/shadcn/ui/input-otp.d.ts.map +1 -0
  379. package/storybook-static/shadcn/ui/input.d.ts +4 -0
  380. package/storybook-static/shadcn/ui/input.d.ts.map +1 -0
  381. package/storybook-static/shadcn/ui/label.d.ts +5 -0
  382. package/storybook-static/shadcn/ui/label.d.ts.map +1 -0
  383. package/storybook-static/shadcn/ui/menubar.d.ts +27 -0
  384. package/storybook-static/shadcn/ui/menubar.d.ts.map +1 -0
  385. package/storybook-static/shadcn/ui/navigation-menu.d.ts +15 -0
  386. package/storybook-static/shadcn/ui/navigation-menu.d.ts.map +1 -0
  387. package/storybook-static/shadcn/ui/pagination.d.ts +14 -0
  388. package/storybook-static/shadcn/ui/pagination.d.ts.map +1 -0
  389. package/storybook-static/shadcn/ui/popover.d.ts +8 -0
  390. package/storybook-static/shadcn/ui/popover.d.ts.map +1 -0
  391. package/storybook-static/shadcn/ui/progress.d.ts +5 -0
  392. package/storybook-static/shadcn/ui/progress.d.ts.map +1 -0
  393. package/storybook-static/shadcn/ui/radio-group.d.ts +6 -0
  394. package/storybook-static/shadcn/ui/radio-group.d.ts.map +1 -0
  395. package/storybook-static/shadcn/ui/resizable.d.ts +9 -0
  396. package/storybook-static/shadcn/ui/resizable.d.ts.map +1 -0
  397. package/storybook-static/shadcn/ui/scroll-area.d.ts +6 -0
  398. package/storybook-static/shadcn/ui/scroll-area.d.ts.map +1 -0
  399. package/storybook-static/shadcn/ui/select.d.ts +16 -0
  400. package/storybook-static/shadcn/ui/select.d.ts.map +1 -0
  401. package/storybook-static/shadcn/ui/separator.d.ts +5 -0
  402. package/storybook-static/shadcn/ui/separator.d.ts.map +1 -0
  403. package/storybook-static/shadcn/ui/sheet.d.ts +14 -0
  404. package/storybook-static/shadcn/ui/sheet.d.ts.map +1 -0
  405. package/storybook-static/shadcn/ui/sidebar.d.ts +70 -0
  406. package/storybook-static/shadcn/ui/sidebar.d.ts.map +1 -0
  407. package/storybook-static/shadcn/ui/skeleton.d.ts +3 -0
  408. package/storybook-static/shadcn/ui/skeleton.d.ts.map +1 -0
  409. package/storybook-static/shadcn/ui/slider.d.ts +5 -0
  410. package/storybook-static/shadcn/ui/slider.d.ts.map +1 -0
  411. package/storybook-static/shadcn/ui/sonner.d.ts +4 -0
  412. package/storybook-static/shadcn/ui/sonner.d.ts.map +1 -0
  413. package/storybook-static/shadcn/ui/switch.d.ts +5 -0
  414. package/storybook-static/shadcn/ui/switch.d.ts.map +1 -0
  415. package/storybook-static/shadcn/ui/table.d.ts +11 -0
  416. package/storybook-static/shadcn/ui/table.d.ts.map +1 -0
  417. package/storybook-static/shadcn/ui/tabs.d.ts +8 -0
  418. package/storybook-static/shadcn/ui/tabs.d.ts.map +1 -0
  419. package/storybook-static/shadcn/ui/textarea.d.ts +4 -0
  420. package/storybook-static/shadcn/ui/textarea.d.ts.map +1 -0
  421. package/storybook-static/shadcn/ui/toggle-group.d.ts +8 -0
  422. package/storybook-static/shadcn/ui/toggle-group.d.ts.map +1 -0
  423. package/storybook-static/shadcn/ui/toggle.d.ts +10 -0
  424. package/storybook-static/shadcn/ui/toggle.d.ts.map +1 -0
  425. package/storybook-static/shadcn/ui/tooltip.d.ts +8 -0
  426. package/storybook-static/shadcn/ui/tooltip.d.ts.map +1 -0
  427. package/storybook-static/vite-inject-mocker-entry.js +18 -0
@@ -1,806 +1,553 @@
1
- import type React from "react";
2
- import {
3
- useCallback,
4
- useEffect,
5
- useMemo,
6
- useRef,
7
- useState,
8
- useId,
9
- } from "react";
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import { useCombobox } from "downshift";
3
+ import { useDebounce } from "use-debounce";
10
4
  import { cn } from "../../../shadcn/lib/utils";
11
- import {
12
- Popover,
13
- PopoverContent,
14
- PopoverTrigger,
15
- } from "../../../shadcn/ui/popover";
16
- import {
17
- Command,
18
- CommandEmpty,
19
- CommandGroup,
20
- CommandInput,
21
- CommandItem,
22
- CommandList,
23
- CommandSeparator,
24
- } from "../../../shadcn/ui/command";
25
- import { ChevronsUpDown, Check, Loader2, X } from "lucide-react";
5
+ import { Popover, PopoverContent, PopoverTrigger } from "../../../shadcn/ui/popover";
26
6
  import { Badge } from "../../../shadcn/ui/badge";
7
+ import { ChevronsUpDown, X, Check, Loader2 } from "lucide-react";
27
8
  import type {
28
9
  AutocompleteFetcher,
29
10
  AutocompleteMode,
30
11
  AutocompleteOption,
31
12
  AutocompleteFetchResult,
32
13
  } from "./types";
33
- import { useDebounce } from "use-debounce";
34
14
 
35
15
  export type AutocompleteProps<T = unknown> = {
36
16
  mode: AutocompleteMode;
37
17
  options?: AutocompleteOption<T>[];
38
18
  fetcher?: AutocompleteFetcher<T>;
39
- fetcherFilter?: () => Record<string, string | number | boolean | null>,
19
+ fetcherFilter?: () => Record<string, string | number | boolean | null>;
40
20
  pageSize?: number;
41
- /**
42
- * Value can be a single primitive or an array when `multiple` is true
43
- */
44
21
  value?: string | number | null | Array<string | number>;
45
- /**
46
- * onChange returns a single value + option in single mode, or an array of values + options in multiple mode
47
- */
48
22
  onChange?: (
49
23
  value: string | number | null | Array<string | number>,
50
- option: AutocompleteOption<T> | AutocompleteOption<T>[] | null,
24
+ selected: AutocompleteOption<T> | AutocompleteOption<T>[] | null,
51
25
  raw?: T | T[] | null,
52
26
  ) => void;
53
- /** Enable selecting multiple values (shows chips) */
54
- multiple?: boolean;
55
- /** Placeholder shown when nothing is selected */
56
27
  placeholder?: string;
57
28
  disabled?: boolean;
29
+ multiple?: boolean;
58
30
  className?: string;
31
+ chipVariant?: "default" | "secondary" | "destructive" | "outline";
32
+ chipClassName?: string;
59
33
  emptyText?: string;
60
- renderOption?: (
61
- option: AutocompleteOption<T>,
62
- selected: boolean,
63
- ) => React.ReactNode;
64
- searchPlaceholder?: string;
65
- /** Controls initial open state; component is uncontrolled otherwise */
34
+ renderOption?: (option: AutocompleteOption<T>, selected: boolean) => React.ReactNode;
66
35
  defaultOpen?: boolean;
67
- /** Initial value when component is uncontrolled */
68
36
  defaultValue?: string | number | null | Array<string | number>;
69
- /** Allow entering custom values not present in options (useful for tagging) */
70
37
  allowCustomValue?: boolean;
71
- /**
72
- * Chip visual style for multiple selection. Uses shadcn Badge variants.
73
- * default | secondary | destructive | outline
74
- */
75
- chipVariant?: "default" | "secondary" | "destructive" | "outline";
76
- /** Additional className for each chip */
77
- chipClassName?: string;
78
- /** Show a clear button when a selection exists */
79
38
  clearable?: boolean;
80
- /**
81
- * Optional: seed selected labels for edit pages. These options are only used to populate the
82
- * internal label map so labels render correctly when values are prefilled.
83
- */
84
- initialSelectedOptions?: AutocompleteOption<T> | AutocompleteOption<T>[] | null;
85
- /**
86
- * Optional: load labels/options for a list of values whose labels are unknown.
87
- * Useful for edit pages in server mode when only values are available.
88
- */
39
+ initialSelectedOptions?: AutocompleteOption<T> | AutocompleteOption<T>[];
89
40
  loadSelected?: (values: Array<string | number>) => Promise<AutocompleteOption<T>[]>;
90
41
  };
91
42
 
92
- const DEFAULT_PAGE_SIZE = 20;
93
-
94
- const EMPTY_OPTIONS: AutocompleteOption[] = [];
43
+ const DEFAULT_PAGE_SIZE = 50;
95
44
 
96
45
  export function Autocomplete<T = unknown>({
97
- mode,
98
- options = EMPTY_OPTIONS as AutocompleteOption<T>[],
46
+ mode = "client",
47
+ options = [],
99
48
  fetcher,
100
49
  fetcherFilter,
101
50
  pageSize = DEFAULT_PAGE_SIZE,
102
51
  value: controlledValue,
103
52
  onChange,
53
+ placeholder = "Search or select...",
54
+ disabled = false,
104
55
  multiple = false,
105
- placeholder = "Select...",
106
- disabled,
107
56
  className,
57
+ chipVariant = "secondary",
58
+ chipClassName,
108
59
  emptyText = "No results found",
109
60
  renderOption,
110
- searchPlaceholder = "Search...",
111
61
  defaultOpen,
112
62
  defaultValue,
113
63
  allowCustomValue = false,
114
- chipVariant = "secondary",
115
- chipClassName,
116
64
  clearable = true,
117
65
  initialSelectedOptions,
118
66
  loadSelected,
119
67
  }: AutocompleteProps<T>) {
120
- const [open, setOpen] = useState<boolean>(!!defaultOpen);
121
- const [search, setSearch] = useState("");
122
- const [debouncedSearch] = useDebounce(search, 250);
123
- const listId = useId();
124
-
125
- // Selection
126
68
  const isMultiple = !!multiple;
127
- const [value, setValue] = useState<
128
- string | number | null | Array<string | number>
129
- >(() => {
130
- if (controlledValue !== undefined) return controlledValue;
69
+ const isControlled = controlledValue !== undefined;
70
+
71
+ // Internal value state
72
+ const [internalValue, setInternalValue] = useState<string | number | null | Array<string | number>>(() => {
131
73
  if (defaultValue !== undefined) return defaultValue;
132
74
  return isMultiple ? [] : null;
133
75
  });
134
- useEffect(() => {
135
- if (controlledValue !== undefined) setValue(controlledValue);
136
- }, [controlledValue]);
137
76
 
138
- // Keep a map of value -> label to ensure we can render chips/labels even if the option
139
- // is not present in the current page (especially in server mode or for custom values)
77
+ const currentValue = isControlled ? controlledValue : internalValue;
78
+
79
+ // Label and raw data maps
140
80
  const labelMapRef = useRef<Map<string | number, string>>(new Map());
141
81
  const rawMapRef = useRef<Map<string | number, T>>(new Map());
142
- const addToLabelMap = useCallback((opt: AutocompleteOption<T>) => {
82
+
83
+ const storeOption = useCallback((opt: AutocompleteOption<T>) => {
143
84
  labelMapRef.current.set(opt.value, opt.label);
144
- if (Object.prototype.hasOwnProperty.call(opt, "raw") && (opt as AutocompleteOption<T>).raw !== undefined) {
145
- rawMapRef.current.set(opt.value, (opt as AutocompleteOption<T>).raw as T);
85
+ if (opt.raw !== undefined) {
86
+ rawMapRef.current.set(opt.value, opt.raw);
146
87
  }
147
88
  }, []);
148
- // Tick to force re-render when labels hydrate via async
149
- const [labelTick, setLabelTick] = useState(0);
150
- const getLabel = useCallback(
151
- (v: string | number) =>
152
- labelMapRef.current.get(v) ??
153
- options.find((o) => o.value === v)?.label ??
154
- (loadSelected ? "Loading..." : String(v)),
155
- [options, loadSelected],
156
- );
157
89
 
158
- const handleSelect = useCallback(
159
- (next: AutocompleteOption<T>) => {
160
- addToLabelMap(next);
161
- if (isMultiple) {
162
- const prevValues = Array.isArray(value) ? value : [];
163
- const exists = prevValues.some((v) => v === next.value);
164
- const newValues = exists
165
- ? prevValues.filter((v) => v !== next.value)
166
- : [...prevValues, next.value];
167
-
168
- if (controlledValue === undefined) setValue(newValues);
169
- const selectedOptions: AutocompleteOption<T>[] = newValues.map((v) => ({
170
- value: v,
171
- label: getLabel(v),
172
- raw: rawMapRef.current.get(v),
173
- }));
174
- const raws = selectedOptions.map((o) => o.raw as T);
175
- onChange?.(newValues, selectedOptions, raws);
176
- // Keep open for multi-select
177
- } else {
178
- const newValue = next.value;
179
- if (controlledValue === undefined) setValue(newValue);
180
- onChange?.(newValue, next, (next as AutocompleteOption<T>).raw as T | undefined ?? null);
181
- setOpen(false);
90
+ // Immediately populate labels for initial values (runs once on mount)
91
+ if (labelMapRef.current.size === 0 && currentValue) {
92
+ const values = Array.isArray(currentValue) ? currentValue : [currentValue];
93
+ // First, store initialSelectedOptions if provided
94
+ if (initialSelectedOptions) {
95
+ const arr = Array.isArray(initialSelectedOptions) ? initialSelectedOptions : [initialSelectedOptions];
96
+ arr.forEach(opt => {
97
+ labelMapRef.current.set(opt.value, opt.label);
98
+ if (opt.raw !== undefined) {
99
+ rawMapRef.current.set(opt.value, opt.raw);
100
+ }
101
+ });
102
+ }
103
+ // Then look up missing values in options array
104
+ values.forEach(val => {
105
+ if (!labelMapRef.current.has(val)) {
106
+ const option = options.find(opt => opt.value === val);
107
+ if (option) {
108
+ labelMapRef.current.set(option.value, option.label);
109
+ if (option.raw !== undefined) {
110
+ rawMapRef.current.set(option.value, option.raw);
111
+ }
112
+ }
182
113
  }
183
- },
184
- [addToLabelMap, controlledValue, getLabel, isMultiple, onChange, value],
185
- );
114
+ });
115
+ }
186
116
 
187
- // Data state (shared for both modes)
117
+ // Data state
188
118
  const [items, setItems] = useState<AutocompleteOption<T>[]>([]);
189
119
  const [loading, setLoading] = useState(false);
190
120
  const [hasMore, setHasMore] = useState(false);
191
- const [nextCursor, setNextCursor] = useState<
192
- string | number | null | undefined
193
- >(undefined);
194
121
  const [page, setPage] = useState(1);
122
+ const [searchInput, setSearchInput] = useState("");
123
+ const [debouncedSearch] = useDebounce(searchInput, 300);
124
+ const [clearCounter, setClearCounter] = useState(0);
195
125
 
196
- const resetData = useCallback(() => {
197
- setItems([]);
198
- setHasMore(false);
199
- setNextCursor(undefined);
200
- setPage(1);
201
- }, []);
126
+ // Popover state
127
+ const [isOpen, setIsOpen] = useState(!!defaultOpen);
128
+
129
+ // Clear input in multiple mode after selection
130
+ useEffect(() => {
131
+ if (clearCounter > 0) {
132
+ setSearchInput("");
133
+ }
134
+ }, [clearCounter]);
202
135
 
203
136
  // Load data
204
- const load = useCallback(async () => {
205
- if (mode === "server") {
206
- if (!fetcher) return;
207
- setLoading(true);
208
- try {
209
- const res: AutocompleteFetchResult<T> = await fetcher({
210
- search: debouncedSearch,
211
- moreFilter: fetcherFilter,
212
- cursor: nextCursor ?? null,
213
- page,
214
- pageSize,
215
- });
216
- setItems((prev) => (page === 1 ? res.items : [...prev, ...res.items]));
217
- setHasMore(!!res.hasMore);
218
- setNextCursor(res.nextCursor);
219
- } finally {
220
- setLoading(false);
221
- }
222
- } else {
223
- // client mode: filter and paginate locally
224
- setLoading(true);
225
- try {
226
- const filtered = debouncedSearch
227
- ? options.filter((o) =>
228
- o.label.toLowerCase().includes(debouncedSearch.toLowerCase()),
229
- )
137
+ const loadData = useCallback(
138
+ async (pageNum: number, search: string) => {
139
+ if (mode === "server") {
140
+ if (!fetcher) return;
141
+ setLoading(true);
142
+ try {
143
+ const res: AutocompleteFetchResult<T> = await fetcher({
144
+ search,
145
+ moreFilter: fetcherFilter,
146
+ cursor: null,
147
+ page: pageNum,
148
+ pageSize,
149
+ });
150
+ res.items.forEach(storeOption);
151
+ setItems((prev) => (pageNum === 1 ? res.items : [...prev, ...res.items]));
152
+ setHasMore(!!res.hasMore);
153
+ } catch (_error) {
154
+ if (pageNum === 1) setItems([]);
155
+ setHasMore(false);
156
+ } finally {
157
+ setLoading(false);
158
+ }
159
+ } else {
160
+ // Client mode
161
+ const filtered = search
162
+ ? options.filter((o) => o.label.toLowerCase().includes(search.toLowerCase()))
230
163
  : options;
231
- const start = (page - 1) * pageSize;
164
+ options.forEach(storeOption);
165
+ const start = (pageNum - 1) * pageSize;
232
166
  const slice = filtered.slice(start, start + pageSize);
233
- setItems((prev) => (page === 1 ? slice : [...prev, ...slice]));
167
+ setItems((prev) => (pageNum === 1 ? slice : [...prev, ...slice]));
234
168
  setHasMore(start + pageSize < filtered.length);
235
- setNextCursor(undefined);
236
- } finally {
237
- setLoading(false);
238
169
  }
239
- }
240
- }, [mode, fetcher, fetcherFilter, debouncedSearch, nextCursor, page, pageSize, options]);
170
+ },
171
+ [mode, fetcher, fetcherFilter, options, pageSize, storeOption],
172
+ );
241
173
 
242
- // Keep a ref to latest load() to avoid stale closures
243
- const loadRef = useRef(load);
174
+ // Reset and load on search change
244
175
  useEffect(() => {
245
- loadRef.current = load;
246
- }, [load]);
176
+ if (!isOpen) return;
177
+ setPage(1);
178
+ loadData(1, debouncedSearch);
179
+ // eslint-disable-next-line react-hooks/exhaustive-deps
180
+ }, [isOpen, debouncedSearch]);
247
181
 
248
- // Reset and load on open/search change (do NOT depend on load to avoid resets during pagination)
182
+ // Load more pages
249
183
  useEffect(() => {
250
- if (!open) return;
251
- // Reference debouncedSearch to intentionally re-run on search changes
252
- void debouncedSearch;
253
- resetData();
254
- const t = window.setTimeout(() => {
255
- void loadRef.current();
256
- }, 0);
257
- return () => window.clearTimeout(t);
258
- }, [open, debouncedSearch, resetData]);
259
-
260
- // Load subsequent pages when page changes (>1)
184
+ if (!isOpen || page <= 1) return;
185
+ loadData(page, debouncedSearch);
186
+ // eslint-disable-next-line react-hooks/exhaustive-deps
187
+ }, [page]);
188
+
189
+ // Store initial/selected options (takes precedence)
261
190
  useEffect(() => {
262
- if (!open) return;
263
- if (page <= 1) return;
264
- void loadRef.current();
265
- }, [open, page]);
266
-
267
- // Infinite scroll
268
- const listRef = useRef<HTMLDivElement | null>(null);
269
- const onListScroll = useCallback(() => {
270
- const el = listRef.current;
271
- if (!el || loading) return;
272
- const nearBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 24;
273
- if (nearBottom && hasMore) {
274
- setPage((p) => p + 1);
191
+ if (initialSelectedOptions) {
192
+ const arr = Array.isArray(initialSelectedOptions) ? initialSelectedOptions : [initialSelectedOptions];
193
+ arr.forEach(storeOption);
275
194
  }
276
- }, [loading, hasMore]);
195
+ }, [initialSelectedOptions, storeOption]);
277
196
 
278
- // Prime label map from loaded items and static options
197
+ // Auto-populate missing initial options from provided options array
279
198
  useEffect(() => {
280
- items.forEach(addToLabelMap);
281
- }, [items, addToLabelMap]);
282
- useEffect(() => {
283
- options.forEach(addToLabelMap);
284
- }, [options, addToLabelMap]);
285
- // Seed label map from explicitly provided initialSelectedOptions
199
+ if (!currentValue || (Array.isArray(currentValue) && currentValue.length === 0)) return;
200
+
201
+ const values = Array.isArray(currentValue) ? currentValue : [currentValue];
202
+
203
+ // Only look up values that are missing (not in label map)
204
+ const missingValues = values.filter(v => !labelMapRef.current.has(v));
205
+
206
+ if (missingValues.length === 0) return;
207
+
208
+ // Look up missing values in the options array
209
+ missingValues.forEach(val => {
210
+ const option = options.find(opt => opt.value === val);
211
+ if (option) {
212
+ storeOption(option);
213
+ }
214
+ });
215
+ }, [currentValue, options, storeOption]);
216
+
217
+ // Load selected labels on mount for initial values
218
+ const hasLoadedInitial = useRef(false);
219
+ const [labelsLoadedCounter, setLabelsLoadedCounter] = useState(0);
220
+
286
221
  useEffect(() => {
287
- if (!initialSelectedOptions) return;
288
- const arr = Array.isArray(initialSelectedOptions)
289
- ? initialSelectedOptions
290
- : [initialSelectedOptions];
291
- arr.forEach(addToLabelMap);
292
- setLabelTick((t) => t + 1);
293
- }, [initialSelectedOptions, addToLabelMap]);
294
-
295
- // Resolve labels for current values if missing and a resolver is provided
222
+ // Only run once on mount
223
+ if (!loadSelected || hasLoadedInitial.current) return;
224
+ if (!currentValue) return;
225
+
226
+ const values = Array.isArray(currentValue) ? currentValue : [currentValue];
227
+ if (values.length === 0) return;
228
+
229
+ // Check if any values are missing labels
230
+ const missing = values.filter((v) => !labelMapRef.current.has(v));
231
+ if (missing.length === 0) {
232
+ hasLoadedInitial.current = true;
233
+ return;
234
+ }
235
+
236
+ hasLoadedInitial.current = true;
237
+ let cancelled = false;
238
+ loadSelected(missing).then((opts) => {
239
+ if (!cancelled && opts.length > 0) {
240
+ opts.forEach(storeOption);
241
+ // Only trigger re-render if we actually stored something
242
+ setLabelsLoadedCounter(c => c + 1);
243
+ }
244
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
245
+ }).catch(() => {});
246
+ return () => {
247
+ cancelled = true;
248
+ };
249
+ // eslint-disable-next-line react-hooks/exhaustive-deps
250
+ }, []); // Only run once on mount - uses closure values
251
+
252
+ // Load selected labels if missing when dropdown opens
296
253
  useEffect(() => {
297
- if (!loadSelected) return;
298
- const curValues: Array<string | number> = Array.isArray(value)
299
- ? (isMultiple ? (value as Array<string | number>) : [])
300
- : value !== null && value !== undefined
301
- ? [value as string | number]
302
- : [];
303
- if (curValues.length === 0) return;
304
- const missing = curValues.filter(
305
- (v) => !labelMapRef.current.has(v) && !options.some((o) => o.value === v),
306
- );
254
+ if (!loadSelected || !isOpen) return;
255
+ const values = Array.isArray(currentValue) ? currentValue : currentValue ? [currentValue] : [];
256
+ const missing = values.filter((v) => !labelMapRef.current.has(v));
307
257
  if (missing.length === 0) return;
258
+
308
259
  let cancelled = false;
309
- loadSelected(missing)
310
- .then((opts) => {
311
- if (cancelled) return;
312
- opts.forEach(addToLabelMap);
313
- setLabelTick((t) => t + 1);
314
- })
315
- .catch(() => {
316
- /* swallow resolver errors */
317
- });
260
+ loadSelected(missing).then((opts) => {
261
+ if (!cancelled) opts.forEach(storeOption);
262
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
263
+ }).catch(() => {});
318
264
  return () => {
319
265
  cancelled = true;
320
266
  };
321
- // include options so we don't resolve when already present
322
- }, [value, isMultiple, loadSelected, options, addToLabelMap]);
323
-
324
- // Selected label
325
- const selectedOption = useMemo(() => {
326
- if (isMultiple || Array.isArray(value)) return undefined;
327
- return items.find((i) => i.value === value);
328
- }, [isMultiple, items, value]);
329
-
330
- // Ensure selected label when item not in current page (server mode)
331
- // biome-ignore lint/correctness/useExhaustiveDependencies: labelTick intentionally triggers recompute when labelMap hydrates
332
- const displayedLabel = useMemo(() => {
333
- if (isMultiple || Array.isArray(value)) return placeholder;
334
- if (selectedOption) return selectedOption.label;
335
- const v = value;
336
- if (v !== null && v !== undefined && !Array.isArray(v)) {
337
- const mapped = labelMapRef.current.get(v);
338
- if (mapped) return mapped;
339
- if (mode === "client") {
340
- const found = options.find((i) => i.value === v);
341
- return found?.label ?? placeholder;
342
- }
343
- }
344
- return placeholder;
345
- }, [isMultiple, mode, options, selectedOption, value, placeholder, labelTick]);
267
+ }, [currentValue, loadSelected, isOpen, storeOption]);
346
268
 
347
- const selectedValues: Array<string | number> = useMemo(
348
- () => (isMultiple && Array.isArray(value) ? value : []),
349
- [isMultiple, value],
350
- );
351
- // biome-ignore lint/correctness/useExhaustiveDependencies: labelTick intentionally triggers recompute when labelMap hydrates
352
- const selectedOptionsMulti: AutocompleteOption<T>[] = useMemo(
353
- () => selectedValues.map((v) => ({ value: v, label: getLabel(v), raw: rawMapRef.current.get(v) })),
354
- [getLabel, selectedValues, labelTick],
355
- );
269
+ // Get label helper
270
+ const getLabel = useCallback((v: string | number) => {
271
+ return labelMapRef.current.get(v) ?? String(v);
272
+ }, []);
356
273
 
357
- const handleClear = useCallback(() => {
358
- if (isMultiple) {
359
- if (controlledValue === undefined) setValue([]);
360
- onChange?.([], [], []);
361
- } else {
362
- if (controlledValue === undefined) setValue(null);
363
- onChange?.(null, null, null);
274
+ // Selected items for display
275
+ const selectedItems = useMemo(() => {
276
+ if (!isMultiple) {
277
+ if (currentValue === null || currentValue === undefined || Array.isArray(currentValue)) return [];
278
+ return [{ value: currentValue, label: getLabel(currentValue), raw: rawMapRef.current.get(currentValue) }];
364
279
  }
365
- }, [controlledValue, isMultiple, onChange]);
366
-
367
- // Helper: add custom value from current search
368
- const addCustomValue = useCallback(
369
- (text: string) => {
370
- const t = text.trim();
371
- if (!t) return;
372
- const created: AutocompleteOption<T> = { value: t, label: t };
373
- addToLabelMap(created);
280
+ const values = Array.isArray(currentValue) ? currentValue : [];
281
+ return values.map((v) => ({ value: v, label: getLabel(v), raw: rawMapRef.current.get(v) }));
282
+ // eslint-disable-next-line react-hooks/exhaustive-deps
283
+ }, [currentValue, isMultiple, getLabel, labelsLoadedCounter]); // labelsLoadedCounter triggers re-compute when loadSelected completes
284
+
285
+ // Handle selection
286
+ const handleSelect = useCallback(
287
+ (item: AutocompleteOption<T> | null) => {
288
+ if (!item) return;
289
+ storeOption(item);
290
+
374
291
  if (isMultiple) {
375
- const prevValues = Array.isArray(value) ? value : [];
376
- const exists = prevValues.some((v) => v === created.value);
377
- const newValues = exists ? prevValues : [...prevValues, created.value];
378
- if (controlledValue === undefined) setValue(newValues);
379
- const newOptions = newValues.map((v) => ({
380
- value: v,
381
- label: getLabel(v),
382
- raw: rawMapRef.current.get(v),
383
- }));
384
- const raws = newOptions.map((o) => o.raw as T);
292
+ const values = Array.isArray(currentValue) ? currentValue : [];
293
+ const exists = values.includes(item.value);
294
+
295
+ // Skip if already selected (don't toggle), but still clear the search
296
+ if (exists) {
297
+ setClearCounter(c => c + 1);
298
+ return;
299
+ }
300
+
301
+ const newValues = [...values, item.value];
302
+
303
+ if (!isControlled) setInternalValue(newValues);
304
+ const newOptions = newValues.map((v) => ({ value: v, label: getLabel(v), raw: rawMapRef.current.get(v) }));
305
+ const raws = newOptions.map((o) => o.raw).filter((r): r is T => r !== undefined);
385
306
  onChange?.(newValues, newOptions, raws);
386
- setSearch("");
307
+
308
+ // Trigger input clear
309
+ setClearCounter(c => c + 1);
387
310
  } else {
388
- if (controlledValue === undefined) setValue(created.value);
389
- onChange?.(created.value, created, (created.raw as T | undefined) ?? null);
390
- setSearch("");
391
- setOpen(false);
311
+ if (!isControlled) setInternalValue(item.value);
312
+ onChange?.(item.value, item, item.raw ?? null);
313
+ setIsOpen(false);
392
314
  }
393
315
  },
394
- [addToLabelMap, controlledValue, getLabel, isMultiple, onChange, value],
316
+ [isMultiple, currentValue, isControlled, onChange, getLabel, storeOption],
395
317
  );
396
318
 
397
- const inlineInputRef = useRef<HTMLInputElement | null>(null);
398
- const canOpen = useMemo(() => {
399
- // When purely tagging (no static options and no fetcher) in client mode with multi+allowCustomValue,
400
- // we do not show the dropdown; user types inline.
401
- const pureTagging =
402
- isMultiple &&
403
- allowCustomValue &&
404
- mode === "client" &&
405
- options.length === 0 &&
406
- !fetcher;
407
- return !pureTagging;
408
- }, [allowCustomValue, fetcher, isMultiple, mode, options.length]);
319
+ // Handle remove chip
320
+ const handleRemove = useCallback(
321
+ (valueToRemove: string | number) => {
322
+ const values = Array.isArray(currentValue) ? currentValue : [];
323
+ const newValues = values.filter((v) => v !== valueToRemove);
324
+ if (!isControlled) setInternalValue(newValues);
325
+ const newOptions = newValues.map((v) => ({ value: v, label: getLabel(v), raw: rawMapRef.current.get(v) }));
326
+ const raws = newOptions.map((o) => o.raw).filter((r): r is T => r !== undefined);
327
+ onChange?.(newValues, newOptions, raws);
328
+ },
329
+ [currentValue, isControlled, onChange, getLabel],
330
+ );
331
+
332
+ // Handle clear
333
+ const handleClear = useCallback(() => {
334
+ const newValue = isMultiple ? [] : null;
335
+ if (!isControlled) setInternalValue(newValue);
336
+ onChange?.(newValue, isMultiple ? [] : null, isMultiple ? [] : null);
337
+ }, [isMultiple, isControlled, onChange]);
338
+
339
+ // Handle custom value creation
340
+ const handleCreateCustom = useCallback(() => {
341
+ const trimmed = searchInput.trim();
342
+ if (!trimmed || !allowCustomValue) return;
343
+
344
+ const newOption: AutocompleteOption<T> = { value: trimmed, label: trimmed };
345
+ storeOption(newOption);
346
+
347
+ if (isMultiple) {
348
+ const values = Array.isArray(currentValue) ? currentValue : [];
349
+ if (values.includes(trimmed)) return;
350
+ const newValues = [...values, trimmed];
351
+ if (!isControlled) setInternalValue(newValues);
352
+ const newOptions = newValues.map((v) => ({ value: v, label: getLabel(v), raw: rawMapRef.current.get(v) }));
353
+ onChange?.(newValues, newOptions, []);
354
+ setSearchInput("");
355
+ } else {
356
+ if (!isControlled) setInternalValue(trimmed);
357
+ onChange?.(trimmed, newOption, null);
358
+ setSearchInput("");
359
+ setIsOpen(false);
360
+ }
361
+ }, [searchInput, allowCustomValue, isMultiple, currentValue, isControlled, onChange, getLabel, storeOption]);
362
+
363
+ // Compute input value based on mode and state
364
+ const computedInputValue = useMemo(() => {
365
+ // In multiple mode or when dropdown is open, show search input
366
+ if (isMultiple || isOpen) {
367
+ return searchInput;
368
+ }
369
+ // In single mode when closed, show selected item label
370
+ if (selectedItems.length > 0) {
371
+ return selectedItems[0].label;
372
+ }
373
+ return "";
374
+ }, [isMultiple, isOpen, searchInput, selectedItems]);
375
+
376
+ // Downshift
377
+ const {
378
+ getInputProps,
379
+ getItemProps,
380
+ getMenuProps,
381
+ highlightedIndex,
382
+ } = useCombobox({
383
+ items,
384
+ itemToString: (item) => item?.label ?? "",
385
+ selectedItem: isMultiple ? null : (selectedItems[0] ?? null),
386
+ onSelectedItemChange: ({ selectedItem }) => handleSelect(selectedItem),
387
+ isOpen,
388
+ onIsOpenChange: ({ isOpen: newIsOpen }) => setIsOpen(newIsOpen ?? false),
389
+ inputValue: computedInputValue,
390
+ onInputValueChange: ({ inputValue }) => setSearchInput(inputValue ?? ""),
391
+ });
392
+
393
+ // Refs
394
+ const parentRef = useRef<HTMLDivElement>(null);
395
+ const inputRef = useRef<HTMLInputElement>(null);
396
+
397
+ const handleScroll = useCallback(() => {
398
+ if (!parentRef.current || !hasMore || loading) return;
399
+
400
+ const { scrollTop, scrollHeight, clientHeight } = parentRef.current;
401
+ const scrolledToBottom = scrollHeight - scrollTop - clientHeight < 50;
402
+
403
+ if (scrolledToBottom) {
404
+ setPage((p) => p + 1);
405
+ }
406
+ }, [hasMore, loading]);
407
+
409
408
  useEffect(() => {
410
- if (!canOpen && open) setOpen(false);
411
- }, [canOpen, open]);
409
+ const element = parentRef.current;
410
+ if (!element) return;
411
+
412
+ element.addEventListener('scroll', handleScroll);
413
+ return () => element.removeEventListener('scroll', handleScroll);
414
+ }, [handleScroll]);
415
+
416
+ const showClearButton = clearable && (
417
+ (isMultiple && selectedItems.length > 0) ||
418
+ (!isMultiple && currentValue !== null && currentValue !== undefined && !Array.isArray(currentValue))
419
+ );
412
420
 
413
421
  return (
414
- <Popover open={open} onOpenChange={setOpen}>
415
- {canOpen ? (
416
- <PopoverTrigger asChild>
417
- <div
418
- // biome-ignore lint/a11y/useSemanticElements: <explanation>
419
- role="combobox"
420
- aria-expanded={open}
421
- aria-controls={listId}
422
- tabIndex={disabled ? -1 : 0}
423
- aria-disabled={disabled || undefined}
424
- className={cn(
425
- "w-full inline-flex items-center justify-between rounded-md border border-border bg-background px-3 py-2 text-sm shadow-sm transition-colors",
426
- "hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
427
- disabled && "opacity-50 pointer-events-none",
428
- className,
429
- )}
430
- onKeyDown={(e) => {
431
- if (disabled || !canOpen) return;
432
- if (e.target instanceof HTMLInputElement) return; // let input handle keys
433
- if (e.key === "Enter" || e.key === " ") {
434
- e.preventDefault();
435
- setOpen((v) => !v);
436
- }
437
- }}
438
- >
439
- {isMultiple ? (
440
- <div
441
- className={cn(
442
- "flex min-w-0 flex-1 flex-wrap items-center gap-1 text-left",
443
- )}
444
- >
445
- {selectedOptionsMulti.length > 0
446
- ? selectedOptionsMulti.map((opt) => (
447
- <Badge
448
- key={`${opt.value}`}
449
- variant={chipVariant}
450
- className={cn("pr-1", chipClassName)}
451
- >
452
- <span className="truncate max-w-[10rem]">
453
- {opt.label}
454
- </span>
455
- <button
456
- type="button"
457
- aria-label={`Remove ${opt.label}`}
458
- className="ml-1 inline-flex items-center rounded-sm hover:bg-black/5 dark:hover:bg-white/10"
459
- onMouseDown={(e) => {
460
- e.preventDefault();
461
- e.stopPropagation();
462
- }}
463
- onClick={(e) => {
464
- e.preventDefault();
465
- e.stopPropagation();
466
- const prevValues = Array.isArray(value)
467
- ? value
468
- : [];
469
- const newValues = prevValues.filter(
470
- (v) => v !== opt.value,
471
- );
472
- if (controlledValue === undefined)
473
- setValue(newValues);
474
- const newOptions = newValues.map((v) => ({
475
- value: v,
476
- label: getLabel(v),
477
- }));
478
- onChange?.(newValues, newOptions);
479
- }}
480
- >
481
- <X className="h-3 w-3" />
482
- </button>
483
- </Badge>
484
- ))
485
- : null}
486
- {allowCustomValue ? (
487
- <input
488
- ref={inlineInputRef}
489
- value={search}
490
- onChange={(e) => setSearch(e.target.value)}
491
- placeholder={
492
- selectedOptionsMulti.length === 0
493
- ? placeholder
494
- : undefined
495
- }
496
- className="flex-1 min-w-[8ch] bg-transparent outline-none text-sm placeholder:text-muted-foreground"
497
- onKeyDown={(e) => {
498
- if (e.key === "Enter" || e.key === ",") {
499
- e.preventDefault();
500
- addCustomValue(search);
501
- } else if (
502
- e.key === "Backspace" &&
503
- search === "" &&
504
- selectedOptionsMulti.length > 0
505
- ) {
506
- // Remove last chip when input is empty
507
- const prevValues = Array.isArray(value) ? value : [];
508
- const newValues = prevValues.slice(0, -1);
509
- if (controlledValue === undefined) setValue(newValues);
510
- const newOptions = newValues.map((v) => ({
511
- value: v,
512
- label: getLabel(v),
513
- }));
514
- onChange?.(newValues, newOptions);
515
- }
516
- }}
517
- />
518
- ) : null}
519
- {selectedOptionsMulti.length === 0 && !allowCustomValue ? (
520
- <span className="truncate text-muted-foreground">
521
- {placeholder}
522
- </span>
523
- ) : null}
524
- </div>
525
- ) : (
526
- <span
527
- className={cn(
528
- "truncate",
529
- (!value || Array.isArray(value)) && "text-muted-foreground",
530
- )}
531
- >
532
- {displayedLabel}
533
- </span>
534
- )}
535
- {clearable &&
536
- ((isMultiple && selectedOptionsMulti.length > 0) ||
537
- (!isMultiple &&
538
- value !== null &&
539
- value !== undefined &&
540
- !Array.isArray(value))) ? (
541
- <button
542
- type="button"
543
- aria-label="Clear selection"
544
- className="ml-2 inline-flex items-center rounded-sm p-1 hover:bg-black/5 dark:hover:bg-white/10"
545
- onMouseDown={(e) => {
546
- e.preventDefault();
547
- e.stopPropagation();
548
- }}
549
- onClick={(e) => {
550
- e.preventDefault();
551
- e.stopPropagation();
552
- handleClear();
553
- }}
554
- onKeyDown={(e) => {
555
- if (e.key === "Enter" || e.key === " ") {
556
- e.preventDefault();
557
- e.stopPropagation();
558
- handleClear();
559
- }
560
- }}
561
- >
562
- <X className="h-4 w-4 opacity-60" />
563
- </button>
564
- ) : null}
565
- <ChevronsUpDown
566
- className={cn(
567
- "ml-2 h-4 w-4 shrink-0 opacity-50",
568
- !canOpen && "hidden",
569
- )}
570
- />
571
- </div>
572
- </PopoverTrigger>
573
- ) : (
422
+ <Popover open={isOpen} onOpenChange={setIsOpen}>
423
+ <PopoverTrigger asChild>
574
424
  <div
575
- // biome-ignore lint/a11y/useSemanticElements: <explanation>
576
- role="combobox"
577
- aria-expanded={open}
578
- aria-controls={listId}
579
- tabIndex={disabled ? -1 : 0}
580
- aria-disabled={disabled || undefined}
581
425
  className={cn(
582
- "w-full inline-flex items-center justify-between rounded-md border bg-background px-3 py-2 text-sm shadow-sm transition-colors",
583
- "hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
584
- disabled && "opacity-50 pointer-events-none",
426
+ "flex min-h-10 w-full items-center gap-2 rounded-md border border-input bg-background px-3 py-2 text-sm",
427
+ "ring-offset-background",
428
+ "focus-within:outline-none focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2",
429
+ disabled && "cursor-not-allowed opacity-50",
585
430
  className,
586
431
  )}
587
- onKeyDown={(_e) => {
588
- if (disabled || canOpen) return;
589
- // Do not toggle popover; handle inline input only
590
- }}
591
432
  >
592
- {isMultiple ? (
593
- <div
594
- className={cn(
595
- "flex min-w-0 flex-1 flex-wrap items-center gap-1 text-left",
596
- )}
597
- >
598
- {selectedOptionsMulti.length > 0
599
- ? selectedOptionsMulti.map((opt) => (
600
- <Badge
601
- key={`${opt.value}`}
602
- variant={chipVariant}
603
- className={cn("pr-1", chipClassName)}
433
+ {isMultiple && (
434
+ <div className="flex flex-wrap gap-1">
435
+ {selectedItems.map((item) => (
436
+ <Badge key={item.value} variant={chipVariant} className={cn("gap-1", chipClassName)}>
437
+ <span className="max-w-[150px] truncate">{item.label}</span>
438
+ <button
439
+ type="button"
440
+ onClick={(e) => {
441
+ e.stopPropagation();
442
+ handleRemove(item.value);
443
+ }}
444
+ className="rounded-sm opacity-70 hover:opacity-100"
604
445
  >
605
- <span className="truncate max-w-[10rem]">
606
- {opt.label}
607
- </span>
608
- <button
609
- type="button"
610
- aria-label={`Remove ${opt.label}`}
611
- className="ml-1 inline-flex items-center rounded-sm hover:bg-black/5 dark:hover:bg-white/10"
612
- onMouseDown={(e) => {
613
- e.preventDefault();
614
- e.stopPropagation();
615
- }}
616
- onClick={(e) => {
617
- e.preventDefault();
618
- e.stopPropagation();
619
- const prevValues = Array.isArray(value) ? value : [];
620
- const newValues = prevValues.filter(
621
- (v) => v !== opt.value,
622
- );
623
- if (controlledValue === undefined)
624
- setValue(newValues);
625
- const newOptions = newValues.map((v) => ({
626
- value: v,
627
- label: getLabel(v),
628
- }));
629
- onChange?.(newValues, newOptions);
630
- }}
631
- >
632
- <X className="h-3 w-3" />
633
- </button>
634
- </Badge>
635
- ))
636
- : null}
637
- {allowCustomValue ? (
638
- <input
639
- ref={inlineInputRef}
640
- value={search}
641
- onChange={(e) => setSearch(e.target.value)}
642
- placeholder={
643
- selectedOptionsMulti.length === 0 ? placeholder : undefined
644
- }
645
- className="flex-1 min-w-[8ch] bg-transparent outline-none text-sm placeholder:text-muted-foreground"
646
- onKeyDown={(e) => {
647
- if (e.key === "Enter" || e.key === ",") {
648
- e.preventDefault();
649
- addCustomValue(search);
650
- } else if (
651
- e.key === "Backspace" &&
652
- search === "" &&
653
- selectedOptionsMulti.length > 0
654
- ) {
655
- const prevValues = Array.isArray(value) ? value : [];
656
- const newValues = prevValues.slice(0, -1);
657
- if (controlledValue === undefined) setValue(newValues);
658
- const newOptions = newValues.map((v) => ({
659
- value: v,
660
- label: getLabel(v),
661
- }));
662
- onChange?.(newValues, newOptions);
663
- }
664
- }}
665
- />
666
- ) : null}
667
- {selectedOptionsMulti.length === 0 && !allowCustomValue ? (
668
- <span className="truncate text-muted-foreground">
669
- {placeholder}
670
- </span>
671
- ) : null}
446
+ <X className="h-3 w-3" />
447
+ </button>
448
+ </Badge>
449
+ ))}
672
450
  </div>
673
- ) : (
674
- <span
675
- className={cn(
676
- "truncate",
677
- (!value || Array.isArray(value)) && "text-muted-foreground",
678
- )}
679
- >
680
- {displayedLabel}
681
- </span>
682
451
  )}
683
- {clearable &&
684
- ((isMultiple && selectedOptionsMulti.length > 0) ||
685
- (!isMultiple &&
686
- value !== null &&
687
- value !== undefined &&
688
- !Array.isArray(value))) ? (
689
- <button
690
- type="button"
691
- aria-label="Clear selection"
692
- className="ml-2 inline-flex items-center rounded-sm p-1 hover:bg-black/5 dark:hover:bg-white/10"
693
- onMouseDown={(e) => {
694
- e.preventDefault();
695
- e.stopPropagation();
696
- }}
697
- onClick={(e) => {
698
- e.preventDefault();
699
- e.stopPropagation();
700
- handleClear();
701
- }}
702
- onKeyDown={(e) => {
703
- if (e.key === "Enter" || e.key === " ") {
452
+ <input
453
+ {...getInputProps({
454
+ ref: inputRef,
455
+ placeholder,
456
+ disabled,
457
+ onClick: () => {
458
+ if (!isOpen) setIsOpen(true);
459
+ },
460
+ onKeyDown: (e) => {
461
+ if (e.key === "Enter" && allowCustomValue && searchInput.trim() && items.length === 0) {
704
462
  e.preventDefault();
463
+ handleCreateCustom();
464
+ }
465
+ },
466
+ })}
467
+ className="flex-1 bg-transparent outline-none placeholder:text-muted-foreground min-w-[120px]"
468
+ />
469
+ <div className="flex items-center gap-2 shrink-0">
470
+ {showClearButton && (
471
+ <button
472
+ type="button"
473
+ onClick={(e) => {
705
474
  e.stopPropagation();
706
475
  handleClear();
707
- }
708
- }}
709
- >
710
- <X className="h-4 w-4 opacity-60" />
711
- </button>
712
- ) : null}
713
- <ChevronsUpDown
714
- className={cn(
715
- "ml-2 h-4 w-4 shrink-0 opacity-50",
716
- !canOpen && "hidden",
476
+ }}
477
+ className="rounded-sm opacity-70 hover:opacity-100"
478
+ >
479
+ <X className="h-4 w-4" />
480
+ </button>
717
481
  )}
718
- />
482
+ <ChevronsUpDown className="h-4 w-4 opacity-50" />
483
+ </div>
719
484
  </div>
720
- )}
721
- {canOpen ? (
722
- <PopoverContent
723
- className="w-[--radix-popover-trigger-width] p-0"
724
- align="start"
485
+ </PopoverTrigger>
486
+ <PopoverContent
487
+ className="p-0"
488
+ style={{ width: "var(--radix-popover-trigger-width)" }}
489
+ align="start"
490
+ onOpenAutoFocus={(e) => e.preventDefault()}
491
+ >
492
+ <div
493
+ {...getMenuProps()}
494
+ ref={parentRef}
495
+ className="max-h-[300px] overflow-auto"
725
496
  >
726
- <Command shouldFilter={false} className="w-full">
727
- <div className="p-2">
728
- <CommandInput
729
- value={search}
730
- onValueChange={setSearch}
731
- placeholder={searchPlaceholder}
732
- autoFocus
733
- onKeyDown={(e) => {
734
- if (e.key === "Enter" && allowCustomValue && search.trim()) {
735
- e.preventDefault();
736
- addCustomValue(search);
737
- }
738
- }}
739
- />
740
- </div>
741
- <CommandList
742
- id={listId}
743
- className="max-h-56 overflow-auto"
744
- ref={listRef}
745
- onScroll={onListScroll}
746
- >
747
- {loading && items.length === 0 ? (
748
- <div className="flex items-center justify-center py-6 text-sm text-muted-foreground">
749
- <Loader2 className="mr-2 h-4 w-4 animate-spin" /> Loading
750
- </div>
751
- ) : null}
752
- <CommandEmpty>
753
- {allowCustomValue && search.trim() ? (
754
- <span>Press Enter to add "{search.trim()}"</span>
497
+ {loading && items.length === 0 ? (
498
+ <div className="flex items-center justify-center py-6">
499
+ <Loader2 className="h-4 w-4 animate-spin mr-2" />
500
+ <span className="text-sm text-muted-foreground">Loading...</span>
501
+ </div>
502
+ ) : items.length === 0 ? (
503
+ <div className="py-6 text-center text-sm text-muted-foreground">
504
+ {allowCustomValue && searchInput.trim() ? (
505
+ <>Press Enter to add &quot;{searchInput.trim()}&quot;</>
755
506
  ) : (
756
507
  emptyText
757
508
  )}
758
- </CommandEmpty>
759
- {items.length > 0 ? (
760
- <CommandGroup>
761
- {items.map((item) => {
762
- const selected = isMultiple
763
- ? Array.isArray(value)
764
- ? value.includes(item.value)
765
- : false
766
- : item.value === value;
767
- return (
768
- <CommandItem
769
- key={`${item.value}`}
770
- value={`${item.label}`}
771
- onSelect={() => handleSelect(item)}
772
- className="flex items-center justify-between"
773
- >
774
- <div className="min-w-0 truncate">
775
- {renderOption
776
- ? renderOption(item, selected)
777
- : item.label}
778
- </div>
779
- {selected ? <Check className="h-4 w-4" /> : null}
780
- </CommandItem>
781
- );
782
- })}
783
- </CommandGroup>
784
- ) : null}
785
- {hasMore ? (
786
- <>
787
- <CommandSeparator />
788
- <div className="flex items-center justify-center py-2 text-xs text-muted-foreground">
789
- {loading ? (
790
- <>
791
- <Loader2 className="mr-1 h-3 w-3 animate-spin" />{" "}
792
- Loading more
793
- </>
794
- ) : (
795
- "Scroll to load more"
796
- )}
797
- </div>
798
- </>
799
- ) : null}
800
- </CommandList>
801
- </Command>
802
- </PopoverContent>
803
- ) : null}
509
+ </div>
510
+ ) : (
511
+ <>
512
+ {items.map((item, index) => {
513
+ const isSelected = isMultiple
514
+ ? Array.isArray(currentValue) && currentValue.includes(item.value)
515
+ : currentValue === item.value;
516
+ const isHighlighted = highlightedIndex === index;
517
+
518
+ return (
519
+ <div
520
+ key={item.value}
521
+ {...getItemProps({ item, index })}
522
+ className={cn(
523
+ "flex cursor-pointer items-center justify-between px-2 py-2 text-sm outline-none transition-colors",
524
+ isHighlighted && "bg-accent text-accent-foreground",
525
+ isSelected && "font-medium",
526
+ )}
527
+ >
528
+ <div className="flex-1 truncate">
529
+ {renderOption ? renderOption(item, isSelected) : item.label}
530
+ </div>
531
+ {isSelected && <Check className="h-4 w-4 shrink-0" />}
532
+ </div>
533
+ );
534
+ })}
535
+ </>
536
+ )}
537
+ {hasMore && items.length > 0 && (
538
+ <div className="flex items-center justify-center border-t py-2">
539
+ {loading ? (
540
+ <>
541
+ <Loader2 className="h-3 w-3 animate-spin mr-1" />
542
+ <span className="text-xs text-muted-foreground">Loading more...</span>
543
+ </>
544
+ ) : (
545
+ <span className="text-xs text-muted-foreground">Scroll for more</span>
546
+ )}
547
+ </div>
548
+ )}
549
+ </div>
550
+ </PopoverContent>
804
551
  </Popover>
805
552
  );
806
553
  }