@goplusvn/core 0.1.0 → 0.1.2

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 (591) hide show
  1. package/package.json +31 -175
  2. package/src/assets/erp_wallpaper.png +0 -0
  3. package/src/assets/goeat_logo.png +0 -0
  4. package/src/audit/audit-manager.ts +139 -0
  5. package/src/audit/index.ts +11 -0
  6. package/src/audit/memory-audit-logger.ts +86 -0
  7. package/src/audit/types.ts +50 -0
  8. package/src/auth/auth-service.ts +97 -0
  9. package/src/auth/index.ts +266 -0
  10. package/src/code-generation/index.ts +69 -0
  11. package/src/configs/auth-routes.ts +17 -0
  12. package/src/configs/crud.ts +136 -0
  13. package/src/configs/data/navigations.ts +781 -0
  14. package/src/configs/data/oauth-links.ts +10 -0
  15. package/src/configs/entities/material-categories.config.ts +125 -0
  16. package/src/configs/i18n.ts +12 -0
  17. package/src/configs/index.ts +26 -0
  18. package/src/configs/status.ts +25 -0
  19. package/src/configs/themes.ts +100 -0
  20. package/src/crud/components/crud-bulk-actions.tsx +91 -0
  21. package/src/crud/components/crud-card-view.tsx +241 -0
  22. package/src/crud/components/crud-context.tsx +122 -0
  23. package/src/crud/components/crud-delete-dialog.tsx +145 -0
  24. package/src/crud/components/crud-dialog.tsx +406 -0
  25. package/src/crud/components/crud-empty-state.tsx +104 -0
  26. package/src/crud/components/crud-export-button.tsx +170 -0
  27. package/src/crud/components/crud-field-renderer.tsx +653 -0
  28. package/src/crud/components/crud-filter-chips.tsx +102 -0
  29. package/src/crud/components/crud-filters/checkbox-filter.tsx +97 -0
  30. package/src/crud/components/crud-filters/datetime-filter.tsx +83 -0
  31. package/src/crud/components/crud-filters/filter-builder.tsx +66 -0
  32. package/src/crud/components/crud-filters/index.tsx +76 -0
  33. package/src/crud/components/crud-filters/radio-filter.tsx +86 -0
  34. package/src/crud/components/crud-filters/select-filter.tsx +141 -0
  35. package/src/crud/components/crud-filters/text-filter.tsx +86 -0
  36. package/src/crud/components/crud-form.tsx +642 -0
  37. package/src/crud/components/crud-import-dialog.tsx +440 -0
  38. package/src/crud/components/crud-infinite-scroll.tsx +116 -0
  39. package/src/crud/components/crud-page.tsx +1017 -0
  40. package/src/crud/components/crud-provider.tsx +277 -0
  41. package/src/crud/components/crud-row-actions.tsx +189 -0
  42. package/src/crud/components/crud-search.tsx +82 -0
  43. package/src/crud/components/crud-sheet.tsx +336 -0
  44. package/src/crud/components/crud-table-skeleton.tsx +26 -0
  45. package/src/crud/components/crud-table-toolbar.tsx +91 -0
  46. package/src/crud/components/crud-table.tsx +352 -0
  47. package/src/crud/components/crud-virtual-table.tsx +55 -0
  48. package/src/crud/components/index.tsx +20 -0
  49. package/src/crud/crud-filters/checkbox-filter.tsx +87 -0
  50. package/src/crud/crud-filters/datetime-filter.tsx +82 -0
  51. package/src/crud/crud-filters/filter-builder.tsx +64 -0
  52. package/src/crud/crud-filters/index.tsx +78 -0
  53. package/src/crud/crud-filters/radio-filter.tsx +79 -0
  54. package/src/crud/crud-filters/select-filter.tsx +148 -0
  55. package/src/crud/crud-filters/text-filter.tsx +81 -0
  56. package/src/crud/index.ts +43 -0
  57. package/src/crud/lib/crud-service.test.ts +334 -0
  58. package/src/crud/lib/crud-service.ts +358 -0
  59. package/src/crud/lib/crud-utils.test.ts +354 -0
  60. package/src/crud/lib/crud-utils.ts +299 -0
  61. package/src/crud/lib/crud-validator.ts +247 -0
  62. package/src/crud/lib/data-loader.ts +234 -0
  63. package/src/crud/lib/field-calculator.ts +241 -0
  64. package/src/crud/lib/field-formatter.ts +240 -0
  65. package/src/crud/lib/import-export-service.test.ts +290 -0
  66. package/src/crud/lib/import-export-service.ts +352 -0
  67. package/src/crud/lib/import-server-utils.ts +109 -0
  68. package/src/crud/lib/lazy-loader.ts +241 -0
  69. package/src/crud/lib/parse-filters.ts +85 -0
  70. package/src/crud/lib/permissions.ts +52 -0
  71. package/src/crud/lib/serialize-config.ts +60 -0
  72. package/src/crud/lib/stream-loader.ts +145 -0
  73. package/src/crud/lib/translate-config.ts +335 -0
  74. package/src/crud/lib/types.ts +11 -0
  75. package/src/crud/pages/entity-crud-page.tsx +144 -0
  76. package/src/crud/server.ts +8 -0
  77. package/src/home/constants.tsx +142 -0
  78. package/src/home/feature-showcase.tsx +171 -0
  79. package/src/home/home-page.tsx +191 -0
  80. package/src/home/hooks/index.ts +1 -0
  81. package/src/home/hooks/useWidgetPreferences.ts +167 -0
  82. package/src/home/index.ts +33 -0
  83. package/src/home/quick-access-dialog.tsx +271 -0
  84. package/src/home/quick-access-menu.tsx +267 -0
  85. package/src/home/types.ts +140 -0
  86. package/src/home/welcome-card.tsx +92 -0
  87. package/src/home/widget-container.tsx +258 -0
  88. package/src/home/widgets/base-widget.tsx +200 -0
  89. package/src/home/widgets/customers-widget.tsx +74 -0
  90. package/src/home/widgets/index.ts +6 -0
  91. package/src/home/widgets/orders-widget.tsx +87 -0
  92. package/src/home/widgets/revenue-widget.tsx +71 -0
  93. package/src/home/widgets/stock-widget.tsx +109 -0
  94. package/src/hooks/index.tsx +598 -0
  95. package/src/hooks/use-tenant.test.tsx +30 -0
  96. package/src/hooks/use-tenant.ts +5 -0
  97. package/src/index.ts +17 -0
  98. package/src/infrastructure/__tests__/architecture-verification.spec.ts +103 -0
  99. package/src/infrastructure/api-service.ts +317 -0
  100. package/src/infrastructure/cache/cache-manager.ts +107 -0
  101. package/src/infrastructure/cache/cache.ts +120 -0
  102. package/src/infrastructure/cache/index.ts +8 -0
  103. package/src/infrastructure/cache/types.ts +48 -0
  104. package/src/infrastructure/cron/cron-manager.ts +239 -0
  105. package/src/infrastructure/cron/index.ts +6 -0
  106. package/src/infrastructure/cron/types.ts +41 -0
  107. package/src/infrastructure/event-bus/event-bus.ts +145 -0
  108. package/src/infrastructure/event-bus/index.ts +2 -0
  109. package/src/infrastructure/event-bus/types.ts +22 -0
  110. package/src/infrastructure/index.ts +32 -0
  111. package/src/infrastructure/lock/decorators.ts +67 -0
  112. package/src/infrastructure/lock/index.ts +2 -0
  113. package/src/infrastructure/lock/lock-manager.ts +33 -0
  114. package/src/infrastructure/logger/index.ts +2 -0
  115. package/src/infrastructure/logger/logger.ts +96 -0
  116. package/src/infrastructure/logger/types.ts +25 -0
  117. package/src/layout/index.tsx +185 -0
  118. package/src/navigation/index.ts +91 -0
  119. package/src/notification/index.ts +14 -0
  120. package/src/notification/notification-service.ts +120 -0
  121. package/src/notification/storage/in-memory.ts +56 -0
  122. package/src/notification/storage/index.ts +1 -0
  123. package/src/notification/types.ts +51 -0
  124. package/src/organization/branch-service.ts +299 -0
  125. package/src/organization/branches.config.ts +154 -0
  126. package/src/organization/index.ts +5 -0
  127. package/src/plugin/apps-registry.ts +97 -0
  128. package/src/plugin/index.ts +5 -0
  129. package/src/plugin/types.ts +41 -0
  130. package/src/providers/index.tsx +109 -0
  131. package/src/providers/tenant-provider.tsx +45 -0
  132. package/src/rbac/components/roles/role-card.tsx +158 -0
  133. package/src/rbac/components/roles/role-stats-cards.tsx +29 -0
  134. package/src/rbac/components/roles/role-toolbar.tsx +123 -0
  135. package/src/rbac/hooks/use-role-operations.ts +159 -0
  136. package/src/rbac/hooks/use-roles-data.ts +59 -0
  137. package/src/rbac/index.ts +297 -0
  138. package/src/rbac/lib/permission-helpers.ts +63 -0
  139. package/src/rbac/pages/action-list-page.tsx +25 -0
  140. package/src/rbac/pages/resource-list-page.tsx +25 -0
  141. package/src/rbac/pages/role-list-page.tsx +378 -0
  142. package/src/rbac/permission-service.ts +140 -0
  143. package/src/rbac/permissions.ts +135 -0
  144. package/src/rbac/resource-service.ts +115 -0
  145. package/src/rbac/resource-validator.ts +119 -0
  146. package/src/rbac/role-service.ts +165 -0
  147. package/src/rbac/server.ts +16 -0
  148. package/src/rbac/types.ts +38 -0
  149. package/src/schemas/action.schema.ts +66 -0
  150. package/src/schemas/branch.schema.ts +52 -0
  151. package/src/schemas/coming-soon-schema.ts +9 -0
  152. package/src/schemas/company.schema.ts +44 -0
  153. package/src/schemas/forgot-passward-schema.ts +9 -0
  154. package/src/schemas/index.ts +30 -0
  155. package/src/schemas/material-category.schema.ts +43 -0
  156. package/src/schemas/material-pricing.schema.ts +74 -0
  157. package/src/schemas/material.schema.ts +76 -0
  158. package/src/schemas/materials.ts +52 -0
  159. package/src/schemas/new-passward-schema.ts +15 -0
  160. package/src/schemas/partner-company.schema.ts +149 -0
  161. package/src/schemas/register-schema.ts +36 -0
  162. package/src/schemas/resource.schema.ts +133 -0
  163. package/src/schemas/role.schema.ts +11 -0
  164. package/src/schemas/sign-in-schema.ts +24 -0
  165. package/src/schemas/supplier-pricing.schema.ts +15 -0
  166. package/src/schemas/supplier.schema.ts +120 -0
  167. package/src/schemas/system-category-group.schema.ts +67 -0
  168. package/src/schemas/system-category.schema.ts +77 -0
  169. package/src/schemas/system-config.schema.ts +118 -0
  170. package/src/schemas/uom.schema.ts +75 -0
  171. package/src/schemas/user-supplier.schema.ts +179 -0
  172. package/src/schemas/user.schema.ts +18 -0
  173. package/src/schemas/verify-email-schema.ts +9 -0
  174. package/src/schemas/warehouse.schema.ts +49 -0
  175. package/src/system/components/categories/category-list.tsx +529 -0
  176. package/src/system/components/categories/category-manager.tsx +89 -0
  177. package/src/system/components/categories/group-sidebar.tsx +308 -0
  178. package/src/system/components/settings/setting-dialogs.tsx +197 -0
  179. package/src/system/components/settings/setting-field.tsx +291 -0
  180. package/src/system/components/settings/setting-form-dialog.tsx +308 -0
  181. package/src/system/components/settings/settings-groups.ts +80 -0
  182. package/src/system/components/settings/settings-search.tsx +71 -0
  183. package/src/system/components/settings/settings-section.tsx +74 -0
  184. package/src/system/components/settings/settings-sidebar.tsx +81 -0
  185. package/src/system/constants.ts +3 -0
  186. package/src/system/index.ts +150 -0
  187. package/src/system/job-manager.ts +176 -0
  188. package/src/system/pages/components/categories/category-list.tsx +537 -0
  189. package/src/system/pages/components/categories/category-manager.tsx +90 -0
  190. package/src/system/pages/components/categories/group-sidebar.tsx +311 -0
  191. package/src/system/pages/components/settings/sales-rules-settings.tsx +222 -0
  192. package/src/system/pages/components/settings/setting-dialogs.tsx +197 -0
  193. package/src/system/pages/components/settings/setting-field.tsx +292 -0
  194. package/src/system/pages/components/settings/setting-form-dialog.tsx +308 -0
  195. package/src/system/pages/components/settings/settings-groups.ts +87 -0
  196. package/src/system/pages/components/settings/settings-page.tsx +372 -0
  197. package/src/system/pages/components/settings/settings-search.tsx +71 -0
  198. package/src/system/pages/components/settings/settings-section.tsx +74 -0
  199. package/src/system/pages/components/settings/settings-sidebar.tsx +81 -0
  200. package/src/system/pages/components/settings/system-settings.tsx +244 -0
  201. package/src/system/pages/system-category-page.tsx +15 -0
  202. package/src/system/pages/system-settings-page.tsx +380 -0
  203. package/src/system/schemas/system-category-group.schema.ts +46 -0
  204. package/src/system/schemas/system-category.schema.ts +56 -0
  205. package/src/system/services/settings-service.ts +127 -0
  206. package/src/system/services/system-category-service.ts +63 -0
  207. package/src/system/types.ts +45 -0
  208. package/src/types/index.ts +703 -0
  209. package/src/ui/auth/auth-layout.tsx +135 -0
  210. package/src/ui/auth/forgot-password-form.tsx +98 -0
  211. package/src/ui/auth/index.tsx +7 -0
  212. package/src/ui/auth/new-password-form.tsx +107 -0
  213. package/src/ui/auth/oauth-links.tsx +30 -0
  214. package/src/ui/auth/register-form.tsx +202 -0
  215. package/src/ui/auth/sign-in-form.tsx +238 -0
  216. package/src/ui/auth/verify-email-form.tsx +104 -0
  217. package/src/ui/crud/index.tsx +10 -0
  218. package/src/ui/data-display/accordion.tsx +65 -0
  219. package/src/ui/data-display/aspect-ratio.tsx +11 -0
  220. package/src/ui/data-display/avatar.tsx +163 -0
  221. package/src/ui/data-display/bento-grid.tsx +77 -0
  222. package/src/ui/data-display/carousel.tsx +249 -0
  223. package/src/ui/data-display/chart.tsx +363 -0
  224. package/src/ui/data-display/code-block-highlight.tsx +54 -0
  225. package/src/ui/data-display/collapsible.tsx +42 -0
  226. package/src/ui/data-display/compact-stat-bar.tsx +149 -0
  227. package/src/ui/data-display/data-table/data-table-context.tsx +255 -0
  228. package/src/ui/data-display/data-table/data-table-empty-state.tsx +133 -0
  229. package/src/ui/data-display/data-table/data-table-skeleton.tsx +145 -0
  230. package/src/ui/data-display/data-table/data-table-toolbar.tsx +353 -0
  231. package/src/ui/data-display/data-table/data-table.tsx +597 -0
  232. package/src/ui/data-display/data-table/index.ts +44 -0
  233. package/src/ui/data-display/data-table-column-header.tsx +75 -0
  234. package/src/ui/data-display/data-table-pagination.tsx +130 -0
  235. package/src/ui/data-display/data-table-view-options.tsx +59 -0
  236. package/src/ui/data-display/formatted-number-input.tsx +210 -0
  237. package/src/ui/data-display/highlight.tsx +20 -0
  238. package/src/ui/data-display/hover-card.tsx +48 -0
  239. package/src/ui/data-display/index.tsx +50 -0
  240. package/src/ui/data-display/iphone-15-pro.tsx +114 -0
  241. package/src/ui/data-display/kanban/index.ts +4 -0
  242. package/src/ui/data-display/kanban/kanban-board.tsx +192 -0
  243. package/src/ui/data-display/kanban/kanban-column.tsx +74 -0
  244. package/src/ui/data-display/kanban/kanban-item.tsx +50 -0
  245. package/src/ui/data-display/kanban/kanban-types.ts +21 -0
  246. package/src/ui/data-display/kpi-card.tsx +68 -0
  247. package/src/ui/data-display/media-grid.tsx +110 -0
  248. package/src/ui/data-display/safari.tsx +175 -0
  249. package/src/ui/data-display/show-more-text.tsx +55 -0
  250. package/src/ui/data-display/tabs.tsx +68 -0
  251. package/src/ui/data-display/timeline.tsx +256 -0
  252. package/src/ui/feedback/alert.tsx +60 -0
  253. package/src/ui/feedback/context-menu.tsx +245 -0
  254. package/src/ui/feedback/drawer.tsx +132 -0
  255. package/src/ui/feedback/error-dialog.tsx +273 -0
  256. package/src/ui/feedback/index.tsx +183 -0
  257. package/src/ui/feedback/progress.tsx +32 -0
  258. package/src/ui/feedback/sheet.tsx +148 -0
  259. package/src/ui/feedback/sonner.tsx +36 -0
  260. package/src/ui/forms/command.tsx +157 -0
  261. package/src/ui/forms/date-picker.tsx +73 -0
  262. package/src/ui/forms/date-range-picker.tsx +76 -0
  263. package/src/ui/forms/date-time-picker.tsx +109 -0
  264. package/src/ui/forms/editor/editor-menu-bar.tsx +394 -0
  265. package/src/ui/forms/editor/index.tsx +130 -0
  266. package/src/ui/forms/editor/multi-select-example.tsx +1234 -0
  267. package/src/ui/forms/emoji-picker.tsx +109 -0
  268. package/src/ui/forms/file-dropzone.tsx +169 -0
  269. package/src/ui/forms/file-thumbnail.tsx +29 -0
  270. package/src/ui/forms/index.tsx +201 -0
  271. package/src/ui/forms/input-file.tsx +99 -0
  272. package/src/ui/forms/input-group.tsx +46 -0
  273. package/src/ui/forms/input-otp.tsx +81 -0
  274. package/src/ui/forms/input-phone.tsx +172 -0
  275. package/src/ui/forms/input-spin.tsx +116 -0
  276. package/src/ui/forms/input-tags.tsx +219 -0
  277. package/src/ui/forms/input-time.tsx +42 -0
  278. package/src/ui/forms/multi-select.tsx +629 -0
  279. package/src/ui/forms/multiple-date-picker.tsx +74 -0
  280. package/src/ui/forms/radio-group.tsx +42 -0
  281. package/src/ui/forms/rating.tsx +158 -0
  282. package/src/ui/forms/time-picker.tsx +57 -0
  283. package/src/ui/index.tsx +17 -0
  284. package/src/ui/layout/animated-list.tsx +77 -0
  285. package/src/ui/layout/animated-sidebar.tsx +294 -0
  286. package/src/ui/layout/command-menu.tsx +355 -0
  287. package/src/ui/layout/customizer.tsx +324 -0
  288. package/src/ui/layout/footer.tsx +43 -0
  289. package/src/ui/layout/full-screen-toggle.tsx +52 -0
  290. package/src/ui/layout/header-breadcrumb.tsx +77 -0
  291. package/src/ui/layout/horizontal-layout-header.tsx +83 -0
  292. package/src/ui/layout/horizontal-layout.tsx +50 -0
  293. package/src/ui/layout/index.tsx +25 -0
  294. package/src/ui/layout/language-dropdown.tsx +103 -0
  295. package/src/ui/layout/logo.tsx +63 -0
  296. package/src/ui/layout/main-layout.tsx +57 -0
  297. package/src/ui/layout/mode-dropdown.tsx +58 -0
  298. package/src/ui/layout/notification-dropdown.tsx +127 -0
  299. package/src/ui/layout/page-tabs.tsx +306 -0
  300. package/src/ui/layout/route-cache.tsx +214 -0
  301. package/src/ui/layout/sidebar-group-icon-menu.tsx +195 -0
  302. package/src/ui/layout/sidebar.tsx +279 -0
  303. package/src/ui/layout/tab-content-cache.tsx +201 -0
  304. package/src/ui/layout/tab-navigation-provider.tsx +536 -0
  305. package/src/ui/layout/toggle-mobile-sidebar.tsx +33 -0
  306. package/src/ui/layout/top-bar-header-menubar.tsx +412 -0
  307. package/src/ui/layout/user-dropdown.tsx +188 -0
  308. package/src/ui/layout/vertical-layout-header.tsx +65 -0
  309. package/src/ui/layout/vertical-layout.tsx +47 -0
  310. package/src/ui/management/audit-log-page.tsx +209 -0
  311. package/src/ui/management/cache-management.tsx +349 -0
  312. package/src/ui/management/index.ts +3 -0
  313. package/src/ui/management/job-management.tsx +308 -0
  314. package/src/ui/pages/not-found.tsx +30 -0
  315. package/src/ui/primitives/badge.tsx +66 -0
  316. package/src/ui/primitives/breadcrumb.tsx +103 -0
  317. package/src/ui/primitives/button.tsx +129 -0
  318. package/src/ui/primitives/calendar.tsx +74 -0
  319. package/src/ui/primitives/card.tsx +86 -0
  320. package/src/ui/primitives/checkbox.tsx +31 -0
  321. package/src/ui/primitives/client.ts +30 -0
  322. package/src/ui/primitives/combobox.tsx +290 -0
  323. package/src/ui/primitives/dialog.tsx +121 -0
  324. package/src/ui/primitives/dropdown-menu.tsx +239 -0
  325. package/src/ui/primitives/dynamic-icon.tsx +24 -0
  326. package/src/ui/primitives/index.tsx +134 -0
  327. package/src/ui/primitives/input-number.tsx +131 -0
  328. package/src/ui/primitives/input.tsx +22 -0
  329. package/src/ui/primitives/keyboard.tsx +23 -0
  330. package/src/ui/primitives/label.tsx +24 -0
  331. package/src/ui/primitives/menubar.tsx +262 -0
  332. package/src/ui/primitives/navigation-menu.tsx +157 -0
  333. package/src/ui/primitives/pagination.tsx +118 -0
  334. package/src/ui/primitives/popover.tsx +56 -0
  335. package/src/ui/primitives/prefetch-link.tsx +60 -0
  336. package/src/ui/primitives/resizable.tsx +59 -0
  337. package/src/ui/primitives/scroll-area.tsx +63 -0
  338. package/src/ui/primitives/select.tsx +172 -0
  339. package/src/ui/primitives/separator.tsx +51 -0
  340. package/src/ui/primitives/sidebar.tsx +844 -0
  341. package/src/ui/primitives/slider.tsx +27 -0
  342. package/src/ui/primitives/status-badge.tsx +47 -0
  343. package/src/ui/primitives/sticky-layout.tsx +50 -0
  344. package/src/ui/primitives/switch.tsx +29 -0
  345. package/src/ui/primitives/table.tsx +116 -0
  346. package/src/ui/primitives/tabs.tsx +55 -0
  347. package/src/ui/primitives/toggle-group.tsx +70 -0
  348. package/src/ui/primitives/toggle.tsx +47 -0
  349. package/src/ui/primitives/tooltip.tsx +59 -0
  350. package/src/user/components/dangerous-zone.tsx +34 -0
  351. package/src/user/components/delete-account-form.tsx +40 -0
  352. package/src/user/components/index.ts +4 -0
  353. package/src/user/components/profile-info-form.tsx +390 -0
  354. package/src/user/components/profile-info.tsx +32 -0
  355. package/src/user/components/unified-profile-dialog.tsx +1019 -0
  356. package/src/user/components/user-stats.tsx +27 -0
  357. package/src/user/components/user-toolbar.tsx +137 -0
  358. package/src/user/components/users-card-view.tsx +253 -0
  359. package/src/user/index.ts +11 -0
  360. package/src/user/pages/user-list-page.tsx +234 -0
  361. package/src/user/pages/users-client-page.tsx +385 -0
  362. package/src/user/profile-page.tsx +19 -0
  363. package/src/user/schemas.ts +68 -0
  364. package/src/user/types.ts +34 -0
  365. package/src/user/user-service.ts +538 -0
  366. package/src/utils/index.ts +906 -0
  367. package/src/workflow/activity-timeline.tsx +412 -0
  368. package/src/workflow/approval-workflow.tsx +31 -0
  369. package/src/workflow/index.ts +2 -0
  370. package/dist/audit/index.d.mts +0 -115
  371. package/dist/audit/index.d.ts +0 -115
  372. package/dist/audit/index.js +0 -204
  373. package/dist/audit/index.js.map +0 -1
  374. package/dist/audit/index.mjs +0 -200
  375. package/dist/audit/index.mjs.map +0 -1
  376. package/dist/auth/index.d.mts +0 -86
  377. package/dist/auth/index.d.ts +0 -86
  378. package/dist/auth/index.js +0 -210
  379. package/dist/auth/index.js.map +0 -1
  380. package/dist/auth/index.mjs +0 -198
  381. package/dist/auth/index.mjs.map +0 -1
  382. package/dist/button-1dWvP9Ib.d.mts +0 -30
  383. package/dist/button-1dWvP9Ib.d.ts +0 -30
  384. package/dist/calendar-2QzdEo1z.d.mts +0 -20
  385. package/dist/calendar-2QzdEo1z.d.ts +0 -20
  386. package/dist/code-generation/index.d.mts +0 -30
  387. package/dist/code-generation/index.d.ts +0 -30
  388. package/dist/code-generation/index.js +0 -31
  389. package/dist/code-generation/index.js.map +0 -1
  390. package/dist/code-generation/index.mjs +0 -28
  391. package/dist/code-generation/index.mjs.map +0 -1
  392. package/dist/configs/index.d.mts +0 -175
  393. package/dist/configs/index.d.ts +0 -175
  394. package/dist/configs/index.js +0 -254
  395. package/dist/configs/index.js.map +0 -1
  396. package/dist/configs/index.mjs +0 -233
  397. package/dist/configs/index.mjs.map +0 -1
  398. package/dist/crud/index.d.mts +0 -646
  399. package/dist/crud/index.d.ts +0 -646
  400. package/dist/crud/index.js +0 -11772
  401. package/dist/crud/index.js.map +0 -1
  402. package/dist/crud/index.mjs +0 -11665
  403. package/dist/crud/index.mjs.map +0 -1
  404. package/dist/crud/server.d.mts +0 -20
  405. package/dist/crud/server.d.ts +0 -20
  406. package/dist/crud/server.js +0 -123
  407. package/dist/crud/server.js.map +0 -1
  408. package/dist/crud/server.mjs +0 -120
  409. package/dist/crud/server.mjs.map +0 -1
  410. package/dist/data-table-skeleton-12NA8Mjx.d.mts +0 -39
  411. package/dist/data-table-skeleton-12NA8Mjx.d.ts +0 -39
  412. package/dist/dialog-bKfjZMTd.d.mts +0 -22
  413. package/dist/dialog-bKfjZMTd.d.ts +0 -22
  414. package/dist/dynamic-icon-DrGIiu2N.d.mts +0 -10
  415. package/dist/dynamic-icon-DrGIiu2N.d.ts +0 -10
  416. package/dist/home/index.d.mts +0 -269
  417. package/dist/home/index.d.ts +0 -269
  418. package/dist/home/index.js +0 -1678
  419. package/dist/home/index.js.map +0 -1
  420. package/dist/home/index.mjs +0 -1635
  421. package/dist/home/index.mjs.map +0 -1
  422. package/dist/hooks/index.d.mts +0 -7
  423. package/dist/hooks/index.d.ts +0 -7
  424. package/dist/hooks/index.js +0 -8316
  425. package/dist/hooks/index.js.map +0 -1
  426. package/dist/hooks/index.mjs +0 -8255
  427. package/dist/hooks/index.mjs.map +0 -1
  428. package/dist/index-50hpiPrV.d.ts +0 -116
  429. package/dist/index-B9zQVEVi.d.mts +0 -116
  430. package/dist/index.d.mts +0 -5
  431. package/dist/index.d.ts +0 -5
  432. package/dist/index.js +0 -123
  433. package/dist/index.js.map +0 -1
  434. package/dist/index.mjs +0 -118
  435. package/dist/index.mjs.map +0 -1
  436. package/dist/infrastructure/index.d.mts +0 -423
  437. package/dist/infrastructure/index.d.ts +0 -423
  438. package/dist/infrastructure/index.js +0 -633
  439. package/dist/infrastructure/index.js.map +0 -1
  440. package/dist/infrastructure/index.mjs +0 -619
  441. package/dist/infrastructure/index.mjs.map +0 -1
  442. package/dist/label-DWTEkNPo.d.ts +0 -226
  443. package/dist/label-LPpdcoBx.d.mts +0 -226
  444. package/dist/layout/index.d.mts +0 -48
  445. package/dist/layout/index.d.ts +0 -48
  446. package/dist/layout/index.js +0 -117
  447. package/dist/layout/index.js.map +0 -1
  448. package/dist/layout/index.mjs +0 -90
  449. package/dist/layout/index.mjs.map +0 -1
  450. package/dist/navigation/index.d.mts +0 -16
  451. package/dist/navigation/index.d.ts +0 -16
  452. package/dist/navigation/index.js +0 -53
  453. package/dist/navigation/index.js.map +0 -1
  454. package/dist/navigation/index.mjs +0 -50
  455. package/dist/navigation/index.mjs.map +0 -1
  456. package/dist/notification/index.d.mts +0 -105
  457. package/dist/notification/index.d.ts +0 -105
  458. package/dist/notification/index.js +0 -278
  459. package/dist/notification/index.js.map +0 -1
  460. package/dist/notification/index.mjs +0 -274
  461. package/dist/notification/index.mjs.map +0 -1
  462. package/dist/organization/index.d.mts +0 -99
  463. package/dist/organization/index.d.ts +0 -99
  464. package/dist/organization/index.js +0 -360
  465. package/dist/organization/index.js.map +0 -1
  466. package/dist/organization/index.mjs +0 -352
  467. package/dist/organization/index.mjs.map +0 -1
  468. package/dist/plugin/index.d.mts +0 -83
  469. package/dist/plugin/index.d.ts +0 -83
  470. package/dist/plugin/index.js +0 -86
  471. package/dist/plugin/index.js.map +0 -1
  472. package/dist/plugin/index.mjs +0 -84
  473. package/dist/plugin/index.mjs.map +0 -1
  474. package/dist/providers/index.d.mts +0 -25
  475. package/dist/providers/index.d.ts +0 -25
  476. package/dist/providers/index.js +0 -84
  477. package/dist/providers/index.js.map +0 -1
  478. package/dist/providers/index.mjs +0 -77
  479. package/dist/providers/index.mjs.map +0 -1
  480. package/dist/rbac/index.d.mts +0 -226
  481. package/dist/rbac/index.d.ts +0 -226
  482. package/dist/rbac/index.js +0 -4784
  483. package/dist/rbac/index.js.map +0 -1
  484. package/dist/rbac/index.mjs +0 -4722
  485. package/dist/rbac/index.mjs.map +0 -1
  486. package/dist/rbac/permissions.d.mts +0 -26
  487. package/dist/rbac/permissions.d.ts +0 -26
  488. package/dist/rbac/permissions.js +0 -94
  489. package/dist/rbac/permissions.js.map +0 -1
  490. package/dist/rbac/permissions.mjs +0 -90
  491. package/dist/rbac/permissions.mjs.map +0 -1
  492. package/dist/rbac/server.d.mts +0 -1
  493. package/dist/rbac/server.d.ts +0 -1
  494. package/dist/rbac/server.js +0 -128
  495. package/dist/rbac/server.js.map +0 -1
  496. package/dist/rbac/server.mjs +0 -124
  497. package/dist/rbac/server.mjs.map +0 -1
  498. package/dist/schemas/index.d.mts +0 -1257
  499. package/dist/schemas/index.d.ts +0 -1257
  500. package/dist/schemas/index.js +0 -572
  501. package/dist/schemas/index.js.map +0 -1
  502. package/dist/schemas/index.mjs +0 -523
  503. package/dist/schemas/index.mjs.map +0 -1
  504. package/dist/server-QuYCTa89.d.mts +0 -83
  505. package/dist/server-QuYCTa89.d.ts +0 -83
  506. package/dist/sonner-C74GlRDQ.d.mts +0 -71
  507. package/dist/sonner-C74GlRDQ.d.ts +0 -71
  508. package/dist/status-BOXZgIqX.d.mts +0 -12
  509. package/dist/status-BOXZgIqX.d.ts +0 -12
  510. package/dist/system/index.d.mts +0 -77
  511. package/dist/system/index.d.ts +0 -77
  512. package/dist/system/index.js +0 -102
  513. package/dist/system/index.js.map +0 -1
  514. package/dist/system/index.mjs +0 -100
  515. package/dist/system/index.mjs.map +0 -1
  516. package/dist/tabs-C6FfBwPY.d.mts +0 -18
  517. package/dist/tabs-C6FfBwPY.d.ts +0 -18
  518. package/dist/tenant-provider-B8eC_Wpb.d.mts +0 -27
  519. package/dist/tenant-provider-B8eC_Wpb.d.ts +0 -27
  520. package/dist/types/index.d.mts +0 -469
  521. package/dist/types/index.d.ts +0 -469
  522. package/dist/types/index.js +0 -25
  523. package/dist/types/index.js.map +0 -1
  524. package/dist/types/index.mjs +0 -21
  525. package/dist/types/index.mjs.map +0 -1
  526. package/dist/ui/auth.d.mts +0 -39
  527. package/dist/ui/auth.d.ts +0 -39
  528. package/dist/ui/auth.js +0 -4941
  529. package/dist/ui/auth.js.map +0 -1
  530. package/dist/ui/auth.mjs +0 -4896
  531. package/dist/ui/auth.mjs.map +0 -1
  532. package/dist/ui/crud.d.mts +0 -2
  533. package/dist/ui/crud.d.ts +0 -2
  534. package/dist/ui/crud.js +0 -4
  535. package/dist/ui/crud.js.map +0 -1
  536. package/dist/ui/crud.mjs +0 -3
  537. package/dist/ui/crud.mjs.map +0 -1
  538. package/dist/ui/data-display.d.mts +0 -596
  539. package/dist/ui/data-display.d.ts +0 -596
  540. package/dist/ui/data-display.js +0 -5307
  541. package/dist/ui/data-display.js.map +0 -1
  542. package/dist/ui/data-display.mjs +0 -5212
  543. package/dist/ui/data-display.mjs.map +0 -1
  544. package/dist/ui/feedback.d.mts +0 -55
  545. package/dist/ui/feedback.d.ts +0 -55
  546. package/dist/ui/feedback.js +0 -2608
  547. package/dist/ui/feedback.js.map +0 -1
  548. package/dist/ui/feedback.mjs +0 -2526
  549. package/dist/ui/feedback.mjs.map +0 -1
  550. package/dist/ui/forms.d.mts +0 -309
  551. package/dist/ui/forms.d.ts +0 -309
  552. package/dist/ui/forms.js +0 -4656
  553. package/dist/ui/forms.js.map +0 -1
  554. package/dist/ui/forms.mjs +0 -4571
  555. package/dist/ui/forms.mjs.map +0 -1
  556. package/dist/ui/index.d.mts +0 -331
  557. package/dist/ui/index.d.ts +0 -331
  558. package/dist/ui/index.js +0 -16953
  559. package/dist/ui/index.js.map +0 -1
  560. package/dist/ui/index.mjs +0 -16598
  561. package/dist/ui/index.mjs.map +0 -1
  562. package/dist/ui/primitives/client.d.mts +0 -61
  563. package/dist/ui/primitives/client.d.ts +0 -61
  564. package/dist/ui/primitives/client.js +0 -3408
  565. package/dist/ui/primitives/client.js.map +0 -1
  566. package/dist/ui/primitives/client.mjs +0 -3256
  567. package/dist/ui/primitives/client.mjs.map +0 -1
  568. package/dist/ui/primitives.d.mts +0 -113
  569. package/dist/ui/primitives.d.ts +0 -113
  570. package/dist/ui/primitives.js +0 -3356
  571. package/dist/ui/primitives.js.map +0 -1
  572. package/dist/ui/primitives.mjs +0 -3227
  573. package/dist/ui/primitives.mjs.map +0 -1
  574. package/dist/user/index.d.mts +0 -228
  575. package/dist/user/index.d.ts +0 -228
  576. package/dist/user/index.js +0 -4306
  577. package/dist/user/index.js.map +0 -1
  578. package/dist/user/index.mjs +0 -4260
  579. package/dist/user/index.mjs.map +0 -1
  580. package/dist/utils/index.d.mts +0 -205
  581. package/dist/utils/index.d.ts +0 -205
  582. package/dist/utils/index.js +0 -574
  583. package/dist/utils/index.js.map +0 -1
  584. package/dist/utils/index.mjs +0 -514
  585. package/dist/utils/index.mjs.map +0 -1
  586. package/dist/workflow/index.d.mts +0 -40
  587. package/dist/workflow/index.d.ts +0 -40
  588. package/dist/workflow/index.js +0 -3710
  589. package/dist/workflow/index.js.map +0 -1
  590. package/dist/workflow/index.mjs +0 -3677
  591. package/dist/workflow/index.mjs.map +0 -1
