@comitor/ui 0.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 (307) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +458 -0
  3. package/dist/chart/chart.d.ts +88 -0
  4. package/dist/chart/chart.js +199 -0
  5. package/dist/chart/chart.js.map +1 -0
  6. package/dist/chart/comitor-chart.d.ts +109 -0
  7. package/dist/chart/comitor-chart.js +189 -0
  8. package/dist/chart/comitor-chart.js.map +1 -0
  9. package/dist/chart/index.d.ts +4 -0
  10. package/dist/chart/index.js +3 -0
  11. package/dist/chart/index.js.map +1 -0
  12. package/dist/components/composite/avatar-group.d.ts +33 -0
  13. package/dist/components/composite/avatar-group.js +86 -0
  14. package/dist/components/composite/avatar-group.js.map +1 -0
  15. package/dist/components/composite/combobox.d.ts +70 -0
  16. package/dist/components/composite/combobox.js +291 -0
  17. package/dist/components/composite/combobox.js.map +1 -0
  18. package/dist/components/composite/command-palette.d.ts +56 -0
  19. package/dist/components/composite/command-palette.js +131 -0
  20. package/dist/components/composite/command-palette.js.map +1 -0
  21. package/dist/components/composite/confirm-dialog.d.ts +40 -0
  22. package/dist/components/composite/confirm-dialog.js +96 -0
  23. package/dist/components/composite/confirm-dialog.js.map +1 -0
  24. package/dist/components/composite/copy-button.d.ts +27 -0
  25. package/dist/components/composite/copy-button.js +80 -0
  26. package/dist/components/composite/copy-button.js.map +1 -0
  27. package/dist/components/composite/data-table.d.ts +136 -0
  28. package/dist/components/composite/data-table.js +300 -0
  29. package/dist/components/composite/data-table.js.map +1 -0
  30. package/dist/components/composite/date-picker.d.ts +77 -0
  31. package/dist/components/composite/date-picker.js +268 -0
  32. package/dist/components/composite/date-picker.js.map +1 -0
  33. package/dist/components/composite/empty-state.d.ts +38 -0
  34. package/dist/components/composite/empty-state.js +35 -0
  35. package/dist/components/composite/empty-state.js.map +1 -0
  36. package/dist/components/composite/filter-chips.d.ts +39 -0
  37. package/dist/components/composite/filter-chips.js +79 -0
  38. package/dist/components/composite/filter-chips.js.map +1 -0
  39. package/dist/components/composite/form-field.d.ts +58 -0
  40. package/dist/components/composite/form-field.js +81 -0
  41. package/dist/components/composite/form-field.js.map +1 -0
  42. package/dist/components/composite/form-section.d.ts +33 -0
  43. package/dist/components/composite/form-section.js +85 -0
  44. package/dist/components/composite/form-section.js.map +1 -0
  45. package/dist/components/composite/inline-edit.d.ts +38 -0
  46. package/dist/components/composite/inline-edit.js +165 -0
  47. package/dist/components/composite/inline-edit.js.map +1 -0
  48. package/dist/components/composite/keyboard-hint.d.ts +31 -0
  49. package/dist/components/composite/keyboard-hint.js +72 -0
  50. package/dist/components/composite/keyboard-hint.js.map +1 -0
  51. package/dist/components/composite/letter-avatar.d.ts +47 -0
  52. package/dist/components/composite/letter-avatar.js +66 -0
  53. package/dist/components/composite/letter-avatar.js.map +1 -0
  54. package/dist/components/composite/page-container.d.ts +25 -0
  55. package/dist/components/composite/page-container.js +23 -0
  56. package/dist/components/composite/page-container.js.map +1 -0
  57. package/dist/components/composite/page-header.d.ts +49 -0
  58. package/dist/components/composite/page-header.js +75 -0
  59. package/dist/components/composite/page-header.js.map +1 -0
  60. package/dist/components/composite/relative-time.d.ts +28 -0
  61. package/dist/components/composite/relative-time.js +44 -0
  62. package/dist/components/composite/relative-time.js.map +1 -0
  63. package/dist/components/composite/search-filter-bar.d.ts +38 -0
  64. package/dist/components/composite/search-filter-bar.js +135 -0
  65. package/dist/components/composite/search-filter-bar.js.map +1 -0
  66. package/dist/components/composite/stat-card.d.ts +48 -0
  67. package/dist/components/composite/stat-card.js +76 -0
  68. package/dist/components/composite/stat-card.js.map +1 -0
  69. package/dist/components/composite/status-config.d.ts +85 -0
  70. package/dist/components/composite/status-config.js +41 -0
  71. package/dist/components/composite/status-config.js.map +1 -0
  72. package/dist/components/composite/status-pill.d.ts +26 -0
  73. package/dist/components/composite/status-pill.js +59 -0
  74. package/dist/components/composite/status-pill.js.map +1 -0
  75. package/dist/components/composite/table-pagination.d.ts +31 -0
  76. package/dist/components/composite/table-pagination.js +103 -0
  77. package/dist/components/composite/table-pagination.js.map +1 -0
  78. package/dist/components/composite/text-utils.d.ts +38 -0
  79. package/dist/components/composite/text-utils.js +40 -0
  80. package/dist/components/composite/text-utils.js.map +1 -0
  81. package/dist/components/composite/toast.d.ts +56 -0
  82. package/dist/components/composite/toast.js +63 -0
  83. package/dist/components/composite/toast.js.map +1 -0
  84. package/dist/components/ui/accordion.d.ts +17 -0
  85. package/dist/components/ui/accordion.js +53 -0
  86. package/dist/components/ui/accordion.js.map +1 -0
  87. package/dist/components/ui/alert-dialog.d.ts +34 -0
  88. package/dist/components/ui/alert-dialog.js +103 -0
  89. package/dist/components/ui/alert-dialog.js.map +1 -0
  90. package/dist/components/ui/alert.d.ts +16 -0
  91. package/dist/components/ui/alert.js +50 -0
  92. package/dist/components/ui/alert.js.map +1 -0
  93. package/dist/components/ui/aspect-ratio.d.ts +7 -0
  94. package/dist/components/ui/aspect-ratio.js +10 -0
  95. package/dist/components/ui/aspect-ratio.js.map +1 -0
  96. package/dist/components/ui/avatar.d.ts +11 -0
  97. package/dist/components/ui/avatar.js +33 -0
  98. package/dist/components/ui/avatar.js.map +1 -0
  99. package/dist/components/ui/badge.d.ts +15 -0
  100. package/dist/components/ui/badge.js +37 -0
  101. package/dist/components/ui/badge.js.map +1 -0
  102. package/dist/components/ui/breadcrumb.d.ts +30 -0
  103. package/dist/components/ui/breadcrumb.js +89 -0
  104. package/dist/components/ui/breadcrumb.js.map +1 -0
  105. package/dist/components/ui/button-group.d.ts +21 -0
  106. package/dist/components/ui/button-group.js +62 -0
  107. package/dist/components/ui/button-group.js.map +1 -0
  108. package/dist/components/ui/button.d.ts +21 -0
  109. package/dist/components/ui/button.js +70 -0
  110. package/dist/components/ui/button.js.map +1 -0
  111. package/dist/components/ui/calendar.d.ts +26 -0
  112. package/dist/components/ui/calendar.js +148 -0
  113. package/dist/components/ui/calendar.js.map +1 -0
  114. package/dist/components/ui/card.d.ts +33 -0
  115. package/dist/components/ui/card.js +70 -0
  116. package/dist/components/ui/card.js.map +1 -0
  117. package/dist/components/ui/carousel.d.ts +46 -0
  118. package/dist/components/ui/carousel.js +173 -0
  119. package/dist/components/ui/carousel.js.map +1 -0
  120. package/dist/components/ui/checkbox.d.ts +17 -0
  121. package/dist/components/ui/checkbox.js +67 -0
  122. package/dist/components/ui/checkbox.js.map +1 -0
  123. package/dist/components/ui/collapsible.d.ts +17 -0
  124. package/dist/components/ui/collapsible.js +18 -0
  125. package/dist/components/ui/collapsible.js.map +1 -0
  126. package/dist/components/ui/command.d.ts +41 -0
  127. package/dist/components/ui/command.js +122 -0
  128. package/dist/components/ui/command.js.map +1 -0
  129. package/dist/components/ui/context-menu.d.ts +51 -0
  130. package/dist/components/ui/context-menu.js +166 -0
  131. package/dist/components/ui/context-menu.js.map +1 -0
  132. package/dist/components/ui/dialog.d.ts +48 -0
  133. package/dist/components/ui/dialog.js +131 -0
  134. package/dist/components/ui/dialog.js.map +1 -0
  135. package/dist/components/ui/drawer.d.ts +32 -0
  136. package/dist/components/ui/drawer.js +102 -0
  137. package/dist/components/ui/drawer.js.map +1 -0
  138. package/dist/components/ui/dropdown-menu.d.ts +52 -0
  139. package/dist/components/ui/dropdown-menu.js +167 -0
  140. package/dist/components/ui/dropdown-menu.js.map +1 -0
  141. package/dist/components/ui/empty.d.ts +22 -0
  142. package/dist/components/ui/empty.js +87 -0
  143. package/dist/components/ui/empty.js.map +1 -0
  144. package/dist/components/ui/field.d.ts +46 -0
  145. package/dist/components/ui/field.js +197 -0
  146. package/dist/components/ui/field.js.map +1 -0
  147. package/dist/components/ui/form.d.ts +50 -0
  148. package/dist/components/ui/form.js +100 -0
  149. package/dist/components/ui/form.js.map +1 -0
  150. package/dist/components/ui/hover-card.d.ts +17 -0
  151. package/dist/components/ui/hover-card.js +31 -0
  152. package/dist/components/ui/hover-card.js.map +1 -0
  153. package/dist/components/ui/input-group.d.ts +26 -0
  154. package/dist/components/ui/input-group.js +148 -0
  155. package/dist/components/ui/input-group.js.map +1 -0
  156. package/dist/components/ui/input-otp.d.ts +23 -0
  157. package/dist/components/ui/input-otp.js +54 -0
  158. package/dist/components/ui/input-otp.js.map +1 -0
  159. package/dist/components/ui/input.d.ts +21 -0
  160. package/dist/components/ui/input.js +62 -0
  161. package/dist/components/ui/input.js.map +1 -0
  162. package/dist/components/ui/item.d.ts +39 -0
  163. package/dist/components/ui/item.js +142 -0
  164. package/dist/components/ui/item.js.map +1 -0
  165. package/dist/components/ui/kbd.d.ts +8 -0
  166. package/dist/components/ui/kbd.js +25 -0
  167. package/dist/components/ui/kbd.js.map +1 -0
  168. package/dist/components/ui/label.d.ts +7 -0
  169. package/dist/components/ui/label.js +21 -0
  170. package/dist/components/ui/label.js.map +1 -0
  171. package/dist/components/ui/menubar.d.ts +51 -0
  172. package/dist/components/ui/menubar.js +196 -0
  173. package/dist/components/ui/menubar.js.map +1 -0
  174. package/dist/components/ui/navigation-menu.d.ts +39 -0
  175. package/dist/components/ui/navigation-menu.js +123 -0
  176. package/dist/components/ui/navigation-menu.js.map +1 -0
  177. package/dist/components/ui/pagination.d.ts +40 -0
  178. package/dist/components/ui/pagination.js +95 -0
  179. package/dist/components/ui/pagination.js.map +1 -0
  180. package/dist/components/ui/popover.d.ts +19 -0
  181. package/dist/components/ui/popover.js +35 -0
  182. package/dist/components/ui/popover.js.map +1 -0
  183. package/dist/components/ui/progress.d.ts +12 -0
  184. package/dist/components/ui/progress.js +27 -0
  185. package/dist/components/ui/progress.js.map +1 -0
  186. package/dist/components/ui/radio-group.d.ts +9 -0
  187. package/dist/components/ui/radio-group.js +34 -0
  188. package/dist/components/ui/radio-group.js.map +1 -0
  189. package/dist/components/ui/resizable.d.ts +19 -0
  190. package/dist/components/ui/resizable.js +38 -0
  191. package/dist/components/ui/resizable.js.map +1 -0
  192. package/dist/components/ui/scroll-area.d.ts +13 -0
  193. package/dist/components/ui/scroll-area.js +46 -0
  194. package/dist/components/ui/scroll-area.js.map +1 -0
  195. package/dist/components/ui/select.d.ts +33 -0
  196. package/dist/components/ui/select.js +133 -0
  197. package/dist/components/ui/select.js.map +1 -0
  198. package/dist/components/ui/separator.d.ts +7 -0
  199. package/dist/components/ui/separator.js +23 -0
  200. package/dist/components/ui/separator.js.map +1 -0
  201. package/dist/components/ui/sheet.d.ts +38 -0
  202. package/dist/components/ui/sheet.js +96 -0
  203. package/dist/components/ui/sheet.js.map +1 -0
  204. package/dist/components/ui/skeleton.d.ts +6 -0
  205. package/dist/components/ui/skeleton.js +9 -0
  206. package/dist/components/ui/skeleton.js.map +1 -0
  207. package/dist/components/ui/slider.d.ts +12 -0
  208. package/dist/components/ui/slider.js +54 -0
  209. package/dist/components/ui/slider.js.map +1 -0
  210. package/dist/components/ui/sonner.d.ts +9 -0
  211. package/dist/components/ui/sonner.js +25 -0
  212. package/dist/components/ui/sonner.js.map +1 -0
  213. package/dist/components/ui/spinner.d.ts +6 -0
  214. package/dist/components/ui/spinner.js +10 -0
  215. package/dist/components/ui/spinner.js.map +1 -0
  216. package/dist/components/ui/switch.d.ts +14 -0
  217. package/dist/components/ui/switch.js +65 -0
  218. package/dist/components/ui/switch.js.map +1 -0
  219. package/dist/components/ui/table.d.ts +30 -0
  220. package/dist/components/ui/table.js +87 -0
  221. package/dist/components/ui/table.js.map +1 -0
  222. package/dist/components/ui/tabs.d.ts +35 -0
  223. package/dist/components/ui/tabs.js +54 -0
  224. package/dist/components/ui/tabs.js.map +1 -0
  225. package/dist/components/ui/textarea.d.ts +6 -0
  226. package/dist/components/ui/textarea.js +19 -0
  227. package/dist/components/ui/textarea.js.map +1 -0
  228. package/dist/components/ui/toggle-group.d.ts +18 -0
  229. package/dist/components/ui/toggle-group.js +52 -0
  230. package/dist/components/ui/toggle-group.js.map +1 -0
  231. package/dist/components/ui/toggle.d.ts +15 -0
  232. package/dist/components/ui/toggle.js +33 -0
  233. package/dist/components/ui/toggle.js.map +1 -0
  234. package/dist/components/ui/tooltip.d.ts +21 -0
  235. package/dist/components/ui/tooltip.js +38 -0
  236. package/dist/components/ui/tooltip.js.map +1 -0
  237. package/dist/form.d.ts +5 -0
  238. package/dist/form.js +2 -0
  239. package/dist/form.js.map +1 -0
  240. package/dist/index.d.ts +111 -0
  241. package/dist/index.js +76 -0
  242. package/dist/index.js.map +1 -0
  243. package/dist/lib/cn.d.ts +16 -0
  244. package/dist/lib/cn.js +9 -0
  245. package/dist/lib/cn.js.map +1 -0
  246. package/dist/shell/app-header.d.ts +41 -0
  247. package/dist/shell/app-header.js +136 -0
  248. package/dist/shell/app-header.js.map +1 -0
  249. package/dist/shell/app-launcher.d.ts +33 -0
  250. package/dist/shell/app-launcher.js +189 -0
  251. package/dist/shell/app-launcher.js.map +1 -0
  252. package/dist/shell/app-shell.d.ts +44 -0
  253. package/dist/shell/app-shell.js +99 -0
  254. package/dist/shell/app-shell.js.map +1 -0
  255. package/dist/shell/comitor-lockup.d.ts +33 -0
  256. package/dist/shell/comitor-lockup.js +112 -0
  257. package/dist/shell/comitor-lockup.js.map +1 -0
  258. package/dist/shell/comitor-logo.d.ts +28 -0
  259. package/dist/shell/comitor-logo.js +92 -0
  260. package/dist/shell/comitor-logo.js.map +1 -0
  261. package/dist/shell/index.d.ts +24 -0
  262. package/dist/shell/index.js +23 -0
  263. package/dist/shell/index.js.map +1 -0
  264. package/dist/shell/mobile-menu.d.ts +26 -0
  265. package/dist/shell/mobile-menu.js +56 -0
  266. package/dist/shell/mobile-menu.js.map +1 -0
  267. package/dist/shell/notifications-button.d.ts +22 -0
  268. package/dist/shell/notifications-button.js +39 -0
  269. package/dist/shell/notifications-button.js.map +1 -0
  270. package/dist/shell/shell-context.d.ts +91 -0
  271. package/dist/shell/shell-context.js +233 -0
  272. package/dist/shell/shell-context.js.map +1 -0
  273. package/dist/shell/shell-link.d.ts +19 -0
  274. package/dist/shell/shell-link.js +18 -0
  275. package/dist/shell/shell-link.js.map +1 -0
  276. package/dist/shell/sidebar-group.d.ts +21 -0
  277. package/dist/shell/sidebar-group.js +43 -0
  278. package/dist/shell/sidebar-group.js.map +1 -0
  279. package/dist/shell/sidebar-menu-item.d.ts +27 -0
  280. package/dist/shell/sidebar-menu-item.js +152 -0
  281. package/dist/shell/sidebar-menu-item.js.map +1 -0
  282. package/dist/shell/sidebar.d.ts +26 -0
  283. package/dist/shell/sidebar.js +92 -0
  284. package/dist/shell/sidebar.js.map +1 -0
  285. package/dist/shell/theme-provider.d.ts +18 -0
  286. package/dist/shell/theme-provider.js +29 -0
  287. package/dist/shell/theme-provider.js.map +1 -0
  288. package/dist/shell/theme-toggle.d.ts +28 -0
  289. package/dist/shell/theme-toggle.js +67 -0
  290. package/dist/shell/theme-toggle.js.map +1 -0
  291. package/dist/shell/types.d.ts +168 -0
  292. package/dist/shell/types.js +38 -0
  293. package/dist/shell/types.js.map +1 -0
  294. package/dist/shell/user-menu.d.ts +42 -0
  295. package/dist/shell/user-menu.js +132 -0
  296. package/dist/shell/user-menu.js.map +1 -0
  297. package/dist/shell/utils.d.ts +62 -0
  298. package/dist/shell/utils.js +73 -0
  299. package/dist/shell/utils.js.map +1 -0
  300. package/dist/shell/workspace-switcher.d.ts +24 -0
  301. package/dist/shell/workspace-switcher.js +219 -0
  302. package/dist/shell/workspace-switcher.js.map +1 -0
  303. package/dist/tokens.d.ts +173 -0
  304. package/dist/tokens.js +161 -0
  305. package/dist/tokens.js.map +1 -0
  306. package/package.json +151 -0
  307. package/styles.css +357 -0
