@comitor/ui 3.10.0 → 3.11.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.
- package/dist/shell/app-launcher.js +3 -3
- package/dist/shell/app-launcher.js.map +1 -1
- package/dist/shell/display-axis-provider.d.ts +1 -1
- package/dist/tokens.d.ts +8 -0
- package/dist/tokens.js +10 -2
- package/dist/tokens.js.map +1 -1
- package/package.json +1 -1
- package/styles.css +18 -2
|
@@ -170,8 +170,8 @@ function AppLauncherPanel({ variant = "grid", className, onSelected }) {
|
|
|
170
170
|
);
|
|
171
171
|
};
|
|
172
172
|
if (total === 0) return null;
|
|
173
|
-
const groupClass = variant === "grid" ? "grid auto-rows-fr grid-cols-3 gap-
|
|
174
|
-
return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: cn("flex flex-col gap-3", className), children: [
|
|
173
|
+
const groupClass = variant === "grid" ? "grid auto-rows-fr grid-cols-3 gap-2" : "flex flex-col gap-0.5";
|
|
174
|
+
return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: cn("flex flex-col", variant === "grid" ? "gap-4" : "gap-3", className), children: [
|
|
175
175
|
entitled.length > 0 ? /* @__PURE__ */ jsxs("section", { children: [
|
|
176
176
|
/* @__PURE__ */ jsx("p", { className: "px-1 pb-1.5 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: labels.myApps }),
|
|
177
177
|
/* @__PURE__ */ jsx("div", { className: groupClass, children: entitled.map(renderTile) })
|
|
@@ -212,7 +212,7 @@ function AppLauncher({
|
|
|
212
212
|
) }) }),
|
|
213
213
|
/* @__PURE__ */ jsx(TooltipContent, { side: "bottom", children: labels.apps })
|
|
214
214
|
] }),
|
|
215
|
-
/* @__PURE__ */ jsx(PopoverContent, { align, sideOffset: 8, className: "w-[
|
|
215
|
+
/* @__PURE__ */ jsx(PopoverContent, { align, sideOffset: 8, className: "w-[21rem] px-4 py-3", children: /* @__PURE__ */ jsx(
|
|
216
216
|
AppLauncherPanel,
|
|
217
217
|
{
|
|
218
218
|
variant,
|
|
@@ -1 +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 { IconAvatar } from \"../components/composite/icon-avatar.js\";\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 { 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; \"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 // Ô KHOÁ không tô `opacity-60` nữa. Nó KHÔNG phải control vô hiệu — bấm được và gọi\n // `onUpsell(appId)` — nên không hưởng miễn trừ \"thành phần vô hiệu\" của WCAG 1.4.3/1.4.11.\n // Với 60% độ mờ thì mô tả app tụt còn 2,41:1 (light) / 2,74:1 (dark) ✗ và icon trong ô\n // `bg-muted` còn 2,31:1 / 2,60:1 ✗, chỉ phục hồi khi hover — tức người không rê chuột thì\n // không đọc được. Bỏ độ mờ: mô tả `text-muted-foreground` 5,23:1 (light) / 5,35:1 (dark) ✓.\n // Tín hiệu \"chưa mở khoá\" không mất gì: huy hiệu ổ khoá, `title`/`aria-label` nói rõ (ô icon\n // giữ màu app — xem chú thích ở `IconAvatar` bên dưới).\n //\n // Ô LƯỚI là hình VUÔNG, icon + tên căn GIỮA. Bản trước là `items-start` + `text-left`: ô kéo\n // theo bề rộng cột (~109px ở bản 22rem) nhưng chỉ cao bằng nội dung, nên ra hình chữ nhật nằm ngang với\n // khoảng trống thừa bên phải icon và tên — mắt đọc ra một ô \"chưa điền hết\". `aspect-square`\n // là SÀN chứ không phải trần: `min-height: auto` mặc định để ô cao thêm khi app truyền\n // `description` dài, và `auto-rows-fr` của lưới giữ cả hàng cao đều nhau.\n // Cạnh ô do bề rộng popover quyết (`w-[19rem]` ở `AppLauncher`, ba cột): 22rem cho ô 109px mà\n // nội dung (icon 36px + một dòng tên) chỉ cao ~62px — gần nửa ô là khoảng trống. 19rem cho ô\n // 92,7px; `p-1.5` để lại 80,7px cho tên — vừa đủ \"Khách hàng\" (79,7px ở `text-sm`) trên một\n // dòng. `p-2` là nó xuống hai dòng và ô hàng đó cao hơn ô vuông. Tên dài hơn vẫn xuống dòng.\n const tileClass = cn(\n \"group relative flex rounded-md transition-colors duration-150 ease-out\",\n variant === \"grid\"\n ? \"aspect-square flex-col items-center justify-center gap-1.5 p-1.5 text-center\"\n : \"items-center gap-2.5 p-2 text-left\",\n isLocked ? \"hover:bg-muted\" : \"hover:bg-accent\",\n isCurrent && \"bg-accent\"\n );\n\n const body = (\n <>\n {/*\n * ── Ô icon: `IconAvatar`, màu TỰ SUY theo `id` ────────────────────────────────────\n * Từ 1.10.0 ô này thôi tô bằng `--app-accent`. Lý do không phải thẩm mỹ: bảng accent cũ\n * bắt mỗi app phải được ai đó gán màu tay, nên sản phẩm thứ sáu hoặc trùng màu với sản\n * phẩm khác, hoặc phải chờ một lần sửa mã. `IconAvatar` suy màu từ `id` bằng bảng tone\n * dùng chung với `LetterAvatar`, nên thêm app là nó tự có màu — và cùng màu ấy ở email,\n * canvas, PDF vì `getAvatarToneColors` đọc cùng bảng.\n *\n * ⚠ `seed={app.id}`, KHÔNG phải `app.name`: tên đổi theo ngôn ngữ giao diện. Đo được\n * 4/6 app đổi màu khi chuyển vi ⇄ en nếu băm theo tên.\n *\n * ⚠ Nhánh KHOÁ giữ ĐÚNG màu của app (từ 3.10.0), tín hiệu duy nhất là huy hiệu ổ khoá.\n * Bản trước tô xám cả ô: ở mục \"Khám phá thêm\" mọi app chưa mở trông y hệt nhau, tức người\n * dùng mất đúng thứ giúp nhận ra sản phẩm ngay ở chỗ cần mời họ dùng thử. \"Chưa mở khoá\"\n * vẫn được nói đủ: ổ khoá, nhãn nhóm, `title`/`aria-label` của ô.\n */}\n <span className=\"relative flex shrink-0\">\n <IconAvatar\n name={app.name}\n seed={app.id}\n icon={Icon}\n tone={app.tone}\n size={variant === \"grid\" ? \"md\" : \"sm\"}\n />\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 {/*\n * `w-full` ở ô lưới là thứ THẬT SỰ bó được chữ — không phải `min-w-0`.\n *\n * Ô lưới là `flex-col` (xem `tileClass`), nên theo trục ngang cột này lấy\n * `fit-content`: nó RỘNG RA THEO TÊN thay vì bằng ô. Mọi phép cắt bên trong vì thế không\n * bao giờ có gì để bám vào. Đo ở app thật, ô 109px: tên một từ dài cho cột 272px và tràn\n * 174px, `scrollWidth === clientWidth` — `line-clamp` được đặt nhưng chưa từng chạy.\n * Thêm `min-w-0` vào hàng bên dưới KHÔNG đổi được số nào (đo: y hệt bản hỏng); `w-full`\n * đưa cột về đúng 89px và từ đó phép cắt mới có hiệu lực.\n *\n * Biến thể `list` không dính: ô đó là hàng ngang nên `flex-1` đã ghim bề rộng sẵn.\n */}\n <span className={cn(\"flex min-w-0 flex-col\", variant === \"grid\" ? \"w-full\" : \"flex-1\")}>\n {/*\n * `items-start` chứ không `items-center` khi tên có thể xuống hai dòng: căn giữa đẩy\n * badge xuống giữa khe hai dòng (đo được 10,6px), xa hẳn dòng chữ nó đang chú thích.\n * Tên một dòng thì hai cách lệch nhau 0,8px — không nhìn thấy.\n */}\n <span\n className={cn(\"flex min-w-0 gap-1.5\", variant === \"grid\" ? \"items-start justify-center\" : \"items-center\")}\n >\n <span\n className={cn(\n \"text-sm font-medium text-foreground\",\n /*\n * Lưới: tên ứng dụng dài hơn ô là chuyện thường (\"Tài khoản Comitor\" ở ô 109px),\n * nên cho hai dòng rồi mới cắt — `grid-cols-3` khai `items-stretch` mặc định nên\n * ô cao thêm một dòng không làm hàng bị so le.\n *\n * `break-words` đi KÈM `line-clamp-2`, không phải phòng xa: `-webkit-line-clamp`\n * chỉ đặt được dấu \"…\" khi chữ THẬT SỰ tràn sang dòng thứ ba. Một tên không có\n * khoảng trắng thì không xuống dòng được, nên nó nằm gọn một dòng và bị\n * `overflow: hidden` cắt PHẲNG giữa chữ — đo được: `soDong: 1`,\n * `scrollHeight === clientHeight`, không có dấu ba chấm nào. Cho phép ngắt trong\n * từ thì nó thành hai dòng và dấu \"…\" quay lại.\n *\n * Danh sách: mỗi mục là một hàng ngang trong popover hẹp; hai dòng ở đó làm chiều\n * cao các mục nhấp nhô. Giữ một dòng — `truncate` vốn đã chạy đúng ở đó vì `flex-1`\n * ghim sẵn bề rộng. Cùng lý do tác giả chỉ cho `description` xuống hai dòng ở lưới.\n */\n variant === \"grid\" ? \"line-clamp-2 break-words\" : \"truncate\"\n )}\n >\n {app.name}\n </span>\n {app.badge ? (\n /*\n * `shrink-0` KHÔNG phải để chống bóp méo — đo rồi: có hay không, badge đều 116,8px,\n * vì `whitespace-nowrap` trong `Badge` đã giữ sàn `min-width: auto` bằng trọn bề rộng\n * chữ, và phần co dồn hết sang ô tên (`overflow: hidden` → sàn 0).\n *\n * Nó ở đây vì hai lý do khác: khớp quy ước của mọi chỗ gọi `Badge` khác trong shell\n * (`workspace-switcher`, `sidebar-menu-item` — chỗ này là chỗ DUY NHẤT thiếu), và để\n * lá chắn thôi phụ thuộc vào một lớp ngầm trong `Badge`. Ai đó thêm `truncate` vào\n * base cva của `Badge` là sàn tụt về 0 và badge méo, im lặng.\n */\n <Badge variant=\"destructive\" size=\"sm\" className=\"shrink-0\">\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 /*\n * `auto-rows-fr` vá thứ duy nhất mà việc cho tên xuống hai dòng làm xấu đi: TRONG một hàng thì\n * các ô đã bằng nhau sẵn (`align-items: stretch` là mặc định của Grid), nhưng GIỮA các hàng thì\n * không — hàng có một tên hai dòng cao 116px trong khi hàng kia 97px. `fr` cho mọi hàng chung\n * một chiều cao.\n */\n const groupClass = variant === \"grid\" ? \"grid auto-rows-fr 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 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-[19rem] 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":[]}
|
|
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 { IconAvatar } from \"../components/composite/icon-avatar.js\";\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 { 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; \"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 // Ô KHOÁ không tô `opacity-60` nữa. Nó KHÔNG phải control vô hiệu — bấm được và gọi\n // `onUpsell(appId)` — nên không hưởng miễn trừ \"thành phần vô hiệu\" của WCAG 1.4.3/1.4.11.\n // Với 60% độ mờ thì mô tả app tụt còn 2,41:1 (light) / 2,74:1 (dark) ✗ và icon trong ô\n // `bg-muted` còn 2,31:1 / 2,60:1 ✗, chỉ phục hồi khi hover — tức người không rê chuột thì\n // không đọc được. Bỏ độ mờ: mô tả `text-muted-foreground` 5,23:1 (light) / 5,35:1 (dark) ✓.\n // Tín hiệu \"chưa mở khoá\" không mất gì: huy hiệu ổ khoá, `title`/`aria-label` nói rõ (ô icon\n // giữ màu app — xem chú thích ở `IconAvatar` bên dưới).\n //\n // Ô LƯỚI là hình VUÔNG, icon + tên căn GIỮA. Bản trước là `items-start` + `text-left`: ô kéo\n // theo bề rộng cột (~109px ở bản 22rem) nhưng chỉ cao bằng nội dung, nên ra hình chữ nhật nằm ngang với\n // khoảng trống thừa bên phải icon và tên — mắt đọc ra một ô \"chưa điền hết\". `aspect-square`\n // là SÀN chứ không phải trần: `min-height: auto` mặc định để ô cao thêm khi app truyền\n // `description` dài, và `auto-rows-fr` của lưới giữ cả hàng cao đều nhau.\n // Cạnh ô do bề rộng popover quyết (`w-[21rem] px-4` ở `AppLauncher`, ba cột, `gap-2`): 336 − 32\n // padding − 16 khe = 288 → ô 96px; `p-1.5` để lại 84px cho tên — \"Khách hàng\" (79,7px ở `text-sm`)\n // nằm một dòng. Bản 22rem `p-2` `gap-1` cho ô 109px mà nội dung (icon 36px + một dòng tên) chỉ cao\n // ~62px — gần nửa ô trống; bản 19rem kế đó vừa ô nhưng menu hẹp, khe 4px làm các ô dính nhau và\n // mép menu sát ô. Ba số (rộng · padding · khe) đi CÙNG nhau: nới một số là ô co lại, tên xuống dòng.\n const tileClass = cn(\n \"group relative flex rounded-md transition-colors duration-150 ease-out\",\n variant === \"grid\"\n ? \"aspect-square flex-col items-center justify-center gap-1.5 p-1.5 text-center\"\n : \"items-center gap-2.5 p-2 text-left\",\n isLocked ? \"hover:bg-muted\" : \"hover:bg-accent\",\n isCurrent && \"bg-accent\"\n );\n\n const body = (\n <>\n {/*\n * ── Ô icon: `IconAvatar`, màu TỰ SUY theo `id` ────────────────────────────────────\n * Từ 1.10.0 ô này thôi tô bằng `--app-accent`. Lý do không phải thẩm mỹ: bảng accent cũ\n * bắt mỗi app phải được ai đó gán màu tay, nên sản phẩm thứ sáu hoặc trùng màu với sản\n * phẩm khác, hoặc phải chờ một lần sửa mã. `IconAvatar` suy màu từ `id` bằng bảng tone\n * dùng chung với `LetterAvatar`, nên thêm app là nó tự có màu — và cùng màu ấy ở email,\n * canvas, PDF vì `getAvatarToneColors` đọc cùng bảng.\n *\n * ⚠ `seed={app.id}`, KHÔNG phải `app.name`: tên đổi theo ngôn ngữ giao diện. Đo được\n * 4/6 app đổi màu khi chuyển vi ⇄ en nếu băm theo tên.\n *\n * ⚠ Nhánh KHOÁ giữ ĐÚNG màu của app (từ 3.10.0), tín hiệu duy nhất là huy hiệu ổ khoá.\n * Bản trước tô xám cả ô: ở mục \"Khám phá thêm\" mọi app chưa mở trông y hệt nhau, tức người\n * dùng mất đúng thứ giúp nhận ra sản phẩm ngay ở chỗ cần mời họ dùng thử. \"Chưa mở khoá\"\n * vẫn được nói đủ: ổ khoá, nhãn nhóm, `title`/`aria-label` của ô.\n */}\n <span className=\"relative flex shrink-0\">\n <IconAvatar\n name={app.name}\n seed={app.id}\n icon={Icon}\n tone={app.tone}\n size={variant === \"grid\" ? \"md\" : \"sm\"}\n />\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 {/*\n * `w-full` ở ô lưới là thứ THẬT SỰ bó được chữ — không phải `min-w-0`.\n *\n * Ô lưới là `flex-col` (xem `tileClass`), nên theo trục ngang cột này lấy\n * `fit-content`: nó RỘNG RA THEO TÊN thay vì bằng ô. Mọi phép cắt bên trong vì thế không\n * bao giờ có gì để bám vào. Đo ở app thật, ô 109px: tên một từ dài cho cột 272px và tràn\n * 174px, `scrollWidth === clientWidth` — `line-clamp` được đặt nhưng chưa từng chạy.\n * Thêm `min-w-0` vào hàng bên dưới KHÔNG đổi được số nào (đo: y hệt bản hỏng); `w-full`\n * đưa cột về đúng 89px và từ đó phép cắt mới có hiệu lực.\n *\n * Biến thể `list` không dính: ô đó là hàng ngang nên `flex-1` đã ghim bề rộng sẵn.\n */}\n <span className={cn(\"flex min-w-0 flex-col\", variant === \"grid\" ? \"w-full\" : \"flex-1\")}>\n {/*\n * `items-start` chứ không `items-center` khi tên có thể xuống hai dòng: căn giữa đẩy\n * badge xuống giữa khe hai dòng (đo được 10,6px), xa hẳn dòng chữ nó đang chú thích.\n * Tên một dòng thì hai cách lệch nhau 0,8px — không nhìn thấy.\n */}\n <span\n className={cn(\"flex min-w-0 gap-1.5\", variant === \"grid\" ? \"items-start justify-center\" : \"items-center\")}\n >\n <span\n className={cn(\n \"text-sm font-medium text-foreground\",\n /*\n * Lưới: tên ứng dụng dài hơn ô là chuyện thường (\"Tài khoản Comitor\" ở ô 109px),\n * nên cho hai dòng rồi mới cắt — `grid-cols-3` khai `items-stretch` mặc định nên\n * ô cao thêm một dòng không làm hàng bị so le.\n *\n * `break-words` đi KÈM `line-clamp-2`, không phải phòng xa: `-webkit-line-clamp`\n * chỉ đặt được dấu \"…\" khi chữ THẬT SỰ tràn sang dòng thứ ba. Một tên không có\n * khoảng trắng thì không xuống dòng được, nên nó nằm gọn một dòng và bị\n * `overflow: hidden` cắt PHẲNG giữa chữ — đo được: `soDong: 1`,\n * `scrollHeight === clientHeight`, không có dấu ba chấm nào. Cho phép ngắt trong\n * từ thì nó thành hai dòng và dấu \"…\" quay lại.\n *\n * Danh sách: mỗi mục là một hàng ngang trong popover hẹp; hai dòng ở đó làm chiều\n * cao các mục nhấp nhô. Giữ một dòng — `truncate` vốn đã chạy đúng ở đó vì `flex-1`\n * ghim sẵn bề rộng. Cùng lý do tác giả chỉ cho `description` xuống hai dòng ở lưới.\n */\n variant === \"grid\" ? \"line-clamp-2 break-words\" : \"truncate\"\n )}\n >\n {app.name}\n </span>\n {app.badge ? (\n /*\n * `shrink-0` KHÔNG phải để chống bóp méo — đo rồi: có hay không, badge đều 116,8px,\n * vì `whitespace-nowrap` trong `Badge` đã giữ sàn `min-width: auto` bằng trọn bề rộng\n * chữ, và phần co dồn hết sang ô tên (`overflow: hidden` → sàn 0).\n *\n * Nó ở đây vì hai lý do khác: khớp quy ước của mọi chỗ gọi `Badge` khác trong shell\n * (`workspace-switcher`, `sidebar-menu-item` — chỗ này là chỗ DUY NHẤT thiếu), và để\n * lá chắn thôi phụ thuộc vào một lớp ngầm trong `Badge`. Ai đó thêm `truncate` vào\n * base cva của `Badge` là sàn tụt về 0 và badge méo, im lặng.\n */\n <Badge variant=\"destructive\" size=\"sm\" className=\"shrink-0\">\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 /*\n * `auto-rows-fr` vá thứ duy nhất mà việc cho tên xuống hai dòng làm xấu đi: TRONG một hàng thì\n * các ô đã bằng nhau sẵn (`align-items: stretch` là mặc định của Grid), nhưng GIỮA các hàng thì\n * không — hàng có một tên hai dòng cao 116px trong khi hàng kia 97px. `fr` cho mọi hàng chung\n * một chiều cao.\n */\n const groupClass = variant === \"grid\" ? \"grid auto-rows-fr grid-cols-3 gap-2\" : \"flex flex-col gap-0.5\";\n\n return (\n <div ref={rootRef} className={cn(\"flex flex-col\", variant === \"grid\" ? \"gap-4\" : \"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 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-[21rem] px-4 py-3\">\n <AppLauncherPanel\n variant={variant}\n onSelected={() => {\n setAppLauncherOpen(false);\n onSelected?.();\n }}\n />\n </PopoverContent>\n </Popover>\n );\n}\n"],"mappings":";AAkGM,mBAkBI,KADF,YAjBF;AAhGN,SAAS,YAAY,YAAY;AACjC,SAA6B,cAAc;AAC3C,SAAS,kBAAkB;AAC3B,SAAS,aAAa;AACtB,SAAS,SAAS,gBAAgB,sBAAsB;AACxD,SAAS,SAAS,gBAAgB,sBAAsB;AACxD,SAAS,UAAU;AACnB,SAAS,gBAAgB;AACzB,SAAS,oBAAoB;AAE7B,SAAS,iBAAiB,qBAAqB;AAG/C,MAAM,YAAY;AAkBX,SAAS,iBAAiB,EAAE,UAAU,QAAQ,WAAW,WAAW,GAA0B;AACnG,QAAM,EAAE,MAAM,YAAY,aAAa,UAAU,OAAO,IAAI,SAAS;AAErE,QAAM,WAAW,gBAAgB,IAAI;AACrC,QAAM,SAAS,cAAc,IAAI;AACjC,QAAM,QAAQ,SAAS,SAAS,OAAO;AAEvC,QAAM,UAAU,OAAuB,IAAI;AAC3C,QAAM,UAAU,YAAY,SAAS,IAAI;AAEzC,QAAM,gBAAgB,CAAC,UAAsC;AAC3D,UAAM,OAAO,QAAQ;AACrB,QAAI,CAAC,KAAM;AACX,UAAM,QAAQ,MAAM,KAAK,KAAK,iBAA8B,IAAI,SAAS,GAAG,CAAC;AAC7E,UAAM,QAAQ,MAAM,QAAQ,MAAM,aAAa;AAC/C,QAAI,QAAQ,EAAG;AAEf,UAAM,QAA4C;AAAA,MAChD,YAAY,QAAQ;AAAA,MACpB,WAAW,QAAQ;AAAA,MACnB,WAAW,QAAQ;AAAA,MACnB,SAAS,QAAQ;AAAA,MACjB,MAAM;AAAA,MACN,KAAK,MAAM,SAAS;AAAA,IACtB;AACA,UAAM,OAAO,MAAM,MAAM,GAAG;AAC5B,QAAI,SAAS,OAAW;AAExB,UAAM,eAAe;AACrB,UAAM,KAAK,IAAI,GAAG,KAAK,IAAI,MAAM,SAAS,GAAG,IAAI,CAAC,CAAC,GAAG,MAAM;AAAA,EAC9D;AAEA,QAAM,aAAa,CAAC,QAAuB;AACzC,UAAM,WAAW,IAAI,aAAa;AAClC,UAAM,YAAY,IAAI,OAAO,YAAY;AACzC,UAAM,OAAO,IAAI;AAoBjB,UAAM,YAAY;AAAA,MAChB;AAAA,MACA,YAAY,SACR,iFACA;AAAA,MACJ,WAAW,mBAAmB;AAAA,MAC9B,aAAa;AAAA,IACf;AAEA,UAAM,OACJ,iCAiBE;AAAA,2BAAC,UAAK,WAAU,0BACd;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,MAAM,IAAI;AAAA,YACV,MAAM,IAAI;AAAA,YACV,MAAM;AAAA,YACN,MAAM,IAAI;AAAA,YACV,MAAM,YAAY,SAAS,OAAO;AAAA;AAAA,QACpC;AAAA,QACC,WACC,oBAAC,UAAK,WAAU,uIACd,8BAAC,QAAK,WAAU,YAAW,GAC7B,IACE;AAAA,SACN;AAAA,MAcA,qBAAC,UAAK,WAAW,GAAG,yBAAyB,YAAY,SAAS,WAAW,QAAQ,GAMnF;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,WAAW,GAAG,wBAAwB,YAAY,SAAS,+BAA+B,cAAc;AAAA,YAExG;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,WAAW;AAAA,oBACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAiBA,YAAY,SAAS,6BAA6B;AAAA,kBACpD;AAAA,kBAEC,cAAI;AAAA;AAAA,cACP;AAAA,cACC,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gBAWH,oBAAC,SAAM,SAAQ,eAAc,MAAK,MAAK,WAAU,YAC9C,cAAI,OACP;AAAA,kBACE;AAAA;AAAA;AAAA,QACN;AAAA,QACC,IAAI,cACH;AAAA,UAAC;AAAA;AAAA,YACC,WAAW,GAAG,kDAAkD,YAAY,UAAU,cAAc;AAAA,YAEnG,cAAI;AAAA;AAAA,QACP,IACE;AAAA,SACN;AAAA,OACF;AAIF,QAAI,UAAU;AACZ,aACE;AAAA,QAAC;AAAA;AAAA,UAEC,MAAK;AAAA,UACL,iBAAc;AAAA,UACd,OAAO,OAAO;AAAA,UACd,cAAY,GAAG,IAAI,IAAI,WAAM,OAAO,MAAM;AAAA,UAC1C,WAAW;AAAA,UACX,WAAW;AAAA,UACX,SAAS,MAAM;AACb,uBAAW,IAAI,EAAE;AACjB,yBAAa;AAAA,UACf;AAAA,UAEC;AAAA;AAAA,QAZI,IAAI;AAAA,MAaX;AAAA,IAEJ;AAIA,QAAI,aAAa;AACf,aACE;AAAA,QAAC;AAAA;AAAA,UAEC,MAAK;AAAA,UACL,iBAAc;AAAA,UACd,gBAAc,YAAY,SAAS;AAAA,UACnC,WAAW;AAAA,UACX,WAAW;AAAA,UACX,SAAS,MAAM;AACb,wBAAY,GAAG;AACf,yBAAa;AAAA,UACf;AAAA,UAEC;AAAA;AAAA,QAXI,IAAI;AAAA,MAYX;AAAA,IAEJ;AAEA,WACE;AAAA,MAAC;AAAA;AAAA,QAEC,MAAM,IAAI;AAAA,QACV,iBAAc;AAAA,QACd,gBAAc,YAAY,SAAS;AAAA,QACnC,WAAW;AAAA,QACX,WAAW;AAAA,QACX,SAAS,MAAM,aAAa;AAAA,QAE3B;AAAA;AAAA,MARI,IAAI;AAAA,IASX;AAAA,EAEJ;AAEA,MAAI,UAAU,EAAG,QAAO;AAQxB,QAAM,aAAa,YAAY,SAAS,wCAAwC;AAEhF,SACE,qBAAC,SAAI,KAAK,SAAS,WAAW,GAAG,iBAAiB,YAAY,SAAS,UAAU,SAAS,SAAS,GAChG;AAAA,aAAS,SAAS,IACjB,qBAAC,aACC;AAAA,0BAAC,OAAE,WAAU,uFACV,iBAAO,QACV;AAAA,MACA,oBAAC,SAAI,WAAW,YAAa,mBAAS,IAAI,UAAU,GAAE;AAAA,OACxD,IACE;AAAA,IAEH,OAAO,SAAS,IACf,qBAAC,aACC;AAAA,0BAAC,OAAE,WAAU,uFACV,iBAAO,cACV;AAAA,MACA,oBAAC,SAAI,WAAW,YAAa,iBAAO,IAAI,UAAU,GAAE;AAAA,OACtD,IACE;AAAA,KACN;AAEJ;AAeO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA,UAAU;AAAA,EACV,SAAS;AAAA,EACT,QAAQ;AAAA,EACR;AACF,GAAqB;AACnB,QAAM,EAAE,MAAM,iBAAiB,oBAAoB,OAAO,IAAI,SAAS;AAEvE,MAAI,KAAK,WAAW,EAAG,QAAO;AAE9B,MAAI,QAAQ;AACV,WAAO,oBAAC,oBAAiB,SAAkB,WAAsB,YAAwB;AAAA,EAC3F;AAEA,SACE,qBAAC,WAAQ,MAAM,iBAAiB,cAAc,oBAC5C;AAAA,yBAAC,WACC;AAAA,0BAAC,kBAAe,SAAO,MACrB,8BAAC,kBAAe,SAAO,MACrB;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,cAAY,OAAO;AAAA,UACnB,WAAW;AAAA,YACT;AAAA,YACA;AAAA,YACA,mBAAmB;AAAA,YACnB;AAAA,UACF;AAAA,UAEA,8BAAC,cAAW,WAAU,UAAS;AAAA;AAAA,MACjC,GACF,GACF;AAAA,MACA,oBAAC,kBAAe,MAAK,UAAU,iBAAO,MAAK;AAAA,OAC7C;AAAA,IAEA,oBAAC,kBAAe,OAAc,YAAY,GAAG,WAAU,uBACrD;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,YAAY,MAAM;AAChB,6BAAmB,KAAK;AACxB,uBAAa;AAAA,QACf;AAAA;AAAA,IACF,GACF;AAAA,KACF;AAEJ;","names":[]}
|
|
@@ -37,7 +37,7 @@ declare function useDensity(): {
|
|
|
37
37
|
};
|
|
38
38
|
declare const FontSizeProvider: ({ children, defaultValue, storageKey, nonce }: {
|
|
39
39
|
children: ReactNode;
|
|
40
|
-
defaultValue?: "
|
|
40
|
+
defaultValue?: "sm" | "md" | "lg" | undefined;
|
|
41
41
|
storageKey?: string;
|
|
42
42
|
/**
|
|
43
43
|
* Nonce của CSP, cho script chống nháy mà provider này TỰ render.
|
package/dist/tokens.d.ts
CHANGED
|
@@ -159,6 +159,10 @@ declare const accentForeground: {
|
|
|
159
159
|
readonly green: "#0E1116";
|
|
160
160
|
readonly yellow: "#0E1116";
|
|
161
161
|
readonly orange: "#0E1116";
|
|
162
|
+
/** Ba sắc định danh (3.11.0): blue/purple chữ TRẮNG ở light (4,85 · 5,01:1), pink chữ mực (5,20:1). */
|
|
163
|
+
readonly blue: "#FFFFFF";
|
|
164
|
+
readonly purple: "#FFFFFF";
|
|
165
|
+
readonly pink: "#0E1116";
|
|
162
166
|
};
|
|
163
167
|
readonly dark: {
|
|
164
168
|
readonly navy: "#FFFFFF";
|
|
@@ -166,6 +170,10 @@ declare const accentForeground: {
|
|
|
166
170
|
readonly green: "#0E1116";
|
|
167
171
|
readonly yellow: "#0E1116";
|
|
168
172
|
readonly orange: "#0E1116";
|
|
173
|
+
/** Bản dark của ba sắc đều sáng → mực: blue 7,99 · purple 6,95 · pink 7,14:1. */
|
|
174
|
+
readonly blue: "#0E1116";
|
|
175
|
+
readonly purple: "#0E1116";
|
|
176
|
+
readonly pink: "#0E1116";
|
|
169
177
|
};
|
|
170
178
|
};
|
|
171
179
|
/** Semantic token của shadcn — mirror khối `:root` / `.dark` trong styles.css. */
|
package/dist/tokens.js
CHANGED
|
@@ -90,14 +90,22 @@ const accentForeground = {
|
|
|
90
90
|
teal: "#0E1116",
|
|
91
91
|
green: "#0E1116",
|
|
92
92
|
yellow: "#0E1116",
|
|
93
|
-
orange: "#0E1116"
|
|
93
|
+
orange: "#0E1116",
|
|
94
|
+
/** Ba sắc định danh (3.11.0): blue/purple chữ TRẮNG ở light (4,85 · 5,01:1), pink chữ mực (5,20:1). */
|
|
95
|
+
blue: "#FFFFFF",
|
|
96
|
+
purple: "#FFFFFF",
|
|
97
|
+
pink: "#0E1116"
|
|
94
98
|
},
|
|
95
99
|
dark: {
|
|
96
100
|
navy: "#FFFFFF",
|
|
97
101
|
teal: "#0E1116",
|
|
98
102
|
green: "#0E1116",
|
|
99
103
|
yellow: "#0E1116",
|
|
100
|
-
orange: "#0E1116"
|
|
104
|
+
orange: "#0E1116",
|
|
105
|
+
/** Bản dark của ba sắc đều sáng → mực: blue 7,99 · purple 6,95 · pink 7,14:1. */
|
|
106
|
+
blue: "#0E1116",
|
|
107
|
+
purple: "#0E1116",
|
|
108
|
+
pink: "#0E1116"
|
|
101
109
|
}
|
|
102
110
|
};
|
|
103
111
|
const semantic = {
|
package/dist/tokens.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tokens.ts"],"sourcesContent":["/**\n * Design token dạng TS object — dành cho nơi KHÔNG có Tailwind: canvas, biểu đồ tuỳ biến,\n * email HTML, xuất PDF, (sau này) React Native.\n *\n * ⚠ Nguồn sự thật của giá trị là `styles.css` (CSS variable). File này chỉ MIRROR hằng số.\n * Sửa màu ở styles.css thì phải sửa kèm ở đây — và ngược lại.\n *\n * Trong môi trường có Tailwind, LUÔN dùng class token (`bg-primary`, `text-ash-500`),\n * đừng đọc hex từ đây rồi nhét vào style inline: làm vậy là mất dark mode.\n *\n * Hợp đồng ba vai trò của màu (giống hệt `styles.css`; bảng đầy đủ ở docs/hop-dong-mau.md),\n * đọc tên là biết dùng ở đâu:\n * `x` → tô NỀN (fill đặc hoặc tint /5–/20 — trần chung an toàn cho mọi tone)\n * `xForeground` → chữ/icon NẰM TRÊN nền `x` ĐẶC — đo với `x`, ngưỡng 4,5:1\n * `xInk` → chính màu đó khi ĐI LÊN nền trang hoặc lên tint của chính nó (chữ, icon\n * rời, viền, vạch active) — đo với nền đó\n *\n * Cố ý KHÔNG mirror (xem docs/quyet-dinh-tich-hop.md §21): `--elevation-*` (chuỗi box-shadow chỉ\n * có nghĩa trong CSS), `--app-accent*` (giá trị thuộc về APP, không phải gói), `--font-*`.\n */\n\nimport { AVATAR_TONES, type AvatarToneColors, type AvatarTonePalette, getAvatarToneIndex } from \"./lib/avatar-tone.js\";\n\n/** Thang GOLD 7 bậc — trục màu chính của brand Comitor. */\nexport const gold = {\n 50: \"#FBF5DC\",\n 100: \"#F8E89D\",\n 200: \"#F5D060\",\n /** primary ở light mode */\n 300: \"#E8B824\",\n 400: \"#B88A00\",\n 500: \"#7A5800\",\n 600: \"#5C4006\"\n} as const;\n\n/** Trung tính brand (khác thang ash — đây là màu \"giấy/mực\" của brand book). */\nexport const brandNeutral = {\n ink: \"#0E1116\",\n inkSoft: \"#3A3F4A\",\n paper: \"#FBF7EE\",\n warm: \"#EDE8D8\"\n} as const;\n\n/**\n * Thang xám Comitor — namespace `ash`, KHÔNG phải `gray`.\n * Chỉ có 6 bậc; các bậc khuyết (200/400/600/800) cố ý không khai để không lẫn với\n * thang gray dựng sẵn của Tailwind (xem chú thích trong styles.css).\n */\nexport const ash = {\n 50: \"#F7F7F8\",\n 100: \"#EDEEF1\",\n 300: \"#C8CBD3\",\n 500: \"#7E8290\",\n 700: \"#3A3F4A\",\n 900: \"#16181D\"\n} as const;\n\n/**\n * Màu phụ — vai trò TÔ NỀN (fill đặc hoặc tint). Bản `dark` được nâng sáng vì trên nền #0E1116\n * bản `light` chìm nghỉm.\n *\n * ⚠ ĐỪNG dùng bảng này làm màu CHỮ ở light: nó được chọn để tô nền nên quá nhạt — dùng\n * `accentInk` ngay dưới. `accent.dark.navy` giữ nguyên #0F1E3D chỉ để mirror đúng `styles.css`\n * (khối `.dark` không override `--navy`); vai trò navy ở dark do `accentInk.dark.navy` gánh.\n */\nexport const accent = {\n light: {\n navy: \"#0F1E3D\",\n teal: \"#1F8A8B\",\n red: \"#D64545\",\n green: \"#2EA56A\",\n yellow: \"#F2C94C\",\n /** Gốc của tone `severe`. Giữ 3:1 trên mọi nền trang (3,56:1 trắng · 3,32:1 `muted`) — vì nó\n * TÔ chấm của `StatusPill variant=\"dot\"`; bậc sáng hơn (#F97316) chỉ 2,80:1. */\n orange: \"#EA580C\",\n /** Ba sắc ĐỊNH DANH — chỉ bảng tone avatar/icon app đọc (`lib/avatar-tone.ts`). */\n blue: \"#4169E1\",\n purple: \"#8A4FD8\",\n pink: \"#FF1493\"\n },\n /** Bảng MẶC ĐỊNH (comitor-ds) không khai lại màu phụ ở dark — cùng hex với light. Bản sáng\n * dành cho nền tối nằm ở `highContrast.dark`. Trừ `orange`: comitor-ds không có cam, và cùng\n * hex thì chữ cam trên tint /15 của chính nó ở dark chỉ 4,21:1 — nên bảng mặc định có bản\n * dark riêng. */\n dark: {\n navy: \"#0F1E3D\",\n teal: \"#1F8A8B\",\n red: \"#D64545\",\n green: \"#2EA56A\",\n yellow: \"#F2C94C\",\n orange: \"#FB923C\",\n /** Ba sắc định danh có bản dark RIÊNG như cam — bản light trên tint /15 hợp lên `card` chỉ 3,1–3,9:1. */\n blue: \"#82A8F5\",\n purple: \"#A78BFA\",\n pink: \"#FF69B4\"\n }\n} as const;\n\n/**\n * Bậc INK của màu phụ — mirror `--teal-ink` / `--green-ink` / `--red-ink` / `--yellow-ink` /\n * `--orange-ink` / `--navy-ink`. Đây là màu phụ khi nó làm **CHỮ/ICON/VIỀN trên nền trang**\n * (hoặc trên tint /5–/20 của chính nó), tức vai trò NGƯỢC với `accent`.\n *\n * Hợp đồng đặt tên của cả gói: `--x` tô nền · `--x-foreground` là chữ NẰM TRÊN nền `--x` ·\n * `--x-ink` là chính màu đó ĐI LÊN nền trang.\n *\n * Bản `light` là bậc tối đã đóng băng (kết quả của `color-mix(in srgb, --<màu>, --ink 30/40%)`\n * mà trước đây mỗi component tự tính lấy); bản `dark` chính là `accent.dark`. Đo trên tint 15%\n * của chính nó — nền thật mà chữ initials avatar và badge tonal đứng lên: teal 5,54 · green 5,60\n * · red 5,83:1 ở light (bản chưa có ink: 3,44 / 2,69 / 3,57:1 ✗).\n *\n * ⚠ Ở bảng MẶC ĐỊNH, `accent.dark` bằng hex của light nên bậc ink ở dark cũng vậy — đúng thứ\n * comitor-ds render. Bậc sáng cho nền tối nằm ở `highContrast.dark`.\n */\nexport const accentInk = {\n light: {\n navy: accent.light.navy,\n teal: \"#1A6668\",\n red: \"#9A3537\",\n green: \"#216A48\",\n /** = gold[500]: vàng không có bậc tối riêng, mượn thang gold cùng sắc. */\n yellow: gold[500],\n /** = color-mix(srgb, orange 72%, ink 28%) — bậc SÁNG nhất còn giữ trần tint /20: 5,85:1 trắng ·\n * 4,87:1 trên orange/15 · 4,58:1 trên orange/20. Bảng tương phản cao tối hơn: `highContrast`. */\n orange: \"#AC440F\",\n /** Ba sắc định danh — trên tint /15 của chính nó: blue 5,67 · purple 5,72 · pink 5,33:1. */\n blue: \"#3053B8\",\n purple: \"#6E3FB0\",\n pink: \"#B0126A\"\n },\n dark: {\n /** navy nguyên bản chìm vào nền dark (1,15:1) → bậc sáng riêng; cũng là `--chart-3` ở dark. */\n navy: \"#7EA2E8\",\n teal: accent.dark.teal,\n red: accent.dark.red,\n green: accent.dark.green,\n yellow: accent.dark.yellow,\n orange: accent.dark.orange,\n blue: accent.dark.blue,\n purple: accent.dark.purple,\n pink: accent.dark.pink\n }\n} as const;\n\n/**\n * Bậc FOREGROUND của màu phụ — mirror `--teal-foreground` / `--green-foreground` /\n * `--yellow-foreground` / `--orange-foreground` / `--navy-foreground`. Đây là chữ/icon nằm\n * **TRÊN nền `accent.*` ĐẶC**, tức vai trò thứ ba, khác hẳn `accentInk` (màu đó đi lên nền TRANG).\n *\n * Cả bốn sắc teal/green/yellow/orange đều sáng ở light và còn sáng hơn ở dark → chữ là MỰC ở cả\n * hai theme, một giá trị đủ dùng: teal 4,56:1 (light) / 10,16:1 (dark) · green 6,04 / 10,85 ·\n * yellow 11,92 / 12,35 · orange 5,31 / 8,36. Navy thì ngược lại — chữ TRẮNG, 16,49:1 ở cả hai theme.\n *\n * ⚠ CỐ Ý KHÔNG có `red`: nền đỏ đặc không mang được chữ tốt ở light — trắng trên #D64545 được\n * 4,38:1 và mực 4,32:1, cả hai đều dưới AA. Nền đỏ đặc có chữ phải dùng `status.*.destructive`;\n * ở bảng tương phản cao token đó là #C93B3B và đạt 5,03:1. `accent.*.red` chỉ để tô TINT và làm\n * màu chart. (Cam thì mực cứu được — 5,31:1 — nên `orange` có mặt.)\n */\nexport const accentForeground = {\n light: {\n navy: \"#FFFFFF\",\n teal: \"#0E1116\",\n green: \"#0E1116\",\n yellow: \"#0E1116\",\n orange: \"#0E1116\"\n },\n dark: {\n navy: \"#FFFFFF\",\n teal: \"#0E1116\",\n green: \"#0E1116\",\n yellow: \"#0E1116\",\n orange: \"#0E1116\"\n }\n} as const;\n\n/** Semantic token của shadcn — mirror khối `:root` / `.dark` trong styles.css. */\nexport const semantic = {\n light: {\n background: \"#FFFFFF\",\n foreground: \"#0E1116\",\n card: \"#FFFFFF\",\n cardForeground: \"#0E1116\",\n popover: \"#FFFFFF\",\n popoverForeground: \"#0E1116\",\n primary: \"#E8B824\",\n primaryForeground: \"#0E1116\",\n /** Bậc INK của brand. Ở bảng MẶC ĐỊNH nó BẰNG `primary`: comitor-ds tô gold cho cả những vai\n * trò mà 1.4.11 đòi 3:1 (rãnh Switch BẬT, gạch active của Tabs) — 1,86:1, đã biết và chấp\n * nhận. Bậc tối #7A5800 nằm ở `highContrast.light.primaryInk`. */\n primaryInk: \"#E8B824\",\n secondary: \"#EDE8D8\",\n secondaryForeground: \"#0E1116\",\n muted: \"#F7F7F8\",\n /** Bậc chữ phụ — giá trị của comitor-ds, bằng đúng `ash[500]`. 3,83:1 trên nền trắng và\n * 3,58:1 trên `muted`, tức dưới AA 4,5:1: đã biết và chấp nhận, bản đạt chuẩn nằm ở\n * `highContrast.light.mutedForeground`. */\n mutedForeground: ash[500],\n accent: \"#FBF5DC\",\n accentForeground: \"#0E1116\",\n /** Nền nút xoá. Chữ trắng trên nó được 4,38:1 — thiếu 0,12 so với AA, đã biết và chấp nhận. */\n destructive: \"#D64545\",\n destructiveForeground: \"#FFFFFF\",\n /** Nền nút xoá lúc HOVER = `destructive` ở 90% trên nền trắng, đúng thứ `hover:bg-red/90`\n * của comitor-ds render. Ghim hex vì `/90` phụ thuộc nền phía sau. */\n destructiveHover: \"#DA5A5A\",\n /** Chữ báo lỗi trên nền trang. comitor-ds viết `text-destructive` cho vai trò này, tức cùng\n * hex với nền — 4,38:1 trên nền trắng. */\n destructiveInk: \"#D64545\",\n /** RANH GIỚI TRANG TRÍ (vạch chia, viền thẻ nghỉ) — 1,16:1, cố ý: 1.4.11 không đòi tương\n * phản cho thứ không mang thông tin. Ranh giới của CONTROL đi qua `controlEdge`. */\n border: \"#EDEEF1\",\n /** Viền ô nhập — comitor-ds cho nó cùng bậc với `border`. 1,16:1, đã biết và chấp nhận. */\n input: \"#EDEEF1\",\n ring: \"#E8B824\",\n /** Ranh giới của control lúc NGHỈ — ở bảng mặc định bằng đúng `input`. */\n controlEdge: \"#EDEEF1\",\n /** Cùng vai trò nhưng ở HOVER/ACTIVE. comitor-ds dùng `hover:border-gray-300` = `ash[300]`. */\n controlEdgeStrong: ash[300],\n /** Nền control khi BẬT/ĐÃ CHỌN (rãnh Switch bật, ô Checkbox đã chọn, chấm Radio). comitor-ds\n * tô gold: 1,86:1 với nền trang và với núm — đã biết và chấp nhận. */\n controlOn: \"#E8B824\",\n /** Núm Switch / chấm Radio — TRẮNG ở cả hai theme, đúng `bg-white` của comitor-ds. */\n controlKnob: \"#FFFFFF\",\n /** DẤU CHECK của Checkbox và chữ trên ngày ĐANG CHỌN của Calendar — tách khỏi `controlKnob`\n * vì trên nền gold SÁNG thì dấu phải là MỰC (10,19:1), còn ở bảng tương phản cao nền là\n * gold-500 TỐI nên dấu lật thành màu nền trang. */\n controlCheck: brandNeutral.ink,\n /** Rãnh Switch khi TẮT — comitor-ds dùng `bg-gray-300` / `dark:bg-gray-700`. */\n switchTrack: ash[300],\n /** Rãnh Slider — comitor-ds dùng `bg-muted`, khác bậc với rãnh Switch. */\n sliderTrack: \"#F7F7F8\",\n /** Button variant `link`. comitor-ds dùng `gold[400]` — 3,15:1, dưới AA, đã biết và chấp nhận. */\n linkInk: gold[400],\n linkInkHover: gold[300],\n sidebar: \"#FAFAF9\",\n sidebarForeground: \"#0E1116\",\n sidebarBorder: \"#EDEEF1\",\n sidebarAccent: \"#FBF5DC\",\n sidebarAccentForeground: \"#0E1116\"\n },\n dark: {\n background: \"#0E1116\",\n foreground: \"#F7F7F8\",\n card: \"#16181D\",\n cardForeground: \"#F7F7F8\",\n popover: \"#16181D\",\n popoverForeground: \"#F7F7F8\",\n primary: \"#F5D060\",\n primaryForeground: \"#0E1116\",\n /** Ở dark `primary` vốn đã là bậc sáng (12,67:1 trên nền `background`) nên ink = chính nó. */\n primaryInk: \"#F5D060\",\n secondary: \"#1E2128\",\n secondaryForeground: \"#F7F7F8\",\n muted: \"#1E2128\",\n /** comitor-ds không khai lại token này ở dark — cùng hex với light. 4,21:1 trên `muted`,\n * dưới AA: đã biết và chấp nhận. */\n mutedForeground: ash[500],\n accent: \"#1E2128\",\n accentForeground: \"#F7F7F8\",\n /** Ghim đúng giá trị của light — comitor-ds dùng chung một hex cho cả hai theme. */\n destructive: \"#D64545\",\n destructiveForeground: \"#FFFFFF\",\n destructiveHover: \"#DA5A5A\",\n /** 4,06:1 trên `card` — cùng hex với nền, đúng `text-destructive` của comitor-ds. */\n destructiveInk: \"#D64545\",\n /** TRANG TRÍ, 1,17:1 trên `background` và 1,10:1 trên `card` — cố ý, xem nhánh light. */\n border: \"#1E2128\",\n /** Viền ô nhập bản dark — KHÔNG phải #1E2128 của comitor-ds (chỉ 1,10:1 trên `card`, ô nhập tan\n * vào nền). #434A5A = 2,00:1, mirror `.dark { --input }` từ `0.5.0`; trước `3.3.3` mirror này còn\n * ghi #1E2128. */\n input: \"#434A5A\",\n ring: \"#F5D060\",\n /** = `input` (`--control-edge: var(--input)`). */\n controlEdge: \"#434A5A\",\n /** comitor-ds dùng `dark:hover:border-gray-500` = `ash[500]` (lật chiều so với light). */\n controlEdgeStrong: ash[500],\n /** comitor-ds tô `dark:data-[state=checked]:bg-gold-300`, nhưng token `primary` của chính nó\n * ở dark là #F5D060; gói theo token, nên control BẬT ở dark là bậc sáng. */\n controlOn: \"#F5D060\",\n /** TRẮNG ở cả hai theme, đúng `bg-white` của comitor-ds. */\n controlKnob: \"#FFFFFF\",\n /** comitor-ds vẽ dấu check bằng `text-ink` ở CẢ hai theme. */\n controlCheck: brandNeutral.ink,\n switchTrack: ash[700],\n sliderTrack: \"#1E2128\",\n linkInk: gold[400],\n linkInkHover: gold[300],\n sidebar: \"#16181D\",\n sidebarForeground: \"#F7F7F8\",\n sidebarBorder: \"#1E2128\",\n sidebarAccent: \"#1E2128\",\n sidebarAccentForeground: \"#F7F7F8\"\n }\n} as const;\n\n/**\n * BẢNG MÀU THỨ HAI — TƯƠNG PHẢN CAO. Mirror khối `[data-contrast=\"high\"]` /\n * `.dark[data-contrast=\"high\"]` trong styles.css.\n *\n * Chỉ chứa những token KHÁC bảng mặc định, đúng như CSS: hợp lên trên để có bảng đầy đủ.\n *\n * ```ts\n * const mau = { ...semantic.light, ...highContrast.light };\n * ```\n *\n * Bảng mặc định là comitor-ds (bản duyệt, có những cặp dưới ngưỡng — đã liệt kê ở từng token).\n * Bảng này là bảng đã đo, do NGƯỜI DÙNG bật qua `<ContrastProvider>` + `<ContrastToggle>`.\n * `accent` là màu phụ bản sáng dành cho nền tối — bảng mặc định không có, xem `accent.dark`.\n */\nexport const highContrast = {\n light: {\n /** 5,23:1 trên `background` · 4,89:1 trên `muted` (bảng mặc định: 3,83 / 3,58) */\n mutedForeground: \"#686C7A\",\n /** Chữ trắng trên nền này được 5,03:1 (bảng mặc định #D64545 chỉ 4,38:1) */\n destructive: \"#C93B3B\",\n /** = color-mix(destructive, ink 15%) — ĐẬM thêm khi hover, ngược chiều bảng mặc định. */\n destructiveHover: \"#AD3535\",\n /** 7,15:1 trên nền trắng (bảng mặc định dùng chính #D64545, 4,38:1) */\n destructiveInk: accentInk.light.red,\n /** 3,83:1 trên nền trắng (bảng mặc định #EDEEF1 chỉ 1,16:1) */\n input: \"#7E8290\",\n controlEdge: \"#7E8290\",\n controlEdgeStrong: ash[700],\n /** #B48700 = gold-400 tối đi 2%: 3,28:1 trên `background`/`card` · 3,06:1 trên `muted` · ⇄ núm\n * trắng 3,28:1 (bảng mặc định: 1,86 / 1,86). Trước `3.3.3` mirror này còn ghi gold-500 — giá trị\n * `styles.css` đã bỏ. */\n controlOn: \"#B48700\",\n /** = `background`, nên luôn tương phản đúng chiều với cả rãnh BẬT lẫn rãnh TẮT. */\n controlKnob: \"#FFFFFF\",\n /** MỰC trên nền gold (5,77:1) — cùng cách vẽ với bảng mặc định; trước `3.3.3` mirror này ghi trắng. */\n controlCheck: brandNeutral.ink,\n switchTrack: \"#7E8290\",\n sliderTrack: \"#7E8290\",\n primaryInk: gold[500],\n linkInk: gold[500],\n linkInkHover: gold[600],\n /** = `--orange-ink` của khối này — bậc ink màu phụ DUY NHẤT mà bảng này đổi ở light:\n * color-mix(srgb, orange 65%, ink 35%), 6,66:1 trắng · 5,55:1 trên orange/15 (bảng mặc định\n * #AC440F: 5,85 / 4,87:1). */\n severeInk: \"#9D3F10\",\n accent: accent.light\n },\n dark: {\n /** 5,70:1 trên `background` · 5,35:1 trên `card` · 4,86:1 trên `muted` */\n mutedForeground: \"#888D9B\",\n destructive: \"#C93B3B\",\n destructiveHover: \"#AD3535\",\n destructiveInk: \"#F87171\",\n /** Ghim một giá trị cho cả hai theme — 4,94:1 trên `background` */\n input: \"#7E8290\",\n controlEdge: \"#7E8290\",\n controlEdgeStrong: ash[300],\n /** 12,67:1 với nền trang VÀ với `controlKnob` (#0E1116) */\n controlOn: \"#F5D060\",\n controlKnob: \"#0E1116\",\n controlCheck: \"#0E1116\",\n switchTrack: \"#7E8290\",\n sliderTrack: \"#7E8290\",\n primaryInk: \"#F5D060\",\n linkInk: gold[200],\n linkInkHover: gold[100],\n /** Bằng bảng mặc định (8,36:1 trên `background` · 6,09:1 trên orange/15) — khai cho đủ cặp\n * với `light`, vì CSS phải huỷ bậc tối của khối light ở đây. */\n severeInk: accent.dark.orange,\n /** Màu phụ bản SÁNG cho nền tối — bảng mặc định không có bản dark cho nhóm này (trừ `orange`,\n * vốn đã sáng ở `accent.dark`). */\n accent: {\n navy: \"#0F1E3D\",\n teal: \"#2DD4BF\",\n red: \"#F87171\",\n green: \"#4ADE80\",\n yellow: \"#FACC15\",\n orange: accent.dark.orange\n }\n }\n} as const;\n\n/**\n * Alias trạng thái — success/warning/severe/info trỏ vào màu phụ, destructive giữ nguyên 2 chế độ.\n * `severe` (cam) là nấc giữa warning và destructive: ưu tiên cao, SLA sắp trễ.\n *\n * Ba lớp KHÁC nhau, đừng gộp:\n * • `destructive` — màu NỀN (nút xoá): #D64545 ở bảng mặc định (chữ trắng 4,38:1, dưới AA và đã\n * chấp nhận), #C93B3B ở bảng tương phản cao (5,03:1 ✓);\n * • `destructiveForeground` (#FFFFFF) — chữ NẰM TRÊN nền đó;\n * • `destructiveInk` — màu CHỮ đỏ trên nền trang (dòng lỗi). Bảng mặc định cho nó cùng hex với\n * nền (đúng `text-destructive` của comitor-ds); bảng tương phản cao tách thành bậc tối riêng.\n *\n * `successForeground` / `warningForeground` / `infoForeground` là MỰC ở CẢ hai theme (không phải\n * trắng ở light rồi lật ở dark như bản 0.1.0): ba nền trạng thái đều là sắc sáng.\n * success mực 6,04:1 (light) · 10,85:1 (dark) [trắng chỉ 3,13 / 1,74:1 ✗]\n * info mực 4,56:1 (light) · 10,16:1 (dark) [trắng chỉ 4,14 / 1,86:1 ✗]\n * warning mực 11,92:1 (light) · 12,35:1 (dark) [trắng chỉ 1,59 / 1,53:1 ✗]\n * severe mực 5,31:1 (light) · 8,36:1 (dark) [trắng chỉ 3,56 / 2,26:1 ✗]\n * ⚠ `info` ở light là cặp chặt nhất gói (4,56:1, dư 0,06) — đổi `accent.light.teal` là phải đo lại.\n */\nexport const status = {\n light: {\n success: accent.light.green,\n successForeground: \"#0E1116\",\n warning: accent.light.yellow,\n warningForeground: \"#0E1116\",\n severe: accent.light.orange,\n severeForeground: \"#0E1116\",\n info: accent.light.teal,\n infoForeground: \"#0E1116\",\n destructive: \"#D64545\",\n destructiveForeground: \"#FFFFFF\",\n successInk: accentInk.light.green,\n warningInk: accentInk.light.yellow,\n severeInk: accentInk.light.orange,\n infoInk: accentInk.light.teal,\n /** = `semantic.light.destructiveInk` (#D64545 — `--destructive-ink: var(--destructive)` ở bảng mặc\n * định). Trước `3.3.3` trỏ nhầm `accentInk.light.red` (#9A3537 — bậc của bảng TƯƠNG PHẢN CAO). */\n destructiveInk: semantic.light.destructiveInk\n },\n dark: {\n success: accent.dark.green,\n successForeground: \"#0E1116\",\n warning: accent.dark.yellow,\n warningForeground: \"#0E1116\",\n severe: accent.dark.orange,\n severeForeground: \"#0E1116\",\n info: accent.dark.teal,\n infoForeground: \"#0E1116\",\n destructive: \"#D64545\",\n destructiveForeground: \"#FFFFFF\",\n successInk: accentInk.dark.green,\n warningInk: accentInk.dark.yellow,\n severeInk: accentInk.dark.orange,\n infoInk: accentInk.dark.teal,\n destructiveInk: semantic.dark.destructiveInk\n }\n} as const;\n\n/**\n * Bảng màu biểu đồ (chart-1…chart-5). Thứ tự = thứ tự series mặc định.\n *\n * ⚠ `chart-1` ở dark là `gold[300]` như light: `styles.css` KHÔNG khai lại `--chart-1` trong `.dark`\n * (chỉ `--chart-3` tự sáng lên qua `--navy-ink`). Trước `3.3.3` mirror này ghi `gold[200]` — biểu\n * đồ canvas/email/PDF dựng từ `tokens` ra màu khác biểu đồ trên trang.\n */\nexport const chart = {\n light: [gold[300], accent.light.teal, accent.light.navy, accent.light.green, accent.light.red],\n dark: [gold[300], accent.dark.teal, accentInk.dark.navy, accent.dark.green, accent.dark.red]\n} as const;\n\n/**\n * Màu avatar chữ cái (hex đục) cho nơi KHÔNG có Tailwind: canvas, email HTML, PDF.\n *\n * ⚠ KHÔNG phải bảng màu thứ hai. Bảng tone DUY NHẤT của cả gói nằm ở `lib/avatar-tone.ts`, mỗi\n * tone mang cả chuỗi class (cho DOM) lẫn công thức hex tương ứng; ở đây chỉ nạp bảng màu vào công\n * thức đó. Web và email vì vậy dùng chung một thứ tự tone, một hàm băm, một cách chuẩn hoá tên —\n * không còn hai bảng để lệch nhau.\n */\nconst avatarTonePalette = {\n light: {\n gold,\n accent: accent.light,\n accentInk: accentInk.light,\n card: semantic.light.card\n },\n dark: {\n gold,\n accent: accent.dark,\n accentInk: accentInk.dark,\n card: semantic.dark.card\n }\n} satisfies Record<ThemeMode, AvatarTonePalette>;\n\n/**\n * Cặp nền/chữ (hex) của avatar cho một tên, theo theme.\n *\n * Tên hàm nói đúng thứ nó trả về: `...Colors` = hex (canvas/email/PDF), còn bản trả về chuỗi\n * class Tailwind cho DOM là `getAvatarToneClasses` ở `@comitor/ui`. Cùng một tên người luôn ra\n * cùng một tone ở cả hai đường vì chỉ số do MỘT hàm tính (`getAvatarToneIndex`).\n */\nexport function getAvatarToneColors(name: string, mode: ThemeMode = \"light\"): AvatarToneColors {\n // `noUncheckedIndexedAccess` bật → phải có nhánh lùi, dù chỉ số luôn nằm trong [0, 8).\n const tone = AVATAR_TONES[getAvatarToneIndex(name)] ?? AVATAR_TONES[0];\n return tone[mode](avatarTonePalette[mode]);\n}\n\nexport type { AvatarToneColors };\nexport { getAvatarToneIndex };\n\n/** Số đo khung ứng dụng — mirror --sidebar-width / --sidebar-width-icon / --header-height. */\nexport const layout = {\n /** px — --header-height 3.5rem */\n headerHeight: 56,\n /** px — --sidebar-width 16rem */\n sidebarWidth: 256,\n /** px — --sidebar-width-icon 3.5rem */\n sidebarCollapsedWidth: 56,\n /** px — --radius 0.375rem */\n radius: 6,\n breakpoints: { mobile: 768, tablet: 1024, desktop: 1280 },\n /** px — kích thước tối thiểu của vùng chạm trên mobile (WCAG 2.5.5) */\n minTapTarget: 44\n} as const;\n\n/** Font stack — app phải tự nạp Inter (subsets latin + vietnamese) và gán --font-inter. */\nexport const typography = {\n fontSans: '\"Inter\", system-ui, sans-serif',\n fontMono: '\"Geist Mono\", ui-monospace, monospace'\n} as const;\n\nexport type ThemeMode = \"light\" | \"dark\";\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/tokens.ts"],"sourcesContent":["/**\n * Design token dạng TS object — dành cho nơi KHÔNG có Tailwind: canvas, biểu đồ tuỳ biến,\n * email HTML, xuất PDF, (sau này) React Native.\n *\n * ⚠ Nguồn sự thật của giá trị là `styles.css` (CSS variable). File này chỉ MIRROR hằng số.\n * Sửa màu ở styles.css thì phải sửa kèm ở đây — và ngược lại.\n *\n * Trong môi trường có Tailwind, LUÔN dùng class token (`bg-primary`, `text-ash-500`),\n * đừng đọc hex từ đây rồi nhét vào style inline: làm vậy là mất dark mode.\n *\n * Hợp đồng ba vai trò của màu (giống hệt `styles.css`; bảng đầy đủ ở docs/hop-dong-mau.md),\n * đọc tên là biết dùng ở đâu:\n * `x` → tô NỀN (fill đặc hoặc tint /5–/20 — trần chung an toàn cho mọi tone)\n * `xForeground` → chữ/icon NẰM TRÊN nền `x` ĐẶC — đo với `x`, ngưỡng 4,5:1\n * `xInk` → chính màu đó khi ĐI LÊN nền trang hoặc lên tint của chính nó (chữ, icon\n * rời, viền, vạch active) — đo với nền đó\n *\n * Cố ý KHÔNG mirror (xem docs/quyet-dinh-tich-hop.md §21): `--elevation-*` (chuỗi box-shadow chỉ\n * có nghĩa trong CSS), `--app-accent*` (giá trị thuộc về APP, không phải gói), `--font-*`.\n */\n\nimport { AVATAR_TONES, type AvatarToneColors, type AvatarTonePalette, getAvatarToneIndex } from \"./lib/avatar-tone.js\";\n\n/** Thang GOLD 7 bậc — trục màu chính của brand Comitor. */\nexport const gold = {\n 50: \"#FBF5DC\",\n 100: \"#F8E89D\",\n 200: \"#F5D060\",\n /** primary ở light mode */\n 300: \"#E8B824\",\n 400: \"#B88A00\",\n 500: \"#7A5800\",\n 600: \"#5C4006\"\n} as const;\n\n/** Trung tính brand (khác thang ash — đây là màu \"giấy/mực\" của brand book). */\nexport const brandNeutral = {\n ink: \"#0E1116\",\n inkSoft: \"#3A3F4A\",\n paper: \"#FBF7EE\",\n warm: \"#EDE8D8\"\n} as const;\n\n/**\n * Thang xám Comitor — namespace `ash`, KHÔNG phải `gray`.\n * Chỉ có 6 bậc; các bậc khuyết (200/400/600/800) cố ý không khai để không lẫn với\n * thang gray dựng sẵn của Tailwind (xem chú thích trong styles.css).\n */\nexport const ash = {\n 50: \"#F7F7F8\",\n 100: \"#EDEEF1\",\n 300: \"#C8CBD3\",\n 500: \"#7E8290\",\n 700: \"#3A3F4A\",\n 900: \"#16181D\"\n} as const;\n\n/**\n * Màu phụ — vai trò TÔ NỀN (fill đặc hoặc tint). Bản `dark` được nâng sáng vì trên nền #0E1116\n * bản `light` chìm nghỉm.\n *\n * ⚠ ĐỪNG dùng bảng này làm màu CHỮ ở light: nó được chọn để tô nền nên quá nhạt — dùng\n * `accentInk` ngay dưới. `accent.dark.navy` giữ nguyên #0F1E3D chỉ để mirror đúng `styles.css`\n * (khối `.dark` không override `--navy`); vai trò navy ở dark do `accentInk.dark.navy` gánh.\n */\nexport const accent = {\n light: {\n navy: \"#0F1E3D\",\n teal: \"#1F8A8B\",\n red: \"#D64545\",\n green: \"#2EA56A\",\n yellow: \"#F2C94C\",\n /** Gốc của tone `severe`. Giữ 3:1 trên mọi nền trang (3,56:1 trắng · 3,32:1 `muted`) — vì nó\n * TÔ chấm của `StatusPill variant=\"dot\"`; bậc sáng hơn (#F97316) chỉ 2,80:1. */\n orange: \"#EA580C\",\n /** Ba sắc ĐỊNH DANH — chỉ bảng tone avatar/icon app đọc (`lib/avatar-tone.ts`). */\n blue: \"#4169E1\",\n purple: \"#8A4FD8\",\n pink: \"#FF1493\"\n },\n /** Bảng MẶC ĐỊNH (comitor-ds) không khai lại màu phụ ở dark — cùng hex với light. Bản sáng\n * dành cho nền tối nằm ở `highContrast.dark`. Trừ `orange`: comitor-ds không có cam, và cùng\n * hex thì chữ cam trên tint /15 của chính nó ở dark chỉ 4,21:1 — nên bảng mặc định có bản\n * dark riêng. */\n dark: {\n navy: \"#0F1E3D\",\n teal: \"#1F8A8B\",\n red: \"#D64545\",\n green: \"#2EA56A\",\n yellow: \"#F2C94C\",\n orange: \"#FB923C\",\n /** Ba sắc định danh có bản dark RIÊNG như cam — bản light trên tint /15 hợp lên `card` chỉ 3,1–3,9:1. */\n blue: \"#82A8F5\",\n purple: \"#A78BFA\",\n pink: \"#FF69B4\"\n }\n} as const;\n\n/**\n * Bậc INK của màu phụ — mirror `--teal-ink` / `--green-ink` / `--red-ink` / `--yellow-ink` /\n * `--orange-ink` / `--navy-ink`. Đây là màu phụ khi nó làm **CHỮ/ICON/VIỀN trên nền trang**\n * (hoặc trên tint /5–/20 của chính nó), tức vai trò NGƯỢC với `accent`.\n *\n * Hợp đồng đặt tên của cả gói: `--x` tô nền · `--x-foreground` là chữ NẰM TRÊN nền `--x` ·\n * `--x-ink` là chính màu đó ĐI LÊN nền trang.\n *\n * Bản `light` là bậc tối đã đóng băng (kết quả của `color-mix(in srgb, --<màu>, --ink 30/40%)`\n * mà trước đây mỗi component tự tính lấy); bản `dark` chính là `accent.dark`. Đo trên tint 15%\n * của chính nó — nền thật mà chữ initials avatar và badge tonal đứng lên: teal 5,54 · green 5,60\n * · red 5,83:1 ở light (bản chưa có ink: 3,44 / 2,69 / 3,57:1 ✗).\n *\n * ⚠ Ở bảng MẶC ĐỊNH, `accent.dark` bằng hex của light nên bậc ink ở dark cũng vậy — đúng thứ\n * comitor-ds render. Bậc sáng cho nền tối nằm ở `highContrast.dark`.\n */\nexport const accentInk = {\n light: {\n navy: accent.light.navy,\n teal: \"#1A6668\",\n red: \"#9A3537\",\n green: \"#216A48\",\n /** = gold[500]: vàng không có bậc tối riêng, mượn thang gold cùng sắc. */\n yellow: gold[500],\n /** = color-mix(srgb, orange 72%, ink 28%) — bậc SÁNG nhất còn giữ trần tint /20: 5,85:1 trắng ·\n * 4,87:1 trên orange/15 · 4,58:1 trên orange/20. Bảng tương phản cao tối hơn: `highContrast`. */\n orange: \"#AC440F\",\n /** Ba sắc định danh — trên tint /15 của chính nó: blue 5,67 · purple 5,72 · pink 5,33:1. */\n blue: \"#3053B8\",\n purple: \"#6E3FB0\",\n pink: \"#B0126A\"\n },\n dark: {\n /** navy nguyên bản chìm vào nền dark (1,15:1) → bậc sáng riêng; cũng là `--chart-3` ở dark. */\n navy: \"#7EA2E8\",\n teal: accent.dark.teal,\n red: accent.dark.red,\n green: accent.dark.green,\n yellow: accent.dark.yellow,\n orange: accent.dark.orange,\n blue: accent.dark.blue,\n purple: accent.dark.purple,\n pink: accent.dark.pink\n }\n} as const;\n\n/**\n * Bậc FOREGROUND của màu phụ — mirror `--teal-foreground` / `--green-foreground` /\n * `--yellow-foreground` / `--orange-foreground` / `--navy-foreground`. Đây là chữ/icon nằm\n * **TRÊN nền `accent.*` ĐẶC**, tức vai trò thứ ba, khác hẳn `accentInk` (màu đó đi lên nền TRANG).\n *\n * Cả bốn sắc teal/green/yellow/orange đều sáng ở light và còn sáng hơn ở dark → chữ là MỰC ở cả\n * hai theme, một giá trị đủ dùng: teal 4,56:1 (light) / 10,16:1 (dark) · green 6,04 / 10,85 ·\n * yellow 11,92 / 12,35 · orange 5,31 / 8,36. Navy thì ngược lại — chữ TRẮNG, 16,49:1 ở cả hai theme.\n *\n * ⚠ CỐ Ý KHÔNG có `red`: nền đỏ đặc không mang được chữ tốt ở light — trắng trên #D64545 được\n * 4,38:1 và mực 4,32:1, cả hai đều dưới AA. Nền đỏ đặc có chữ phải dùng `status.*.destructive`;\n * ở bảng tương phản cao token đó là #C93B3B và đạt 5,03:1. `accent.*.red` chỉ để tô TINT và làm\n * màu chart. (Cam thì mực cứu được — 5,31:1 — nên `orange` có mặt.)\n */\nexport const accentForeground = {\n light: {\n navy: \"#FFFFFF\",\n teal: \"#0E1116\",\n green: \"#0E1116\",\n yellow: \"#0E1116\",\n orange: \"#0E1116\",\n /** Ba sắc định danh (3.11.0): blue/purple chữ TRẮNG ở light (4,85 · 5,01:1), pink chữ mực (5,20:1). */\n blue: \"#FFFFFF\",\n purple: \"#FFFFFF\",\n pink: \"#0E1116\"\n },\n dark: {\n navy: \"#FFFFFF\",\n teal: \"#0E1116\",\n green: \"#0E1116\",\n yellow: \"#0E1116\",\n orange: \"#0E1116\",\n /** Bản dark của ba sắc đều sáng → mực: blue 7,99 · purple 6,95 · pink 7,14:1. */\n blue: \"#0E1116\",\n purple: \"#0E1116\",\n pink: \"#0E1116\"\n }\n} as const;\n\n/** Semantic token của shadcn — mirror khối `:root` / `.dark` trong styles.css. */\nexport const semantic = {\n light: {\n background: \"#FFFFFF\",\n foreground: \"#0E1116\",\n card: \"#FFFFFF\",\n cardForeground: \"#0E1116\",\n popover: \"#FFFFFF\",\n popoverForeground: \"#0E1116\",\n primary: \"#E8B824\",\n primaryForeground: \"#0E1116\",\n /** Bậc INK của brand. Ở bảng MẶC ĐỊNH nó BẰNG `primary`: comitor-ds tô gold cho cả những vai\n * trò mà 1.4.11 đòi 3:1 (rãnh Switch BẬT, gạch active của Tabs) — 1,86:1, đã biết và chấp\n * nhận. Bậc tối #7A5800 nằm ở `highContrast.light.primaryInk`. */\n primaryInk: \"#E8B824\",\n secondary: \"#EDE8D8\",\n secondaryForeground: \"#0E1116\",\n muted: \"#F7F7F8\",\n /** Bậc chữ phụ — giá trị của comitor-ds, bằng đúng `ash[500]`. 3,83:1 trên nền trắng và\n * 3,58:1 trên `muted`, tức dưới AA 4,5:1: đã biết và chấp nhận, bản đạt chuẩn nằm ở\n * `highContrast.light.mutedForeground`. */\n mutedForeground: ash[500],\n accent: \"#FBF5DC\",\n accentForeground: \"#0E1116\",\n /** Nền nút xoá. Chữ trắng trên nó được 4,38:1 — thiếu 0,12 so với AA, đã biết và chấp nhận. */\n destructive: \"#D64545\",\n destructiveForeground: \"#FFFFFF\",\n /** Nền nút xoá lúc HOVER = `destructive` ở 90% trên nền trắng, đúng thứ `hover:bg-red/90`\n * của comitor-ds render. Ghim hex vì `/90` phụ thuộc nền phía sau. */\n destructiveHover: \"#DA5A5A\",\n /** Chữ báo lỗi trên nền trang. comitor-ds viết `text-destructive` cho vai trò này, tức cùng\n * hex với nền — 4,38:1 trên nền trắng. */\n destructiveInk: \"#D64545\",\n /** RANH GIỚI TRANG TRÍ (vạch chia, viền thẻ nghỉ) — 1,16:1, cố ý: 1.4.11 không đòi tương\n * phản cho thứ không mang thông tin. Ranh giới của CONTROL đi qua `controlEdge`. */\n border: \"#EDEEF1\",\n /** Viền ô nhập — comitor-ds cho nó cùng bậc với `border`. 1,16:1, đã biết và chấp nhận. */\n input: \"#EDEEF1\",\n ring: \"#E8B824\",\n /** Ranh giới của control lúc NGHỈ — ở bảng mặc định bằng đúng `input`. */\n controlEdge: \"#EDEEF1\",\n /** Cùng vai trò nhưng ở HOVER/ACTIVE. comitor-ds dùng `hover:border-gray-300` = `ash[300]`. */\n controlEdgeStrong: ash[300],\n /** Nền control khi BẬT/ĐÃ CHỌN (rãnh Switch bật, ô Checkbox đã chọn, chấm Radio). comitor-ds\n * tô gold: 1,86:1 với nền trang và với núm — đã biết và chấp nhận. */\n controlOn: \"#E8B824\",\n /** Núm Switch / chấm Radio — TRẮNG ở cả hai theme, đúng `bg-white` của comitor-ds. */\n controlKnob: \"#FFFFFF\",\n /** DẤU CHECK của Checkbox và chữ trên ngày ĐANG CHỌN của Calendar — tách khỏi `controlKnob`\n * vì trên nền gold SÁNG thì dấu phải là MỰC (10,19:1), còn ở bảng tương phản cao nền là\n * gold-500 TỐI nên dấu lật thành màu nền trang. */\n controlCheck: brandNeutral.ink,\n /** Rãnh Switch khi TẮT — comitor-ds dùng `bg-gray-300` / `dark:bg-gray-700`. */\n switchTrack: ash[300],\n /** Rãnh Slider — comitor-ds dùng `bg-muted`, khác bậc với rãnh Switch. */\n sliderTrack: \"#F7F7F8\",\n /** Button variant `link`. comitor-ds dùng `gold[400]` — 3,15:1, dưới AA, đã biết và chấp nhận. */\n linkInk: gold[400],\n linkInkHover: gold[300],\n sidebar: \"#FAFAF9\",\n sidebarForeground: \"#0E1116\",\n sidebarBorder: \"#EDEEF1\",\n sidebarAccent: \"#FBF5DC\",\n sidebarAccentForeground: \"#0E1116\"\n },\n dark: {\n background: \"#0E1116\",\n foreground: \"#F7F7F8\",\n card: \"#16181D\",\n cardForeground: \"#F7F7F8\",\n popover: \"#16181D\",\n popoverForeground: \"#F7F7F8\",\n primary: \"#F5D060\",\n primaryForeground: \"#0E1116\",\n /** Ở dark `primary` vốn đã là bậc sáng (12,67:1 trên nền `background`) nên ink = chính nó. */\n primaryInk: \"#F5D060\",\n secondary: \"#1E2128\",\n secondaryForeground: \"#F7F7F8\",\n muted: \"#1E2128\",\n /** comitor-ds không khai lại token này ở dark — cùng hex với light. 4,21:1 trên `muted`,\n * dưới AA: đã biết và chấp nhận. */\n mutedForeground: ash[500],\n accent: \"#1E2128\",\n accentForeground: \"#F7F7F8\",\n /** Ghim đúng giá trị của light — comitor-ds dùng chung một hex cho cả hai theme. */\n destructive: \"#D64545\",\n destructiveForeground: \"#FFFFFF\",\n destructiveHover: \"#DA5A5A\",\n /** 4,06:1 trên `card` — cùng hex với nền, đúng `text-destructive` của comitor-ds. */\n destructiveInk: \"#D64545\",\n /** TRANG TRÍ, 1,17:1 trên `background` và 1,10:1 trên `card` — cố ý, xem nhánh light. */\n border: \"#1E2128\",\n /** Viền ô nhập bản dark — KHÔNG phải #1E2128 của comitor-ds (chỉ 1,10:1 trên `card`, ô nhập tan\n * vào nền). #434A5A = 2,00:1, mirror `.dark { --input }` từ `0.5.0`; trước `3.3.3` mirror này còn\n * ghi #1E2128. */\n input: \"#434A5A\",\n ring: \"#F5D060\",\n /** = `input` (`--control-edge: var(--input)`). */\n controlEdge: \"#434A5A\",\n /** comitor-ds dùng `dark:hover:border-gray-500` = `ash[500]` (lật chiều so với light). */\n controlEdgeStrong: ash[500],\n /** comitor-ds tô `dark:data-[state=checked]:bg-gold-300`, nhưng token `primary` của chính nó\n * ở dark là #F5D060; gói theo token, nên control BẬT ở dark là bậc sáng. */\n controlOn: \"#F5D060\",\n /** TRẮNG ở cả hai theme, đúng `bg-white` của comitor-ds. */\n controlKnob: \"#FFFFFF\",\n /** comitor-ds vẽ dấu check bằng `text-ink` ở CẢ hai theme. */\n controlCheck: brandNeutral.ink,\n switchTrack: ash[700],\n sliderTrack: \"#1E2128\",\n linkInk: gold[400],\n linkInkHover: gold[300],\n sidebar: \"#16181D\",\n sidebarForeground: \"#F7F7F8\",\n sidebarBorder: \"#1E2128\",\n sidebarAccent: \"#1E2128\",\n sidebarAccentForeground: \"#F7F7F8\"\n }\n} as const;\n\n/**\n * BẢNG MÀU THỨ HAI — TƯƠNG PHẢN CAO. Mirror khối `[data-contrast=\"high\"]` /\n * `.dark[data-contrast=\"high\"]` trong styles.css.\n *\n * Chỉ chứa những token KHÁC bảng mặc định, đúng như CSS: hợp lên trên để có bảng đầy đủ.\n *\n * ```ts\n * const mau = { ...semantic.light, ...highContrast.light };\n * ```\n *\n * Bảng mặc định là comitor-ds (bản duyệt, có những cặp dưới ngưỡng — đã liệt kê ở từng token).\n * Bảng này là bảng đã đo, do NGƯỜI DÙNG bật qua `<ContrastProvider>` + `<ContrastToggle>`.\n * `accent` là màu phụ bản sáng dành cho nền tối — bảng mặc định không có, xem `accent.dark`.\n */\nexport const highContrast = {\n light: {\n /** 5,23:1 trên `background` · 4,89:1 trên `muted` (bảng mặc định: 3,83 / 3,58) */\n mutedForeground: \"#686C7A\",\n /** Chữ trắng trên nền này được 5,03:1 (bảng mặc định #D64545 chỉ 4,38:1) */\n destructive: \"#C93B3B\",\n /** = color-mix(destructive, ink 15%) — ĐẬM thêm khi hover, ngược chiều bảng mặc định. */\n destructiveHover: \"#AD3535\",\n /** 7,15:1 trên nền trắng (bảng mặc định dùng chính #D64545, 4,38:1) */\n destructiveInk: accentInk.light.red,\n /** 3,83:1 trên nền trắng (bảng mặc định #EDEEF1 chỉ 1,16:1) */\n input: \"#7E8290\",\n controlEdge: \"#7E8290\",\n controlEdgeStrong: ash[700],\n /** #B48700 = gold-400 tối đi 2%: 3,28:1 trên `background`/`card` · 3,06:1 trên `muted` · ⇄ núm\n * trắng 3,28:1 (bảng mặc định: 1,86 / 1,86). Trước `3.3.3` mirror này còn ghi gold-500 — giá trị\n * `styles.css` đã bỏ. */\n controlOn: \"#B48700\",\n /** = `background`, nên luôn tương phản đúng chiều với cả rãnh BẬT lẫn rãnh TẮT. */\n controlKnob: \"#FFFFFF\",\n /** MỰC trên nền gold (5,77:1) — cùng cách vẽ với bảng mặc định; trước `3.3.3` mirror này ghi trắng. */\n controlCheck: brandNeutral.ink,\n switchTrack: \"#7E8290\",\n sliderTrack: \"#7E8290\",\n primaryInk: gold[500],\n linkInk: gold[500],\n linkInkHover: gold[600],\n /** = `--orange-ink` của khối này — bậc ink màu phụ DUY NHẤT mà bảng này đổi ở light:\n * color-mix(srgb, orange 65%, ink 35%), 6,66:1 trắng · 5,55:1 trên orange/15 (bảng mặc định\n * #AC440F: 5,85 / 4,87:1). */\n severeInk: \"#9D3F10\",\n accent: accent.light\n },\n dark: {\n /** 5,70:1 trên `background` · 5,35:1 trên `card` · 4,86:1 trên `muted` */\n mutedForeground: \"#888D9B\",\n destructive: \"#C93B3B\",\n destructiveHover: \"#AD3535\",\n destructiveInk: \"#F87171\",\n /** Ghim một giá trị cho cả hai theme — 4,94:1 trên `background` */\n input: \"#7E8290\",\n controlEdge: \"#7E8290\",\n controlEdgeStrong: ash[300],\n /** 12,67:1 với nền trang VÀ với `controlKnob` (#0E1116) */\n controlOn: \"#F5D060\",\n controlKnob: \"#0E1116\",\n controlCheck: \"#0E1116\",\n switchTrack: \"#7E8290\",\n sliderTrack: \"#7E8290\",\n primaryInk: \"#F5D060\",\n linkInk: gold[200],\n linkInkHover: gold[100],\n /** Bằng bảng mặc định (8,36:1 trên `background` · 6,09:1 trên orange/15) — khai cho đủ cặp\n * với `light`, vì CSS phải huỷ bậc tối của khối light ở đây. */\n severeInk: accent.dark.orange,\n /** Màu phụ bản SÁNG cho nền tối — bảng mặc định không có bản dark cho nhóm này (trừ `orange`,\n * vốn đã sáng ở `accent.dark`). */\n accent: {\n navy: \"#0F1E3D\",\n teal: \"#2DD4BF\",\n red: \"#F87171\",\n green: \"#4ADE80\",\n yellow: \"#FACC15\",\n orange: accent.dark.orange\n }\n }\n} as const;\n\n/**\n * Alias trạng thái — success/warning/severe/info trỏ vào màu phụ, destructive giữ nguyên 2 chế độ.\n * `severe` (cam) là nấc giữa warning và destructive: ưu tiên cao, SLA sắp trễ.\n *\n * Ba lớp KHÁC nhau, đừng gộp:\n * • `destructive` — màu NỀN (nút xoá): #D64545 ở bảng mặc định (chữ trắng 4,38:1, dưới AA và đã\n * chấp nhận), #C93B3B ở bảng tương phản cao (5,03:1 ✓);\n * • `destructiveForeground` (#FFFFFF) — chữ NẰM TRÊN nền đó;\n * • `destructiveInk` — màu CHỮ đỏ trên nền trang (dòng lỗi). Bảng mặc định cho nó cùng hex với\n * nền (đúng `text-destructive` của comitor-ds); bảng tương phản cao tách thành bậc tối riêng.\n *\n * `successForeground` / `warningForeground` / `infoForeground` là MỰC ở CẢ hai theme (không phải\n * trắng ở light rồi lật ở dark như bản 0.1.0): ba nền trạng thái đều là sắc sáng.\n * success mực 6,04:1 (light) · 10,85:1 (dark) [trắng chỉ 3,13 / 1,74:1 ✗]\n * info mực 4,56:1 (light) · 10,16:1 (dark) [trắng chỉ 4,14 / 1,86:1 ✗]\n * warning mực 11,92:1 (light) · 12,35:1 (dark) [trắng chỉ 1,59 / 1,53:1 ✗]\n * severe mực 5,31:1 (light) · 8,36:1 (dark) [trắng chỉ 3,56 / 2,26:1 ✗]\n * ⚠ `info` ở light là cặp chặt nhất gói (4,56:1, dư 0,06) — đổi `accent.light.teal` là phải đo lại.\n */\nexport const status = {\n light: {\n success: accent.light.green,\n successForeground: \"#0E1116\",\n warning: accent.light.yellow,\n warningForeground: \"#0E1116\",\n severe: accent.light.orange,\n severeForeground: \"#0E1116\",\n info: accent.light.teal,\n infoForeground: \"#0E1116\",\n destructive: \"#D64545\",\n destructiveForeground: \"#FFFFFF\",\n successInk: accentInk.light.green,\n warningInk: accentInk.light.yellow,\n severeInk: accentInk.light.orange,\n infoInk: accentInk.light.teal,\n /** = `semantic.light.destructiveInk` (#D64545 — `--destructive-ink: var(--destructive)` ở bảng mặc\n * định). Trước `3.3.3` trỏ nhầm `accentInk.light.red` (#9A3537 — bậc của bảng TƯƠNG PHẢN CAO). */\n destructiveInk: semantic.light.destructiveInk\n },\n dark: {\n success: accent.dark.green,\n successForeground: \"#0E1116\",\n warning: accent.dark.yellow,\n warningForeground: \"#0E1116\",\n severe: accent.dark.orange,\n severeForeground: \"#0E1116\",\n info: accent.dark.teal,\n infoForeground: \"#0E1116\",\n destructive: \"#D64545\",\n destructiveForeground: \"#FFFFFF\",\n successInk: accentInk.dark.green,\n warningInk: accentInk.dark.yellow,\n severeInk: accentInk.dark.orange,\n infoInk: accentInk.dark.teal,\n destructiveInk: semantic.dark.destructiveInk\n }\n} as const;\n\n/**\n * Bảng màu biểu đồ (chart-1…chart-5). Thứ tự = thứ tự series mặc định.\n *\n * ⚠ `chart-1` ở dark là `gold[300]` như light: `styles.css` KHÔNG khai lại `--chart-1` trong `.dark`\n * (chỉ `--chart-3` tự sáng lên qua `--navy-ink`). Trước `3.3.3` mirror này ghi `gold[200]` — biểu\n * đồ canvas/email/PDF dựng từ `tokens` ra màu khác biểu đồ trên trang.\n */\nexport const chart = {\n light: [gold[300], accent.light.teal, accent.light.navy, accent.light.green, accent.light.red],\n dark: [gold[300], accent.dark.teal, accentInk.dark.navy, accent.dark.green, accent.dark.red]\n} as const;\n\n/**\n * Màu avatar chữ cái (hex đục) cho nơi KHÔNG có Tailwind: canvas, email HTML, PDF.\n *\n * ⚠ KHÔNG phải bảng màu thứ hai. Bảng tone DUY NHẤT của cả gói nằm ở `lib/avatar-tone.ts`, mỗi\n * tone mang cả chuỗi class (cho DOM) lẫn công thức hex tương ứng; ở đây chỉ nạp bảng màu vào công\n * thức đó. Web và email vì vậy dùng chung một thứ tự tone, một hàm băm, một cách chuẩn hoá tên —\n * không còn hai bảng để lệch nhau.\n */\nconst avatarTonePalette = {\n light: {\n gold,\n accent: accent.light,\n accentInk: accentInk.light,\n card: semantic.light.card\n },\n dark: {\n gold,\n accent: accent.dark,\n accentInk: accentInk.dark,\n card: semantic.dark.card\n }\n} satisfies Record<ThemeMode, AvatarTonePalette>;\n\n/**\n * Cặp nền/chữ (hex) của avatar cho một tên, theo theme.\n *\n * Tên hàm nói đúng thứ nó trả về: `...Colors` = hex (canvas/email/PDF), còn bản trả về chuỗi\n * class Tailwind cho DOM là `getAvatarToneClasses` ở `@comitor/ui`. Cùng một tên người luôn ra\n * cùng một tone ở cả hai đường vì chỉ số do MỘT hàm tính (`getAvatarToneIndex`).\n */\nexport function getAvatarToneColors(name: string, mode: ThemeMode = \"light\"): AvatarToneColors {\n // `noUncheckedIndexedAccess` bật → phải có nhánh lùi, dù chỉ số luôn nằm trong [0, 8).\n const tone = AVATAR_TONES[getAvatarToneIndex(name)] ?? AVATAR_TONES[0];\n return tone[mode](avatarTonePalette[mode]);\n}\n\nexport type { AvatarToneColors };\nexport { getAvatarToneIndex };\n\n/** Số đo khung ứng dụng — mirror --sidebar-width / --sidebar-width-icon / --header-height. */\nexport const layout = {\n /** px — --header-height 3.5rem */\n headerHeight: 56,\n /** px — --sidebar-width 16rem */\n sidebarWidth: 256,\n /** px — --sidebar-width-icon 3.5rem */\n sidebarCollapsedWidth: 56,\n /** px — --radius 0.375rem */\n radius: 6,\n breakpoints: { mobile: 768, tablet: 1024, desktop: 1280 },\n /** px — kích thước tối thiểu của vùng chạm trên mobile (WCAG 2.5.5) */\n minTapTarget: 44\n} as const;\n\n/** Font stack — app phải tự nạp Inter (subsets latin + vietnamese) và gán --font-inter. */\nexport const typography = {\n fontSans: '\"Inter\", system-ui, sans-serif',\n fontMono: '\"Geist Mono\", ui-monospace, monospace'\n} as const;\n\nexport type ThemeMode = \"light\" | \"dark\";\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
package/styles.css
CHANGED
|
@@ -137,8 +137,8 @@
|
|
|
137
137
|
app khác nhau trông như cùng một màu. Gốc lấy từ bảng màu web thông dụng: royal blue #4169E1 ·
|
|
138
138
|
một nấc giữa medium purple và blue violet · deep pink #FF1493. comitor-ds không có ba sắc này
|
|
139
139
|
→ màu ĐO của gói (như cam). Số đo bậc ink ở khối "Bậc INK" ngay dưới.
|
|
140
|
-
|
|
141
|
-
|
|
140
|
+
Bậc -foreground (chữ trên nền ĐẶC — accent khung app của module tô nền này) ở khối "Bậc
|
|
141
|
+
FOREGROUND" ngay dưới, từ 3.11.0. */
|
|
142
142
|
--blue: #4169e1;
|
|
143
143
|
--purple: #8a4fd8;
|
|
144
144
|
--pink: #ff1493;
|
|
@@ -237,6 +237,15 @@
|
|
|
237
237
|
--yellow-foreground: #0e1116;
|
|
238
238
|
--orange-foreground: #0e1116;
|
|
239
239
|
--navy-foreground: #ffffff;
|
|
240
|
+
/* Ba sắc định danh (3.11.0). Khác teal/green/yellow: blue và purple ở light là sắc VỪA-TỐI nên chữ
|
|
241
|
+
là TRẮNG; bản dark của chúng sáng nên .dark lật sang MỰC. Pink thì mực ở cả hai theme:
|
|
242
|
+
blue trắng 4,85:1 (light) [mực chỉ 3,90 ✗] · mực 7,99:1 (dark #82A8F5) [trắng 2,37 ✗]
|
|
243
|
+
purple trắng 5,01:1 (light) [mực chỉ 3,77 ✗] · mực 6,95:1 (dark #A78BFA) [trắng 2,72 ✗]
|
|
244
|
+
pink mực 5,20:1 (light) [trắng chỉ 3,64 ✗] · mực 7,14:1 (dark #FF69B4) [trắng 2,65 ✗]
|
|
245
|
+
⚠ Đổi --blue/--purple/--pink (ở :root hoặc .dark) là phải đo lại ba cặp này. */
|
|
246
|
+
--blue-foreground: #ffffff;
|
|
247
|
+
--purple-foreground: #ffffff;
|
|
248
|
+
--pink-foreground: #0e1116;
|
|
240
249
|
|
|
241
250
|
/* ─ Semantic shadcn ─ */
|
|
242
251
|
--background: #ffffff;
|
|
@@ -640,6 +649,10 @@
|
|
|
640
649
|
--blue-ink: var(--blue);
|
|
641
650
|
--purple-ink: var(--purple);
|
|
642
651
|
--pink-ink: var(--pink);
|
|
652
|
+
/* Bản dark của blue/purple sáng → chữ trên nền đặc lật sang MỰC (7,99 · 6,95:1; trắng chỉ 2,37 ·
|
|
653
|
+
2,72 ✗). Pink vốn là mực ở :root nên không khai lại. */
|
|
654
|
+
--blue-foreground: #0e1116;
|
|
655
|
+
--purple-foreground: #0e1116;
|
|
643
656
|
/* Accent app tách khỏi --primary-ink (xem :root): ở dark gold-500 chìm vào nền, nên bậc sáng
|
|
644
657
|
của brand gánh vai trò này. */
|
|
645
658
|
--app-accent-ink: var(--primary);
|
|
@@ -953,6 +966,9 @@
|
|
|
953
966
|
--color-green-foreground: var(--green-foreground);
|
|
954
967
|
--color-yellow-foreground: var(--yellow-foreground);
|
|
955
968
|
--color-orange-foreground: var(--orange-foreground);
|
|
969
|
+
--color-blue-foreground: var(--blue-foreground);
|
|
970
|
+
--color-purple-foreground: var(--purple-foreground);
|
|
971
|
+
--color-pink-foreground: var(--pink-foreground);
|
|
956
972
|
|
|
957
973
|
/* Trạng thái */
|
|
958
974
|
--color-success: var(--success);
|