@exxatdesignux/ui 0.0.6 → 0.0.7

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 (264) hide show
  1. package/bin/init.mjs +29 -0
  2. package/package.json +7 -2
  3. package/template/.nvmrc +1 -0
  4. package/template/.prettierignore +7 -0
  5. package/template/.prettierrc +11 -0
  6. package/template/AGENTS.md +485 -0
  7. package/template/Logo/Exxat_Prism.svg +39 -0
  8. package/template/Logo/Exxat_one.svg +36 -0
  9. package/template/README.md +58 -0
  10. package/template/app/(app)/compliance/page.tsx +10 -0
  11. package/template/app/(app)/dashboard/loading.tsx +18 -0
  12. package/template/app/(app)/dashboard/page.tsx +36 -0
  13. package/template/app/(app)/data-list/[id]/page.tsx +28 -0
  14. package/template/app/(app)/data-list/new/page.tsx +31 -0
  15. package/template/app/(app)/data-list/page.tsx +10 -0
  16. package/template/app/(app)/error.tsx +43 -0
  17. package/template/app/(app)/help/page.tsx +34 -0
  18. package/template/app/(app)/layout.tsx +54 -0
  19. package/template/app/(app)/loading.tsx +18 -0
  20. package/template/app/(app)/question-bank/page.tsx +10 -0
  21. package/template/app/(app)/rotations/page.tsx +15 -0
  22. package/template/app/(app)/settings/page.tsx +17 -0
  23. package/template/app/(app)/sites/all/page.tsx +13 -0
  24. package/template/app/(app)/team/page.tsx +10 -0
  25. package/template/app/favicon.ico +0 -0
  26. package/template/app/globals.css +1811 -0
  27. package/template/app/layout.tsx +95 -0
  28. package/template/app/page.tsx +9 -0
  29. package/template/components/.gitkeep +0 -0
  30. package/template/components/app-sidebar-dynamic.tsx +15 -0
  31. package/template/components/app-sidebar.tsx +901 -0
  32. package/template/components/ask-leo-composer.tsx +216 -0
  33. package/template/components/ask-leo-sidebar.tsx +509 -0
  34. package/template/components/chart-area-interactive.tsx +293 -0
  35. package/template/components/charts-overview.tsx +2321 -0
  36. package/template/components/command-menu-01.tsx +133 -0
  37. package/template/components/command-menu-02.tsx +386 -0
  38. package/template/components/command-menu.tsx +182 -0
  39. package/template/components/compliance-board-view.tsx +134 -0
  40. package/template/components/compliance-client.tsx +92 -0
  41. package/template/components/compliance-list-view.tsx +59 -0
  42. package/template/components/compliance-page-header.tsx +89 -0
  43. package/template/components/compliance-table.tsx +525 -0
  44. package/template/components/dashboard-onboarding-gallery.tsx +13 -0
  45. package/template/components/dashboard-onboarding.tsx +21 -0
  46. package/template/components/dashboard-promo-banner.tsx +67 -0
  47. package/template/components/dashboard-quota-progress-card.tsx +369 -0
  48. package/template/components/dashboard-report-charts.tsx +69 -0
  49. package/template/components/dashboard-section-heading.tsx +68 -0
  50. package/template/components/dashboard-tabs.tsx +598 -0
  51. package/template/components/data-list-client.tsx +239 -0
  52. package/template/components/data-list-table-cells.test.tsx +22 -0
  53. package/template/components/data-list-table-cells.tsx +173 -0
  54. package/template/components/data-list-table.tsx +879 -0
  55. package/template/components/data-table/filter-date-calendar.tsx +38 -0
  56. package/template/components/data-table/filter-text-value-input.tsx +77 -0
  57. package/template/components/data-table/index.tsx +1612 -0
  58. package/template/components/data-table/pagination.tsx +256 -0
  59. package/template/components/data-table/types.ts +91 -0
  60. package/template/components/data-table/use-table-state.ts +566 -0
  61. package/template/components/data-view-dashboard-charts-compliance.tsx +960 -0
  62. package/template/components/data-view-dashboard-charts-team.tsx +968 -0
  63. package/template/components/data-view-dashboard-charts.tsx +1668 -0
  64. package/template/components/data-views/board-card-primitives.tsx +93 -0
  65. package/template/components/data-views/index.ts +41 -0
  66. package/template/components/data-views/list-page-board-card.tsx +192 -0
  67. package/template/components/data-views/list-page-board-template.tsx +122 -0
  68. package/template/components/data-views/placement-board-card.tsx +262 -0
  69. package/template/components/export-drawer.tsx +375 -0
  70. package/template/components/exxat-product-logo.tsx +453 -0
  71. package/template/components/form-layout-01.tsx +131 -0
  72. package/template/components/getting-started.tsx +625 -0
  73. package/template/components/key-metrics.tsx +920 -0
  74. package/template/components/leo-insight-indicator.tsx +364 -0
  75. package/template/components/leo-typing-dots.tsx +121 -0
  76. package/template/components/list-hub-status-badge.tsx +51 -0
  77. package/template/components/list-page-dashboard-charts.tsx +18 -0
  78. package/template/components/nav-documents.tsx +89 -0
  79. package/template/components/nav-main.tsx +58 -0
  80. package/template/components/nav-secondary.tsx +64 -0
  81. package/template/components/nav-user.tsx +190 -0
  82. package/template/components/new-placement-back-btn.tsx +28 -0
  83. package/template/components/new-placement-form.tsx +1066 -0
  84. package/template/components/onboarding/index.ts +4 -0
  85. package/template/components/onboarding/onboarding-01.tsx +7 -0
  86. package/template/components/onboarding/onboarding-02.tsx +7 -0
  87. package/template/components/onboarding/onboarding-03.tsx +7 -0
  88. package/template/components/onboarding/onboarding-04.tsx +7 -0
  89. package/template/components/page-header.tsx +57 -0
  90. package/template/components/placement-detail.tsx +438 -0
  91. package/template/components/placements-board-view.tsx +404 -0
  92. package/template/components/placements-list-view.tsx +285 -0
  93. package/template/components/placements-page-header.tsx +160 -0
  94. package/template/components/placements-table-columns.tsx +639 -0
  95. package/template/components/product-switcher.tsx +116 -0
  96. package/template/components/question-bank-board-view.tsx +205 -0
  97. package/template/components/question-bank-client.tsx +77 -0
  98. package/template/components/question-bank-list-view.tsx +59 -0
  99. package/template/components/question-bank-page-header.tsx +89 -0
  100. package/template/components/question-bank-table.tsx +586 -0
  101. package/template/components/rotations-empty-state.tsx +47 -0
  102. package/template/components/rotations-panel-activator.tsx +8 -0
  103. package/template/components/secondary-nav.tsx +394 -0
  104. package/template/components/secondary-panel.tsx +239 -0
  105. package/template/components/section-cards.tsx +106 -0
  106. package/template/components/settings-appearance-card.tsx +424 -0
  107. package/template/components/settings-client.tsx +537 -0
  108. package/template/components/settings-form-row.tsx +42 -0
  109. package/template/components/sidebar-auto-collapse.tsx +23 -0
  110. package/template/components/sidebar-auto-open.tsx +18 -0
  111. package/template/components/sidebar-shell.tsx +37 -0
  112. package/template/components/site-header.tsx +93 -0
  113. package/template/components/sites-all-client.tsx +154 -0
  114. package/template/components/sites-board-view.tsx +67 -0
  115. package/template/components/sites-list-view.tsx +47 -0
  116. package/template/components/sites-table.tsx +312 -0
  117. package/template/components/system-banner-slot.tsx +66 -0
  118. package/template/components/table-properties/column-row.tsx +90 -0
  119. package/template/components/table-properties/draggable-list.ts +49 -0
  120. package/template/components/table-properties/drawer-button.tsx +231 -0
  121. package/template/components/table-properties/drawer.tsx +1102 -0
  122. package/template/components/table-properties/filter-card.tsx +251 -0
  123. package/template/components/table-properties/index.ts +22 -0
  124. package/template/components/table-properties/sort-card.tsx +59 -0
  125. package/template/components/table-properties/types.ts +124 -0
  126. package/template/components/task-list-panel.tsx +98 -0
  127. package/template/components/task-priority-badge.tsx +28 -0
  128. package/template/components/team-board-view.tsx +114 -0
  129. package/template/components/team-client.tsx +93 -0
  130. package/template/components/team-list-view.tsx +62 -0
  131. package/template/components/team-page-header.tsx +92 -0
  132. package/template/components/team-table.tsx +525 -0
  133. package/template/components/templates/list-page.tsx +576 -0
  134. package/template/components/templates/primary-page-template.tsx +56 -0
  135. package/template/components/theme-color-sync.tsx +32 -0
  136. package/template/components/theme-provider.tsx +71 -0
  137. package/template/components/tinted-icon-disc.tsx +53 -0
  138. package/template/components/ui/ai-thinking-surface.tsx +121 -0
  139. package/template/components/ui/avatar.tsx +1 -0
  140. package/template/components/ui/badge.tsx +1 -0
  141. package/template/components/ui/banner.tsx +1 -0
  142. package/template/components/ui/breadcrumb.tsx +1 -0
  143. package/template/components/ui/button.tsx +1 -0
  144. package/template/components/ui/calendar.tsx +1 -0
  145. package/template/components/ui/card.tsx +1 -0
  146. package/template/components/ui/chart.tsx +1 -0
  147. package/template/components/ui/checkbox.tsx +1 -0
  148. package/template/components/ui/coach-mark.tsx +1 -0
  149. package/template/components/ui/collapsible.tsx +1 -0
  150. package/template/components/ui/command.tsx +1 -0
  151. package/template/components/ui/date-picker-field.tsx +1 -0
  152. package/template/components/ui/dialog.tsx +1 -0
  153. package/template/components/ui/dot-pattern.tsx +159 -0
  154. package/template/components/ui/drag-handle-grip.tsx +1 -0
  155. package/template/components/ui/drawer.tsx +1 -0
  156. package/template/components/ui/dropdown-menu.tsx +1 -0
  157. package/template/components/ui/field.tsx +1 -0
  158. package/template/components/ui/form.tsx +1 -0
  159. package/template/components/ui/input-group.tsx +1 -0
  160. package/template/components/ui/input-mask.tsx +1 -0
  161. package/template/components/ui/input.tsx +1 -0
  162. package/template/components/ui/kbd.tsx +1 -0
  163. package/template/components/ui/label.tsx +1 -0
  164. package/template/components/ui/leo-icon.tsx +726 -0
  165. package/template/components/ui/payment-card-fields.tsx +1 -0
  166. package/template/components/ui/popover.tsx +1 -0
  167. package/template/components/ui/radio-group.tsx +1 -0
  168. package/template/components/ui/select.tsx +1 -0
  169. package/template/components/ui/selection-tile-grid.tsx +1 -0
  170. package/template/components/ui/separator.tsx +1 -0
  171. package/template/components/ui/sheet.tsx +1 -0
  172. package/template/components/ui/sidebar.tsx +1 -0
  173. package/template/components/ui/skeleton.tsx +1 -0
  174. package/template/components/ui/sonner.tsx +1 -0
  175. package/template/components/ui/status-badge.tsx +1 -0
  176. package/template/components/ui/table.tsx +1 -0
  177. package/template/components/ui/tabs.tsx +1 -0
  178. package/template/components/ui/textarea.tsx +1 -0
  179. package/template/components/ui/tip.tsx +1 -0
  180. package/template/components/ui/toggle-group.tsx +1 -0
  181. package/template/components/ui/toggle-switch.tsx +1 -0
  182. package/template/components/ui/toggle.tsx +1 -0
  183. package/template/components/ui/tooltip.tsx +1 -0
  184. package/template/components/ui/view-segmented-control.tsx +1 -0
  185. package/template/components.json +27 -0
  186. package/template/contexts/chart-variant-context.tsx +35 -0
  187. package/template/contexts/command-menu-context.tsx +28 -0
  188. package/template/contexts/dashboard-view-context.tsx +35 -0
  189. package/template/contexts/product-context.tsx +38 -0
  190. package/template/contexts/system-banner-context.tsx +127 -0
  191. package/template/docs/command-menu-pattern.md +45 -0
  192. package/template/docs/data-views-pattern.md +160 -0
  193. package/template/ecosystem.config.cjs +20 -0
  194. package/template/eslint.config.mjs +18 -0
  195. package/template/fontawesome-subset.manifest.json +190 -0
  196. package/template/hooks/.gitkeep +0 -0
  197. package/template/hooks/use-app-theme.ts +1 -0
  198. package/template/hooks/use-coach-mark.ts +1 -0
  199. package/template/hooks/use-mobile.ts +1 -0
  200. package/template/hooks/use-mod-key-label.ts +1 -0
  201. package/template/lib/.gitkeep +0 -0
  202. package/template/lib/ask-leo-route-context.ts +133 -0
  203. package/template/lib/chart-keyboard-selection.test.ts +20 -0
  204. package/template/lib/chart-keyboard-selection.ts +17 -0
  205. package/template/lib/chart-line-dash.ts +16 -0
  206. package/template/lib/coach-mark-registry.ts +68 -0
  207. package/template/lib/command-menu-config.ts +127 -0
  208. package/template/lib/command-menu-search-data.ts +44 -0
  209. package/template/lib/conditional-rule-match.ts +32 -0
  210. package/template/lib/dashboard-customize-coach-mark.ts +18 -0
  211. package/template/lib/dashboard-layout-merge.ts +63 -0
  212. package/template/lib/data-list-display-options.ts +35 -0
  213. package/template/lib/data-list-persistence.ts +280 -0
  214. package/template/lib/data-list-view-surface.ts +58 -0
  215. package/template/lib/data-list-view.ts +29 -0
  216. package/template/lib/data-view-dashboard-storage.ts +101 -0
  217. package/template/lib/date-filter.ts +8 -0
  218. package/template/lib/dev-log.test.ts +28 -0
  219. package/template/lib/dev-log.ts +8 -0
  220. package/template/lib/editable-target.ts +10 -0
  221. package/template/lib/floating-sheet-panel.ts +72 -0
  222. package/template/lib/initials-from-name.ts +7 -0
  223. package/template/lib/list-page-table-properties.ts +52 -0
  224. package/template/lib/list-status-badges.ts +168 -0
  225. package/template/lib/logo-dev.ts +12 -0
  226. package/template/lib/mock/compliance-kpi.ts +61 -0
  227. package/template/lib/mock/compliance.ts +146 -0
  228. package/template/lib/mock/dashboard.ts +105 -0
  229. package/template/lib/mock/navigation.tsx +231 -0
  230. package/template/lib/mock/placements-kpi.ts +134 -0
  231. package/template/lib/mock/placements.ts +183 -0
  232. package/template/lib/mock/question-bank-kpi.ts +61 -0
  233. package/template/lib/mock/question-bank.ts +142 -0
  234. package/template/lib/mock/sites-directory.ts +16 -0
  235. package/template/lib/mock/sites-kpi.ts +25 -0
  236. package/template/lib/mock/team-kpi.ts +60 -0
  237. package/template/lib/mock/team.ts +118 -0
  238. package/template/lib/motion-ui.ts +17 -0
  239. package/template/lib/placement-board-card-layout.ts +79 -0
  240. package/template/lib/placement-lifecycle.ts +5 -0
  241. package/template/lib/row-height.ts +10 -0
  242. package/template/lib/stock-portrait.ts +11 -0
  243. package/template/lib/utils.test.ts +13 -0
  244. package/template/lib/utils.ts +1 -0
  245. package/template/next.config.mjs +15 -0
  246. package/template/package.json +83 -0
  247. package/template/postcss.config.mjs +8 -0
  248. package/template/public/.gitkeep +0 -0
  249. package/template/public/Illustration/Rotation.svg +74 -0
  250. package/template/public/avatars/user.svg +11 -0
  251. package/template/public/favicon/favicon.ico +0 -0
  252. package/template/public/favicon.ico +0 -0
  253. package/template/public/logos/exxat-one.svg +36 -0
  254. package/template/public/logos/exxat-prism.svg +39 -0
  255. package/template/public/mock-schools/emory.svg +4 -0
  256. package/template/public/mock-schools/rush.svg +4 -0
  257. package/template/scripts/fontawesome-subset-audit.mjs +190 -0
  258. package/template/scripts/pm2-startup-macos.sh +13 -0
  259. package/template/skills-lock.json +10 -0
  260. package/template/stores/app-store.ts +33 -0
  261. package/template/tests/setup.ts +1 -0
  262. package/template/tsconfig.json +35 -0
  263. package/template/types/react-payment-inputs.d.ts +19 -0
  264. package/template/vitest.config.ts +18 -0
