@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.
Files changed (156) hide show
  1. package/README.md +303 -43
  2. package/dist/chart/chart.d.ts +35 -3
  3. package/dist/chart/chart.js.map +1 -1
  4. package/dist/chart/comitor-chart.d.ts +16 -3
  5. package/dist/chart/comitor-chart.js.map +1 -1
  6. package/dist/chart/index.d.ts +2 -2
  7. package/dist/components/composite/avatar-group.js +1 -1
  8. package/dist/components/composite/avatar-group.js.map +1 -1
  9. package/dist/components/composite/combobox.d.ts +9 -2
  10. package/dist/components/composite/combobox.js +9 -2
  11. package/dist/components/composite/combobox.js.map +1 -1
  12. package/dist/components/composite/confirm-dialog.js +1 -1
  13. package/dist/components/composite/confirm-dialog.js.map +1 -1
  14. package/dist/components/composite/copy-button.js +1 -1
  15. package/dist/components/composite/copy-button.js.map +1 -1
  16. package/dist/components/composite/data-table.d.ts +17 -8
  17. package/dist/components/composite/data-table.js +8 -8
  18. package/dist/components/composite/data-table.js.map +1 -1
  19. package/dist/components/composite/filter-chips.js +6 -5
  20. package/dist/components/composite/filter-chips.js.map +1 -1
  21. package/dist/components/composite/form-field.js +3 -1
  22. package/dist/components/composite/form-field.js.map +1 -1
  23. package/dist/components/composite/form-section.js +5 -5
  24. package/dist/components/composite/form-section.js.map +1 -1
  25. package/dist/components/composite/inline-edit.js +1 -1
  26. package/dist/components/composite/inline-edit.js.map +1 -1
  27. package/dist/components/composite/keyboard-hint.d.ts +16 -0
  28. package/dist/components/composite/keyboard-hint.js +8 -6
  29. package/dist/components/composite/keyboard-hint.js.map +1 -1
  30. package/dist/components/composite/letter-avatar.d.ts +10 -18
  31. package/dist/components/composite/letter-avatar.js +6 -17
  32. package/dist/components/composite/letter-avatar.js.map +1 -1
  33. package/dist/components/composite/search-filter-bar.d.ts +41 -3
  34. package/dist/components/composite/search-filter-bar.js +27 -15
  35. package/dist/components/composite/search-filter-bar.js.map +1 -1
  36. package/dist/components/composite/stat-card.d.ts +16 -1
  37. package/dist/components/composite/stat-card.js +16 -9
  38. package/dist/components/composite/stat-card.js.map +1 -1
  39. package/dist/components/composite/status-config.d.ts +69 -16
  40. package/dist/components/composite/status-config.js +10 -10
  41. package/dist/components/composite/status-config.js.map +1 -1
  42. package/dist/components/composite/status-pill.js +5 -1
  43. package/dist/components/composite/status-pill.js.map +1 -1
  44. package/dist/components/composite/table-pagination.d.ts +46 -4
  45. package/dist/components/composite/table-pagination.js +27 -24
  46. package/dist/components/composite/table-pagination.js.map +1 -1
  47. package/dist/components/composite/text-utils.d.ts +12 -11
  48. package/dist/components/composite/text-utils.js +7 -16
  49. package/dist/components/composite/text-utils.js.map +1 -1
  50. package/dist/components/composite/toast.js +16 -6
  51. package/dist/components/composite/toast.js.map +1 -1
  52. package/dist/components/ui/alert.js +1 -1
  53. package/dist/components/ui/alert.js.map +1 -1
  54. package/dist/components/ui/badge.js +4 -4
  55. package/dist/components/ui/badge.js.map +1 -1
  56. package/dist/components/ui/button.js +3 -3
  57. package/dist/components/ui/button.js.map +1 -1
  58. package/dist/components/ui/calendar.d.ts +41 -0
  59. package/dist/components/ui/calendar.js +1 -1
  60. package/dist/components/ui/calendar.js.map +1 -1
  61. package/dist/components/ui/card.d.ts +35 -2
  62. package/dist/components/ui/card.js +17 -3
  63. package/dist/components/ui/card.js.map +1 -1
  64. package/dist/components/ui/checkbox.js +3 -3
  65. package/dist/components/ui/checkbox.js.map +1 -1
  66. package/dist/components/ui/command.js +1 -1
  67. package/dist/components/ui/command.js.map +1 -1
  68. package/dist/components/ui/context-menu.js +1 -1
  69. package/dist/components/ui/context-menu.js.map +1 -1
  70. package/dist/components/ui/drawer.js +1 -1
  71. package/dist/components/ui/drawer.js.map +1 -1
  72. package/dist/components/ui/dropdown-menu.js +1 -1
  73. package/dist/components/ui/dropdown-menu.js.map +1 -1
  74. package/dist/components/ui/empty.js +1 -1
  75. package/dist/components/ui/empty.js.map +1 -1
  76. package/dist/components/ui/field.js +4 -4
  77. package/dist/components/ui/field.js.map +1 -1
  78. package/dist/components/ui/form.js +8 -2
  79. package/dist/components/ui/form.js.map +1 -1
  80. package/dist/components/ui/input-group.js +3 -3
  81. package/dist/components/ui/input-group.js.map +1 -1
  82. package/dist/components/ui/input-otp.js +1 -1
  83. package/dist/components/ui/input-otp.js.map +1 -1
  84. package/dist/components/ui/input.js +3 -3
  85. package/dist/components/ui/input.js.map +1 -1
  86. package/dist/components/ui/item.js +1 -1
  87. package/dist/components/ui/item.js.map +1 -1
  88. package/dist/components/ui/menubar.js +1 -1
  89. package/dist/components/ui/menubar.js.map +1 -1
  90. package/dist/components/ui/navigation-menu.js +1 -1
  91. package/dist/components/ui/navigation-menu.js.map +1 -1
  92. package/dist/components/ui/pagination.js +1 -1
  93. package/dist/components/ui/pagination.js.map +1 -1
  94. package/dist/components/ui/progress.d.ts +26 -0
  95. package/dist/components/ui/progress.js +5 -2
  96. package/dist/components/ui/progress.js.map +1 -1
  97. package/dist/components/ui/radio-group.js +2 -2
  98. package/dist/components/ui/radio-group.js.map +1 -1
  99. package/dist/components/ui/resizable.d.ts +14 -0
  100. package/dist/components/ui/resizable.js +2 -2
  101. package/dist/components/ui/resizable.js.map +1 -1
  102. package/dist/components/ui/scroll-area.js +1 -1
  103. package/dist/components/ui/scroll-area.js.map +1 -1
  104. package/dist/components/ui/select.d.ts +33 -0
  105. package/dist/components/ui/select.js +3 -3
  106. package/dist/components/ui/select.js.map +1 -1
  107. package/dist/components/ui/slider.d.ts +19 -0
  108. package/dist/components/ui/slider.js +3 -3
  109. package/dist/components/ui/slider.js.map +1 -1
  110. package/dist/components/ui/sonner.d.ts +21 -3
  111. package/dist/components/ui/sonner.js +1 -2
  112. package/dist/components/ui/sonner.js.map +1 -1
  113. package/dist/components/ui/switch.js +2 -2
  114. package/dist/components/ui/switch.js.map +1 -1
  115. package/dist/components/ui/table.d.ts +16 -0
  116. package/dist/components/ui/table.js +1 -1
  117. package/dist/components/ui/table.js.map +1 -1
  118. package/dist/components/ui/tabs.js +1 -1
  119. package/dist/components/ui/tabs.js.map +1 -1
  120. package/dist/components/ui/textarea.js +1 -1
  121. package/dist/components/ui/textarea.js.map +1 -1
  122. package/dist/components/ui/toggle.js +1 -1
  123. package/dist/components/ui/toggle.js.map +1 -1
  124. package/dist/index.d.ts +6 -5
  125. package/dist/index.js +17 -2
  126. package/dist/index.js.map +1 -1
  127. package/dist/lib/avatar-tone.d.ts +126 -0
  128. package/dist/lib/avatar-tone.js +68 -0
  129. package/dist/lib/avatar-tone.js.map +1 -0
  130. package/dist/shell/app-header.js +12 -2
  131. package/dist/shell/app-header.js.map +1 -1
  132. package/dist/shell/app-launcher.js +2 -2
  133. package/dist/shell/app-launcher.js.map +1 -1
  134. package/dist/shell/app-shell.d.ts +2 -0
  135. package/dist/shell/index.d.ts +3 -3
  136. package/dist/shell/shell-context.d.ts +29 -8
  137. package/dist/shell/shell-context.js +6 -20
  138. package/dist/shell/shell-context.js.map +1 -1
  139. package/dist/shell/sidebar-group.js +5 -3
  140. package/dist/shell/sidebar-group.js.map +1 -1
  141. package/dist/shell/sidebar-menu-item.js +15 -7
  142. package/dist/shell/sidebar-menu-item.js.map +1 -1
  143. package/dist/shell/types.d.ts +17 -1
  144. package/dist/shell/types.js.map +1 -1
  145. package/dist/shell/user-menu.js +3 -2
  146. package/dist/shell/user-menu.js.map +1 -1
  147. package/dist/shell/utils.d.ts +36 -24
  148. package/dist/shell/utils.js +55 -20
  149. package/dist/shell/utils.js.map +1 -1
  150. package/dist/shell/workspace-switcher.js +23 -8
  151. package/dist/shell/workspace-switcher.js.map +1 -1
  152. package/dist/tokens.d.ts +165 -17
  153. package/dist/tokens.js +122 -25
  154. package/dist/tokens.js.map +1 -1
  155. package/package.json +8 -5
  156. package/styles.css +312 -22
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/ui/switch.tsx"],"sourcesContent":["\"use client\";\n\nimport * as SwitchPrimitive from \"@radix-ui/react-switch\";\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\n/* Bảng cỡ tách ra ngoài component để không dựng lại object mỗi lần render. */\nconst trackSizes = {\n sm: \"h-4 w-7\",\n md: \"h-5 w-9\"\n} as const;\n\nconst thumbSizes = {\n sm: \"size-3 data-[state=checked]:translate-x-3\",\n md: \"size-4 data-[state=checked]:translate-x-4\"\n} as const;\n\nexport interface SwitchProps extends React.ComponentProps<typeof SwitchPrimitive.Root> {\n /** Cỡ công tắc. Mặc định `md`. */\n size?: \"sm\" | \"md\";\n /** Nhãn đặt cạnh công tắc; có nhãn thì component tự bọc thêm lớp bố cục. */\n label?: string;\n /** Mô tả phụ dưới nhãn. */\n description?: string;\n}\n\n/*\n * Style port từ comitor-ds (đổi gray-* → ash-* theo namespace của package).\n *\n * Rãnh khi BẬT dùng `bg-primary` thay cho `bg-gold-300 dark:bg-gold-300`: bậc gold thô giống\n * nhau ở cả hai theme nên bảng màu dark của package (`--primary: #F5D060`) không được áp dụng,\n * và app đổi `--primary` cho sản phẩm của mình cũng không đổi được công tắc. Không cần biến thể\n * `dark:` nữa vì `--primary` tự đổi theo theme.\n *\n * CÒN TỒN (thuộc giá trị token, không sửa được ở đây): rãnh khi TẮT `bg-ash-300` chỉ đạt 1,62:1\n * so với nền trắng — dưới mức 3:1 mà WCAG 1.4.11 đòi cho phần tử phi văn bản, tức công tắc tắt\n * gần như chìm vào nền ở light mode (dark: ash-700 trên #0E1116 = 1,79:1). Xem phần khuyến nghị\n * token trong docs/quyet-dinh-tich-hop.md.\n */\nfunction Switch({ className, size = \"md\", label, description, id, ...props }: SwitchProps) {\n const generatedId = React.useId();\n const switchId = id ?? generatedId;\n\n const switchElement = (\n <SwitchPrimitive.Root\n data-slot=\"switch\"\n id={switchId}\n className={cn(\n \"peer inline-flex shrink-0 items-center rounded-full bg-ash-300 dark:bg-ash-700 data-[state=checked]:bg-primary\",\n \"transition-colors duration-150 ease-out\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n trackSizes[size],\n className\n )}\n {...props}\n >\n <SwitchPrimitive.Thumb\n data-slot=\"switch-thumb\"\n className={cn(\n \"pointer-events-none block translate-x-0.5 rounded-full bg-white shadow-sm ring-0\",\n \"transition-transform duration-150 ease-out\",\n thumbSizes[size]\n )}\n />\n </SwitchPrimitive.Root>\n );\n\n if (!label && !description) {\n return switchElement;\n }\n\n return (\n <div className=\"flex items-start gap-2.5\">\n {switchElement}\n <div className=\"flex flex-col gap-0.5\">\n {label && (\n <label\n htmlFor={switchId}\n className=\"text-sm font-medium text-foreground cursor-pointer select-none leading-none\"\n >\n {label}\n </label>\n )}\n {description && <p className=\"text-xs text-muted-foreground leading-relaxed\">{description}</p>}\n </div>\n </div>\n );\n}\n\nexport { Switch };\n"],"mappings":";AAyDM,cAkBA,YAlBA;AAvDN,YAAY,qBAAqB;AACjC,YAAY,WAAW;AACvB,SAAS,UAAU;AAGnB,MAAM,aAAa;AAAA,EACjB,IAAI;AAAA,EACJ,IAAI;AACN;AAEA,MAAM,aAAa;AAAA,EACjB,IAAI;AAAA,EACJ,IAAI;AACN;AAwBA,SAAS,OAAO,EAAE,WAAW,OAAO,MAAM,OAAO,aAAa,IAAI,GAAG,MAAM,GAAgB;AACzF,QAAM,cAAc,MAAM,MAAM;AAChC,QAAM,WAAW,MAAM;AAEvB,QAAM,gBACJ;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV,IAAI;AAAA,MACJ,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,WAAW,IAAI;AAAA,QACf;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA,QAAC,gBAAgB;AAAA,QAAhB;AAAA,UACC,aAAU;AAAA,UACV,WAAW;AAAA,YACT;AAAA,YACA;AAAA,YACA,WAAW,IAAI;AAAA,UACjB;AAAA;AAAA,MACF;AAAA;AAAA,EACF;AAGF,MAAI,CAAC,SAAS,CAAC,aAAa;AAC1B,WAAO;AAAA,EACT;AAEA,SACE,qBAAC,SAAI,WAAU,4BACZ;AAAA;AAAA,IACD,qBAAC,SAAI,WAAU,yBACZ;AAAA,eACC;AAAA,QAAC;AAAA;AAAA,UACC,SAAS;AAAA,UACT,WAAU;AAAA,UAET;AAAA;AAAA,MACH;AAAA,MAED,eAAe,oBAAC,OAAE,WAAU,iDAAiD,uBAAY;AAAA,OAC5F;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/ui/switch.tsx"],"sourcesContent":["\"use client\";\n\nimport * as SwitchPrimitive from \"@radix-ui/react-switch\";\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\n/* Bảng cỡ tách ra ngoài component để không dựng lại object mỗi lần render. */\nconst trackSizes = {\n sm: \"h-4 w-7\",\n md: \"h-5 w-9\"\n} as const;\n\nconst thumbSizes = {\n sm: \"size-3 data-[state=checked]:translate-x-3\",\n md: \"size-4 data-[state=checked]:translate-x-4\"\n} as const;\n\nexport interface SwitchProps extends React.ComponentProps<typeof SwitchPrimitive.Root> {\n /** Cỡ công tắc. Mặc định `md`. */\n size?: \"sm\" | \"md\";\n /** Nhãn đặt cạnh công tắc; có nhãn thì component tự bọc thêm lớp bố cục. */\n label?: string;\n /** Mô tả phụ dưới nhãn. */\n description?: string;\n}\n\n/*\n * Style port từ comitor-ds (đổi gray-* → ash-* theo namespace của package).\n *\n * ─ CÔNG TẮC LÀ THÀNH PHẦN PHI VĂN BẢN: BA PHÉP ĐO, KHÔNG PHẢI MỘT ─\n *\n * WCAG 1.4.11 đòi 3:1 cho \"thông tin thị giác cần để nhận ra thành phần và TRẠNG THÁI của nó\".\n * Với công tắc, đó là ba cặp — hỏng cặp nào cũng là hỏng:\n *\n * (1) rãnh ⇄ nền trang → có nhìn thấy cái công tắc không\n * (2) núm ⇄ rãnh → núm đang nằm bên nào\n * (3) rãnh BẬT ⇄ rãnh TẮT → hai trạng thái có khác nhau không (ở đây khác cả màu lẫn vị trí)\n *\n * ─ RÃNH KHI TẮT: `--control-edge` ─\n * Bản đầu dùng `bg-ash-300 dark:bg-ash-700`, hỏng cả (1) lẫn (2): 1,62:1 ở light và 1,79:1 ở dark.\n * Bản kế dùng `bg-muted-foreground` — đúng số nhưng SAI token: `--muted-foreground` là bậc CHỮ,\n * nó bị kéo tối/sáng theo nhu cầu đọc chữ (và đã đổi một lần: #7E8290 → #686C7A / #888D9B), tức\n * ràng buộc \"núm trắng còn nổi\" không có gì bảo vệ. `--control-edge` #7E8290 là token sinh ra\n * đúng cho vai trò này và ghim MỘT giá trị ở cả hai theme nên không cần biến thể `dark:`:\n *\n * rãnh ⇄ nền : 3,83:1 (light, nền trắng) · 4,94:1 (dark, #0E1116) ✓\n * núm ⇄ rãnh : 3,83:1 (light) · 4,94:1 (dark) ✓\n *\n * ─ RÃNH KHI BẬT: `--control-on`, KHÔNG PHẢI `--primary` ─\n * `--primary` (gold #E8B824 ở light) là màu TÔ NỀN cho khối lớn; làm rãnh công tắc thì hỏng cả\n * (1) lẫn (2) ở light — **1,86:1** so với nền trắng và **1,86:1** so với núm trắng — nên trạng\n * thái BẬ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, nhưng núm\n * trắng lại chìm vào rãnh (**1,49:1** ✗). `--control-on` (= `--primary-ink`: gold-500 ở light,\n * = `--primary` ở dark) là token dành đúng cho vai trò \"nền control khi BẬT\", đạt cả hai phía\n * ở cả hai theme:\n *\n * rãnh ⇄ nền : 6,51:1 (light) · 12,67:1 (dark) ✓\n * núm ⇄ rãnh : 6,51:1 (light) · 12,67:1 (dark) ✓\n *\n * ⚠ (3) rãnh BẬT ⇄ rãnh TẮT chỉ 1,70:1 (light) / 2,57:1 (dark) — cố ý và chấp nhận được: hai\n * trạng thái còn khác nhau bằng VỊ TRÍ núm, và cả hai rãnh đều tự đạt ≥3:1 với nền trang lẫn\n * với núm. Không có cặp màu nào vừa đạt (1) vừa đạt (3) khi nền trang là trắng.\n *\n * ─ NÚM: `bg-control-knob`, KHÔNG PHẢI `bg-white` ─\n * Núm mang màu NỀN TRANG (`--control-knob` = `--background`: trắng ở light, #0E1116 ở dark) nên\n * nó luôn tương phản đúng chiều với rãnh — bốn tổ hợp trạng thái × theme ở trên đều là số đo của\n * chính lựa chọn này. `bg-white` cứng chỉ đúng ở light: ở dark nó ăn 1,49:1 với rãnh BẬT.\n *\n * Đúng luật \"chọn ĐỌC ĐƯỢC hơn ĐÚNG BRAND\": rãnh BẬT ở light nay là gold sẫm chứ không phải gold\n * rực của brand book. Đổi lại người dùng nhìn thấy trạng thái. Xem docs/quyet-dinh-tich-hop.md §30.\n * ⚠ App ghi đè `--primary` phải ghi đè kèm `--primary-ink`, nếu không công tắc giữ nguyên gold\n * mặc định — sai màu mà không sai chuẩn, đúng thứ tự ưu tiên của repo.\n */\nfunction Switch({ className, size = \"md\", label, description, id, ...props }: SwitchProps) {\n const generatedId = React.useId();\n const switchId = id ?? generatedId;\n\n const switchElement = (\n <SwitchPrimitive.Root\n data-slot=\"switch\"\n id={switchId}\n className={cn(\n \"peer inline-flex shrink-0 items-center rounded-full bg-control-edge data-[state=checked]:bg-control-on\",\n \"transition-colors duration-150 ease-out\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n trackSizes[size],\n className\n )}\n {...props}\n >\n <SwitchPrimitive.Thumb\n data-slot=\"switch-thumb\"\n className={cn(\n \"pointer-events-none block translate-x-0.5 rounded-full bg-control-knob shadow-sm ring-0\",\n \"transition-transform duration-150 ease-out\",\n thumbSizes[size]\n )}\n />\n </SwitchPrimitive.Root>\n );\n\n if (!label && !description) {\n return switchElement;\n }\n\n return (\n <div className=\"flex items-start gap-2.5\">\n {switchElement}\n <div className=\"flex flex-col gap-0.5\">\n {label && (\n <label\n htmlFor={switchId}\n className=\"text-sm font-medium text-foreground cursor-pointer select-none leading-none\"\n >\n {label}\n </label>\n )}\n {description && <p className=\"text-xs text-muted-foreground leading-relaxed\">{description}</p>}\n </div>\n </div>\n );\n}\n\nexport { Switch };\n"],"mappings":";AA2FM,cAkBA,YAlBA;AAzFN,YAAY,qBAAqB;AACjC,YAAY,WAAW;AACvB,SAAS,UAAU;AAGnB,MAAM,aAAa;AAAA,EACjB,IAAI;AAAA,EACJ,IAAI;AACN;AAEA,MAAM,aAAa;AAAA,EACjB,IAAI;AAAA,EACJ,IAAI;AACN;AA0DA,SAAS,OAAO,EAAE,WAAW,OAAO,MAAM,OAAO,aAAa,IAAI,GAAG,MAAM,GAAgB;AACzF,QAAM,cAAc,MAAM,MAAM;AAChC,QAAM,WAAW,MAAM;AAEvB,QAAM,gBACJ;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV,IAAI;AAAA,MACJ,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,WAAW,IAAI;AAAA,QACf;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA,QAAC,gBAAgB;AAAA,QAAhB;AAAA,UACC,aAAU;AAAA,UACV,WAAW;AAAA,YACT;AAAA,YACA;AAAA,YACA,WAAW,IAAI;AAAA,UACjB;AAAA;AAAA,MACF;AAAA;AAAA,EACF;AAGF,MAAI,CAAC,SAAS,CAAC,aAAa;AAC1B,WAAO;AAAA,EACT;AAEA,SACE,qBAAC,SAAI,WAAU,4BACZ;AAAA;AAAA,IACD,qBAAC,SAAI,WAAU,yBACZ;AAAA,eACC;AAAA,QAAC;AAAA;AAAA,UACC,SAAS;AAAA,UACT,WAAU;AAAA,UAET;AAAA;AAAA,MACH;AAAA,MAED,eAAe,oBAAC,OAAE,WAAU,iDAAiD,uBAAY;AAAA,OAC5F;AAAA,KACF;AAEJ;","names":[]}
@@ -22,6 +22,22 @@ declare function Table({ className, variant, striped, ...props }: TableProps): R
22
22
  declare function TableHeader({ className, ...props }: TableHeaderProps): React.JSX.Element;
