@comitor/ui 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/README.md +222 -41
  2. package/dist/components/composite/avatar-group.js +1 -1
  3. package/dist/components/composite/avatar-group.js.map +1 -1
  4. package/dist/components/composite/combobox.js +2 -2
  5. package/dist/components/composite/combobox.js.map +1 -1
  6. package/dist/components/composite/confirm-dialog.js +1 -1
  7. package/dist/components/composite/confirm-dialog.js.map +1 -1
  8. package/dist/components/composite/copy-button.js +1 -1
  9. package/dist/components/composite/copy-button.js.map +1 -1
  10. package/dist/components/composite/data-table.d.ts +17 -8
  11. package/dist/components/composite/data-table.js +8 -8
  12. package/dist/components/composite/data-table.js.map +1 -1
  13. package/dist/components/composite/filter-chips.js +6 -5
  14. package/dist/components/composite/filter-chips.js.map +1 -1
  15. package/dist/components/composite/form-field.js +3 -1
  16. package/dist/components/composite/form-field.js.map +1 -1
  17. package/dist/components/composite/form-section.js +5 -5
  18. package/dist/components/composite/form-section.js.map +1 -1
  19. package/dist/components/composite/inline-edit.js +1 -1
  20. package/dist/components/composite/inline-edit.js.map +1 -1
  21. package/dist/components/composite/keyboard-hint.d.ts +16 -0
  22. package/dist/components/composite/keyboard-hint.js +8 -6
  23. package/dist/components/composite/keyboard-hint.js.map +1 -1
  24. package/dist/components/composite/letter-avatar.d.ts +10 -18
  25. package/dist/components/composite/letter-avatar.js +6 -17
  26. package/dist/components/composite/letter-avatar.js.map +1 -1
  27. package/dist/components/composite/search-filter-bar.d.ts +41 -3
  28. package/dist/components/composite/search-filter-bar.js +27 -15
  29. package/dist/components/composite/search-filter-bar.js.map +1 -1
  30. package/dist/components/composite/stat-card.d.ts +16 -1
  31. package/dist/components/composite/stat-card.js +16 -9
  32. package/dist/components/composite/stat-card.js.map +1 -1
  33. package/dist/components/composite/status-config.d.ts +69 -16
  34. package/dist/components/composite/status-config.js +10 -10
  35. package/dist/components/composite/status-config.js.map +1 -1
  36. package/dist/components/composite/status-pill.js +5 -1
  37. package/dist/components/composite/status-pill.js.map +1 -1
  38. package/dist/components/composite/table-pagination.d.ts +46 -4
  39. package/dist/components/composite/table-pagination.js +27 -24
  40. package/dist/components/composite/table-pagination.js.map +1 -1
  41. package/dist/components/composite/text-utils.d.ts +12 -11
  42. package/dist/components/composite/text-utils.js +7 -16
  43. package/dist/components/composite/text-utils.js.map +1 -1
  44. package/dist/components/composite/toast.js +16 -6
  45. package/dist/components/composite/toast.js.map +1 -1
  46. package/dist/components/ui/alert.js +1 -1
  47. package/dist/components/ui/alert.js.map +1 -1
  48. package/dist/components/ui/badge.js +4 -4
  49. package/dist/components/ui/badge.js.map +1 -1
  50. package/dist/components/ui/button.js +2 -2
  51. package/dist/components/ui/button.js.map +1 -1
  52. package/dist/components/ui/calendar.d.ts +41 -0
  53. package/dist/components/ui/calendar.js +1 -1
  54. package/dist/components/ui/calendar.js.map +1 -1
  55. package/dist/components/ui/checkbox.js +3 -3
  56. package/dist/components/ui/checkbox.js.map +1 -1
  57. package/dist/components/ui/command.js +1 -1
  58. package/dist/components/ui/command.js.map +1 -1
  59. package/dist/components/ui/context-menu.js +1 -1
  60. package/dist/components/ui/context-menu.js.map +1 -1
  61. package/dist/components/ui/drawer.js +1 -1
  62. package/dist/components/ui/drawer.js.map +1 -1
  63. package/dist/components/ui/dropdown-menu.js +1 -1
  64. package/dist/components/ui/dropdown-menu.js.map +1 -1
  65. package/dist/components/ui/empty.js +1 -1
  66. package/dist/components/ui/empty.js.map +1 -1
  67. package/dist/components/ui/field.js +4 -4
  68. package/dist/components/ui/field.js.map +1 -1
  69. package/dist/components/ui/form.js +8 -2
  70. package/dist/components/ui/form.js.map +1 -1
  71. package/dist/components/ui/input-group.js +3 -3
  72. package/dist/components/ui/input-group.js.map +1 -1
  73. package/dist/components/ui/input-otp.js +1 -1
  74. package/dist/components/ui/input-otp.js.map +1 -1
  75. package/dist/components/ui/input.js +3 -3
  76. package/dist/components/ui/input.js.map +1 -1
  77. package/dist/components/ui/item.js +1 -1
  78. package/dist/components/ui/item.js.map +1 -1
  79. package/dist/components/ui/menubar.js +1 -1
  80. package/dist/components/ui/menubar.js.map +1 -1
  81. package/dist/components/ui/navigation-menu.js +1 -1
  82. package/dist/components/ui/navigation-menu.js.map +1 -1
  83. package/dist/components/ui/pagination.js +1 -1
  84. package/dist/components/ui/pagination.js.map +1 -1
  85. package/dist/components/ui/progress.d.ts +26 -0
  86. package/dist/components/ui/progress.js +5 -2
  87. package/dist/components/ui/progress.js.map +1 -1
  88. package/dist/components/ui/radio-group.js +2 -2
  89. package/dist/components/ui/radio-group.js.map +1 -1
  90. package/dist/components/ui/resizable.d.ts +14 -0
  91. package/dist/components/ui/resizable.js +2 -2
  92. package/dist/components/ui/resizable.js.map +1 -1
  93. package/dist/components/ui/scroll-area.js +1 -1
  94. package/dist/components/ui/scroll-area.js.map +1 -1
  95. package/dist/components/ui/select.d.ts +33 -0
  96. package/dist/components/ui/select.js +3 -3
  97. package/dist/components/ui/select.js.map +1 -1
  98. package/dist/components/ui/slider.d.ts +19 -0
  99. package/dist/components/ui/slider.js +3 -3
  100. package/dist/components/ui/slider.js.map +1 -1
  101. package/dist/components/ui/sonner.d.ts +21 -3
  102. package/dist/components/ui/sonner.js +1 -2
  103. package/dist/components/ui/sonner.js.map +1 -1
  104. package/dist/components/ui/switch.js +2 -2
  105. package/dist/components/ui/switch.js.map +1 -1
  106. package/dist/components/ui/table.d.ts +16 -0
  107. package/dist/components/ui/table.js +1 -1
  108. package/dist/components/ui/table.js.map +1 -1
  109. package/dist/components/ui/tabs.js +1 -1
  110. package/dist/components/ui/tabs.js.map +1 -1
  111. package/dist/components/ui/textarea.js +1 -1
  112. package/dist/components/ui/textarea.js.map +1 -1
  113. package/dist/components/ui/toggle.js +1 -1
  114. package/dist/components/ui/toggle.js.map +1 -1
  115. package/dist/index.d.ts +6 -5
  116. package/dist/index.js +17 -2
  117. package/dist/index.js.map +1 -1
  118. package/dist/lib/avatar-tone.d.ts +126 -0
  119. package/dist/lib/avatar-tone.js +68 -0
  120. package/dist/lib/avatar-tone.js.map +1 -0
  121. package/dist/shell/app-header.js +12 -2
  122. package/dist/shell/app-header.js.map +1 -1
  123. package/dist/shell/app-launcher.js +2 -2
  124. package/dist/shell/app-launcher.js.map +1 -1
  125. package/dist/shell/index.d.ts +3 -3
  126. package/dist/shell/shell-context.d.ts +1 -7
  127. package/dist/shell/shell-context.js +1 -19
  128. package/dist/shell/shell-context.js.map +1 -1
  129. package/dist/shell/sidebar-group.js +5 -3
  130. package/dist/shell/sidebar-group.js.map +1 -1
  131. package/dist/shell/sidebar-menu-item.js +12 -4
  132. package/dist/shell/sidebar-menu-item.js.map +1 -1
  133. package/dist/shell/types.d.ts +17 -1
  134. package/dist/shell/types.js.map +1 -1
  135. package/dist/shell/user-menu.js +3 -2
  136. package/dist/shell/user-menu.js.map +1 -1
  137. package/dist/shell/utils.d.ts +10 -22
  138. package/dist/shell/utils.js +3 -11
  139. package/dist/shell/utils.js.map +1 -1
  140. package/dist/shell/workspace-switcher.js +23 -8
  141. package/dist/shell/workspace-switcher.js.map +1 -1
  142. package/dist/tokens.d.ts +165 -17
  143. package/dist/tokens.js +122 -25
  144. package/dist/tokens.js.map +1 -1
  145. package/package.json +4 -2
  146. package/styles.css +312 -22
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
- --muted-foreground: #7e8290;
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
- --destructive: #d64545;
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
- --input: #edeef1;
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
- --info-foreground: #ffffff;
114
-
115
- /* ─ Bảng màu biểu đồ (đổi độc lập với brand) ─ */
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
- --chart-3: var(--navy);
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
- App set lại 2 biến này ở <body> là đổi được ô app trong AppLauncher và
124
- thanh active của sidebar, không cần build lại package. */
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
- --muted-foreground: #7e8290;
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
- --destructive: #d64545;
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: #1e2128;
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
- /* Foreground của trạng thái phải lật lại: nền sáng → chữ tối. */
191
- --success-foreground: #0e1116;
192
- --info-foreground: #0e1116;
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
  }