@wakastellar/ui 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (290) hide show
  1. package/dist/cli/commands/add.d.ts +7 -0
  2. package/dist/cli/commands/init.d.ts +6 -0
  3. package/dist/cli/commands/list.d.ts +5 -0
  4. package/dist/cli/commands/search.d.ts +1 -0
  5. package/dist/cli/index.cjs +4844 -0
  6. package/dist/cli/index.d.ts +1 -0
  7. package/dist/cli/utils/config.d.ts +29 -0
  8. package/dist/cli/utils/logger.d.ts +20 -0
  9. package/dist/cli/utils/registry.d.ts +23 -0
  10. package/package.json +14 -3
  11. package/src/blocks/activity-timeline/index.tsx +586 -0
  12. package/src/blocks/calendar-view/index.tsx +756 -0
  13. package/src/blocks/chat/index.tsx +1018 -0
  14. package/src/blocks/chat/widget.tsx +504 -0
  15. package/src/blocks/dashboard/index.tsx +522 -0
  16. package/src/blocks/empty-states/index.tsx +452 -0
  17. package/src/blocks/error-pages/index.tsx +426 -0
  18. package/src/blocks/faq/index.tsx +479 -0
  19. package/src/blocks/file-manager/index.tsx +890 -0
  20. package/src/blocks/footer/index.tsx +133 -0
  21. package/src/blocks/header/index.tsx +357 -0
  22. package/src/blocks/headtab/index.tsx +139 -0
  23. package/src/blocks/i18n-editor/index.tsx +1016 -0
  24. package/src/blocks/index.ts +80 -0
  25. package/src/blocks/kanban-board/index.tsx +779 -0
  26. package/src/blocks/landing/index.tsx +677 -0
  27. package/src/blocks/language-selector/index.tsx +88 -0
  28. package/src/blocks/layout/index.tsx +159 -0
  29. package/src/blocks/login/index.tsx +339 -0
  30. package/src/blocks/login/types.ts +131 -0
  31. package/src/blocks/pricing/index.tsx +564 -0
  32. package/src/blocks/profile/index.tsx +746 -0
  33. package/src/blocks/settings/index.tsx +558 -0
  34. package/src/blocks/sidebar/index.tsx +713 -0
  35. package/src/blocks/theme-creator-block/index.tsx +835 -0
  36. package/src/blocks/user-management/index.tsx +1037 -0
  37. package/src/blocks/wizard/index.tsx +719 -0
  38. package/src/components/DataTable/DataTable.tsx +406 -0
  39. package/src/components/DataTable/DataTableAdvanced.tsx +720 -0
  40. package/src/components/DataTable/DataTableBody.tsx +216 -0
  41. package/src/components/DataTable/DataTableCell.tsx +172 -0
  42. package/src/components/DataTable/DataTableColumnResizer.tsx +62 -0
  43. package/src/components/DataTable/DataTableConflictResolver.tsx +478 -0
  44. package/src/components/DataTable/DataTableContextMenu.tsx +219 -0
  45. package/src/components/DataTable/DataTableEditCell.tsx +279 -0
  46. package/src/components/DataTable/DataTableFilterBuilder.tsx +519 -0
  47. package/src/components/DataTable/DataTableFilters.tsx +535 -0
  48. package/src/components/DataTable/DataTableGrouping.tsx +147 -0
  49. package/src/components/DataTable/DataTableHeader.tsx +172 -0
  50. package/src/components/DataTable/DataTablePagination.tsx +125 -0
  51. package/src/components/DataTable/DataTableSelection.tsx +269 -0
  52. package/src/components/DataTable/DataTableSyncStatus.tsx +281 -0
  53. package/src/components/DataTable/DataTableToolbar.tsx +262 -0
  54. package/src/components/DataTable/README.md +446 -0
  55. package/src/components/DataTable/__tests__/DataTableAdvanced.test.tsx +426 -0
  56. package/src/components/DataTable/__tests__/DataTableEdit.test.tsx +329 -0
  57. package/src/components/DataTable/__tests__/useDataTableAdvanced.test.ts +455 -0
  58. package/src/components/DataTable/examples/EditExample.tsx +166 -0
  59. package/src/components/DataTable/formatters/index.ts +335 -0
  60. package/src/components/DataTable/hooks/__tests__/useDataTableEdit.test.ts +239 -0
  61. package/src/components/DataTable/hooks/useDataTable.ts +145 -0
  62. package/src/components/DataTable/hooks/useDataTableAdvanced.ts +342 -0
  63. package/src/components/DataTable/hooks/useDataTableAdvancedFilters.ts +637 -0
  64. package/src/components/DataTable/hooks/useDataTableColumnTemplates.ts +186 -0
  65. package/src/components/DataTable/hooks/useDataTableEdit.ts +167 -0
  66. package/src/components/DataTable/hooks/useDataTableExport.ts +227 -0
  67. package/src/components/DataTable/hooks/useDataTableImport.ts +216 -0
  68. package/src/components/DataTable/hooks/useDataTableOffline.ts +481 -0
  69. package/src/components/DataTable/hooks/useDataTableTheme.ts +213 -0
  70. package/src/components/DataTable/hooks/useDataTableVirtualization.ts +99 -0
  71. package/src/components/DataTable/hooks/useTableLayout.ts +85 -0
  72. package/src/components/DataTable/index.ts +81 -0
  73. package/src/components/DataTable/services/IndexedDBService.ts +504 -0
  74. package/src/components/DataTable/templates/index.tsx +803 -0
  75. package/src/components/DataTable/types.ts +504 -0
  76. package/src/components/DataTable/utils.ts +164 -0
  77. package/src/components/DataTable/workers/exportWorker.ts +213 -0
  78. package/src/components/accordion/index.tsx +61 -0
  79. package/src/components/alert/index.tsx +61 -0
  80. package/src/components/alert-dialog/index.tsx +146 -0
  81. package/src/components/aspect-ratio/index.tsx +12 -0
  82. package/src/components/avatar/index.tsx +54 -0
  83. package/src/components/badge/Badge.stories.tsx +64 -0
  84. package/src/components/badge/index.tsx +38 -0
  85. package/src/components/button/Button.stories.tsx +173 -0
  86. package/src/components/button/index.tsx +56 -0
  87. package/src/components/calendar/index.tsx +73 -0
  88. package/src/components/card/index.tsx +78 -0
  89. package/src/components/checkbox/index.tsx +34 -0
  90. package/src/components/code/index.tsx +229 -0
  91. package/src/components/collapsible/index.tsx +16 -0
  92. package/src/components/command/index.tsx +162 -0
  93. package/src/components/context-menu/index.tsx +204 -0
  94. package/src/components/dialog/index.tsx +126 -0
  95. package/src/components/dropdown-menu/index.tsx +204 -0
  96. package/src/components/error-boundary/ErrorBoundary.tsx +281 -0
  97. package/src/components/error-boundary/index.ts +7 -0
  98. package/src/components/form/index.tsx +183 -0
  99. package/src/components/hover-card/index.tsx +33 -0
  100. package/src/components/index.ts +368 -0
  101. package/src/components/input/Input.stories.tsx +100 -0
  102. package/src/components/input/index.tsx +27 -0
  103. package/src/components/input-otp/index.tsx +277 -0
  104. package/src/components/label/index.tsx +30 -0
  105. package/src/components/language-selector/index.tsx +341 -0
  106. package/src/components/menubar/index.tsx +240 -0
  107. package/src/components/navigation-menu/index.tsx +134 -0
  108. package/src/components/popover/index.tsx +35 -0
  109. package/src/components/progress/index.tsx +32 -0
  110. package/src/components/radio-group/index.tsx +48 -0
  111. package/src/components/scroll-area/index.tsx +52 -0
  112. package/src/components/select/index.tsx +164 -0
  113. package/src/components/separator/index.tsx +35 -0
  114. package/src/components/sheet/index.tsx +147 -0
  115. package/src/components/skeleton/index.tsx +22 -0
  116. package/src/components/slider/index.tsx +32 -0
  117. package/src/components/switch/index.tsx +33 -0
  118. package/src/components/table/index.tsx +117 -0
  119. package/src/components/tabs/index.tsx +59 -0
  120. package/src/components/textarea/index.tsx +30 -0
  121. package/src/components/theme-selector/index.tsx +327 -0
  122. package/src/components/toast/index.tsx +133 -0
  123. package/src/components/toaster/index.tsx +34 -0
  124. package/src/components/toggle/index.tsx +49 -0
  125. package/src/components/tooltip/index.tsx +34 -0
  126. package/src/components/typography/index.tsx +276 -0
  127. package/src/components/waka-3d-pie-chart/index.tsx +486 -0
  128. package/src/components/waka-achievement-unlock/index.tsx +716 -0
  129. package/src/components/waka-activity-feed/index.tsx +686 -0
  130. package/src/components/waka-address-autocomplete/index.tsx +1202 -0
  131. package/src/components/waka-admincrumb/index.tsx +349 -0
  132. package/src/components/waka-alert-stack/index.tsx +827 -0
  133. package/src/components/waka-allocation-matrix/index.tsx +1278 -0
  134. package/src/components/waka-approval-chain/index.tsx +766 -0
  135. package/src/components/waka-audit-log/index.tsx +1475 -0
  136. package/src/components/waka-autocomplete/index.tsx +358 -0
  137. package/src/components/waka-badge-showcase/index.tsx +704 -0
  138. package/src/components/waka-barcode/index.tsx +260 -0
  139. package/src/components/waka-biometric-prompt/index.tsx +765 -0
  140. package/src/components/waka-bottom-sheet/index.tsx +495 -0
  141. package/src/components/waka-breadcrumb/index.tsx +376 -0
  142. package/src/components/waka-breadcrumb-path/index.tsx +513 -0
  143. package/src/components/waka-budget-burn/index.tsx +1234 -0
  144. package/src/components/waka-capacity-planner/index.tsx +1107 -0
  145. package/src/components/waka-carousel/index.tsx +893 -0
  146. package/src/components/waka-cart-summary/index.tsx +1055 -0
  147. package/src/components/waka-challenge-timer/index.tsx +1044 -0
  148. package/src/components/waka-charts/WakaAreaChart.tsx +251 -0
  149. package/src/components/waka-charts/WakaBarChart.tsx +222 -0
  150. package/src/components/waka-charts/WakaChart.tsx +124 -0
  151. package/src/components/waka-charts/WakaLineChart.tsx +219 -0
  152. package/src/components/waka-charts/WakaMiniChart.tsx +133 -0
  153. package/src/components/waka-charts/WakaPieChart.tsx +214 -0
  154. package/src/components/waka-charts/WakaSparkline.tsx +229 -0
  155. package/src/components/waka-charts/dataTableHelpers.ts +109 -0
  156. package/src/components/waka-charts/hooks/useChartTheme.ts +123 -0
  157. package/src/components/waka-charts/hooks/useRechartsLoader.ts +234 -0
  158. package/src/components/waka-charts/index.ts +90 -0
  159. package/src/components/waka-charts/types.ts +330 -0
  160. package/src/components/waka-chat-bubble/index.tsx +1060 -0
  161. package/src/components/waka-checklist/index.tsx +1067 -0
  162. package/src/components/waka-checkout-stepper/index.tsx +976 -0
  163. package/src/components/waka-cohort-table/index.tsx +1011 -0
  164. package/src/components/waka-color-picker/index.tsx +447 -0
  165. package/src/components/waka-combo-counter/index.tsx +864 -0
  166. package/src/components/waka-combobox/index.tsx +497 -0
  167. package/src/components/waka-command-bar/index.tsx +403 -0
  168. package/src/components/waka-compare-period/index.tsx +1230 -0
  169. package/src/components/waka-connection-matrix/index.tsx +1053 -0
  170. package/src/components/waka-contribution-graph/index.tsx +552 -0
  171. package/src/components/waka-cost-breakdown/index.tsx +1065 -0
  172. package/src/components/waka-coupon-input/index.tsx +592 -0
  173. package/src/components/waka-credit-card-input/index.tsx +982 -0
  174. package/src/components/waka-daily-reward/index.tsx +762 -0
  175. package/src/components/waka-date-range-picker/index.tsx +378 -0
  176. package/src/components/waka-datetime-picker/index.tsx +793 -0
  177. package/src/components/waka-datetime-picker.form-integration/index.tsx +402 -0
  178. package/src/components/waka-deployment-lane/index.tsx +673 -0
  179. package/src/components/waka-device-trust/index.tsx +1259 -0
  180. package/src/components/waka-dock/index.tsx +285 -0
  181. package/src/components/waka-drawer/index.tsx +319 -0
  182. package/src/components/waka-empty-state/index.tsx +545 -0
  183. package/src/components/waka-error-shake/index.tsx +398 -0
  184. package/src/components/waka-feature-announcement/index.tsx +991 -0
  185. package/src/components/waka-file-upload/index.tsx +437 -0
  186. package/src/components/waka-floating-nav/index.tsx +413 -0
  187. package/src/components/waka-flow-diagram/index.tsx +508 -0
  188. package/src/components/waka-funnel-chart/index.tsx +823 -0
  189. package/src/components/waka-glow-card/index.tsx +246 -0
  190. package/src/components/waka-goal-progress/index.tsx +1025 -0
  191. package/src/components/waka-haptic-button/index.tsx +388 -0
  192. package/src/components/waka-health-pulse/index.tsx +451 -0
  193. package/src/components/waka-heatmap/index.tsx +1026 -0
  194. package/src/components/waka-hotspot/index.tsx +682 -0
  195. package/src/components/waka-image/index.tsx +373 -0
  196. package/src/components/waka-incident-timeline/index.tsx +686 -0
  197. package/src/components/waka-invoice-preview/index.tsx +829 -0
  198. package/src/components/waka-kanban/index.tsx +646 -0
  199. package/src/components/waka-kpi-dashboard/index.tsx +755 -0
  200. package/src/components/waka-leaderboard/index.tsx +746 -0
  201. package/src/components/waka-level-progress/index.tsx +665 -0
  202. package/src/components/waka-liquid-button/index.tsx +520 -0
  203. package/src/components/waka-loading-orbit/index.tsx +478 -0
  204. package/src/components/waka-loot-box/index.tsx +1091 -0
  205. package/src/components/waka-magic-link/index.tsx +321 -0
  206. package/src/components/waka-magnetic-button/index.tsx +567 -0
  207. package/src/components/waka-mention-input/index.tsx +953 -0
  208. package/src/components/waka-metric-sparkline/index.tsx +627 -0
  209. package/src/components/waka-milestone-road/index.tsx +1064 -0
  210. package/src/components/waka-modal/index.tsx +374 -0
  211. package/src/components/waka-morph-button/index.tsx +495 -0
  212. package/src/components/waka-network-topology/index.tsx +801 -0
  213. package/src/components/waka-notifications/index.tsx +414 -0
  214. package/src/components/waka-number-input/index.tsx +373 -0
  215. package/src/components/waka-orbital-menu/index.tsx +445 -0
  216. package/src/components/waka-order-tracker/index.tsx +1041 -0
  217. package/src/components/waka-pagination/index.tsx +393 -0
  218. package/src/components/waka-password-strength/index.tsx +824 -0
  219. package/src/components/waka-payment-method-picker/index.tsx +715 -0
  220. package/src/components/waka-permission-matrix/index.tsx +1302 -0
  221. package/src/components/waka-phone-input/index.tsx +801 -0
  222. package/src/components/waka-pipeline-view/index.tsx +604 -0
  223. package/src/components/waka-player-card/index.tsx +691 -0
  224. package/src/components/waka-points-popup/index.tsx +366 -0
  225. package/src/components/waka-power-up/index.tsx +1155 -0
  226. package/src/components/waka-presence-indicator/index.tsx +1181 -0
  227. package/src/components/waka-pricing-table/index.tsx +755 -0
  228. package/src/components/waka-product-card/index.tsx +786 -0
  229. package/src/components/waka-progress-onboarding/index.tsx +878 -0
  230. package/src/components/waka-pull-to-refresh/index.tsx +451 -0
  231. package/src/components/waka-qrcode/index.tsx +232 -0
  232. package/src/components/waka-quest-card/index.tsx +1275 -0
  233. package/src/components/waka-quota-bar/index.tsx +693 -0
  234. package/src/components/waka-radar-score/index.tsx +512 -0
  235. package/src/components/waka-rank-badge/index.tsx +813 -0
  236. package/src/components/waka-rating-input/index.tsx +560 -0
  237. package/src/components/waka-reaction-picker/index.tsx +1062 -0
  238. package/src/components/waka-region-map/index.tsx +730 -0
  239. package/src/components/waka-resource-gauge/index.tsx +654 -0
  240. package/src/components/waka-resource-pool/index.tsx +1035 -0
  241. package/src/components/waka-rich-text-editor/index.tsx +594 -0
  242. package/src/components/waka-rollback-slider/index.tsx +891 -0
  243. package/src/components/waka-sankey-diagram/index.tsx +1032 -0
  244. package/src/components/waka-schedule-picker/index.tsx +1060 -0
  245. package/src/components/waka-scratch-card/index.tsx +914 -0
  246. package/src/components/waka-season-pass/index.tsx +886 -0
  247. package/src/components/waka-security-score/index.tsx +1126 -0
  248. package/src/components/waka-segmented-control/index.tsx +238 -0
  249. package/src/components/waka-server-rack/index.tsx +764 -0
  250. package/src/components/waka-session-manager/index.tsx +815 -0
  251. package/src/components/waka-signature-pad/index.tsx +744 -0
  252. package/src/components/waka-skeleton-wave/index.tsx +454 -0
  253. package/src/components/waka-skill-tree/index.tsx +1031 -0
  254. package/src/components/waka-sla-tracker/index.tsx +798 -0
  255. package/src/components/waka-slider-range/index.tsx +765 -0
  256. package/src/components/waka-spin-wheel/index.tsx +671 -0
  257. package/src/components/waka-spinner/index.tsx +284 -0
  258. package/src/components/waka-spotlight/index.tsx +410 -0
  259. package/src/components/waka-stat/index.tsx +428 -0
  260. package/src/components/waka-stats-hexagon/index.tsx +824 -0
  261. package/src/components/waka-status-matrix/index.tsx +565 -0
  262. package/src/components/waka-stepper/index.tsx +489 -0
  263. package/src/components/waka-streak-counter/index.tsx +334 -0
  264. package/src/components/waka-success-explosion/index.tsx +453 -0
  265. package/src/components/waka-swipe-card/index.tsx +574 -0
  266. package/src/components/waka-tabs-morph/index.tsx +509 -0
  267. package/src/components/waka-tag-input/index.tsx +877 -0
  268. package/src/components/waka-team-banner/index.tsx +1183 -0
  269. package/src/components/waka-terminal-output/index.tsx +836 -0
  270. package/src/components/waka-theme-creator/index.tsx +762 -0
  271. package/src/components/waka-theme-manager/index.tsx +654 -0
  272. package/src/components/waka-thread-view/index.tsx +874 -0
  273. package/src/components/waka-tilt-card/index.tsx +250 -0
  274. package/src/components/waka-time-picker/index.tsx +479 -0
  275. package/src/components/waka-timeline/index.tsx +385 -0
  276. package/src/components/waka-tooltip-tour/index.tsx +855 -0
  277. package/src/components/waka-tour-guide/index.tsx +920 -0
  278. package/src/components/waka-tournament-bracket/index.tsx +1276 -0
  279. package/src/components/waka-tree/index.tsx +557 -0
  280. package/src/components/waka-treemap-chart/index.tsx +1031 -0
  281. package/src/components/waka-two-factor-setup/index.tsx +995 -0
  282. package/src/components/waka-typewriter/index.tsx +566 -0
  283. package/src/components/waka-typing-indicator/index.tsx +649 -0
  284. package/src/components/waka-versus-card/index.tsx +1026 -0
  285. package/src/components/waka-video/index.tsx +557 -0
  286. package/src/components/waka-video-call/index.tsx +1087 -0
  287. package/src/components/waka-virtual-list/index.tsx +327 -0
  288. package/src/components/waka-voice-message/index.tsx +1019 -0
  289. package/src/components/waka-welcome-modal/index.tsx +790 -0
  290. package/src/components/waka-xp-bar/index.tsx +799 -0
