@comitor/ui 0.2.1 → 0.3.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 (90) hide show
  1. package/README.md +236 -30
  2. package/dist/chart/chart-colors.d.ts +56 -0
  3. package/dist/chart/chart-colors.js +21 -0
  4. package/dist/chart/chart-colors.js.map +1 -0
  5. package/dist/chart/chart.d.ts +40 -0
  6. package/dist/chart/chart.js.map +1 -1
  7. package/dist/chart/comitor-chart.d.ts +21 -43
  8. package/dist/chart/comitor-chart.js +13 -24
  9. package/dist/chart/comitor-chart.js.map +1 -1
  10. package/dist/chart/index.d.ts +2 -1
  11. package/dist/chart/index.js +1 -0
  12. package/dist/chart/index.js.map +1 -1
  13. package/dist/components/composite/data-table-constants.d.ts +40 -0
  14. package/dist/components/composite/data-table-constants.js +13 -0
  15. package/dist/components/composite/data-table-constants.js.map +1 -0
  16. package/dist/components/composite/data-table.d.ts +2 -27
  17. package/dist/components/composite/data-table.js +1 -10
  18. package/dist/components/composite/data-table.js.map +1 -1
  19. package/dist/components/composite/date-picker-constants.d.ts +21 -0
  20. package/dist/components/composite/date-picker-constants.js +39 -0
  21. package/dist/components/composite/date-picker-constants.js.map +1 -0
  22. package/dist/components/composite/date-picker.d.ts +2 -18
  23. package/dist/components/composite/date-picker.js +6 -48
  24. package/dist/components/composite/date-picker.js.map +1 -1
  25. package/dist/components/composite/form-field-constants.d.ts +26 -0
  26. package/dist/components/composite/form-field-constants.js +13 -0
  27. package/dist/components/composite/form-field-constants.js.map +1 -0
  28. package/dist/components/composite/form-field.d.ts +1 -13
  29. package/dist/components/composite/form-field.js +1 -11
  30. package/dist/components/composite/form-field.js.map +1 -1
  31. package/dist/components/composite/search-filter-bar-constants.d.ts +52 -0
  32. package/dist/components/composite/search-filter-bar-constants.js +18 -0
  33. package/dist/components/composite/search-filter-bar-constants.js.map +1 -0
  34. package/dist/components/composite/search-filter-bar.d.ts +3 -37
  35. package/dist/components/composite/search-filter-bar.js +2 -15
  36. package/dist/components/composite/search-filter-bar.js.map +1 -1
  37. package/dist/components/composite/table-pagination-constants.d.ts +56 -0
  38. package/dist/components/composite/table-pagination-constants.js +18 -0
  39. package/dist/components/composite/table-pagination-constants.js.map +1 -0
  40. package/dist/components/composite/table-pagination.d.ts +2 -43
  41. package/dist/components/composite/table-pagination.js +4 -15
  42. package/dist/components/composite/table-pagination.js.map +1 -1
  43. package/dist/components/ui/badge.js +3 -3
  44. package/dist/components/ui/badge.js.map +1 -1
  45. package/dist/components/ui/button.js +4 -4
  46. package/dist/components/ui/button.js.map +1 -1
  47. package/dist/components/ui/calendar.js +1 -1
  48. package/dist/components/ui/calendar.js.map +1 -1
  49. package/dist/components/ui/checkbox.js +2 -2
  50. package/dist/components/ui/checkbox.js.map +1 -1
  51. package/dist/components/ui/command.js +1 -1
  52. package/dist/components/ui/command.js.map +1 -1
  53. package/dist/components/ui/input-group.js +1 -1
  54. package/dist/components/ui/input-group.js.map +1 -1
  55. package/dist/components/ui/input-otp.js +1 -1
  56. package/dist/components/ui/input-otp.js.map +1 -1
  57. package/dist/components/ui/navigation-menu.js +1 -1
  58. package/dist/components/ui/navigation-menu.js.map +1 -1
  59. package/dist/components/ui/radio-group.js +1 -1
  60. package/dist/components/ui/radio-group.js.map +1 -1
  61. package/dist/components/ui/resizable.js +1 -1
  62. package/dist/components/ui/resizable.js.map +1 -1
  63. package/dist/components/ui/slider.js +1 -1
  64. package/dist/components/ui/slider.js.map +1 -1
  65. package/dist/components/ui/switch.js +1 -1
  66. package/dist/components/ui/switch.js.map +1 -1
  67. package/dist/components/ui/textarea.js +1 -1
  68. package/dist/components/ui/textarea.js.map +1 -1
  69. package/dist/index.d.ts +11 -6
  70. package/dist/index.js +5 -0
  71. package/dist/index.js.map +1 -1
  72. package/dist/shell/contrast-constants.d.ts +30 -0
  73. package/dist/shell/contrast-constants.js +16 -0
  74. package/dist/shell/contrast-constants.js.map +1 -0
  75. package/dist/shell/contrast-provider.d.ts +60 -0
  76. package/dist/shell/contrast-provider.js +61 -0
  77. package/dist/shell/contrast-provider.js.map +1 -0
  78. package/dist/shell/contrast-toggle.d.ts +24 -0
  79. package/dist/shell/contrast-toggle.js +29 -0
  80. package/dist/shell/contrast-toggle.js.map +1 -0
  81. package/dist/shell/index.d.ts +3 -0
  82. package/dist/shell/index.js +3 -0
  83. package/dist/shell/index.js.map +1 -1
  84. package/dist/shell/shell-context.js +3 -2
  85. package/dist/shell/shell-context.js.map +1 -1
  86. package/dist/tokens.d.ts +170 -70
  87. package/dist/tokens.js +124 -47
  88. package/dist/tokens.js.map +1 -1
  89. package/package.json +3 -2
  90. package/styles.css +255 -120