23
23
  declare function TableBody({ className, ...props }: TableBodyProps): React.JSX.Element;
24
24
  declare function TableFooter({ className, ...props }: TableFooterProps): React.JSX.Element;
25
+ /**
26
+ * TableRow — dòng bảng. Trạng thái ĐÃ CHỌN (`data-state="selected"`) mang HAI dấu hiệu:
27
+ *
28
+ * 1. nền `bg-muted` — chỉ là tint nhận biết, cố ý nhạt để chữ trong dòng vẫn đọc tốt:
29
+ * 1,07:1 với `--card` ở light · 1,10:1 ở dark. Một mình nó KHÔNG đủ 3:1 của WCAG 1.4.11,
30
+ * và không có tint nào vừa nhạt vừa đủ 3:1 — muốn 3:1 với nền trắng thì tint phải tối
31
+ * tới mức thành mảng đặc (xem lý lẽ y hệt ở đầu `progress.tsx`).
32
+ * 2. vạch `border-b-app-accent-ink` — ĐÂY mới là dấu mang thông tin, và nó đạt ngưỡng:
33
+ * 6,51:1 với `--card`/`--background`, tệ nhất 6,09:1 khi dòng nằm trên `--muted` (light);
34
+ * 12,67:1 / 10,80:1 (dark). Dùng bậc `-ink` chứ không `--app-accent` trần: gold `#E8B824`
35
+ * chỉ 1,86:1 với nền trắng — đúng cái bẫy "token tô NỀN đem vẽ DẤU".
36
+ *
37
+ * Vạch nằm ở CẠNH DƯỚI chứ không phải cạnh trái vì `<tr>` cuộn ngang cùng bảng: mép trái
38
+ * trôi khỏi khung cuộn (và bị ô ghim `position:sticky` của `DataTable` đè lên), còn cạnh dưới
39
+ * trải hết bề ngang nên luôn còn thấy. Bề rộng viền không đổi theo trạng thái ⇒ không xô layout.
40
+ */
25
41
  declare function TableRow({ className, ...props }: TableRowProps): React.JSX.Element;
26
42
  declare function TableHead({ className, ...props }: TableHeadProps): React.JSX.Element;
27
43
  declare function TableCell({ className, ...props }: TableCellProps): React.JSX.Element;