@@ -0,0 +1,133 @@
1
+ "use client";
2
+
3
+ import {
4
+ IconBell,
5
+ IconBolt,
6
+ IconCalendar,
7
+ IconChartBar,
8
+ IconChartPie,
9
+ IconClock,
10
+ IconFileText,
11
+ IconHelp,
12
+ IconKeyboard,
13
+ IconLayoutDashboard,
14
+ IconLayoutKanban,
15
+ IconLogout,
16
+ IconMessage,
17
+ IconPalette,
18
+ IconSettings,
19
+ IconSquareCheck,
20
+ IconTarget,
21
+ IconTrendingUp,
22
+ IconUsers,
23
+ } from "@tabler/icons-react";
24
+ import { useEffect, useState } from "react";
25
+ import { Button } from "@/components/ui/button";
26
+ import {
27
+ Command,
28
+ CommandDialog,
29
+ CommandEmpty,
30
+ CommandGroup,
31
+ CommandInput,
32
+ CommandItem,
33
+ CommandList,
34
+ } from "@/components/ui/command";
35
+ import { Kbd } from "@/components/ui/kbd";
36
+
37
+ const workspaceItems = [
38
+ { icon: IconLayoutDashboard, label: "Dashboard" },
39
+ { icon: IconLayoutKanban, label: "Projects" },
40
+ { icon: IconSquareCheck, label: "Tasks" },
41
+ { icon: IconCalendar, label: "Calendar" },
42
+ { icon: IconUsers, label: "Team members" },
43
+ { icon: IconMessage, label: "Messages" },
44
+ { icon: IconFileText, label: "Documents" },
45
+ { icon: IconBell, label: "Notifications" },
46
+ { icon: IconClock, label: "Time tracking" },
47
+ { icon: IconTarget, label: "Goals" },
48
+ ];
49
+
50
+ const analyticsItems = [
51
+ { icon: IconChartBar, label: "Overview" },
52
+ { icon: IconTrendingUp, label: "Performance" },
53
+ { icon: IconChartPie, label: "Reports" },
54
+ { icon: IconBolt, label: "Insights" },
55
+ ];
56
+
57
+ const settingsItems = [
58
+ { icon: IconSettings, label: "Preferences" },
59
+ { icon: IconPalette, label: "Appearance" },
60
+ { icon: IconKeyboard, label: "Keyboard shortcuts" },
61
+ { icon: IconHelp, label: "Help & support" },
62
+ { icon: IconLogout, label: "Sign out" },
63
+ ];
64
+
65
+ export function CommandMenu01() {
66
+ const [open, setOpen] = useState(true);
67
+
68
+ useEffect(() => {
69
+ const down = (e: KeyboardEvent) => {
70
+ if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
71
+ e.preventDefault();
72
+ setOpen((prev) => !prev);
73
+ }
74
+ };
75
+
76
+ document.addEventListener("keydown", down);
77
+ return () => document.removeEventListener("keydown", down);
78
+ }, []);
79
+
80
+ return (
81
+ <>
82
+ <Button onClick={() => setOpen(true)} variant="outline">
83
+ Open Command Menu
84
+ </Button>
85
+
86
+ <CommandDialog onOpenChange={setOpen} open={open} showCloseButton={false}>
87
+ <Command className="rounded-none border-0 bg-transparent shadow-none">
88
+ <CommandInput
89
+ className="h-12"
90
+ placeholder="Type a command or search..."
91
+ />
92
+ <CommandList className="min-h-[min(420px,50vh)] max-h-[min(560px,65vh)]">
93
+ <CommandEmpty>No results found.</CommandEmpty>
94
+ <CommandGroup heading="Workspace">
95
+ {workspaceItems.map((item) => (
96
+ <CommandItem key={item.label}>
97
+ <item.icon className="me-2 h-5 w-5" />
98
+ <span>{item.label}</span>
99
+ </CommandItem>
100
+ ))}
101
+ </CommandGroup>
102
+ <CommandGroup heading="Analytics">
103
+ {analyticsItems.map((item) => (
104
+ <CommandItem key={item.label}>
105
+ <item.icon className="me-2 h-5 w-5" />
106
+ <span>{item.label}</span>
107
+ </CommandItem>
108
+ ))}
109
+ </CommandGroup>
110
+ <CommandGroup heading="Settings">
111
+ {settingsItems.map((item) => (
112
+ <CommandItem key={item.label}>
113
+ <item.icon className="me-2 h-5 w-5" />
114
+ <span>{item.label}</span>
115
+ </CommandItem>
116
+ ))}
117
+ </CommandGroup>
118
+ </CommandList>
119
+ </Command>
120
+ <div className="flex h-12 items-center justify-end border-t px-3">
121
+ <button
122
+ className="flex items-center gap-1 text-muted-foreground text-sm hover:text-foreground"
123
+ onClick={() => setOpen(false)}
124
+ type="button"
125
+ >
126
+ <span>Close</span>
127
+ <Kbd className="ms-1">Esc</Kbd>
128
+ </button>
129
+ </div>
130
+ </CommandDialog>
131
+ </>
132
+ );
133
+ }
@@ -0,0 +1,386 @@
1
+ "use client"
2
+
3
+ import {
4
+ IconArrowRight,
5
+ IconAt,
6
+ IconCopy,
7
+ IconDeviceDesktop,
8
+ IconDownload,
9
+ IconFile,
10
+ IconFileSearch,
11
+ IconKeyboard,
12
+ IconLink,
13
+ IconLogout,
14
+ IconMessage,
15
+ IconPencil,
16
+ IconPlus,
17
+ IconSend,
18
+ IconSettings,
19
+ IconTemplate,
20
+ IconUser,
21
+ IconUsers,
22
+ } from "@tabler/icons-react"
23
+ import { useEffect, useState } from "react"
24
+ import { Button } from "@/components/ui/button"
25
+ import {
26
+ Command,
27
+ CommandEmpty,
28
+ CommandGroup,
29
+ CommandInput,
30
+ CommandItem,
31
+ CommandList,
32
+ } from "@/components/ui/command"
33
+ import {
34
+ Dialog,
35
+ DialogContent,
36
+ DialogDescription,
37
+ DialogHeader,
38
+ DialogTitle,
39
+ } from "@/components/ui/dialog"
40
+ import { Kbd, KbdGroup } from "@/components/ui/kbd"
41
+
42
+ export function CommandMenu02() {
43
+ const [open, setOpen] = useState(false)
44
+ const [inputValue, setInputValue] = useState("")
45
+
46
+ useEffect(() => {
47
+ const down = (e: KeyboardEvent) => {
48
+ if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
49
+ e.preventDefault()
50
+ setOpen((prev) => !prev)
51
+ }
52
+ }
53
+
54
+ document.addEventListener("keydown", down)
55
+ return () => document.removeEventListener("keydown", down)
56
+ }, [])
57
+
58
+ return (
59
+ <>
60
+ <Button onClick={() => setOpen(true)} type="button" variant="outline">
61
+ Open Command Menu
62
+ </Button>
63
+
64
+ <Dialog onOpenChange={setOpen} open={open}>
65
+ <DialogContent
66
+ className="max-w-[min(42rem,calc(100%-2rem))] gap-0 overflow-hidden rounded-xl border-border/50 p-0 ring-0 shadow-[0_0_0_1px_color-mix(in_oklch,var(--brand-color)_48%,transparent),0_0_72px_-14px_color-mix(in_oklch,var(--brand-color)_58%,transparent),0_10px_15px_-3px_oklch(0_0_0_/_0.1)] dark:shadow-[0_0_0_1px_color-mix(in_oklch,var(--brand-color)_55%,transparent),0_0_80px_-12px_color-mix(in_oklch,var(--brand-color)_65%,transparent),0_10px_15px_-3px_oklch(0_0_0_/_0.35)] sm:max-w-3xl md:max-w-4xl"
67
+ showCloseButton={false}
68
+ >
69
+ <DialogHeader className="sr-only">
70
+ <DialogTitle>Command Menu</DialogTitle>
71
+ <DialogDescription>
72
+ Use the command menu to navigate through the app.
73
+ </DialogDescription>
74
+ </DialogHeader>
75
+ <Command className="flex h-full w-full flex-col overflow-hidden bg-popover p-0">
76
+ <div className="flex h-12 items-center gap-2 border-border/50 border-b px-4">
77
+ <CommandInput
78
+ variant="palette"
79
+ onValueChange={setInputValue}
80
+ placeholder="What do you need?"
81
+ value={inputValue}
82
+ />
83
+ <Button
84
+ type="button"
85
+ variant="ghost"
86
+ size="icon-sm"
87
+ tabIndex={-1}
88
+ className="shrink-0 text-muted-foreground hover:text-foreground"
89
+ aria-label="Close command menu"
90
+ onClick={() => setOpen(false)}
91
+ >
92
+ <Kbd>Esc</Kbd>
93
+ </Button>
94
+ </div>
95
+
96
+ <CommandList className="max-h-[min(640px,72vh)] py-2">
97
+ <CommandEmpty>No results found.</CommandEmpty>
98
+
99
+ <CommandGroup>
100
+ <CommandItem
101
+ className="mx-2 rounded-lg py-2.5"
102
+ onSelect={() => setOpen(false)}
103
+ >
104
+ <IconSettings aria-hidden />
105
+ Account Settings...
106
+ <KbdGroup className="ml-auto">
107
+ <Kbd>⌘</Kbd>
108
+ <Kbd>,</Kbd>
109
+ </KbdGroup>
110
+ </CommandItem>
111
+ <CommandItem
112
+ className="mx-2 rounded-lg py-2.5"
113
+ onSelect={() => setOpen(false)}
114
+ >
115
+ <IconUser aria-hidden />
116
+ Switch Workspace...
117
+ </CommandItem>
118
+ <CommandItem
119
+ className="mx-2 rounded-lg py-2.5"
120
+ onSelect={() => setOpen(false)}
121
+ >
122
+ <IconLogout aria-hidden />
123
+ Log Out
124
+ <KbdGroup className="ml-auto">
125
+ <Kbd>⌘</Kbd>
126
+ <Kbd>Q</Kbd>
127
+ </KbdGroup>
128
+ </CommandItem>
129
+ </CommandGroup>
130
+
131
+ <CommandGroup heading="Documents">
132
+ <CommandItem
133
+ className="mx-2 rounded-lg py-2.5"
134
+ onSelect={() => setOpen(false)}
135
+ >
136
+ <IconFile aria-hidden />
137
+ Search Documents...
138
+ <KbdGroup className="ml-auto">
139
+ <Kbd>⌘</Kbd>
140
+ <Kbd>F</Kbd>
141
+ </KbdGroup>
142
+ </CommandItem>
143
+ <CommandItem
144
+ className="mx-2 rounded-lg py-2.5"
145
+ onSelect={() => setOpen(false)}
146
+ >
147
+ <IconPlus aria-hidden />
148
+ Create New Document...
149
+ <KbdGroup className="ml-auto">
150
+ <Kbd>⌘</Kbd>
151
+ <Kbd>N</Kbd>
152
+ </KbdGroup>
153
+ </CommandItem>
154
+ <CommandItem
155
+ className="mx-2 rounded-lg py-2.5"
156
+ onSelect={() => setOpen(false)}
157
+ >
158
+ <IconFile aria-hidden />
159
+ Upload Document...
160
+ <KbdGroup className="ml-auto">
161
+ <Kbd>⌘</Kbd>
162
+ <Kbd>U</Kbd>
163
+ </KbdGroup>
164
+ </CommandItem>
165
+ </CommandGroup>
166
+
167
+ <CommandGroup heading="Signing">
168
+ <CommandItem
169
+ className="mx-2 rounded-lg py-2.5"
170
+ onSelect={() => setOpen(false)}
171
+ >
172
+ <IconSend aria-hidden />
173
+ Request Signature...
174
+ </CommandItem>
175
+ <CommandItem
176
+ className="mx-2 rounded-lg py-2.5"
177
+ onSelect={() => setOpen(false)}
178
+ >
179
+ <IconPencil aria-hidden />
180
+ Sign a Document...
181
+ </CommandItem>
182
+ <CommandItem
183
+ className="mx-2 rounded-lg py-2.5"
184
+ onSelect={() => setOpen(false)}
185
+ >
186
+ <IconUsers aria-hidden />
187
+ Bulk Send for Signature...
188
+ </CommandItem>
189
+ </CommandGroup>
190
+
191
+ <CommandGroup heading="Templates">
192
+ <CommandItem
193
+ className="mx-2 rounded-lg py-2.5"
194
+ onSelect={() => setOpen(false)}
195
+ >
196
+ <IconTemplate aria-hidden />
197
+ Search Templates...
198
+ </CommandItem>
199
+ <CommandItem
200
+ className="mx-2 rounded-lg py-2.5"
201
+ onSelect={() => setOpen(false)}
202
+ >
203
+ <IconPlus aria-hidden />
204
+ Create New Template...
205
+ </CommandItem>
206
+ </CommandGroup>
207
+
208
+ <CommandGroup heading="General">
209
+ <CommandItem
210
+ className="mx-2 rounded-lg py-2.5"
211
+ onSelect={() => setOpen(false)}
212
+ >
213
+ <IconDeviceDesktop aria-hidden />
214
+ Change Theme...
215
+ <KbdGroup className="ml-auto">
216
+ <Kbd>⌘</Kbd>
217
+ <Kbd>T</Kbd>
218
+ </KbdGroup>
219
+ </CommandItem>
220
+ <CommandItem
221
+ className="mx-2 rounded-lg py-2.5"
222
+ onSelect={() => setOpen(false)}
223
+ >
224
+ <IconCopy aria-hidden />
225
+ Copy Current URL
226
+ <KbdGroup className="ml-auto">
227
+ <Kbd>⌘</Kbd>
228
+ <Kbd>⇧</Kbd>
229
+ <Kbd>C</Kbd>
230
+ </KbdGroup>
231
+ </CommandItem>
232
+ </CommandGroup>
233
+
234
+ <CommandGroup heading="Navigation">
235
+ <CommandItem
236
+ className="mx-2 rounded-lg py-2.5"
237
+ onSelect={() => setOpen(false)}
238
+ >
239
+ <IconArrowRight aria-hidden />
240
+ <span>
241
+ Go to&nbsp;<strong className="font-semibold">Inbox</strong>
242
+ </span>
243
+ </CommandItem>
244
+ <CommandItem
245
+ className="mx-2 rounded-lg py-2.5"
246
+ onSelect={() => setOpen(false)}
247
+ >
248
+ <IconArrowRight aria-hidden />
249
+ <span>
250
+ Go to&nbsp;
251
+ <strong className="font-semibold">Action Required</strong>
252
+ </span>
253
+ </CommandItem>
254
+ <CommandItem
255
+ className="mx-2 rounded-lg py-2.5"
256
+ onSelect={() => setOpen(false)}
257
+ >
258
+ <IconArrowRight aria-hidden />
259
+ <span>
260
+ Go to&nbsp;
261
+ <strong className="font-semibold">
262
+ Waiting for Others
263
+ </strong>
264
+ </span>
265
+ </CommandItem>
266
+ <CommandItem
267
+ className="mx-2 rounded-lg py-2.5"
268
+ onSelect={() => setOpen(false)}
269
+ >
270
+ <IconArrowRight aria-hidden />
271
+ <span>
272
+ Go to&nbsp;
273
+ <strong className="font-semibold">Completed</strong>
274
+ </span>
275
+ </CommandItem>
276
+ <CommandItem
277
+ className="mx-2 rounded-lg py-2.5"
278
+ onSelect={() => setOpen(false)}
279
+ >
280
+ <IconArrowRight aria-hidden />
281
+ <span>
282
+ Go to&nbsp;<strong className="font-semibold">Drafts</strong>
283
+ </span>
284
+ </CommandItem>
285
+ <CommandItem
286
+ className="mx-2 rounded-lg py-2.5"
287
+ onSelect={() => setOpen(false)}
288
+ >
289
+ <IconArrowRight aria-hidden />
290
+ <span>
291
+ Go to&nbsp;
292
+ <strong className="font-semibold">Templates</strong>
293
+ </span>
294
+ </CommandItem>
295
+ <CommandItem
296
+ className="mx-2 rounded-lg py-2.5"
297
+ onSelect={() => setOpen(false)}
298
+ >
299
+ <IconArrowRight aria-hidden />
300
+ <span>
301
+ Go to&nbsp;
302
+ <strong className="font-semibold">Archive</strong>
303
+ </span>
304
+ </CommandItem>
305
+ <CommandItem
306
+ className="mx-2 rounded-lg py-2.5"
307
+ onSelect={() => setOpen(false)}
308
+ >
309
+ <IconArrowRight aria-hidden />
310
+ <span>
311
+ Go to&nbsp;<strong className="font-semibold">Trash</strong>
312
+ </span>
313
+ </CommandItem>
314
+ <CommandItem
315
+ className="mx-2 rounded-lg py-2.5"
316
+ onSelect={() => setOpen(false)}
317
+ >
318
+ <IconArrowRight aria-hidden />
319
+ <span>
320
+ Go to&nbsp;
321
+ <strong className="font-semibold">Settings</strong>
322
+ </span>
323
+ </CommandItem>
324
+ </CommandGroup>
325
+
326
+ <CommandGroup heading="Quick Actions">
327
+ <CommandItem
328
+ className="mx-2 rounded-lg py-2.5"
329
+ onSelect={() => setOpen(false)}
330
+ >
331
+ <IconLink aria-hidden />
332
+ Copy Signing Link
333
+ </CommandItem>
334
+ <CommandItem
335
+ className="mx-2 rounded-lg py-2.5"
336
+ onSelect={() => setOpen(false)}
337
+ >
338
+ <IconDownload aria-hidden />
339
+ Download Document
340
+ </CommandItem>
341
+ </CommandGroup>
342
+
343
+ <CommandGroup heading="Help">
344
+ <CommandItem
345
+ className="mx-2 rounded-lg py-2.5"
346
+ onSelect={() => setOpen(false)}
347
+ >
348
+ <IconFileSearch aria-hidden />
349
+ Search Help Center...
350
+ </CommandItem>
351
+ <CommandItem
352
+ className="mx-2 rounded-lg py-2.5"
353
+ onSelect={() => setOpen(false)}
354
+ >
355
+ <IconMessage aria-hidden />
356
+ Send Feedback...
357
+ </CommandItem>
358
+ <CommandItem
359
+ className="mx-2 rounded-lg py-2.5"
360
+ onSelect={() => setOpen(false)}
361
+ >
362
+ <IconAt aria-hidden />
363
+ Contact Support
364
+ </CommandItem>
365
+ </CommandGroup>
366
+
367
+ <CommandGroup heading="Keyboard Shortcuts">
368
+ <CommandItem
369
+ className="mx-2 rounded-lg py-2.5"
370
+ onSelect={() => setOpen(false)}
371
+ >
372
+ <IconKeyboard aria-hidden />
373
+ View Keyboard Shortcuts
374
+ <KbdGroup className="ml-auto">
375
+ <Kbd>⌘</Kbd>
376
+ <Kbd>/</Kbd>
377
+ </KbdGroup>
378
+ </CommandItem>
379
+ </CommandGroup>
380
+ </CommandList>
381
+ </Command>
382
+ </DialogContent>
383
+ </Dialog>
384
+ </>
385
+ )
386
+ }
@@ -0,0 +1,182 @@
1
+ "use client"
2
+
3
+ /**
4
+ * CommandMenu — ⌘K palette shell (Dialog + Command). Data comes from `CommandMenuProvider`
5
+ * (`lib/command-menu-config.ts`); no hard-coded nav or copy here.
6
+ */
7
+
8
+ import { useRouter } from "next/navigation"
9
+ import { useCallback, useEffect, useRef, useState } from "react"
10
+ import {
11
+ Command,
12
+ CommandEmpty,
13
+ CommandGroup,
14
+ CommandInput,
15
+ CommandItem,
16
+ CommandList,
17
+ } from "@/components/ui/command"
18
+ import {
19
+ Dialog,
20
+ DialogContent,
21
+ DialogDescription,
22
+ DialogHeader,
23
+ DialogTitle,
24
+ } from "@/components/ui/dialog"
25
+ import { Button } from "@/components/ui/button"
26
+ import { Kbd } from "@/components/ui/kbd"
27
+ import { useAskLeo } from "@/components/ask-leo-sidebar"
28
+ import { useCommandMenuConfig } from "@/contexts/command-menu-context"
29
+ import type { CommandMenuItem } from "@/lib/command-menu-config"
30
+
31
+ /** Dispatched to open the palette from sidebar and other callers (avoid synthetic ⌘K). */
32
+ export const OPEN_COMMAND_MENU_EVENT = "exxat:open-command-menu"
33
+
34
+ export function requestOpenCommandMenu() {
35
+ window.dispatchEvent(new CustomEvent(OPEN_COMMAND_MENU_EVENT))
36
+ }
37
+
38
+ function CommandMenuItemRow({
39
+ item,
40
+ onLink,
41
+ onLeo,
42
+ }: {
43
+ item: CommandMenuItem
44
+ onLink: (href: string) => void
45
+ onLeo: (prompt: string) => void
46
+ }) {
47
+ const isLeo = Boolean(item.askLeoPrompt)
48
+ const iconClass = isLeo
49
+ ? "fa-duotone fa-solid fa-star-christmas w-5 shrink-0 text-center text-brand"
50
+ : `${item.icon ?? "fa-light fa-arrow-right"} w-5 shrink-0 text-center`
51
+
52
+ return (
53
+ <CommandItem
54
+ className="mx-2 rounded-lg py-2.5"
55
+ keywords={item.keywords ? [item.keywords] : undefined}
56
+ onSelect={() => {
57
+ if (item.askLeoPrompt) onLeo(item.askLeoPrompt)
58
+ else if (item.href) onLink(item.href)
59
+ }}
60
+ >
61
+ <i className={iconClass} aria-hidden="true" />
62
+ <span>{item.label}</span>
63
+ </CommandItem>
64
+ )
65
+ }
66
+
67
+ export function CommandMenu() {
68
+ const config = useCommandMenuConfig()
69
+ const [open, setOpen] = useState(false)
70
+ const [inputValue, setInputValue] = useState("")
71
+ const searchInputRef = useRef<HTMLInputElement>(null)
72
+ const router = useRouter()
73
+ const { openWithPrompt } = useAskLeo()
74
+
75
+ useEffect(() => {
76
+ const down = (e: KeyboardEvent) => {
77
+ if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
78
+ e.preventDefault()
79
+ setOpen((prev) => !prev)
80
+ }
81
+ }
82
+ document.addEventListener("keydown", down)
83
+ return () => document.removeEventListener("keydown", down)
84
+ }, [])
85
+
86
+ useEffect(() => {
87
+ const onOpenRequest = () => setOpen(true)
88
+ window.addEventListener(OPEN_COMMAND_MENU_EVENT, onOpenRequest)
89
+ return () => window.removeEventListener(OPEN_COMMAND_MENU_EVENT, onOpenRequest)
90
+ }, [])
91
+
92
+ const navigate = useCallback(
93
+ (href: string) => {
94
+ setOpen(false)
95
+ setInputValue("")
96
+ router.push(href)
97
+ },
98
+ [router],
99
+ )
100
+
101
+ const sendLeoSuggestion = useCallback(
102
+ (prompt: string) => {
103
+ setOpen(false)
104
+ setInputValue("")
105
+ openWithPrompt(prompt)
106
+ },
107
+ [openWithPrompt],
108
+ )
109
+
110
+ return (
111
+ <Dialog
112
+ onOpenChange={(next) => {
113
+ setOpen(next)
114
+ if (!next) setInputValue("")
115
+ }}
116
+ open={open}
117
+ >
118
+ <DialogContent
119
+ overlayClassName="bg-transparent supports-backdrop-filter:backdrop-blur-none"
120
+ showCloseButton={false}
121
+ onOpenAutoFocus={(e) => {
122
+ e.preventDefault()
123
+ requestAnimationFrame(() => searchInputRef.current?.focus())
124
+ }}
125
+ className="max-w-[min(42rem,calc(100%-2rem))] gap-0 overflow-hidden rounded-xl border-border/50 p-0 ring-0 shadow-[0_0_0_1px_color-mix(in_oklch,var(--brand-color)_48%,transparent),0_0_72px_-14px_color-mix(in_oklch,var(--brand-color)_58%,transparent),0_10px_15px_-3px_oklch(0_0_0_/_0.1)] dark:shadow-[0_0_0_1px_color-mix(in_oklch,var(--brand-color)_55%,transparent),0_0_80px_-12px_color-mix(in_oklch,var(--brand-color)_65%,transparent),0_10px_15px_-3px_oklch(0_0_0_/_0.35)] sm:max-w-3xl md:max-w-4xl"
126
+ >
127
+ <DialogHeader className="sr-only">
128
+ <DialogTitle>{config.dialogTitle}</DialogTitle>
129
+ <DialogDescription>{config.dialogDescription}</DialogDescription>
130
+ </DialogHeader>
131
+ <Command
132
+ loop
133
+ label={config.commandLabel}
134
+ className="flex h-full w-full flex-col overflow-hidden bg-popover p-0"
135
+ >
136
+ <div className="flex h-12 items-center gap-2 border-border/50 border-b px-4">
137
+ <CommandInput
138
+ ref={searchInputRef}
139
+ variant="palette"
140
+ aria-label={config.inputAriaLabel}
141
+ onValueChange={setInputValue}
142
+ placeholder={config.inputPlaceholder}
143
+ value={inputValue}
144
+ />
145
+ <Button
146
+ type="button"
147
+ variant="ghost"
148
+ size="icon-sm"
149
+ tabIndex={-1}
150
+ className="shrink-0 text-muted-foreground hover:text-foreground"
151
+ aria-label={config.closeMenuAriaLabel}
152
+ onClick={() => setOpen(false)}
153
+ >
154
+ <Kbd>Esc</Kbd>
155
+ </Button>
156
+ </div>
157
+
158
+ <CommandList className="max-h-[min(640px,72vh)] py-2">
159
+ <CommandEmpty>{config.emptyMessage}</CommandEmpty>
160
+
161
+ {config.groups.map((group) => {
162
+ if (group.items.length === 0) return null
163
+ if (group.searchOnly && !inputValue.trim()) return null
164
+ return (
165
+ <CommandGroup key={group.id} heading={group.heading}>
166
+ {group.items.map((item) => (
167
+ <CommandMenuItemRow
168
+ key={item.id}
169
+ item={item}
170
+ onLeo={sendLeoSuggestion}
171
+ onLink={navigate}
172
+ />
173
+ ))}
174
+ </CommandGroup>
175
+ )
176
+ })}
177
+ </CommandList>
178
+ </Command>
179
+ </DialogContent>
180
+ </Dialog>
181
+ )
182
+ }