package/styles.css CHANGED
@@ -29,6 +29,55 @@
29
29
  cạnh nhau thì phải bọc thêm một lớp: <div class="dark"><div>…</div></div>. */
30
30
  @custom-variant dark (&:is(.dark *));
31
31
 
32
+ /* ════════════════════════════════════════════════════════════════════════════
33
+ NGUỒN SỰ THẬT — ĐỌC TRƯỚC KHI ĐỔI BẤT KỲ GIÁ TRỊ NÀO Ở DƯỚI
34
+
35
+ Bảng màu và style component của gói này KHỚP VỚI comitor-ds (repo design system đã được
36
+ duyệt). comitor-ds là bản duyệt về HÌNH THỨC và nó thắng khi có mâu thuẫn.
37
+
38
+ 0.2.x từng đi hướng ngược lại: nó tự sửa một loạt giá trị của comitor-ds để đạt ngưỡng WCAG,
39
+ và hệ quả là app dựng trên gói KHÔNG còn giống bản duyệt — mất gold ở mọi control đang BẬT,
40
+ viền ô nhập đậm hẳn lên, đỏ và chữ phụ tối đi. 0.3.0 kéo tất cả về đúng comitor-ds.
41
+
42
+ ─ HAI BẢNG MÀU, MỘT BỘ COMPONENT ─
43
+ Bảng màu đã đo của 0.2.x KHÔNG bị xoá: nó thành bảng thứ hai, bật bằng
44
+ `<html data-contrast="high">` (khối 5b bên dưới), do NGƯỜI DÙNG chọn trong phần cài đặt của
45
+ app. Lý do có nó: bảng comitor-ds đủ rõ trên màn hình tốt nhưng không đủ trên màn hình rẻ,
46
+ độ sáng thấp, hoặc ngoài nắng.
47
+
48
+ Điều kiện để hai bảng cùng sống: MỌI component đi qua TOKEN VAI TRÒ (--control-on,
49
+ --control-edge, --control-check, --switch-track, --slider-track, --primary-ink, --link-ink…)
50
+ chứ không viết thẳng `bg-gold-300` / `bg-ash-300` / `text-gold-400`. Viết thẳng thang màu là
51
+ khoá cứng một bảng và bảng kia lặng lẽ sai — đó là lỗi dễ mắc nhất khi thêm component mới.
52
+
53
+ ─ NHỮNG KHOẢN DƯỚI NGƯỠNG CỦA BẢNG MẶC ĐỊNH, ĐÃ BIẾT VÀ ĐÃ CHẤP NHẬN ─
54
+ Ghi ra để không ai tưởng là lỗi bỏ sót và "sửa" lại lần nữa — chỗ để sửa là bảng tương phản
55
+ cao, không phải bảng mặc định. Cột cuối là ngưỡng WCAG mà khoản đó không đạt:
56
+
57
+ viền control (--input / --control-edge) trên nền trang 1,16:1 (1.4.11 · 3:1)
58
+ rãnh Switch BẬT (--control-on) ⇄ nền trang và ⇄ núm 1,86:1 (1.4.11 · 3:1)
59
+ gạch active của Tabs, viền focus ô nhập (gold) 1,86:1 (1.4.11 · 3:1)
60
+ --muted-foreground trên --muted 3,58:1 (1.4.3 · 4,5:1)
61
+ chữ trắng trên nền --destructive 4,38:1 (1.4.3 · 4,5:1)
62
+ Button variant link (--gold-400) trên nền trang 3,15:1 (1.4.3 · 4,5:1)
63
+ Badge destructive: --red trên red/10 3,82:1 (1.4.3 · 4,5:1)
64
+
65
+ ─ HAI NGOẠI LỆ GIỮ BẢN SỬA Ở CẢ HAI BẢNG MÀU ─
66
+ Chỉ hai chỗ mà màu chữ của comitor-ds gần trùng màu nền tới mức không đọc nổi trên BẤT KỲ màn
67
+ hình nào, nên chúng không chờ người dùng bật tương phản cao. Nền giữ NGUYÊN của bản duyệt,
68
+ chỉ đậm chữ:
69
+
70
+ Badge warning --yellow trên yellow/10 = 1,52:1 → --yellow-ink = 6,24:1
71
+ Badge success --green trên green/10 = 2,82:1 → --green-ink = 5,88:1
72
+
73
+ ─ NGOÀI PHẠM VI ĐỐI CHIẾU ─
74
+ comitor-ds không có các cụm sau, nên không có bản duyệt nào để khớp và chúng giữ nguyên bảng
75
+ màu đã đo của gói: toàn bộ tầng composite (StatusPill, StatCard, DataTable, Toast,
76
+ FilterChips, LetterAvatar, EmptyState…), tầng shell (AppShell, Sidebar, AppLauncher…),
77
+ --app-accent-ink, và thang đổ bóng bản dark (--elevation-* trong .dark — comitor-ds không
78
+ khai bóng cho dark).
79
+ ════════════════════════════════════════════════════════════════════════════ */
80
+
32
81
  /* ────────────────────────────────────────────────────────────────────────────
33
82
  4. BIẾN — LIGHT
34
83
  ──────────────────────────────────────────────────────────────────────────── */
