@comitor/ui 0.3.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +12 -8
  2. package/dist/components/composite/avatar-group.d.ts +13 -1
  3. package/dist/components/composite/avatar-group.js +19 -4
  4. package/dist/components/composite/avatar-group.js.map +1 -1
  5. package/dist/components/composite/combobox.js +169 -146
  6. package/dist/components/composite/combobox.js.map +1 -1
  7. package/dist/components/composite/data-table.d.ts +24 -1
  8. package/dist/components/composite/data-table.js +7 -1
  9. package/dist/components/composite/data-table.js.map +1 -1
  10. package/dist/components/composite/date-picker.js +46 -17
  11. package/dist/components/composite/date-picker.js.map +1 -1
  12. package/dist/components/composite/form-section.js +1 -1
  13. package/dist/components/composite/form-section.js.map +1 -1
  14. package/dist/components/composite/route-tabs.d.ts +75 -0
  15. package/dist/components/composite/route-tabs.js +50 -0
  16. package/dist/components/composite/route-tabs.js.map +1 -0
  17. package/dist/components/composite/stat-card.js +14 -10
  18. package/dist/components/composite/stat-card.js.map +1 -1
  19. package/dist/components/ui/calendar.d.ts +29 -24
  20. package/dist/components/ui/calendar.js +1 -1
  21. package/dist/components/ui/calendar.js.map +1 -1
  22. package/dist/components/ui/checkbox.js +2 -2
  23. package/dist/components/ui/checkbox.js.map +1 -1
  24. package/dist/components/ui/command.js +1 -1
  25. package/dist/components/ui/command.js.map +1 -1
  26. package/dist/components/ui/context-menu.js +4 -4
  27. package/dist/components/ui/context-menu.js.map +1 -1
  28. package/dist/components/ui/dropdown-menu.js +4 -4
  29. package/dist/components/ui/dropdown-menu.js.map +1 -1
  30. package/dist/components/ui/empty.js +1 -1
  31. package/dist/components/ui/empty.js.map +1 -1
  32. package/dist/components/ui/field.js +1 -1
  33. package/dist/components/ui/field.js.map +1 -1
  34. package/dist/components/ui/item.js +1 -1
  35. package/dist/components/ui/item.js.map +1 -1
  36. package/dist/components/ui/menubar.js +4 -4
  37. package/dist/components/ui/menubar.js.map +1 -1
  38. package/dist/components/ui/radio-group.js +1 -1
  39. package/dist/components/ui/radio-group.js.map +1 -1
  40. package/dist/components/ui/select.d.ts +35 -12
  41. package/dist/components/ui/select.js +1 -1
  42. package/dist/components/ui/select.js.map +1 -1
  43. package/dist/components/ui/switch.js +2 -2
  44. package/dist/components/ui/switch.js.map +1 -1
  45. package/dist/components/ui/table.js +2 -2
  46. package/dist/components/ui/table.js.map +1 -1
  47. package/dist/components/ui/tabs-variants.d.ts +21 -0
  48. package/dist/components/ui/tabs-variants.js +15 -0
  49. package/dist/components/ui/tabs-variants.js.map +1 -0
  50. package/dist/components/ui/tabs.d.ts +2 -10
  51. package/dist/components/ui/tabs.js +3 -14
  52. package/dist/components/ui/tabs.js.map +1 -1
  53. package/dist/index.d.ts +3 -1
  54. package/dist/index.js +2 -0
  55. package/dist/index.js.map +1 -1
  56. package/dist/shell/app-header.js +47 -41
  57. package/dist/shell/app-header.js.map +1 -1
  58. package/dist/shell/mobile-menu.d.ts +6 -1
  59. package/dist/shell/mobile-menu.js +5 -5
  60. package/dist/shell/mobile-menu.js.map +1 -1
  61. package/dist/shell/notifications-button.js +31 -9
  62. package/dist/shell/notifications-button.js.map +1 -1
  63. package/dist/shell/sidebar.js +1 -1
  64. package/dist/shell/sidebar.js.map +1 -1
  65. package/dist/shell/types.d.ts +13 -0
  66. package/dist/shell/types.js +1 -0
  67. package/dist/shell/types.js.map +1 -1
  68. package/dist/shell/user-menu.d.ts +14 -2
  69. package/dist/shell/user-menu.js +3 -3
  70. package/dist/shell/user-menu.js.map +1 -1
  71. package/dist/shell/workspace-switcher.js +48 -34
  72. package/dist/shell/workspace-switcher.js.map +1 -1
  73. package/package.json +1 -1
  74. package/styles.css +95 -16
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/ui/select.tsx"],"sourcesContent":["\"use client\";\n\nimport * as SelectPrimitive from \"@radix-ui/react-select\";\nimport { CheckIcon, ChevronDownIcon, ChevronUpIcon } from \"lucide-react\";\nimport type * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\n/**\n * Select (Radix) theo style new-york + brand Comitor.\n * Trigger có 2 cỡ (`sm` = h-8, `default` = h-9); mục đang chọn tô nền gold nhạt.\n * Lưu ý: Radix KHÔNG nhận `value=\"\"` cho SelectItem — dùng sentinel (vd `__empty__`).\n *\n * ─ VIỀN TRIGGER: BA TRẠNG THÁI, BA TOKEN ─\n * Trigger nền trong suốt nên VIỀN là thứ duy nhất khoanh vùng control → ngưỡng 3:1 ở mọi\n * trạng thái, và hover/focus phải ĐẬM HƠN lúc nghỉ chứ không nhạt đi:\n *\n * nghỉ `border-input` (= --control-edge) : 3,83:1 (light) · 4,94:1 (dark) ✓\n * hover `border-control-edge-strong` : 10,55:1 (light) · 11,65:1 (dark) ✓\n * focus `border-primary-ink` : 6,51:1 (light) · 12,67:1 (dark) ✓\n * lỗi `border-destructive-ink` : 7,15:1 (light) · 6,84:1 (dark) ✓\n *\n * Bản trước đi SAI CHIỀU ở light: `hover:border-ash-300` chỉ **1,62:1** và\n * `focus:border-gold-300` chỉ **1,86:1** — rê chuột hoặc focus vào là trigger biến mất, đúng\n * cái bẫy mà docs/hop-dong-mau.md §3 nêu. Viền lỗi cũ `border-red` (#D64545) chỉ 4,38:1 và là\n * token TÔ NỀN đem làm viền; bậc `-ink` mới là bậc đứng trên nền trang.\n * (`ring-*` giữ nguyên bậc `--destructive`/`--ring`: quầng ring là vai trò TÔ, đúng hợp đồng.)\n */\n\nexport type SelectProps = React.ComponentProps<typeof SelectPrimitive.Root>;\nexport type SelectGroupProps = React.ComponentProps<typeof SelectPrimitive.Group>;\nexport type SelectValueProps = React.ComponentProps<typeof SelectPrimitive.Value>;\nexport type SelectTriggerProps = React.ComponentProps<typeof SelectPrimitive.Trigger> & {\n /** Chiều cao trigger: `sm` = 32px, `default` = 36px. */\n size?: \"sm\" | \"default\";\n};\nexport type SelectContentProps = React.ComponentProps<typeof SelectPrimitive.Content>;\nexport type SelectLabelProps = React.ComponentProps<typeof SelectPrimitive.Label>;\nexport type SelectItemProps = React.ComponentProps<typeof SelectPrimitive.Item>;\nexport type SelectSeparatorProps = React.ComponentProps<typeof SelectPrimitive.Separator>;\nexport type SelectScrollUpButtonProps = React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>;\nexport type SelectScrollDownButtonProps = React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>;\n\nfunction Select({ ...props }: SelectProps) {\n return <SelectPrimitive.Root data-slot=\"select\" {...props} />;\n}\n\nfunction SelectGroup({ ...props }: SelectGroupProps) {\n return <SelectPrimitive.Group data-slot=\"select-group\" {...props} />;\n}\n\nfunction SelectValue({ ...props }: SelectValueProps) {\n return <SelectPrimitive.Value data-slot=\"select-value\" {...props} />;\n}\n\nfunction SelectTrigger({ className, size = \"default\", children, ...props }: SelectTriggerProps) {\n return (\n <SelectPrimitive.Trigger\n data-slot=\"select-trigger\"\n data-size={size}\n className={cn(\n \"border-input bg-background text-foreground data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground hover:border-control-edge-strong focus:outline-none focus:ring-2 focus:ring-ring/50 focus:border-primary-ink aria-invalid:border-destructive-ink aria-invalid:ring-destructive/30 flex w-fit items-center justify-between gap-2 rounded-md border px-3 text-sm whitespace-nowrap transition-colors duration-150 ease-out outline-none disabled:cursor-not-allowed disabled:opacity-50 disabled:bg-muted data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n {...props}\n >\n {children}\n <SelectPrimitive.Icon asChild>\n <ChevronDownIcon className=\"size-3.5 text-muted-foreground\" />\n </SelectPrimitive.Icon>\n </SelectPrimitive.Trigger>\n );\n}\n\nfunction SelectContent({ className, children, position = \"popper\", ...props }: SelectContentProps) {\n return (\n <SelectPrimitive.Portal>\n <SelectPrimitive.Content\n data-slot=\"select-content\"\n className={cn(\n \"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border border-border shadow-overlay\",\n position === \"popper\" &&\n \"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1\",\n className\n )}\n position={position}\n {...props}\n >\n <SelectScrollUpButton />\n <SelectPrimitive.Viewport\n className={cn(\n \"p-1\",\n position === \"popper\" &&\n \"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1\"\n )}\n >\n {children}\n </SelectPrimitive.Viewport>\n <SelectScrollDownButton />\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n );\n}\n\nfunction SelectLabel({ className, ...props }: SelectLabelProps) {\n return (\n <SelectPrimitive.Label\n data-slot=\"select-label\"\n className={cn(\"text-muted-foreground px-2 py-1.5 text-xs\", className)}\n {...props}\n />\n );\n}\n\n/**\n * SelectItem — mục trong danh sách chọn.\n *\n * Mục ĐANG RÊ/ĐANG FOCUS và mục ĐÃ CHỌN trước đây dùng CÙNG một tint gold (`gold-50` ở light,\n * `gold-500/10` ở dark) nên nền không phân biệt được hai thứ đó, mà tint thì quá nhạt để tự\n * mang thông tin: 1,09:1 với `--popover` ở light · 1,07:1 ở dark — dưới xa 3:1 của WCAG 1.4.11.\n *\n * Tint được giữ nguyên (nó chỉ là lớp nhận biết mềm), còn dấu ĐÃ CHỌN nay có nét thật:\n * vạch `border-l-app-accent-ink` — 6,51:1 với `--popover`, tệ nhất 6,09:1 nếu popover nằm trên\n * `--muted` (light); 12,67:1 / 10,80:1 (dark). Bề rộng 2px được ĐẶT SẴN bằng `border-l-transparent`\n * cho mọi mục nên bật/tắt trạng thái không xô chữ. Dấu check bên phải giữ nguyên và cũng được\n * nhuộm cùng bậc ink — đo TRÊN CHÍNH tint gold mà nó đứng lên: 5,96:1 (light) · 11,15:1 (dark),\n * tệ nhất 10,04:1 khi popover nằm trên `--muted` — hai dấu hiệu độc lập, không dấu nào chỉ dựa\n * vào màu nền.\n *\n * Vì sao là `-ink` chứ không `--app-accent` trần: gold `#E8B824` chỉ 1,86:1 với nền trắng —\n * đúng cái bẫy \"token tô NỀN đem vẽ DẤU\" mà hợp đồng màu §1 cấm.\n */\nfunction SelectItem({ className, children, ...props }: SelectItemProps) {\n return (\n <SelectPrimitive.Item\n data-slot=\"select-item\"\n className={cn(\n \"focus:bg-gold-50 dark:focus:bg-gold-500/10 data-[state=checked]:bg-gold-50 dark:data-[state=checked]:bg-gold-500/10 border-l-2 border-l-transparent data-[state=checked]:border-l-app-accent-ink [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none transition-colors duration-150 ease-out data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2\",\n className\n )}\n {...props}\n >\n <span className=\"absolute right-2 flex size-3.5 items-center justify-center\">\n <SelectPrimitive.ItemIndicator>\n <CheckIcon className=\"size-4 text-app-accent-ink\" />\n </SelectPrimitive.ItemIndicator>\n </span>\n <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n </SelectPrimitive.Item>\n );\n}\n\nfunction SelectSeparator({ className, ...props }: SelectSeparatorProps) {\n return (\n <SelectPrimitive.Separator\n data-slot=\"select-separator\"\n className={cn(\"bg-border pointer-events-none -mx-1 my-1 h-px\", className)}\n {...props}\n />\n );\n}\n\nfunction SelectScrollUpButton({ className, ...props }: SelectScrollUpButtonProps) {\n return (\n <SelectPrimitive.ScrollUpButton\n data-slot=\"select-scroll-up-button\"\n className={cn(\"flex cursor-default items-center justify-center py-1\", className)}\n {...props}\n >\n <ChevronUpIcon className=\"size-4\" />\n </SelectPrimitive.ScrollUpButton>\n );\n}\n\nfunction SelectScrollDownButton({ className, ...props }: SelectScrollDownButtonProps) {\n return (\n <SelectPrimitive.ScrollDownButton\n data-slot=\"select-scroll-down-button\"\n className={cn(\"flex cursor-default items-center justify-center py-1\", className)}\n {...props}\n >\n <ChevronDownIcon className=\"size-4\" />\n </SelectPrimitive.ScrollDownButton>\n );\n}\n\nexport {\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectScrollDownButton,\n SelectScrollUpButton,\n SelectSeparator,\n SelectTrigger,\n SelectValue\n};\n"],"mappings":";AA2CS,cAaL,YAbK;AAzCT,YAAY,qBAAqB;AACjC,SAAS,WAAW,iBAAiB,qBAAqB;AAE1D,SAAS,UAAU;AAqCnB,SAAS,OAAO,EAAE,GAAG,MAAM,GAAgB;AACzC,SAAO,oBAAC,gBAAgB,MAAhB,EAAqB,aAAU,UAAU,GAAG,OAAO;AAC7D;AAEA,SAAS,YAAY,EAAE,GAAG,MAAM,GAAqB;AACnD,SAAO,oBAAC,gBAAgB,OAAhB,EAAsB,aAAU,gBAAgB,GAAG,OAAO;AACpE;AAEA,SAAS,YAAY,EAAE,GAAG,MAAM,GAAqB;AACnD,SAAO,oBAAC,gBAAgB,OAAhB,EAAsB,aAAU,gBAAgB,GAAG,OAAO;AACpE;AAEA,SAAS,cAAc,EAAE,WAAW,OAAO,WAAW,UAAU,GAAG,MAAM,GAAuB;AAC9F,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV,aAAW;AAAA,MACX,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,QACD,oBAAC,gBAAgB,MAAhB,EAAqB,SAAO,MAC3B,8BAAC,mBAAgB,WAAU,kCAAiC,GAC9D;AAAA;AAAA;AAAA,EACF;AAEJ;AAEA,SAAS,cAAc,EAAE,WAAW,UAAU,WAAW,UAAU,GAAG,MAAM,GAAuB;AACjG,SACE,oBAAC,gBAAgB,QAAhB,EACC;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA,aAAa,YACX;AAAA,QACF;AAAA,MACF;AAAA,MACA;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA,4BAAC,wBAAqB;AAAA,QACtB;AAAA,UAAC,gBAAgB;AAAA,UAAhB;AAAA,YACC,WAAW;AAAA,cACT;AAAA,cACA,aAAa,YACX;AAAA,YACJ;AAAA,YAEC;AAAA;AAAA,QACH;AAAA,QACA,oBAAC,0BAAuB;AAAA;AAAA;AAAA,EAC1B,GACF;AAEJ;AAEA,SAAS,YAAY,EAAE,WAAW,GAAG,MAAM,GAAqB;AAC9D,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,6CAA6C,SAAS;AAAA,MACnE,GAAG;AAAA;AAAA,EACN;AAEJ;AAoBA,SAAS,WAAW,EAAE,WAAW,UAAU,GAAG,MAAM,GAAoB;AACtE,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA,4BAAC,UAAK,WAAU,8DACd,8BAAC,gBAAgB,eAAhB,EACC,8BAAC,aAAU,WAAU,8BAA6B,GACpD,GACF;AAAA,QACA,oBAAC,gBAAgB,UAAhB,EAA0B,UAAS;AAAA;AAAA;AAAA,EACtC;AAEJ;AAEA,SAAS,gBAAgB,EAAE,WAAW,GAAG,MAAM,GAAyB;AACtE,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,iDAAiD,SAAS;AAAA,MACvE,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,SAAS,qBAAqB,EAAE,WAAW,GAAG,MAAM,GAA8B;AAChF,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,wDAAwD,SAAS;AAAA,MAC9E,GAAG;AAAA,MAEJ,8BAAC,iBAAc,WAAU,UAAS;AAAA;AAAA,EACpC;AAEJ;AAEA,SAAS,uBAAuB,EAAE,WAAW,GAAG,MAAM,GAAgC;AACpF,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,wDAAwD,SAAS;AAAA,MAC9E,GAAG;AAAA,MAEJ,8BAAC,mBAAgB,WAAU,UAAS;AAAA;AAAA,EACtC;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/ui/select.tsx"],"sourcesContent":["\"use client\";\n\nimport * as SelectPrimitive from \"@radix-ui/react-select\";\nimport { CheckIcon, ChevronDownIcon, ChevronUpIcon } from \"lucide-react\";\nimport type * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\n/**\n * Select (Radix) theo style new-york + brand Comitor.\n * Trigger có 2 cỡ (`sm` = h-8, `default` = h-9); mục đang chọn tô nền gold nhạt.\n * Lưu ý: Radix KHÔNG nhận `value=\"\"` cho SelectItem — dùng sentinel (vd `__empty__`).\n *\n * ─ VIỀN TRIGGER: BA TRẠNG THÁI, BA TOKEN ─\n * Trigger nền trong suốt nên VIỀN là thứ duy nhất khoanh vùng control → ngưỡng 3:1 ở mọi\n * trạng thái, và hover/focus phải ĐẬM HƠN lúc nghỉ chứ không nhạt đi:\n *\n * nghỉ `border-input` (= --control-edge) : 3,83:1 (light) · 4,94:1 (dark) ✓\n * hover `border-control-edge-strong` : 10,55:1 (light) · 11,65:1 (dark) ✓\n * focus `border-primary-ink` : 6,51:1 (light) · 12,67:1 (dark) ✓\n * lỗi `border-destructive-ink` : 7,15:1 (light) · 6,84:1 (dark) ✓\n *\n * Bản trước đi SAI CHIỀU ở light: `hover:border-ash-300` chỉ **1,62:1** và\n * `focus:border-gold-300` chỉ **1,86:1** — rê chuột hoặc focus vào là trigger biến mất, đúng\n * cái bẫy mà docs/hop-dong-mau.md §3 nêu. Viền lỗi cũ `border-red` (#D64545) chỉ 4,38:1 và là\n * token TÔ NỀN đem làm viền; bậc `-ink` mới là bậc đứng trên nền trang.\n * (`ring-*` giữ nguyên bậc `--destructive`/`--ring`: quầng ring là vai trò TÔ, đúng hợp đồng.)\n */\n\nexport type SelectProps = React.ComponentProps<typeof SelectPrimitive.Root>;\nexport type SelectGroupProps = React.ComponentProps<typeof SelectPrimitive.Group>;\nexport type SelectValueProps = React.ComponentProps<typeof SelectPrimitive.Value>;\nexport type SelectTriggerProps = React.ComponentProps<typeof SelectPrimitive.Trigger> & {\n /** Chiều cao trigger: `sm` = 32px, `default` = 36px. */\n size?: \"sm\" | \"default\";\n};\nexport type SelectContentProps = React.ComponentProps<typeof SelectPrimitive.Content>;\nexport type SelectLabelProps = React.ComponentProps<typeof SelectPrimitive.Label>;\nexport type SelectItemProps = React.ComponentProps<typeof SelectPrimitive.Item>;\nexport type SelectSeparatorProps = React.ComponentProps<typeof SelectPrimitive.Separator>;\nexport type SelectScrollUpButtonProps = React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>;\nexport type SelectScrollDownButtonProps = React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>;\n\nfunction Select({ ...props }: SelectProps) {\n return <SelectPrimitive.Root data-slot=\"select\" {...props} />;\n}\n\nfunction SelectGroup({ ...props }: SelectGroupProps) {\n return <SelectPrimitive.Group data-slot=\"select-group\" {...props} />;\n}\n\nfunction SelectValue({ ...props }: SelectValueProps) {\n return <SelectPrimitive.Value data-slot=\"select-value\" {...props} />;\n}\n\nfunction SelectTrigger({ className, size = \"default\", children, ...props }: SelectTriggerProps) {\n return (\n <SelectPrimitive.Trigger\n data-slot=\"select-trigger\"\n data-size={size}\n className={cn(\n \"border-input bg-background text-foreground data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground hover:border-control-edge-strong focus:outline-none focus:ring-2 focus:ring-ring/50 focus:border-primary-ink aria-invalid:border-destructive-ink aria-invalid:ring-destructive/30 flex w-fit items-center justify-between gap-2 rounded-md border px-3 text-sm whitespace-nowrap transition-colors duration-150 ease-out outline-none disabled:cursor-not-allowed disabled:opacity-50 disabled:bg-muted data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n {...props}\n >\n {children}\n <SelectPrimitive.Icon asChild>\n <ChevronDownIcon className=\"size-3.5 text-muted-foreground\" />\n </SelectPrimitive.Icon>\n </SelectPrimitive.Trigger>\n );\n}\n\nfunction SelectContent({ className, children, position = \"popper\", ...props }: SelectContentProps) {\n return (\n <SelectPrimitive.Portal>\n <SelectPrimitive.Content\n data-slot=\"select-content\"\n className={cn(\n \"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border border-border shadow-overlay\",\n position === \"popper\" &&\n \"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1\",\n className\n )}\n position={position}\n {...props}\n >\n <SelectScrollUpButton />\n <SelectPrimitive.Viewport\n className={cn(\n \"p-1\",\n position === \"popper\" &&\n \"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1\"\n )}\n >\n {children}\n </SelectPrimitive.Viewport>\n <SelectScrollDownButton />\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n );\n}\n\nfunction SelectLabel({ className, ...props }: SelectLabelProps) {\n return (\n <SelectPrimitive.Label\n data-slot=\"select-label\"\n className={cn(\"text-muted-foreground px-2 py-1.5 text-xs\", className)}\n {...props}\n />\n );\n}\n\n/**\n * SelectItem — mục trong danh sách chọn.\n *\n * ─ KHOẢNG CÁCH GIỮA CÁC MỤC: `mt-1 first:mt-0` ─\n * Đặt trên CHÍNH mục chứ không phải `gap` của vùng chứa, và cả họ menu của gói đều theo đúng lối\n * này (`CommandItem`, `DropdownMenuItem`/CheckboxItem/RadioItem/SubTrigger, `ContextMenu*`,\n * `Menubar*` — 14 công thức, cùng một class).\n *\n * Vì sao không `gap` ở vùng chứa: mục có thể nằm TRỰC TIẾP trong viewport hoặc lồng trong một\n * `Group`, nên `gap` phải khai ở hai chỗ và sẽ lệch nhau ngay lần sửa đầu tiên. `:first-child`\n * thì tính theo cha thật của mục, nên một class chạy đúng cho cả hai bố cục.\n *\n * Và vì sao không đổi viewport sang `display:flex`: viewport của Radix mang sẵn\n * `h-[var(--radix-select-trigger-height)]` — hôm nay không ăn thua vì nó là flex item của Content,\n * nhưng đổi kiểu bố cục ra là đánh cược rằng nó vẫn im. Margin không đụng tới chuyện đó.\n *\n * ⚠ `Menubar`Trigger KHÔNG có class này: nó nằm trên thanh NGANG, không phải trong danh sách dọc.\n *\n * Mục ĐANG RÊ/ĐANG FOCUS và mục ĐÃ CHỌN dùng CÙNG một tint `--accent`, và tint đó quá nhạt để\n * TỰ mang thông tin: ~1,0:1 với nền của popover. Nó chỉ là lớp nhận biết mềm.\n *\n * `bg-accent` chứ không phải `bg-gold-50 dark:bg-gold-500/10` như bản trước: `--accent` LÀ nhóm\n * token mà hợp đồng màu cấp riêng cho tint trạng thái nhạt, và mọi menu khác của gói\n * (`DropdownMenu`, `ContextMenu`, `Menubar`) đều dùng đúng nó — `Select` là chỗ duy nhất còn tô\n * bằng màu thang thô. Ở light hai bên là CÙNG một hex `#FBF5DC` nên không đổi gì trên màn hình;\n * ở dark tint đổi từ warm `#191814` sang trung tính `#1E2128`, tức khớp lại với các menu kia.\n *\n * ⚠ Vậy nên thứ NÓI \"đã chọn\" phải là DẤU CHECK bên phải, không phải nền: nó là một hình dạng\n * (WCAG 1.4.1 — không truyền tin chỉ bằng màu) và được nhuộm `text-app-accent-ink`, đo TRÊN CHÍNH\n * tint gold mà nó đứng lên: 5,96:1 (light) · 11,15:1 (dark), tệ nhất 10,04:1 khi popover nằm trên\n * `--muted`. Bỏ dấu check đi là mục đã chọn chỉ còn khác nhau ở một lớp tint 1,09:1 — lúc đó mới\n * là lỗi thật.\n *\n * Trước đây còn một vạch `border-l-2 border-l-app-accent-ink` làm dấu hiệu THỨ HAI. Đã bỏ: dấu\n * check một mình đã đủ cho cả 1.4.1 lẫn 1.4.11, `Combobox`/`DropdownMenu` xưa nay cũng chỉ có dấu\n * check, và mẫu Select ở comitor-ds không có vạch nào. Bỏ luôn `border-l-transparent` đi kèm —\n * nó chỉ tồn tại để giữ chỗ 2px cho vạch đó, giờ `pl-2` khớp đúng bản duyệt.\n *\n * Vì sao dấu check là `-ink` chứ không `--app-accent` trần: gold `#E8B824` chỉ 1,86:1 với nền\n * trắng — đúng cái bẫy \"token tô NỀN đem vẽ DẤU\" mà hợp đồng màu §1 cấm.\n */\nfunction SelectItem({ className, children, ...props }: SelectItemProps) {\n return (\n <SelectPrimitive.Item\n data-slot=\"select-item\"\n className={cn(\n \"focus:bg-accent data-[state=checked]:bg-accent [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 mt-1 first:mt-0 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none transition-colors duration-150 ease-out data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2\",\n className\n )}\n {...props}\n >\n <span className=\"absolute right-2 flex size-3.5 items-center justify-center\">\n <SelectPrimitive.ItemIndicator>\n <CheckIcon className=\"size-4 text-app-accent-ink\" />\n </SelectPrimitive.ItemIndicator>\n </span>\n <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n </SelectPrimitive.Item>\n );\n}\n\nfunction SelectSeparator({ className, ...props }: SelectSeparatorProps) {\n return (\n <SelectPrimitive.Separator\n data-slot=\"select-separator\"\n className={cn(\"bg-border pointer-events-none -mx-1 my-1 h-px\", className)}\n {...props}\n />\n );\n}\n\nfunction SelectScrollUpButton({ className, ...props }: SelectScrollUpButtonProps) {\n return (\n <SelectPrimitive.ScrollUpButton\n data-slot=\"select-scroll-up-button\"\n className={cn(\"flex cursor-default items-center justify-center py-1\", className)}\n {...props}\n >\n <ChevronUpIcon className=\"size-4\" />\n </SelectPrimitive.ScrollUpButton>\n );\n}\n\nfunction SelectScrollDownButton({ className, ...props }: SelectScrollDownButtonProps) {\n return (\n <SelectPrimitive.ScrollDownButton\n data-slot=\"select-scroll-down-button\"\n className={cn(\"flex cursor-default items-center justify-center py-1\", className)}\n {...props}\n >\n <ChevronDownIcon className=\"size-4\" />\n </SelectPrimitive.ScrollDownButton>\n );\n}\n\nexport {\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectScrollDownButton,\n SelectScrollUpButton,\n SelectSeparator,\n SelectTrigger,\n SelectValue\n};\n"],"mappings":"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","names":[]}
@@ -8,8 +8,8 @@ const trackSizes = {
8
8
  md: "h-5 w-9"
9
9
  };