@@ -0,0 +1,1234 @@
1
+ import * as React from "react";
2
+ import { cva } from "class-variance-authority";
3
+ import type { VariantProps } from "class-variance-authority";
4
+ import {
5
+ CheckIcon,
6
+ XCircle,
7
+ ChevronDown,
8
+ XIcon,
9
+ WandSparkles,
10
+ } from "lucide-react";
11
+
12
+ import { cn } from "../../../utils";
13
+ import { Separator } from "../../primitives";
14
+ import { Button } from "../../primitives";
15
+ import { Badge } from "../../primitives";
16
+ import { Popover, PopoverContent, PopoverTrigger } from "../../primitives";
17
+ import {
18
+ Command,
19
+ CommandEmpty,
20
+ CommandGroup,
21
+ CommandInput,
22
+ CommandItem,
23
+ CommandList,
24
+ CommandSeparator,
25
+ } from "../command";
26
+
27
+ /**
28
+ * Animation types and configurations
29
+ */
30
+ export interface AnimationConfig {
31
+ /** Badge animation type */
32
+ badgeAnimation?: "bounce" | "pulse" | "wiggle" | "fade" | "slide" | "none";
33
+ /** Popover animation type */
34
+ popoverAnimation?: "scale" | "slide" | "fade" | "flip" | "none";
35
+ /** Option hover animation type */
36
+ optionHoverAnimation?: "highlight" | "scale" | "glow" | "none";
37
+ /** Animation duration in seconds */
38
+ duration?: number;
39
+ /** Animation delay in seconds */
40
+ delay?: number;
41
+ }
42
+
43
+ /**
44
+ * Variants for the multi-select component to handle different styles.
45
+ * Uses class-variance-authority (cva) to define different styles based on "variant" prop.
46
+ */
47
+ const multiSelectVariants = cva("m-1 transition-all duration-300 ease-in-out", {
48
+ variants: {
49
+ variant: {
50
+ default: "border-foreground/10 text-foreground bg-card hover:bg-card/80",
51
+ secondary:
52
+ "border-foreground/10 bg-secondary text-secondary-foreground hover:bg-secondary/80",
53
+ destructive:
54
+ "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
55
+ inverted: "inverted",
56
+ },
57
+ badgeAnimation: {
58
+ bounce: "hover:-translate-y-1 hover:scale-110",
59
+ pulse: "hover:animate-pulse",
60
+ wiggle: "hover:animate-wiggle",
61
+ fade: "hover:opacity-80",
62
+ slide: "hover:translate-x-1",
63
+ none: "",
64
+ },
65
+ },
66
+ defaultVariants: {
67
+ variant: "default",
68
+ badgeAnimation: "bounce",
69
+ },
70
+ });
71
+
72
+ /**
73
+ * Option interface for MultiSelect component
74
+ */
75
+ interface MultiSelectOption {
76
+ /** The text to display for the option. */
77
+ label: string;
78
+ /** The unique value associated with the option. */
79
+ value: string;
80
+ /** Optional icon component to display alongside the option. */
81
+ icon?: React.ComponentType<{ className?: string }>;
82
+ /** Whether this option is disabled */
83
+ disabled?: boolean;
84
+ /** Custom styling for the option */
85
+ style?: {
86
+ /** Custom badge color */
87
+ badgeColor?: string;
88
+ /** Custom icon color */
89
+ iconColor?: string;
90
+ /** Gradient background for badge */
91
+ gradient?: string;
92
+ };
93
+ }
94
+
95
+ /**
96
+ * Group interface for organizing options
97
+ */
98
+ interface MultiSelectGroup {
99
+ /** Group heading */
100
+ heading: string;
101
+ /** Options in this group */
102
+ options: MultiSelectOption[];
103
+ }
104
+
105
+ /**
106
+ * Props for MultiSelect component
107
+ */
108
+ interface MultiSelectProps
109
+ extends Omit<
110
+ React.ButtonHTMLAttributes<HTMLButtonElement>,
111
+ "animationConfig"
112
+ >,
113
+ VariantProps<typeof multiSelectVariants> {
114
+ /**
115
+ * An array of option objects or groups to be displayed in the multi-select component.
116
+ */
117
+ options: MultiSelectOption[] | MultiSelectGroup[];
118
+ /**
119
+ * Callback function triggered when the selected values change.
120
+ * Receives an array of the new selected values.
121
+ */
122
+ onValueChange: (value: string[]) => void;
123
+
124
+ /** The default selected values when the component mounts. */
125
+ defaultValue?: string[];
126
+
127
+ /**
128
+ * Placeholder text to be displayed when no values are selected.
129
+ * Optional, defaults to "Select options".
130
+ */
131
+ placeholder?: string;
132
+
133
+ /**
134
+ * Animation duration in seconds for the visual effects (e.g., bouncing badges).
135
+ * Optional, defaults to 0 (no animation).
136
+ */
137
+ animation?: number;
138
+
139
+ /**
140
+ * Advanced animation configuration for different component parts.
141
+ * Optional, allows fine-tuning of various animation effects.
142
+ */
143
+ animationConfig?: AnimationConfig;
144
+
145
+ /**
146
+ * Maximum number of items to display. Extra selected items will be summarized.
147
+ * Optional, defaults to 3.
148
+ */
149
+ maxCount?: number;
150
+
151
+ /**
152
+ * The modality of the popover. When set to true, interaction with outside elements
153
+ * will be disabled and only popover content will be visible to screen readers.
154
+ * Optional, defaults to false.
155
+ */
156
+ modalPopover?: boolean;
157
+
158
+ /**
159
+ * If true, renders the multi-select component as a child of another component.
160
+ * Optional, defaults to false.
161
+ */
162
+ asChild?: boolean;
163
+
164
+ /**
165
+ * Additional class names to apply custom styles to the multi-select component.
166
+ * Optional, can be used to add custom styles.
167
+ */
168
+ className?: string;
169
+
170
+ /**
171
+ * If true, disables the select all functionality.
172
+ * Optional, defaults to false.
173
+ */
174
+ hideSelectAll?: boolean;
175
+
176
+ /**
177
+ * If true, shows search functionality in the popover.
178
+ * If false, hides the search input completely.
179
+ * Optional, defaults to true.
180
+ */
181
+ searchable?: boolean;
182
+
183
+ /**
184
+ * Custom empty state message when no options match search.
185
+ * Optional, defaults to "No results found."
186
+ */
187
+ emptyIndicator?: React.ReactNode;
188
+
189
+ /**
190
+ * If true, allows the component to grow and shrink with its content.
191
+ * If false, uses fixed width behavior.
192
+ * Optional, defaults to false.
193
+ */
194
+ autoSize?: boolean;
195
+
196
+ /**
197
+ * If true, shows badges in a single line with horizontal scroll.
198
+ * If false, badges wrap to multiple lines.
199
+ * Optional, defaults to false.
200
+ */
201
+ singleLine?: boolean;
202
+
203
+ /**
204
+ * Custom CSS class for the popover content.
205
+ * Optional, can be used to customize popover appearance.
206
+ */
207
+ popoverClassName?: string;
208
+
209
+ /**
210
+ * If true, disables the component completely.
211
+ * Optional, defaults to false.
212
+ */
213
+ disabled?: boolean;
214
+
215
+ /**
216
+ * Responsive configuration for different screen sizes.
217
+ * Allows customizing maxCount and other properties based on viewport.
218
+ * Can be boolean true for default responsive behavior or an object for custom configuration.
219
+ */
220
+ responsive?:
221
+ | boolean
222
+ | {
223
+ /** Configuration for mobile devices (< 640px) */
224
+ mobile?: {
225
+ maxCount?: number;
226
+ hideIcons?: boolean;
227
+ compactMode?: boolean;
228
+ };
229
+ /** Configuration for tablet devices (640px - 1024px) */
230
+ tablet?: {
231
+ maxCount?: number;
232
+ hideIcons?: boolean;
233
+ compactMode?: boolean;
234
+ };
235
+ /** Configuration for desktop devices (> 1024px) */
236
+ desktop?: {
237
+ maxCount?: number;
238
+ hideIcons?: boolean;
239
+ compactMode?: boolean;
240
+ };
241
+ };
242
+
243
+ /**
244
+ * Minimum width for the component.
245
+ * Optional, defaults to auto-sizing based on content.
246
+ * When set, component will not shrink below this width.
247
+ */
248
+ minWidth?: string;
249
+
250
+ /**
251
+ * Maximum width for the component.
252
+ * Optional, defaults to 100% of container.
253
+ * Component will not exceed container boundaries.
254
+ */
255
+ maxWidth?: string;
256
+
257
+ /**
258
+ * If true, automatically removes duplicate options based on their value.
259
+ * Optional, defaults to false (shows warning in dev mode instead).
260
+ */
261
+ deduplicateOptions?: boolean;
262
+
263
+ /**
264
+ * If true, the component will reset its internal state when defaultValue changes.
265
+ * Useful for React Hook Form integration and form reset functionality.
266
+ * Optional, defaults to true.
267
+ */
268
+ resetOnDefaultValueChange?: boolean;
269
+
270
+ /**
271
+ * If true, automatically closes the popover after selecting an option.
272
+ * Useful for single-selection-like behavior or mobile UX.
273
+ * Optional, defaults to false.
274
+ */
275
+ closeOnSelect?: boolean;
276
+ }
277
+
278
+ /**
279
+ * Imperative methods exposed through ref
280
+ */
281
+ export interface MultiSelectRef {
282
+ /**
283
+ * Programmatically reset the component to its default value
284
+ */
285
+ reset: () => void;
286
+ /**
287
+ * Get current selected values
288
+ */
289
+ getSelectedValues: () => string[];
290
+ /**
291
+ * Set selected values programmatically
292
+ */
293
+ setSelectedValues: (values: string[]) => void;
294
+ /**
295
+ * Clear all selected values
296
+ */
297
+ clear: () => void;
298
+ /**
299
+ * Focus the component
300
+ */
301
+ focus: () => void;
302
+ }
303
+
304
+ export const MultiSelect = React.forwardRef<MultiSelectRef, MultiSelectProps>(
305
+ (
306
+ {
307
+ options,
308
+ onValueChange,
309
+ variant,
310
+ defaultValue = [],
311
+ placeholder = "Select options",
312
+ animation = 0,
313
+ animationConfig,
314
+ maxCount = 3,
315
+ modalPopover = false,
316
+ asChild = false,
317
+ className,
318
+ hideSelectAll = false,
319
+ searchable = true,
320
+ emptyIndicator,
321
+ autoSize = false,
322
+ singleLine = false,
323
+ popoverClassName,
324
+ disabled = false,
325
+ responsive,
326
+ minWidth,
327
+ maxWidth,
328
+ deduplicateOptions = false,
329
+ resetOnDefaultValueChange = true,
330
+ closeOnSelect = false,
331
+ ...props
332
+ },
333
+ ref,
334
+ ) => {
335
+ const [selectedValues, setSelectedValues] =
336
+ React.useState<string[]>(defaultValue);
337
+ const [isPopoverOpen, setIsPopoverOpen] = React.useState(false);
338
+ const [isAnimating, setIsAnimating] = React.useState(false);
339
+ const [searchValue, setSearchValue] = React.useState("");
340
+
341
+ const [politeMessage, setPoliteMessage] = React.useState("");
342
+ const [assertiveMessage, setAssertiveMessage] = React.useState("");
343
+ const prevSelectedCount = React.useRef(selectedValues.length);
344
+ const prevIsOpen = React.useRef(isPopoverOpen);
345
+ const prevSearchValue = React.useRef(searchValue);
346
+
347
+ const announce = React.useCallback(
348
+ (message: string, priority: "polite" | "assertive" = "polite") => {
349
+ if (priority === "assertive") {
350
+ setAssertiveMessage(message);
351
+ setTimeout(() => setAssertiveMessage(""), 100);
352
+ } else {
353
+ setPoliteMessage(message);
354
+ setTimeout(() => setPoliteMessage(""), 100);
355
+ }
356
+ },
357
+ [],
358
+ );
359
+
360
+ const multiSelectId = React.useId();
361
+ const listboxId = `${multiSelectId}-listbox`;
362
+ const triggerDescriptionId = `${multiSelectId}-description`;
363
+ const selectedCountId = `${multiSelectId}-count`;
364
+
365
+ const prevDefaultValueRef = React.useRef<string[]>(defaultValue);
366
+
367
+ const isGroupedOptions = React.useCallback(
368
+ (
369
+ opts: MultiSelectOption[] | MultiSelectGroup[],
370
+ ): opts is MultiSelectGroup[] => {
371
+ return opts.length > 0 && "heading" in opts[0];
372
+ },
373
+ [],
374
+ );
375
+
376
+ const arraysEqual = React.useCallback(
377
+ (a: string[], b: string[]): boolean => {
378
+ if (a.length !== b.length) return false;
379
+ const sortedA = [...a].sort();
380
+ const sortedB = [...b].sort();
381
+ return sortedA.every((val, index) => val === sortedB[index]);
382
+ },
383
+ [],
384
+ );
385
+
386
+ const resetToDefault = React.useCallback(() => {
387
+ setSelectedValues(defaultValue);
388
+ setIsPopoverOpen(false);
389
+ setSearchValue("");
390
+ onValueChange(defaultValue);
391
+ }, [defaultValue, onValueChange]);
392
+
393
+ const buttonRef = React.useRef<HTMLButtonElement>(null);
394
+
395
+ React.useImperativeHandle(
396
+ ref,
397
+ () => ({
398
+ reset: resetToDefault,
399
+ getSelectedValues: () => selectedValues,
400
+ setSelectedValues: (values: string[]) => {
401
+ setSelectedValues(values);
402
+ onValueChange(values);
403
+ },
404
+ clear: () => {
405
+ setSelectedValues([]);
406
+ onValueChange([]);
407
+ },
408
+ focus: () => {
409
+ if (buttonRef.current) {
410
+ buttonRef.current.focus();
411
+ const originalOutline = buttonRef.current.style.outline;
412
+ const originalOutlineOffset = buttonRef.current.style.outlineOffset;
413
+ buttonRef.current.style.outline = "2px solid hsl(var(--ring))";
414
+ buttonRef.current.style.outlineOffset = "2px";
415
+ setTimeout(() => {
416
+ if (buttonRef.current) {
417
+ buttonRef.current.style.outline = originalOutline;
418
+ buttonRef.current.style.outlineOffset = originalOutlineOffset;
419
+ }
420
+ }, 1000);
421
+ }
422
+ },
423
+ }),
424
+ [resetToDefault, selectedValues, onValueChange],
425
+ );
426
+
427
+ const [screenSize, setScreenSize] = React.useState<
428
+ "mobile" | "tablet" | "desktop"
429
+ >("desktop");
430
+
431
+ React.useEffect(() => {
432
+ if (typeof window === "undefined") return;
433
+ const handleResize = () => {
434
+ const width = window.innerWidth;
435
+ if (width < 640) {
436
+ setScreenSize("mobile");
437
+ } else if (width < 1024) {
438
+ setScreenSize("tablet");
439
+ } else {
440
+ setScreenSize("desktop");
441
+ }
442
+ };
443
+ handleResize();
444
+ window.addEventListener("resize", handleResize);
445
+ return () => {
446
+ if (typeof window !== "undefined") {
447
+ window.removeEventListener("resize", handleResize);
448
+ }
449
+ };
450
+ }, []);
451
+
452
+ const getResponsiveSettings = () => {
453
+ if (!responsive) {
454
+ return {
455
+ maxCount: maxCount,
456
+ hideIcons: false,
457
+ compactMode: false,
458
+ };
459
+ }
460
+ if (responsive === true) {
461
+ const defaultResponsive = {
462
+ mobile: { maxCount: 2, hideIcons: false, compactMode: true },
463
+ tablet: { maxCount: 4, hideIcons: false, compactMode: false },
464
+ desktop: { maxCount: 6, hideIcons: false, compactMode: false },
465
+ };
466
+ const currentSettings = defaultResponsive[screenSize];
467
+ return {
468
+ maxCount: currentSettings?.maxCount ?? maxCount,
469
+ hideIcons: currentSettings?.hideIcons ?? false,
470
+ compactMode: currentSettings?.compactMode ?? false,
471
+ };
472
+ }
473
+ const currentSettings = responsive[screenSize];
474
+ return {
475
+ maxCount: currentSettings?.maxCount ?? maxCount,
476
+ hideIcons: currentSettings?.hideIcons ?? false,
477
+ compactMode: currentSettings?.compactMode ?? false,
478
+ };
479
+ };
480
+
481
+ const responsiveSettings = getResponsiveSettings();
482
+
483
+ const getBadgeAnimationClass = () => {
484
+ if (animationConfig?.badgeAnimation) {
485
+ switch (animationConfig.badgeAnimation) {
486
+ case "bounce":
487
+ return isAnimating
488
+ ? "animate-bounce"
489
+ : "hover:-translate-y-1 hover:scale-110";
490
+ case "pulse":
491
+ return "hover:animate-pulse";
492
+ case "wiggle":
493
+ return "hover:animate-wiggle";
494
+ case "fade":
495
+ return "hover:opacity-80";
496
+ case "slide":
497
+ return "hover:translate-x-1";
498
+ case "none":
499
+ return "";
500
+ default:
501
+ return "";
502
+ }
503
+ }
504
+ return isAnimating ? "animate-bounce" : "";
505
+ };
506
+
507
+ const getPopoverAnimationClass = () => {
508
+ if (animationConfig?.popoverAnimation) {
509
+ switch (animationConfig.popoverAnimation) {
510
+ case "scale":
511
+ return "animate-scaleIn";
512
+ case "slide":
513
+ return "animate-slideInDown";
514
+ case "fade":
515
+ return "animate-fadeIn";
516
+ case "flip":
517
+ return "animate-flipIn";
518
+ case "none":
519
+ return "";
520
+ default:
521
+ return "";
522
+ }
523
+ }
524
+ return "";
525
+ };
526
+
527
+ const getAllOptions = React.useCallback((): MultiSelectOption[] => {
528
+ if (options.length === 0) return [];
529
+ let allOptions: MultiSelectOption[];
530
+ if (isGroupedOptions(options)) {
531
+ allOptions = options.flatMap((group) => group.options);
532
+ } else {
533
+ allOptions = options;
534
+ }
535
+ const valueSet = new Set<string>();
536
+ const duplicates: string[] = [];
537
+ const uniqueOptions: MultiSelectOption[] = [];
538
+ allOptions.forEach((option) => {
539
+ if (valueSet.has(option.value)) {
540
+ duplicates.push(option.value);
541
+ if (!deduplicateOptions) {
542
+ uniqueOptions.push(option);
543
+ }
544
+ } else {
545
+ valueSet.add(option.value);
546
+ uniqueOptions.push(option);
547
+ }
548
+ });
549
+ if (process.env.NODE_ENV === "development" && duplicates.length > 0) {
550
+ const action = deduplicateOptions
551
+ ? "automatically removed"
552
+ : "detected";
553
+ console.warn(
554
+ `MultiSelect: Duplicate option values ${action}: ${duplicates.join(
555
+ ", ",
556
+ )}. ` +
557
+ `${
558
+ deduplicateOptions
559
+ ? "Duplicates have been removed automatically."
560
+ : "This may cause unexpected behavior. Consider setting 'deduplicateOptions={true}' or ensure all option values are unique."
561
+ }`,
562
+ );
563
+ }
564
+ return deduplicateOptions ? uniqueOptions : allOptions;
565
+ }, [options, deduplicateOptions, isGroupedOptions]);
566
+
567
+ const getOptionByValue = React.useCallback(
568
+ (value: string): MultiSelectOption | undefined => {
569
+ const option = getAllOptions().find((option) => option.value === value);
570
+ if (!option && process.env.NODE_ENV === "development") {
571
+ console.warn(
572
+ `MultiSelect: Option with value "${value}" not found in options list`,
573
+ );
574
+ }
575
+ return option;
576
+ },
577
+ [getAllOptions],
578
+ );
579
+
580
+ const filteredOptions = React.useMemo(() => {
581
+ if (!searchable || !searchValue) return options;
582
+ if (options.length === 0) return [];
583
+ if (isGroupedOptions(options)) {
584
+ return options
585
+ .map((group) => ({
586
+ ...group,
587
+ options: group.options.filter(
588
+ (option) =>
589
+ option.label
590
+ .toLowerCase()
591
+ .includes(searchValue.toLowerCase()) ||
592
+ option.value.toLowerCase().includes(searchValue.toLowerCase()),
593
+ ),
594
+ }))
595
+ .filter((group) => group.options.length > 0);
596
+ }
597
+ return options.filter(
598
+ (option) =>
599
+ option.label.toLowerCase().includes(searchValue.toLowerCase()) ||
600
+ option.value.toLowerCase().includes(searchValue.toLowerCase()),
601
+ );
602
+ }, [options, searchValue, searchable, isGroupedOptions]);
603
+
604
+ const handleInputKeyDown = (
605
+ event: React.KeyboardEvent<HTMLInputElement>,
606
+ ) => {
607
+ if (event.key === "Enter") {
608
+ setIsPopoverOpen(true);
609
+ } else if (event.key === "Backspace" && !event.currentTarget.value) {
610
+ const newSelectedValues = [...selectedValues];
611
+ newSelectedValues.pop();
612
+ setSelectedValues(newSelectedValues);
613
+ onValueChange(newSelectedValues);
614
+ }
615
+ };
616
+
617
+ const toggleOption = (optionValue: string) => {
618
+ if (disabled) return;
619
+ const option = getOptionByValue(optionValue);
620
+ if (option?.disabled) return;
621
+ const newSelectedValues = selectedValues.includes(optionValue)
622
+ ? selectedValues.filter((value) => value !== optionValue)
623
+ : [...selectedValues, optionValue];
624
+ setSelectedValues(newSelectedValues);
625
+ onValueChange(newSelectedValues);
626
+ if (closeOnSelect) {
627
+ setIsPopoverOpen(false);
628
+ }
629
+ };
630
+
631
+ const handleClear = () => {
632
+ if (disabled) return;
633
+ setSelectedValues([]);
634
+ onValueChange([]);
635
+ };
636
+
637
+ const handleTogglePopover = () => {
638
+ if (disabled) return;
639
+ setIsPopoverOpen((prev) => !prev);
640
+ };
641
+
642
+ const clearExtraOptions = () => {
643
+ if (disabled) return;
644
+ const newSelectedValues = selectedValues.slice(
645
+ 0,
646
+ responsiveSettings.maxCount,
647
+ );
648
+ setSelectedValues(newSelectedValues);
649
+ onValueChange(newSelectedValues);
650
+ };
651
+
652
+ const toggleAll = () => {
653
+ if (disabled) return;
654
+ const allOptions = getAllOptions().filter((option) => !option.disabled);
655
+ if (selectedValues.length === allOptions.length) {
656
+ handleClear();
657
+ } else {
658
+ const allValues = allOptions.map((option) => option.value);
659
+ setSelectedValues(allValues);
660
+ onValueChange(allValues);
661
+ }
662
+
663
+ if (closeOnSelect) {
664
+ setIsPopoverOpen(false);
665
+ }
666
+ };
667
+
668
+ React.useEffect(() => {
669
+ if (!resetOnDefaultValueChange) return;
670
+ const prevDefaultValue = prevDefaultValueRef.current;
671
+ if (!arraysEqual(prevDefaultValue, defaultValue)) {
672
+ if (!arraysEqual(selectedValues, defaultValue)) {
673
+ setSelectedValues(defaultValue);
674
+ }
675
+ prevDefaultValueRef.current = [...defaultValue];
676
+ }
677
+ }, [defaultValue, selectedValues, arraysEqual, resetOnDefaultValueChange]);
678
+
679
+ const getWidthConstraints = () => {
680
+ const defaultMinWidth = screenSize === "mobile" ? "0px" : "200px";
681
+ const effectiveMinWidth = minWidth || defaultMinWidth;
682
+ const effectiveMaxWidth = maxWidth || "100%";
683
+ return {
684
+ minWidth: effectiveMinWidth,
685
+ maxWidth: effectiveMaxWidth,
686
+ width: autoSize ? "auto" : "100%",
687
+ };
688
+ };
689
+
690
+ const widthConstraints = getWidthConstraints();
691
+
692
+ React.useEffect(() => {
693
+ if (!isPopoverOpen) {
694
+ setSearchValue("");
695
+ }
696
+ }, [isPopoverOpen]);
697
+
698
+ React.useEffect(() => {
699
+ const selectedCount = selectedValues.length;
700
+ const allOptions = getAllOptions();
701
+ const totalOptions = allOptions.filter((opt) => !opt.disabled).length;
702
+ if (selectedCount !== prevSelectedCount.current) {
703
+ const diff = selectedCount - prevSelectedCount.current;
704
+ if (diff > 0) {
705
+ const addedItems = selectedValues.slice(-diff);
706
+ const addedLabels = addedItems
707
+ .map(
708
+ (value) => allOptions.find((opt) => opt.value === value)?.label,
709
+ )
710
+ .filter(Boolean);
711
+
712
+ if (addedLabels.length === 1) {
713
+ announce(
714
+ `${addedLabels[0]} selected. ${selectedCount} of ${totalOptions} options selected.`,
715
+ );
716
+ } else {
717
+ announce(
718
+ `${addedLabels.length} options selected. ${selectedCount} of ${totalOptions} total selected.`,
719
+ );
720
+ }
721
+ } else if (diff < 0) {
722
+ announce(
723
+ `Option removed. ${selectedCount} of ${totalOptions} options selected.`,
724
+ );
725
+ }
726
+ prevSelectedCount.current = selectedCount;
727
+ }
728
+
729
+ if (isPopoverOpen !== prevIsOpen.current) {
730
+ if (isPopoverOpen) {
731
+ announce(
732
+ `Dropdown opened. ${totalOptions} options available. Use arrow keys to navigate.`,
733
+ );
734
+ } else {
735
+ announce("Dropdown closed.");
736
+ }
737
+ prevIsOpen.current = isPopoverOpen;
738
+ }
739
+
740
+ if (
741
+ searchValue !== prevSearchValue.current &&
742
+ searchValue !== undefined
743
+ ) {
744
+ if (searchValue && isPopoverOpen) {
745
+ const filteredCount = allOptions.filter(
746
+ (opt) =>
747
+ opt.label.toLowerCase().includes(searchValue.toLowerCase()) ||
748
+ opt.value.toLowerCase().includes(searchValue.toLowerCase()),
749
+ ).length;
750
+
751
+ announce(
752
+ `${filteredCount} option${
753
+ filteredCount === 1 ? "" : "s"
754
+ } found for "${searchValue}"`,
755
+ );
756
+ }
757
+ prevSearchValue.current = searchValue;
758
+ }
759
+ }, [selectedValues, isPopoverOpen, searchValue, announce, getAllOptions]);
760
+
761
+ return (
762
+ <>
763
+ <div className="sr-only">
764
+ <div aria-live="polite" aria-atomic="true" role="status">
765
+ {politeMessage}
766
+ </div>
767
+ <div aria-live="assertive" aria-atomic="true" role="alert">
768
+ {assertiveMessage}
769
+ </div>
770
+ </div>
771
+
772
+ <Popover
773
+ open={isPopoverOpen}
774
+ onOpenChange={setIsPopoverOpen}
775
+ modal={modalPopover}
776
+ >
777
+ <div id={triggerDescriptionId} className="sr-only">
778
+ Multi-select dropdown. Use arrow keys to navigate, Enter to select,
779
+ and Escape to close.
780
+ </div>
781
+ <div id={selectedCountId} className="sr-only" aria-live="polite">
782
+ {selectedValues.length === 0
783
+ ? "No options selected"
784
+ : `${selectedValues.length} option${
785
+ selectedValues.length === 1 ? "" : "s"
786
+ } selected: ${selectedValues
787
+ .map((value) => getOptionByValue(value)?.label)
788
+ .filter(Boolean)
789
+ .join(", ")}`}
790
+ </div>
791
+
792
+ <PopoverTrigger asChild>
793
+ <Button
794
+ ref={buttonRef}
795
+ {...props}
796
+ onClick={handleTogglePopover}
797
+ disabled={disabled}
798
+ role="combobox"
799
+ aria-expanded={isPopoverOpen}
800
+ aria-haspopup="listbox"
801
+ aria-controls={isPopoverOpen ? listboxId : undefined}
802
+ aria-describedby={`${triggerDescriptionId} ${selectedCountId}`}
803
+ aria-label={`Multi-select: ${selectedValues.length} of ${
804
+ getAllOptions().length
805
+ } options selected. ${placeholder}`}
806
+ className={cn(
807
+ "flex p-1 rounded-md border min-h-10 h-auto items-center justify-between bg-inherit hover:bg-inherit [&_svg]:pointer-events-auto",
808
+ autoSize ? "w-auto" : "w-full",
809
+ responsiveSettings.compactMode && "min-h-8 text-sm",
810
+ screenSize === "mobile" && "min-h-12 text-base",
811
+ disabled && "opacity-50 cursor-not-allowed",
812
+ className,
813
+ )}
814
+ style={{
815
+ ...widthConstraints,
816
+ maxWidth: `min(${widthConstraints.maxWidth}, 100%)`,
817
+ }}
818
+ >
819
+ {selectedValues.length > 0 ? (
820
+ <div className="flex justify-between items-center w-full">
821
+ <div
822
+ className={cn(
823
+ "flex items-center gap-1",
824
+ singleLine
825
+ ? "overflow-x-auto multiselect-singleline-scroll"
826
+ : "flex-wrap",
827
+ responsiveSettings.compactMode && "gap-0.5",
828
+ )}
829
+ style={
830
+ singleLine
831
+ ? {
832
+ paddingBottom: "4px",
833
+ }
834
+ : {}
835
+ }
836
+ >
837
+ {selectedValues
838
+ .slice(0, responsiveSettings.maxCount)
839
+ .map((value) => {
840
+ const option = getOptionByValue(value);
841
+ const IconComponent = option?.icon as any;
842
+ const customStyle = option?.style;
843
+ if (!option) {
844
+ return null;
845
+ }
846
+ const badgeStyle: React.CSSProperties = {
847
+ animationDuration: `${animation}s`,
848
+ ...(customStyle?.badgeColor && {
849
+ backgroundColor: customStyle.badgeColor,
850
+ }),
851
+ ...(customStyle?.gradient && {
852
+ background: customStyle.gradient,
853
+ color: "white",
854
+ }),
855
+ };
856
+ return (
857
+ <Badge
858
+ key={value}
859
+ className={cn(
860
+ getBadgeAnimationClass(),
861
+ multiSelectVariants({ variant }),
862
+ customStyle?.gradient &&
863
+ "text-white border-transparent",
864
+ responsiveSettings.compactMode &&
865
+ "text-xs px-1.5 py-0.5",
866
+ screenSize === "mobile" &&
867
+ "max-w-[120px] truncate",
868
+ singleLine && "flex-shrink-0 whitespace-nowrap",
869
+ "[&>svg]:pointer-events-auto",
870
+ )}
871
+ style={{
872
+ ...badgeStyle,
873
+ animationDuration: `${
874
+ animationConfig?.duration || animation
875
+ }s`,
876
+ animationDelay: `${animationConfig?.delay || 0}s`,
877
+ }}
878
+ >
879
+ {IconComponent && !responsiveSettings.hideIcons && (
880
+ <IconComponent
881
+ className={cn(
882
+ "h-4 w-4 mr-2",
883
+ responsiveSettings.compactMode &&
884
+ "h-3 w-3 mr-1",
885
+ customStyle?.iconColor && "text-current",
886
+ )}
887
+ {...(customStyle?.iconColor && {
888
+ style: { color: customStyle.iconColor },
889
+ })}
890
+ />
891
+ )}
892
+ <span
893
+ className={cn(
894
+ screenSize === "mobile" && "truncate",
895
+ )}
896
+ >
897
+ {option.label}
898
+ </span>
899
+ <div
900
+ role="button"
901
+ tabIndex={0}
902
+ onClick={(event) => {
903
+ event.stopPropagation();
904
+ toggleOption(value);
905
+ }}
906
+ onKeyDown={(event) => {
907
+ if (
908
+ event.key === "Enter" ||
909
+ event.key === " "
910
+ ) {
911
+ event.preventDefault();
912
+ event.stopPropagation();
913
+ toggleOption(value);
914
+ }
915
+ }}
916
+ aria-label={`Remove ${option.label} from selection`}
917
+ className="ml-2 h-4 w-4 cursor-pointer hover:bg-white/20 rounded-sm p-0.5 -m-0.5 focus:outline-none focus:ring-1 focus:ring-white/50"
918
+ >
919
+ <XCircle
920
+ className={cn(
921
+ "h-3 w-3",
922
+ responsiveSettings.compactMode &&
923
+ "h-2.5 w-2.5",
924
+ )}
925
+ />
926
+ </div>
927
+ </Badge>
928
+ );
929
+ })
930
+ .filter(Boolean)}
931
+ {selectedValues.length > responsiveSettings.maxCount && (
932
+ <Badge
933
+ className={cn(
934
+ "bg-transparent text-foreground border-foreground/1 hover:bg-transparent",
935
+ getBadgeAnimationClass(),
936
+ multiSelectVariants({ variant }),
937
+ responsiveSettings.compactMode &&
938
+ "text-xs px-1.5 py-0.5",
939
+ singleLine && "flex-shrink-0 whitespace-nowrap",
940
+ "[&>svg]:pointer-events-auto",
941
+ )}
942
+ style={{
943
+ animationDuration: `${
944
+ animationConfig?.duration || animation
945
+ }s`,
946
+ animationDelay: `${animationConfig?.delay || 0}s`,
947
+ }}
948
+ >
949
+ {`+ ${
950
+ selectedValues.length - responsiveSettings.maxCount
951
+ } more`}
952
+ <XCircle
953
+ className={cn(
954
+ "ml-2 h-4 w-4 cursor-pointer",
955
+ responsiveSettings.compactMode && "ml-1 h-3 w-3",
956
+ )}
957
+ onClick={(event) => {
958
+ event.stopPropagation();
959
+ clearExtraOptions();
960
+ }}
961
+ />
962
+ </Badge>
963
+ )}
964
+ </div>
965
+ <div className="flex items-center justify-between">
966
+ <div
967
+ role="button"
968
+ tabIndex={0}
969
+ onClick={(event) => {
970
+ event.stopPropagation();
971
+ handleClear();
972
+ }}
973
+ onKeyDown={(event) => {
974
+ if (event.key === "Enter" || event.key === " ") {
975
+ event.preventDefault();
976
+ event.stopPropagation();
977
+ handleClear();
978
+ }
979
+ }}
980
+ aria-label={`Clear all ${selectedValues.length} selected options`}
981
+ className="flex items-center justify-center h-4 w-4 mx-2 cursor-pointer text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-1 rounded-sm"
982
+ >
983
+ <XIcon className="h-4 w-4" />
984
+ </div>
985
+ <Separator
986
+ orientation="vertical"
987
+ className="flex min-h-6 h-full"
988
+ />
989
+ <ChevronDown
990
+ className="h-4 mx-2 cursor-pointer text-muted-foreground"
991
+ aria-hidden="true"
992
+ />
993
+ </div>
994
+ </div>
995
+ ) : (
996
+ <div className="flex items-center justify-between w-full mx-auto">
997
+ <span className="text-sm text-muted-foreground mx-3">
998
+ {placeholder}
999
+ </span>
1000
+ <ChevronDown className="h-4 cursor-pointer text-muted-foreground mx-2" />
1001
+ </div>
1002
+ )}
1003
+ </Button>
1004
+ </PopoverTrigger>
1005
+ <PopoverContent
1006
+ id={listboxId}
1007
+ role="listbox"
1008
+ aria-multiselectable="true"
1009
+ aria-label="Available options"
1010
+ className={cn(
1011
+ "w-auto p-0",
1012
+ getPopoverAnimationClass(),
1013
+ screenSize === "mobile" && "w-[85vw] max-w-[280px]",
1014
+ screenSize === "tablet" && "w-[70vw] max-w-md",
1015
+ screenSize === "desktop" && "min-w-[300px]",
1016
+ popoverClassName,
1017
+ )}
1018
+ style={{
1019
+ animationDuration: `${animationConfig?.duration || animation}s`,
1020
+ animationDelay: `${animationConfig?.delay || 0}s`,
1021
+ maxWidth: `min(${widthConstraints.maxWidth}, 85vw)`,
1022
+ maxHeight: screenSize === "mobile" ? "70vh" : "60vh",
1023
+ touchAction: "manipulation",
1024
+ }}
1025
+ align="start"
1026
+ onEscapeKeyDown={() => setIsPopoverOpen(false)}
1027
+ >
1028
+ <Command>
1029
+ {searchable && (
1030
+ <CommandInput
1031
+ placeholder="Search options..."
1032
+ onKeyDown={handleInputKeyDown}
1033
+ value={searchValue}
1034
+ onValueChange={setSearchValue}
1035
+ aria-label="Search through available options"
1036
+ aria-describedby={`${multiSelectId}-search-help`}
1037
+ />
1038
+ )}
1039
+ {searchable && (
1040
+ <div id={`${multiSelectId}-search-help`} className="sr-only">
1041
+ Type to filter options. Use arrow keys to navigate results.
1042
+ </div>
1043
+ )}
1044
+ <CommandList
1045
+ className={cn(
1046
+ "max-h-[40vh] overflow-y-auto multiselect-scrollbar",
1047
+ screenSize === "mobile" && "max-h-[50vh]",
1048
+ "overscroll-behavior-y-contain",
1049
+ )}
1050
+ >
1051
+ <CommandEmpty>
1052
+ {(emptyIndicator || "No results found.") as any}
1053
+ </CommandEmpty>
1054
+ {!hideSelectAll && !searchValue && (
1055
+ <CommandGroup>
1056
+ <CommandItem
1057
+ key="all"
1058
+ onSelect={toggleAll}
1059
+ role="option"
1060
+ aria-selected={
1061
+ selectedValues.length ===
1062
+ getAllOptions().filter((opt) => !opt.disabled).length
1063
+ }
1064
+ aria-label={`Select all ${
1065
+ getAllOptions().length
1066
+ } options`}
1067
+ className="cursor-pointer"
1068
+ >
1069
+ <div
1070
+ className={cn(
1071
+ "mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
1072
+ selectedValues.length ===
1073
+ getAllOptions().filter((opt) => !opt.disabled)
1074
+ .length
1075
+ ? "bg-primary text-primary-foreground"
1076
+ : "opacity-50 [&_svg]:invisible",
1077
+ )}
1078
+ aria-hidden="true"
1079
+ >
1080
+ <CheckIcon className="h-4 w-4" />
1081
+ </div>
1082
+ <span>
1083
+ (Select All
1084
+ {getAllOptions().length > 20
1085
+ ? ` - ${getAllOptions().length} options`
1086
+ : ""}
1087
+ )
1088
+ </span>
1089
+ </CommandItem>
1090
+ </CommandGroup>
1091
+ )}
1092
+ {isGroupedOptions(filteredOptions) ? (
1093
+ filteredOptions.map((group) => (
1094
+ <CommandGroup key={group.heading} heading={group.heading}>
1095
+ {group.options.map((option) => {
1096
+ const isSelected = selectedValues.includes(
1097
+ option.value,
1098
+ );
1099
+ return (
1100
+ <CommandItem
1101
+ key={option.value}
1102
+ onSelect={() => toggleOption(option.value)}
1103
+ role="option"
1104
+ aria-selected={isSelected}
1105
+ aria-disabled={option.disabled}
1106
+ aria-label={`${option.label}${
1107
+ isSelected ? ", selected" : ", not selected"
1108
+ }${option.disabled ? ", disabled" : ""}`}
1109
+ className={cn(
1110
+ "cursor-pointer",
1111
+ option.disabled &&
1112
+ "opacity-50 cursor-not-allowed",
1113
+ )}
1114
+ disabled={option.disabled}
1115
+ >
1116
+ <div
1117
+ className={cn(
1118
+ "mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
1119
+ isSelected
1120
+ ? "bg-primary text-primary-foreground"
1121
+ : "opacity-50 [&_svg]:invisible",
1122
+ )}
1123
+ aria-hidden="true"
1124
+ >
1125
+ <CheckIcon className="h-4 w-4" />
1126
+ </div>
1127
+ {option.icon &&
1128
+ (() => {
1129
+ const Icon = option.icon as any;
1130
+ return (
1131
+ <Icon
1132
+ className="mr-2 h-4 w-4 text-muted-foreground"
1133
+ aria-hidden="true"
1134
+ />
1135
+ );
1136
+ })()}
1137
+ <span>{option.label}</span>
1138
+ </CommandItem>
1139
+ );
1140
+ })}
1141
+ </CommandGroup>
1142
+ ))
1143
+ ) : (
1144
+ <CommandGroup>
1145
+ {filteredOptions.map((option) => {
1146
+ const isSelected = selectedValues.includes(option.value);
1147
+ return (
1148
+ <CommandItem
1149
+ key={option.value}
1150
+ onSelect={() => toggleOption(option.value)}
1151
+ role="option"
1152
+ aria-selected={isSelected}
1153
+ aria-disabled={option.disabled}
1154
+ aria-label={`${option.label}${
1155
+ isSelected ? ", selected" : ", not selected"
1156
+ }${option.disabled ? ", disabled" : ""}`}
1157
+ className={cn(
1158
+ "cursor-pointer",
1159
+ option.disabled && "opacity-50 cursor-not-allowed",
1160
+ )}
1161
+ disabled={option.disabled}
1162
+ >
1163
+ <div
1164
+ className={cn(
1165
+ "mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
1166
+ isSelected
1167
+ ? "bg-primary text-primary-foreground"
1168
+ : "opacity-50 [&_svg]:invisible",
1169
+ )}
1170
+ aria-hidden="true"
1171
+ >
1172
+ <CheckIcon className="h-4 w-4" />
1173
+ </div>
1174
+ {option.icon &&
1175
+ (() => {
1176
+ const Icon = option.icon as any;
1177
+ return (
1178
+ <Icon
1179
+ className="mr-2 h-4 w-4 text-muted-foreground"
1180
+ aria-hidden="true"
1181
+ />
1182
+ );
1183
+ })()}
1184
+ <span>{option.label}</span>
1185
+ </CommandItem>
1186
+ );
1187
+ })}
1188
+ </CommandGroup>
1189
+ )}
1190
+ <CommandSeparator />
1191
+ <CommandGroup>
1192
+ <div className="flex items-center justify-between">
1193
+ {selectedValues.length > 0 && (
1194
+ <>
1195
+ <CommandItem
1196
+ onSelect={handleClear}
1197
+ className="flex-1 justify-center cursor-pointer"
1198
+ >
1199
+ Clear
1200
+ </CommandItem>
1201
+ <Separator
1202
+ orientation="vertical"
1203
+ className="flex min-h-6 h-full"
1204
+ />
1205
+ </>
1206
+ )}
1207
+ <CommandItem
1208
+ onSelect={() => setIsPopoverOpen(false)}
1209
+ className="flex-1 justify-center cursor-pointer max-w-full"
1210
+ >
1211
+ Close
1212
+ </CommandItem>
1213
+ </div>
1214
+ </CommandGroup>
1215
+ </CommandList>
1216
+ </Command>
1217
+ </PopoverContent>
1218
+ {animation > 0 && selectedValues.length > 0 && (
1219
+ <WandSparkles
1220
+ className={cn(
1221
+ "cursor-pointer my-2 text-foreground bg-background w-3 h-3",
1222
+ isAnimating ? "" : "text-muted-foreground",
1223
+ )}
1224
+ onClick={() => setIsAnimating(!isAnimating)}
1225
+ />
1226
+ )}
1227
+ </Popover>
1228
+ </>
1229
+ );
1230
+ },
1231
+ );
1232
+
1233
+ MultiSelect.displayName = "MultiSelect";
1234
+ export type { MultiSelectOption, MultiSelectGroup, MultiSelectProps };