@@ -107,8 +156,14 @@
107
156
  % giữa Badge/LetterAvatar/Input), (c) `tokens.ts` mirror được ra hex thật.
108
157
  ⚠ Đổi --teal/--green/--red/--yellow thì PHẢI tính lại bậc ink tương ứng — không tự theo.
109
158
 
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). */
159
+ ⚠ PHẠM VI ÁP DỤNG TỪ 0.3.0: hợp đồng này chỉ còn áp cho các cụm TONAL mà comitor-ds KHÔNG
160
+ có (Badge success/warning, StatusPill, LetterAvatar, StatCard, Toast) — đó là chỗ chữ đứng
161
+ trên tint của chính nó và bậc ink là thứ duy nhất giữ được chữ đọc ra. Với các primitive CÓ
162
+ trong comitor-ds (Button, Input, Switch, Checkbox, Tabs, Alert, Form…), bản duyệt là nguồn
163
+ sự thật và bậc ink đã được gỡ khỏi đường render — xem khối NGUỒN SỰ THẬT ở đầu file.
164
+
165
+ Ở DARK, --x-ink trỏ về var(--x): comitor-ds dùng một hex cho cả hai theme, nên bên dark bậc
166
+ ink chính là màu phụ. */
112
167
  --teal-ink: #1a6668;
113
168
  --green-ink: #216a48;
114
169
  --red-ink: #9a3537;
@@ -147,47 +202,44 @@
147
202
  --primary: #e8b824;
148
203
  --primary-foreground: #0e1116;
149
204
  /* 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);
205
+ trò ở khối "Bậc INK của màu phụ" bên trên.
206
+
207
+ ⚠ TỪ 0.3.0: --primary-ink TRỎ THẲNG VỀ --primary ở cả hai theme. comitor-ds tô gold #E8B824
208
+ cho mọi vai trò brand — kể cả những vai trò mà WCAG 1.4.11 đòi 3:1 (rãnh Switch BẬT, ô
209
+ Checkbox đã chọn, gạch active của Tabs, viền focus của ô nhập) — và bản duyệt đó là nguồn
210
+ sự thật. Bậc tối #7A5800 mà vòng token trước dùng cho các vai trò này ĐÃ ĐƯỢC GỠ; số đo còn
211
+ tồn: gold trên nền trắng 1,86:1 (dưới 3:1). Xem khối NGUỒN SỰ THẬT ở đầu file.
212
+
213
+ Token vẫn TỒN TẠI như API công khai (app đang import `text-primary-ink`) và vẫn là chỗ để
214
+ app ghi đè nếu muốn một bậc tối hơn cho brand của mình. Chỗ DUY NHẤT trong gói còn giữ bậc
215
+ tối là --app-accent-ink (accent theo app, không có bản duyệt nào để khớp) và ba tone tonal
216
+ của Badge/StatusPill. */
217
+ --primary-ink: var(--primary);
164
218
  --secondary: #ede8d8;
