@comitor/ui 1.8.0 → 1.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ component ghép sẵn và khung ứng dụng (shell) kiểu workspace × app.
|
|
|
8
8
|
- Token brand nằm ở **`styles.css`** — đổi token, bump version, mọi app nhận cùng một thay đổi.
|
|
9
9
|
- Server-component-friendly: `"use client"` chỉ nằm ở file thật sự cần.
|
|
10
10
|
|
|
11
|
-
> Trạng thái: `1.
|
|
11
|
+
> Trạng thái: `1.8.0` — 52 file primitive (Tier 1) · 31 file composite (Tier 2) · 25 file shell
|
|
12
12
|
> (Tier 3) · 4 file chart · 5 file uploader. API chốt từ `1.0.0`.
|
|
13
13
|
>
|
|
14
14
|
> Tài liệu đầy đủ: **https://design-system.comitor.ai** —
|
|
@@ -167,7 +167,8 @@ riêng kèm demo, bảng prop và cạm bẫy của nó:
|
|
|
167
167
|
| **Tier 1 — primitive** · 52 file · `@comitor/ui` | shadcn/ui style `new-york`, đã tùy biến token theo brand Comitor | https://design-system.comitor.ai/components |
|
|
168
168
|
| **Tier 2 — composite** · 31 file · `@comitor/ui` | Giá trị thật của gói: pattern lặp ở mọi app, ghép từ nhiều primitive (`DataTable`, `PageHeader`, `Combobox`, `DatePicker`, `StatCard`…) | https://design-system.comitor.ai/composites |
|
|
169
169
|
| **Tier 3 — shell** · 25 file · `@comitor/ui/shell` | Khung ứng dụng workspace × app: sidebar, header, theme, ba trục hiển thị (tương phản · mật độ · cỡ chữ) | https://design-system.comitor.ai/shell |
|
|
170
|
-
| **chart** · 4 file · `@comitor/ui/chart` | Lớp shadcn bọc recharts, cùng ba bản dựng sẵn theo brand | https://design-system.comitor.ai/components/chart |
|
|
170
|
+
| **chart** · 4 file · `@comitor/ui/chart` | Lớp shadcn bọc recharts, cùng ba bản dựng sẵn theo brand. ⚠ Ba wrapper `Comitor*Chart` **mặc định TẮT** hiệu ứng vẽ dần (`isAnimationActive = false`) — bật lại là biểu đồ kẹt ở khung đầu: có trục, có lưới, KHÔNG có dữ liệu. Mặc định đó KHÔNG áp cho `<ChartContainer>` + recharts do app tự lắp | https://design-system.comitor.ai/components/chart |
|
|
171
|
+
| **uploader** · 5 file · `@comitor/ui/uploader` | `FileUpload` (tệp) · `ImageUploadField` (ảnh) · `useFileUpload` — kéo thả, xem trước, tiến độ. Peer optional: `@uppy/core` + `@uppy/xhr-upload` | https://design-system.comitor.ai/components |
|
|
171
172
|
| **form** · `@comitor/ui/form` | Binding shadcn ⇄ react-hook-form | https://design-system.comitor.ai/components/form |
|
|
172
173
|
|
|
173
174
|
**Cơ chế `labels` — đổi ngôn ngữ mà không fork component.** Chuỗi hiển thị mặc định của gói là **tiếng
|
|
@@ -234,8 +235,10 @@ Mỗi bản publish đi qua cùng một chuỗi cổng (`prepublishOnly` nối c
|
|
|
234
235
|
không làm đỏ — nó là bản duyệt và đã được chấp nhận với đúng những con số dưới ngưỡng ở §6; bắt
|
|
235
236
|
nó là bắt một quyết định, và một cổng lúc nào cũng đỏ thì lần sau không ai chạy nữa.
|
|
236
237
|
⚠ **Cổng xanh vì thế KHÔNG có nghĩa bảng mặc định đạt** — đọc khối "BẢNG MÀU MẶC ĐỊNH: N cặp dưới
|
|
237
|
-
ngưỡng" mà nó in ra
|
|
238
|
-
|
|
238
|
+
ngưỡng" mà nó in ra, cùng khối "Bốn lớp nhiều nhất" ngay dưới — **đọc output, đừng đọc con số ở
|
|
239
|
+
đây**: nó đổi theo mỗi lần chạm token. Hình dạng của khoản nợ thì ổn định, và đó mới là thứ đáng
|
|
240
|
+
nhớ: gần ba phần tư nằm ở đúng `text-muted-foreground`, nên "chặn được chúng" nghĩa là đổi MỘT
|
|
241
|
+
token và đổi vẻ ngoài mọi app. Trước `1.6.0` cổng còn không ĐO bảng mặc định lần nào,
|
|
239
242
|
tức nó in ✓ cho một bảng nó chưa từng nhìn.
|
|
240
243
|
Ở bảng tương phản cao thì mọi loại phát hiện đều `exit 1`, kể cả lớp dùng bảng màu mặc định của
|
|
241
244
|
Tailwind và miễn trừ đã thừa; miễn trừ mỗi mục bắt buộc kèm lý do bằng chữ.
|
|
@@ -45,7 +45,7 @@ function AvatarGroup({
|
|
|
45
45
|
{
|
|
46
46
|
type: "button",
|
|
47
47
|
className: cn(
|
|
48
|
-
"rounded-full ring-2 transition-transform hover:z-10 hover:-translate-y-0.5 focus-visible:z-10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-foreground",
|
|
48
|
+
"inline-flex rounded-full ring-2 transition-transform hover:z-10 hover:-translate-y-0.5 focus-visible:z-10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-foreground",
|
|
49
49
|
surfaceClassName
|
|
50
50
|
),
|
|
51
51
|
children: /* @__PURE__ */ jsx(LetterAvatar, { name: item.name, src: item.src, size, title: void 0 })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/composite/avatar-group.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ComponentProps } from \"react\";\nimport { cn } from \"../../lib/cn.js\";\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"../ui/tooltip.js\";\nimport { LetterAvatar, type LetterAvatarSize } from \"./letter-avatar.js\";\n\nexport interface AvatarGroupItem {\n id: string;\n name: string;\n src?: string | null;\n /** Dòng phụ trong tooltip (vd email, vai trò) */\n caption?: string;\n}\n\nexport interface AvatarGroupProps extends Omit<ComponentProps<\"div\">, \"children\"> {\n items: AvatarGroupItem[];\n /** Số avatar hiện tối đa; phần dư gộp vào ô \"+k\" (mặc định 4) */\n max?: number;\n size?: LetterAvatarSize;\n /** Bấm vào ô \"+k\" — vd mở danh sách đầy đủ */\n onOverflowClick?: () => void;\n /**\n * Nhãn `sr-only` của ô \"+k\" khi ô đó bấm được — mặc định\n * `(count) => \"Xem tất cả \" + count + \" thành viên\"`.\n *\n * Là HÀM chứ không phải chuỗi mẫu vì có số chèn vào giữa câu: trật tự từ quanh con số khác nhau\n * theo ngôn ngữ, nên mọi cách để gói tự ghép chuỗi đều là chỗ chắc chắn dịch sai.\n *\n * Nhận TỔNG số thành viên (`items.length`), không phải phần dư đang bị giấu: mặt nút đã in sẵn\n * \"+k\" là phần dư rồi, còn câu này nói ra HÀNH ĐỘNG — bấm vào là xem hết cả nhóm.\n *\n * Khác `label` ở tầm phủ: `label` đặt tên cho CẢ NHÓM (`aria-label` của `role=\"group\"`), đọc lên\n * khi con trỏ bước vào nhóm; `overflowLabel` đặt tên cho MỘT nút bên trong, đọc lên khi Tab tới\n * đúng ô \"+k\". App đa ngôn ngữ phải dịch cả hai — dịch mỗi `label` thì nhóm mang tên tiếng Anh\n * mà nút cuối vẫn xưng tiếng Việt.\n */\n overflowLabel?: (count: number) => string;\n /** Nhãn của cả nhóm cho trình đọc màn hình (mặc định \"Thành viên\") — xem `overflowLabel` để phân biệt */\n label?: string;\n /**\n * BỀ MẶT mà nhóm đang đứng lên — cho cả lớp nền đục dưới mỗi avatar lẫn màu vòng ngăn cách.\n *\n * Vì sao phải khai: tone avatar là các tint CÓ ALPHA (`bg-teal/15`, `bg-yellow/25`…), mà nhóm\n * thì cố tình cho chúng chồng lên nhau `-space-x-2`. Không có lớp đục ở dưới thì tint của avatar\n * trên hợp thành với MÀU CỦA AVATAR DƯỚI chứ không phải với nền trang — ba khuôn mặt lẫn vào\n * nhau đúng ở chỗ chồng. Đây cũng chính là bề mặt mà `getAvatarToneColors` giả định khi tính hex\n * đục cho canvas/email (`tintOver(…, p.card, …)`), nên mặc định là `--card`.\n *\n * Đặt nhóm lên bề mặt khác thì khai lại cả hai class, vd `\"bg-sidebar ring-sidebar\"`.\n */\n surfaceClassName?: string;\n}\n\n/**\n * Độ chồng theo CỠ, không phải một hằng số cho mọi cỡ.\n *\n * Mẫu Avatar stack ở comitor-ds là `-space-x-2` trên avatar 40px — tức chồng **20%**. Áp thẳng\n * `-space-x-2` cho mọi cỡ thì ở `xs` (24px) thành chồng 33%, và cộng thêm vòng 2px nữa là phần bị\n * che ăn vào đúng chữ cái đầu của initials: \"ĐH\" ở 10px bắt đầu cách mép ~5,5px, trong khi mép che\n * đã tới 10px. Giữ tỉ lệ ~20% thì mọi cỡ đọc được mà mật độ vẫn y như bản duyệt.\n *\n * xs 24px → 4px (17%) · sm 28px → 6px (21%) · md 36px → 8px (22%)\n * lg 44px → 10px (23%) · xl 56px → 12px (21%)\n */\nconst overlapClasses: Record<LetterAvatarSize, string> = {\n xs: \"-space-x-1\",\n sm: \"-space-x-1.5\",\n md: \"-space-x-2\",\n lg: \"-space-x-2.5\",\n xl: \"-space-x-3\"\n};\n\nconst overflowSizeClasses: Record<LetterAvatarSize, string> = {\n xs: \"size-6 text-[10px]\",\n sm: \"size-7 text-xs\",\n md: \"size-9 text-xs\",\n lg: \"size-11 text-sm\",\n xl: \"size-14 text-base\"\n};\n\n/**\n * Chồng avatar + ô \"+k\" — dùng cho người thực hiện (Tasks), người tham gia (Chat), người theo dõi (CRM).\n *\n * Tooltip hiện tên đầy đủ vì avatar chồng nhau thì initials bị che một phần; không có tooltip\n * thì nhóm avatar chỉ là trang trí, người dùng không biết ai đang ở trong việc.\n *\n * Mỗi avatar là một nút focusable (Tab tới được) và mang tên thành viên cho trình đọc màn hình\n * — xem chú thích ở trigger bên dưới.\n */\nexport function AvatarGroup({\n items,\n max = 4,\n size = \"sm\",\n onOverflowClick,\n overflowLabel = (count) => `Xem tất cả ${count} thành viên`,\n label = \"Thành viên\",\n surfaceClassName = \"bg-card ring-card\",\n className,\n ...props\n}: AvatarGroupProps) {\n const visible = items.slice(0, Math.max(0, max));\n const overflow = items.length - visible.length;\n\n return (\n <TooltipProvider delayDuration={200}>\n <div\n data-slot=\"avatar-group\"\n role=\"group\"\n aria-label={label}\n className={cn(\"flex items-center\", overlapClasses[size], className)}\n {...props}\n >\n {visible.map((item) => (\n <Tooltip key={item.id}>\n <TooltipTrigger asChild>\n {/* Trigger phải FOCUSABLE: `<span>` thường chỉ nhận sự kiện chuột nên tooltip\n (và cả tên thành viên) biến mất với người dùng bàn phím. Nút không có\n onClick là chủ ý — nó chỉ mở tooltip; tên accessible lấy từ `sr-only`\n bên trong LetterAvatar nên không cần aria-label lặp lại. */}\n {/* Viền focus dùng `foreground` chứ không `ring`: `--ring` là gold — #E8B824 chỉ\n 1,86:1 trên nền trang ở light, dưới 3:1 mà WCAG 1.4.11/2.4.11 đòi cho chỉ báo\n focus (ở dark #F5D060 mới đạt 12,67:1, tức hỏng đúng một nửa số người dùng).\n `outline-foreground` đạt 18,91:1 (light) / 17,66:1 (dark) — cùng lối với viền\n focus của dòng trong `DataTable`. */}\n {/* Nền đục nằm DƯỚI tint có alpha của avatar — xem `surfaceClassName`. Vòng\n `ring-2` cùng màu bề mặt chỉ tách hai avatar ra, nó KHÔNG che được phần tint\n chồng lên nhau bên trong vòng. */}\n <button\n type=\"button\"\n className={cn(\n \"rounded-full ring-2 transition-transform hover:z-10 hover:-translate-y-0.5 focus-visible:z-10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-foreground\",\n surfaceClassName\n )}\n >\n <LetterAvatar name={item.name} src={item.src} size={size} title={undefined} />\n </button>\n </TooltipTrigger>\n <TooltipContent>\n <span className=\"font-medium\">{item.name}</span>\n {/* `opacity-80` ở đây ĐẠT chuẩn và đã đo, không phải chỗ sót: Tooltip là\n `bg-foreground text-background`, nên chữ mờ 80% ra 12,15:1 (light) / 9,85:1\n (dark) — trên xa ngưỡng 4,5:1. Tooltip cũng không tương tác được. */}\n {item.caption && <span className=\"block text-xs opacity-80\">{item.caption}</span>}\n </TooltipContent>\n </Tooltip>\n ))}\n\n {overflow > 0 &&\n (onOverflowClick ? (\n <button\n type=\"button\"\n onClick={onOverflowClick}\n className={cn(\n // `surfaceClassName` đứng TRƯỚC để `bg-muted` ngay sau đè lại phần nền của nó:\n // ô \"+k\" vốn đã đục, ở đây chỉ mượn màu VÒNG của bề mặt.\n surfaceClassName,\n \"z-10 inline-flex shrink-0 items-center justify-center rounded-full bg-muted font-medium text-muted-foreground ring-2 transition-colors hover:bg-accent hover:text-accent-foreground\",\n overflowSizeClasses[size]\n )}\n >\n +{overflow}\n <span className=\"sr-only\">{overflowLabel(items.length)}</span>\n </button>\n ) : (\n <span\n className={cn(\n surfaceClassName,\n \"z-10 inline-flex shrink-0 items-center justify-center rounded-full bg-muted font-medium text-muted-foreground ring-2\",\n overflowSizeClasses[size]\n )}\n >\n +{overflow}\n </span>\n ))}\n </div>\n </TooltipProvider>\n );\n}\n"],"mappings":";AAuIgB,cAGJ,YAHI;AApIhB,SAAS,UAAU;AACnB,SAAS,SAAS,gBAAgB,iBAAiB,sBAAsB;AACzE,SAAS,oBAA2C;AA4DpD,MAAM,iBAAmD;AAAA,EACvD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAEA,MAAM,sBAAwD;AAAA,EAC5D,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAWO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA,MAAM;AAAA,EACN,OAAO;AAAA,EACP;AAAA,EACA,gBAAgB,CAAC,UAAU,wBAAc,KAAK;AAAA,EAC9C,QAAQ;AAAA,EACR,mBAAmB;AAAA,EACnB;AAAA,EACA,GAAG;AACL,GAAqB;AACnB,QAAM,UAAU,MAAM,MAAM,GAAG,KAAK,IAAI,GAAG,GAAG,CAAC;AAC/C,QAAM,WAAW,MAAM,SAAS,QAAQ;AAExC,SACE,oBAAC,mBAAgB,eAAe,KAC9B;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,MAAK;AAAA,MACL,cAAY;AAAA,MACZ,WAAW,GAAG,qBAAqB,eAAe,IAAI,GAAG,SAAS;AAAA,MACjE,GAAG;AAAA,MAEH;AAAA,gBAAQ,IAAI,CAAC,SACZ,qBAAC,WACC;AAAA,8BAAC,kBAAe,SAAO,MAarB;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,WAAW;AAAA,gBACT;AAAA,gBACA;AAAA,cACF;AAAA,cAEA,8BAAC,gBAAa,MAAM,KAAK,MAAM,KAAK,KAAK,KAAK,MAAY,OAAO,QAAW;AAAA;AAAA,UAC9E,GACF;AAAA,UACA,qBAAC,kBACC;AAAA,gCAAC,UAAK,WAAU,eAAe,eAAK,MAAK;AAAA,YAIxC,KAAK,WAAW,oBAAC,UAAK,WAAU,4BAA4B,eAAK,SAAQ;AAAA,aAC5E;AAAA,aA9BY,KAAK,EA+BnB,CACD;AAAA,QAEA,WAAW,MACT,kBACC;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS;AAAA,YACT,WAAW;AAAA;AAAA;AAAA,cAGT;AAAA,cACA;AAAA,cACA,oBAAoB,IAAI;AAAA,YAC1B;AAAA,YACD;AAAA;AAAA,cACG;AAAA,cACF,oBAAC,UAAK,WAAU,WAAW,wBAAc,MAAM,MAAM,GAAE;AAAA;AAAA;AAAA,QACzD,IAEA;AAAA,UAAC;AAAA;AAAA,YACC,WAAW;AAAA,cACT;AAAA,cACA;AAAA,cACA,oBAAoB,IAAI;AAAA,YAC1B;AAAA,YACD;AAAA;AAAA,cACG;AAAA;AAAA;AAAA,QACJ;AAAA;AAAA;AAAA,EAEN,GACF;AAEJ;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/composite/avatar-group.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ComponentProps } from \"react\";\nimport { cn } from \"../../lib/cn.js\";\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"../ui/tooltip.js\";\nimport { LetterAvatar, type LetterAvatarSize } from \"./letter-avatar.js\";\n\nexport interface AvatarGroupItem {\n id: string;\n name: string;\n src?: string | null;\n /** Dòng phụ trong tooltip (vd email, vai trò) */\n caption?: string;\n}\n\nexport interface AvatarGroupProps extends Omit<ComponentProps<\"div\">, \"children\"> {\n items: AvatarGroupItem[];\n /** Số avatar hiện tối đa; phần dư gộp vào ô \"+k\" (mặc định 4) */\n max?: number;\n size?: LetterAvatarSize;\n /** Bấm vào ô \"+k\" — vd mở danh sách đầy đủ */\n onOverflowClick?: () => void;\n /**\n * Nhãn `sr-only` của ô \"+k\" khi ô đó bấm được — mặc định\n * `(count) => \"Xem tất cả \" + count + \" thành viên\"`.\n *\n * Là HÀM chứ không phải chuỗi mẫu vì có số chèn vào giữa câu: trật tự từ quanh con số khác nhau\n * theo ngôn ngữ, nên mọi cách để gói tự ghép chuỗi đều là chỗ chắc chắn dịch sai.\n *\n * Nhận TỔNG số thành viên (`items.length`), không phải phần dư đang bị giấu: mặt nút đã in sẵn\n * \"+k\" là phần dư rồi, còn câu này nói ra HÀNH ĐỘNG — bấm vào là xem hết cả nhóm.\n *\n * Khác `label` ở tầm phủ: `label` đặt tên cho CẢ NHÓM (`aria-label` của `role=\"group\"`), đọc lên\n * khi con trỏ bước vào nhóm; `overflowLabel` đặt tên cho MỘT nút bên trong, đọc lên khi Tab tới\n * đúng ô \"+k\". App đa ngôn ngữ phải dịch cả hai — dịch mỗi `label` thì nhóm mang tên tiếng Anh\n * mà nút cuối vẫn xưng tiếng Việt.\n */\n overflowLabel?: (count: number) => string;\n /** Nhãn của cả nhóm cho trình đọc màn hình (mặc định \"Thành viên\") — xem `overflowLabel` để phân biệt */\n label?: string;\n /**\n * BỀ MẶT mà nhóm đang đứng lên — cho cả lớp nền đục dưới mỗi avatar lẫn màu vòng ngăn cách.\n *\n * Vì sao phải khai: tone avatar là các tint CÓ ALPHA (`bg-teal/15`, `bg-yellow/25`…), mà nhóm\n * thì cố tình cho chúng chồng lên nhau `-space-x-2`. Không có lớp đục ở dưới thì tint của avatar\n * trên hợp thành với MÀU CỦA AVATAR DƯỚI chứ không phải với nền trang — ba khuôn mặt lẫn vào\n * nhau đúng ở chỗ chồng. Đây cũng chính là bề mặt mà `getAvatarToneColors` giả định khi tính hex\n * đục cho canvas/email (`tintOver(…, p.card, …)`), nên mặc định là `--card`.\n *\n * Đặt nhóm lên bề mặt khác thì khai lại cả hai class, vd `\"bg-sidebar ring-sidebar\"`.\n */\n surfaceClassName?: string;\n}\n\n/**\n * Độ chồng theo CỠ, không phải một hằng số cho mọi cỡ.\n *\n * Mẫu Avatar stack ở comitor-ds là `-space-x-2` trên avatar 40px — tức chồng **20%**. Áp thẳng\n * `-space-x-2` cho mọi cỡ thì ở `xs` (24px) thành chồng 33%, và cộng thêm vòng 2px nữa là phần bị\n * che ăn vào đúng chữ cái đầu của initials: \"ĐH\" ở 10px bắt đầu cách mép ~5,5px, trong khi mép che\n * đã tới 10px. Giữ tỉ lệ ~20% thì mọi cỡ đọc được mà mật độ vẫn y như bản duyệt.\n *\n * xs 24px → 4px (17%) · sm 28px → 6px (21%) · md 36px → 8px (22%)\n * lg 44px → 10px (23%) · xl 56px → 12px (21%)\n */\nconst overlapClasses: Record<LetterAvatarSize, string> = {\n xs: \"-space-x-1\",\n sm: \"-space-x-1.5\",\n md: \"-space-x-2\",\n lg: \"-space-x-2.5\",\n xl: \"-space-x-3\"\n};\n\nconst overflowSizeClasses: Record<LetterAvatarSize, string> = {\n xs: \"size-6 text-[10px]\",\n sm: \"size-7 text-xs\",\n md: \"size-9 text-xs\",\n lg: \"size-11 text-sm\",\n xl: \"size-14 text-base\"\n};\n\n/**\n * Chồng avatar + ô \"+k\" — dùng cho người thực hiện (Tasks), người tham gia (Chat), người theo dõi (CRM).\n *\n * Tooltip hiện tên đầy đủ vì avatar chồng nhau thì initials bị che một phần; không có tooltip\n * thì nhóm avatar chỉ là trang trí, người dùng không biết ai đang ở trong việc.\n *\n * Mỗi avatar là một nút focusable (Tab tới được) và mang tên thành viên cho trình đọc màn hình\n * — xem chú thích ở trigger bên dưới.\n */\nexport function AvatarGroup({\n items,\n max = 4,\n size = \"sm\",\n onOverflowClick,\n overflowLabel = (count) => `Xem tất cả ${count} thành viên`,\n label = \"Thành viên\",\n surfaceClassName = \"bg-card ring-card\",\n className,\n ...props\n}: AvatarGroupProps) {\n const visible = items.slice(0, Math.max(0, max));\n const overflow = items.length - visible.length;\n\n return (\n <TooltipProvider delayDuration={200}>\n <div\n data-slot=\"avatar-group\"\n role=\"group\"\n aria-label={label}\n className={cn(\"flex items-center\", overlapClasses[size], className)}\n {...props}\n >\n {visible.map((item) => (\n <Tooltip key={item.id}>\n <TooltipTrigger asChild>\n {/* Trigger phải FOCUSABLE: `<span>` thường chỉ nhận sự kiện chuột nên tooltip\n (và cả tên thành viên) biến mất với người dùng bàn phím. Nút không có\n onClick là chủ ý — nó chỉ mở tooltip; tên accessible lấy từ `sr-only`\n bên trong LetterAvatar nên không cần aria-label lặp lại. */}\n {/* Viền focus dùng `foreground` chứ không `ring`: `--ring` là gold — #E8B824 chỉ\n 1,86:1 trên nền trang ở light, dưới 3:1 mà WCAG 1.4.11/2.4.11 đòi cho chỉ báo\n focus (ở dark #F5D060 mới đạt 12,67:1, tức hỏng đúng một nửa số người dùng).\n `outline-foreground` đạt 18,91:1 (light) / 17,66:1 (dark) — cùng lối với viền\n focus của dòng trong `DataTable`. */}\n {/* Nền đục nằm DƯỚI tint có alpha của avatar — xem `surfaceClassName`. Vòng\n `ring-2` cùng màu bề mặt chỉ tách hai avatar ra, nó KHÔNG che được phần tint\n chồng lên nhau bên trong vòng. */}\n {/* ⚠ `inline-flex` KHÔNG phải để bố trí — nó vá một khoảng trắng có thật.\n `LetterAvatar` là `inline-flex`, nên trong một nút KHỐI nó thành nội dung\n inline và bị canh theo BASELINE: line box chừa thêm khoảng chân chữ BÊN DƯỚI.\n Đã đo ở `xs` trên `/`: nút cao **30,5px** ôm một avatar **24px** — dư 6,5px,\n toàn bộ ở đáy. Nút lại có `bg-card` ĐỤC và `rounded-full`, nên chỗ dư ấy là\n một mảng trắng hình lưỡi liềm dưới mỗi khuôn mặt. Lúc nghỉ nó lẫn vào nền thẻ;\n `hover:z-10` + `hover:-translate-y-0.5` kéo nút lên trước và phơi nó ra.\n ⚠ `leading-none` KHÔNG đủ — đã đo: còn dư 2,5px. Phải bỏ hẳn line box.\n Ô \"+k\" bên dưới vốn đã `inline-flex`; chỗ này chỉ là sót. */}\n <button\n type=\"button\"\n className={cn(\n \"inline-flex rounded-full ring-2 transition-transform hover:z-10 hover:-translate-y-0.5 focus-visible:z-10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-foreground\",\n surfaceClassName\n )}\n >\n <LetterAvatar name={item.name} src={item.src} size={size} title={undefined} />\n </button>\n </TooltipTrigger>\n <TooltipContent>\n <span className=\"font-medium\">{item.name}</span>\n {/* `opacity-80` ở đây ĐẠT chuẩn và đã đo, không phải chỗ sót: Tooltip là\n `bg-foreground text-background`, nên chữ mờ 80% ra 12,15:1 (light) / 9,85:1\n (dark) — trên xa ngưỡng 4,5:1. Tooltip cũng không tương tác được. */}\n {item.caption && <span className=\"block text-xs opacity-80\">{item.caption}</span>}\n </TooltipContent>\n </Tooltip>\n ))}\n\n {overflow > 0 &&\n (onOverflowClick ? (\n <button\n type=\"button\"\n onClick={onOverflowClick}\n className={cn(\n // `surfaceClassName` đứng TRƯỚC để `bg-muted` ngay sau đè lại phần nền của nó:\n // ô \"+k\" vốn đã đục, ở đây chỉ mượn màu VÒNG của bề mặt.\n surfaceClassName,\n \"z-10 inline-flex shrink-0 items-center justify-center rounded-full bg-muted font-medium text-muted-foreground ring-2 transition-colors hover:bg-accent hover:text-accent-foreground\",\n overflowSizeClasses[size]\n )}\n >\n +{overflow}\n <span className=\"sr-only\">{overflowLabel(items.length)}</span>\n </button>\n ) : (\n <span\n className={cn(\n surfaceClassName,\n \"z-10 inline-flex shrink-0 items-center justify-center rounded-full bg-muted font-medium text-muted-foreground ring-2\",\n overflowSizeClasses[size]\n )}\n >\n +{overflow}\n </span>\n ))}\n </div>\n </TooltipProvider>\n );\n}\n"],"mappings":";AAgJgB,cAGJ,YAHI;AA7IhB,SAAS,UAAU;AACnB,SAAS,SAAS,gBAAgB,iBAAiB,sBAAsB;AACzE,SAAS,oBAA2C;AA4DpD,MAAM,iBAAmD;AAAA,EACvD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAEA,MAAM,sBAAwD;AAAA,EAC5D,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAWO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA,MAAM;AAAA,EACN,OAAO;AAAA,EACP;AAAA,EACA,gBAAgB,CAAC,UAAU,wBAAc,KAAK;AAAA,EAC9C,QAAQ;AAAA,EACR,mBAAmB;AAAA,EACnB;AAAA,EACA,GAAG;AACL,GAAqB;AACnB,QAAM,UAAU,MAAM,MAAM,GAAG,KAAK,IAAI,GAAG,GAAG,CAAC;AAC/C,QAAM,WAAW,MAAM,SAAS,QAAQ;AAExC,SACE,oBAAC,mBAAgB,eAAe,KAC9B;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,MAAK;AAAA,MACL,cAAY;AAAA,MACZ,WAAW,GAAG,qBAAqB,eAAe,IAAI,GAAG,SAAS;AAAA,MACjE,GAAG;AAAA,MAEH;AAAA,gBAAQ,IAAI,CAAC,SACZ,qBAAC,WACC;AAAA,8BAAC,kBAAe,SAAO,MAsBrB;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,WAAW;AAAA,gBACT;AAAA,gBACA;AAAA,cACF;AAAA,cAEA,8BAAC,gBAAa,MAAM,KAAK,MAAM,KAAK,KAAK,KAAK,MAAY,OAAO,QAAW;AAAA;AAAA,UAC9E,GACF;AAAA,UACA,qBAAC,kBACC;AAAA,gCAAC,UAAK,WAAU,eAAe,eAAK,MAAK;AAAA,YAIxC,KAAK,WAAW,oBAAC,UAAK,WAAU,4BAA4B,eAAK,SAAQ;AAAA,aAC5E;AAAA,aAvCY,KAAK,EAwCnB,CACD;AAAA,QAEA,WAAW,MACT,kBACC;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS;AAAA,YACT,WAAW;AAAA;AAAA;AAAA,cAGT;AAAA,cACA;AAAA,cACA,oBAAoB,IAAI;AAAA,YAC1B;AAAA,YACD;AAAA;AAAA,cACG;AAAA,cACF,oBAAC,UAAK,WAAU,WAAW,wBAAc,MAAM,MAAM,GAAE;AAAA;AAAA;AAAA,QACzD,IAEA;AAAA,UAAC;AAAA;AAAA,YACC,WAAW;AAAA,cACT;AAAA,cACA;AAAA,cACA,oBAAoB,IAAI;AAAA,YAC1B;AAAA,YACD;AAAA;AAAA,cACG;AAAA;AAAA;AAAA,QACJ;AAAA;AAAA;AAAA,EAEN,GACF;AAEJ;","names":[]}
|
|
@@ -54,7 +54,8 @@ function SidebarMenuItem({
|
|
|
54
54
|
);
|
|
55
55
|
const iconClass = cn(
|
|
56
56
|
"size-4 shrink-0",
|
|
57
|
-
active ? "text-sidebar-accent-foreground" :
|
|
57
|
+
active ? "text-sidebar-accent-foreground" : "text-muted-foreground",
|
|
58
|
+
!disabled && !active && "group-hover:text-sidebar-foreground"
|
|
58
59
|
);
|
|
59
60
|
const badge = item.badge === void 0 || item.badge === null || item.badge === "" ? null : /* @__PURE__ */ jsx(Badge, { variant: BADGE_VARIANT[item.badgeVariant ?? "default"], size: "sm", className: "ml-auto shrink-0", children: item.badge });
|
|
60
61
|
if (collapsed && depth === 0) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/shell/sidebar-menu-item.tsx"],"sourcesContent":["\"use client\";\n\nimport { ChevronRight } from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\nimport { Badge } from \"../components/ui/badge.js\";\nimport { Collapsible, CollapsibleContent, CollapsibleTrigger } from \"../components/ui/collapsible.js\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"../components/ui/tooltip.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { useShell } from \"./shell-context.js\";\nimport { ShellNavLink } from \"./shell-link.js\";\nimport type { NavBadgeVariant, NavItem } from \"./types.js\";\nimport { isNavBranchActive, isNavItemActive } from \"./utils.js\";\n\n/** Variant badge của NavItem → variant của primitive Badge (tên trùng nhau, map tường minh cho an toàn). */\nconst BADGE_VARIANT: Record<NavBadgeVariant, \"default\" | \"destructive\" | \"warning\" | \"success\"> = {\n default: \"default\",\n destructive: \"destructive\",\n warning: \"warning\",\n success: \"success\"\n};\n\n/**\n * Vòng focus cho mục đã khoá.\n *\n * Mục khoá vẫn nằm trong tab order (`tabIndex={0}` + `aria-disabled`) theo đúng khuyến nghị\n * của ARIA APG: người dùng bàn phím phải *biết* mục đó tồn tại và đang bị khoá, thay vì mục\n * lặng lẽ biến mất. Không có href và không có handler nên Enter/click không đi đâu cả.\n */\nconst FOCUS_RING = \"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring\";\n\nexport interface SidebarMenuItemProps {\n item: NavItem;\n /** Sidebar đang thu gọn (chỉ icon) — chỉ áp dụng cho mục cấp 0. */\n collapsed?: boolean;\n /** Cấp lồng (0 = gốc). Cấp > 0 thụt lề và không có icon vuông. */\n depth?: number;\n /**\n * Tập mục để đối chiếu khớp CHÍNH XÁC pathname — nên truyền toàn bộ mục của app\n * hiện tại. Thiếu nó, `/tasks` sẽ vẫn sáng khi đang ở `/tasks/inbox`.\n */\n siblings?: NavItem[];\n /** Gọi khi người dùng bấm vào một LINK thật (không gọi khi chỉ mở/đóng nhánh con) —\n * `MobileMenu` dùng để tự đóng. */\n onNavigate?: () => void;\n className?: string;\n}\n\n/**\n * Một mục menu sidebar: active matching thông minh, badge số, mục con `Collapsible`,\n * khi sidebar thu gọn thì hiện `Tooltip` + chấm badge.\n */\nexport function SidebarMenuItem({\n item,\n collapsed = false,\n depth = 0,\n siblings = [],\n onNavigate,\n className\n}: SidebarMenuItemProps) {\n const { pathname, searchParams } = useShell();\n\n const active = isNavItemActive(item, pathname, siblings, searchParams);\n const branchActive = isNavBranchActive(item, pathname, siblings, searchParams);\n const hasChildren = Boolean(item.children?.length);\n\n const [open, setOpen] = useState(branchActive);\n\n // Điều hướng tới một mục con → tự mở nhánh chứa nó.\n useEffect(() => {\n if (branchActive) setOpen(true);\n }, [branchActive]);\n\n const Icon = item.icon;\n const disabled = Boolean(item.disabled);\n const rowActive = active || (branchActive && !hasChildren);\n\n const rowClass = cn(\n \"group relative flex w-full items-center gap-2.5 rounded-md px-2 py-(--nav-item-py) text-sm\",\n \"transition-colors duration-150 ease-out\",\n depth > 0 && \"pl-8 text-[13px]\",\n rowActive ? \"bg-sidebar-accent font-medium text-sidebar-accent-foreground\" : \"text-sidebar-foreground/70\",\n // Mục đã khoá thì KHÔNG gắn hover — đổi màu khi rê chuột là hứa hẹn bấm được.\n !disabled && !rowActive && \"hover:bg-sidebar-accent/60 hover:text-sidebar-foreground\",\n // Mục khoá tô bằng MỘT bậc chữ mờ hơn, KHÔNG phải `opacity-50` chồng lên màu sẵn có.\n // Vì sao: mục khoá vẫn nằm trong tab order (`role=\"link\" aria-disabled tabIndex={0}` — xem\n // chú thích FOCUS_RING) nên KHÔNG được hưởng miễn trừ \"thành phần vô hiệu\" của WCAG 1.4.3,\n // mà `opacity-50` chồng lên `text-sidebar-foreground/70` cho độ mờ thực 35% → 2,28:1 (light)\n // / 3,10:1 (dark) ✗ cho chữ, và 27,5% trên icon → 1,86:1 / 2,39:1 ✗.\n // `--muted-foreground` là bậc chữ phụ đã đo cho đúng vai trò này: 5,01:1 (light) / 5,35:1\n // (dark) trên `--sidebar` ✓. Tín hiệu \"khoá\" vẫn còn nguyên ở con trỏ và ở `aria-disabled`.\n disabled && \"cursor-not-allowed text-muted-foreground\",\n className\n );\n\n /**\n * Màu icon/vạch active dùng `--sidebar-accent-foreground`, KHÔNG dùng `--app-accent`.\n *\n * Accent mặc định (= `--primary` = gold #E8B824) chỉ đạt **1,70:1** trên nền hàng active\n * (`--sidebar-accent` #FBF5DC) ở light — dưới ngưỡng 3:1 của WCAG 1.4.11 cho thành phần\n * phi-văn-bản, nên vạch chỉ mục active gần như vô hình đúng lúc nó phải nói lên trạng thái.\n * (Ở dark thì gold #F5D060 trên #1E2128 đạt 10,8:1 — chỉ light hỏng.)\n * `--sidebar-accent-foreground` đạt 17,3:1 (light) và 15,1:1 (dark), lại là token semantic\n * nên app đổi theme vẫn đúng. Cùng cách xử lý mà `stat-card.tsx` đã chọn (`text-foreground`).\n *\n * Muốn vạch active mang màu riêng của app trở lại thì phải sửa ở tầng token: cho\n * `--app-accent` một bậc đủ tối ở light (vd gold-500 #7A5800 = 6,0:1), rồi mới quay lại\n * `bg-app-accent` ở đây.\n */\n const iconClass = cn(\n \"size-4 shrink-0\",\n active\n ? \"text-sidebar-accent-foreground\"\n : disabled\n ? \"text-muted-foreground\"\n : \"text-sidebar-foreground/55 group-hover:text-sidebar-foreground\"\n );\n\n /*\n * KHÔNG có vạch dọc bên trái ở hàng active.\n *\n * Bản duyệt có HAI cách vẽ mục active và chúng khác nhau: `SidebarNav` của trang tài liệu dùng\n * `border-l-2 border-gold-300`, còn khung ứng dụng ở mục **Patterns** chỉ tô nền\n * (`bg-gold-300/15 text-foreground`), không vạch. Shell này là bản dựng của Patterns nên theo\n * Patterns.\n *\n * Bỏ vạch KHÔNG làm mất tín hiệu trạng thái: hàng active vẫn khác hàng thường ở nền\n * (`--sidebar-accent`), ở màu chữ (`--sidebar-accent-foreground` thay cho\n * `--sidebar-foreground/70`), ở `font-medium`, và ở `aria-current=\"page\"` cho trình đọc màn\n * hình. WCAG 1.4.11 đòi tương phản cho thông tin thị giác, không đòi một hình dạng cụ thể.\n */\n\n const badge =\n item.badge === undefined || item.badge === null || item.badge === \"\" ? null : (\n <Badge variant={BADGE_VARIANT[item.badgeVariant ?? \"default\"]} size=\"sm\" className=\"ml-auto shrink-0\">\n {item.badge}\n </Badge>\n );\n\n /* ── Sidebar thu gọn: icon vuông + tooltip ─────────────────────────────── */\n if (collapsed && depth === 0) {\n const tileClass = cn(\n \"relative flex size-9 items-center justify-center rounded-md\",\n \"transition-colors duration-150 ease-out\",\n active ? \"bg-sidebar-accent text-sidebar-accent-foreground\" : \"text-sidebar-foreground/70\",\n !disabled && !active && \"hover:bg-sidebar-accent/60 hover:text-sidebar-foreground\",\n // Cùng lý do với `rowClass` ở trên: bậc chữ mờ hơn thay cho `opacity-50`.\n disabled && \"cursor-not-allowed text-muted-foreground\"\n );\n\n const tileBody = (\n <>\n {Icon ? <Icon className=\"size-4\" /> : null}\n {/* Thu gọn không đủ chỗ cho số → chỉ chấm báo có nội dung mới */}\n {item.badge ? <span className=\"absolute right-1.5 top-1.5 size-1.5 rounded-full bg-destructive\" /> : null}\n </>\n );\n\n return (\n <Tooltip>\n <TooltipTrigger asChild>\n {disabled ? (\n // Mục khoá: KHÔNG render <a> — xem chú thích ở nhánh mục lá bên dưới.\n <span\n role=\"link\"\n aria-disabled=\"true\"\n aria-label={item.label}\n tabIndex={0}\n className={cn(tileClass, FOCUS_RING)}\n >\n {tileBody}\n </span>\n ) : (\n <ShellNavLink\n href={item.href}\n external={item.external}\n onClick={() => onNavigate?.()}\n aria-current={active ? \"page\" : undefined}\n aria-label={item.label}\n className={tileClass}\n >\n {tileBody}\n </ShellNavLink>\n )}\n </TooltipTrigger>\n <TooltipContent side=\"right\" className=\"flex items-center gap-2\">\n <span>{item.label}</span>\n {item.badge ? <span className=\"text-xs opacity-70\">{item.badge}</span> : null}\n </TooltipContent>\n </Tooltip>\n );\n }\n\n /* ── Mục lá ────────────────────────────────────────────────────────────── */\n if (!hasChildren) {\n const leafBody = (\n <>\n {Icon ? <Icon className={iconClass} /> : null}\n <span className=\"truncate\">{item.label}</span>\n {badge}\n </>\n );\n\n /*\n * Mục khoá phải dựng bằng <span role=\"link\" aria-disabled>, KHÔNG phải <a> tô mờ:\n * `pointer-events-none` chỉ chặn CHUỘT — người dùng bàn phím vẫn Tab tới thẻ <a> và\n * Enter vẫn điều hướng, lại không có tín hiệu nào cho trình đọc màn hình. Bỏ hẳn href\n * là cách duy nhất chặn được cả hai đường.\n */\n if (disabled) {\n return (\n <span role=\"link\" aria-disabled=\"true\" tabIndex={0} className={cn(rowClass, FOCUS_RING)}>\n {leafBody}\n </span>\n );\n }\n\n return (\n <ShellNavLink\n href={item.href}\n external={item.external}\n // Phải gọi `onNavigate` ở ĐÂY nữa, không chỉ ở nhánh thu gọn: `MobileMenu` truyền\n // `onNavigate={closeMenu}` cho mọi mục, thiếu nó thì bấm một mục điều hướng thường\n // trong menu mobile mà menu không đóng.\n onClick={() => onNavigate?.()}\n aria-current={active ? \"page\" : undefined}\n className={rowClass}\n >\n {leafBody}\n </ShellNavLink>\n );\n }\n\n /* ── Mục có con ────────────────────────────────────────────────────────── */\n return (\n <Collapsible open={open} onOpenChange={setOpen} disabled={disabled}>\n <CollapsibleTrigger asChild>\n {/* `disabled` thật trên <button>: bỏ khỏi tab order và chặn cả Enter/Space,\n khác hẳn `pointer-events-none` vốn chỉ chặn chuột. */}\n <button type=\"button\" disabled={disabled} className={rowClass} aria-expanded={open}>\n {Icon ? <Icon className={iconClass} /> : null}\n <span className=\"truncate\">{item.label}</span>\n {badge}\n {/* `text-muted-foreground` chứ không `text-sidebar-foreground/40`: mũi tên MANG THÔNG TIN\n (nhánh đang đóng hay mở) nên chịu ngưỡng 3:1 của WCAG 1.4.11, mà /40 chỉ được\n 2,60:1 ở light ✗ (dark 3,64:1 — lại một chỗ mỗi theme hỏng một nửa).\n `--muted-foreground`: 5,01:1 (light) · 5,35:1 (dark) trên `--sidebar` ✓ */}\n <ChevronRight\n className={cn(\n \"ml-auto size-3.5 shrink-0 text-muted-foreground transition-transform duration-150\",\n badge && \"ml-1\",\n open && \"rotate-90\"\n )}\n />\n </button>\n </CollapsibleTrigger>\n <CollapsibleContent className=\"overflow-hidden data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down\">\n <div className=\"mt-0.5 flex flex-col gap-0.5\">\n {item.children?.map((child) => (\n <SidebarMenuItem\n key={child.id}\n item={child}\n depth={depth + 1}\n siblings={siblings}\n onNavigate={onNavigate}\n />\n ))}\n </div>\n </CollapsibleContent>\n </Collapsible>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/shell/sidebar-menu-item.tsx"],"sourcesContent":["\"use client\";\n\nimport { ChevronRight } from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\nimport { Badge } from \"../components/ui/badge.js\";\nimport { Collapsible, CollapsibleContent, CollapsibleTrigger } from \"../components/ui/collapsible.js\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"../components/ui/tooltip.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { useShell } from \"./shell-context.js\";\nimport { ShellNavLink } from \"./shell-link.js\";\nimport type { NavBadgeVariant, NavItem } from \"./types.js\";\nimport { isNavBranchActive, isNavItemActive } from \"./utils.js\";\n\n/** Variant badge của NavItem → variant của primitive Badge (tên trùng nhau, map tường minh cho an toàn). */\nconst BADGE_VARIANT: Record<NavBadgeVariant, \"default\" | \"destructive\" | \"warning\" | \"success\"> = {\n default: \"default\",\n destructive: \"destructive\",\n warning: \"warning\",\n success: \"success\"\n};\n\n/**\n * Vòng focus cho mục đã khoá.\n *\n * Mục khoá vẫn nằm trong tab order (`tabIndex={0}` + `aria-disabled`) theo đúng khuyến nghị\n * của ARIA APG: người dùng bàn phím phải *biết* mục đó tồn tại và đang bị khoá, thay vì mục\n * lặng lẽ biến mất. Không có href và không có handler nên Enter/click không đi đâu cả.\n */\nconst FOCUS_RING = \"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring\";\n\nexport interface SidebarMenuItemProps {\n item: NavItem;\n /** Sidebar đang thu gọn (chỉ icon) — chỉ áp dụng cho mục cấp 0. */\n collapsed?: boolean;\n /** Cấp lồng (0 = gốc). Cấp > 0 thụt lề và không có icon vuông. */\n depth?: number;\n /**\n * Tập mục để đối chiếu khớp CHÍNH XÁC pathname — nên truyền toàn bộ mục của app\n * hiện tại. Thiếu nó, `/tasks` sẽ vẫn sáng khi đang ở `/tasks/inbox`.\n */\n siblings?: NavItem[];\n /** Gọi khi người dùng bấm vào một LINK thật (không gọi khi chỉ mở/đóng nhánh con) —\n * `MobileMenu` dùng để tự đóng. */\n onNavigate?: () => void;\n className?: string;\n}\n\n/**\n * Một mục menu sidebar: active matching thông minh, badge số, mục con `Collapsible`,\n * khi sidebar thu gọn thì hiện `Tooltip` + chấm badge.\n */\nexport function SidebarMenuItem({\n item,\n collapsed = false,\n depth = 0,\n siblings = [],\n onNavigate,\n className\n}: SidebarMenuItemProps) {\n const { pathname, searchParams } = useShell();\n\n const active = isNavItemActive(item, pathname, siblings, searchParams);\n const branchActive = isNavBranchActive(item, pathname, siblings, searchParams);\n const hasChildren = Boolean(item.children?.length);\n\n const [open, setOpen] = useState(branchActive);\n\n // Điều hướng tới một mục con → tự mở nhánh chứa nó.\n useEffect(() => {\n if (branchActive) setOpen(true);\n }, [branchActive]);\n\n const Icon = item.icon;\n const disabled = Boolean(item.disabled);\n const rowActive = active || (branchActive && !hasChildren);\n\n const rowClass = cn(\n \"group relative flex w-full items-center gap-2.5 rounded-md px-2 py-(--nav-item-py) text-sm\",\n \"transition-colors duration-150 ease-out\",\n depth > 0 && \"pl-8 text-[13px]\",\n rowActive ? \"bg-sidebar-accent font-medium text-sidebar-accent-foreground\" : \"text-sidebar-foreground/70\",\n // Mục đã khoá thì KHÔNG gắn hover — đổi màu khi rê chuột là hứa hẹn bấm được.\n !disabled && !rowActive && \"hover:bg-sidebar-accent/60 hover:text-sidebar-foreground\",\n // Mục khoá tô bằng MỘT bậc chữ mờ hơn, KHÔNG phải `opacity-50` chồng lên màu sẵn có.\n // Vì sao: mục khoá vẫn nằm trong tab order (`role=\"link\" aria-disabled tabIndex={0}` — xem\n // chú thích FOCUS_RING) nên KHÔNG được hưởng miễn trừ \"thành phần vô hiệu\" của WCAG 1.4.3,\n // mà `opacity-50` chồng lên `text-sidebar-foreground/70` cho độ mờ thực 35% → 2,28:1 (light)\n // / 3,10:1 (dark) ✗ cho chữ, và 27,5% trên icon → 1,86:1 / 2,39:1 ✗.\n // `--muted-foreground` là bậc chữ phụ đã đo cho đúng vai trò này: 5,01:1 (light) / 5,35:1\n // (dark) trên `--sidebar` ✓. Tín hiệu \"khoá\" vẫn còn nguyên ở con trỏ và ở `aria-disabled`.\n disabled && \"cursor-not-allowed text-muted-foreground\",\n className\n );\n\n /**\n * Màu icon/vạch active dùng `--sidebar-accent-foreground`, KHÔNG dùng `--app-accent`.\n *\n * Accent mặc định (= `--primary` = gold #E8B824) chỉ đạt **1,70:1** trên nền hàng active\n * (`--sidebar-accent` #FBF5DC) ở light — dưới ngưỡng 3:1 của WCAG 1.4.11 cho thành phần\n * phi-văn-bản, nên vạch chỉ mục active gần như vô hình đúng lúc nó phải nói lên trạng thái.\n * (Ở dark thì gold #F5D060 trên #1E2128 đạt 10,8:1 — chỉ light hỏng.)\n * `--sidebar-accent-foreground` đạt 17,3:1 (light) và 15,1:1 (dark), lại là token semantic\n * nên app đổi theme vẫn đúng. Cùng cách xử lý mà `stat-card.tsx` đã chọn (`text-foreground`).\n *\n * Muốn vạch active mang màu riêng của app trở lại thì phải sửa ở tầng token: cho\n * `--app-accent` một bậc đủ tối ở light (vd gold-500 #7A5800 = 6,0:1), rồi mới quay lại\n * `bg-app-accent` ở đây.\n */\n /*\n * ── ⚠ ICON KHÔNG BAO GIỜ ĐƯỢC TÔ BẰNG MÀU CÓ ALPHA ──────────────────────────────────────\n * Icon `lucide-react` là NHIỀU phần tử `<path>`/`<rect>`/`<circle>` vẽ bằng NÉT\n * (`stroke=\"currentColor\"`), và ở khá nhiều icon các nét ấy CẮT NHAU. Nét bán trong suốt chồng\n * lên nhau thì chỗ giao được tô hai lần — nó đậm hơn phần còn lại, và icon lộ ra những vệt nối\n * mà người thiết kế không vẽ.\n *\n * Đo được trên `account.dev.comitor.ai`: hàng \"Danh mục sản phẩm\" dùng icon `Boxes` — **12**\n * phần tử con, chồng nhau chằng chịt — nên vệt nối hiện rõ; `User` hay `KeyRound` (2 phần tử,\n * không cắt nhau) thì gần như không thấy, và hàng ACTIVE thì không bao giờ thấy vì nó tô đặc.\n * Đó là lý do lỗi này sống lâu: nó chỉ lộ ở đúng vài icon, ở đúng trạng thái không-active.\n *\n * Vì vậy `text-sidebar-foreground/55` (đã phẳng ra `rgb(120,122,124)`, 4,13:1) được thay bằng\n * `text-muted-foreground` — ĐẶC, `rgb(126,130,144)`, **3,67:1** trên `--sidebar`, vẫn trên\n * ngưỡng 3:1 của WCAG 1.4.11 cho thành phần phi-văn-bản. Cùng token mà nhánh `disabled` ngay\n * dưới và `workspace-switcher.tsx` đã chọn, và cùng lý do: bậc màu ĐO ĐƯỢC thay cho alpha chồng.\n *\n * ⚠ Icon của hàng thường nay TRÙNG MÀU với icon hàng khoá. Chấp nhận được: tín hiệu \"khoá\" chưa\n * bao giờ nằm ở icon — nó ở màu CHỮ (`text-muted-foreground` so với `text-sidebar-foreground/70`),\n * ở con trỏ `cursor-not-allowed`, và ở `aria-disabled`.\n *\n * ⚠ Luật này áp cho MỌI icon lucide trong gói, không riêng chỗ này: đừng dùng `/NN`,\n * `opacity-*`, hay bất kỳ màu có alpha nào lên một `<svg>` icon. Cần nhạt hơn thì lấy một BẬC\n * MÀU đặc.\n */\n const iconClass = cn(\n \"size-4 shrink-0\",\n active ? \"text-sidebar-accent-foreground\" : \"text-muted-foreground\",\n !disabled && !active && \"group-hover:text-sidebar-foreground\"\n );\n\n /*\n * KHÔNG có vạch dọc bên trái ở hàng active.\n *\n * Bản duyệt có HAI cách vẽ mục active và chúng khác nhau: `SidebarNav` của trang tài liệu dùng\n * `border-l-2 border-gold-300`, còn khung ứng dụng ở mục **Patterns** chỉ tô nền\n * (`bg-gold-300/15 text-foreground`), không vạch. Shell này là bản dựng của Patterns nên theo\n * Patterns.\n *\n * Bỏ vạch KHÔNG làm mất tín hiệu trạng thái: hàng active vẫn khác hàng thường ở nền\n * (`--sidebar-accent`), ở màu chữ (`--sidebar-accent-foreground` thay cho\n * `--sidebar-foreground/70`), ở `font-medium`, và ở `aria-current=\"page\"` cho trình đọc màn\n * hình. WCAG 1.4.11 đòi tương phản cho thông tin thị giác, không đòi một hình dạng cụ thể.\n */\n\n const badge =\n item.badge === undefined || item.badge === null || item.badge === \"\" ? null : (\n <Badge variant={BADGE_VARIANT[item.badgeVariant ?? \"default\"]} size=\"sm\" className=\"ml-auto shrink-0\">\n {item.badge}\n </Badge>\n );\n\n /* ── Sidebar thu gọn: icon vuông + tooltip ─────────────────────────────── */\n if (collapsed && depth === 0) {\n const tileClass = cn(\n \"relative flex size-9 items-center justify-center rounded-md\",\n \"transition-colors duration-150 ease-out\",\n active ? \"bg-sidebar-accent text-sidebar-accent-foreground\" : \"text-sidebar-foreground/70\",\n !disabled && !active && \"hover:bg-sidebar-accent/60 hover:text-sidebar-foreground\",\n // Cùng lý do với `rowClass` ở trên: bậc chữ mờ hơn thay cho `opacity-50`.\n disabled && \"cursor-not-allowed text-muted-foreground\"\n );\n\n const tileBody = (\n <>\n {Icon ? <Icon className=\"size-4\" /> : null}\n {/* Thu gọn không đủ chỗ cho số → chỉ chấm báo có nội dung mới */}\n {item.badge ? <span className=\"absolute right-1.5 top-1.5 size-1.5 rounded-full bg-destructive\" /> : null}\n </>\n );\n\n return (\n <Tooltip>\n <TooltipTrigger asChild>\n {disabled ? (\n // Mục khoá: KHÔNG render <a> — xem chú thích ở nhánh mục lá bên dưới.\n <span\n role=\"link\"\n aria-disabled=\"true\"\n aria-label={item.label}\n tabIndex={0}\n className={cn(tileClass, FOCUS_RING)}\n >\n {tileBody}\n </span>\n ) : (\n <ShellNavLink\n href={item.href}\n external={item.external}\n onClick={() => onNavigate?.()}\n aria-current={active ? \"page\" : undefined}\n aria-label={item.label}\n className={tileClass}\n >\n {tileBody}\n </ShellNavLink>\n )}\n </TooltipTrigger>\n <TooltipContent side=\"right\" className=\"flex items-center gap-2\">\n <span>{item.label}</span>\n {item.badge ? <span className=\"text-xs opacity-70\">{item.badge}</span> : null}\n </TooltipContent>\n </Tooltip>\n );\n }\n\n /* ── Mục lá ────────────────────────────────────────────────────────────── */\n if (!hasChildren) {\n const leafBody = (\n <>\n {Icon ? <Icon className={iconClass} /> : null}\n <span className=\"truncate\">{item.label}</span>\n {badge}\n </>\n );\n\n /*\n * Mục khoá phải dựng bằng <span role=\"link\" aria-disabled>, KHÔNG phải <a> tô mờ:\n * `pointer-events-none` chỉ chặn CHUỘT — người dùng bàn phím vẫn Tab tới thẻ <a> và\n * Enter vẫn điều hướng, lại không có tín hiệu nào cho trình đọc màn hình. Bỏ hẳn href\n * là cách duy nhất chặn được cả hai đường.\n */\n if (disabled) {\n return (\n <span role=\"link\" aria-disabled=\"true\" tabIndex={0} className={cn(rowClass, FOCUS_RING)}>\n {leafBody}\n </span>\n );\n }\n\n return (\n <ShellNavLink\n href={item.href}\n external={item.external}\n // Phải gọi `onNavigate` ở ĐÂY nữa, không chỉ ở nhánh thu gọn: `MobileMenu` truyền\n // `onNavigate={closeMenu}` cho mọi mục, thiếu nó thì bấm một mục điều hướng thường\n // trong menu mobile mà menu không đóng.\n onClick={() => onNavigate?.()}\n aria-current={active ? \"page\" : undefined}\n className={rowClass}\n >\n {leafBody}\n </ShellNavLink>\n );\n }\n\n /* ── Mục có con ────────────────────────────────────────────────────────── */\n return (\n <Collapsible open={open} onOpenChange={setOpen} disabled={disabled}>\n <CollapsibleTrigger asChild>\n {/* `disabled` thật trên <button>: bỏ khỏi tab order và chặn cả Enter/Space,\n khác hẳn `pointer-events-none` vốn chỉ chặn chuột. */}\n <button type=\"button\" disabled={disabled} className={rowClass} aria-expanded={open}>\n {Icon ? <Icon className={iconClass} /> : null}\n <span className=\"truncate\">{item.label}</span>\n {badge}\n {/* `text-muted-foreground` chứ không `text-sidebar-foreground/40`: mũi tên MANG THÔNG TIN\n (nhánh đang đóng hay mở) nên chịu ngưỡng 3:1 của WCAG 1.4.11, mà /40 chỉ được\n 2,60:1 ở light ✗ (dark 3,64:1 — lại một chỗ mỗi theme hỏng một nửa).\n `--muted-foreground`: 5,01:1 (light) · 5,35:1 (dark) trên `--sidebar` ✓ */}\n <ChevronRight\n className={cn(\n \"ml-auto size-3.5 shrink-0 text-muted-foreground transition-transform duration-150\",\n badge && \"ml-1\",\n open && \"rotate-90\"\n )}\n />\n </button>\n </CollapsibleTrigger>\n <CollapsibleContent className=\"overflow-hidden data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down\">\n <div className=\"mt-0.5 flex flex-col gap-0.5\">\n {item.children?.map((child) => (\n <SidebarMenuItem\n key={child.id}\n item={child}\n depth={depth + 1}\n siblings={siblings}\n onNavigate={onNavigate}\n />\n ))}\n </div>\n </CollapsibleContent>\n </Collapsible>\n );\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@comitor/ui",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.2",
|
|
4
4
|
"description": "Comitor design system — Shadcn/UI tùy biến theo brand Comitor + component dùng chung cho hệ sinh thái SaaS (Tasks, Chat, CRM, HR…)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
"class-variance-authority": "^0.7.1",
|
|
120
120
|
"clsx": "^2.1.1",
|
|
121
121
|
"cmdk": "1.1.1",
|
|
122
|
-
"date-fns": "4.
|
|
122
|
+
"date-fns": "4.4.0",
|
|
123
123
|
"embla-carousel-react": "8.6.0",
|
|
124
124
|
"input-otp": "1.4.2",
|
|
125
125
|
"react-day-picker": "9.13.2",
|
|
@@ -137,8 +137,8 @@
|
|
|
137
137
|
"@types/react-dom": "^19.0.0",
|
|
138
138
|
"@uppy/core": "^6.0.0",
|
|
139
139
|
"@uppy/xhr-upload": "^6.0.0",
|
|
140
|
-
"lucide-react": "^1.
|
|
141
|
-
"next": "^16.
|
|
140
|
+
"lucide-react": "^1.41.0",
|
|
141
|
+
"next": "^16.3.4",
|
|
142
142
|
"next-themes": "^0.4.6",
|
|
143
143
|
"publint": "^0.3.21",
|
|
144
144
|
"react": "^19.0.0",
|