@@ -38,7 +38,7 @@ function TableRow({ className, ...props }) {
38
38
  {
39
39
  "data-slot": "table-row",
40
40
  className: cn(
41
- "border-b border-border transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
41
+ "border-b border-border transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted data-[state=selected]:border-b-app-accent-ink",
42
42
  className
43
43
  ),
44
44
  ...props
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/ui/table.tsx"],"sourcesContent":["\"use client\";\n\nimport type * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\n/**\n * Table — bảng cơ bản, đã bọc sẵn khung cuộn ngang + viền bo `rounded-xl`.\n * `variant=\"striped\"` (hoặc `striped`) tô nền xen kẽ cho hàng chẵn.\n *\n * Đây là primitive tĩnh: sort / chọn dòng / ghim cột nằm ở composite `DataTable`.\n */\n\nexport type TableProps = React.ComponentProps<\"table\"> & {\n variant?: \"default\" | \"striped\";\n /** Alias tiện dụng của `variant=\"striped\"`. */\n striped?: boolean;\n};\nexport type TableHeaderProps = React.ComponentProps<\"thead\">;\nexport type TableBodyProps = React.ComponentProps<\"tbody\">;\nexport type TableFooterProps = React.ComponentProps<\"tfoot\">;\nexport type TableRowProps = React.ComponentProps<\"tr\">;\nexport type TableHeadProps = React.ComponentProps<\"th\">;\nexport type TableCellProps = React.ComponentProps<\"td\">;\nexport type TableCaptionProps = React.ComponentProps<\"caption\">;\n\nfunction Table({ className, variant = \"default\", striped, ...props }: TableProps) {\n const isStriped = striped || variant === \"striped\";\n return (\n <div data-slot=\"table-container\" className=\"w-full overflow-auto rounded-xl border border-border\">\n <table\n data-slot=\"table\"\n className={cn(\n \"w-full caption-bottom text-sm\",\n isStriped && \"[&_tbody_tr:nth-child(even)]:bg-muted/30\",\n className\n )}\n {...props}\n />\n </div>\n );\n}\n\nfunction TableHeader({ className, ...props }: TableHeaderProps) {\n return <thead data-slot=\"table-header\" className={cn(\"bg-muted/60 border-b border-border\", className)} {...props} />;\n}\n\nfunction TableBody({ className, ...props }: TableBodyProps) {\n return <tbody data-slot=\"table-body\" className={cn(\"[&_tr:last-child]:border-0\", className)} {...props} />;\n}\n\nfunction TableFooter({ className, ...props }: TableFooterProps) {\n return (\n <tfoot\n data-slot=\"table-footer\"\n className={cn(\"bg-muted/40 border-t border-border font-medium [&>tr]:last:border-b-0\", className)}\n {...props}\n />\n );\n}\n\nfunction TableRow({ className, ...props }: TableRowProps) {\n return (\n <tr\n data-slot=\"table-row\"\n className={cn(\n \"border-b border-border transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction TableHead({ className, ...props }: TableHeadProps) {\n return (\n <th\n data-slot=\"table-head\"\n className={cn(\n \"h-10 px-4 text-left align-middle text-xs font-bold tracking-[0.08em] uppercase text-muted-foreground whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction TableCell({ className, ...props }: TableCellProps) {\n return (\n <td\n data-slot=\"table-cell\"\n className={cn(\n \"px-4 py-3 align-middle text-foreground whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction TableCaption({ className, ...props }: TableCaptionProps) {\n return (\n <caption data-slot=\"table-caption\" className={cn(\"mt-4 text-sm text-muted-foreground\", className)} {...props} />\n );\n}\n\nexport { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow };\n"],"mappings":";AA6BM;AA1BN,SAAS,UAAU;AAsBnB,SAAS,MAAM,EAAE,WAAW,UAAU,WAAW,SAAS,GAAG,MAAM,GAAe;AAChF,QAAM,YAAY,WAAW,YAAY;AACzC,SACE,oBAAC,SAAI,aAAU,mBAAkB,WAAU,wDACzC;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA,aAAa;AAAA,QACb;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN,GACF;AAEJ;AAEA,SAAS,YAAY,EAAE,WAAW,GAAG,MAAM,GAAqB;AAC9D,SAAO,oBAAC,WAAM,aAAU,gBAAe,WAAW,GAAG,sCAAsC,SAAS,GAAI,GAAG,OAAO;AACpH;AAEA,SAAS,UAAU,EAAE,WAAW,GAAG,MAAM,GAAmB;AAC1D,SAAO,oBAAC,WAAM,aAAU,cAAa,WAAW,GAAG,8BAA8B,SAAS,GAAI,GAAG,OAAO;AAC1G;AAEA,SAAS,YAAY,EAAE,WAAW,GAAG,MAAM,GAAqB;AAC9D,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,yEAAyE,SAAS;AAAA,MAC/F,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,SAAS,SAAS,EAAE,WAAW,GAAG,MAAM,GAAkB;AACxD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,SAAS,UAAU,EAAE,WAAW,GAAG,MAAM,GAAmB;AAC1D,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,SAAS,UAAU,EAAE,WAAW,GAAG,MAAM,GAAmB;AAC1D,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,SAAS,aAAa,EAAE,WAAW,GAAG,MAAM,GAAsB;AAChE,SACE,oBAAC,aAAQ,aAAU,iBAAgB,WAAW,GAAG,sCAAsC,SAAS,GAAI,GAAG,OAAO;AAElH;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/ui/table.tsx"],"sourcesContent":["\"use client\";\n\nimport type * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\n/**\n * Table — bảng cơ bản, đã bọc sẵn khung cuộn ngang + viền bo `rounded-xl`.\n * `variant=\"striped\"` (hoặc `striped`) tô nền xen kẽ cho hàng chẵn.\n *\n * Đây là primitive tĩnh: sort / chọn dòng / ghim cột nằm ở composite `DataTable`.\n */\n\nexport type TableProps = React.ComponentProps<\"table\"> & {\n variant?: \"default\" | \"striped\";\n /** Alias tiện dụng của `variant=\"striped\"`. */\n striped?: boolean;\n};\nexport type TableHeaderProps = React.ComponentProps<\"thead\">;\nexport type TableBodyProps = React.ComponentProps<\"tbody\">;\nexport type TableFooterProps = React.ComponentProps<\"tfoot\">;\nexport type TableRowProps = React.ComponentProps<\"tr\">;\nexport type TableHeadProps = React.ComponentProps<\"th\">;\nexport type TableCellProps = React.ComponentProps<\"td\">;\nexport type TableCaptionProps = React.ComponentProps<\"caption\">;\n\nfunction Table({ className, variant = \"default\", striped, ...props }: TableProps) {\n const isStriped = striped || variant === \"striped\";\n return (\n <div data-slot=\"table-container\" className=\"w-full overflow-auto rounded-xl border border-border\">\n <table\n data-slot=\"table\"\n className={cn(\n \"w-full caption-bottom text-sm\",\n isStriped && \"[&_tbody_tr:nth-child(even)]:bg-muted/30\",\n className\n )}\n {...props}\n />\n </div>\n );\n}\n\nfunction TableHeader({ className, ...props }: TableHeaderProps) {\n return <thead data-slot=\"table-header\" className={cn(\"bg-muted/60 border-b border-border\", className)} {...props} />;\n}\n\nfunction TableBody({ className, ...props }: TableBodyProps) {\n return <tbody data-slot=\"table-body\" className={cn(\"[&_tr:last-child]:border-0\", className)} {...props} />;\n}\n\nfunction TableFooter({ className, ...props }: TableFooterProps) {\n return (\n <tfoot\n data-slot=\"table-footer\"\n className={cn(\"bg-muted/40 border-t border-border font-medium [&>tr]:last:border-b-0\", className)}\n {...props}\n />\n );\n}\n\n/**\n * TableRow — dòng bảng. Trạng thái ĐÃ CHỌN (`data-state=\"selected\"`) mang HAI dấu hiệu:\n *\n * 1. nền `bg-muted` — chỉ là tint nhận biết, cố ý nhạt để chữ trong dòng vẫn đọc tốt:\n * 1,07:1 với `--card` ở light · 1,10:1 ở dark. Một mình nó KHÔNG đủ 3:1 của WCAG 1.4.11,\n * và không có tint nào vừa nhạt vừa đủ 3:1 — muốn 3:1 với nền trắng thì tint phải tối\n * tới mức thành mảng đặc (xem lý lẽ y hệt ở đầu `progress.tsx`).\n * 2. vạch `border-b-app-accent-ink` — ĐÂY mới là dấu mang thông tin, và nó đạt ngưỡng:\n * 6,51:1 với `--card`/`--background`, tệ nhất 6,09:1 khi dòng nằm trên `--muted` (light);\n * 12,67:1 / 10,80:1 (dark). Dùng bậc `-ink` chứ không `--app-accent` trần: gold `#E8B824`\n * chỉ 1,86:1 với nền trắng — đúng cái bẫy \"token tô NỀN đem vẽ DẤU\".\n *\n * Vạch nằm ở CẠNH DƯỚI chứ không phải cạnh trái vì `<tr>` cuộn ngang cùng bảng: mép trái\n * trôi khỏi khung cuộn (và bị ô ghim `position:sticky` của `DataTable` đè lên), còn cạnh dưới\n * trải hết bề ngang nên luôn còn thấy. Bề rộng viền không đổi theo trạng thái ⇒ không xô layout.\n */\nfunction TableRow({ className, ...props }: TableRowProps) {\n return (\n <tr\n data-slot=\"table-row\"\n className={cn(\n \"border-b border-border transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted data-[state=selected]:border-b-app-accent-ink\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction TableHead({ className, ...props }: TableHeadProps) {\n return (\n <th\n data-slot=\"table-head\"\n className={cn(\n \"h-10 px-4 text-left align-middle text-xs font-bold tracking-[0.08em] uppercase text-muted-foreground whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction TableCell({ className, ...props }: TableCellProps) {\n return (\n <td\n data-slot=\"table-cell\"\n className={cn(\n \"px-4 py-3 align-middle text-foreground whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction TableCaption({ className, ...props }: TableCaptionProps) {\n return (\n <caption data-slot=\"table-caption\" className={cn(\"mt-4 text-sm text-muted-foreground\", className)} {...props} />\n );\n}\n\nexport { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow };\n"],"mappings":";AA6BM;AA1BN,SAAS,UAAU;AAsBnB,SAAS,MAAM,EAAE,WAAW,UAAU,WAAW,SAAS,GAAG,MAAM,GAAe;AAChF,QAAM,YAAY,WAAW,YAAY;AACzC,SACE,oBAAC,SAAI,aAAU,mBAAkB,WAAU,wDACzC;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA,aAAa;AAAA,QACb;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN,GACF;AAEJ;AAEA,SAAS,YAAY,EAAE,WAAW,GAAG,MAAM,GAAqB;AAC9D,SAAO,oBAAC,WAAM,aAAU,gBAAe,WAAW,GAAG,sCAAsC,SAAS,GAAI,GAAG,OAAO;AACpH;AAEA,SAAS,UAAU,EAAE,WAAW,GAAG,MAAM,GAAmB;AAC1D,SAAO,oBAAC,WAAM,aAAU,cAAa,WAAW,GAAG,8BAA8B,SAAS,GAAI,GAAG,OAAO;AAC1G;AAEA,SAAS,YAAY,EAAE,WAAW,GAAG,MAAM,GAAqB;AAC9D,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,yEAAyE,SAAS;AAAA,MAC/F,GAAG;AAAA;AAAA,EACN;AAEJ;AAkBA,SAAS,SAAS,EAAE,WAAW,GAAG,MAAM,GAAkB;AACxD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,SAAS,UAAU,EAAE,WAAW,GAAG,MAAM,GAAmB;AAC1D,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,SAAS,UAAU,EAAE,WAAW,GAAG,MAAM,GAAmB;AAC1D,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,SAAS,aAAa,EAAE,WAAW,GAAG,MAAM,GAAsB;AAChE,SACE,oBAAC,aAAQ,aAAU,iBAAgB,WAAW,GAAG,sCAAsC,SAAS,GAAI,GAAG,OAAO;AAElH;","names":[]}
@@ -20,7 +20,7 @@ function TabsList({ variant = "default", className, ...props }) {
20
20
  );
21
21
  }
22
22
  const triggerVariants = {
23
- default: "px-4 py-2.5 text-sm font-medium border-b-2 -mb-px transition-colors duration-150 border-transparent text-muted-foreground hover:text-foreground data-[state=active]:border-gold-300 data-[state=active]:text-foreground",
23
+ default: "px-4 py-2.5 text-sm font-medium border-b-2 -mb-px transition-colors duration-150 border-transparent text-muted-foreground hover:text-foreground data-[state=active]:border-primary-ink data-[state=active]:text-foreground",
24
24
  pills: "px-3 py-1.5 text-sm font-medium rounded-md transition-colors duration-150 text-muted-foreground hover:text-foreground data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm"
25
25
  };