165
219
  --secondary-foreground: #0e1116;
166
220
  --muted: #f7f7f8;
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;
221
+ /* Bậc chữ phụ (placeholder, mô tả, nhãn trục biểu đồ). Giá trị của comitor-ds, bằng đúng
222
+ --ash-500. Số đo còn tồn (dưới AA 4,5:1 — ĐÃ BIẾT VÀ CHẤP NHẬN, xem khối NGUỒN SỰ THẬT ở
223
+ đầu file): 3,83:1 trên --background · 3,58:1 trên --muted · 3,67:1 trên --sidebar. */
224
+ --muted-foreground: #7e8290;
173
225
  --accent: #fbf5dc;
174
226
  --accent-foreground: #0e1116;
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;
227
+ /* Đỏ dùng làm NỀN (nút xoá, chấm huy hiệu). Giá trị của comitor-ds, ghim cho cả hai theme và
228
+ BẰNG ĐÚNG --red (bản duyệt không tách hai bậc). Số đo còn tồn: chữ trắng trên nền này được
229
+ 4,38:1 — thiếu 0,12 so với AA 4,5:1. Xem khối NGUỒN SỰ THẬT ở đầu file. */
230
+ --destructive: #d64545;
181
231
  --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);
232
+ /* Nền nút xoá lúc HOVER. Phải là token riêng vì hai bảng màu đi hai HƯỚNG ngược nhau:
233
+ comitor-ds viết `hover:bg-red/90`, tức NHẠT dần về phía nền — #DA5A5A là đúng thứ nó render
234
+ trên trang trắng. Bảng tương phản cao thì phải ĐẬM thêm, nếu không chữ trắng tụt xuống
235
+ 4,32:1. Ghim hex thay vì `/90` vì `/90` phụ thuộc nền phía sau: trên nền tối nó cho ra một
236
+ màu khác hẳn và không ai đo được. */
237
+ --destructive-hover: #da5a5a;
238
+ /* Chữ báo lỗi trên nền TRANG (helper text của Input, dòng lỗi của Form). comitor-ds viết thẳng
239
+ `text-destructive` / `border-red` cho vai trò này, tức CÙNG một hex với nền đỏ — nên token
240
+ trỏ về --destructive. Số đo: 4,38:1 trên --background (light) · 4,06:1 trên --card (dark).
241
+ Token giữ lại vì là API công khai và là chỗ app ghi đè nếu muốn bậc đỏ tối hơn. */
242
+ --destructive-ink: var(--destructive);
191
243
  /* ─ RANH GIỚI: HAI VAI TRÒ, HAI TOKEN, TUYỆT ĐỐI ĐỪNG LẪN ─
192
244
 
193
245
  --border (+ --sidebar-border) VIỀN TRANG TRÍ. Đường kẻ chia, khung Card lúc nghỉ, vạch
@@ -198,74 +250,73 @@
198
250
  light #EDEEF1 trên trắng = 1,16:1 · dark #1E2128 trên #0E1116 = 1,17:1
199
251
  dark #1E2128 trên --card #16181D = 1,10:1 · --sidebar-border/--sidebar = 1,11:1
200
252
 
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.
253
+ --control-edge (= --input) VIỀN CONTROL. TỪ 0.3.0 token này TRỎ VỀ --input, tức bằng
254
+ đúng bậc trang trí của comitor-ds (#EDEEF1 light · #1E2128 dark). Bản duyệt vẽ viền ô
255
+ nhập ở bậc đó và bản duyệt là nguồn sự thật; số đo còn tồn là 1,16:1, dưới 3:1 của
256
+ WCAG 1.4.11 — xem khối NGUỒN SỰ THẬT ở đầu file.
257
+ Dùng cho: Input/Textarea/Select/Combobox, ô Checkbox chưa chọn, viền Radio, rãnh
258
+ Slider, Button variant outline/secondary, Badge variant outline, ô tìm của
259
+ WorkspaceSwitcher. (Rãnh Switch khi TẮT KHÔNG đi qua đây — comitor-ds dùng
260
+ `bg-gray-300 dark:bg-gray-700`, xem switch.tsx.)
206
261
 
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.
262
+ --control-edge-strong Cùng vai trò nhưng ở HOVER/ACTIVE. Giá trị của comitor-ds:
263
+ `hover:border-gray-300` ở light (--ash-300) và `dark:hover:border-gray-500` ở dark
264
+ (--ash-500).
210
265
 
211
266
  ⚠ `@layer base { * { @apply border-border } }` ở mục 7 gán viền TRANG TRÍ cho MỌI phần tử.
212
267
  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
268
  `border-input` / `border-control-edge` tường minh. Bảng đầy đủ: docs/hop-dong-mau.md §3. */