10
10
  const thumbSizes = {
11
- sm: "size-3 data-[state=checked]:translate-x-3",
12
- md: "size-4 data-[state=checked]:translate-x-4"
11
+ sm: "size-3 data-[state=checked]:translate-x-3.5",
12
+ md: "size-4 data-[state=checked]:translate-x-4.5"
13
13
  };
14
14
  function Switch({ className, size = "md", label, description, id, ...props }) {
15
15
  const generatedId = React.useId();
@@ -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 * ─ BA LỚP MÀU, BA TOKEN VAI TRÒ — KHÔNG viết thẳng thang màu ─\n *\n * rãnh TẮT `bg-switch-track` (comitor-ds: bg-gray-300 / dark:bg-gray-700)\n * rãnh BẬT `data-[state=checked]:bg-control-on` (comitor-ds: bg-gold-300)\n * núm `bg-control-knob` (comitor-ds: bg-white)\n *\n * Ba token này là chỗ DUY NHẤT phân biệt hai bảng màu của gói (styles.css khối 5b). Viết thẳng\n * `bg-ash-300` / `bg-gold-300` / `bg-white` là khoá cứng bảng mặc định và bảng tương phản cao\n * lặng lẽ không đổi được công tắc — không có gì báo đỏ.\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:\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\n *\n * Số đo của hai bảng — bảng mặc định là bản duyệt comitor-ds, nó KHÔNG đạt (1) và (2), và đó là\n * quyết định đã chấp nhận (xem khối NGUỒN SỰ THẬT đầu styles.css):\n *\n * bảng MẶC ĐỊNH (light) bảng TƯƠNG PHẢN CAO (light · dark)\n * rãnh TẮT ⇄ nền 1,52:1 ✗ 3,83:1 · 4,94:1 ✓\n * rãnh BẬT ⇄ nền 1,86:1 ✗ 6,51:1 · 12,67:1 ✓\n * núm ⇄ rãnh BẬT 1,86:1 ✗ 6,51:1 · 12,67:1 ✓\n *\n * ⚠ Ở bảng tương phản cao, (3) rãnh BẬT ⇄ rãnh TẮT chỉ 1,70:1 (light) / 2,57:1 (dark) — cố ý:\n * hai 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 * ⚠ ĐỪNG dùng `--muted-foreground` cho rãnh TẮT dù số có vẻ vừa: đó là bậc CHỮ, nó bị kéo\n * tối/sáng theo nhu cầu đọc chữ (đã đổi một lần #7E8290 → #686C7A / #888D9B), tức ràng buộc\n * \"núm còn nổi trên rãnh\" không có gì bảo vệ.\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-switch-track 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":";AAkFM,cAkBA,YAlBA;AAhFN,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;AAiDA,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\n/*\n * Quãng dịch của núm phải để hở HAI BÊN BẰNG NHAU: núm nghỉ ở `translate-x-0.5` (2px), nên khi\n * BẬT nó phải dừng ở `rãnh − núm − 2px`, không phải ở một số tròn.\n *\n * md 36 − 16 − 2 = 18px → translate-x-4.5 (trước: translate-x-4 = 16px → hở phải 4px)\n * sm 28 − 12 − 2 = 14px → translate-x-3.5 (trước: translate-x-3 = 12px → hở phải 4px)\n *\n * Bản trước dùng số tròn nên công tắc hở 2px lúc TẮT mà 4px lúc BẬT — mắt bắt được ngay khi hai\n * công tắc khác trạng thái đứng cạnh nhau trong cùng một danh sách.\n */\nconst thumbSizes = {\n sm: \"size-3 data-[state=checked]:translate-x-3.5\",\n md: \"size-4 data-[state=checked]:translate-x-4.5\"\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 * ─ BA LỚP MÀU, BA TOKEN VAI TRÒ — KHÔNG viết thẳng thang màu ─\n *\n * rãnh TẮT `bg-switch-track` (comitor-ds: bg-gray-300 / dark:bg-gray-700)\n * rãnh BẬT `data-[state=checked]:bg-control-on` (comitor-ds: bg-gold-300)\n * núm `bg-control-knob` (comitor-ds: bg-white)\n *\n * Ba token này là chỗ DUY NHẤT phân biệt hai bảng màu của gói (styles.css khối 5b). Viết thẳng\n * `bg-ash-300` / `bg-gold-300` / `bg-white` là khoá cứng bảng mặc định và bảng tương phản cao\n * lặng lẽ không đổi được công tắc — không có gì báo đỏ.\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:\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\n *\n * Số đo của hai bảng — bảng mặc định là bản duyệt comitor-ds, nó KHÔNG đạt (1) và (2), và đó là\n * quyết định đã chấp nhận (xem khối NGUỒN SỰ THẬT đầu styles.css):\n *\n * bảng MẶC ĐỊNH (light) bảng TƯƠNG PHẢN CAO (light · dark)\n * rãnh TẮT ⇄ nền 1,52:1 ✗ 3,83:1 · 4,94:1 ✓\n * rãnh BẬT ⇄ nền 1,86:1 ✗ 6,51:1 · 12,67:1 ✓\n * núm ⇄ rãnh BẬT 1,86:1 ✗ 6,51:1 · 12,67:1 ✓\n *\n * ⚠ Ở bảng tương phản cao, (3) rãnh BẬT ⇄ rãnh TẮT chỉ 1,70:1 (light) / 2,57:1 (dark) — cố ý:\n * hai 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 * ⚠ ĐỪNG dùng `--muted-foreground` cho rãnh TẮT dù số có vẻ vừa: đó là bậc CHỮ, nó bị kéo\n * tối/sáng theo nhu cầu đọc chữ (đã đổi một lần #7E8290 → #686C7A / #888D9B), tức ràng buộc\n * \"núm còn nổi trên rãnh\" không có gì bảo vệ.\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-switch-track 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":";AA4FM,cAkBA,YAlBA;AA1FN,YAAY,qBAAqB;AACjC,YAAY,WAAW;AACvB,SAAS,UAAU;AAGnB,MAAM,aAAa;AAAA,EACjB,IAAI;AAAA,EACJ,IAAI;AACN;AAYA,MAAM,aAAa;AAAA,EACjB,IAAI;AAAA,EACJ,IAAI;AACN;AAiDA,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":[]}
@@ -9,7 +9,7 @@ function Table({ className, variant = "default", striped, ...props }) {
9
9
  "data-slot": "table",
10
10
  className: cn(
11
11
  "w-full caption-bottom text-sm",
12
- isStriped && "[&_tbody_tr:nth-child(even)]:bg-muted/30",
12
+ isStriped && "[&_tbody_tr:nth-child(even)]:bg-table-row-stripe [&_tbody_tr:nth-child(even):hover]:bg-table-row-hover",
13
13
  className
14
14
  ),
15
15
  ...props
@@ -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 data-[state=selected]:border-b-app-accent-ink",
41
+ "border-b border-border transition-colors hover:bg-table-row-hover 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\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":[]}
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 &&\n \"[&_tbody_tr:nth-child(even)]:bg-table-row-stripe [&_tbody_tr:nth-child(even):hover]:bg-table-row-hover\",\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-table-row-hover 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,aACE;AAAA,QACF;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":[]}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Bảng lớp của hai biến thể Tabs — module THUẦN, cố ý KHÔNG có `"use client"`.
3
+ *
4
+ * Vì sao tách khỏi `tabs.tsx`: hai nơi dùng chung nó — `Tabs` (Radix, có state, client) và
5
+ * `RouteTabs` (điều hướng bằng link, không state). Để bảng ở file client thì `RouteTabs` phải
6
+ * chép lại chuỗi lớp, và hai bản chép sẽ lệch nhau ngay lần đầu có người sửa một bên.
7
+ *
8
+ * Ranh giới RSC cắt theo FILE, nên hằng dữ liệu thuần phải sống ở module không directive —
9
+ * xem CHANGELOG 0.2.2 §2 và cổng `verify:directives`.
10
+ */
11
+ /**
12
+ * - `default`: gạch chân, vạch active màu brand — tab CẤP TRANG.
13
+ * - `pills`: viên bo tròn trên nền muted — tab cấp panel/thẻ, và tab điều hướng của trang cài đặt.
14
+ */
15
+ type TabsVariant = "default" | "pills";
16
+ declare const tabsListVariants: Record<TabsVariant, string>;
17
+ declare const tabsTriggerVariants: Record<TabsVariant, string>;
18
+ /** Vòng focus + trạng thái vô hiệu, chung cho cả hai biến thể và cả hai kiểu tab. */
19
+ declare const TABS_TRIGGER_BASE = "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";
20
+
21
+ export { TABS_TRIGGER_BASE, type TabsVariant, tabsListVariants, tabsTriggerVariants };
@@ -0,0 +1,15 @@
1
+ const tabsListVariants = {
2
+ default: "border-b border-border",
3
+ pills: "bg-muted p-1 rounded-lg"
4
+ };
5
+ const tabsTriggerVariants = {
6
+ 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",
7
+ 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"
8
+ };
9
+ const TABS_TRIGGER_BASE = "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";
10
+ export {
11
+ TABS_TRIGGER_BASE,
12
+ tabsListVariants,
13
+ tabsTriggerVariants
14
+ };
15
+ //# sourceMappingURL=tabs-variants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/ui/tabs-variants.ts"],"sourcesContent":["/**\n * Bảng lớp của hai biến thể Tabs — module THUẦN, cố ý KHÔNG có `\"use client\"`.\n *\n * Vì sao tách khỏi `tabs.tsx`: hai nơi dùng chung nó — `Tabs` (Radix, có state, client) và\n * `RouteTabs` (điều hướng bằng link, không state). Để bảng ở file client thì `RouteTabs` phải\n * chép lại chuỗi lớp, và hai bản chép sẽ lệch nhau ngay lần đầu có người sửa một bên.\n *\n * Ranh giới RSC cắt theo FILE, nên hằng dữ liệu thuần phải sống ở module không directive —\n * xem CHANGELOG 0.2.2 §2 và cổng `verify:directives`.\n */\n\n/**\n * - `default`: gạch chân, vạch active màu brand — tab CẤP TRANG.\n * - `pills`: viên bo tròn trên nền muted — tab cấp panel/thẻ, và tab điều hướng của trang cài đặt.\n */\nexport type TabsVariant = \"default\" | \"pills\";\n\nexport const tabsListVariants: Record<TabsVariant, string> = {\n default: \"border-b border-border\",\n pills: \"bg-muted p-1 rounded-lg\"\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. Đi qua token nên app đổi\n * brand là vạch đổi theo, thay vì đóng đinh vào một bậc của thang gold.\n */\nexport const tabsTriggerVariants: 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\n/** Vòng focus + trạng thái vô hiệu, chung cho cả hai biến thể và cả hai kiểu tab. */\nexport const TABS_TRIGGER_BASE =\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"],"mappings":"AAiBO,MAAM,mBAAgD;AAAA,EAC3D,SAAS;AAAA,EACT,OAAO;AACT;AAQO,MAAM,sBAAmD;AAAA,EAC9D,SACE;AAAA,EACF,OACE;AACJ;AAGO,MAAM,oBACX;","names":[]}
@@ -1,15 +1,7 @@
1
1
  import * as TabsPrimitive from '@radix-ui/react-tabs';
2
2
  import * as React from 'react';
3
+ import { TabsVariant } from './tabs-variants.js';
3
4
 
4
- /**
5
- * Tabs (Radix) — 2 biến thể thị giác:
6
- * - `default`: gạch chân, thanh active màu gold (dùng cho tab cấp trang).
7
- * - `pills`: viên bo tròn trên nền muted (dùng cho tab cấp panel/thẻ).
8
- *
9
- * `variant` phải truyền cho CẢ `TabsList` lẫn từng `TabsTrigger` — hai cấp không
10
- * chia sẻ context, đây là thiết kế của comitor-ds, giữ nguyên để tương thích.
11
- */
12
- type TabsVariant = "default" | "pills";
13
5
  type TabsProps = React.ComponentProps<typeof TabsPrimitive.Root>;
14
6
  type TabsListProps = React.ComponentProps<typeof TabsPrimitive.List> & {
15
7
  variant?: TabsVariant;
@@ -32,4 +24,4 @@ declare const Tabs: typeof TabsRoot & {
32
24
  Content: typeof TabsContent;
33
25
  };
34
26
 
35
- export { Tabs, TabsContent, type TabsContentProps, TabsList, type TabsListProps, type TabsProps, TabsTrigger, type TabsTriggerProps, type TabsVariant };
27
+ export { Tabs, TabsContent, type TabsContentProps, TabsList, type TabsListProps, type TabsProps, TabsTrigger, type TabsTriggerProps, TabsVariant };
@@ -2,37 +2,26 @@
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  import * as TabsPrimitive from "@radix-ui/react-tabs";
4
4
  import { cn } from "../../lib/cn.js";
5
+ import { TABS_TRIGGER_BASE, tabsListVariants, tabsTriggerVariants } from "./tabs-variants.js";
5
6
  function TabsRoot({ className, ...props }) {
6
7
  return /* @__PURE__ */ jsx(TabsPrimitive.Root, { "data-slot": "tabs", className: cn("flex flex-col gap-2", className), ...props });
7
8
  }
8
- const listVariants = {
9
- default: "border-b border-border",
10
- pills: "bg-muted p-1 rounded-lg"
11
- };
12
9
  function TabsList({ variant = "default", className, ...props }) {
13
10
  return /* @__PURE__ */ jsx(
14
11
  TabsPrimitive.List,
15
12
  {
16
13
  "data-slot": "tabs-list",
17
- className: cn("flex gap-0", listVariants[variant], className),
14
+ className: cn("flex gap-0", tabsListVariants[variant], className),
18
15
  ...props
19
16
  }
20
17
  );
21
18
  }
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-primary-ink data-[state=active]:text-foreground",
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
- };
26
19
  function TabsTrigger({ variant = "default", className, ...props }) {
27
20
  return /* @__PURE__ */ jsx(
28
21
  TabsPrimitive.Trigger,
29
22
  {
30
23
  "data-slot": "tabs-trigger",
31
- className: cn(
32
- triggerVariants[variant],
33
- "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",
34
- className
35
- ),
24
+ className: cn(tabsTriggerVariants[variant], TABS_TRIGGER_BASE, className),
36
25
  ...props
37
26
  }
38
27
  );
@@ -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\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":[]}
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\";\nimport { TABS_TRIGGER_BASE, type TabsVariant, tabsListVariants, tabsTriggerVariants } from \"./tabs-variants.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 * ⚠ Đây là tab CÓ STATE (Radix giữ tab nào đang mở, nội dung nằm trong `TabsContent` cùng trang).\n * Tab mà mỗi mục là một ROUTE riêng thì dùng `RouteTabs` — cùng bảng lớp, nhưng là `<nav>` chứa\n * link thật, không phải `role=\"tablist\"`.\n */\n\nexport type { TabsVariant };\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\nfunction TabsList({ variant = \"default\", className, ...props }: TabsListProps) {\n return (\n <TabsPrimitive.List\n data-slot=\"tabs-list\"\n className={cn(\"flex gap-0\", tabsListVariants[variant], className)}\n {...props}\n />\n );\n}\n\nfunction TabsTrigger({ variant = \"default\", className, ...props }: TabsTriggerProps) {\n return (\n <TabsPrimitive.Trigger\n data-slot=\"tabs-trigger\"\n className={cn(tabsTriggerVariants[variant], TABS_TRIGGER_BASE, className)}\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":";AA4BS;AA1BT,YAAY,mBAAmB;AAE/B,SAAS,UAAU;AACnB,SAAS,mBAAqC,kBAAkB,2BAA2B;AAsB3F,SAAS,SAAS,EAAE,WAAW,GAAG,MAAM,GAAc;AACpD,SAAO,oBAAC,cAAc,MAAd,EAAmB,aAAU,QAAO,WAAW,GAAG,uBAAuB,SAAS,GAAI,GAAG,OAAO;AAC1G;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,iBAAiB,OAAO,GAAG,SAAS;AAAA,MAC/D,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,SAAS,YAAY,EAAE,UAAU,WAAW,WAAW,GAAG,MAAM,GAAqB;AACnF,SACE;AAAA,IAAC,cAAc;AAAA,IAAd;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,oBAAoB,OAAO,GAAG,mBAAmB,SAAS;AAAA,MACvE,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":[]}
package/dist/index.d.ts CHANGED
@@ -18,6 +18,7 @@ export { LetterAvatar, LetterAvatarProps, LetterAvatarSize, getAvatarToneClasses
18
18
  export { PageContainer, PageContainerProps, PageContainerWidth } from './components/composite/page-container.js';
19
19
  export { PageHeader, PageHeaderBreadcrumbItem, PageHeaderLinkProps, PageHeaderProps } from './components/composite/page-header.js';
20
20
  export { RelativeTime, RelativeTimeProps } from './components/composite/relative-time.js';
21
+ export { RouteTabItem, RouteTabs, RouteTabsLinkComponent, RouteTabsLinkProps, RouteTabsProps } from './components/composite/route-tabs.js';
21
22
  export { SearchFilterBar, SearchFilterBarProps } from './components/composite/search-filter-bar.js';
22
23
  export { DEFAULT_SEARCH_FILTER_BAR_LABELS, SearchFilterBarLabels } from './components/composite/search-filter-bar-constants.js';
23
24
  export { StatCard, StatCardDelta, StatCardDeltaDirection, StatCardProps, StatCardTone, statCardIconVariants } from './components/composite/stat-card.js';
@@ -71,7 +72,8 @@ export { Slider, SliderProps } from './components/ui/slider.js';
71
72
  export { Spinner, SpinnerProps } from './components/ui/spinner.js';
72
73
  export { Switch, SwitchProps } from './components/ui/switch.js';
73
74
  export { Table, TableBody, TableBodyProps, TableCaption, TableCaptionProps, TableCell, TableCellProps, TableFooter, TableFooterProps, TableHead, TableHeadProps, TableHeader, TableHeaderProps, TableProps, TableRow, TableRowProps } from './components/ui/table.js';
74
- export { Tabs, TabsContent, TabsContentProps, TabsList, TabsListProps, TabsProps, TabsTrigger, TabsTriggerProps, TabsVariant } from './components/ui/tabs.js';
75
+ export { Tabs, TabsContent, TabsContentProps, TabsList, TabsListProps, TabsProps, TabsTrigger, TabsTriggerProps } from './components/ui/tabs.js';
76
+ export { TABS_TRIGGER_BASE, TabsVariant, tabsListVariants, tabsTriggerVariants } from './components/ui/tabs-variants.js';
75
77
  export { Textarea, TextareaProps } from './components/ui/textarea.js';
76
78
  export { Toggle, ToggleProps, ToggleVariantProps, toggleVariants } from './components/ui/toggle.js';
77
79
  export { ToggleGroup, ToggleGroupItem, ToggleGroupItemProps, ToggleGroupProps, ToggleGroupVariantProps } from './components/ui/toggle-group.js';
package/dist/index.js CHANGED
@@ -18,6 +18,7 @@ import { getAvatarToneClasses, LetterAvatar } from "./components/composite/lette
18
18
  export * from "./components/composite/page-container.js";
19
19
  export * from "./components/composite/page-header.js";
20
20
  export * from "./components/composite/relative-time.js";
21
+ export * from "./components/composite/route-tabs.js";
21
22
  export * from "./components/composite/search-filter-bar.js";
22
23
  export * from "./components/composite/search-filter-bar-constants.js";
23
24
  export * from "./components/composite/stat-card.js";
@@ -78,6 +79,7 @@ export * from "./components/ui/spinner.js";
78
79
  export * from "./components/ui/switch.js";
79
80
  export * from "./components/ui/table.js";
80
81
  export * from "./components/ui/tabs.js";
82
+ export * from "./components/ui/tabs-variants.js";
81
83
  export * from "./components/ui/textarea.js";
82
84
  export * from "./components/ui/toggle.js";
83
85
  export * from "./components/ui/toggle-group.js";
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/*\n * ⚠ Năm file `*-constants` bên dưới KHÔNG phải chỗ để gom code cho gọn — chúng là RANH GIỚI\n * SERVER/CLIENT. Component chủ (`data-table.tsx`, `date-picker.tsx`, `form-field.tsx`,\n * `search-filter-bar.tsx`, `table-pagination.tsx`) đều mở đầu bằng `\"use client\"`, mà mọi export\n * của một module client đến tay Server Component dưới dạng client-reference proxy: đọc ra\n * `undefined`, KHÔNG throw, KHÔNG warning, trong khi TypeScript vẫn khai kiểu thật. Nhãn mặc\n * định / preset / sentinel là hằng dữ liệu thuần nên phải nằm ngoài ranh giới đó, và barrel này\n * phải xuất chúng THẲNG — re-export vòng qua file client là vô hiệu.\n * Cùng lý do với `chart/chart-colors.ts`; chi tiết ghi ở đầu file đó.\n * `scripts/verify-directives.mjs` canh không cho hằng thuần lọt ngược vào file client.\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/data-table-constants.js\";\nexport * from \"./components/composite/date-picker.js\";\nexport * from \"./components/composite/date-picker-constants.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-field-constants.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/search-filter-bar-constants.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/table-pagination-constants.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":"AAqCA,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;AAKd,SAAS,sBAAsB,oBAAoB;AACnD,cAAc;AACd,cAAc;AACd,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":[]}
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/*\n * ⚠ Năm file `*-constants` bên dưới KHÔNG phải chỗ để gom code cho gọn — chúng là RANH GIỚI\n * SERVER/CLIENT. Component chủ (`data-table.tsx`, `date-picker.tsx`, `form-field.tsx`,\n * `search-filter-bar.tsx`, `table-pagination.tsx`) đều mở đầu bằng `\"use client\"`, mà mọi export\n * của một module client đến tay Server Component dưới dạng client-reference proxy: đọc ra\n * `undefined`, KHÔNG throw, KHÔNG warning, trong khi TypeScript vẫn khai kiểu thật. Nhãn mặc\n * định / preset / sentinel là hằng dữ liệu thuần nên phải nằm ngoài ranh giới đó, và barrel này\n * phải xuất chúng THẲNG — re-export vòng qua file client là vô hiệu.\n * Cùng lý do với `chart/chart-colors.ts`; chi tiết ghi ở đầu file đó.\n * `scripts/verify-directives.mjs` canh không cho hằng thuần lọt ngược vào file client.\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/data-table-constants.js\";\nexport * from \"./components/composite/date-picker.js\";\nexport * from \"./components/composite/date-picker-constants.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-field-constants.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/route-tabs.js\";\nexport * from \"./components/composite/search-filter-bar.js\";\nexport * from \"./components/composite/search-filter-bar-constants.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/table-pagination-constants.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\";\n/* Bảng lớp của Tabs xuất THẲNG từ module thuần — `RouteTabs` và `Tabs` dùng chung nó, và hằng\n dữ liệu đi vòng qua file \"use client\" sẽ thành proxy ở Server Component (CHANGELOG 0.2.2 §2). */\nexport * from \"./components/ui/tabs-variants.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":"AAqCA,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;AAKd,SAAS,sBAAsB,oBAAoB;AACnD,cAAc;AACd,cAAc;AACd,cAAc;AACd,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;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAGd,cAAc;AACd,cAAc;","names":[]}
@@ -77,56 +77,62 @@ function AppHeader({
77
77
  logo ? /* @__PURE__ */ jsx("div", { className: "shrink-0", children: logo }) : null,
78
78
  /* @__PURE__ */ jsx("div", { className: "flex min-w-0 items-center", children: breadcrumb ?? (title ? /* @__PURE__ */ jsx("h1", { className: "truncate text-sm font-semibold text-foreground", children: title }) : null) })
79
79
  ] }),