26
26
  function TabsTrigger({ variant = "default", className, ...props }) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/ui/tabs.tsx"],"sourcesContent":["\"use client\";\n\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\";\nimport type * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\n/**\n * Tabs (Radix) — 2 biến thể thị giác:\n * - `default`: gạch chân, thanh active màu gold (dùng cho tab cấp trang).\n * - `pills`: viên bo tròn trên nền muted (dùng cho tab cấp panel/thẻ).\n *\n * `variant` phải truyền cho CẢ `TabsList` lẫn từng `TabsTrigger` — hai cấp không\n * chia sẻ context, đây là thiết kế của comitor-ds, giữ nguyên để tương thích.\n */\n\nexport type TabsVariant = \"default\" | \"pills\";\n\nexport type TabsProps = React.ComponentProps<typeof TabsPrimitive.Root>;\nexport type TabsListProps = React.ComponentProps<typeof TabsPrimitive.List> & { variant?: TabsVariant };\nexport type TabsTriggerProps = React.ComponentProps<typeof TabsPrimitive.Trigger> & { variant?: TabsVariant };\nexport type TabsContentProps = React.ComponentProps<typeof TabsPrimitive.Content>;\n\nfunction TabsRoot({ className, ...props }: TabsProps) {\n return <TabsPrimitive.Root data-slot=\"tabs\" className={cn(\"flex flex-col gap-2\", className)} {...props} />;\n}\n\nconst listVariants: Record<TabsVariant, string> = {\n default: \"border-b border-border\",\n pills: \"bg-muted p-1 rounded-lg\"\n};\n\nfunction TabsList({ variant = \"default\", className, ...props }: TabsListProps) {\n return (\n <TabsPrimitive.List\n data-slot=\"tabs-list\"\n className={cn(\"flex gap-0\", listVariants[variant], className)}\n {...props}\n />\n );\n}\n\nconst triggerVariants: Record<TabsVariant, string> = {\n default:\n \"px-4 py-2.5 text-sm font-medium border-b-2 -mb-px transition-colors duration-150 border-transparent text-muted-foreground hover:text-foreground data-[state=active]:border-gold-300 data-[state=active]:text-foreground\",\n pills:\n \"px-3 py-1.5 text-sm font-medium rounded-md transition-colors duration-150 text-muted-foreground hover:text-foreground data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm\"\n};\n\nfunction TabsTrigger({ variant = \"default\", className, ...props }: TabsTriggerProps) {\n return (\n <TabsPrimitive.Trigger\n data-slot=\"tabs-trigger\"\n className={cn(\n triggerVariants[variant],\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction TabsContent({ className, ...props }: TabsContentProps) {\n return (\n <TabsPrimitive.Content data-slot=\"tabs-content\" className={cn(\"mt-4 flex-1 outline-none\", className)} {...props} />\n );\n}\n\n/**\n * `Tabs` mở cả API rời (Tabs + TabsList/TabsTrigger/TabsContent) lẫn API gộp\n * (Tabs.List / Tabs.Trigger / Tabs.Content) — giữ đúng như comitor-ds.\n */\nconst Tabs = Object.assign(TabsRoot, {\n List: TabsList,\n Trigger: TabsTrigger,\n Content: TabsContent\n});\n\nexport { Tabs, TabsContent, TabsList, TabsTrigger };\n"],"mappings":";AAuBS;AArBT,YAAY,mBAAmB;AAE/B,SAAS,UAAU;AAkBnB,SAAS,SAAS,EAAE,WAAW,GAAG,MAAM,GAAc;AACpD,SAAO,oBAAC,cAAc,MAAd,EAAmB,aAAU,QAAO,WAAW,GAAG,uBAAuB,SAAS,GAAI,GAAG,OAAO;AAC1G;AAEA,MAAM,eAA4C;AAAA,EAChD,SAAS;AAAA,EACT,OAAO;AACT;AAEA,SAAS,SAAS,EAAE,UAAU,WAAW,WAAW,GAAG,MAAM,GAAkB;AAC7E,SACE;AAAA,IAAC,cAAc;AAAA,IAAd;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,cAAc,aAAa,OAAO,GAAG,SAAS;AAAA,MAC3D,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,MAAM,kBAA+C;AAAA,EACnD,SACE;AAAA,EACF,OACE;AACJ;AAEA,SAAS,YAAY,EAAE,UAAU,WAAW,WAAW,GAAG,MAAM,GAAqB;AACnF,SACE;AAAA,IAAC,cAAc;AAAA,IAAd;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT,gBAAgB,OAAO;AAAA,QACvB;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,SAAS,YAAY,EAAE,WAAW,GAAG,MAAM,GAAqB;AAC9D,SACE,oBAAC,cAAc,SAAd,EAAsB,aAAU,gBAAe,WAAW,GAAG,4BAA4B,SAAS,GAAI,GAAG,OAAO;AAErH;AAMA,MAAM,OAAO,OAAO,OAAO,UAAU;AAAA,EACnC,MAAM;AAAA,EACN,SAAS;AAAA,EACT,SAAS;AACX,CAAC;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/ui/tabs.tsx"],"sourcesContent":["\"use client\";\n\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\";\nimport type * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\n/**\n * Tabs (Radix) — 2 biến thể thị giác:\n * - `default`: gạch chân, thanh active màu gold (dùng cho tab cấp trang).\n * - `pills`: viên bo tròn trên nền muted (dùng cho tab cấp panel/thẻ).\n *\n * `variant` phải truyền cho CẢ `TabsList` lẫn từng `TabsTrigger` — hai cấp không\n * chia sẻ context, đây là thiết kế của comitor-ds, giữ nguyên để tương thích.\n */\n\nexport type TabsVariant = \"default\" | \"pills\";\n\nexport type TabsProps = React.ComponentProps<typeof TabsPrimitive.Root>;\nexport type TabsListProps = React.ComponentProps<typeof TabsPrimitive.List> & { variant?: TabsVariant };\nexport type TabsTriggerProps = React.ComponentProps<typeof TabsPrimitive.Trigger> & { variant?: TabsVariant };\nexport type TabsContentProps = React.ComponentProps<typeof TabsPrimitive.Content>;\n\nfunction TabsRoot({ className, ...props }: TabsProps) {\n return <TabsPrimitive.Root data-slot=\"tabs\" className={cn(\"flex flex-col gap-2\", className)} {...props} />;\n}\n\nconst listVariants: Record<TabsVariant, string> = {\n default: \"border-b border-border\",\n pills: \"bg-muted p-1 rounded-lg\"\n};\n\nfunction TabsList({ variant = \"default\", className, ...props }: TabsListProps) {\n return (\n <TabsPrimitive.List\n data-slot=\"tabs-list\"\n className={cn(\"flex gap-0\", listVariants[variant], className)}\n {...props}\n />\n );\n}\n\n/*\n * Vạch active của biến thể `default` là `border-primary-ink`, KHÔNG phải `border-gold-300`:\n * vạch 2px này là thứ DUY NHẤT mang màu để nói \"tab nào đang mở\" (chữ chỉ đổi từ\n * `muted-foreground` sang `foreground`), nên nó là thông tin thị giác — ngưỡng 3:1.\n * gold-300 #E8B824 trên nền trắng chỉ **1,86:1** ✗ (ở dark thì 10,19:1, nên lỗi chỉ lộ ở light).\n * `--primary-ink`: 6,51:1 (light) · 12,67:1 (dark) ✓ — và đi qua token nên app đổi brand là\n * vạch đổi theo, thay vì đóng đinh vào một bậc của thang gold.\n */\nconst triggerVariants: Record<TabsVariant, string> = {\n default:\n \"px-4 py-2.5 text-sm font-medium border-b-2 -mb-px transition-colors duration-150 border-transparent text-muted-foreground hover:text-foreground data-[state=active]:border-primary-ink data-[state=active]:text-foreground\",\n pills:\n \"px-3 py-1.5 text-sm font-medium rounded-md transition-colors duration-150 text-muted-foreground hover:text-foreground data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm\"\n};\n\nfunction TabsTrigger({ variant = \"default\", className, ...props }: TabsTriggerProps) {\n return (\n <TabsPrimitive.Trigger\n data-slot=\"tabs-trigger\"\n className={cn(\n triggerVariants[variant],\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction TabsContent({ className, ...props }: TabsContentProps) {\n return (\n <TabsPrimitive.Content data-slot=\"tabs-content\" className={cn(\"mt-4 flex-1 outline-none\", className)} {...props} />\n );\n}\n\n/**\n * `Tabs` mở cả API rời (Tabs + TabsList/TabsTrigger/TabsContent) lẫn API gộp\n * (Tabs.List / Tabs.Trigger / Tabs.Content) — giữ đúng như comitor-ds.\n */\nconst Tabs = Object.assign(TabsRoot, {\n List: TabsList,\n Trigger: TabsTrigger,\n Content: TabsContent\n});\n\nexport { Tabs, TabsContent, TabsList, TabsTrigger };\n"],"mappings":";AAuBS;AArBT,YAAY,mBAAmB;AAE/B,SAAS,UAAU;AAkBnB,SAAS,SAAS,EAAE,WAAW,GAAG,MAAM,GAAc;AACpD,SAAO,oBAAC,cAAc,MAAd,EAAmB,aAAU,QAAO,WAAW,GAAG,uBAAuB,SAAS,GAAI,GAAG,OAAO;AAC1G;AAEA,MAAM,eAA4C;AAAA,EAChD,SAAS;AAAA,EACT,OAAO;AACT;AAEA,SAAS,SAAS,EAAE,UAAU,WAAW,WAAW,GAAG,MAAM,GAAkB;AAC7E,SACE;AAAA,IAAC,cAAc;AAAA,IAAd;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,cAAc,aAAa,OAAO,GAAG,SAAS;AAAA,MAC3D,GAAG;AAAA;AAAA,EACN;AAEJ;AAUA,MAAM,kBAA+C;AAAA,EACnD,SACE;AAAA,EACF,OACE;AACJ;AAEA,SAAS,YAAY,EAAE,UAAU,WAAW,WAAW,GAAG,MAAM,GAAqB;AACnF,SACE;AAAA,IAAC,cAAc;AAAA,IAAd;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT,gBAAgB,OAAO;AAAA,QACvB;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,SAAS,YAAY,EAAE,WAAW,GAAG,MAAM,GAAqB;AAC9D,SACE,oBAAC,cAAc,SAAd,EAAsB,aAAU,gBAAe,WAAW,GAAG,4BAA4B,SAAS,GAAI,GAAG,OAAO;AAErH;AAMA,MAAM,OAAO,OAAO,OAAO,UAAU;AAAA,EACnC,MAAM;AAAA,EACN,SAAS;AAAA,EACT,SAAS;AACX,CAAC;","names":[]}
@@ -6,7 +6,7 @@ function Textarea({ className, ...props }) {
6
6
  {
7
7
  "data-slot": "textarea",
8
8
  className: cn(
9
- "border-input placeholder:text-muted-foreground aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-colors duration-150 ease-out focus:outline-none focus:ring-2 focus:ring-ring/50 focus:border-gold-300 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
9
+ "border-input placeholder:text-muted-foreground aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive-ink flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-colors duration-150 ease-out focus:outline-none focus:ring-2 focus:ring-ring/50 focus:border-primary-ink disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
10
10
  className
11
11
  ),
12
12
  ...props
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/ui/textarea.tsx"],"sourcesContent":["import type * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\nexport type TextareaProps = React.ComponentProps<\"textarea\">;\n\nfunction Textarea({ className, ...props }: TextareaProps) {\n return (\n <textarea\n data-slot=\"textarea\"\n className={cn(\n \"border-input placeholder:text-muted-foreground aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-colors duration-150 ease-out focus:outline-none focus:ring-2 focus:ring-ring/50 focus:border-gold-300 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport { Textarea };\n"],"mappings":"AAOI;AANJ,SAAS,UAAU;AAInB,SAAS,SAAS,EAAE,WAAW,GAAG,MAAM,GAAkB;AACxD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/ui/textarea.tsx"],"sourcesContent":["import type * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\n/*\n * ─ KHÔNG CÓ `dark:bg-input/30` ─\n * `--input` là alias của `--control-edge`, tức token VIỀN control (hợp đồng màu §3) — không phải\n * bậc tô nền. Bản trước mượn nó tô ruột ô ở dark, và lớp tô ấy SÁNG hơn mọi nền trang nên kéo\n * placeholder `--muted-foreground` (token chữ, hiệu chỉnh theo nền TRANG — §6) xuống dưới ngưỡng\n * 4,5:1 ở TẤT CẢ nền dark, không riêng ca tệ nhất:\n *\n * cũ #888D9B trên `input/30` hợp lên nền: 3,81 (background) · 3,54 (card) · 3,22:1 (muted) ✗\n * nay #888D9B trên chính nền trang : 5,70 (background) · 5,35 (card) · 4,86:1 (muted) ✓\n *\n * Ô vẫn được khoanh vùng bằng `border-input` — 3,83:1 (light) · 4,21–4,94:1 (dark), thừa ngưỡng\n * 3:1 của thành phần phi văn bản, đúng vai trò mà §3 giao cho viền control.\n */\nexport type TextareaProps = React.ComponentProps<\"textarea\">;\n\nfunction Textarea({ className, ...props }: TextareaProps) {\n return (\n <textarea\n data-slot=\"textarea\"\n className={cn(\n \"border-input placeholder:text-muted-foreground aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive-ink flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-colors duration-150 ease-out focus:outline-none focus:ring-2 focus:ring-ring/50 focus:border-primary-ink disabled:cursor-not-allowed disabled:opacity-50 md:text-sm\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport { Textarea };\n"],"mappings":"AAoBI;AAnBJ,SAAS,UAAU;AAiBnB,SAAS,SAAS,EAAE,WAAW,GAAG,MAAM,GAAkB;AACxD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
@@ -4,7 +4,7 @@ import * as TogglePrimitive from "@radix-ui/react-toggle";
4
4
  import { cva } from "class-variance-authority";
5
5
  import { cn } from "../../lib/cn.js";