214
269
  --border: #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);
270
+ /* Viền ô nhập — giá trị của comitor-ds, cùng bậc với --border. Khối .dark khai lại (#1E2128). */
271
+ --input: #edeef1;
272
+ /* LỚP TÔ của ô nhập ở dark — comitor-ds viết `dark:bg-input/30`, và nó không phải trang trí:
273
+ ở dark viền --input (#1E2128) trên --card (#16181D) chỉ 1,10:1, nên lớp tô mờ này là thứ
274
+ DUY NHẤT còn khoanh vùng được ô Radio và ô OTP. Phải là token riêng chứ không viết thẳng
275
+ `bg-input/30`: ở bảng tương phản cao --input là #7E8290, tô 30% ra một mảng xám sáng làm
276
+ placeholder tụt còn 3,22:1 — bên đó viền đã đủ đậm nên lớp tô thành trong suốt.
277
+ Ở light comitor-ds không tô (class chỉ có nhánh `dark:`) nên giá trị này không được dùng. */
278
+ --input-fill: transparent;
219
279
  --ring: #e8b824;
220
280
 
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);
281
+ /* Trỏ về --input, không còn là một bậc xám riêng. Token giữ lại vì component trong gói đang
282
+ viết `border-control-edge` và vì app có thể ghi đè MỘT chỗ này để làm đậm viền control mà
283
+ không đụng đường kẻ trang trí (--border). Số đo còn tồn: 1,16:1 light · 1,17:1 dark. */
284
+ --control-edge: var(--input);
285
+ /* Hover/active của viền control — đúng cặp của comitor-ds: `hover:border-gray-300` (ash-300,
286
+ #C8CBD3) ở light và `dark:hover:border-gray-500` (ash-500, #7E8290) ở dark. Token hoá để
287
+ Input/Select/Combobox/Checkbox khỏi chép lệch. */
288
+ --control-edge-strong: var(--ash-300);
247
289
 
248
290
  /* ─ 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);
291
+ Rãnh `Switch` khi BẬT, ô `Checkbox` đã chọn, chấm `Radio`, đoạn đã chọn của `Slider`.
292
+
293
+ comitor-ds tô GOLD cho toàn bộ nhóm này (`data-[state=checked]:bg-gold-300`), nên --control-on
294
+ trỏ thẳng về --primary. Số đo còn tồn, ghi ra để không ai tưởng là bỏ sót: rãnh BẬT so với nền
295
+ trang 1,86:1 và so với núm trắng cũng 1,86:1 (dưới 3:1 của WCAG 1.4.11) — tức ở light, BẬT/TẮT
296
+ phân biệt chủ yếu bằng VỊ TRÍ núm và bằng sắc, không bằng độ sáng. Đây là đánh đổi ĐÃ CHỌN, xem
297
+ khối NGUỒN SỰ THẬT ở đầu file.
298
+
299
+ --control-knob là NÚM Switch / chấm Radio: TRẮNG ở cả hai theme, đúng `bg-white` của bản duyệt.
300
+ ⚠ DẤU CHECK của Checkbox KHÔNG dùng token này — comitor-ds vẽ nó bằng `text-ink` trên nền gold
301
+ (10,19:1), xem checkbox.tsx.
302
+ ⚠ App ghi đè --primary là control đổi màu theo ngay, không cần ghi đè thêm gì. */
303
+ --control-on: var(--primary);
304
+ --control-knob: #ffffff;
305
+ /* DẤU CHECK của Checkbox và chữ trên ô ngày ĐANG CHỌN của Calendar — tách khỏi --control-knob
306
+ vì hai vai trò đòi hai chiều tương phản khác nhau khi --control-on đổi giữa hai bảng màu:
307
+ bảng comitor-ds tô nền gold SÁNG nên dấu phải là MỰC (10,19:1); bảng tương phản cao tô nền
308
+ gold-500 TỐI nên dấu phải là màu nền trang (6,51:1). Núm Switch thì luôn trắng ở bảng duyệt,
309
+ nên nó ở lại --control-knob. */
310
+ --control-check: var(--ink);
311
+ /* Rãnh Switch khi TẮT và rãnh Slider — comitor-ds cho hai thứ này hai bậc khác nhau
312
+ (`bg-gray-300 dark:bg-gray-700` cho Switch, `bg-muted` cho Slider) nên phải là hai token,
313
+ không gộp được vào --control-edge. */
314
+ --switch-track: var(--ash-300);
315
+ --slider-track: var(--muted);
316
+ /* Button variant `link`. Tách khỏi --primary-ink vì comitor-ds dùng gold-400 cho chữ link
317
+ nhưng gold-300 cho viền focus/vạch active — hai bậc khác nhau của cùng thang. */
318
+ --link-ink: var(--gold-400);
319
+ --link-ink-hover: var(--gold-300);
269
320
 