@@ -0,0 +1,403 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../../utils/cn"
5
+ import { Command, Search, ArrowRight, CornerDownLeft, Hash, Settings, User, FileText, Zap } from "lucide-react"
6
+
7
+ // ============================================================================
8
+ // Types
9
+ // ============================================================================
10
+
11
+ export interface CommandItem {
12
+ id: string
13
+ label: string
14
+ description?: string
15
+ icon?: React.ReactNode
16
+ shortcut?: string[]
17
+ category?: string
18
+ action?: () => void
19
+ href?: string
20
+ keywords?: string[]
21
+ }
22
+
23
+ export interface CommandCategory {
24
+ id: string
25
+ label: string
26
+ items: CommandItem[]
27
+ }
28
+
29
+ export interface WakaCommandBarProps {
30
+ /** Whether the command bar is open */
31
+ open: boolean
32
+ /** Callback when open state changes */
33
+ onOpenChange: (open: boolean) => void
34
+ /** Command items or categories */
35
+ items: CommandItem[] | CommandCategory[]
36
+ /** Placeholder text */
37
+ placeholder?: string
38
+ /** Called when an item is selected */
39
+ onSelect?: (item: CommandItem) => void
40
+ /** Custom empty state */
41
+ emptyMessage?: string
42
+ /** Show recent items */
43
+ recentItems?: CommandItem[]
44
+ /** Custom footer content */
45
+ footer?: React.ReactNode
46
+ /** Keyboard shortcut to open (default: cmd+k) */
47
+ shortcut?: string[]
48
+ }
49
+
50
+ // ============================================================================
51
+ // Fuzzy Search
52
+ // ============================================================================
53
+
54
+ function fuzzyMatch(text: string, query: string): { match: boolean; score: number } {
55
+ if (!query) return { match: true, score: 1 }
56
+
57
+ const textLower = text.toLowerCase()
58
+ const queryLower = query.toLowerCase()
59
+
60
+ // Exact match
61
+ if (textLower.includes(queryLower)) {
62
+ return { match: true, score: textLower.indexOf(queryLower) === 0 ? 1 : 0.8 }
63
+ }
64
+
65
+ // Fuzzy match
66
+ let queryIdx = 0
67
+ let score = 0
68
+ let consecutive = 0
69
+
70
+ for (let i = 0; i < textLower.length && queryIdx < queryLower.length; i++) {
71
+ if (textLower[i] === queryLower[queryIdx]) {
72
+ score += 1 + consecutive * 0.5
73
+ consecutive++
74
+ queryIdx++
75
+ } else {
76
+ consecutive = 0
77
+ }
78
+ }
79
+
80
+ const matched = queryIdx === queryLower.length
81
+ return { match: matched, score: matched ? score / queryLower.length : 0 }
82
+ }
83
+
84
+ function searchItems(items: CommandItem[], query: string): CommandItem[] {
85
+ if (!query) return items
86
+
87
+ const results = items
88
+ .map(item => {
89
+ const labelMatch = fuzzyMatch(item.label, query)
90
+ const descMatch = item.description ? fuzzyMatch(item.description, query) : { match: false, score: 0 }
91
+ const keywordMatch = item.keywords?.some(k => fuzzyMatch(k, query).match) ? { match: true, score: 0.5 } : { match: false, score: 0 }
92
+
93
+ const bestScore = Math.max(labelMatch.score, descMatch.score * 0.7, keywordMatch.score)
94
+ const matched = labelMatch.match || descMatch.match || keywordMatch.match
95
+
96
+ return { item, score: bestScore, matched }
97
+ })
98
+ .filter(r => r.matched)
99
+ .sort((a, b) => b.score - a.score)
100
+
101
+ return results.map(r => r.item)
102
+ }
103
+
104
+ // ============================================================================
105
+ // Component
106
+ // ============================================================================
107
+
108
+ export function WakaCommandBar({
109
+ open,
110
+ onOpenChange,
111
+ items,
112
+ placeholder = "Type a command or search...",
113
+ onSelect,
114
+ emptyMessage = "No results found.",
115
+ recentItems,
116
+ footer,
117
+ shortcut = ["⌘", "K"],
118
+ }: WakaCommandBarProps) {
119
+ const [query, setQuery] = React.useState("")
120
+ const [selectedIndex, setSelectedIndex] = React.useState(0)
121
+ const inputRef = React.useRef<HTMLInputElement>(null)
122
+ const listRef = React.useRef<HTMLDivElement>(null)
123
+
124
+ // Normalize items to flat list
125
+ const isCategories = items.length > 0 && "items" in items[0]
126
+ const allItems = React.useMemo(() => {
127
+ if (isCategories) {
128
+ return (items as CommandCategory[]).flatMap(cat =>
129
+ cat.items.map(item => ({ ...item, category: cat.label }))
130
+ )
131
+ }
132
+ return items as CommandItem[]
133
+ }, [items, isCategories])
134
+
135
+ // Filter items
136
+ const filteredItems = React.useMemo(() => {
137
+ return searchItems(allItems, query)
138
+ }, [allItems, query])
139
+
140
+ // Group by category for display
141
+ const groupedItems = React.useMemo(() => {
142
+ if (!isCategories || query) {
143
+ return [{ label: query ? "Results" : "Commands", items: filteredItems }]
144
+ }
145
+ return items as CommandCategory[]
146
+ }, [items, isCategories, filteredItems, query])
147
+
148
+ // Flatten for keyboard navigation
149
+ const flatItems = React.useMemo(() => {
150
+ return groupedItems.flatMap(g => g.items)
151
+ }, [groupedItems])
152
+
153
+ // Keyboard navigation
154
+ React.useEffect(() => {
155
+ if (!open) return
156
+
157
+ const handleKeyDown = (e: KeyboardEvent) => {
158
+ switch (e.key) {
159
+ case "ArrowDown":
160
+ e.preventDefault()
161
+ setSelectedIndex(i => Math.min(i + 1, flatItems.length - 1))
162
+ break
163
+ case "ArrowUp":
164
+ e.preventDefault()
165
+ setSelectedIndex(i => Math.max(i - 1, 0))
166
+ break
167
+ case "Enter":
168
+ e.preventDefault()
169
+ const selected = flatItems[selectedIndex]
170
+ if (selected) {
171
+ handleSelect(selected)
172
+ }
173
+ break
174
+ case "Escape":
175
+ e.preventDefault()
176
+ onOpenChange(false)
177
+ break
178
+ }
179
+ }
180
+
181
+ window.addEventListener("keydown", handleKeyDown)
182
+ return () => window.removeEventListener("keydown", handleKeyDown)
183
+ }, [open, selectedIndex, flatItems, onOpenChange])
184
+
185
+ // Reset on open
186
+ React.useEffect(() => {
187
+ if (open) {
188
+ setQuery("")
189
+ setSelectedIndex(0)
190
+ setTimeout(() => inputRef.current?.focus(), 0)
191
+ }
192
+ }, [open])
193
+
194
+ // Scroll selected into view
195
+ React.useEffect(() => {
196
+ const selected = listRef.current?.querySelector(`[data-index="${selectedIndex}"]`)
197
+ selected?.scrollIntoView({ block: "nearest" })
198
+ }, [selectedIndex])
199
+
200
+ // Global shortcut
201
+ React.useEffect(() => {
202
+ const handleGlobalKeyDown = (e: KeyboardEvent) => {
203
+ if ((e.metaKey || e.ctrlKey) && e.key === "k") {
204
+ e.preventDefault()
205
+ onOpenChange(!open)
206
+ }
207
+ }
208
+
209
+ window.addEventListener("keydown", handleGlobalKeyDown)
210
+ return () => window.removeEventListener("keydown", handleGlobalKeyDown)
211
+ }, [open, onOpenChange])
212
+
213
+ const handleSelect = (item: CommandItem) => {
214
+ onSelect?.(item)
215
+ item.action?.()
216
+ if (item.href) {
217
+ window.location.href = item.href
218
+ }
219
+ onOpenChange(false)
220
+ }
221
+
222
+ if (!open) return null
223
+
224
+ let itemIndex = -1
225
+
226
+ return (
227
+ <>
228
+ {/* Backdrop */}
229
+ <div
230
+ className="fixed inset-0 z-50 bg-black/50 backdrop-blur-sm animate-in fade-in-0"
231
+ onClick={() => onOpenChange(false)}
232
+ />
233
+
234
+ {/* Command Bar */}
235
+ <div className="fixed left-1/2 top-[20%] z-50 w-full max-w-xl -translate-x-1/2 animate-in fade-in-0 zoom-in-95 slide-in-from-top-2">
236
+ <div className="overflow-hidden rounded-xl border bg-popover shadow-2xl">
237
+ {/* Search Input */}
238
+ <div className="flex items-center border-b px-4">
239
+ <Search className="h-5 w-5 shrink-0 text-muted-foreground" />
240
+ <input
241
+ ref={inputRef}
242
+ type="text"
243
+ value={query}
244
+ onChange={(e) => {
245
+ setQuery(e.target.value)
246
+ setSelectedIndex(0)
247
+ }}
248
+ placeholder={placeholder}
249
+ className="flex-1 bg-transparent py-4 px-3 text-sm outline-none placeholder:text-muted-foreground"
250
+ />
251
+ <kbd className="hidden sm:inline-flex h-6 items-center gap-1 rounded border bg-muted px-2 text-xs text-muted-foreground">
252
+ ESC
253
+ </kbd>
254
+ </div>
255
+
256
+ {/* Results */}
257
+ <div ref={listRef} className="max-h-80 overflow-y-auto p-2">
258
+ {/* Recent Items */}
259
+ {!query && recentItems && recentItems.length > 0 && (
260
+ <div className="mb-2">
261
+ <div className="px-2 py-1.5 text-xs font-medium text-muted-foreground">
262
+ Recent
263
+ </div>
264
+ {recentItems.map((item) => {
265
+ itemIndex++
266
+ const idx = itemIndex
267
+ return (
268
+ <CommandItemRow
269
+ key={`recent-${item.id}`}
270
+ item={item}
271
+ selected={selectedIndex === idx}
272
+ dataIndex={idx}
273
+ onSelect={() => handleSelect(item)}
274
+ onHover={() => setSelectedIndex(idx)}
275
+ />
276
+ )
277
+ })}
278
+ </div>
279
+ )}
280
+
281
+ {/* Grouped Items */}
282
+ {groupedItems.map((group) => (
283
+ <div key={group.label} className="mb-2">
284
+ <div className="px-2 py-1.5 text-xs font-medium text-muted-foreground">
285
+ {group.label}
286
+ </div>
287
+ {group.items.length === 0 ? (
288
+ <div className="px-2 py-8 text-center text-sm text-muted-foreground">
289
+ {emptyMessage}
290
+ </div>
291
+ ) : (
292
+ group.items.map((item) => {
293
+ itemIndex++
294
+ const idx = itemIndex
295
+ return (
296
+ <CommandItemRow
297
+ key={item.id}
298
+ item={item}
299
+ selected={selectedIndex === idx}
300
+ dataIndex={idx}
301
+ onSelect={() => handleSelect(item)}
302
+ onHover={() => setSelectedIndex(idx)}
303
+ />
304
+ )
305
+ })
306
+ )}
307
+ </div>
308
+ ))}
309
+ </div>
310
+
311
+ {/* Footer */}
312
+ <div className="flex items-center justify-between border-t bg-muted/50 px-4 py-2 text-xs text-muted-foreground">
313
+ {footer || (
314
+ <>
315
+ <div className="flex items-center gap-4">
316
+ <span className="flex items-center gap-1">
317
+ <kbd className="rounded border bg-background px-1.5 py-0.5">↑↓</kbd>
318
+ Navigate
319
+ </span>
320
+ <span className="flex items-center gap-1">
321
+ <kbd className="rounded border bg-background px-1.5 py-0.5">↵</kbd>
322
+ Select
323
+ </span>
324
+ </div>
325
+ <span className="flex items-center gap-1">
326
+ <kbd className="rounded border bg-background px-1.5 py-0.5">ESC</kbd>
327
+ Close
328
+ </span>
329
+ </>
330
+ )}
331
+ </div>
332
+ </div>
333
+ </div>
334
+ </>
335
+ )
336
+ }
337
+
338
+ // ============================================================================
339
+ // Command Item Row
340
+ // ============================================================================
341
+
342
+ interface CommandItemRowProps {
343
+ item: CommandItem
344
+ selected: boolean
345
+ dataIndex: number
346
+ onSelect: () => void
347
+ onHover: () => void
348
+ }
349
+
350
+ function CommandItemRow({ item, selected, dataIndex, onSelect, onHover }: CommandItemRowProps) {
351
+ return (
352
+ <button
353
+ data-index={dataIndex}
354
+ onClick={onSelect}
355
+ onMouseEnter={onHover}
356
+ className={cn(
357
+ "flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-left text-sm transition-colors",
358
+ selected ? "bg-accent text-accent-foreground" : "hover:bg-accent/50"
359
+ )}
360
+ >
361
+ {item.icon && (
362
+ <span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md border bg-background">
363
+ {item.icon}
364
+ </span>
365
+ )}
366
+ <div className="flex-1 min-w-0">
367
+ <div className="font-medium truncate">{item.label}</div>
368
+ {item.description && (
369
+ <div className="text-xs text-muted-foreground truncate">{item.description}</div>
370
+ )}
371
+ </div>
372
+ {item.shortcut && (
373
+ <div className="flex items-center gap-1 shrink-0">
374
+ {item.shortcut.map((key, i) => (
375
+ <kbd key={i} className="rounded border bg-muted px-1.5 py-0.5 text-xs">
376
+ {key}
377
+ </kbd>
378
+ ))}
379
+ </div>
380
+ )}
381
+ {selected && !item.shortcut && (
382
+ <CornerDownLeft className="h-4 w-4 shrink-0 text-muted-foreground" />
383
+ )}
384
+ </button>
385
+ )
386
+ }
387
+
388
+ // ============================================================================
389
+ // Hook
390
+ // ============================================================================
391
+
392
+ export function useCommandBar() {
393
+ const [open, setOpen] = React.useState(false)
394
+
395
+ return {
396
+ open,
397
+ onOpenChange: setOpen,
398
+ openCommandBar: () => setOpen(true),
399
+ closeCommandBar: () => setOpen(false),
400
+ toggleCommandBar: () => setOpen(o => !o),
401
+ }
402
+ }
403
+