80
- /* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center justify-center", children: showSearch ? searchSlot ?? /* @__PURE__ */ jsxs(Fragment, { children: [
81
- /* @__PURE__ */ jsxs(
82
- "button",
83
- {
84
- type: "button",
85
- onClick: () => setCommandPaletteOpen(true),
86
- className: cn(
87
- // `rounded-full` + `w-64`: đúng ô tìm của AppHeader ở mục Patterns
88
- // (`<Input className="rounded-full w-64" />`). Pill cần nhiều đệm ngang hơn ô
89
- // vuông góc nên `px-3` thay vì `px-2.5`.
90
- "hidden h-8 w-64 items-center gap-2 rounded-full border border-input bg-muted/40 px-3 sm:flex",
91
- "text-sm text-muted-foreground transition-colors duration-150 ease-out",
92
- // Viền control lúc hover phải ĐẬM HƠN lúc nghỉ, không nhạt đi. `border-gold-300`
93
- // (#E8B824) chỉ được 1,86:1 với `--background` và 1,73:1 với `--muted` (chính là
94
- // nền mà `hover:bg-muted` đổi sang) ở light — ô tìm gần như MẤT VIỀN đúng lúc rê
95
- // 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à
96
- // bậc thang màu thô, không đổi theo theme, nên ở dark cùng giá trị đó thành
97
- // 10,19:1 — mỗi theme một hành vi.
98
- // `--control-edge-strong` là bậc viền control lúc hover, đo đúng cho vai trò này:
99
- // 10,55:1 / 9,86:1 (light, trên `--background` / `--muted`) · 11,65:1 / 9,93:1
100
- // (dark). Lúc nghỉ vẫn `border-input` = 3,83:1 (light) · 4,94:1 (dark) ✓.
101
- "hover:border-control-edge-strong hover:bg-muted"
102
- ),
103
- children: [
104
- /* @__PURE__ */ jsx(Search, { className: "size-3.5 shrink-0" }),
105
- /* @__PURE__ */ jsx("span", { className: "flex-1 truncate text-left", children: labels.searchPlaceholder }),
106
- /* @__PURE__ */ jsxs(Kbd, { children: [
107
- modKey,
108
- "K"
109
- ] })
110
- ]
111
- }
112
- ),
113
- /* @__PURE__ */ jsx(
80
+ /* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center justify-center", children: showSearch ? searchSlot ?? /* @__PURE__ */ jsxs(
81
+ "button",
82
+ {
83
+ type: "button",
84
+ onClick: () => setCommandPaletteOpen(true),
85
+ className: cn(
86
+ // `rounded-full` + `w-64`: đúng ô tìm của AppHeader ở mục Patterns
87
+ // (`<Input className="rounded-full w-64" />`). Pill cần nhiều đệm ngang hơn ô
88
+ // vuông góc nên `px-3` thay vì `px-2.5`.
89
+ //
90
+ // ⚠ `lg:` chứ KHÔNG phải `sm:`. Ô tìm nằm ở vùng GIỮA và `shrink-0`, nên nó
91
+ // ăn 256px cố định của vùng nội dung. Dải nguy hiểm là md→lg: từ 768px thanh
92
+ // bên đã hiện (chiếm 256px) mà vùng nội dung mới 512px — trừ ô tìm thì hai
93
+ // bên chỉ còn 104px mỗi bên, không đủ cho cụm nút (~190px). Đo thật ở 768px
94
+ // trước khi vá: ba nút header tụt còn 16px bề ngang.
95
+ // Từ 1024px vùng nội dung là 768px → mỗi bên 232px, vừa đủ. Dưới ngưỡng đó
96
+ // dùng nút icon (36px) ngay bên dưới; phím ⌘K vẫn mở bảng lệnh như thường.
97
+ "hidden h-8 w-64 items-center gap-2 rounded-full border border-input bg-muted/40 px-3 lg:flex",
98
+ "text-sm text-muted-foreground transition-colors duration-150 ease-out",
99
+ // Viền control lúc hover phải ĐẬM HƠN lúc nghỉ, không nhạt đi. `border-gold-300`
100
+ // (#E8B824) chỉ được 1,86:1 với `--background` và 1,73:1 với `--muted` (chính là
101
+ // nền mà `hover:bg-muted` đổi sang) ở light — ô tìm gần như MẤT VIỀN đúng lúc rê
102
+ // 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à
103
+ // bậc thang màu thô, không đổi theo theme, nên ở dark cùng giá trị đó thành
104
+ // 10,19:1 — mỗi theme một hành vi.
105
+ // `--control-edge-strong` là bậc viền control lúc hover, đo đúng cho vai trò này:
106
+ // 10,55:1 / 9,86:1 (light, trên `--background` / `--muted`) · 11,65:1 / 9,93:1
107
+ // (dark). Lúc nghỉ vẫn `border-input` = 3,83:1 (light) · 4,94:1 (dark) ✓.
108
+ "hover:border-control-edge-strong hover:bg-muted"
109
+ ),
110
+ children: [
111
+ /* @__PURE__ */ jsx(Search, { className: "size-3.5 shrink-0" }),
112
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate text-left", children: labels.searchPlaceholder }),
113
+ /* @__PURE__ */ jsxs(Kbd, { children: [
114
+ modKey,
115
+ "K"
116
+ ] })
117
+ ]
118
+ }
119
+ ) : null }),
120
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-1 items-center justify-end gap-1", children: [
121
+ extraSlot,
122
+ showSearch && !searchSlot ? /* @__PURE__ */ jsx(
114
123
  "button",
115
124
  {
116
125
  type: "button",
117
126
  onClick: () => setCommandPaletteOpen(true),
118
127
  "aria-label": labels.search,
119
128
  className: cn(
120
- "flex size-9 items-center justify-center rounded-md text-muted-foreground sm:hidden",
129
+ "flex size-9 items-center justify-center rounded-md text-muted-foreground lg:hidden",
121
130
  "transition-colors duration-150 ease-out hover:bg-muted hover:text-foreground"
122
131
  ),
123
132
  children: /* @__PURE__ */ jsx(Search, { className: "size-4" })
124
133
  }
125
- )
126
- ] }) : null }),
127
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center justify-end gap-1", children: [
128
- extraSlot,
129
- showAppLauncher ? /* @__PURE__ */ jsx(AppLauncher, {}) : null,
134
+ ) : null,
135
+ showAppLauncher ? /* @__PURE__ */ jsx(AppLauncher, { className: "hidden md:flex" }) : null,
130
136
  notificationsSlot ?? (notificationCount === void 0 ? null : /* @__PURE__ */ jsx(
131
137
  NotificationsButton,
132
138
  {
@@ -135,7 +141,7 @@ function AppHeader({
135
141
  onClick: onNotificationsClick
136
142
  }
137
143
  )),
138
- showThemeToggle ? /* @__PURE__ */ jsx(ThemeToggle, { labels }) : null,
144
+ showThemeToggle ? /* @__PURE__ */ jsx(ThemeToggle, { labels, className: "hidden md:flex" }) : null,
139
145
  showUserMenu && user ? /* @__PURE__ */ jsxs(Fragment, { children: [
140
146
  /* @__PURE__ */ jsx("span", { className: "mx-1 hidden h-5 w-px bg-border sm:block", "aria-hidden": "true" }),
141
147
  /* @__PURE__ */ jsx(UserMenu, { ...userMenuProps })
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/shell/app-header.tsx"],"sourcesContent":["\"use client\";\n\nimport { Menu, PanelLeft, Search } from \"lucide-react\";\nimport type { ReactNode } from \"react\";\n// `useIsMac` nhập thẳng từ Tier 2: 0.2.0 chỉ còn MỘT cửa xuất cho nó (entry `.`),\n// `/shell` không re-export nữa.\nimport { useIsMac } from \"../components/composite/keyboard-hint.js\";\nimport { Kbd } from \"../components/ui/kbd.js\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"../components/ui/tooltip.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { AppLauncher } from \"./app-launcher.js\";\nimport { NotificationsButton } from \"./notifications-button.js\";\nimport { useShell } from \"./shell-context.js\";\nimport { ThemeToggle } from \"./theme-toggle.js\";\nimport { UserMenu, type UserMenuProps } from \"./user-menu.js\";\n\nexport interface AppHeaderProps {\n className?: string;\n /** Tiêu đề trang — dùng khi không truyền `breadcrumb`. */\n title?: ReactNode;\n /** Slot breadcrumb (composite `PageHeader` của app) — ưu tiên hơn `title`. */\n breadcrumb?: ReactNode;\n /** Slot ngoài cùng bên trái (vd `<ComitorLockup size=\"xs\" variant=\"auto\" />`). */\n logo?: ReactNode;\n\n /** Thay ô tìm kiếm dựng sẵn bằng UI riêng của app. */\n searchSlot?: ReactNode;\n showSearch?: boolean;\n /** Nút thu gọn sidebar ở header (layout sidebar-first thường để ở sidebar). */\n showSidebarToggle?: boolean;\n\n /** Slot thông báo (app tự render Popover + panel). Có slot thì `notificationCount` bị bỏ qua. */\n notificationsSlot?: ReactNode;\n notificationCount?: number;\n onNotificationsClick?: () => void;\n\n /** Slot phụ bên phải, đứng trước AppLauncher. */\n extraSlot?: ReactNode;\n\n showAppLauncher?: boolean;\n showThemeToggle?: boolean;\n showUserMenu?: boolean;\n /** Chuyển tiếp props cho `UserMenu` (profileHref, settingsHref, items…). */\n userMenuProps?: UserMenuProps;\n}\n\n/**\n * Header cao 56px (`--header-height`), nền `bg-background/80 backdrop-blur`.\n * Comitor là hệ sáng — **không** dùng header luôn-tối.\n *\n * BA VÙNG, và ô tìm nằm ở vùng GIỮA — đúng bố cục AppHeader ở mục Patterns của comitor-ds:\n *\n * trái hamburger (mobile) / nút thu gọn sidebar + logo + breadcrumb hoặc tiêu đề\n * giữa ô tìm (Cmd/Ctrl+K), bo tròn hết cỡ\n * phải AppLauncher · thông báo · ThemeToggle · UserMenu\n *\n * Hai vùng ngoài đều `flex-1`, vùng giữa `shrink-0` — đó là điều kiện để vùng giữa nằm CHÍNH GIỮA\n * header bất kể breadcrumb bên trái dài ngắn thế nào. Nếu để ô tìm trong cụm phải (bản trước) thì\n * nó trôi theo số lượng nút bên phải; nếu căn giữa bằng `absolute left-1/2` thì nó đè lên\n * breadcrumb dài. Cách này không mắc cả hai.\n */\nexport function AppHeader({\n className,\n title,\n breadcrumb,\n logo,\n searchSlot,\n showSearch = true,\n showSidebarToggle = false,\n notificationsSlot,\n notificationCount,\n onNotificationsClick,\n extraSlot,\n showAppLauncher = true,\n showThemeToggle = true,\n showUserMenu = true,\n userMenuProps\n}: AppHeaderProps) {\n const { setMobileMenuOpen, setCommandPaletteOpen, toggleSidebar, sidebarCollapsed, labels, user } = useShell();\n const isMac = useIsMac();\n const modKey = isMac ? \"⌘\" : \"Ctrl\";\n\n return (\n <header\n className={cn(\n \"sticky top-0 z-40 flex h-header shrink-0 items-center gap-2 px-3 sm:px-4\",\n \"border-b border-border bg-background/80 backdrop-blur-sm\",\n className\n )}\n >\n {/* ── VÙNG TRÁI ─────────────────────────────────────────────────────── */}\n <div className=\"flex min-w-0 flex-1 items-center gap-2\">\n {/* Mở menu off-canvas — chỉ màn hình hẹp */}\n <button\n type=\"button\"\n onClick={() => setMobileMenuOpen(true)}\n aria-label={labels.openMenu}\n className={cn(\n \"-ml-1 flex size-9 shrink-0 items-center justify-center rounded-md text-muted-foreground md:hidden\",\n \"transition-colors duration-150 ease-out hover:bg-muted hover:text-foreground\"\n )}\n >\n <Menu className=\"size-5\" />\n </button>\n\n {showSidebarToggle ? (\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n type=\"button\"\n onClick={toggleSidebar}\n aria-label={sidebarCollapsed ? labels.expandSidebar : labels.collapseSidebar}\n className={cn(\n \"hidden size-9 shrink-0 items-center justify-center rounded-md text-muted-foreground md:flex\",\n \"transition-colors duration-150 ease-out hover:bg-muted hover:text-foreground\"\n )}\n >\n <PanelLeft className=\"size-4\" />\n </button>\n </TooltipTrigger>\n <TooltipContent side=\"bottom\">\n {sidebarCollapsed ? labels.expandSidebar : labels.collapseSidebar} · {modKey} + B\n </TooltipContent>\n </Tooltip>\n ) : null}\n\n {logo ? <div className=\"shrink-0\">{logo}</div> : null}\n\n {/* Breadcrumb thắng title khi có cả hai */}\n <div className=\"flex min-w-0 items-center\">\n {breadcrumb ?? (title ? <h1 className=\"truncate text-sm font-semibold text-foreground\">{title}</h1> : null)}\n </div>\n </div>\n\n {/* ── VÙNG GIỮA: ô tìm ──────────────────────────────────────────────── */}\n <div className=\"flex shrink-0 items-center justify-center\">\n {showSearch\n ? (searchSlot ?? (\n <>\n {/* ≥sm: ô giả lập trường tìm kiếm, kèm gợi ý phím tắt */}\n <button\n type=\"button\"\n onClick={() => setCommandPaletteOpen(true)}\n className={cn(\n // `rounded-full` + `w-64`: đúng ô tìm của AppHeader ở mục Patterns\n // (`<Input className=\"rounded-full w-64\" />`). Pill cần nhiều đệm ngang hơn ô\n // vuông góc nên `px-3` thay vì `px-2.5`.\n \"hidden h-8 w-64 items-center gap-2 rounded-full border border-input bg-muted/40 px-3 sm:flex\",\n \"text-sm text-muted-foreground transition-colors duration-150 ease-out\",\n // Viền control lúc hover phải ĐẬM HƠN lúc nghỉ, không nhạt đi. `border-gold-300`\n // (#E8B824) chỉ được 1,86:1 với `--background` và 1,73:1 với `--muted` (chính là\n // nền mà `hover:bg-muted` đổi sang) ở light — ô tìm gần như MẤT VIỀN đúng lúc rê\n // 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à\n // bậc thang màu thô, không đổi theo theme, nên ở dark cùng giá trị đó thành\n // 10,19:1 — mỗi theme một hành vi.\n // `--control-edge-strong` là bậc viền control lúc hover, đo đúng cho vai trò này:\n // 10,55:1 / 9,86:1 (light, trên `--background` / `--muted`) · 11,65:1 / 9,93:1\n // (dark). Lúc nghỉ vẫn `border-input` = 3,83:1 (light) · 4,94:1 (dark) ✓.\n \"hover:border-control-edge-strong hover:bg-muted\"\n )}\n >\n <Search className=\"size-3.5 shrink-0\" />\n <span className=\"flex-1 truncate text-left\">{labels.searchPlaceholder}</span>\n <Kbd>{modKey}K</Kbd>\n </button>\n\n {/* <sm: chỉ icon */}\n <button\n type=\"button\"\n onClick={() => setCommandPaletteOpen(true)}\n aria-label={labels.search}\n className={cn(\n \"flex size-9 items-center justify-center rounded-md text-muted-foreground sm:hidden\",\n \"transition-colors duration-150 ease-out hover:bg-muted hover:text-foreground\"\n )}\n >\n <Search className=\"size-4\" />\n </button>\n </>\n ))\n : null}\n </div>\n\n {/* ── VÙNG PHẢI ─────────────────────────────────────────────────────── */}\n <div className=\"flex min-w-0 flex-1 items-center justify-end gap-1\">\n {extraSlot}\n\n {showAppLauncher ? <AppLauncher /> : null}\n\n {notificationsSlot ??\n (notificationCount === undefined ? null : (\n <NotificationsButton\n count={notificationCount}\n label={labels.notifications}\n onClick={onNotificationsClick}\n />\n ))}\n\n {showThemeToggle ? <ThemeToggle labels={labels} /> : null}\n\n {showUserMenu && user ? (\n <>\n <span className=\"mx-1 hidden h-5 w-px bg-border sm:block\" aria-hidden=\"true\" />\n <UserMenu {...userMenuProps} />\n </>\n ) : null}\n </div>\n </header>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/shell/app-header.tsx"],"sourcesContent":["\"use client\";\n\nimport { Menu, PanelLeft, Search } from \"lucide-react\";\nimport type { ReactNode } from \"react\";\n// `useIsMac` nhập thẳng từ Tier 2: 0.2.0 chỉ còn MỘT cửa xuất cho nó (entry `.`),\n// `/shell` không re-export nữa.\nimport { useIsMac } from \"../components/composite/keyboard-hint.js\";\nimport { Kbd } from \"../components/ui/kbd.js\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"../components/ui/tooltip.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { AppLauncher } from \"./app-launcher.js\";\nimport { NotificationsButton } from \"./notifications-button.js\";\nimport { useShell } from \"./shell-context.js\";\nimport { ThemeToggle } from \"./theme-toggle.js\";\nimport { UserMenu, type UserMenuProps } from \"./user-menu.js\";\n\nexport interface AppHeaderProps {\n className?: string;\n /** Tiêu đề trang — dùng khi không truyền `breadcrumb`. */\n title?: ReactNode;\n /** Slot breadcrumb (composite `PageHeader` của app) — ưu tiên hơn `title`. */\n breadcrumb?: ReactNode;\n /** Slot ngoài cùng bên trái (vd `<ComitorLockup size=\"xs\" variant=\"auto\" />`). */\n logo?: ReactNode;\n\n /** Thay ô tìm kiếm dựng sẵn bằng UI riêng của app. */\n searchSlot?: ReactNode;\n showSearch?: boolean;\n /** Nút thu gọn sidebar ở header (layout sidebar-first thường để ở sidebar). */\n showSidebarToggle?: boolean;\n\n /** Slot thông báo (app tự render Popover + panel). Có slot thì `notificationCount` bị bỏ qua. */\n notificationsSlot?: ReactNode;\n notificationCount?: number;\n onNotificationsClick?: () => void;\n\n /** Slot phụ bên phải, đứng trước AppLauncher. */\n extraSlot?: ReactNode;\n\n showAppLauncher?: boolean;\n showThemeToggle?: boolean;\n showUserMenu?: boolean;\n /** Chuyển tiếp props cho `UserMenu` (profileHref, settingsHref, items…). */\n userMenuProps?: UserMenuProps;\n}\n\n/**\n * Header cao 56px (`--header-height`), nền `bg-background/80 backdrop-blur`.\n * Comitor là hệ sáng — **không** dùng header luôn-tối.\n *\n * BA VÙNG, và ô tìm nằm ở vùng GIỮA — đúng bố cục AppHeader ở mục Patterns của comitor-ds:\n *\n * trái hamburger (mobile) / nút thu gọn sidebar + logo + breadcrumb hoặc tiêu đề\n * giữa ô tìm (Cmd/Ctrl+K), bo tròn hết cỡ\n * phải AppLauncher · thông báo · ThemeToggle · UserMenu\n *\n * Hai vùng ngoài đều `flex-1`, vùng giữa `shrink-0` — đó là điều kiện để vùng giữa nằm CHÍNH GIỮA\n * header bất kể breadcrumb bên trái dài ngắn thế nào. Nếu để ô tìm trong cụm phải (bản trước) thì\n * nó trôi theo số lượng nút bên phải; nếu căn giữa bằng `absolute left-1/2` thì nó đè lên\n * breadcrumb dài. Cách này không mắc cả hai.\n */\nexport function AppHeader({\n className,\n title,\n breadcrumb,\n logo,\n searchSlot,\n showSearch = true,\n showSidebarToggle = false,\n notificationsSlot,\n notificationCount,\n onNotificationsClick,\n extraSlot,\n showAppLauncher = true,\n showThemeToggle = true,\n showUserMenu = true,\n userMenuProps\n}: AppHeaderProps) {\n const { setMobileMenuOpen, setCommandPaletteOpen, toggleSidebar, sidebarCollapsed, labels, user } = useShell();\n const isMac = useIsMac();\n const modKey = isMac ? \"⌘\" : \"Ctrl\";\n\n return (\n <header\n className={cn(\n \"sticky top-0 z-40 flex h-header shrink-0 items-center gap-2 px-3 sm:px-4\",\n \"border-b border-border bg-background/80 backdrop-blur-sm\",\n className\n )}\n >\n {/* ── VÙNG TRÁI ─────────────────────────────────────────────────────── */}\n <div className=\"flex min-w-0 flex-1 items-center gap-2\">\n {/* Mở menu off-canvas — chỉ màn hình hẹp */}\n <button\n type=\"button\"\n onClick={() => setMobileMenuOpen(true)}\n aria-label={labels.openMenu}\n className={cn(\n \"-ml-1 flex size-9 shrink-0 items-center justify-center rounded-md text-muted-foreground md:hidden\",\n \"transition-colors duration-150 ease-out hover:bg-muted hover:text-foreground\"\n )}\n >\n <Menu className=\"size-5\" />\n </button>\n\n {showSidebarToggle ? (\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n type=\"button\"\n onClick={toggleSidebar}\n aria-label={sidebarCollapsed ? labels.expandSidebar : labels.collapseSidebar}\n className={cn(\n \"hidden size-9 shrink-0 items-center justify-center rounded-md text-muted-foreground md:flex\",\n \"transition-colors duration-150 ease-out hover:bg-muted hover:text-foreground\"\n )}\n >\n <PanelLeft className=\"size-4\" />\n </button>\n </TooltipTrigger>\n <TooltipContent side=\"bottom\">\n {sidebarCollapsed ? labels.expandSidebar : labels.collapseSidebar} · {modKey} + B\n </TooltipContent>\n </Tooltip>\n ) : null}\n\n {logo ? <div className=\"shrink-0\">{logo}</div> : null}\n\n {/* Breadcrumb thắng title khi có cả hai */}\n <div className=\"flex min-w-0 items-center\">\n {breadcrumb ?? (title ? <h1 className=\"truncate text-sm font-semibold text-foreground\">{title}</h1> : null)}\n </div>\n </div>\n\n {/*\n ── VÙNG GIỮA: ô tìm dạng PILL, và chỉ nó ────────────────────────────\n Từ `lg` trở lên vùng này giữ ô tìm 256px và nằm chính giữa header. Dưới `lg` ô pill bị\n `hidden` nên vùng này RỖNG và co về 0 — lúc đó hai vùng `flex-1` hai bên chia đôi phần\n còn lại, và nút icon tìm kiếm (nằm ở vùng PHẢI) được căn phải cùng các icon khác.\n ⚠ Nút icon KHÔNG được để ở đây: nó sẽ bị căn giữa header, tức một icon lẻ trôi giữa\n khoảng trống trong khi mọi icon khác nằm sát phải.\n */}\n <div className=\"flex shrink-0 items-center justify-center\">\n {showSearch\n ? (searchSlot ?? (\n <button\n type=\"button\"\n onClick={() => setCommandPaletteOpen(true)}\n className={cn(\n // `rounded-full` + `w-64`: đúng ô tìm của AppHeader ở mục Patterns\n // (`<Input className=\"rounded-full w-64\" />`). Pill cần nhiều đệm ngang hơn ô\n // vuông góc nên `px-3` thay vì `px-2.5`.\n //\n // ⚠ `lg:` chứ KHÔNG phải `sm:`. Ô tìm nằm ở vùng GIỮA và `shrink-0`, nên nó\n // ăn 256px cố định của vùng nội dung. Dải nguy hiểm là md→lg: từ 768px thanh\n // bên đã hiện (chiếm 256px) mà vùng nội dung mới 512px — trừ ô tìm thì hai\n // bên chỉ còn 104px mỗi bên, không đủ cho cụm nút (~190px). Đo thật ở 768px\n // trước khi vá: ba nút header tụt còn 16px bề ngang.\n // Từ 1024px vùng nội dung là 768px → mỗi bên 232px, vừa đủ. Dưới ngưỡng đó\n // dùng nút icon (36px) ngay bên dưới; phím ⌘K vẫn mở bảng lệnh như thường.\n \"hidden h-8 w-64 items-center gap-2 rounded-full border border-input bg-muted/40 px-3 lg:flex\",\n \"text-sm text-muted-foreground transition-colors duration-150 ease-out\",\n // Viền control lúc hover phải ĐẬM HƠN lúc nghỉ, không nhạt đi. `border-gold-300`\n // (#E8B824) chỉ được 1,86:1 với `--background` và 1,73:1 với `--muted` (chính là\n // nền mà `hover:bg-muted` đổi sang) ở light — ô tìm gần như MẤT VIỀN đúng lúc rê\n // 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à\n // bậc thang màu thô, không đổi theo theme, nên ở dark cùng giá trị đó thành\n // 10,19:1 — mỗi theme một hành vi.\n // `--control-edge-strong` là bậc viền control lúc hover, đo đúng cho vai trò này:\n // 10,55:1 / 9,86:1 (light, trên `--background` / `--muted`) · 11,65:1 / 9,93:1\n // (dark). Lúc nghỉ vẫn `border-input` = 3,83:1 (light) · 4,94:1 (dark) ✓.\n \"hover:border-control-edge-strong hover:bg-muted\"\n )}\n >\n <Search className=\"size-3.5 shrink-0\" />\n <span className=\"flex-1 truncate text-left\">{labels.searchPlaceholder}</span>\n <Kbd>{modKey}K</Kbd>\n </button>\n ))\n : null}\n </div>\n\n {/* ── VÙNG PHẢI ─────────────────────────────────────────────────────── */}\n {/* `shrink-0`: cụm nút KHÔNG được co. Thiếu nó thì flexbox bóp chúng thay vì bóp ô tìm —\n ở 768px (thanh bên vừa hiện mà vùng nội dung mới 512px) chúng tụt còn 16px bề ngang. */}\n <div className=\"flex shrink-0 flex-1 items-center justify-end gap-1\">\n {extraSlot}\n\n {/*\n Dưới `lg`: ô tìm thu thành icon và đứng ở ĐÂY, cùng hàng với các icon khác bên phải —\n không phải ở vùng giữa. `searchSlot` của app thì không có bản icon nên nó ở lại vùng\n giữa và tự lo phần thu hẹp.\n */}\n {showSearch && !searchSlot ? (\n <button\n type=\"button\"\n onClick={() => setCommandPaletteOpen(true)}\n aria-label={labels.search}\n className={cn(\n \"flex size-9 items-center justify-center rounded-md text-muted-foreground lg:hidden\",\n \"transition-colors duration-150 ease-out hover:bg-muted hover:text-foreground\"\n )}\n >\n <Search className=\"size-4\" />\n </button>\n ) : null}\n\n {/*\n `hidden md:flex`: nút danh sách ứng dụng chỉ ở desktop. Dưới `md`, `MobileMenu` đã\n render `<AppLauncher inline variant=\"list\">` ngay trong menu hamburger — để cả hai là\n hai lối vào cho cùng một danh sách, và trên màn hình hẹp thì mỗi ô 36px của header đều\n đáng giá. Cùng mốc `md` với nút đổi giao diện ngay dưới.\n ⚠ App nào tắt `showMobileMenu`/`showAppLauncher` của `MobileMenu` thì phải xét lại đây,\n nếu không màn hình hẹp sẽ mất hẳn lối vào danh sách ứng dụng.\n */}\n {showAppLauncher ? <AppLauncher className=\"hidden md:flex\" /> : null}\n\n {notificationsSlot ??\n (notificationCount === undefined ? null : (\n <NotificationsButton\n count={notificationCount}\n label={labels.notifications}\n onClick={onNotificationsClick}\n />\n ))}\n\n {/*\n `hidden md:flex`: nút đổi giao diện chỉ ở desktop. Dưới `md` lối vào là submenu \"Giao\n diện\" trong `UserMenu` (mặc định `showThemeSubmenu=\"mobile\"`) — hai bên dùng CHUNG mốc\n `md` nên ở mọi bề rộng có đúng MỘT lối vào, không trùng và không hụt.\n ⚠ Đổi mốc ở một bên thì phải đổi ở bên kia, nếu không sẽ có dải bề rộng mất hẳn nút đổi\n giao diện.\n */}\n {showThemeToggle ? <ThemeToggle labels={labels} className=\"hidden md:flex\" /> : null}\n\n {showUserMenu && user ? (\n <>\n <span className=\"mx-1 hidden h-5 w-px bg-border sm:block\" aria-hidden=\"true\" />\n <UserMenu {...userMenuProps} />\n </>\n ) : null}\n </div>\n </header>\n );\n}\n"],"mappings":"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","names":[]}