270
321
  --sidebar: #fafaf9;
271
322
  --sidebar-foreground: #0e1116;
@@ -360,7 +411,11 @@
360
411
  đo, mà đây đúng là chỗ 0.1.0 đã ngã. */
361
412
  --app-accent: var(--primary);
362
413
  --app-accent-foreground: var(--primary-foreground);
363
- --app-accent-ink: var(--primary-ink);
414
+ /* Bậc ink của accent app là chỗ DUY NHẤT còn giữ bậc tối sau khi 0.3.0 kéo --primary-ink về
415
+ gold: vai trò của nó là icon/vạch active của SidebarMenuItem và khung icon của PageHeader —
416
+ những cụm CHỈ có trong gói, comitor-ds không có bản duyệt nào để khớp. Gold đặc trên
417
+ --sidebar-accent #FBF5DC chỉ 1,70:1 (icon biến mất), gold-500 được 5,96:1. */
418
+ --app-accent-ink: var(--gold-500);
364
419
 
365
420
  /* ─ Số đo khung ứng dụng (shell dùng ở cả CSS lẫn TS — xem tokens.ts) ─ */
366
421
  --sidebar-width: 16rem; /* 256px */
@@ -381,7 +436,9 @@
381
436
  overlay — lớp phủ lên nội dung (Dialog, Sheet, Popover, menu, Select, Tooltip) */
382
437
  --elevation-surface: 0 1px 2px rgba(14, 17, 22, 0.04);
383
438
  --elevation-control: 0 1px 2px rgba(14, 17, 22, 0.08);
384
- --elevation-card: 0 1px 2px rgba(14, 17, 22, 0.04), 0 8px 24px rgba(14, 17, 22, 0.04);
439
+ /* Lớp thứ hai để 0.06 — đúng thứ Card variant="elevated" của comitor-ds render
440
+ (bản duyệt khai --shadow-card 0.04 nhưng không component nào đọc biến đó). */
441
+ --elevation-card: 0 1px 2px rgba(14, 17, 22, 0.04), 0 8px 24px rgba(14, 17, 22, 0.06);
385
442
  --elevation-overlay: 0 8px 32px rgba(14, 17, 22, 0.16);
386
443
 
387
444
  --radius: 0.375rem; /* 6px — chữ ký thị giác của Comitor */
@@ -405,23 +462,27 @@
405
462
  --secondary: #1e2128;
406
463
  --secondary-foreground: #f7f7f8;
407
464
  --muted: #1e2128;
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;
465
+ /* Cùng một giá trị với bản light — comitor-ds không khai lại token này trong .dark. Số đo còn
466
+ tồn (dưới AA, ĐÃ BIẾT VÀ CHẤP NHẬN): 4,21:1 trên --muted · 4,73:1 trên --card. Xem khối
467
+ NGUỒN SỰ THẬT ở đầu file. */
468
+ --muted-foreground: #7e8290;
415
469
  --accent: #1e2128;
416
470
  --accent-foreground: #f7f7f8;
417
- /* Ghim đúng giá trị của light — xem chú thích ở :root. */
418
- --destructive: #c93b3b;
471
+ /* Ghim đúng giá trị của light — comitor-ds dùng chung một hex cho cả hai theme. */
472
+ --destructive: #d64545;
419
473
  --destructive-foreground: #ffffff;
420
474
  --border: #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);
475
+ /* Viền ô nhập bản dark của comitor-ds. --control-edge trỏ vào --input nên tự theo; --control-on
476
+ và --control-knob cũng không khai lại (chúng trỏ vào --primary — đã có bản dark ngay trên —
477
+ và vào trắng cố định, đúng như núm Switch của comitor-ds). */
478
+ --input: #1e2128;
479
+ --input-fill: color-mix(in srgb, var(--input) 30%, transparent);
480
+ /* Hover viền control bản dark của comitor-ds là dark:hover:border-gray-500, tức ash-500. */
481
+ --control-edge-strong: var(--ash-500);
482
+ /* Rãnh Switch khi TẮT bản dark của comitor-ds (dark:bg-gray-700). --slider-track không khai lại
483
+ vì nó trỏ vào --muted, biến đã có bản dark ngay trong khối này. --control-check cũng vậy:
484
+ comitor-ds vẽ dấu check bằng text-ink ở CẢ hai theme. */
485
+ --switch-track: var(--ash-700);
425
486
  --ring: #f5d060;
426
487
 