6
6
  const toggleVariants = cva(
7
- "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background transition-colors duration-150 ease-out aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap",
7
+ "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background transition-colors duration-150 ease-out aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive-ink whitespace-nowrap",
8
8
  {
9
9
  variants: {
10
10
  variant: {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/ui/toggle.tsx"],"sourcesContent":["\"use client\";\n\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\n/** Toggle (Radix) — nút bật/tắt hai trạng thái, dùng cho thanh công cụ. */\nconst toggleVariants = cva(\n \"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background transition-colors duration-150 ease-out aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap\",\n {\n variants: {\n variant: {\n default: \"bg-transparent\",\n outline: \"border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground\"\n },\n size: {\n default: \"h-9 px-2 min-w-9\",\n sm: \"h-8 px-1.5 min-w-8\",\n lg: \"h-10 px-2.5 min-w-10\"\n }\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\"\n }\n }\n);\n\nexport type ToggleVariantProps = VariantProps<typeof toggleVariants>;\nexport type ToggleProps = React.ComponentProps<typeof TogglePrimitive.Root> & ToggleVariantProps;\n\nfunction Toggle({ className, variant, size, ...props }: ToggleProps) {\n return (\n <TogglePrimitive.Root data-slot=\"toggle\" className={cn(toggleVariants({ variant, size, className }))} {...props} />\n );\n}\n\nexport { Toggle, toggleVariants };\n"],"mappings":";AAkCI;AAhCJ,YAAY,qBAAqB;AACjC,SAAS,WAA8B;AAEvC,SAAS,UAAU;AAGnB,MAAM,iBAAiB;AAAA,EACrB;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,SAAS;AAAA,QACP,SAAS;AAAA,QACT,SAAS;AAAA,MACX;AAAA,MACA,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,IAAI;AAAA,QACJ,IAAI;AAAA,MACN;AAAA,IACF;AAAA,IACA,iBAAiB;AAAA,MACf,SAAS;AAAA,MACT,MAAM;AAAA,IACR;AAAA,EACF;AACF;AAKA,SAAS,OAAO,EAAE,WAAW,SAAS,MAAM,GAAG,MAAM,GAAgB;AACnE,SACE,oBAAC,gBAAgB,MAAhB,EAAqB,aAAU,UAAS,WAAW,GAAG,eAAe,EAAE,SAAS,MAAM,UAAU,CAAC,CAAC,GAAI,GAAG,OAAO;AAErH;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/ui/toggle.tsx"],"sourcesContent":["\"use client\";\n\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\n/** Toggle (Radix) — nút bật/tắt hai trạng thái, dùng cho thanh công cụ. */\nconst toggleVariants = cva(\n \"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background transition-colors duration-150 ease-out aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive-ink whitespace-nowrap\",\n {\n variants: {\n variant: {\n default: \"bg-transparent\",\n outline: \"border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground\"\n },\n size: {\n default: \"h-9 px-2 min-w-9\",\n sm: \"h-8 px-1.5 min-w-8\",\n lg: \"h-10 px-2.5 min-w-10\"\n }\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\"\n }\n }\n);\n\nexport type ToggleVariantProps = VariantProps<typeof toggleVariants>;\nexport type ToggleProps = React.ComponentProps<typeof TogglePrimitive.Root> & ToggleVariantProps;\n\nfunction Toggle({ className, variant, size, ...props }: ToggleProps) {\n return (\n <TogglePrimitive.Root data-slot=\"toggle\" className={cn(toggleVariants({ variant, size, className }))} {...props} />\n );\n}\n\nexport { Toggle, toggleVariants };\n"],"mappings":";AAkCI;AAhCJ,YAAY,qBAAqB;AACjC,SAAS,WAA8B;AAEvC,SAAS,UAAU;AAGnB,MAAM,iBAAiB;AAAA,EACrB;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,SAAS;AAAA,QACP,SAAS;AAAA,QACT,SAAS;AAAA,MACX;AAAA,MACA,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,IAAI;AAAA,QACJ,IAAI;AAAA,MACN;AAAA,IACF;AAAA,IACA,iBAAiB;AAAA,MACf,SAAS;AAAA,MACT,MAAM;AAAA,IACR;AAAA,EACF;AACF;AAKA,SAAS,OAAO,EAAE,WAAW,SAAS,MAAM,GAAG,MAAM,GAAgB;AACnE,SACE,oBAAC,gBAAgB,MAAhB,EAAqB,aAAU,UAAS,WAAW,GAAG,eAAe,EAAE,SAAS,MAAM,UAAU,CAAC,CAAC,GAAI,GAAG,OAAO;AAErH;","names":[]}
package/dist/index.d.ts CHANGED
@@ -11,16 +11,16 @@ export { FormField, FormFieldColSpan, FormFieldControlProps, FormFieldProps, SEL
11
11
  export { FormSection, FormSectionAccent, FormSectionProps } from './components/composite/form-section.js';
12
12
  export { InlineEdit, InlineEditProps } from './components/composite/inline-edit.js';
13
13
  export { KeyboardHint, KeyboardHintProps, useIsMac } from './components/composite/keyboard-hint.js';
14
- export { LETTER_AVATAR_TONES, LetterAvatar, LetterAvatarProps, LetterAvatarSize, getLetterAvatarTone } from './components/composite/letter-avatar.js';
14
+ export { LetterAvatar, LetterAvatarProps, LetterAvatarSize, getAvatarToneClasses } from './components/composite/letter-avatar.js';
15
15
  export { PageContainer, PageContainerProps, PageContainerWidth } from './components/composite/page-container.js';
16
16
  export { PageHeader, PageHeaderBreadcrumbItem, PageHeaderLinkProps, PageHeaderProps } from './components/composite/page-header.js';
17
17
  export { RelativeTime, RelativeTimeProps } from './components/composite/relative-time.js';
18
- export { SearchFilterBar, SearchFilterBarProps } from './components/composite/search-filter-bar.js';
18
+ export { DEFAULT_SEARCH_FILTER_BAR_LABELS, SearchFilterBar, SearchFilterBarLabels, SearchFilterBarProps } from './components/composite/search-filter-bar.js';
19
19
  export { StatCard, StatCardDelta, StatCardDeltaDirection, StatCardProps, StatCardTone, statCardIconVariants } from './components/composite/stat-card.js';
20
20
  export { STATUS_TONES, StatusConfig, StatusTone, StatusToneName, getStatusClasses, getStatusConfig, getStatusLabel, sortByStatusPriority } from './components/composite/status-config.js';
21
21
  export { StatusPill, StatusPillProps, StatusPillVariant } from './components/composite/status-pill.js';
22
- export { DEFAULT_PAGE_SIZE_OPTIONS, TablePagination, TablePaginationProps } from './components/composite/table-pagination.js';
23
- export { getVietnameseInitials, hashString, matchesSearch, pickByString, removeVietnameseTones, truncate } from './components/composite/text-utils.js';
22
+ export { DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_TABLE_PAGINATION_LABELS, TablePagination, TablePaginationLabels, TablePaginationProps } from './components/composite/table-pagination.js';
23
+ export { getVietnameseInitials, matchesSearch, normalizeVi, removeVietnameseTones, truncate } from './components/composite/text-utils.js';
24
24
  export { ToastOptions, ToastVariant, Toaster, ToasterProps, dismissToast, toast, toastPromise } from './components/composite/toast.js';
25
25
  export { Accordion, AccordionContent, AccordionContentProps, AccordionItem, AccordionItemProps, AccordionProps, AccordionTrigger, AccordionTriggerProps } from './components/ui/accordion.js';
26
26
  export { Alert, AlertDescription, AlertDescriptionProps, AlertProps, AlertTitle, AlertTitleProps, alertVariants } from './components/ui/alert.js';
@@ -72,7 +72,8 @@ export { Toggle, ToggleProps, ToggleVariantProps, toggleVariants } from './compo
72
72
  export { ToggleGroup, ToggleGroupItem, ToggleGroupItemProps, ToggleGroupProps, ToggleGroupVariantProps } from './components/ui/toggle-group.js';
73
73
  export { Tooltip, TooltipContent, TooltipContentProps, TooltipProps, TooltipProvider, TooltipProviderProps, TooltipTrigger, TooltipTriggerProps } from './components/ui/tooltip.js';
74
74
  export { cn } from './lib/cn.js';
75
- export { ThemeMode, accent, ash, avatarPalette, brandNeutral, chart, gold, layout, semantic, status, typography } from './tokens.js';
75
+ export { ThemeMode, accent, accentForeground, accentInk, ash, brandNeutral, chart, getAvatarToneColors, gold, layout, semantic, status, typography } from './tokens.js';
76
+ export { AvatarToneColors, getAvatarToneIndex } from './lib/avatar-tone.js';
76
77
  export { DateRange } from 'react-day-picker';
77
78
  import 'react';
78
79
  import 'class-variance-authority/types';
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ export * from "./components/composite/form-field.js";
11
11
  export * from "./components/composite/form-section.js";
12
12
  export * from "./components/composite/inline-edit.js";
13
13
  export * from "./components/composite/keyboard-hint.js";
14
- export * from "./components/composite/letter-avatar.js";
14
+ import { getAvatarToneClasses, LetterAvatar } from "./components/composite/letter-avatar.js";
15
15
  export * from "./components/composite/page-container.js";
16
16
  export * from "./components/composite/page-header.js";
17
17
  export * from "./components/composite/relative-time.js";
@@ -20,7 +20,13 @@ export * from "./components/composite/stat-card.js";
20
20
  export * from "./components/composite/status-config.js";
21
21
  export * from "./components/composite/status-pill.js";
22
22
  export * from "./components/composite/table-pagination.js";
23
- export * from "./components/composite/text-utils.js";
23
+ import {
24
+ getVietnameseInitials,
25
+ matchesSearch,
26
+ normalizeVi,
27
+ removeVietnameseTones,
28
+ truncate
29
+ } from "./components/composite/text-utils.js";
24
30
  export * from "./components/composite/toast.js";
25
31
  export * from "./components/ui/accordion.js";
26
32
  export * from "./components/ui/alert.js";
@@ -73,4 +79,13 @@ export * from "./components/ui/toggle-group.js";
73
79
  export * from "./components/ui/tooltip.js";
74
80
  export * from "./lib/cn.js";
75
81
  export * from "./tokens.js";
82
+ export {
83
+ LetterAvatar,
84
+ getAvatarToneClasses,
85
+ getVietnameseInitials,
86
+ matchesSearch,
87
+ normalizeVi,
88
+ removeVietnameseTones,
89
+ truncate
90
+ };
76
91
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Entry chính \"@comitor/ui\" — Tier 1 (primitive) + Tier 2 (composite) + lib + token.\n *\n * Quy tắc của entry này: **chỉ được phụ thuộc peer BẮT BUỘC** (react, react-dom,\n * lucide-react, tailwindcss). Không `next/*`, không `next-themes`, không `recharts`,\n * không `react-hook-form` — đó đều là peer OPTIONAL. Vì `tsup` chạy `bundle: false`\n * (ESM per-file), chỉ cần barrel này re-export một file có import optional peer là\n * mọi app import \"@comitor/ui\" đều buộc phải cài gói đó, nếu không bundler gãy ở\n * bước resolve. Vì vậy 3 nhánh dưới đây nằm ở entry riêng:\n *\n * - Tier 3 shell (next/*, next-themes) → \"@comitor/ui/shell\"\n * - chart (recharts) → \"@comitor/ui/chart\"\n * - binding react-hook-form → \"@comitor/ui/form\"\n *\n * ⚠ Hai file trong `components/ui/` CỐ Ý không có mặt ở đây:\n * - `form.tsx` → xuất qua \"@comitor/ui/form\" (react-hook-form).\n * - `sonner.tsx` → xuất qua \"@comitor/ui/shell\" dưới tên `SonnerToaster`\n * (nó gọi `useTheme()` của next-themes). Bản dùng hằng ngày là composite\n * `Toaster`/`toast` ở Tier 2 — không phụ thuộc next-themes, có sẵn ở entry này.\n *\n * ⚠ Mọi import nội bộ phải giữ đuôi `.js`: output là ESM per-file, Node ESM thuần\n * không tự suy ra phần mở rộng (bundler thì tha, Node thì không).\n */\n\n// ─── Tier 2 — composite (pattern lặp lại ở mọi app Comitor) ───\nexport * from \"./components/composite/avatar-group.js\";\nexport * from \"./components/composite/combobox.js\";\nexport * from \"./components/composite/command-palette.js\";\nexport * from \"./components/composite/confirm-dialog.js\";\nexport * from \"./components/composite/copy-button.js\";\nexport * from \"./components/composite/data-table.js\";\nexport * from \"./components/composite/date-picker.js\";\nexport * from \"./components/composite/empty-state.js\";\nexport * from \"./components/composite/filter-chips.js\";\nexport * from \"./components/composite/form-field.js\";\nexport * from \"./components/composite/form-section.js\";\nexport * from \"./components/composite/inline-edit.js\";\nexport * from \"./components/composite/keyboard-hint.js\";\nexport * from \"./components/composite/letter-avatar.js\";\nexport * from \"./components/composite/page-container.js\";\nexport * from \"./components/composite/page-header.js\";\nexport * from \"./components/composite/relative-time.js\";\nexport * from \"./components/composite/search-filter-bar.js\";\nexport * from \"./components/composite/stat-card.js\";\nexport * from \"./components/composite/status-config.js\";\nexport * from \"./components/composite/status-pill.js\";\nexport * from \"./components/composite/table-pagination.js\";\nexport * from \"./components/composite/text-utils.js\";\nexport * from \"./components/composite/toast.js\";\n// ─── Tier 1 — primitive (shadcn/ui \"new-york\", đã custom theo brand Comitor) ───\nexport * from \"./components/ui/accordion.js\";\nexport * from \"./components/ui/alert.js\";\nexport * from \"./components/ui/alert-dialog.js\";\nexport * from \"./components/ui/aspect-ratio.js\";\nexport * from \"./components/ui/avatar.js\";\nexport * from \"./components/ui/badge.js\";\nexport * from \"./components/ui/breadcrumb.js\";\nexport * from \"./components/ui/button.js\";\nexport * from \"./components/ui/button-group.js\";\nexport * from \"./components/ui/calendar.js\";\nexport * from \"./components/ui/card.js\";\nexport * from \"./components/ui/carousel.js\";\nexport * from \"./components/ui/checkbox.js\";\nexport * from \"./components/ui/collapsible.js\";\nexport * from \"./components/ui/command.js\";\nexport * from \"./components/ui/context-menu.js\";\nexport * from \"./components/ui/dialog.js\";\nexport * from \"./components/ui/drawer.js\";\nexport * from \"./components/ui/dropdown-menu.js\";\nexport * from \"./components/ui/empty.js\";\nexport * from \"./components/ui/field.js\";\nexport * from \"./components/ui/hover-card.js\";\nexport * from \"./components/ui/input.js\";\nexport * from \"./components/ui/input-group.js\";\nexport * from \"./components/ui/input-otp.js\";\nexport * from \"./components/ui/item.js\";\nexport * from \"./components/ui/kbd.js\";\nexport * from \"./components/ui/label.js\";\nexport * from \"./components/ui/menubar.js\";\nexport * from \"./components/ui/navigation-menu.js\";\nexport * from \"./components/ui/pagination.js\";\nexport * from \"./components/ui/popover.js\";\nexport * from \"./components/ui/progress.js\";\nexport * from \"./components/ui/radio-group.js\";\nexport * from \"./components/ui/resizable.js\";\nexport * from \"./components/ui/scroll-area.js\";\nexport * from \"./components/ui/select.js\";\nexport * from \"./components/ui/separator.js\";\nexport * from \"./components/ui/sheet.js\";\nexport * from \"./components/ui/skeleton.js\";\nexport * from \"./components/ui/slider.js\";\nexport * from \"./components/ui/spinner.js\";\nexport * from \"./components/ui/switch.js\";\nexport * from \"./components/ui/table.js\";\nexport * from \"./components/ui/tabs.js\";\nexport * from \"./components/ui/textarea.js\";\nexport * from \"./components/ui/toggle.js\";\nexport * from \"./components/ui/toggle-group.js\";\nexport * from \"./components/ui/tooltip.js\";\n\n// ─── lib + token ───\nexport * from \"./lib/cn.js\";\nexport * from \"./tokens.js\";\n"],"mappings":"AAyBA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAEd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAGd,cAAc;AACd,cAAc;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Entry chính \"@comitor/ui\" — Tier 1 (primitive) + Tier 2 (composite) + lib + token.\n *\n * Quy tắc của entry này: **chỉ được phụ thuộc peer BẮT BUỘC** (react, react-dom,\n * lucide-react, tailwindcss). Không `next/*`, không `next-themes`, không `recharts`,\n * không `react-hook-form` — đó đều là peer OPTIONAL. Vì `tsup` chạy `bundle: false`\n * (ESM per-file), chỉ cần barrel này re-export một file có import optional peer là\n * mọi app import \"@comitor/ui\" đều buộc phải cài gói đó, nếu không bundler gãy ở\n * bước resolve. Vì vậy 3 nhánh dưới đây nằm ở entry riêng:\n *\n * - Tier 3 shell (next/*, next-themes) → \"@comitor/ui/shell\"\n * - chart (recharts) → \"@comitor/ui/chart\"\n * - binding react-hook-form → \"@comitor/ui/form\"\n *\n * ⚠ Hai file trong `components/ui/` CỐ Ý không có mặt ở đây:\n * - `form.tsx` → xuất qua \"@comitor/ui/form\" (react-hook-form).\n * - `sonner.tsx` → xuất qua \"@comitor/ui/shell\" dưới tên `SonnerToaster`\n * (nó gọi `useTheme()` của next-themes). Bản dùng hằng ngày là composite\n * `Toaster`/`toast` ở Tier 2 — không phụ thuộc next-themes, có sẵn ở entry này.\n *\n * ⚠ Mọi import nội bộ phải giữ đuôi `.js`: output là ESM per-file, Node ESM thuần\n * không tự suy ra phần mở rộng (bundler thì tha, Node thì không).\n */\n\n// ─── Tier 2 — composite (pattern lặp lại ở mọi app Comitor) ───\nexport * from \"./components/composite/avatar-group.js\";\nexport * from \"./components/composite/combobox.js\";\nexport * from \"./components/composite/command-palette.js\";\nexport * from \"./components/composite/confirm-dialog.js\";\nexport * from \"./components/composite/copy-button.js\";\nexport * from \"./components/composite/data-table.js\";\nexport * from \"./components/composite/date-picker.js\";\nexport * from \"./components/composite/empty-state.js\";\nexport * from \"./components/composite/filter-chips.js\";\nexport * from \"./components/composite/form-field.js\";\nexport * from \"./components/composite/form-section.js\";\nexport * from \"./components/composite/inline-edit.js\";\nexport * from \"./components/composite/keyboard-hint.js\";\nexport type { LetterAvatarProps, LetterAvatarSize } from \"./components/composite/letter-avatar.js\";\n// Xuất CHỌN LỌC (không `export *`): file này còn `AVATAR_TONES` nhập từ `lib/avatar-tone.js`\n// và các helper nội bộ khác. Liệt kê tường minh thì thêm helper riêng tư vào file KHÔNG tự động\n// biến nó thành API công khai — hàng rào mà `export *` không dựng được.\nexport { getAvatarToneClasses, LetterAvatar } from \"./components/composite/letter-avatar.js\";\nexport * from \"./components/composite/page-container.js\";\nexport * from \"./components/composite/page-header.js\";\nexport * from \"./components/composite/relative-time.js\";\nexport * from \"./components/composite/search-filter-bar.js\";\nexport * from \"./components/composite/stat-card.js\";\nexport * from \"./components/composite/status-config.js\";\nexport * from \"./components/composite/status-pill.js\";\nexport * from \"./components/composite/table-pagination.js\";\n// Xuất CHỌN LỌC vì cùng lý do với `letter-avatar` ở trên: `text-utils.ts` là chỗ dễ bị thêm\n// helper nội bộ nhất của Tier 2 (0.1.0 từng rò `hashString`/`pickByString` ra API công khai\n// đúng theo đường này). Năm hàm dưới đây là hợp đồng, phần còn lại của file thì không.\nexport {\n getVietnameseInitials,\n matchesSearch,\n normalizeVi,\n removeVietnameseTones,\n truncate\n} from \"./components/composite/text-utils.js\";\nexport * from \"./components/composite/toast.js\";\n// ─── Tier 1 — primitive (shadcn/ui \"new-york\", đã custom theo brand Comitor) ───\nexport * from \"./components/ui/accordion.js\";\nexport * from \"./components/ui/alert.js\";\nexport * from \"./components/ui/alert-dialog.js\";\nexport * from \"./components/ui/aspect-ratio.js\";\nexport * from \"./components/ui/avatar.js\";\nexport * from \"./components/ui/badge.js\";\nexport * from \"./components/ui/breadcrumb.js\";\nexport * from \"./components/ui/button.js\";\nexport * from \"./components/ui/button-group.js\";\nexport * from \"./components/ui/calendar.js\";\nexport * from \"./components/ui/card.js\";\nexport * from \"./components/ui/carousel.js\";\nexport * from \"./components/ui/checkbox.js\";\nexport * from \"./components/ui/collapsible.js\";\nexport * from \"./components/ui/command.js\";\nexport * from \"./components/ui/context-menu.js\";\nexport * from \"./components/ui/dialog.js\";\nexport * from \"./components/ui/drawer.js\";\nexport * from \"./components/ui/dropdown-menu.js\";\nexport * from \"./components/ui/empty.js\";\nexport * from \"./components/ui/field.js\";\nexport * from \"./components/ui/hover-card.js\";\nexport * from \"./components/ui/input.js\";\nexport * from \"./components/ui/input-group.js\";\nexport * from \"./components/ui/input-otp.js\";\nexport * from \"./components/ui/item.js\";\nexport * from \"./components/ui/kbd.js\";\nexport * from \"./components/ui/label.js\";\nexport * from \"./components/ui/menubar.js\";\nexport * from \"./components/ui/navigation-menu.js\";\nexport * from \"./components/ui/pagination.js\";\nexport * from \"./components/ui/popover.js\";\nexport * from \"./components/ui/progress.js\";\nexport * from \"./components/ui/radio-group.js\";\nexport * from \"./components/ui/resizable.js\";\nexport * from \"./components/ui/scroll-area.js\";\nexport * from \"./components/ui/select.js\";\nexport * from \"./components/ui/separator.js\";\nexport * from \"./components/ui/sheet.js\";\nexport * from \"./components/ui/skeleton.js\";\nexport * from \"./components/ui/slider.js\";\nexport * from \"./components/ui/spinner.js\";\nexport * from \"./components/ui/switch.js\";\nexport * from \"./components/ui/table.js\";\nexport * from \"./components/ui/tabs.js\";\nexport * from \"./components/ui/textarea.js\";\nexport * from \"./components/ui/toggle.js\";\nexport * from \"./components/ui/toggle-group.js\";\nexport * from \"./components/ui/tooltip.js\";\n\n// ─── lib + token ───\nexport * from \"./lib/cn.js\";\nexport * from \"./tokens.js\";\n"],"mappings":"AAyBA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAKd,SAAS,sBAAsB,oBAAoB;AACnD,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAId;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,cAAc;AAEd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAGd,cAAc;AACd,cAAc;","names":[]}
@@ -0,0 +1,126 @@
1
+ /**
2
+ * NGUỒN SỰ THẬT DUY NHẤT của màu avatar chữ cái — cả gói chỉ có bảng này.
3
+ *
4
+ * File nội bộ (không nằm trong barrel nào): app không import trực tiếp được, mà đi qua đúng
5
+ * hai cửa tuỳ theo chỗ vẽ:
6
+ * • có Tailwind (DOM) → `getAvatarToneClasses(name)` ở `composite/letter-avatar.tsx`
7
+ * • không có Tailwind (canvas, → `getAvatarToneColors(name, mode)` ở `tokens.ts`
8
+ * email HTML, PDF)
9
+ *
10
+ * Vì sao MỘT bảng chứ không phải hai (một bảng class + một bảng hex "ảnh chiếu"): hai bảng viết
11
+ * tay song song thì chỉ cần một lần thêm/xoá/đảo tone là cùng một người ra hai màu khác nhau giữa
12
+ * web và email — mà không có gì báo. Ở đây mỗi tone là MỘT phần tử mang cả chuỗi class lẫn công
13
+ * thức hex tương ứng, nằm sát nhau trên vài dòng liền: thêm tone là thêm cả ba, không quên được.
14
+ * Thứ tự phần tử = chỉ số băm, nên thứ tự cũng chỉ tồn tại ở một chỗ.
15
+ *
16
+ * Vì sao công thức hex là HÀM nhận `palette` chứ không đọc thẳng token: `tokens.ts` là entry
17
+ * `@comitor/ui/tokens`; nếu file này `import { accent } from "../tokens.js"` thì thành vòng import
18
+ * (tokens.ts phải import ngược lại bảng này) — vòng có khởi tạo hằng ở giữa là lỗi TDZ lúc chạy.
19
+ * Nhận bảng màu qua tham số thì phụ thuộc chỉ đi MỘT chiều: tokens.ts → file này.
20
+ */
21
+ /** Đúng 8 tone — chốt ở tầng kiểu: `getAvatarToneIndex` băm theo độ dài này. */
22
+ type AvatarToneTable<T> = readonly [T, T, T, T, T, T, T, T];
23
+ /** Cặp nền + màu chữ (hex đục) của một tone avatar. */
24
+ interface AvatarToneColors {
25
+ background: string;
26
+ foreground: string;
27
+ }
28
+ /**
29
+ * Các bậc màu mà công thức hex bên dưới đọc vào — `tokens.ts` truyền xuống một bộ cho light,
30
+ * một bộ cho dark. Khai tường minh bằng `string` (không `typeof gold`) vì bản light và bản dark
31
+ * là hai bộ giá trị khác nhau, không cùng một kiểu literal.
32
+ */
33
+ interface AvatarTonePalette {
34
+ /** Thang gold — cần bậc 100 · 500 · 600. */
35
+ gold: {
36
+ readonly 100: string;
37
+ readonly 500: string;
38
+ readonly 600: string;
39
+ };
40
+ /** Thang xám ash — cần bậc 100 · 500 · 700. */
41
+ ash: {
42
+ readonly 100: string;
43
+ readonly 500: string;
44
+ readonly 700: string;
45
+ };
46
+ /** Trung tính brand — mực và giấy. */
47
+ brandNeutral: {
48
+ readonly ink: string;
49
+ readonly inkSoft: string;
50
+ readonly paper: string;
51
+ };
52
+ /** Màu phụ ở vai trò TÔ NỀN (`accent.light` hoặc `accent.dark`). */
53
+ accent: {
54
+ readonly teal: string;
55
+ readonly green: string;
56
+ readonly red: string;
57
+ readonly yellow: string;
58
+ readonly navy: string;
59
+ };
60
+ /** Cùng màu phụ đó ở vai trò CHỮ trên nền trang (`accentInk.light` hoặc `accentInk.dark`). */
61
+ accentInk: {
62
+ readonly teal: string;
63
+ readonly green: string;
64
+ readonly red: string;
65
+ readonly yellow: string;
66
+ readonly navy: string;
67
+ };
68
+ /** Nền mà tint hợp thành lên — `--card` của theme. */
69
+ card: string;
70
+ }
71
+ interface AvatarTone {
72
+ /** Vai trò DOM: chuỗi class token tĩnh (kèm biến thể `dark:`). */
73
+ readonly classes: string;
74
+ /** Vai trò canvas/email/PDF: đúng những gì `classes` nói, tính ra hex đục ở light. */
75
+ readonly light: (p: AvatarTonePalette) => AvatarToneColors;
76
+ /** Nhánh `dark:` của `classes`. */
77
+ readonly dark: (p: AvatarTonePalette) => AvatarToneColors;
78
+ }
79
+ /**
80
+ * 8 tone deterministic. Phía class TẤT CẢ là token tĩnh — không ghép chuỗi lúc chạy (`bg-${c}-100`)
81
+ * vì Tailwind quét tĩnh, class ghép động sẽ không có mặt trong CSS của app; cũng không dùng hex
82
+ * inline vì như vậy là mất dark mode.
83
+ *
84
+ * ─ VÌ SAO LÀ MÀU PHỤ + CHART CHỨ KHÔNG PHẢI TOKEN SEMANTIC ─
85
+ * Đây là màu ĐỊNH DANH (phân biệt người), không phải màu TRẠNG THÁI — gán `--success` cho một con
86
+ * người là sai nghĩa. Bộ màu phụ + bảng chart là nhóm token DUY NHẤT có đủ 8 sắc khác nhau và có
87
+ * bản riêng cho dark, nên vẫn đổi theo theme và app vẫn ghi đè được.
88
+ *
89
+ * ─ TƯƠNG PHẢN: VÌ SAO BA TONE PHẢI HẠ XUỐNG BẬC INK Ở LIGHT ─
90
+ * Chữ initials tuy `aria-hidden` nhưng người sáng mắt vẫn đọc, nên vẫn phải đạt AA 4,5:1. Bản đầu
91
+ * dùng thẳng `text-teal/green/red` trên nền tint 15% và HỎNG ở light (đo theo WCAG, nền tint đã
92
+ * trộn với `--card` trắng):
93
+ *
94
+ * teal #1F8A8B trên bg-teal/15 = 3,44:1 ✗
95
+ * green #2EA56A trên bg-green/15 = 2,69:1 ✗ (dưới cả ngưỡng 3:1)
96
+ * red #D64545 trên bg-red/15 = 3,57:1 ✗
97
+ *
98
+ * Sửa đúng khuôn `Badge`: dùng bậc `--x-ink` cho light, giữ token đã nâng sáng cho dark. Đo lại
99
+ * trên đúng hex mà `getAvatarToneColors` trả ra (styles.css đã đóng băng bậc ink, xem dưới):
100
+ *
101
+ * teal #1A6668 trên #DDEDEE = 5,54:1 ✓ · dark #2DD4BF trên #193435 = 7,12:1 ✓
102
+ * green #216A48 trên #E0F2E9 = 5,60:1 ✓ · dark #4ADE80 trên #1E362C = 7,44:1 ✓
103
+ * red #9A3537 trên #F9E3E3 = 5,83:1 ✓ · dark #F87171 trên #38252A = 5,18:1 ✓
104
+ *
105
+ * (Bộ số 5,56 / 5,58 / 7,10 / 7,47 / 5,17 ghi ở bản trước là kết quả của chuỗi `color-mix` TRƯỚC
106
+ * khi hex được đóng băng vào token — lệch ở chữ, không lệch ở màu.)
107
+ *
108
+ * Class viết thẳng `text-teal-ink` / `text-green-ink` / `text-red-ink` / `text-yellow-ink` chứ
109
+ * KHÔNG nhắc lại chuỗi `color-mix(...)` — hai cách cho ra đúng cùng một hex (styles.css đã đóng
110
+ * băng kết quả: #1A6668 · #216A48 · #9A3537), và ở `.dark` thì `--teal-ink` vốn trỏ về `--teal`
111
+ * nên không cần biến thể `dark:` nào. Nhờ vậy tên trong class khớp đúng tên trong công thức hex
112
+ * ngay bên dưới (`p.accentInk.teal`) — soi hai vế cạnh nhau là thấy lệch ngay.
113
+ */
114
+ declare const AVATAR_TONES: AvatarToneTable<AvatarTone>;
115
+ /**
116
+ * Chỉ số tone theo tên — phép chọn màu avatar DUY NHẤT của cả gói.
117
+ *
118
+ * Khoá băm là **tên hiển thị** đã `trim` + hạ chữ thường theo locale `vi-VN` (không phải
119
+ * email/slug: chỗ nào hiển thị avatar cũng chắc chắn có tên, còn email/slug thì không — lấy khoá
120
+ * khác nhau ở từng chỗ là cách nhanh nhất để cùng một người ra hai màu).
121
+ */
122
+ declare function getAvatarToneIndex(name: string): number;
123
+ /** Tone (cả class lẫn công thức hex) ứng với một tên. */
124
+ declare function getAvatarTone(name: string): AvatarTone;
125
+
126
+ export { AVATAR_TONES, type AvatarToneColors, type AvatarTonePalette, getAvatarTone, getAvatarToneIndex };
@@ -0,0 +1,68 @@
1
+ function tintOver(color, over, alpha) {
2
+ const channel = (hex, i) => Number.parseInt(hex.slice(1 + i * 2, 3 + i * 2), 16);
3
+ const mixed = [0, 1, 2].map((i) => Math.round(channel(color, i) * alpha + channel(over, i) * (1 - alpha)));
4
+ return `#${mixed.map((v) => v.toString(16).padStart(2, "0")).join("")}`.toUpperCase();
5
+ }
6
+ const AVATAR_TONES = [
7
+ {
8
+ classes: "bg-gold-100 text-gold-600 dark:bg-gold-500/30 dark:text-gold-100",
9
+ light: (p) => ({ background: p.gold[100], foreground: p.gold[600] }),
10
+ dark: (p) => ({ background: tintOver(p.gold[500], p.card, 0.3), foreground: p.gold[100] })
11
+ },
12
+ {
13
+ classes: "bg-teal/15 text-teal-ink",
14
+ light: (p) => ({ background: tintOver(p.accent.teal, p.card, 0.15), foreground: p.accentInk.teal }),
15
+ dark: (p) => ({ background: tintOver(p.accent.teal, p.card, 0.15), foreground: p.accentInk.teal })
16
+ },
17
+ {
18
+ classes: "bg-green/15 text-green-ink",
19
+ light: (p) => ({ background: tintOver(p.accent.green, p.card, 0.15), foreground: p.accentInk.green }),
20
+ dark: (p) => ({ background: tintOver(p.accent.green, p.card, 0.15), foreground: p.accentInk.green })
21
+ },
22
+ {
23
+ classes: "bg-red/15 text-red-ink",
24
+ light: (p) => ({ background: tintOver(p.accent.red, p.card, 0.15), foreground: p.accentInk.red }),
25
+ dark: (p) => ({ background: tintOver(p.accent.red, p.card, 0.15), foreground: p.accentInk.red })
26
+ },
27
+ {
28
+ classes: "bg-yellow/25 text-yellow-ink dark:bg-yellow/20",
29
+ light: (p) => ({ background: tintOver(p.accent.yellow, p.card, 0.25), foreground: p.accentInk.yellow }),
30
+ dark: (p) => ({ background: tintOver(p.accent.yellow, p.card, 0.2), foreground: p.accentInk.yellow })
31
+ },
32
+ {
33
+ // chart-3 = navy ở light, = navy-ink (bậc sáng) ở dark — nền tint cũng đi theo đúng màu đó.
34
+ classes: "bg-chart-3/15 text-chart-3",
35
+ light: (p) => ({ background: tintOver(p.accent.navy, p.card, 0.15), foreground: p.accentInk.navy }),
36
+ dark: (p) => ({ background: tintOver(p.accentInk.navy, p.card, 0.15), foreground: p.accentInk.navy })
37
+ },
38
+ {
39
+ classes: "bg-ash-100 text-ash-700 dark:bg-ash-500/25 dark:text-ash-100",
40
+ light: (p) => ({ background: p.ash[100], foreground: p.ash[700] }),
41
+ dark: (p) => ({ background: tintOver(p.ash[500], p.card, 0.25), foreground: p.ash[100] })
42
+ },
43
+ {
44
+ classes: "bg-ink/10 text-ink-soft dark:bg-paper/15 dark:text-paper",
45
+ light: (p) => ({ background: tintOver(p.brandNeutral.ink, p.card, 0.1), foreground: p.brandNeutral.inkSoft }),
46
+ dark: (p) => ({ background: tintOver(p.brandNeutral.paper, p.card, 0.15), foreground: p.brandNeutral.paper })
47
+ }
48
+ ];
49
+ function hashString(input) {
50
+ let hash = 2166136261;
51
+ for (let i = 0; i < input.length; i++) {
52
+ hash ^= input.charCodeAt(i);
53
+ hash = Math.imul(hash, 16777619);
54
+ }
55
+ return Math.abs(hash | 0);
56
+ }
57
+ function getAvatarToneIndex(name) {
58
+ return hashString(name.trim().toLocaleLowerCase("vi-VN")) % AVATAR_TONES.length;
59
+ }
60
+ function getAvatarTone(name) {
61
+ return AVATAR_TONES[getAvatarToneIndex(name)] ?? AVATAR_TONES[0];
62
+ }
63
+ export {
64
+ AVATAR_TONES,
65
+ getAvatarTone,
66
+ getAvatarToneIndex
67
+ };
68
+ //# sourceMappingURL=avatar-tone.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/avatar-tone.ts"],"sourcesContent":["/**\n * NGUỒN SỰ THẬT DUY NHẤT của màu avatar chữ cái — cả gói chỉ có bảng này.\n *\n * File nội bộ (không nằm trong barrel nào): app không import trực tiếp được, mà đi qua đúng\n * hai cửa tuỳ theo chỗ vẽ:\n * • có Tailwind (DOM) → `getAvatarToneClasses(name)` ở `composite/letter-avatar.tsx`\n * • không có Tailwind (canvas, → `getAvatarToneColors(name, mode)` ở `tokens.ts`\n * email HTML, PDF)\n *\n * Vì sao MỘT bảng chứ không phải hai (một bảng class + một bảng hex \"ảnh chiếu\"): hai bảng viết\n * tay song song thì chỉ cần một lần thêm/xoá/đảo tone là cùng một người ra hai màu khác nhau giữa\n * web và email — mà không có gì báo. Ở đây mỗi tone là MỘT phần tử mang cả chuỗi class lẫn công\n * thức hex tương ứng, nằm sát nhau trên vài dòng liền: thêm tone là thêm cả ba, không quên được.\n * Thứ tự phần tử = chỉ số băm, nên thứ tự cũng chỉ tồn tại ở một chỗ.\n *\n * Vì sao công thức hex là HÀM nhận `palette` chứ không đọc thẳng token: `tokens.ts` là entry\n * `@comitor/ui/tokens`; nếu file này `import { accent } from \"../tokens.js\"` thì thành vòng import\n * (tokens.ts phải import ngược lại bảng này) — vòng có khởi tạo hằng ở giữa là lỗi TDZ lúc chạy.\n * Nhận bảng màu qua tham số thì phụ thuộc chỉ đi MỘT chiều: tokens.ts → file này.\n */\n\n/** Đúng 8 tone — chốt ở tầng kiểu: `getAvatarToneIndex` băm theo độ dài này. */\ntype AvatarToneTable<T> = readonly [T, T, T, T, T, T, T, T];\n\n/** Cặp nền + màu chữ (hex đục) của một tone avatar. */\nexport interface AvatarToneColors {\n background: string;\n foreground: string;\n}\n\n/**\n * Các bậc màu mà công thức hex bên dưới đọc vào — `tokens.ts` truyền xuống một bộ cho light,\n * một bộ cho dark. Khai tường minh bằng `string` (không `typeof gold`) vì bản light và bản dark\n * là hai bộ giá trị khác nhau, không cùng một kiểu literal.\n */\nexport interface AvatarTonePalette {\n /** Thang gold — cần bậc 100 · 500 · 600. */\n gold: { readonly 100: string; readonly 500: string; readonly 600: string };\n /** Thang xám ash — cần bậc 100 · 500 · 700. */\n ash: { readonly 100: string; readonly 500: string; readonly 700: string };\n /** Trung tính brand — mực và giấy. */\n brandNeutral: { readonly ink: string; readonly inkSoft: string; readonly paper: string };\n /** Màu phụ ở vai trò TÔ NỀN (`accent.light` hoặc `accent.dark`). */\n accent: {\n readonly teal: string;\n readonly green: string;\n readonly red: string;\n readonly yellow: string;\n readonly navy: string;\n };\n /** Cùng màu phụ đó ở vai trò CHỮ trên nền trang (`accentInk.light` hoặc `accentInk.dark`). */\n accentInk: {\n readonly teal: string;\n readonly green: string;\n readonly red: string;\n readonly yellow: string;\n readonly navy: string;\n };\n /** Nền mà tint hợp thành lên — `--card` của theme. */\n card: string;\n}\n\ninterface AvatarTone {\n /** Vai trò DOM: chuỗi class token tĩnh (kèm biến thể `dark:`). */\n readonly classes: string;\n /** Vai trò canvas/email/PDF: đúng những gì `classes` nói, tính ra hex đục ở light. */\n readonly light: (p: AvatarTonePalette) => AvatarToneColors;\n /** Nhánh `dark:` của `classes`. */\n readonly dark: (p: AvatarTonePalette) => AvatarToneColors;\n}\n\n/**\n * Trộn `color` phủ `alpha` lên nền `over`, trả về hex đục.\n *\n * Tái hiện đúng phép mà trình duyệt làm với `bg-teal/15`: Tailwind sinh ra màu CÓ alpha, còn nền\n * phía dưới (`--card`) mới quyết định hex cuối cùng. Nơi không có CSS thì không có gì để hợp\n * thành, nên hex phải mang sẵn kết quả đã trộn.\n */\nfunction tintOver(color: string, over: string, alpha: number): string {\n const channel = (hex: string, i: number) => Number.parseInt(hex.slice(1 + i * 2, 3 + i * 2), 16);\n const mixed = [0, 1, 2].map((i) => Math.round(channel(color, i) * alpha + channel(over, i) * (1 - alpha)));\n return `#${mixed.map((v) => v.toString(16).padStart(2, \"0\")).join(\"\")}`.toUpperCase();\n}\n\n/**\n * 8 tone deterministic. Phía class TẤT CẢ là token tĩnh — không ghép chuỗi lúc chạy (`bg-${c}-100`)\n * vì Tailwind quét tĩnh, class ghép động sẽ không có mặt trong CSS của app; cũng không dùng hex\n * inline vì như vậy là mất dark mode.\n *\n * ─ VÌ SAO LÀ MÀU PHỤ + CHART CHỨ KHÔNG PHẢI TOKEN SEMANTIC ─\n * Đây là màu ĐỊNH DANH (phân biệt người), không phải màu TRẠNG THÁI — gán `--success` cho một con\n * người là sai nghĩa. Bộ màu phụ + bảng chart là nhóm token DUY NHẤT có đủ 8 sắc khác nhau và có\n * bản riêng cho dark, nên vẫn đổi theo theme và app vẫn ghi đè được.\n *\n * ─ TƯƠNG PHẢN: VÌ SAO BA TONE PHẢI HẠ XUỐNG BẬC INK Ở LIGHT ─\n * Chữ initials tuy `aria-hidden` nhưng người sáng mắt vẫn đọc, nên vẫn phải đạt AA 4,5:1. Bản đầu\n * dùng thẳng `text-teal/green/red` trên nền tint 15% và HỎNG ở light (đo theo WCAG, nền tint đã\n * trộn với `--card` trắng):\n *\n * teal #1F8A8B trên bg-teal/15 = 3,44:1 ✗\n * green #2EA56A trên bg-green/15 = 2,69:1 ✗ (dưới cả ngưỡng 3:1)\n * red #D64545 trên bg-red/15 = 3,57:1 ✗\n *\n * Sửa đúng khuôn `Badge`: dùng bậc `--x-ink` cho light, giữ token đã nâng sáng cho dark. Đo lại\n * trên đúng hex mà `getAvatarToneColors` trả ra (styles.css đã đóng băng bậc ink, xem dưới):\n *\n * teal #1A6668 trên #DDEDEE = 5,54:1 ✓ · dark #2DD4BF trên #193435 = 7,12:1 ✓\n * green #216A48 trên #E0F2E9 = 5,60:1 ✓ · dark #4ADE80 trên #1E362C = 7,44:1 ✓\n * red #9A3537 trên #F9E3E3 = 5,83:1 ✓ · dark #F87171 trên #38252A = 5,18:1 ✓\n *\n * (Bộ số 5,56 / 5,58 / 7,10 / 7,47 / 5,17 ghi ở bản trước là kết quả của chuỗi `color-mix` TRƯỚC\n * khi hex được đóng băng vào token — lệch ở chữ, không lệch ở màu.)\n *\n * Class viết thẳng `text-teal-ink` / `text-green-ink` / `text-red-ink` / `text-yellow-ink` chứ\n * KHÔNG nhắc lại chuỗi `color-mix(...)` — hai cách cho ra đúng cùng một hex (styles.css đã đóng\n * băng kết quả: #1A6668 · #216A48 · #9A3537), và ở `.dark` thì `--teal-ink` vốn trỏ về `--teal`\n * nên không cần biến thể `dark:` nào. Nhờ vậy tên trong class khớp đúng tên trong công thức hex\n * ngay bên dưới (`p.accentInk.teal`) — soi hai vế cạnh nhau là thấy lệch ngay.\n */\nexport const AVATAR_TONES: AvatarToneTable<AvatarTone> = [\n {\n classes: \"bg-gold-100 text-gold-600 dark:bg-gold-500/30 dark:text-gold-100\",\n light: (p) => ({ background: p.gold[100], foreground: p.gold[600] }),\n dark: (p) => ({ background: tintOver(p.gold[500], p.card, 0.3), foreground: p.gold[100] })\n },\n {\n classes: \"bg-teal/15 text-teal-ink\",\n light: (p) => ({ background: tintOver(p.accent.teal, p.card, 0.15), foreground: p.accentInk.teal }),\n dark: (p) => ({ background: tintOver(p.accent.teal, p.card, 0.15), foreground: p.accentInk.teal })\n },\n {\n classes: \"bg-green/15 text-green-ink\",\n light: (p) => ({ background: tintOver(p.accent.green, p.card, 0.15), foreground: p.accentInk.green }),\n dark: (p) => ({ background: tintOver(p.accent.green, p.card, 0.15), foreground: p.accentInk.green })\n },\n {\n classes: \"bg-red/15 text-red-ink\",\n light: (p) => ({ background: tintOver(p.accent.red, p.card, 0.15), foreground: p.accentInk.red }),\n dark: (p) => ({ background: tintOver(p.accent.red, p.card, 0.15), foreground: p.accentInk.red })\n },\n {\n classes: \"bg-yellow/25 text-yellow-ink dark:bg-yellow/20\",\n light: (p) => ({ background: tintOver(p.accent.yellow, p.card, 0.25), foreground: p.accentInk.yellow }),\n dark: (p) => ({ background: tintOver(p.accent.yellow, p.card, 0.2), foreground: p.accentInk.yellow })\n },\n {\n // chart-3 = navy ở light, = navy-ink (bậc sáng) ở dark — nền tint cũng đi theo đúng màu đó.\n classes: \"bg-chart-3/15 text-chart-3\",\n light: (p) => ({ background: tintOver(p.accent.navy, p.card, 0.15), foreground: p.accentInk.navy }),\n dark: (p) => ({ background: tintOver(p.accentInk.navy, p.card, 0.15), foreground: p.accentInk.navy })\n },\n {\n classes: \"bg-ash-100 text-ash-700 dark:bg-ash-500/25 dark:text-ash-100\",\n light: (p) => ({ background: p.ash[100], foreground: p.ash[700] }),\n dark: (p) => ({ background: tintOver(p.ash[500], p.card, 0.25), foreground: p.ash[100] })\n },\n {\n classes: \"bg-ink/10 text-ink-soft dark:bg-paper/15 dark:text-paper\",\n light: (p) => ({ background: tintOver(p.brandNeutral.ink, p.card, 0.1), foreground: p.brandNeutral.inkSoft }),\n dark: (p) => ({ background: tintOver(p.brandNeutral.paper, p.card, 0.15), foreground: p.brandNeutral.paper })\n }\n];\n\n/**\n * Hash chuỗi → số nguyên không âm (biến thể FNV-1a 32 bit).\n *\n * Nội bộ, KHÔNG xuất ra API: công dụng duy nhất của nó là chọn tone avatar ngay dưới đây. Xuất\n * một hàm băm ra ngoài chỉ mời app dựng lại phép chọn tone của riêng nó — đúng thứ sinh ra chuyện\n * cùng một người hai màu. Chạy trên code point (không phải byte) để tên có dấu vẫn ổn định giữa\n * server và client.\n */\nfunction hashString(input: string): number {\n let hash = 0x811c9dc5;\n for (let i = 0; i < input.length; i++) {\n hash ^= input.charCodeAt(i);\n hash = Math.imul(hash, 0x01000193);\n }\n return Math.abs(hash | 0);\n}\n\n/**\n * Chỉ số tone theo tên — phép chọn màu avatar DUY NHẤT của cả gói.\n *\n * Khoá băm là **tên hiển thị** đã `trim` + hạ chữ thường theo locale `vi-VN` (không phải\n * email/slug: chỗ nào hiển thị avatar cũng chắc chắn có tên, còn email/slug thì không — lấy khoá\n * khác nhau ở từng chỗ là cách nhanh nhất để cùng một người ra hai màu).\n */\nexport function getAvatarToneIndex(name: string): number {\n return hashString(name.trim().toLocaleLowerCase(\"vi-VN\")) % AVATAR_TONES.length;\n}\n\n/** Tone (cả class lẫn công thức hex) ứng với một tên. */\nexport function getAvatarTone(name: string): AvatarTone {\n // `noUncheckedIndexedAccess` bật → phải có nhánh lùi, dù chỉ số luôn nằm trong [0, 8).\n return AVATAR_TONES[getAvatarToneIndex(name)] ?? AVATAR_TONES[0];\n}\n"],"mappings":"AA8EA,SAAS,SAAS,OAAe,MAAc,OAAuB;AACpE,QAAM,UAAU,CAAC,KAAa,MAAc,OAAO,SAAS,IAAI,MAAM,IAAI,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE;AAC/F,QAAM,QAAQ,CAAC,GAAG,GAAG,CAAC,EAAE,IAAI,CAAC,MAAM,KAAK,MAAM,QAAQ,OAAO,CAAC,IAAI,QAAQ,QAAQ,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC;AACzG,SAAO,IAAI,MAAM,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,YAAY;AACtF;AAqCO,MAAM,eAA4C;AAAA,EACvD;AAAA,IACE,SAAS;AAAA,IACT,OAAO,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,GAAG,GAAG,YAAY,EAAE,KAAK,GAAG,EAAE;AAAA,IAClE,MAAM,CAAC,OAAO,EAAE,YAAY,SAAS,EAAE,KAAK,GAAG,GAAG,EAAE,MAAM,GAAG,GAAG,YAAY,EAAE,KAAK,GAAG,EAAE;AAAA,EAC1F;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO,CAAC,OAAO,EAAE,YAAY,SAAS,EAAE,OAAO,MAAM,EAAE,MAAM,IAAI,GAAG,YAAY,EAAE,UAAU,KAAK;AAAA,IACjG,MAAM,CAAC,OAAO,EAAE,YAAY,SAAS,EAAE,OAAO,MAAM,EAAE,MAAM,IAAI,GAAG,YAAY,EAAE,UAAU,KAAK;AAAA,EAClG;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO,CAAC,OAAO,EAAE,YAAY,SAAS,EAAE,OAAO,OAAO,EAAE,MAAM,IAAI,GAAG,YAAY,EAAE,UAAU,MAAM;AAAA,IACnG,MAAM,CAAC,OAAO,EAAE,YAAY,SAAS,EAAE,OAAO,OAAO,EAAE,MAAM,IAAI,GAAG,YAAY,EAAE,UAAU,MAAM;AAAA,EACpG;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO,CAAC,OAAO,EAAE,YAAY,SAAS,EAAE,OAAO,KAAK,EAAE,MAAM,IAAI,GAAG,YAAY,EAAE,UAAU,IAAI;AAAA,IAC/F,MAAM,CAAC,OAAO,EAAE,YAAY,SAAS,EAAE,OAAO,KAAK,EAAE,MAAM,IAAI,GAAG,YAAY,EAAE,UAAU,IAAI;AAAA,EAChG;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO,CAAC,OAAO,EAAE,YAAY,SAAS,EAAE,OAAO,QAAQ,EAAE,MAAM,IAAI,GAAG,YAAY,EAAE,UAAU,OAAO;AAAA,IACrG,MAAM,CAAC,OAAO,EAAE,YAAY,SAAS,EAAE,OAAO,QAAQ,EAAE,MAAM,GAAG,GAAG,YAAY,EAAE,UAAU,OAAO;AAAA,EACrG;AAAA,EACA;AAAA;AAAA,IAEE,SAAS;AAAA,IACT,OAAO,CAAC,OAAO,EAAE,YAAY,SAAS,EAAE,OAAO,MAAM,EAAE,MAAM,IAAI,GAAG,YAAY,EAAE,UAAU,KAAK;AAAA,IACjG,MAAM,CAAC,OAAO,EAAE,YAAY,SAAS,EAAE,UAAU,MAAM,EAAE,MAAM,IAAI,GAAG,YAAY,EAAE,UAAU,KAAK;AAAA,EACrG;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO,CAAC,OAAO,EAAE,YAAY,EAAE,IAAI,GAAG,GAAG,YAAY,EAAE,IAAI,GAAG,EAAE;AAAA,IAChE,MAAM,CAAC,OAAO,EAAE,YAAY,SAAS,EAAE,IAAI,GAAG,GAAG,EAAE,MAAM,IAAI,GAAG,YAAY,EAAE,IAAI,GAAG,EAAE;AAAA,EACzF;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO,CAAC,OAAO,EAAE,YAAY,SAAS,EAAE,aAAa,KAAK,EAAE,MAAM,GAAG,GAAG,YAAY,EAAE,aAAa,QAAQ;AAAA,IAC3G,MAAM,CAAC,OAAO,EAAE,YAAY,SAAS,EAAE,aAAa,OAAO,EAAE,MAAM,IAAI,GAAG,YAAY,EAAE,aAAa,MAAM;AAAA,EAC7G;AACF;AAUA,SAAS,WAAW,OAAuB;AACzC,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAQ,MAAM,WAAW,CAAC;AAC1B,WAAO,KAAK,KAAK,MAAM,QAAU;AAAA,EACnC;AACA,SAAO,KAAK,IAAI,OAAO,CAAC;AAC1B;AASO,SAAS,mBAAmB,MAAsB;AACvD,SAAO,WAAW,KAAK,KAAK,EAAE,kBAAkB,OAAO,CAAC,IAAI,aAAa;AAC3E;AAGO,SAAS,cAAc,MAA0B;AAEtD,SAAO,aAAa,mBAAmB,IAAI,CAAC,KAAK,aAAa,CAAC;AACjE;","names":[]}
@@ -1,12 +1,13 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { Menu, PanelLeft, Search } from "lucide-react";
4
+ import { useIsMac } from "../components/composite/keyboard-hint.js";
4
5
  import { Kbd } from "../components/ui/kbd.js";
