@comitor/ui 0.1.0 → 0.2.1
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/README.md +303 -43
- package/dist/chart/chart.d.ts +35 -3
- package/dist/chart/chart.js.map +1 -1
- package/dist/chart/comitor-chart.d.ts +16 -3
- package/dist/chart/comitor-chart.js.map +1 -1
- package/dist/chart/index.d.ts +2 -2
- package/dist/components/composite/avatar-group.js +1 -1
- package/dist/components/composite/avatar-group.js.map +1 -1
- package/dist/components/composite/combobox.d.ts +9 -2
- package/dist/components/composite/combobox.js +9 -2
- package/dist/components/composite/combobox.js.map +1 -1
- package/dist/components/composite/confirm-dialog.js +1 -1
- package/dist/components/composite/confirm-dialog.js.map +1 -1
- package/dist/components/composite/copy-button.js +1 -1
- package/dist/components/composite/copy-button.js.map +1 -1
- package/dist/components/composite/data-table.d.ts +17 -8
- package/dist/components/composite/data-table.js +8 -8
- package/dist/components/composite/data-table.js.map +1 -1
- package/dist/components/composite/filter-chips.js +6 -5
- package/dist/components/composite/filter-chips.js.map +1 -1
- package/dist/components/composite/form-field.js +3 -1
- package/dist/components/composite/form-field.js.map +1 -1
- package/dist/components/composite/form-section.js +5 -5
- package/dist/components/composite/form-section.js.map +1 -1
- package/dist/components/composite/inline-edit.js +1 -1
- package/dist/components/composite/inline-edit.js.map +1 -1
- package/dist/components/composite/keyboard-hint.d.ts +16 -0
- package/dist/components/composite/keyboard-hint.js +8 -6
- package/dist/components/composite/keyboard-hint.js.map +1 -1
- package/dist/components/composite/letter-avatar.d.ts +10 -18
- package/dist/components/composite/letter-avatar.js +6 -17
- package/dist/components/composite/letter-avatar.js.map +1 -1
- package/dist/components/composite/search-filter-bar.d.ts +41 -3
- package/dist/components/composite/search-filter-bar.js +27 -15
- package/dist/components/composite/search-filter-bar.js.map +1 -1
- package/dist/components/composite/stat-card.d.ts +16 -1
- package/dist/components/composite/stat-card.js +16 -9
- package/dist/components/composite/stat-card.js.map +1 -1
- package/dist/components/composite/status-config.d.ts +69 -16
- package/dist/components/composite/status-config.js +10 -10
- package/dist/components/composite/status-config.js.map +1 -1
- package/dist/components/composite/status-pill.js +5 -1
- package/dist/components/composite/status-pill.js.map +1 -1
- package/dist/components/composite/table-pagination.d.ts +46 -4
- package/dist/components/composite/table-pagination.js +27 -24
- package/dist/components/composite/table-pagination.js.map +1 -1
- package/dist/components/composite/text-utils.d.ts +12 -11
- package/dist/components/composite/text-utils.js +7 -16
- package/dist/components/composite/text-utils.js.map +1 -1
- package/dist/components/composite/toast.js +16 -6
- package/dist/components/composite/toast.js.map +1 -1
- package/dist/components/ui/alert.js +1 -1
- package/dist/components/ui/alert.js.map +1 -1
- package/dist/components/ui/badge.js +4 -4
- package/dist/components/ui/badge.js.map +1 -1
- package/dist/components/ui/button.js +3 -3
- package/dist/components/ui/button.js.map +1 -1
- package/dist/components/ui/calendar.d.ts +41 -0
- package/dist/components/ui/calendar.js +1 -1
- package/dist/components/ui/calendar.js.map +1 -1
- package/dist/components/ui/card.d.ts +35 -2
- package/dist/components/ui/card.js +17 -3
- package/dist/components/ui/card.js.map +1 -1
- package/dist/components/ui/checkbox.js +3 -3
- package/dist/components/ui/checkbox.js.map +1 -1
- package/dist/components/ui/command.js +1 -1
- package/dist/components/ui/command.js.map +1 -1
- package/dist/components/ui/context-menu.js +1 -1
- package/dist/components/ui/context-menu.js.map +1 -1
- package/dist/components/ui/drawer.js +1 -1
- package/dist/components/ui/drawer.js.map +1 -1
- package/dist/components/ui/dropdown-menu.js +1 -1
- package/dist/components/ui/dropdown-menu.js.map +1 -1
- package/dist/components/ui/empty.js +1 -1
- package/dist/components/ui/empty.js.map +1 -1
- package/dist/components/ui/field.js +4 -4
- package/dist/components/ui/field.js.map +1 -1
- package/dist/components/ui/form.js +8 -2
- package/dist/components/ui/form.js.map +1 -1
- package/dist/components/ui/input-group.js +3 -3
- package/dist/components/ui/input-group.js.map +1 -1
- package/dist/components/ui/input-otp.js +1 -1
- package/dist/components/ui/input-otp.js.map +1 -1
- package/dist/components/ui/input.js +3 -3
- package/dist/components/ui/input.js.map +1 -1
- package/dist/components/ui/item.js +1 -1
- package/dist/components/ui/item.js.map +1 -1
- package/dist/components/ui/menubar.js +1 -1
- package/dist/components/ui/menubar.js.map +1 -1
- package/dist/components/ui/navigation-menu.js +1 -1
- package/dist/components/ui/navigation-menu.js.map +1 -1
- package/dist/components/ui/pagination.js +1 -1
- package/dist/components/ui/pagination.js.map +1 -1
- package/dist/components/ui/progress.d.ts +26 -0
- package/dist/components/ui/progress.js +5 -2
- package/dist/components/ui/progress.js.map +1 -1
- package/dist/components/ui/radio-group.js +2 -2
- package/dist/components/ui/radio-group.js.map +1 -1
- package/dist/components/ui/resizable.d.ts +14 -0
- package/dist/components/ui/resizable.js +2 -2
- package/dist/components/ui/resizable.js.map +1 -1
- package/dist/components/ui/scroll-area.js +1 -1
- package/dist/components/ui/scroll-area.js.map +1 -1
- package/dist/components/ui/select.d.ts +33 -0
- package/dist/components/ui/select.js +3 -3
- package/dist/components/ui/select.js.map +1 -1
- package/dist/components/ui/slider.d.ts +19 -0
- package/dist/components/ui/slider.js +3 -3
- package/dist/components/ui/slider.js.map +1 -1
- package/dist/components/ui/sonner.d.ts +21 -3
- package/dist/components/ui/sonner.js +1 -2
- package/dist/components/ui/sonner.js.map +1 -1
- package/dist/components/ui/switch.js +2 -2
- package/dist/components/ui/switch.js.map +1 -1
- package/dist/components/ui/table.d.ts +16 -0
- package/dist/components/ui/table.js +1 -1
- package/dist/components/ui/table.js.map +1 -1
- package/dist/components/ui/tabs.js +1 -1
- package/dist/components/ui/tabs.js.map +1 -1
- package/dist/components/ui/textarea.js +1 -1
- package/dist/components/ui/textarea.js.map +1 -1
- package/dist/components/ui/toggle.js +1 -1
- package/dist/components/ui/toggle.js.map +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.js +17 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/avatar-tone.d.ts +126 -0
- package/dist/lib/avatar-tone.js +68 -0
- package/dist/lib/avatar-tone.js.map +1 -0
- package/dist/shell/app-header.js +12 -2
- package/dist/shell/app-header.js.map +1 -1
- package/dist/shell/app-launcher.js +2 -2
- package/dist/shell/app-launcher.js.map +1 -1
- package/dist/shell/app-shell.d.ts +2 -0
- package/dist/shell/index.d.ts +3 -3
- package/dist/shell/shell-context.d.ts +29 -8
- package/dist/shell/shell-context.js +6 -20
- package/dist/shell/shell-context.js.map +1 -1
- package/dist/shell/sidebar-group.js +5 -3
- package/dist/shell/sidebar-group.js.map +1 -1
- package/dist/shell/sidebar-menu-item.js +15 -7
- package/dist/shell/sidebar-menu-item.js.map +1 -1
- package/dist/shell/types.d.ts +17 -1
- package/dist/shell/types.js.map +1 -1
- package/dist/shell/user-menu.js +3 -2
- package/dist/shell/user-menu.js.map +1 -1
- package/dist/shell/utils.d.ts +36 -24
- package/dist/shell/utils.js +55 -20
- package/dist/shell/utils.js.map +1 -1
- package/dist/shell/workspace-switcher.js +23 -8
- package/dist/shell/workspace-switcher.js.map +1 -1
- package/dist/tokens.d.ts +165 -17
- package/dist/tokens.js +122 -25
- package/dist/tokens.js.map +1 -1
- package/package.json +8 -5
- package/styles.css +312 -22
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\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ụ. Bản `dark` được nâng sáng để đạt tương phản AA khi làm CHỮ trên nền #0E1116 —\n * bản `light` quá tối cho việc đó.\n */\nexport const accent = {\n light: {\n navy: \"#0F1E3D\",\n teal: \"#1F8A8B\",\n red: \"#D64545\",\n green: \"#2EA56A\",\n yellow: \"#F2C94C\"\n },\n dark: {\n navy: \"#0F1E3D\",\n teal: \"#2DD4BF\",\n red: \"#F87171\",\n green: \"#4ADE80\",\n yellow: \"#FACC15\"\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 secondary: \"#EDE8D8\",\n secondaryForeground: \"#0E1116\",\n muted: \"#F7F7F8\",\n mutedForeground: \"#7E8290\",\n accent: \"#FBF5DC\",\n accentForeground: \"#0E1116\",\n destructive: \"#D64545\",\n destructiveForeground: \"#FFFFFF\",\n border: \"#EDEEF1\",\n input: \"#EDEEF1\",\n ring: \"#E8B824\",\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 secondary: \"#1E2128\",\n secondaryForeground: \"#F7F7F8\",\n muted: \"#1E2128\",\n mutedForeground: \"#7E8290\",\n accent: \"#1E2128\",\n accentForeground: \"#F7F7F8\",\n destructive: \"#D64545\",\n destructiveForeground: \"#FFFFFF\",\n border: \"#1E2128\",\n input: \"#1E2128\",\n ring: \"#F5D060\",\n sidebar: \"#16181D\",\n sidebarForeground: \"#F7F7F8\",\n sidebarBorder: \"#1E2128\",\n sidebarAccent: \"#1E2128\",\n sidebarAccentForeground: \"#F7F7F8\"\n }\n} as const;\n\n/** Alias trạng thái — success/warning/info trỏ vào màu phụ, destructive giữ nguyên 2 chế độ. */\nexport const status = {\n light: {\n success: accent.light.green,\n successForeground: \"#FFFFFF\",\n warning: accent.light.yellow,\n warningForeground: \"#0E1116\",\n info: accent.light.teal,\n infoForeground: \"#FFFFFF\",\n destructive: \"#D64545\",\n destructiveForeground: \"#FFFFFF\"\n },\n dark: {\n success: accent.dark.green,\n successForeground: \"#0E1116\",\n warning: accent.dark.yellow,\n warningForeground: \"#0E1116\",\n info: accent.dark.teal,\n infoForeground: \"#0E1116\",\n destructive: \"#D64545\",\n destructiveForeground: \"#FFFFFF\"\n }\n} as const;\n\n/** Bảng màu biểu đồ (chart-1…chart-5). Thứ tự = thứ tự series mặc định. */\nexport const chart = {\n light: [gold[300], accent.light.teal, accent.light.navy, accent.light.green, accent.light.red],\n dark: [gold[200], accent.dark.teal, \"#7EA2E8\", accent.dark.green, accent.dark.red]\n} as const;\n\n/** Màu avatar chữ cái — chọn deterministic theo tên, lấy từ token nên luôn hợp tông brand. */\nexport const avatarPalette = [\n gold[400],\n gold[600],\n accent.light.teal,\n accent.light.navy,\n accent.light.green,\n accent.light.red,\n ash[500],\n brandNeutral.inkSoft\n] as const;\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 pageSizes: [20, 50, 100],\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 },\n dark: {\n navy: \"#0F1E3D\",\n teal: \"#2DD4BF\",\n red: \"#F87171\",\n green: \"#4ADE80\",\n yellow: \"#FACC15\"\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 * `--navy-ink`. Đây là màu phụ khi nó làm **CHỮ/ICON/VIỀN trên nền trang** (hoặc trên tint\n * /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` vì bên đó màu phụ\n * đã đủ sáng để làm chữ. Đo trên tint 15% của chính nó — nền thật mà chữ initials avatar và\n * badge tonal đứng lên: teal 5,54 · green 5,60 · red 5,83:1 ở light (bản chưa có ink: 3,44 /\n * 2,69 / 3,57:1 ✗); 7,12 / 7,44 / 5,18:1 ở 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 },\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 }\n} as const;\n\n/**\n * Bậc FOREGROUND của màu phụ — mirror `--teal-foreground` / `--green-foreground` /\n * `--yellow-foreground` / `--navy-foreground`. Đây là chữ/icon nằm **TRÊN nền `accent.*` ĐẶC**,\n * tức vai trò thứ ba, khác hẳn `accentInk` (màu đó đi lên nền TRANG).\n *\n * Cả ba sắc teal/green/yellow đều sáng ở light và còn sáng hơn ở dark → chữ là MỰC ở cả hai\n * 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. Navy thì ngược lại — chữ TRẮNG, 16,49:1 ở cả hai theme.\n *\n * ⚠ CỐ Ý KHÔNG có `red`: nền `accent.light.red` #D64545 đặc không mang được chữ ở light — trắng\n * 4,38:1 ✗ và mực 4,32:1 ✗. Nền đỏ đặc có chữ phải dùng `status.*.destructive` (#C93B3B) cùng\n * `destructiveForeground` = 5,03:1 ✓. `accent.*.red` chỉ để tô TINT và làm màu chart.\n */\nexport const accentForeground = {\n light: {\n navy: \"#FFFFFF\",\n teal: \"#0E1116\",\n green: \"#0E1116\",\n yellow: \"#0E1116\"\n },\n dark: {\n navy: \"#FFFFFF\",\n teal: \"#0E1116\",\n green: \"#0E1116\",\n yellow: \"#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 — gold khi phải ĐI LÊN nền trang (rãnh Switch khi BẬT, ô Checkbox đã\n * chọn). `primary` #E8B824 chỉ 1,86:1 với nền trắng ✗; bậc này = `gold[500]` = 6,51:1 ✓. */\n primaryInk: gold[500],\n secondary: \"#EDE8D8\",\n secondaryForeground: \"#0E1116\",\n muted: \"#F7F7F8\",\n /** Bậc chữ phụ — là CHỮ nên ngưỡng AA 4,5:1: 5,23:1 trên nền trắng, 4,89:1 trên `muted`.\n * KHÔNG còn bằng `ash[500]` (#7E8290 chỉ 3,83:1 ✗) — bậc bảng màu và token semantic tách nhau. */\n mutedForeground: \"#686C7A\",\n accent: \"#FBF5DC\",\n accentForeground: \"#0E1116\",\n destructive: \"#C93B3B\",\n destructiveForeground: \"#FFFFFF\",\n /** Chữ báo lỗi trên nền trang — KHÁC `destructive` (nền) và `destructiveForeground` (chữ\n * trắng trên nền đó). 7,15:1 trên nền trắng. */\n destructiveInk: accentInk.light.red,\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 /** = `controlEdge`: viền ô nhập là ranh giới nhận dạng control (1.4.11) → 3,83:1 ✓. */\n input: \"#7E8290\",\n ring: \"#E8B824\",\n /** Ranh giới của control lúc NGHỈ (viền Input, ô Checkbox chưa chọn, rãnh Switch khi TẮT) —\n * WCAG 1.4.11, ràng buộc HAI PHÍA. Một giá trị cho cả hai theme: 3,83:1 với nền trắng và\n * 3,83:1 với `controlKnob` (trắng ở light); 4,94:1 với nền dark và với knob #0E1116. */\n controlEdge: \"#7E8290\",\n /** Cùng vai trò nhưng ở HOVER/ACTIVE — phải ĐẬM hơn lúc nghỉ. = `ash[700]`, 10,55:1 trên nền\n * trắng · 9,86:1 trên `muted`. */\n controlEdgeStrong: ash[700],\n /** Nền control khi BẬT/ĐÃ CHỌN (rãnh Switch bật, ô Checkbox đã chọn, chấm Radio) = `primaryInk`.\n * Ràng buộc hai phía: 6,51:1 với nền trang VÀ 6,51:1 với `controlKnob` ở light. `primary`\n * #E8B824 chỉ 1,86:1 cả hai phía ✗. */\n controlOn: gold[500],\n /** Núm Switch / dấu check / chấm Radio — mang màu NỀN TRANG nên luôn tương phản đúng chiều\n * với cả rãnh BẬT (6,51:1) lẫn rãnh TẮT (3,83:1). KHÔNG dùng `primaryForeground` (mực): mực\n * trên `controlOn` #7A5800 chỉ 1,86:1 ✗. */\n controlKnob: \"#FFFFFF\",\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 /** Sáng hơn bản light: chữ phụ ở dark hay nằm trên `card`/`muted`. 5,35:1 trên `card`,\n * 4,86:1 trên `muted` (giá trị cũ #7E8290 chỉ 4,21:1 trên `muted` ✗). */\n mutedForeground: \"#888D9B\",\n accent: \"#1E2128\",\n accentForeground: \"#F7F7F8\",\n destructive: \"#C93B3B\",\n destructiveForeground: \"#FFFFFF\",\n /** 6,42:1 trên `card` — `destructive` dùng làm chữ ở dark chỉ được 3,53:1 ✗. */\n destructiveInk: accentInk.dark.red,\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 /** = `controlEdge`, ghim một giá trị cho cả hai theme — 4,94:1 trên `background` ✓. */\n input: \"#7E8290\",\n ring: \"#F5D060\",\n /** Ghim đúng giá trị của light — xem chú thích ở nhánh light. */\n controlEdge: \"#7E8290\",\n /** = `ash[300]` (lật chiều so với light): 11,65:1 trên `background` · 10,94:1 trên `card`. */\n controlEdgeStrong: ash[300],\n /** = `primaryInk` của dark; 12,67:1 với nền trang VÀ với `controlKnob` (#0E1116). */\n controlOn: \"#F5D060\",\n /** = `background` của dark. Núm trắng cứng sẽ chỉ được 1,49:1 với rãnh BẬT ✗. */\n controlKnob: \"#0E1116\",\n sidebar: \"#16181D\",\n sidebarForeground: \"#F7F7F8\",\n sidebarBorder: \"#1E2128\",\n sidebarAccent: \"#1E2128\",\n sidebarAccentForeground: \"#F7F7F8\"\n }\n} as const;\n\n/**\n * Alias trạng thái — success/warning/info trỏ vào màu phụ, destructive giữ nguyên 2 chế độ.\n *\n * Ba lớp KHÁC nhau, đừng gộp:\n * • `destructive` (#C93B3B) — màu NỀN (nút xoá), đủ đậm cho chữ trắng ở cả hai theme (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). Dùng nhầm `destructive` cho vai\n * trò này thì ở dark chỉ được 3,53:1, dùng `accent.light.red` thì ở light chỉ 4,38:1.\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 * ⚠ `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 info: accent.light.teal,\n infoForeground: \"#0E1116\",\n destructive: \"#C93B3B\",\n destructiveForeground: \"#FFFFFF\",\n successInk: accentInk.light.green,\n warningInk: accentInk.light.yellow,\n infoInk: accentInk.light.teal,\n destructiveInk: accentInk.light.red\n },\n dark: {\n success: accent.dark.green,\n successForeground: \"#0E1116\",\n warning: accent.dark.yellow,\n warningForeground: \"#0E1116\",\n info: accent.dark.teal,\n infoForeground: \"#0E1116\",\n destructive: \"#C93B3B\",\n destructiveForeground: \"#FFFFFF\",\n successInk: accentInk.dark.green,\n warningInk: accentInk.dark.yellow,\n infoInk: accentInk.dark.teal,\n destructiveInk: accentInk.dark.red\n }\n} as const;\n\n/** Bảng màu biểu đồ (chart-1…chart-5). Thứ tự = thứ tự series mặc định. */\nexport const chart = {\n light: [gold[300], accent.light.teal, accent.light.navy, accent.light.green, accent.light.red],\n dark: [gold[200], 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 ash,\n brandNeutral,\n accent: accent.light,\n accentInk: accentInk.light,\n card: semantic.light.card\n },\n dark: {\n gold,\n ash,\n brandNeutral,\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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@comitor/ui",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Comitor design system — Shadcn/UI tùy biến theo brand Comitor + component dùng chung cho hệ sinh thái SaaS (Tasks, Chat, CRM, HR…)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -62,9 +62,12 @@
|
|
|
62
62
|
"typecheck": "tsc --noEmit",
|
|
63
63
|
"lint": "biome check .",
|
|
64
64
|
"lint:fix": "biome check --write .",
|
|
65
|
+
"lint:a11y": "node scripts/check-contrast.mjs",
|
|
66
|
+
"test:cong-a11y": "node scripts/a11y/tu-kiem.mjs",
|
|
67
|
+
"test:nav-active": "node scripts/kiem-nav-active.mjs",
|
|
65
68
|
"verify:directives": "node scripts/verify-directives.mjs",
|
|
66
69
|
"check:exports": "publint --strict && attw --pack . --profile esm-only --exclude-entrypoints styles.css",
|
|
67
|
-
"prepublishOnly": "pnpm run typecheck && pnpm run build && pnpm run verify:directives"
|
|
70
|
+
"prepublishOnly": "pnpm run typecheck && pnpm run build && pnpm run verify:directives && pnpm run test:nav-active && pnpm run test:cong-a11y && pnpm run lint:a11y"
|
|
68
71
|
},
|
|
69
72
|
"peerDependencies": {
|
|
70
73
|
"lucide-react": ">=0.540.0",
|
|
@@ -73,7 +76,7 @@
|
|
|
73
76
|
"react": "^19.0.0",
|
|
74
77
|
"react-dom": "^19.0.0",
|
|
75
78
|
"react-hook-form": "^7.54.0",
|
|
76
|
-
"recharts": "
|
|
79
|
+
"recharts": ">=2.15.0",
|
|
77
80
|
"tailwindcss": "^4.1.0"
|
|
78
81
|
},
|
|
79
82
|
"peerDependenciesMeta": {
|
|
@@ -136,14 +139,14 @@
|
|
|
136
139
|
"@types/node": "^22.0.0",
|
|
137
140
|
"@types/react": "^19.0.0",
|
|
138
141
|
"@types/react-dom": "^19.0.0",
|
|
139
|
-
"lucide-react": "^
|
|
142
|
+
"lucide-react": "^1.33.0",
|
|
140
143
|
"next": "^16.0.0",
|
|
141
144
|
"next-themes": "^0.4.6",
|
|
142
145
|
"publint": "^0.3.21",
|
|
143
146
|
"react": "^19.0.0",
|
|
144
147
|
"react-dom": "^19.0.0",
|
|
145
148
|
"react-hook-form": "^7.54.1",
|
|
146
|
-
"recharts": "^
|
|
149
|
+
"recharts": "^3.10.1",
|
|
147
150
|
"tailwindcss": "^4.2.0",
|
|
148
151
|
"tsup": "^8.3.5",
|
|
149
152
|
"typescript": "^5.7.0"
|
package/styles.css
CHANGED
|
@@ -74,6 +74,69 @@
|
|
|
74
74
|
--green: #2ea56a;
|
|
75
75
|
--yellow: #f2c94c;
|
|
76
76
|
|
|
77
|
+
/* ─ HỢP ĐỒNG BA VAI TRÒ CỦA MỘT TONE MÀU — đọc tên là biết dùng ở đâu ─
|
|
78
|
+
|
|
79
|
+
Đây là hợp đồng đặt tên của cả gói, áp cho MỌI cặp màu (brand, trạng thái, màu phụ, accent
|
|
80
|
+
app). Bảng "vai trò → token nên dùng" kèm ví dụ đúng/sai: docs/hop-dong-mau.md.
|
|
81
|
+
|
|
82
|
+
--x → tô NỀN (fill đặc, hoặc tint /5–/20). TUYỆT ĐỐI không làm màu chữ/icon.
|
|
83
|
+
--x-foreground → chữ/icon nằm TRÊN nền --x ĐẶC — đo tương phản với --x (ngưỡng 4,5:1).
|
|
84
|
+
--x-ink → CHÍNH màu đó khi đi lên nền TRANG (--background/--card/--popover/
|
|
85
|
+
--sidebar) hoặc lên tint /5–/20 của chính nó — đo với nền đó.
|
|
86
|
+
/20 là TRẦN CHUNG an toàn cho mọi tone ở cả hai theme; tone nào chịu
|
|
87
|
+
được đậm hơn thì xem bảng trần tint ở docs/hop-dong-mau.md §2 (chặt
|
|
88
|
+
nhất là đỏ ở dark: --red-ink trên red/25 chỉ 4,29:1 ✗).
|
|
89
|
+
|
|
90
|
+
Vì sao phải có bậc ink riêng thay vì dùng thẳng --teal/--green/--red/--yellow: bản light của
|
|
91
|
+
màu phụ được chọn để TÔ NỀN nên quá nhạt cho vai trò chữ. Đo trên tint 15% của chính nó (nền
|
|
92
|
+
thật mà chữ initials avatar và badge tonal đứng lên) — cột trái là cách dùng SAI:
|
|
93
|
+
|
|
94
|
+
teal #1F8A8B trên #DDEDEE = 3,44:1 ✗ → --teal-ink #1A6668 = 5,54:1 ✓
|
|
95
|
+
green #2EA56A trên #E0F2E9 = 2,69:1 ✗ → --green-ink #216A48 = 5,60:1 ✓
|
|
96
|
+
red #D64545 trên #F9E3E3 = 3,57:1 ✗ → --red-ink #9A3537 = 5,83:1 ✓
|
|
97
|
+
yellow #F2C94C trên #FDF7E4 = 1,48:1 ✗ → --yellow-ink #7A5800 = 6,08:1 ✓
|
|
98
|
+
|
|
99
|
+
Bậc ink phải đạt trên CẢ nền trắng lẫn nền tint — hai phép đo, không phải một. Trên các nền
|
|
100
|
+
trang của light (--background · --sidebar #FAFAF9 · --sidebar-accent #FBF5DC):
|
|
101
|
+
teal 6,68 · 6,39 · 6,10 | green 6,52 · 6,24 · 5,96
|
|
102
|
+
red 7,15 · 6,84 · 6,53 | yellow 6,51 · 6,24 · 5,96 ✓ đều ≥ 4,5:1
|
|
103
|
+
|
|
104
|
+
Hex ở đây là kết quả ĐÃ ĐÓNG BĂNG của color-mix(in srgb, --<màu>, --ink 30/40%) mà vòng
|
|
105
|
+
trước tính tay trong từng component. Đóng băng thành token có chủ ý: (a) một chỗ duy nhất
|
|
106
|
+
để đo lại khi đổi màu phụ, (b) component không phải nhắc lại chuỗi color-mix (dễ chép lệch
|
|
107
|
+
% giữa Badge/LetterAvatar/Input), (c) `tokens.ts` mirror được ra hex thật.
|
|
108
|
+
⚠ Đổi --teal/--green/--red/--yellow thì PHẢI tính lại bậc ink tương ứng — không tự theo.
|
|
109
|
+
|
|
110
|
+
Ở DARK không cần bậc ink riêng: khối .dark đã nâng chính --teal/--green/--red/--yellow lên
|
|
111
|
+
mức làm CHỮ được, nên bên đó --x-ink chỉ trỏ về var(--x). */
|
|
112
|
+
--teal-ink: #1a6668;
|
|
113
|
+
--green-ink: #216a48;
|
|
114
|
+
--red-ink: #9a3537;
|
|
115
|
+
/* Vàng không có bậc tối riêng: mượn thang gold (cùng sắc, đã đo) thay vì đẻ thêm một hex.
|
|
116
|
+
#7A5800 — nền trang 6,51:1 · yellow/15 6,08:1 · yellow/20 5,93:1 · yellow/25 5,82:1 ✓
|
|
117
|
+
(bản dark #FACC15 — nền trang 12,35:1 · yellow/15 trên --card 8,27:1 · yellow/25 6,23:1 ✓) */
|
|
118
|
+
--yellow-ink: var(--gold-500);
|
|
119
|
+
/* Navy vốn đã đủ tối ở light nên ink = chính nó; .dark mới cần bậc sáng (xem khối .dark). */
|
|
120
|
+
--navy-ink: var(--navy);
|
|
121
|
+
|
|
122
|
+
/* ─ Bậc FOREGROUND của màu phụ — chữ/icon nằm TRÊN nền --x ĐẶC ─
|
|
123
|
+
teal/green/yellow đều là sắc SÁNG-VỪA ở light và còn sáng hơn ở dark, nên chữ phải là MỰC
|
|
124
|
+
chứ không phải trắng; một giá trị chạy được cả hai theme (khối .dark không khai lại):
|
|
125
|
+
teal mực 4,56:1 (light) · 10,16:1 (dark) [trắng chỉ 4,14:1 ✗ — cặp CHẶT nhất gói]
|
|
126
|
+
green mực 6,04:1 (light) · 10,85:1 (dark) [trắng chỉ 3,13:1 ✗]
|
|
127
|
+
yellow mực 11,92:1 (light) · 12,35:1 (dark) [trắng chỉ 1,59:1 ✗]
|
|
128
|
+
navy TRẮNG 16,49:1 ở cả hai theme (.dark không override --navy)
|
|
129
|
+
|
|
130
|
+
⚠ CỐ Ý KHÔNG CÓ --red-foreground. Nền --red ĐẶC không mang được chữ ở light: #D64545 với
|
|
131
|
+
chữ trắng = 4,38:1 ✗ và với chữ mực = 4,32:1 ✗ — không màu chữ nào của brand cứu được cặp
|
|
132
|
+
đó. Nền đỏ đặc CÓ CHỮ phải đi qua --destructive (#C93B3B, ghim cả hai theme) cùng
|
|
133
|
+
--destructive-foreground = 5,03:1 ✓. --red chỉ để tô TINT (badge/avatar) và làm màu chart.
|
|
134
|
+
⚠ Đổi --teal/--green/--yellow/--navy thì phải đo lại bốn bậc này. */
|
|
135
|
+
--teal-foreground: #0e1116;
|
|
136
|
+
--green-foreground: #0e1116;
|
|
137
|
+
--yellow-foreground: #0e1116;
|
|
138
|
+
--navy-foreground: #ffffff;
|
|
139
|
+
|
|
77
140
|
/* ─ Semantic shadcn ─ */
|
|
78
141
|
--background: #ffffff;
|
|
79
142
|
--foreground: #0e1116;
|
|
@@ -83,18 +146,127 @@
|
|
|
83
146
|
--popover-foreground: #0e1116;
|
|
84
147
|
--primary: #e8b824;
|
|
85
148
|
--primary-foreground: #0e1116;
|
|
149
|
+
/* Bậc INK của brand — gold khi nó phải ĐI LÊN nền trang thay vì tô nền. Đúng hợp đồng ba vai
|
|
150
|
+
trò ở khối "Bậc INK của màu phụ" bên trên, chỉ khác ở chỗ thang gold đã có sẵn bậc tối nên
|
|
151
|
+
mượn `--gold-500` thay vì đẻ thêm hex.
|
|
152
|
+
Vì sao brand cũng cần bậc ink: gold `#E8B824` là màu SÁNG (L = 0,516) — tô nền thì tuyệt, làm
|
|
153
|
+
nền cho chi tiết nhỏ hoặc đứng trên nền trang thì chỉ được **1,86:1** so với nền trắng, dưới
|
|
154
|
+
xa 3:1 của WCAG 1.4.11. Đó là gốc bệnh chung của rãnh `Switch` khi BẬT và ô `Checkbox` đã
|
|
155
|
+
chọn — hai chỗ mà trạng thái BẬT/ĐÃ CHỌN là thông tin, không phải trang trí; vai trò đó nay
|
|
156
|
+
có tên riêng là `--control-on` (khai ở khối RANH GIỚI bên dưới) và trỏ về chính biến này.
|
|
157
|
+
light `--gold-500` #7A5800 : 6,51:1 trên --background · 6,09:1 trên --muted ✓
|
|
158
|
+
dark = chính --primary (#F5D060, gold-200) : 12,67:1 trên --background ✓
|
|
159
|
+
Ở dark không cần bậc riêng vì bảng màu dark đã nâng --primary lên bậc sáng — cùng lối với
|
|
160
|
+
--teal-ink/--green-ink.
|
|
161
|
+
⚠ App ghi đè --primary thì PHẢI ghi đè kèm --primary-ink (một bậc đủ tối cho light), y như
|
|
162
|
+
hợp đồng của --app-accent. */
|
|
163
|
+
--primary-ink: var(--gold-500);
|
|
86
164
|
--secondary: #ede8d8;
|
|
87
165
|
--secondary-foreground: #0e1116;
|
|
88
166
|
--muted: #f7f7f8;
|
|
89
|
-
|
|
167
|
+
/* Bậc chữ phụ (placeholder, mô tả, nhãn trục biểu đồ) — là CHỮ nên ngưỡng là AA 4,5:1,
|
|
168
|
+
không phải 3:1. Trước đây lấy thẳng --ash-500 #7E8290 và hỏng ở khắp nơi: 3,83:1 trên
|
|
169
|
+
nền trắng, 3,58:1 trên --muted. Nay tách khỏi thang ash (ash-500 vẫn là #7E8290 — bậc
|
|
170
|
+
bảng màu là dữ kiện trung tính, token semantic mới là chỗ mang hợp đồng tương phản):
|
|
171
|
+
#686C7A = 5,23:1 trên --background · 4,89:1 trên --muted · 5,01:1 trên --sidebar. */
|
|
172
|
+
--muted-foreground: #686c7a;
|
|
90
173
|
--accent: #fbf5dc;
|
|
91
174
|
--accent-foreground: #0e1116;
|
|
92
|
-
|
|
175
|
+
/* Đỏ dùng làm NỀN (nút xoá, chấm huy hiệu): ghim MỘT giá trị cho cả hai theme vì chữ trắng
|
|
176
|
+
trên nó phải đọc được ở cả hai. Đậm hơn --red một bậc là bắt buộc, không phải thẩm mỹ:
|
|
177
|
+
#D64545 + chữ trắng chỉ được 4,38:1 — thiếu 0,12 so với AA 4,5:1; #C93B3B đạt 5,03:1.
|
|
178
|
+
ĐỪNG đồng nhất với --red (#D64545 ở light, #F87171 ở dark): --red là màu TÔ (tint của Badge,
|
|
179
|
+
chart-5), còn màu để làm CHỮ đỏ là --red-ink / --destructive-ink ngay dưới. */
|
|
180
|
+
--destructive: #c93b3b;
|
|
93
181
|
--destructive-foreground: #ffffff;
|
|
182
|
+
/* Chữ báo lỗi trên nền TRANG (helper text của Input, dòng lỗi của Form) — KHÁC hai biến ngay
|
|
183
|
+
trên: --destructive là NỀN, --destructive-foreground là chữ TRẮNG nằm trên nền đó. Dùng
|
|
184
|
+
nhầm một trong hai thì mỗi bên hỏng một nửa: --destructive qua được light (5,03:1) nhưng chỉ
|
|
185
|
+
3,53:1 trên --card ở dark ✗, --red thì ngược lại — 4,38:1 ở light ✗. Đi qua bậc ink mới đạt
|
|
186
|
+
cả hai: 7,15:1 (light, trên --background) và 6,42:1 (dark, trên --card). Trên tint của chính
|
|
187
|
+
nó cũng đạt: destructive/10 6,18 · /15 5,76 · /20 5,33:1 (light) · 5,94 · 5,71 · 5,42:1
|
|
188
|
+
(dark, tint hợp lên --card). Alias vào --red-ink theo đúng lối --success: var(--green) — đỏ
|
|
189
|
+
báo lỗi và đỏ định danh là cùng một sắc. */
|
|
190
|
+
--destructive-ink: var(--red-ink);
|
|
191
|
+
/* ─ RANH GIỚI: HAI VAI TRÒ, HAI TOKEN, TUYỆT ĐỐI ĐỪNG LẪN ─
|
|
192
|
+
|
|
193
|
+
--border (+ --sidebar-border) VIỀN TRANG TRÍ. Đường kẻ chia, khung Card lúc nghỉ, vạch
|
|
194
|
+
ngăn nhóm menu, đường kẻ bảng — thứ chỉ tách khối cho mắt dễ quét, KHÔNG mang thông
|
|
195
|
+
tin. Cố ý để nhạt và KHÔNG chịu ngưỡng nào: WCAG 1.4.11 chỉ đòi tương phản cho thông
|
|
196
|
+
tin thị giác cần để NHẬN RA thành phần và trạng thái của nó. Số thật, ghi ra để không
|
|
197
|
+
ai tưởng là lỗi bỏ sót:
|
|
198
|
+
light #EDEEF1 trên trắng = 1,16:1 · dark #1E2128 trên #0E1116 = 1,17:1
|
|
199
|
+
dark #1E2128 trên --card #16181D = 1,10:1 · --sidebar-border/--sidebar = 1,11:1
|
|
200
|
+
|
|
201
|
+
--control-edge (+ alias --input) VIỀN CONTROL, ngưỡng 3:1. Khi nền control trong suốt
|
|
202
|
+
thì VIỀN là thứ duy nhất khoanh vùng nó → viền chính là "thông tin cần để nhận ra
|
|
203
|
+
thành phần". Dùng cho: Input/Textarea/Select/Combobox, ô Checkbox chưa chọn, viền
|
|
204
|
+
Radio, rãnh Switch khi TẮT, rãnh Slider, Button variant outline/secondary, Badge
|
|
205
|
+
variant outline, ô tìm của WorkspaceSwitcher.
|
|
206
|
+
|
|
207
|
+
--control-edge-strong Cùng vai trò nhưng ở HOVER/ACTIVE: phải ĐẬM HƠN lúc nghỉ, không
|
|
208
|
+
nhạt đi. Lỗi kinh điển đang có trong repo là `hover:border-ash-300` ở light — 1,62:1,
|
|
209
|
+
tức hover làm control gần như BIẾN MẤT.
|
|
210
|
+
|
|
211
|
+
⚠ `@layer base { * { @apply border-border } }` ở mục 7 gán viền TRANG TRÍ cho MỌI phần tử.
|
|
212
|
+
Control nào chỉ viết `border` mà không nêu màu sẽ lặng lẽ nhận bậc 1,16:1 — luôn phải nêu
|
|
213
|
+
`border-input` / `border-control-edge` tường minh. Bảng đầy đủ: docs/hop-dong-mau.md §3. */
|
|
94
214
|
--border: #edeef1;
|
|
95
|
-
--
|
|
215
|
+
/* Viền ô nhập = ranh giới nhận dạng control theo 1.4.11 → phải là --control-edge (khai bên
|
|
216
|
+
dưới), không phải bậc trang trí. 3,83:1 (light, nền trắng) · 4,94:1 (dark, nền #0E1116) ✓.
|
|
217
|
+
Vì --control-edge ghim một giá trị cho cả hai theme nên khối .dark không khai lại --input. */
|
|
218
|
+
--input: var(--control-edge);
|
|
96
219
|
--ring: #e8b824;
|
|
97
220
|
|
|
221
|
+
/* --control-edge #7E8290 — bậc xám DUY NHẤT được phép làm ranh giới control. Ràng buộc là HAI
|
|
222
|
+
PHÍA, nên phải là token riêng chứ không mượn --border (thuần trang trí) hay --muted-foreground
|
|
223
|
+
(token CHỮ: nó bị kéo tối/sáng theo nhu cầu đọc chữ — đã đổi một lần #7E8290 → #686C7A/#888D9B
|
|
224
|
+
— và khi đó ràng buộc phía núm không còn gì bảo vệ):
|
|
225
|
+
|
|
226
|
+
đủ TỐI để hiện trên nền trang → ≥3:1 với --background/--card/--muted/--secondary
|
|
227
|
+
đủ tương phản với NÚM / DẤU CHECK → ≥3:1 với --control-knob (= --background của theme)
|
|
228
|
+
|
|
229
|
+
MỘT giá trị chạy được cả hai theme nên không cần biến thể dark:
|
|
230
|
+
|
|
231
|
+
light: --background 3,83 · --card 3,83 · --muted 3,58 · --sidebar 3,67 · --accent 3,50
|
|
232
|
+
· --secondary 3,13 · núm (trắng) 3,83:1 ✓
|
|
233
|
+
dark : --background 4,94 · --card 4,64 · --popover 4,64 · --muted 4,21 · --sidebar 4,64
|
|
234
|
+
· núm (#0E1116) 4,94:1 ✓
|
|
235
|
+
|
|
236
|
+
⚠ Chú thích bản trước ghi "núm Switch tô trắng ở CẢ hai theme" và kèm dải L cho dark — SAI cả
|
|
237
|
+
hai: núm là `bg-background` nên ở dark nó là #0E1116 (xem --control-knob), tức ở dark ràng
|
|
238
|
+
buộc phía núm TRÙNG với ràng buộc phía nền, không có chặn trên nào. Bỏ hẳn dải L: đó đúng là
|
|
239
|
+
chỗ số đã sai một lần; giữ phép đo tương phản là đủ và không tự mâu thuẫn. */
|
|
240
|
+
--control-edge: #7e8290;
|
|
241
|
+
/* Hover/active của viền control. Light = --ash-700, dark = --ash-300 — đúng thứ `Checkbox` đang
|
|
242
|
+
viết tay bằng `border-ash-700 dark:border-ash-300`; token hoá để Input/Select/Combobox khỏi
|
|
243
|
+
chép lệch (Select hiện đang sai chiều: hover NHẠT hơn lúc nghỉ).
|
|
244
|
+
light #3A3F4A: --background 10,55 · --muted 9,86 · --sidebar 10,11 · --secondary 8,61:1 ✓
|
|
245
|
+
dark #C8CBD3: --background 11,65 · --card 10,94 · --muted 9,93:1 ✓ */
|
|
246
|
+
--control-edge-strong: var(--ash-700);
|
|
247
|
+
|
|
248
|
+
/* ─ CONTROL Ở TRẠNG THÁI BẬT / ĐÃ CHỌN ─
|
|
249
|
+
Rãnh `Switch` khi BẬT, ô `Checkbox` đã chọn, chấm `Radio`, đoạn đã chọn của `Slider`. Cũng
|
|
250
|
+
là WCAG 1.4.11 và cũng ràng buộc HAI PHÍA (nền trang · núm/dấu check), y hệt --control-edge.
|
|
251
|
+
|
|
252
|
+
KHÔNG dùng --primary cho vai trò này: gold #E8B824 chỉ 1,86:1 với nền trắng VÀ 1,86:1 với
|
|
253
|
+
núm trắng → BẬT/TẮT chỉ còn phân biệt được bằng VỊ TRÍ núm; ở dark rãnh #F5D060 nổi trên nền
|
|
254
|
+
(12,67:1) nhưng núm trắng lại chìm vào rãnh (1,49:1 ✗). --primary-ink đạt cả hai phía ở cả
|
|
255
|
+
hai theme, nên --control-on trỏ về nó:
|
|
256
|
+
light #7A5800: rãnh ⇄ nền trang 6,51:1 · núm ⇄ rãnh 6,51:1 ✓
|
|
257
|
+
dark #F5D060: rãnh ⇄ nền trang 12,67:1 · núm ⇄ rãnh 12,67:1 ✓
|
|
258
|
+
|
|
259
|
+
--control-knob là NÚM Switch / DẤU CHECK / chấm Radio, và cố ý mang màu NỀN TRANG (trắng ở
|
|
260
|
+
light, #0E1116 ở dark) — nhờ vậy nó luôn tương phản ĐÚNG CHIỀU với cả rãnh BẬT lẫn rãnh TẮT.
|
|
261
|
+
`bg-white` cứng chỉ đúng ở light. Bốn tổ hợp trạng thái × theme:
|
|
262
|
+
light BẬT 6,51 · light TẮT 3,83 · dark BẬT 12,67 · dark TẮT 4,94:1 ✓
|
|
263
|
+
⚠ KHÔNG dùng --primary-foreground cho núm/dấu check: nó là MỰC ở cả hai theme, mà mực trên
|
|
264
|
+
--control-on ở light (#7A5800) chỉ 1,86:1 ✗.
|
|
265
|
+
⚠ App ghi đè --primary PHẢI ghi đè kèm --primary-ink, nếu không control giữ nguyên gold mặc
|
|
266
|
+
định — sai màu mà không sai chuẩn, đúng thứ tự ưu tiên của repo. */
|
|
267
|
+
--control-on: var(--primary-ink);
|
|
268
|
+
--control-knob: var(--background);
|
|
269
|
+
|
|
98
270
|
--sidebar: #fafaf9;
|
|
99
271
|
--sidebar-foreground: #0e1116;
|
|
100
272
|
--sidebar-border: #edeef1;
|
|
@@ -106,24 +278,89 @@
|
|
|
106
278
|
Vì trỏ bằng var(), khối .dark bên dưới đổi --green/--yellow/--teal là
|
|
107
279
|
--success/--warning/--info tự sáng theo, không cần khai lại. */
|
|
108
280
|
--success: var(--green);
|
|
109
|
-
--success-foreground: #ffffff;
|
|
110
281
|
--warning: var(--yellow);
|
|
111
|
-
--warning-foreground: #0e1116;
|
|
112
282
|
--info: var(--teal);
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
283
|
+
/* Chữ/icon trên nền trạng thái ĐẶC. Cả ba sắc đều SÁNG ở light và còn sáng hơn ở dark → chữ là
|
|
284
|
+
MỰC ở CẢ hai theme, nên khối .dark không khai lại (trước đây success/info để #FFFFFF ở light
|
|
285
|
+
rồi lật về mực ở dark — bản light hỏng thẳng ngưỡng AA):
|
|
286
|
+
success mực 6,04:1 (light) · 10,85:1 (dark) [trắng chỉ 3,13:1 / 1,74:1 ✗]
|
|
287
|
+
info mực 4,56:1 (light) · 10,16:1 (dark) [trắng chỉ 4,14:1 / 1,86:1 ✗]
|
|
288
|
+
warning mực 11,92:1 (light) · 12,35:1 (dark) [trắng chỉ 1,59:1 / 1,53:1 ✗]
|
|
289
|
+
⚠ info ở light là cặp CHẶT nhất gói (4,56:1, dư 0,06). Đổi --teal thì phải đo lại ngay. */
|
|
290
|
+
--success-foreground: #0e1116;
|
|
291
|
+
--warning-foreground: #0e1116;
|
|
292
|
+
--info-foreground: #0e1116;
|
|
293
|
+
/* Bậc ink của trạng thái — chữ/icon trạng thái trên nền trang hoặc trên tint của chính nó
|
|
294
|
+
(Badge tonal, dòng lỗi, chip cảnh báo, icon Alert). Trỏ vào bậc ink của màu phụ đúng như
|
|
295
|
+
--success trỏ vào --green, nên .dark đổi màu phụ là cả hai tầng tự đúng theo.
|
|
296
|
+
Đo trên nền trang / trên tint 15% của chính nó — hai phép đo, phải đạt cả hai:
|
|
297
|
+
success #216A48 6,52 / 5,60:1 (light) · #4ADE80 10,85 / 7,44:1 (dark) ✓
|
|
298
|
+
warning #7A5800 6,51 / 6,08:1 (light) · #FACC15 12,35 / 8,27:1 (dark) ✓
|
|
299
|
+
info #1A6668 6,68 / 5,54:1 (light) · #2DD4BF 10,16 / 7,12:1 (dark) ✓ */
|
|
300
|
+
--success-ink: var(--green-ink);
|
|
301
|
+
--warning-ink: var(--yellow-ink);
|
|
302
|
+
--info-ink: var(--teal-ink);
|
|
303
|
+
|
|
304
|
+
/* ─ Bảng màu biểu đồ (đổi độc lập với brand) ─
|
|
305
|
+
⚠ CÒN TỒN, biết và cố ý chưa động tới trong vòng token này: các bậc chart là màu TÔ cho vùng
|
|
306
|
+
lớn (cột, cung tròn, vùng area) nên đứng trên nền trang chúng nhạt — chart-1 #E8B824 chỉ
|
|
307
|
+
1,86:1 · chart-4 #2EA56A 3,13:1 · chart-2 #1F8A8B 4,14:1 · chart-5 #D64545 4,38:1 với nền
|
|
308
|
+
trắng. Với vùng tô lớn kèm nhãn/chú giải văn bản thì WCAG không đòi 3:1 cho chính mảng màu,
|
|
309
|
+
nhưng NÉT MẢNH (đường line 1–2px, chấm điểm) thì có. Nếu vẽ nét mảnh, dùng bậc ink của cùng
|
|
310
|
+
sắc (--teal-ink/--green-ink/--red-ink/--primary-ink) chứ đừng dùng --chart-*; quyết định dứt
|
|
311
|
+
điểm cho tầng chart nằm ngoài phạm vi vòng này. */
|
|
116
312
|
--chart-1: var(--gold-300);
|
|
117
313
|
--chart-2: var(--teal);
|
|
118
|
-
|
|
314
|
+
/* Vai trò "navy" của biểu đồ đi qua bậc ink: nó là nét/khối phải NỔI trên nền trang, nên ở
|
|
315
|
+
dark tự lấy bậc sáng của navy mà không cần .dark khai lại --chart-3. */
|
|
316
|
+
--chart-3: var(--navy-ink);
|
|
119
317
|
--chart-4: var(--green);
|
|
120
318
|
--chart-5: var(--red);
|
|
121
319
|
|
|
122
320
|
/* ─ Accent theo từng app trong hệ sinh thái (Tasks/Chat/CRM/HR…) ─
|
|
123
|
-
|
|
124
|
-
|
|
321
|
+
|
|
322
|
+
⚠ HỢP ĐỒNG DÙNG — ba biến, ba vai trò, KHÔNG hoán đổi cho nhau.
|
|
323
|
+
|
|
324
|
+
--app-accent CHỈ tô NỀN (fill đặc `bg-app-accent`, hoặc tint `/5`–`/15`).
|
|
325
|
+
CẤM dùng làm màu chữ/icon/viền: `text-app-accent`,
|
|
326
|
+
`border-app-accent`, `fill-app-accent` là SAI theo định nghĩa.
|
|
327
|
+
--app-accent-foreground Chữ/icon nằm TRÊN nền `bg-app-accent` đặc.
|
|
328
|
+
--app-accent-ink Chính màu app khi nó phải ĐI LÊN nền trang: chữ, icon rời,
|
|
329
|
+
viền, vạch chỉ mục active, dấu check.
|
|
330
|
+
|
|
331
|
+
Vì sao phải tách chứ không cấm suông: một biến không thể vừa làm nền vừa làm chữ trên
|
|
332
|
+
chính cái nền đó — hai vai trò đòi hai hướng tương phản ngược nhau. Bằng chứng là chỗ tệ
|
|
333
|
+
nhất của 0.1.0, `AppLauncher` với `bg-app-accent/15 text-app-accent`: accent mặc định
|
|
334
|
+
= --primary = gold #E8B824, icon vàng trên nền vàng 15% (đã trộn ra #FCF4DE) chỉ được
|
|
335
|
+
1,69:1 — dưới xa 3:1 mà WCAG 1.4.11 đòi cho icon. Không có giá trị gold nào cứu được
|
|
336
|
+
cặp đó, vì nền càng đúng brand thì chữ cùng màu càng chìm.
|
|
337
|
+
|
|
338
|
+
Số đo của bộ mặc định (accent = gold):
|
|
339
|
+
nền đặc + --app-accent-foreground : 10,19:1 (light) · 12,67:1 (dark) ✓ AA
|
|
340
|
+
--app-accent-ink trên nền trang : 6,51:1 trên --background, 6,24:1 trên --sidebar,
|
|
341
|
+
5,96:1 trên --sidebar-accent, 6,14:1 trên chính
|
|
342
|
+
tint `bg-app-accent/10` (light);
|
|
343
|
+
12,67 / 11,90 / 10,80:1 (dark) ✓ AA
|
|
344
|
+
|
|
345
|
+
Chỗ đọc các biến này (mô tả VAI TRÒ, không phải kiểm kê từng file — kiểm kê chạy theo mỗi
|
|
346
|
+
vòng sửa component nên chép vào đây là chép một thứ chóng hỏng; nguồn chuẩn là
|
|
347
|
+
docs/hop-dong-mau.md §5):
|
|
348
|
+
• nền ĐẶC + foreground : ô app trong `AppLauncher`, chip lọc đang bật
|
|
349
|
+
• nền TINT + …-ink : khung icon của `PageHeader`/`StatCard` (`/10`), dòng đang chọn
|
|
350
|
+
của `DataTable` (`/5`) — trên tint thì màu chữ là INK, không
|
|
351
|
+
phải …-foreground (đó là chữ cho nền ĐẶC)
|
|
352
|
+
• ink : icon/vạch active của `SidebarMenuItem`, mọi chỗ màu app phải đi lên nền trang.
|
|
353
|
+
⚠ `text-app-accent` (không hậu tố) vẫn còn ở vài component — đó là vi phạm hợp đồng, không
|
|
354
|
+
phải cách dùng hợp lệ; xem docs/hop-dong-mau.md §5 và docs/quyet-dinh-tich-hop.md §8.
|
|
355
|
+
|
|
356
|
+
⚠ App ghi đè: set --app-accent thì PHẢI set kèm --app-accent-ink (và --app-accent-foreground
|
|
357
|
+
nếu accent của app sáng/tối khác gold). Quên thì vạch active vẫn ĐỌC ĐƯỢC nhưng còn màu
|
|
358
|
+
gold mặc định — sai màu mà không sai chuẩn, đúng thứ tự ưu tiên của repo. Cố ý KHÔNG suy
|
|
359
|
+
--app-accent-ink bằng color-mix từ --app-accent: dẫn xuất tự động cho ra con số không ai
|
|
360
|
+
đo, mà đây đúng là chỗ 0.1.0 đã ngã. */
|
|
125
361
|
--app-accent: var(--primary);
|
|
126
362
|
--app-accent-foreground: var(--primary-foreground);
|
|
363
|
+
--app-accent-ink: var(--primary-ink);
|
|
127
364
|
|
|
128
365
|
/* ─ Số đo khung ứng dụng (shell dùng ở cả CSS lẫn TS — xem tokens.ts) ─ */
|
|
129
366
|
--sidebar-width: 16rem; /* 256px */
|
|
@@ -162,16 +399,29 @@
|
|
|
162
399
|
--popover-foreground: #f7f7f8;
|
|
163
400
|
--primary: #f5d060;
|
|
164
401
|
--primary-foreground: #0e1116;
|
|
402
|
+
/* Bản dark của --primary vốn đã là bậc sáng đủ nổi trên nền tối (12,67:1 trên --background),
|
|
403
|
+
nên bậc ink trỏ thẳng về nó — cùng lối với --teal-ink/--green-ink ở khối dưới. */
|
|
404
|
+
--primary-ink: var(--primary);
|
|
165
405
|
--secondary: #1e2128;
|
|
166
406
|
--secondary-foreground: #f7f7f8;
|
|
167
407
|
--muted: #1e2128;
|
|
168
|
-
|
|
408
|
+
/* Phải SÁNG hơn bản light (không phải cùng một giá trị): chữ phụ hay nằm trên --card #16181D
|
|
409
|
+
và --muted #1E2128 chứ ít khi trên nền nền trang. #7E8290 cũ chỉ 4,21:1 trên --muted ✗.
|
|
410
|
+
#888D9B: 5,70:1 trên --background · 5,35:1 trên --card · 4,86:1 trên --muted ✓ — và tình cờ
|
|
411
|
+
vẫn ≥3:1 với núm Switch ở cả hai theme (3,32:1 với núm trắng của light, 5,70:1 với núm
|
|
412
|
+
#0E1116 của dark). "Tình cờ" là chữ đúng: đây là token CHỮ, ràng buộc phía núm KHÔNG được
|
|
413
|
+
bảo đảm ở đây — chỗ cần rãnh control phải dùng --control-edge. */
|
|
414
|
+
--muted-foreground: #888d9b;
|
|
169
415
|
--accent: #1e2128;
|
|
170
416
|
--accent-foreground: #f7f7f8;
|
|
171
|
-
|
|
417
|
+
/* Ghim đúng giá trị của light — xem chú thích ở :root. */
|
|
418
|
+
--destructive: #c93b3b;
|
|
172
419
|
--destructive-foreground: #ffffff;
|
|
173
420
|
--border: #1e2128;
|
|
174
|
-
--input:
|
|
421
|
+
/* --input và --control-edge KHÔNG khai lại: --control-edge ghim MỘT giá trị cho cả hai theme
|
|
422
|
+
(xem chứng minh hai phía ở :root). --control-on/--control-knob cũng không, vì chúng trỏ vào
|
|
423
|
+
--primary-ink/--background — hai biến đã có bản dark ngay trong khối này. */
|
|
424
|
+
--control-edge-strong: var(--ash-300);
|
|
175
425
|
--ring: #f5d060;
|
|
176
426
|
|
|
177
427
|
--sidebar: #16181d;
|
|
@@ -187,9 +437,22 @@
|
|
|
187
437
|
--green: #4ade80;
|
|
188
438
|
--yellow: #facc15;
|
|
189
439
|
|
|
190
|
-
/*
|
|
191
|
-
|
|
192
|
-
|
|
440
|
+
/* Bậc ink ở dark = CHÍNH màu phụ: bốn giá trị ngay trên vốn đã được chọn cho vai trò CHỮ.
|
|
441
|
+
Vẫn phải khai lại (chứ không để rơi về hex của :root) vì bản light là bậc tối đóng băng,
|
|
442
|
+
bỏ dòng nào thì bên dark rơi về đúng bậc tối đó và chìm nghỉm vào nền. Đo trên tint của
|
|
443
|
+
chính nó ở --card: teal/15 7,12 · green/15 7,44 · red/15 5,18 · yellow/20 7,19:1 ✓ */
|
|
444
|
+
--teal-ink: var(--teal);
|
|
445
|
+
--green-ink: var(--green);
|
|
446
|
+
--red-ink: var(--red);
|
|
447
|
+
--yellow-ink: var(--yellow);
|
|
448
|
+
/* navy nguyên bản (#0F1E3D) chìm hẳn vào nền dark — 1,15:1 (bản trước ghi 1,17:1, sai). Bậc
|
|
449
|
+
ink gánh vai trò đó (7,40:1 trên --background · 6,95:1 trên --card), và --chart-3 vì trỏ vào
|
|
450
|
+
--navy-ink nên tự đúng, không cần khai lại ở đây nữa. */
|
|
451
|
+
--navy-ink: #7ea2e8;
|
|
452
|
+
|
|
453
|
+
/* KHÔNG khai lại --*-foreground của trạng thái và của màu phụ: chúng là MỰC (#0E1116) ở cả hai
|
|
454
|
+
theme vì cả hai bản màu đều sáng — xem bảng số ở :root. --navy-foreground cũng vậy (trắng),
|
|
455
|
+
vì khối này không override --navy. */
|
|
193
456
|
|
|
194
457
|
/* Đổ bóng bản dark — bóng "mực" 4–16% trên nền #0E1116 gần như vô hình, lớp overlay
|
|
195
458
|
mất sạch tín hiệu độ cao (dialog/popover trông như dán phẳng vào nền). Trên nền tối,
|
|
@@ -199,9 +462,6 @@
|
|
|
199
462
|
--elevation-control: 0 1px 2px rgba(0, 0, 0, 0.4);
|
|
200
463
|
--elevation-card: 0 1px 2px rgba(0, 0, 0, 0.32), 0 8px 24px rgba(0, 0, 0, 0.4);
|
|
201
464
|
--elevation-overlay: 0 8px 32px rgba(0, 0, 0, 0.64);
|
|
202
|
-
|
|
203
|
-
/* navy nguyên bản chìm hẳn vào nền dark → chỉ nâng ở vai trò biểu đồ. */
|
|
204
|
-
--chart-3: #7ea2e8;
|
|
205
465
|
}
|
|
206
466
|
|
|
207
467
|
/* ────────────────────────────────────────────────────────────────────────────
|
|
@@ -232,6 +492,7 @@
|
|
|
232
492
|
--color-popover-foreground: var(--popover-foreground);
|
|
233
493
|
--color-primary: var(--primary);
|
|
234
494
|
--color-primary-foreground: var(--primary-foreground);
|
|
495
|
+
--color-primary-ink: var(--primary-ink);
|
|
235
496
|
--color-secondary: var(--secondary);
|
|
236
497
|
--color-secondary-foreground: var(--secondary-foreground);
|
|
237
498
|
--color-muted: var(--muted);
|
|
@@ -240,9 +501,17 @@
|
|
|
240
501
|
--color-accent-foreground: var(--accent-foreground);
|
|
241
502
|
--color-destructive: var(--destructive);
|
|
242
503
|
--color-destructive-foreground: var(--destructive-foreground);
|
|
504
|
+
--color-destructive-ink: var(--destructive-ink);
|
|
505
|
+
/* Ranh giới: `border-border` là TRANG TRÍ (không ngưỡng) · `border-input` /
|
|
506
|
+
`border-control-edge` là VIỀN CONTROL (3:1) · `border-control-edge-strong` cho hover/active.
|
|
507
|
+
`bg-control-on` + `text-control-knob` là cặp của control BẬT/ĐÃ CHỌN. */
|
|
243
508
|
--color-border: var(--border);
|
|
244
509
|
--color-input: var(--input);
|
|
245
510
|
--color-ring: var(--ring);
|
|
511
|
+
--color-control-edge: var(--control-edge);
|
|
512
|
+
--color-control-edge-strong: var(--control-edge-strong);
|
|
513
|
+
--color-control-on: var(--control-on);
|
|
514
|
+
--color-control-knob: var(--control-knob);
|
|
246
515
|
|
|
247
516
|
/* Sidebar */
|
|
248
517
|
--color-sidebar: var(--sidebar);
|
|
@@ -274,12 +543,23 @@
|
|
|
274
543
|
--color-ash-700: var(--ash-700);
|
|
275
544
|
--color-ash-900: var(--ash-900);
|
|
276
545
|
|
|
277
|
-
/* Màu phụ */
|
|
546
|
+
/* Màu phụ (tô NỀN) và bậc ink của chúng (làm CHỮ/ICON/VIỀN trên nền trang) */
|
|
278
547
|
--color-navy: var(--navy);
|
|
279
548
|
--color-teal: var(--teal);
|
|
280
549
|
--color-red: var(--red);
|
|
281
550
|
--color-green: var(--green);
|
|
282
551
|
--color-yellow: var(--yellow);
|
|
552
|
+
--color-navy-ink: var(--navy-ink);
|
|
553
|
+
--color-teal-ink: var(--teal-ink);
|
|
554
|
+
--color-red-ink: var(--red-ink);
|
|
555
|
+
--color-green-ink: var(--green-ink);
|
|
556
|
+
--color-yellow-ink: var(--yellow-ink);
|
|
557
|
+
/* Chữ trên nền màu phụ ĐẶC. Không có `red-foreground`: nền --red đặc không mang được chữ ở
|
|
558
|
+
light (4,38 / 4,32:1) → dùng `bg-destructive text-destructive-foreground`. */
|
|
559
|
+
--color-navy-foreground: var(--navy-foreground);
|
|
560
|
+
--color-teal-foreground: var(--teal-foreground);
|
|
561
|
+
--color-green-foreground: var(--green-foreground);
|
|
562
|
+
--color-yellow-foreground: var(--yellow-foreground);
|
|
283
563
|
|
|
284
564
|
/* Trạng thái */
|
|
285
565
|
--color-success: var(--success);
|
|
@@ -288,6 +568,9 @@
|
|
|
288
568
|
--color-warning-foreground: var(--warning-foreground);
|
|
289
569
|
--color-info: var(--info);
|
|
290
570
|
--color-info-foreground: var(--info-foreground);
|
|
571
|
+
--color-success-ink: var(--success-ink);
|
|
572
|
+
--color-warning-ink: var(--warning-ink);
|
|
573
|
+
--color-info-ink: var(--info-ink);
|
|
291
574
|
|
|
292
575
|
/* Biểu đồ */
|
|
293
576
|
--color-chart-1: var(--chart-1);
|
|
@@ -296,9 +579,13 @@
|
|
|
296
579
|
--color-chart-4: var(--chart-4);
|
|
297
580
|
--color-chart-5: var(--chart-5);
|
|
298
581
|
|
|
299
|
-
/* Accent theo app
|
|
582
|
+
/* Accent theo app — `bg-app-accent` (nền) · `text-app-accent-foreground` (chữ trên nền đó)
|
|
583
|
+
· `text-app-accent-ink`/`bg-app-accent-ink` (màu app trên nền trang). Utility
|
|
584
|
+
`text-app-accent` VẪN được sinh ra vì Tailwind sinh cả bộ cho mọi --color-*; nó nằm ngoài
|
|
585
|
+
hợp đồng dùng — xem chú thích ở :root trước khi định dùng. */
|
|
300
586
|
--color-app-accent: var(--app-accent);
|
|
301
587
|
--color-app-accent-foreground: var(--app-accent-foreground);
|
|
588
|
+
--color-app-accent-ink: var(--app-accent-ink);
|
|
302
589
|
|
|
303
590
|
/* Đổ bóng — sinh shadow-surface / shadow-control / shadow-card / shadow-overlay.
|
|
304
591
|
BẮT BUỘC là @theme *inline* và tên biến nguồn phải KHÁC (--elevation-*): khai
|
|
@@ -328,6 +615,9 @@
|
|
|
328
615
|
7. BASE LAYER + utility riêng của Comitor
|
|
329
616
|
──────────────────────────────────────────────────────────────────────────── */
|
|
330
617
|
@layer base {
|
|
618
|
+
/* ⚠ Dòng `border-border` này gán VIỀN TRANG TRÍ (1,16:1) làm mặc định cho MỌI phần tử — tiện
|
|
619
|
+
cho đường kẻ, nhưng nghĩa là control nào chỉ viết `border` mà quên nêu màu sẽ tàng hình mà
|
|
620
|
+
không có gì báo. Control luôn phải nêu `border-input`/`border-control-edge`. */
|
|
331
621
|
* {
|
|
332
622
|
@apply border-border outline-ring/50;
|
|
333
623
|
}
|