427
488
  --sidebar: #16181d;
@@ -430,17 +491,13 @@
430
491
  --sidebar-accent: #1e2128;
431
492
  --sidebar-accent-foreground: #f7f7f8;
432
493
 
433
- /* Màu phụ bản dark — bản light quá tối để làm CHỮ trên nền #0E1116 (không đạt AA).
434
- Các giá trị dưới đây đều ≥ 6:1 trên nền dark. Cần đo lại nếu đổi nền. */
435
- --teal: #2dd4bf;
436
- --red: #f87171;
437
- --green: #4ade80;
438
- --yellow: #facc15;
494
+ /* Màu phụ KHÔNG có bản dark — comitor-ds dùng đúng một hex cho cả hai theme, và bản này theo.
495
+ Số đo trên tint /20 hợp lên --card (đọc được, tuy dưới AA ở hai tone): teal 3,39 · green 4,26
496
+ · red 3,34 · yellow 7,02:1.
439
497
 
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 ✓ */
498
+ Bậc ink ở dark PHẢI khai lại (chứ không để rơi về hex của :root) vì bản light của --*-ink là
499
+ bậc tối đóng băng cho vai trò chữ-trên-nền-sáng; bỏ dòng nào thì bên dark rơi về đúng bậc tối
500
+ đó và chìm nghỉm vào nền. Trỏ về chính màu phụ = đúng thứ comitor-ds render ở dark. */
444
501
  --teal-ink: var(--teal);
445
502
  --green-ink: var(--green);
446
503
  --red-ink: var(--red);
@@ -449,6 +506,9 @@
449
506
  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
507
  --navy-ink nên tự đúng, không cần khai lại ở đây nữa. */
451
508
  --navy-ink: #7ea2e8;
509
+ /* Accent app tách khỏi --primary-ink (xem :root): ở dark gold-500 chìm vào nền, nên bậc sáng
510
+ của brand gánh vai trò này. */
511
+ --app-accent-ink: var(--primary);
452
512
 