5
6
  import { Tooltip, TooltipContent, TooltipTrigger } from "../components/ui/tooltip.js";
6
7
  import { cn } from "../lib/cn.js";
7
8
  import { AppLauncher } from "./app-launcher.js";
8
9
  import { NotificationsButton } from "./notifications-button.js";
9
- import { useIsMac, useShell } from "./shell-context.js";
10
+ import { useShell } from "./shell-context.js";
10
11
  import { ThemeToggle } from "./theme-toggle.js";
11
12
  import { UserMenu } from "./user-menu.js";
12
13
  function AppHeader({
@@ -85,7 +86,16 @@ function AppHeader({
85
86
  className: cn(
86
87
  "hidden h-8 w-52 items-center gap-2 rounded-md border border-input bg-muted/40 px-2.5 sm:flex",
87
88
  "text-sm text-muted-foreground transition-colors duration-150 ease-out",
88
- "hover:border-gold-300 hover:bg-muted"
89
+ // Viền control lúc hover phải ĐẬM HƠN lúc nghỉ, không nhạt đi. `border-gold-300`
90
+ // (#E8B824) chỉ được 1,86:1 với `--background` và 1,73:1 với `--muted` (chính là
91
+ // nền mà `hover:bg-muted` đổi sang) ở light — ô tìm gần như MẤT VIỀN đúng lúc rê
92
+ // chuột vào, dưới xa mức 3:1 WCAG 1.4.11 đòi cho viền control. `gold-300` lại là
93
+ // bậc thang màu thô, không đổi theo theme, nên ở dark cùng giá trị đó thành
94
+ // 10,19:1 — mỗi theme một hành vi.
95
+ // `--control-edge-strong` là bậc viền control lúc hover, đo đúng cho vai trò này:
96
+ // 10,55:1 / 9,86:1 (light, trên `--background` / `--muted`) · 11,65:1 / 9,93:1
97
+ // (dark). Lúc nghỉ vẫn `border-input` = 3,83:1 (light) · 4,94:1 (dark) ✓.
98
+ "hover:border-control-edge-strong hover:bg-muted"
89
99
  ),
90
100
  children: [
91
101
  /* @__PURE__ */ jsx(Search, { className: "size-3.5 shrink-0" }),