@@ -0,0 +1,189 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { LayoutGrid, Lock } from "lucide-react";
4
+ import { useRef } from "react";
5
+ import { Badge } from "../components/ui/badge.js";
6
+ import { Popover, PopoverContent, PopoverTrigger } from "../components/ui/popover.js";
7
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../components/ui/tooltip.js";
8
+ import { cn } from "../lib/cn.js";
9
+ import { useShell } from "./shell-context.js";
10
+ import { ShellNavLink } from "./shell-link.js";
11
+ import { appAccentStyle, getEntitledApps, getLockedApps } from "./utils.js";
12
+ const TILE_ATTR = "data-app-tile";
13
+ function AppLauncherPanel({ variant = "grid", className, onSelected }) {
14
+ const { apps, currentApp, onAppSelect, onUpsell, labels } = useShell();
15
+ const entitled = getEntitledApps(apps);
16
+ const locked = getLockedApps(apps);
17
+ const total = entitled.length + locked.length;
18
+ const rootRef = useRef(null);
19
+ const columns = variant === "grid" ? 3 : 1;
20
+ const handleKeyDown = (event) => {
21
+ const root = rootRef.current;
22
+ if (!root) return;
23
+ const tiles = Array.from(root.querySelectorAll(`[${TILE_ATTR}]`));
24
+ const index = tiles.indexOf(event.currentTarget);
25
+ if (index < 0) return;
26
+ const moves = {
27
+ ArrowRight: index + 1,
28
+ ArrowLeft: index - 1,
29
+ ArrowDown: index + columns,
30
+ ArrowUp: index - columns,
31
+ Home: 0,
32
+ End: tiles.length - 1
33
+ };
34
+ const next = moves[event.key];
35
+ if (next === void 0) return;
36
+ event.preventDefault();
37
+ tiles[Math.max(0, Math.min(tiles.length - 1, next))]?.focus();
38
+ };
39
+ const renderTile = (app) => {
40
+ const isLocked = app.entitled === false;
41
+ const isCurrent = app.id === currentApp?.id;
42
+ const Icon = app.icon;
43
+ const tileClass = cn(
44
+ "group relative flex rounded-md text-left transition-colors duration-150 ease-out",
45
+ variant === "grid" ? "flex-col items-start gap-1.5 p-2.5" : "items-center gap-2.5 p-2",
46
+ isLocked ? "opacity-60 hover:bg-muted hover:opacity-100" : "hover:bg-accent",
47
+ isCurrent && "bg-accent"
48
+ );
49
+ const body = /* @__PURE__ */ jsxs(Fragment, { children: [
50
+ /* @__PURE__ */ jsxs(
51
+ "span",
52
+ {
53
+ className: cn(
54
+ "relative flex shrink-0 items-center justify-center rounded-md",
55
+ variant === "grid" ? "size-9" : "size-8",
56
+ isLocked ? "bg-muted text-muted-foreground" : "bg-app-accent/15 text-app-accent"
57
+ ),
58
+ style: appAccentStyle(app),
59
+ children: [
60
+ /* @__PURE__ */ jsx(Icon, { className: "size-4" }),
61
+ isLocked ? /* @__PURE__ */ jsx("span", { className: "absolute -bottom-1 -right-1 flex size-4 items-center justify-center rounded-full bg-muted text-muted-foreground ring-2 ring-popover", children: /* @__PURE__ */ jsx(Lock, { className: "size-2.5" }) }) : null
62
+ ]
63
+ }
64
+ ),
65
+ /* @__PURE__ */ jsxs("span", { className: cn("flex min-w-0 flex-col", variant === "list" && "flex-1"), children: [
66
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5", children: [
67
+ /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-medium text-foreground", children: app.name }),
68
+ app.badge ? /* @__PURE__ */ jsx(Badge, { variant: "destructive", size: "sm", children: app.badge }) : null
69
+ ] }),
70
+ app.description ? /* @__PURE__ */ jsx(
71
+ "span",
72
+ {
73
+ className: cn("text-[11px] leading-snug text-muted-foreground", variant === "grid" && "line-clamp-2"),
74
+ children: app.description
75
+ }
76
+ ) : null
77
+ ] })
78
+ ] });
79
+ if (isLocked) {
80
+ return /* @__PURE__ */ jsx(
81
+ "button",
82
+ {
83
+ type: "button",
84
+ "data-app-tile": "",
85
+ title: labels.lockedHint,
86
+ "aria-label": `${app.name} \u2014 ${labels.locked}`,
87
+ className: tileClass,
88
+ onKeyDown: handleKeyDown,
89
+ onClick: () => {
90
+ onUpsell?.(app.id);
91
+ onSelected?.();
92
+ },
93
+ children: body
94
+ },
95
+ app.id
96
+ );
97
+ }
98
+ if (onAppSelect) {
99
+ return /* @__PURE__ */ jsx(
100
+ "button",
101
+ {
102
+ type: "button",
103
+ "data-app-tile": "",
104
+ "aria-current": isCurrent ? "page" : void 0,
105
+ className: tileClass,
106
+ onKeyDown: handleKeyDown,
107
+ onClick: () => {
108
+ onAppSelect(app);
109
+ onSelected?.();
110
+ },
111
+ children: body
112
+ },
113
+ app.id
114
+ );
115
+ }
116
+ return /* @__PURE__ */ jsx(
117
+ ShellNavLink,
118
+ {
119
+ href: app.href,
120
+ "data-app-tile": "",
121
+ "aria-current": isCurrent ? "page" : void 0,
122
+ className: tileClass,
123
+ onKeyDown: handleKeyDown,
124
+ onClick: () => onSelected?.(),
125
+ children: body
126
+ },
127
+ app.id
128
+ );
129
+ };
130
+ if (total === 0) return null;
131
+ const groupClass = variant === "grid" ? "grid grid-cols-3 gap-1" : "flex flex-col gap-0.5";
132
+ return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: cn("flex flex-col gap-3", className), children: [
133
+ entitled.length > 0 ? /* @__PURE__ */ jsxs("section", { children: [
134
+ /* @__PURE__ */ jsx("p", { className: "px-1 pb-1.5 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: labels.myApps }),
135
+ /* @__PURE__ */ jsx("div", { className: groupClass, children: entitled.map(renderTile) })
136
+ ] }) : null,
137
+ locked.length > 0 ? /* @__PURE__ */ jsxs("section", { children: [
138
+ /* @__PURE__ */ jsx("p", { className: "px-1 pb-1.5 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: labels.discoverApps }),
139
+ /* @__PURE__ */ jsx("div", { className: groupClass, children: locked.map(renderTile) })
140
+ ] }) : null
141
+ ] });
142
+ }
143
+ function AppLauncher({
144
+ className,
145
+ variant = "grid",
146
+ inline = false,
147
+ align = "end",
148
+ onSelected
149
+ }) {
150
+ const { apps, appLauncherOpen, setAppLauncherOpen, labels } = useShell();
151
+ if (apps.length === 0) return null;
152
+ if (inline) {
153
+ return /* @__PURE__ */ jsx(AppLauncherPanel, { variant, className, onSelected });
154
+ }
155
+ return /* @__PURE__ */ jsxs(Popover, { open: appLauncherOpen, onOpenChange: setAppLauncherOpen, children: [
156
+ /* @__PURE__ */ jsxs(Tooltip, { children: [
157
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
158
+ "button",
159
+ {
160
+ type: "button",
161
+ "aria-label": labels.appLauncherLabel,
162
+ className: cn(
163
+ "flex size-9 items-center justify-center rounded-md text-muted-foreground",
164
+ "transition-colors duration-150 ease-out hover:bg-muted hover:text-foreground",
165
+ appLauncherOpen && "bg-muted text-foreground",
166
+ className
167
+ ),
168
+ children: /* @__PURE__ */ jsx(LayoutGrid, { className: "size-4" })
169
+ }
170
+ ) }) }),
171
+ /* @__PURE__ */ jsx(TooltipContent, { side: "bottom", children: labels.apps })
172
+ ] }),
173
+ /* @__PURE__ */ jsx(PopoverContent, { align, sideOffset: 8, className: "w-[22rem] p-2", children: /* @__PURE__ */ jsx(
174
+ AppLauncherPanel,
175
+ {
176
+ variant,
177
+ onSelected: () => {
178
+ setAppLauncherOpen(false);
179
+ onSelected?.();
180
+ }
181
+ }
182
+ ) })
183
+ ] });
184
+ }
185
+ export {
186
+ AppLauncher,
187
+ AppLauncherPanel
188
+ };
189
+ //# sourceMappingURL=app-launcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/shell/app-launcher.tsx"],"sourcesContent":["\"use client\";\n\nimport { LayoutGrid, Lock } from \"lucide-react\";\nimport { type KeyboardEvent, useRef } from \"react\";\nimport { Badge } from \"../components/ui/badge.js\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"../components/ui/popover.js\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"../components/ui/tooltip.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { useShell } from \"./shell-context.js\";\nimport { ShellNavLink } from \"./shell-link.js\";\nimport type { AppDescriptor } from \"./types.js\";\nimport { appAccentStyle, getEntitledApps, getLockedApps } from \"./utils.js\";\n\n/** Đánh dấu ô app để điều hướng bàn phím tìm được, kể cả ô render bằng LinkComponent của app. */\nconst TILE_ATTR = \"data-app-tile\";\n\nexport interface AppLauncherPanelProps {\n /** \"grid\" = lưới 3 cột kiểu Lark/Google Workspace; \"list\" = danh sách dọc (menu mobile). */\n variant?: \"grid\" | \"list\";\n className?: string;\n /** Gọi sau khi người dùng chọn một app — dùng để đóng popover/menu mobile. */\n onSelected?: () => void;\n}\n\n/**\n * Nội dung của AppLauncher, tách riêng để `MobileMenu` nhúng thẳng (không popover).\n *\n * Điều hướng bàn phím: ←/→/↑/↓ chạy giữa các ô (xuyên cả hai nhóm như một lưới liền),\n * Home/End nhảy đầu-cuối; Enter/Space để chính thẻ button/link xử lý.\n * Vị trí ô đọc từ DOM chứ không giữ mảng ref: ô \"đã mở khoá\" có thể là `LinkComponent`\n * do app cung cấp — không chắc component đó nhận `ref`.\n */\nexport function AppLauncherPanel({ variant = \"grid\", className, onSelected }: AppLauncherPanelProps) {\n const { apps, currentApp, onAppSelect, onUpsell, labels } = useShell();\n\n const entitled = getEntitledApps(apps);\n const locked = getLockedApps(apps);\n const total = entitled.length + locked.length;\n\n const rootRef = useRef<HTMLDivElement>(null);\n const columns = variant === \"grid\" ? 3 : 1;\n\n const handleKeyDown = (event: KeyboardEvent<HTMLElement>) => {\n const root = rootRef.current;\n if (!root) return;\n const tiles = Array.from(root.querySelectorAll<HTMLElement>(`[${TILE_ATTR}]`));\n const index = tiles.indexOf(event.currentTarget);\n if (index < 0) return;\n\n const moves: Record<string, number | undefined> = {\n ArrowRight: index + 1,\n ArrowLeft: index - 1,\n ArrowDown: index + columns,\n ArrowUp: index - columns,\n Home: 0,\n End: tiles.length - 1\n };\n const next = moves[event.key];\n if (next === undefined) return;\n\n event.preventDefault();\n tiles[Math.max(0, Math.min(tiles.length - 1, next))]?.focus();\n };\n\n const renderTile = (app: AppDescriptor) => {\n const isLocked = app.entitled === false;\n const isCurrent = app.id === currentApp?.id;\n const Icon = app.icon;\n\n const tileClass = cn(\n \"group relative flex rounded-md text-left transition-colors duration-150 ease-out\",\n variant === \"grid\" ? \"flex-col items-start gap-1.5 p-2.5\" : \"items-center gap-2.5 p-2\",\n isLocked ? \"opacity-60 hover:bg-muted hover:opacity-100\" : \"hover:bg-accent\",\n isCurrent && \"bg-accent\"\n );\n\n const body = (\n <>\n <span\n className={cn(\n \"relative flex shrink-0 items-center justify-center rounded-md\",\n variant === \"grid\" ? \"size-9\" : \"size-8\",\n isLocked ? \"bg-muted text-muted-foreground\" : \"bg-app-accent/15 text-app-accent\"\n )}\n style={appAccentStyle(app)}\n >\n <Icon className=\"size-4\" />\n {isLocked ? (\n <span className=\"absolute -bottom-1 -right-1 flex size-4 items-center justify-center rounded-full bg-muted text-muted-foreground ring-2 ring-popover\">\n <Lock className=\"size-2.5\" />\n </span>\n ) : null}\n </span>\n\n <span className={cn(\"flex min-w-0 flex-col\", variant === \"list\" && \"flex-1\")}>\n <span className=\"flex items-center gap-1.5\">\n <span className=\"truncate text-sm font-medium text-foreground\">{app.name}</span>\n {app.badge ? (\n <Badge variant=\"destructive\" size=\"sm\">\n {app.badge}\n </Badge>\n ) : null}\n </span>\n {app.description ? (\n <span\n className={cn(\"text-[11px] leading-snug text-muted-foreground\", variant === \"grid\" && \"line-clamp-2\")}\n >\n {app.description}\n </span>\n ) : null}\n </span>\n </>\n );\n\n // Chưa mở khoá → không điều hướng, chỉ báo cho app biết để mời nâng gói.\n if (isLocked) {\n return (\n <button\n key={app.id}\n type=\"button\"\n data-app-tile=\"\"\n title={labels.lockedHint}\n aria-label={`${app.name} — ${labels.locked}`}\n className={tileClass}\n onKeyDown={handleKeyDown}\n onClick={() => {\n onUpsell?.(app.id);\n onSelected?.();\n }}\n >\n {body}\n </button>\n );\n }\n\n // Có `onAppSelect` → app tự lo điều hướng (đổi subdomain, nạp trước dữ liệu…);\n // không có → dùng link thật để giữ chuột-giữa / mở tab mới / trình đọc màn hình.\n if (onAppSelect) {\n return (\n <button\n key={app.id}\n type=\"button\"\n data-app-tile=\"\"\n aria-current={isCurrent ? \"page\" : undefined}\n className={tileClass}\n onKeyDown={handleKeyDown}\n onClick={() => {\n onAppSelect(app);\n onSelected?.();\n }}\n >\n {body}\n </button>\n );\n }\n\n return (\n <ShellNavLink\n key={app.id}\n href={app.href}\n data-app-tile=\"\"\n aria-current={isCurrent ? \"page\" : undefined}\n className={tileClass}\n onKeyDown={handleKeyDown}\n onClick={() => onSelected?.()}\n >\n {body}\n </ShellNavLink>\n );\n };\n\n if (total === 0) return null;\n\n const groupClass = variant === \"grid\" ? \"grid grid-cols-3 gap-1\" : \"flex flex-col gap-0.5\";\n\n return (\n <div ref={rootRef} className={cn(\"flex flex-col gap-3\", className)}>\n {entitled.length > 0 ? (\n <section>\n <p className=\"px-1 pb-1.5 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground\">\n {labels.myApps}\n </p>\n <div className={groupClass}>{entitled.map(renderTile)}</div>\n </section>\n ) : null}\n\n {locked.length > 0 ? (\n <section>\n <p className=\"px-1 pb-1.5 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground\">\n {labels.discoverApps}\n </p>\n <div className={groupClass}>{locked.map(renderTile)}</div>\n </section>\n ) : null}\n </div>\n );\n}\n\nexport interface AppLauncherProps extends AppLauncherPanelProps {\n /** Bỏ nút bấm + Popover, render thẳng panel (MobileMenu dùng cách này). */\n inline?: boolean;\n align?: \"start\" | \"center\" | \"end\";\n}\n\n/**\n * **Trục 2 — chuyển sản phẩm trong cùng workspace.**\n *\n * Nút lưới 3×3 ở header (kiểu Lark / Google Workspace) mở popover lưới ô app.\n * Trạng thái mở nằm ở `ShellProvider` để phím tắt `Cmd/Ctrl + Shift + K` bật được.\n * App chưa có trong gói hiện ô khoá và gọi `onUpsell(appId)` thay vì điều hướng.\n */\nexport function AppLauncher({\n className,\n variant = \"grid\",\n inline = false,\n align = \"end\",\n onSelected\n}: AppLauncherProps) {\n const { apps, appLauncherOpen, setAppLauncherOpen, labels } = useShell();\n\n if (apps.length === 0) return null;\n\n if (inline) {\n return <AppLauncherPanel variant={variant} className={className} onSelected={onSelected} />;\n }\n\n return (\n <Popover open={appLauncherOpen} onOpenChange={setAppLauncherOpen}>\n <Tooltip>\n <TooltipTrigger asChild>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n aria-label={labels.appLauncherLabel}\n className={cn(\n \"flex size-9 items-center justify-center rounded-md text-muted-foreground\",\n \"transition-colors duration-150 ease-out hover:bg-muted hover:text-foreground\",\n appLauncherOpen && \"bg-muted text-foreground\",\n className\n )}\n >\n <LayoutGrid className=\"size-4\" />\n </button>\n </PopoverTrigger>\n </TooltipTrigger>\n <TooltipContent side=\"bottom\">{labels.apps}</TooltipContent>\n </Tooltip>\n\n <PopoverContent align={align} sideOffset={8} className=\"w-[22rem] p-2\">\n <AppLauncherPanel\n variant={variant}\n onSelected={() => {\n setAppLauncherOpen(false);\n onSelected?.();\n }}\n />\n </PopoverContent>\n </Popover>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,44 @@
1
+ import * as React from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { AppHeaderProps } from './app-header.js';
4
+ import { MobileMenuProps } from './mobile-menu.js';
5
+ import { ShellProviderProps } from './shell-context.js';
6
+ import { SidebarProps } from './sidebar.js';
7
+ import './user-menu.js';
8
+ import './types.js';
9
+ import 'lucide-react';
10
+
11
+ type AppShellLayout = "sidebar-first" | "header-first";
12
+ interface AppShellProps extends ShellProviderProps {
13
+ /**
14
+ * "sidebar-first" (mặc định) — sidebar cao hết màn hình, header nằm bên phải nó
15
+ * (kiểu Linear/Lark: WorkspaceSwitcher ở đỉnh sidebar).
16
+ * "header-first" — header full width trên cùng, sidebar nằm dưới
17
+ * (WorkspaceSwitcher chuyển lên header).
18
+ */
19
+ layout?: AppShellLayout;
20
+ /** Ẩn sidebar hoàn toàn (app một khu, chỉ có header). */
21
+ showSidebar?: boolean;
22
+ headerProps?: AppHeaderProps;
23
+ sidebarProps?: SidebarProps;
24
+ mobileMenuProps?: MobileMenuProps;
25
+ /**
26
+ * Slot Command Palette — app tự render (dữ liệu tìm kiếm là của app),
27
+ * đọc/ghi trạng thái qua `useShell().commandPaletteOpen`.
28
+ */
29
+ commandPaletteSlot?: ReactNode;
30
+ className?: string;
31
+ contentClassName?: string;
32
+ }
33
+ /**
34
+ * Khung ứng dụng lắp sẵn: `ShellProvider` + `Sidebar` + `AppHeader` + `<main>` + `MobileMenu`.
35
+ *
36
+ * ⚠ Bọc app bằng `<ThemeProvider>` ở **root layout**, NGOÀI `<AppShell>` — next-themes
37
+ * cần nằm trên cùng để tránh nháy theme lúc hydrate.
38
+ *
39
+ * Có `"use client"`: mọi callback (`onLogout`, `onWorkspaceChange`…) là hàm nên nơi
40
+ * render `<AppShell>` phải là client component.
41
+ */
42
+ declare function AppShell({ layout, showSidebar, headerProps, sidebarProps, mobileMenuProps, commandPaletteSlot, className, contentClassName, children, ...providerProps }: AppShellProps): React.JSX.Element;
43
+
44
+ export { AppShell, type AppShellLayout, type AppShellProps };
@@ -0,0 +1,99 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { cn } from "../lib/cn.js";
4
+ import { AppHeader } from "./app-header.js";
5
+ import { MobileMenu } from "./mobile-menu.js";
6
+ import { ShellProvider, useShell } from "./shell-context.js";
7
+ import { Sidebar } from "./sidebar.js";
8
+ import { appAccentStyle } from "./utils.js";
9
+ import { WorkspaceSwitcher } from "./workspace-switcher.js";
10
+ function AppShell({
11
+ layout = "sidebar-first",
12
+ showSidebar = true,
13
+ headerProps,
14
+ sidebarProps,
15
+ mobileMenuProps,
16
+ commandPaletteSlot,
17
+ className,
18
+ contentClassName,
19
+ children,
20
+ ...providerProps
21
+ }) {
22
+ return /* @__PURE__ */ jsx(ShellProvider, { ...providerProps, children: /* @__PURE__ */ jsx(
23
+ AppShellFrame,
24
+ {
25
+ layout,
26
+ showSidebar,
27
+ headerProps,
28
+ sidebarProps,
29
+ mobileMenuProps,
30
+ commandPaletteSlot,
31
+ className,
32
+ contentClassName,
33
+ children
34
+ }
35
+ ) });
36
+ }
37
+ function AppShellFrame({
38
+ layout = "sidebar-first",
39
+ showSidebar = true,
40
+ headerProps,
41
+ sidebarProps,
42
+ mobileMenuProps,
43
+ commandPaletteSlot,
44
+ className,
45
+ contentClassName,
46
+ children
47
+ }) {
48
+ const { currentApp } = useShell();
49
+ const headerFirst = layout === "header-first";
50
+ const header = /* @__PURE__ */ jsx(
51
+ AppHeader,
52
+ {
53
+ ...headerProps,
54
+ showSidebarToggle: headerProps?.showSidebarToggle ?? headerFirst,
55
+ logo: headerProps?.logo ?? (headerFirst ? /* @__PURE__ */ jsx(WorkspaceSwitcher, { className: "w-56 shrink-0" }) : void 0)
56
+ }
57
+ );
58
+ const sidebar = showSidebar ? /* @__PURE__ */ jsx(
59
+ Sidebar,
60
+ {
61
+ ...sidebarProps,
62
+ showWorkspaceSwitcher: sidebarProps?.showWorkspaceSwitcher ?? !headerFirst,
63
+ showCollapseButton: sidebarProps?.showCollapseButton ?? !headerFirst
64
+ }
65
+ ) : null;
66
+ const main = /* @__PURE__ */ jsx("main", { className: cn("flex-1 overflow-y-auto bg-background", contentClassName), children });
67
+ return /* @__PURE__ */ jsxs(
68
+ "div",
69
+ {
70
+ style: appAccentStyle(currentApp),
71
+ className: cn(
72
+ "flex h-dvh w-full overflow-hidden bg-background",
73
+ headerFirst ? "flex-col" : "flex-row",
74
+ className
75
+ ),
76
+ children: [
77
+ headerFirst ? /* @__PURE__ */ jsxs(Fragment, { children: [
78
+ header,
79
+ /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1", children: [
80
+ sidebar,
81
+ main
82
+ ] })
83
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
84
+ sidebar,
85
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [
86
+ header,
87
+ main
88
+ ] })
89
+ ] }),
90
+ /* @__PURE__ */ jsx(MobileMenu, { ...mobileMenuProps }),
91
+ commandPaletteSlot
92
+ ]
93
+ }
94
+ );
95
+ }
96
+ export {
97
+ AppShell
98
+ };
99
+ //# sourceMappingURL=app-shell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/shell/app-shell.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { cn } from \"../lib/cn.js\";\nimport { AppHeader, type AppHeaderProps } from \"./app-header.js\";\nimport { MobileMenu, type MobileMenuProps } from \"./mobile-menu.js\";\nimport { ShellProvider, type ShellProviderProps, useShell } from \"./shell-context.js\";\nimport { Sidebar, type SidebarProps } from \"./sidebar.js\";\nimport { appAccentStyle } from \"./utils.js\";\nimport { WorkspaceSwitcher } from \"./workspace-switcher.js\";\n\nexport type AppShellLayout = \"sidebar-first\" | \"header-first\";\n\nexport interface AppShellProps extends ShellProviderProps {\n /**\n * \"sidebar-first\" (mặc định) — sidebar cao hết màn hình, header nằm bên phải nó\n * (kiểu Linear/Lark: WorkspaceSwitcher ở đỉnh sidebar).\n * \"header-first\" — header full width trên cùng, sidebar nằm dưới\n * (WorkspaceSwitcher chuyển lên header).\n */\n layout?: AppShellLayout;\n /** Ẩn sidebar hoàn toàn (app một khu, chỉ có header). */\n showSidebar?: boolean;\n headerProps?: AppHeaderProps;\n sidebarProps?: SidebarProps;\n mobileMenuProps?: MobileMenuProps;\n /**\n * Slot Command Palette — app tự render (dữ liệu tìm kiếm là của app),\n * đọc/ghi trạng thái qua `useShell().commandPaletteOpen`.\n */\n commandPaletteSlot?: ReactNode;\n className?: string;\n contentClassName?: string;\n}\n\n/**\n * Khung ứng dụng lắp sẵn: `ShellProvider` + `Sidebar` + `AppHeader` + `<main>` + `MobileMenu`.\n *\n * ⚠ Bọc app bằng `<ThemeProvider>` ở **root layout**, NGOÀI `<AppShell>` — next-themes\n * cần nằm trên cùng để tránh nháy theme lúc hydrate.\n *\n * Có `\"use client\"`: mọi callback (`onLogout`, `onWorkspaceChange`…) là hàm nên nơi\n * render `<AppShell>` phải là client component.\n */\nexport function AppShell({\n layout = \"sidebar-first\",\n showSidebar = true,\n headerProps,\n sidebarProps,\n mobileMenuProps,\n commandPaletteSlot,\n className,\n contentClassName,\n children,\n ...providerProps\n}: AppShellProps) {\n return (\n <ShellProvider {...providerProps}>\n <AppShellFrame\n layout={layout}\n showSidebar={showSidebar}\n headerProps={headerProps}\n sidebarProps={sidebarProps}\n mobileMenuProps={mobileMenuProps}\n commandPaletteSlot={commandPaletteSlot}\n className={className}\n contentClassName={contentClassName}\n >\n {children}\n </AppShellFrame>\n </ShellProvider>\n );\n}\n\ntype AppShellFrameProps = Pick<\n AppShellProps,\n | \"layout\"\n | \"showSidebar\"\n | \"headerProps\"\n | \"sidebarProps\"\n | \"mobileMenuProps\"\n | \"commandPaletteSlot\"\n | \"className\"\n | \"contentClassName\"\n> & { children: ReactNode };\n\n/**\n * Phần khung thật sự — tách khỏi `AppShell` vì cần `useShell()` (phải ở TRONG provider)\n * để gán `--app-accent` của app đang mở cho cả cây.\n */\nfunction AppShellFrame({\n layout = \"sidebar-first\",\n showSidebar = true,\n headerProps,\n sidebarProps,\n mobileMenuProps,\n commandPaletteSlot,\n className,\n contentClassName,\n children\n}: AppShellFrameProps) {\n const { currentApp } = useShell();\n const headerFirst = layout === \"header-first\";\n\n // Ở header-first, bộ chuyển workspace chuyển lên header — nếu để cả hai chỗ\n // thì người dùng thấy hai nút giống hệt nhau.\n // Spread TRƯỚC rồi mới đặt giá trị mặc định, nếu không `{...headerProps}` sẽ ghi đè\n // bằng `undefined` và giá trị mặc định tính ở trên thành công cốc.\n const header = (\n <AppHeader\n {...headerProps}\n showSidebarToggle={headerProps?.showSidebarToggle ?? headerFirst}\n logo={headerProps?.logo ?? (headerFirst ? <WorkspaceSwitcher className=\"w-56 shrink-0\" /> : undefined)}\n />\n );\n\n const sidebar = showSidebar ? (\n <Sidebar\n {...sidebarProps}\n showWorkspaceSwitcher={sidebarProps?.showWorkspaceSwitcher ?? !headerFirst}\n showCollapseButton={sidebarProps?.showCollapseButton ?? !headerFirst}\n />\n ) : null;\n\n const main = <main className={cn(\"flex-1 overflow-y-auto bg-background\", contentClassName)}>{children}</main>;\n\n return (\n <div\n style={appAccentStyle(currentApp)}\n className={cn(\n \"flex h-dvh w-full overflow-hidden bg-background\",\n headerFirst ? \"flex-col\" : \"flex-row\",\n className\n )}\n >\n {headerFirst ? (\n <>\n {header}\n <div className=\"flex min-h-0 flex-1\">\n {sidebar}\n {main}\n </div>\n </>\n ) : (\n <>\n {sidebar}\n <div className=\"flex min-w-0 flex-1 flex-col\">\n {header}\n {main}\n </div>\n </>\n )}\n\n <MobileMenu {...mobileMenuProps} />\n {commandPaletteSlot}\n </div>\n );\n}\n"],"mappings":";AA0DM,SA8EE,UA9EF,KAgFI,YAhFJ;AAvDN,SAAS,UAAU;AACnB,SAAS,iBAAsC;AAC/C,SAAS,kBAAwC;AACjD,SAAS,eAAwC,gBAAgB;AACjE,SAAS,eAAkC;AAC3C,SAAS,sBAAsB;AAC/B,SAAS,yBAAyB;AAmC3B,SAAS,SAAS;AAAA,EACvB,SAAS;AAAA,EACT,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAkB;AAChB,SACE,oBAAC,iBAAe,GAAG,eACjB;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MAEC;AAAA;AAAA,EACH,GACF;AAEJ;AAkBA,SAAS,cAAc;AAAA,EACrB,SAAS;AAAA,EACT,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAuB;AACrB,QAAM,EAAE,WAAW,IAAI,SAAS;AAChC,QAAM,cAAc,WAAW;AAM/B,QAAM,SACJ;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,mBAAmB,aAAa,qBAAqB;AAAA,MACrD,MAAM,aAAa,SAAS,cAAc,oBAAC,qBAAkB,WAAU,iBAAgB,IAAK;AAAA;AAAA,EAC9F;AAGF,QAAM,UAAU,cACd;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,uBAAuB,cAAc,yBAAyB,CAAC;AAAA,MAC/D,oBAAoB,cAAc,sBAAsB,CAAC;AAAA;AAAA,EAC3D,IACE;AAEJ,QAAM,OAAO,oBAAC,UAAK,WAAW,GAAG,wCAAwC,gBAAgB,GAAI,UAAS;AAEtG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,eAAe,UAAU;AAAA,MAChC,WAAW;AAAA,QACT;AAAA,QACA,cAAc,aAAa;AAAA,QAC3B;AAAA,MACF;AAAA,MAEC;AAAA,sBACC,iCACG;AAAA;AAAA,UACD,qBAAC,SAAI,WAAU,uBACZ;AAAA;AAAA,YACA;AAAA,aACH;AAAA,WACF,IAEA,iCACG;AAAA;AAAA,UACD,qBAAC,SAAI,WAAU,gCACZ;AAAA;AAAA,YACA;AAAA,aACH;AAAA,WACF;AAAA,QAGF,oBAAC,cAAY,GAAG,iBAAiB;AAAA,QAChC;AAAA;AAAA;AAAA,EACH;AAEJ;","names":[]}
@@ -0,0 +1,33 @@
1
+ import * as React from 'react';
2
+ import { CSSProperties } from 'react';
3
+
4
+ /**
5
+ * Lockup ngang chính thức của Comitor — icon bên trái, wordmark + gạch vàng + slogan bên phải.
6
+ * Giữ đúng tỉ lệ hình học của brand book để mọi bề mặt sản phẩm dùng chung một bố cục.
7
+ *
8
+ * <ComitorLockup variant="auto" size="xs" slogan="Tasks" />
9
+ *
10
+ * • `variant` — light (nền trắng/kem), dark (nền mực/ấm), auto (tự đổi theo theme).
11
+ * • `size` — xs | sm | md | lg: đổi cỡ icon và cỡ chữ cùng lúc.
12
+ * • `wordmark` / `slogan` — đổi chữ khi cần cặp ngôn ngữ khác
13
+ * (vd "COMITOR TASKS", "THIỆN MINH / CÙNG NHAU TIẾN XA").
14
+ *
15
+ * Không có "use client": thuần trình bày, RSC render được.
16
+ */
17
+ type ComitorLockupVariant = "light" | "dark" | "auto";
18
+ type ComitorLockupSize = "xs" | "sm" | "md" | "lg";
19
+ interface ComitorLockupProps {
20
+ className?: string;
21
+ variant?: ComitorLockupVariant;
22
+ size?: ComitorLockupSize;
23
+ wordmark?: string;
24
+ slogan?: string;
25
+ /** Class Tailwind GHI ĐÈ style slogan của preset cỡ chữ. Dùng khi cần căn bề rộng
26
+ * slogan tuỳ biến cho khớp wordmark — vd header truyền cỡ nhỉnh hơn + tracking chặt
27
+ * hơn để "DESIGN SYSTEM" rộng đúng bằng chữ "COMITOR". */
28
+ sloganClassName?: string;
29
+ style?: CSSProperties;
30
+ }
31
+ declare function ComitorLockup({ className, variant, size, wordmark, slogan, sloganClassName, style }: ComitorLockupProps): React.JSX.Element;
32
+
33
+ export { ComitorLockup, type ComitorLockupProps, type ComitorLockupSize, type ComitorLockupVariant };
@@ -0,0 +1,112 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../lib/cn.js";
3
+ import { ComitorLogo } from "./comitor-logo.js";
4
+ const SIZE_MAP = {
5
+ xs: {
6
+ icon: "w-9 h-9",
7
+ // 36px — vừa header cao 56px
8
+ gap: "gap-2.5",
9
+ wordmark: "text-sm",
10
+ // 14px
11
+ slogan: "text-[6px] tracking-[0.12em]",
12
+ // 6px
13
+ ruleMargin: "my-0.5"
14
+ },
15
+ sm: {
16
+ icon: "w-12 h-12",
17
+ // 48px
18
+ gap: "gap-3",
19
+ wordmark: "text-base",
20
+ // 16px
21
+ slogan: "text-[7px] tracking-[0.12em]",
22
+ // ~7px
23
+ ruleMargin: "my-1"
24
+ },
25
+ md: {
26
+ icon: "w-20 h-20",
27
+ // 80px
28
+ gap: "gap-4",
29
+ wordmark: "text-2xl",
30
+ // 24px
31
+ slogan: "text-[10px] tracking-[0.12em]",
32
+ // 10px
33
+ ruleMargin: "my-1.5"
34
+ },
35
+ lg: {
36
+ icon: "w-32 h-32",
37
+ // 128px
38
+ gap: "gap-5",
39
+ wordmark: "text-4xl",
40
+ // 36px
41
+ slogan: "text-[14px] tracking-[0.14em]",
42
+ // 14px
43
+ ruleMargin: "my-2"
44
+ }
45
+ };
46
+ function ComitorLockup({
47
+ className,
48
+ variant = "light",
49
+ size = "md",
50
+ wordmark = "COMITOR",
51
+ slogan = "Together Further",
52
+ sloganClassName,
53
+ style
54
+ }) {
55
+ const s = SIZE_MAP[size];
56
+ const dark = variant === "dark";
57
+ const auto = variant === "auto";
58
+ return /* @__PURE__ */ jsxs(
59
+ "div",
60
+ {
61
+ className: cn("flex items-center", s.gap, className),
62
+ style,
63
+ role: "img",
64
+ "aria-label": `${wordmark} \u2014 ${slogan}`,
65
+ children: [
66
+ auto ? /* @__PURE__ */ jsxs(Fragment, { children: [
67
+ /* @__PURE__ */ jsx(ComitorLogo, { variant: "light", className: cn("shrink-0", s.icon, "dark:hidden") }),
68
+ /* @__PURE__ */ jsx(ComitorLogo, { variant: "dark", className: cn("shrink-0", s.icon, "hidden dark:block") })
69
+ ] }) : /* @__PURE__ */ jsx(ComitorLogo, { variant: dark ? "dark" : "light", className: cn("shrink-0", s.icon) }),
70
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
71
+ /* @__PURE__ */ jsx(
72
+ "span",
73
+ {
74
+ className: cn(
75
+ "font-extrabold tracking-tight leading-none",
76
+ s.wordmark,
77
+ auto ? "text-ink dark:text-white" : dark ? "text-white" : "text-ink"
78
+ ),
79
+ children: wordmark
80
+ }
81
+ ),
82
+ /* @__PURE__ */ jsx(
83
+ "span",
84
+ {
85
+ className: cn(
86
+ "h-px opacity-55",
87
+ s.ruleMargin,
88
+ auto ? "bg-gold-400 dark:bg-gold-200" : dark ? "bg-gold-200" : "bg-gold-400"
89
+ ),
90
+ "aria-hidden": "true"
91
+ }
92
+ ),
93
+ /* @__PURE__ */ jsx(
94
+ "span",
95
+ {
96
+ className: cn(
97
+ "font-semibold uppercase",
98
+ sloganClassName ?? s.slogan,
99
+ auto ? "text-gold-500 dark:text-gold-200" : dark ? "text-gold-200" : "text-gold-500"
100
+ ),
101
+ children: slogan
102
+ }
103
+ )
104
+ ] })
105
+ ]
106
+ }
107
+ );
108
+ }
109
+ export {
110
+ ComitorLockup
111
+ };
112
+ //# sourceMappingURL=comitor-lockup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/shell/comitor-lockup.tsx"],"sourcesContent":["import type { CSSProperties } from \"react\";\nimport { cn } from \"../lib/cn.js\";\nimport { ComitorLogo } from \"./comitor-logo.js\";\n\n/**\n * Lockup ngang chính thức của Comitor — icon bên trái, wordmark + gạch vàng + slogan bên phải.\n * Giữ đúng tỉ lệ hình học của brand book để mọi bề mặt sản phẩm dùng chung một bố cục.\n *\n * <ComitorLockup variant=\"auto\" size=\"xs\" slogan=\"Tasks\" />\n *\n * • `variant` — light (nền trắng/kem), dark (nền mực/ấm), auto (tự đổi theo theme).\n * • `size` — xs | sm | md | lg: đổi cỡ icon và cỡ chữ cùng lúc.\n * • `wordmark` / `slogan` — đổi chữ khi cần cặp ngôn ngữ khác\n * (vd \"COMITOR TASKS\", \"THIỆN MINH / CÙNG NHAU TIẾN XA\").\n *\n * Không có \"use client\": thuần trình bày, RSC render được.\n */\nexport type ComitorLockupVariant = \"light\" | \"dark\" | \"auto\";\nexport type ComitorLockupSize = \"xs\" | \"sm\" | \"md\" | \"lg\";\n\nexport interface ComitorLockupProps {\n className?: string;\n variant?: ComitorLockupVariant;\n size?: ComitorLockupSize;\n wordmark?: string;\n slogan?: string;\n /** Class Tailwind GHI ĐÈ style slogan của preset cỡ chữ. Dùng khi cần căn bề rộng\n * slogan tuỳ biến cho khớp wordmark — vd header truyền cỡ nhỉnh hơn + tracking chặt\n * hơn để \"DESIGN SYSTEM\" rộng đúng bằng chữ \"COMITOR\". */\n sloganClassName?: string;\n style?: CSSProperties;\n}\n\n// Các cỡ căn theo tỉ lệ SVG trong brand book:\n// • chiều cao icon ≈ 3.3–4 lần chiều cao chữ hoa của wordmark (icon là phần chủ đạo)\n// • cỡ chữ + giãn chữ của slogan chọn sao cho slogan luôn HẸP HƠN HOẶC BẰNG wordmark\n// (không bao giờ tràn quá vạch vàng).\n// Giãn chữ ở đây nhỏ hơn mức 0.25–0.36em của brand book — mức đó chỉ vừa ở cỡ hero.\nconst SIZE_MAP: Record<\n ComitorLockupSize,\n { icon: string; gap: string; wordmark: string; slogan: string; ruleMargin: string }\n> = {\n xs: {\n icon: \"w-9 h-9\", // 36px — vừa header cao 56px\n gap: \"gap-2.5\",\n wordmark: \"text-sm\", // 14px\n slogan: \"text-[6px] tracking-[0.12em]\", // 6px\n ruleMargin: \"my-0.5\"\n },\n sm: {\n icon: \"w-12 h-12\", // 48px\n gap: \"gap-3\",\n wordmark: \"text-base\", // 16px\n slogan: \"text-[7px] tracking-[0.12em]\", // ~7px\n ruleMargin: \"my-1\"\n },\n md: {\n icon: \"w-20 h-20\", // 80px\n gap: \"gap-4\",\n wordmark: \"text-2xl\", // 24px\n slogan: \"text-[10px] tracking-[0.12em]\", // 10px\n ruleMargin: \"my-1.5\"\n },\n lg: {\n icon: \"w-32 h-32\", // 128px\n gap: \"gap-5\",\n wordmark: \"text-4xl\", // 36px\n slogan: \"text-[14px] tracking-[0.14em]\", // 14px\n ruleMargin: \"my-2\"\n }\n};\n\nexport function ComitorLockup({\n className,\n variant = \"light\",\n size = \"md\",\n wordmark = \"COMITOR\",\n slogan = \"Together Further\",\n sloganClassName,\n style\n}: ComitorLockupProps) {\n const s = SIZE_MAP[size];\n const dark = variant === \"dark\";\n const auto = variant === \"auto\";\n\n return (\n <div\n className={cn(\"flex items-center\", s.gap, className)}\n style={style}\n role=\"img\"\n aria-label={`${wordmark} — ${slogan}`}\n >\n {auto ? (\n <>\n <ComitorLogo variant=\"light\" className={cn(\"shrink-0\", s.icon, \"dark:hidden\")} />\n <ComitorLogo variant=\"dark\" className={cn(\"shrink-0\", s.icon, \"hidden dark:block\")} />\n </>\n ) : (\n <ComitorLogo variant={dark ? \"dark\" : \"light\"} className={cn(\"shrink-0\", s.icon)} />\n )}\n <div className=\"flex flex-col\">\n <span\n className={cn(\n \"font-extrabold tracking-tight leading-none\",\n s.wordmark,\n auto ? \"text-ink dark:text-white\" : dark ? \"text-white\" : \"text-ink\"\n )}\n >\n {wordmark}\n </span>\n <span\n className={cn(\n \"h-px opacity-55\",\n s.ruleMargin,\n auto ? \"bg-gold-400 dark:bg-gold-200\" : dark ? \"bg-gold-200\" : \"bg-gold-400\"\n )}\n aria-hidden=\"true\"\n />\n <span\n className={cn(\n \"font-semibold uppercase\",\n sloganClassName ?? s.slogan,\n auto ? \"text-gold-500 dark:text-gold-200\" : dark ? \"text-gold-200\" : \"text-gold-500\"\n )}\n >\n {slogan}\n </span>\n </div>\n </div>\n );\n}\n"],"mappings":"AA6FQ,mBACE,KADF;AA5FR,SAAS,UAAU;AACnB,SAAS,mBAAmB;AAoC5B,MAAM,WAGF;AAAA,EACF,IAAI;AAAA,IACF,MAAM;AAAA;AAAA,IACN,KAAK;AAAA,IACL,UAAU;AAAA;AAAA,IACV,QAAQ;AAAA;AAAA,IACR,YAAY;AAAA,EACd;AAAA,EACA,IAAI;AAAA,IACF,MAAM;AAAA;AAAA,IACN,KAAK;AAAA,IACL,UAAU;AAAA;AAAA,IACV,QAAQ;AAAA;AAAA,IACR,YAAY;AAAA,EACd;AAAA,EACA,IAAI;AAAA,IACF,MAAM;AAAA;AAAA,IACN,KAAK;AAAA,IACL,UAAU;AAAA;AAAA,IACV,QAAQ;AAAA;AAAA,IACR,YAAY;AAAA,EACd;AAAA,EACA,IAAI;AAAA,IACF,MAAM;AAAA;AAAA,IACN,KAAK;AAAA,IACL,UAAU;AAAA;AAAA,IACV,QAAQ;AAAA;AAAA,IACR,YAAY;AAAA,EACd;AACF;AAEO,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA,UAAU;AAAA,EACV,OAAO;AAAA,EACP,WAAW;AAAA,EACX,SAAS;AAAA,EACT;AAAA,EACA;AACF,GAAuB;AACrB,QAAM,IAAI,SAAS,IAAI;AACvB,QAAM,OAAO,YAAY;AACzB,QAAM,OAAO,YAAY;AAEzB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,GAAG,qBAAqB,EAAE,KAAK,SAAS;AAAA,MACnD;AAAA,MACA,MAAK;AAAA,MACL,cAAY,GAAG,QAAQ,WAAM,MAAM;AAAA,MAElC;AAAA,eACC,iCACE;AAAA,8BAAC,eAAY,SAAQ,SAAQ,WAAW,GAAG,YAAY,EAAE,MAAM,aAAa,GAAG;AAAA,UAC/E,oBAAC,eAAY,SAAQ,QAAO,WAAW,GAAG,YAAY,EAAE,MAAM,mBAAmB,GAAG;AAAA,WACtF,IAEA,oBAAC,eAAY,SAAS,OAAO,SAAS,SAAS,WAAW,GAAG,YAAY,EAAE,IAAI,GAAG;AAAA,QAEpF,qBAAC,SAAI,WAAU,iBACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,WAAW;AAAA,gBACT;AAAA,gBACA,EAAE;AAAA,gBACF,OAAO,6BAA6B,OAAO,eAAe;AAAA,cAC5D;AAAA,cAEC;AAAA;AAAA,UACH;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,WAAW;AAAA,gBACT;AAAA,gBACA,EAAE;AAAA,gBACF,OAAO,iCAAiC,OAAO,gBAAgB;AAAA,cACjE;AAAA,cACA,eAAY;AAAA;AAAA,UACd;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,WAAW;AAAA,gBACT;AAAA,gBACA,mBAAmB,EAAE;AAAA,gBACrB,OAAO,qCAAqC,OAAO,kBAAkB;AAAA,cACvE;AAAA,cAEC;AAAA;AAAA,UACH;AAAA,WACF;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -0,0 +1,28 @@
1
+ import * as React from 'react';
2
+ import { CSSProperties } from 'react';
3
+
4
+ /**
5
+ * Dấu hiệu nhận diện chính thức của Comitor (khối lập phương vàng + 3 hình người đồng hành).
6
+ *
7
+ * Hai biến thể:
8
+ * • `variant="light"` — mặt trên vàng nhạt; dùng trên nền sáng (mặc định)
9
+ * • `variant="dark"` — mặt trên trắng; dùng trên nền tối / nền ấm
10
+ *
11
+ * ⚠ **Ngoại lệ duy nhất của quy tắc "không hardcode màu"**: mã hex dưới đây là vector
12
+ * lấy nguyên từ file nguồn Figma của brand book. KHÔNG tokenize, KHÔNG đổi màu,
13
+ * KHÔNG đơn giản hoá path — logo phải giống hệt nhau ở mọi sản phẩm.
14
+ *
15
+ * File này KHÔNG có "use client": logo là SVG tĩnh, để RSC render thẳng, không tốn JS phía client.
16
+ */
17
+ type ComitorLogoVariant = "light" | "dark";
18
+ interface ComitorLogoProps {
19
+ className?: string;
20
+ /** Nền sáng dùng "light", nền tối/ấm dùng "dark". */
21
+ variant?: ComitorLogoVariant;
22
+ /** Nội dung <title> của SVG — cũng là aria-label. */
23
+ title?: string;
24
+ style?: CSSProperties;
25
+ }
26
+ declare function ComitorLogo({ className, variant, title, style }: ComitorLogoProps): React.JSX.Element;
27
+
28
+ export { ComitorLogo, type ComitorLogoProps, type ComitorLogoVariant };