453
513
  /* 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
514
  theme vì cả hai bản màu đều sáng — xem bảng số ở :root. --navy-foreground cũng vậy (trắng),
@@ -464,6 +524,74 @@
464
524
  --elevation-overlay: 0 8px 32px rgba(0, 0, 0, 0.64);
465
525
  }
466
526
 
527
+ /* ────────────────────────────────────────────────────────────────────────────
528
+ 5b. BẢNG MÀU THỨ HAI — TƯƠNG PHẢN CAO `<html data-contrast="high">`
529
+
530
+ Bảng mặc định (khối 4 và 5) là comitor-ds: đẹp, đúng brand, và có một số cặp nằm dưới ngưỡng
531
+ WCAG — trên màn hình tốt thì không thành vấn đề, trên màn hình rẻ / độ sáng thấp / ngoài nắng
532
+ thì thành vấn đề thật. Khối này là bảng màu đã đo của 0.2.x, bật theo LỰA CHỌN CỦA NGƯỜI DÙNG.
533
+
534
+ ─ CÁCH BẬT ─
535
+ `<ContrastProvider>` của @comitor/ui/shell gắn `data-contrast="high"` lên <html> (kèm script
536
+ chống nháy, cùng lối với next-themes). App chỉ cần bọc provider và đặt `<ContrastToggle>` vào
537
+ trang cài đặt — KHÔNG app nào tự khai lại màu.
538
+
539
+ ─ VÌ SAO CHỈ KHAI LẠI BIẾN, KHÔNG ĐỘNG VÀO COMPONENT ─
540
+ Mọi component trong gói đi qua TOKEN VAI TRÒ (--control-on, --control-edge, --control-check,
541
+ --primary-ink, --link-ink…) chứ không viết thẳng `bg-gold-300`. Nhờ vậy hai bảng màu chỉ khác
542
+ nhau ở khối này; không có nhánh `data-contrast:` nào rải trong component, và cũng không có cách
543
+ nào để một bảng màu "quên" một component.
544
+
545
+ ⚠ ĐỘ ĐẶC TẢ: [data-contrast="high"] và .dark cùng là (0,1,0). Khối này phải đứng SAU .dark để
546
+ thắng ở những token nó khai; token nào bản dark của bảng tương phản cao khác bản light thì
547
+ PHẢI khai lại ở .dark[data-contrast="high"] (0,2,0) bên dưới. Đảo thứ tự hai khối là hỏng.
548
+ ──────────────────────────────────────────────────────────────────────────── */
549
+ [data-contrast="high"] {
550
+ /* Chữ phụ: 5,23:1 trên --background · 4,89:1 trên --muted (bảng duyệt: 3,83 / 3,58) */
551
+ --muted-foreground: #686c7a;
552
+ /* Nền đỏ + chữ trắng: 5,03:1 (bảng duyệt #D64545 chỉ 4,38:1) */
553
+ --destructive: #c93b3b;
554
+ /* Ngược chiều bảng duyệt: color-mix(--destructive, --ink 15%) — chữ trắng 6,19:1 */
555
+ --destructive-hover: #ad3535;
556
+ --destructive-ink: var(--red-ink);
557
+ /* Viền control: 3,83:1 trên nền trắng (bảng duyệt #EDEEF1 chỉ 1,16:1).
558
+ --control-edge trỏ vào --input nên tự theo. */
559
+ --input: #7e8290;
560
+ /* Viền đã đủ đậm ở cả hai theme nên bỏ hẳn lớp tô — nó chỉ làm placeholder khó đọc. */
561
+ --input-fill: transparent;
562
+ --control-edge-strong: var(--ash-700);
563
+ /* Control BẬT/ĐÃ CHỌN: rãnh ⇄ nền 6,51:1 và rãnh ⇄ núm 6,51:1 (bảng duyệt: 1,86 / 1,86) */
564
+ --control-on: var(--gold-500);
565
+ --control-knob: var(--background);
566
+ --control-check: var(--control-knob);
567
+ --switch-track: var(--control-edge);
568
+ --slider-track: var(--control-edge);
569
+ /* Brand khi phải ĐI LÊN nền trang: viền focus, gạch active của Tabs, ngày chọn của Calendar */
570
+ --primary-ink: var(--gold-500);
571
+ --link-ink: var(--gold-500);
572
+ --link-ink-hover: var(--gold-600);
573
+ }
574
+
575
+ .dark[data-contrast="high"] {
576
+ /* Sáng hơn bản light: chữ phụ ở dark hay nằm trên --card/--muted chứ ít khi trên nền trang.
577
+ 5,70:1 trên --background · 5,35:1 trên --card · 4,86:1 trên --muted */
578
+ --muted-foreground: #888d9b;
579
+ /* Viền control ghim MỘT giá trị cho cả hai theme: 4,94:1 trên nền #0E1116 */
580
+ --input: #7e8290;
581
+ --control-edge-strong: var(--ash-300);
582
+ /* Bảng màu dark đã nâng --primary lên bậc sáng nên bậc ink trỏ thẳng về nó: 12,67:1 */
583
+ --control-on: var(--primary);
584
+ --primary-ink: var(--primary);
585
+ --link-ink: var(--gold-200);
586
+ --link-ink-hover: var(--gold-100);
587
+ /* Màu phụ bản dark — bản light quá tối để làm CHỮ trên nền #0E1116. Đo trên tint /15 hợp lên
588
+ --card: teal 7,12 · green 7,44 · red 5,18 · yellow /20 7,19:1 ✓ */
589
+ --teal: #2dd4bf;
590
+ --red: #f87171;
591
+ --green: #4ade80;
592
+ --yellow: #facc15;
593
+ }
594
+
467
595
  /* ────────────────────────────────────────────────────────────────────────────
468
596
  6. MAP BIẾN → NAMESPACE TAILWIND (sinh bg-*, text-*, border-*, rounded-*…)
469
597
 
@@ -501,6 +629,7 @@
501
629
  --color-accent-foreground: var(--accent-foreground);
502
630
  --color-destructive: var(--destructive);
503
631
  --color-destructive-foreground: var(--destructive-foreground);
632
+ --color-destructive-hover: var(--destructive-hover);
504
633
  --color-destructive-ink: var(--destructive-ink);
505
634
  /* Ranh giới: `border-border` là TRANG TRÍ (không ngưỡng) · `border-input` /
506
635
  `border-control-edge` là VIỀN CONTROL (3:1) · `border-control-edge-strong` cho hover/active.
@@ -508,10 +637,16 @@
508
637
  --color-border: var(--border);
509
638
  --color-input: var(--input);
510
639
  --color-ring: var(--ring);
640
+ --color-input-fill: var(--input-fill);
511
641
  --color-control-edge: var(--control-edge);
512
642
  --color-control-edge-strong: var(--control-edge-strong);
513
643
  --color-control-on: var(--control-on);
514
644
  --color-control-knob: var(--control-knob);
645
+ --color-control-check: var(--control-check);
646
+ --color-switch-track: var(--switch-track);
647
+ --color-slider-track: var(--slider-track);
648
+ --color-link-ink: var(--link-ink);
649
+ --color-link-ink-hover: var(--link-ink-hover);
515
650
 
516
651
  /* Sidebar */
517
652
  --color-sidebar: var(--sidebar);