@comitor/ui 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/README.md +303 -43
  2. package/dist/chart/chart.d.ts +35 -3
  3. package/dist/chart/chart.js.map +1 -1
  4. package/dist/chart/comitor-chart.d.ts +16 -3
  5. package/dist/chart/comitor-chart.js.map +1 -1
  6. package/dist/chart/index.d.ts +2 -2
  7. package/dist/components/composite/avatar-group.js +1 -1
  8. package/dist/components/composite/avatar-group.js.map +1 -1
  9. package/dist/components/composite/combobox.d.ts +9 -2
  10. package/dist/components/composite/combobox.js +9 -2
  11. package/dist/components/composite/combobox.js.map +1 -1
  12. package/dist/components/composite/confirm-dialog.js +1 -1
  13. package/dist/components/composite/confirm-dialog.js.map +1 -1
  14. package/dist/components/composite/copy-button.js +1 -1
  15. package/dist/components/composite/copy-button.js.map +1 -1
  16. package/dist/components/composite/data-table.d.ts +17 -8
  17. package/dist/components/composite/data-table.js +8 -8
  18. package/dist/components/composite/data-table.js.map +1 -1
  19. package/dist/components/composite/filter-chips.js +6 -5
  20. package/dist/components/composite/filter-chips.js.map +1 -1
  21. package/dist/components/composite/form-field.js +3 -1
  22. package/dist/components/composite/form-field.js.map +1 -1
  23. package/dist/components/composite/form-section.js +5 -5
  24. package/dist/components/composite/form-section.js.map +1 -1
  25. package/dist/components/composite/inline-edit.js +1 -1
  26. package/dist/components/composite/inline-edit.js.map +1 -1
  27. package/dist/components/composite/keyboard-hint.d.ts +16 -0
  28. package/dist/components/composite/keyboard-hint.js +8 -6
  29. package/dist/components/composite/keyboard-hint.js.map +1 -1
  30. package/dist/components/composite/letter-avatar.d.ts +10 -18
  31. package/dist/components/composite/letter-avatar.js +6 -17
  32. package/dist/components/composite/letter-avatar.js.map +1 -1
  33. package/dist/components/composite/search-filter-bar.d.ts +41 -3
  34. package/dist/components/composite/search-filter-bar.js +27 -15
  35. package/dist/components/composite/search-filter-bar.js.map +1 -1
  36. package/dist/components/composite/stat-card.d.ts +16 -1
  37. package/dist/components/composite/stat-card.js +16 -9
  38. package/dist/components/composite/stat-card.js.map +1 -1
  39. package/dist/components/composite/status-config.d.ts +69 -16
  40. package/dist/components/composite/status-config.js +10 -10
  41. package/dist/components/composite/status-config.js.map +1 -1
  42. package/dist/components/composite/status-pill.js +5 -1
  43. package/dist/components/composite/status-pill.js.map +1 -1
  44. package/dist/components/composite/table-pagination.d.ts +46 -4
  45. package/dist/components/composite/table-pagination.js +27 -24
  46. package/dist/components/composite/table-pagination.js.map +1 -1
  47. package/dist/components/composite/text-utils.d.ts +12 -11
  48. package/dist/components/composite/text-utils.js +7 -16
  49. package/dist/components/composite/text-utils.js.map +1 -1
  50. package/dist/components/composite/toast.js +16 -6
  51. package/dist/components/composite/toast.js.map +1 -1
  52. package/dist/components/ui/alert.js +1 -1
  53. package/dist/components/ui/alert.js.map +1 -1
  54. package/dist/components/ui/badge.js +4 -4
  55. package/dist/components/ui/badge.js.map +1 -1
  56. package/dist/components/ui/button.js +3 -3
  57. package/dist/components/ui/button.js.map +1 -1
  58. package/dist/components/ui/calendar.d.ts +41 -0
  59. package/dist/components/ui/calendar.js +1 -1
  60. package/dist/components/ui/calendar.js.map +1 -1
  61. package/dist/components/ui/card.d.ts +35 -2
  62. package/dist/components/ui/card.js +17 -3
  63. package/dist/components/ui/card.js.map +1 -1
  64. package/dist/components/ui/checkbox.js +3 -3
  65. package/dist/components/ui/checkbox.js.map +1 -1
  66. package/dist/components/ui/command.js +1 -1
  67. package/dist/components/ui/command.js.map +1 -1
  68. package/dist/components/ui/context-menu.js +1 -1
  69. package/dist/components/ui/context-menu.js.map +1 -1
  70. package/dist/components/ui/drawer.js +1 -1
  71. package/dist/components/ui/drawer.js.map +1 -1
  72. package/dist/components/ui/dropdown-menu.js +1 -1
  73. package/dist/components/ui/dropdown-menu.js.map +1 -1
  74. package/dist/components/ui/empty.js +1 -1
  75. package/dist/components/ui/empty.js.map +1 -1
  76. package/dist/components/ui/field.js +4 -4
  77. package/dist/components/ui/field.js.map +1 -1
  78. package/dist/components/ui/form.js +8 -2
  79. package/dist/components/ui/form.js.map +1 -1
  80. package/dist/components/ui/input-group.js +3 -3
  81. package/dist/components/ui/input-group.js.map +1 -1
  82. package/dist/components/ui/input-otp.js +1 -1
  83. package/dist/components/ui/input-otp.js.map +1 -1
  84. package/dist/components/ui/input.js +3 -3
  85. package/dist/components/ui/input.js.map +1 -1
  86. package/dist/components/ui/item.js +1 -1
  87. package/dist/components/ui/item.js.map +1 -1
  88. package/dist/components/ui/menubar.js +1 -1
  89. package/dist/components/ui/menubar.js.map +1 -1
  90. package/dist/components/ui/navigation-menu.js +1 -1
  91. package/dist/components/ui/navigation-menu.js.map +1 -1
  92. package/dist/components/ui/pagination.js +1 -1
  93. package/dist/components/ui/pagination.js.map +1 -1
  94. package/dist/components/ui/progress.d.ts +26 -0
  95. package/dist/components/ui/progress.js +5 -2
  96. package/dist/components/ui/progress.js.map +1 -1
  97. package/dist/components/ui/radio-group.js +2 -2
  98. package/dist/components/ui/radio-group.js.map +1 -1
  99. package/dist/components/ui/resizable.d.ts +14 -0
  100. package/dist/components/ui/resizable.js +2 -2
  101. package/dist/components/ui/resizable.js.map +1 -1
  102. package/dist/components/ui/scroll-area.js +1 -1
  103. package/dist/components/ui/scroll-area.js.map +1 -1
  104. package/dist/components/ui/select.d.ts +33 -0
  105. package/dist/components/ui/select.js +3 -3
  106. package/dist/components/ui/select.js.map +1 -1
  107. package/dist/components/ui/slider.d.ts +19 -0
  108. package/dist/components/ui/slider.js +3 -3
  109. package/dist/components/ui/slider.js.map +1 -1
  110. package/dist/components/ui/sonner.d.ts +21 -3
  111. package/dist/components/ui/sonner.js +1 -2
  112. package/dist/components/ui/sonner.js.map +1 -1
  113. package/dist/components/ui/switch.js +2 -2
  114. package/dist/components/ui/switch.js.map +1 -1
  115. package/dist/components/ui/table.d.ts +16 -0
  116. package/dist/components/ui/table.js +1 -1
  117. package/dist/components/ui/table.js.map +1 -1
  118. package/dist/components/ui/tabs.js +1 -1
  119. package/dist/components/ui/tabs.js.map +1 -1
  120. package/dist/components/ui/textarea.js +1 -1
  121. package/dist/components/ui/textarea.js.map +1 -1
  122. package/dist/components/ui/toggle.js +1 -1
  123. package/dist/components/ui/toggle.js.map +1 -1
  124. package/dist/index.d.ts +6 -5
  125. package/dist/index.js +17 -2
  126. package/dist/index.js.map +1 -1
  127. package/dist/lib/avatar-tone.d.ts +126 -0
  128. package/dist/lib/avatar-tone.js +68 -0
  129. package/dist/lib/avatar-tone.js.map +1 -0
  130. package/dist/shell/app-header.js +12 -2
  131. package/dist/shell/app-header.js.map +1 -1
  132. package/dist/shell/app-launcher.js +2 -2
  133. package/dist/shell/app-launcher.js.map +1 -1
  134. package/dist/shell/app-shell.d.ts +2 -0
  135. package/dist/shell/index.d.ts +3 -3
  136. package/dist/shell/shell-context.d.ts +29 -8
  137. package/dist/shell/shell-context.js +6 -20
  138. package/dist/shell/shell-context.js.map +1 -1
  139. package/dist/shell/sidebar-group.js +5 -3
  140. package/dist/shell/sidebar-group.js.map +1 -1
  141. package/dist/shell/sidebar-menu-item.js +15 -7
  142. package/dist/shell/sidebar-menu-item.js.map +1 -1
  143. package/dist/shell/types.d.ts +17 -1
  144. package/dist/shell/types.js.map +1 -1
  145. package/dist/shell/user-menu.js +3 -2
  146. package/dist/shell/user-menu.js.map +1 -1
  147. package/dist/shell/utils.d.ts +36 -24
  148. package/dist/shell/utils.js +55 -20
  149. package/dist/shell/utils.js.map +1 -1
  150. package/dist/shell/workspace-switcher.js +23 -8
  151. package/dist/shell/workspace-switcher.js.map +1 -1
  152. package/dist/tokens.d.ts +165 -17
  153. package/dist/tokens.js +122 -25
  154. package/dist/tokens.js.map +1 -1
  155. package/package.json +8 -5
  156. package/styles.css +312 -22
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/ui/navigation-menu.tsx"],"sourcesContent":["import * as NavigationMenuPrimitive from \"@radix-ui/react-navigation-menu\";\nimport { cva } from \"class-variance-authority\";\nimport { ChevronDownIcon } from \"lucide-react\";\nimport type * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\n/**\n * NavigationMenu — thanh điều hướng có menu bung (Radix NavigationMenu).\n *\n * Dùng cho site marketing / trang công khai. Trong app đã đăng nhập thì điều hướng\n * là Sidebar + AppLauncher, không dùng component này.\n *\n * KHÔNG có directive \"use client\": file chỉ khai báo bố cục, phần state nằm trong\n * gói Radix (bản thân gói đã đánh dấu client) → dùng được trực tiếp trong RSC.\n *\n * `viewport` (mặc định true): nội dung menu render vào một khung chung phía dưới\n * thanh, có hiệu ứng đổi kích thước. Đặt false khi muốn mỗi mục tự bung popover\n * riêng — style tương ứng bám theo `group-data-[viewport=false]`.\n */\n\nexport type NavigationMenuProps = React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {\n viewport?: boolean;\n};\n\nfunction NavigationMenu({ className, children, viewport = true, ...props }: NavigationMenuProps) {\n return (\n <NavigationMenuPrimitive.Root\n data-slot=\"navigation-menu\"\n data-viewport={viewport}\n className={cn(\"group/navigation-menu relative flex max-w-max flex-1 items-center justify-center\", className)}\n {...props}\n >\n {children}\n {viewport && <NavigationMenuViewport />}\n </NavigationMenuPrimitive.Root>\n );\n}\n\nexport type NavigationMenuListProps = React.ComponentProps<typeof NavigationMenuPrimitive.List>;\n\nfunction NavigationMenuList({ className, ...props }: NavigationMenuListProps) {\n return (\n <NavigationMenuPrimitive.List\n data-slot=\"navigation-menu-list\"\n className={cn(\"group flex flex-1 list-none items-center justify-center gap-1\", className)}\n {...props}\n />\n );\n}\n\nexport type NavigationMenuItemProps = React.ComponentProps<typeof NavigationMenuPrimitive.Item>;\n\nfunction NavigationMenuItem({ className, ...props }: NavigationMenuItemProps) {\n return (\n <NavigationMenuPrimitive.Item data-slot=\"navigation-menu-item\" className={cn(\"relative\", className)} {...props} />\n );\n}\n\n/** Style dùng lại cho mục điều hướng không có menu con (vd bọc thẻ <a>). */\nconst navigationMenuTriggerStyle = cva(\n \"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=open]:hover:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:focus:bg-accent data-[state=open]:bg-accent/50 outline-none transition-colors duration-150 ease-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background\"\n);\n\nexport type NavigationMenuTriggerProps = React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>;\n\nfunction NavigationMenuTrigger({ className, children, ...props }: NavigationMenuTriggerProps) {\n return (\n <NavigationMenuPrimitive.Trigger\n data-slot=\"navigation-menu-trigger\"\n className={cn(navigationMenuTriggerStyle(), \"group\", className)}\n {...props}\n >\n {children}{\" \"}\n <ChevronDownIcon\n className=\"relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180\"\n aria-hidden=\"true\"\n />\n </NavigationMenuPrimitive.Trigger>\n );\n}\n\nexport type NavigationMenuContentProps = React.ComponentProps<typeof NavigationMenuPrimitive.Content>;\n\nfunction NavigationMenuContent({ className, ...props }: NavigationMenuContentProps) {\n return (\n <NavigationMenuPrimitive.Content\n data-slot=\"navigation-menu-content\"\n className={cn(\n \"data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full p-2 pr-2.5 md:absolute md:w-auto\",\n \"group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow-overlay group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport type NavigationMenuViewportProps = React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>;\n\nfunction NavigationMenuViewport({ className, ...props }: NavigationMenuViewportProps) {\n return (\n <div className=\"absolute top-full left-0 isolate z-50 flex justify-center\">\n <NavigationMenuPrimitive.Viewport\n data-slot=\"navigation-menu-viewport\"\n className={cn(\n \"origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border shadow-overlay md:w-[var(--radix-navigation-menu-viewport-width)]\",\n className\n )}\n {...props}\n />\n </div>\n );\n}\n\nexport type NavigationMenuLinkProps = React.ComponentProps<typeof NavigationMenuPrimitive.Link>;\n\nfunction NavigationMenuLink({ className, ...props }: NavigationMenuLinkProps) {\n return (\n <NavigationMenuPrimitive.Link\n data-slot=\"navigation-menu-link\"\n className={cn(\n \"data-[active=true]:focus:bg-accent data-[active=true]:hover:bg-accent data-[active=true]:bg-accent/50 data-[active=true]:text-accent-foreground hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex flex-col gap-1 rounded-sm p-2 text-sm transition-colors duration-150 ease-out outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport type NavigationMenuIndicatorProps = React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>;\n\nfunction NavigationMenuIndicator({ className, ...props }: NavigationMenuIndicatorProps) {\n return (\n <NavigationMenuPrimitive.Indicator\n data-slot=\"navigation-menu-indicator\"\n className={cn(\n \"data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden\",\n className\n )}\n {...props}\n >\n <div className=\"bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md\" />\n </NavigationMenuPrimitive.Indicator>\n );\n}\n\nexport {\n NavigationMenu,\n NavigationMenuContent,\n NavigationMenuIndicator,\n NavigationMenuItem,\n NavigationMenuLink,\n NavigationMenuList,\n NavigationMenuTrigger,\n NavigationMenuViewport,\n navigationMenuTriggerStyle\n};\n"],"mappings":"AA0BI,SAOe,KAPf;AA1BJ,YAAY,6BAA6B;AACzC,SAAS,WAAW;AACpB,SAAS,uBAAuB;AAEhC,SAAS,UAAU;AAoBnB,SAAS,eAAe,EAAE,WAAW,UAAU,WAAW,MAAM,GAAG,MAAM,GAAwB;AAC/F,SACE;AAAA,IAAC,wBAAwB;AAAA,IAAxB;AAAA,MACC,aAAU;AAAA,MACV,iBAAe;AAAA,MACf,WAAW,GAAG,oFAAoF,SAAS;AAAA,MAC1G,GAAG;AAAA,MAEH;AAAA;AAAA,QACA,YAAY,oBAAC,0BAAuB;AAAA;AAAA;AAAA,EACvC;AAEJ;AAIA,SAAS,mBAAmB,EAAE,WAAW,GAAG,MAAM,GAA4B;AAC5E,SACE;AAAA,IAAC,wBAAwB;AAAA,IAAxB;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,iEAAiE,SAAS;AAAA,MACvF,GAAG;AAAA;AAAA,EACN;AAEJ;AAIA,SAAS,mBAAmB,EAAE,WAAW,GAAG,MAAM,GAA4B;AAC5E,SACE,oBAAC,wBAAwB,MAAxB,EAA6B,aAAU,wBAAuB,WAAW,GAAG,YAAY,SAAS,GAAI,GAAG,OAAO;AAEpH;AAGA,MAAM,6BAA6B;AAAA,EACjC;AACF;AAIA,SAAS,sBAAsB,EAAE,WAAW,UAAU,GAAG,MAAM,GAA+B;AAC5F,SACE;AAAA,IAAC,wBAAwB;AAAA,IAAxB;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,2BAA2B,GAAG,SAAS,SAAS;AAAA,MAC7D,GAAG;AAAA,MAEH;AAAA;AAAA,QAAU;AAAA,QACX;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,eAAY;AAAA;AAAA,QACd;AAAA;AAAA;AAAA,EACF;AAEJ;AAIA,SAAS,sBAAsB,EAAE,WAAW,GAAG,MAAM,GAA+B;AAClF,SACE;AAAA,IAAC,wBAAwB;AAAA,IAAxB;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAIA,SAAS,uBAAuB,EAAE,WAAW,GAAG,MAAM,GAAgC;AACpF,SACE,oBAAC,SAAI,WAAU,6DACb;AAAA,IAAC,wBAAwB;AAAA,IAAxB;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN,GACF;AAEJ;AAIA,SAAS,mBAAmB,EAAE,WAAW,GAAG,MAAM,GAA4B;AAC5E,SACE;AAAA,IAAC,wBAAwB;AAAA,IAAxB;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAIA,SAAS,wBAAwB,EAAE,WAAW,GAAG,MAAM,GAAiC;AACtF,SACE;AAAA,IAAC,wBAAwB;AAAA,IAAxB;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ,8BAAC,SAAI,WAAU,0EAAyE;AAAA;AAAA,EAC1F;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/ui/navigation-menu.tsx"],"sourcesContent":["import * as NavigationMenuPrimitive from \"@radix-ui/react-navigation-menu\";\nimport { cva } from \"class-variance-authority\";\nimport { ChevronDownIcon } from \"lucide-react\";\nimport type * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\n/**\n * NavigationMenu — thanh điều hướng có menu bung (Radix NavigationMenu).\n *\n * Dùng cho site marketing / trang công khai. Trong app đã đăng nhập thì điều hướng\n * là Sidebar + AppLauncher, không dùng component này.\n *\n * KHÔNG có directive \"use client\": file chỉ khai báo bố cục, phần state nằm trong\n * gói Radix (bản thân gói đã đánh dấu client) → dùng được trực tiếp trong RSC.\n *\n * `viewport` (mặc định true): nội dung menu render vào một khung chung phía dưới\n * thanh, có hiệu ứng đổi kích thước. Đặt false khi muốn mỗi mục tự bung popover\n * riêng — style tương ứng bám theo `group-data-[viewport=false]`.\n */\n\nexport type NavigationMenuProps = React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {\n viewport?: boolean;\n};\n\nfunction NavigationMenu({ className, children, viewport = true, ...props }: NavigationMenuProps) {\n return (\n <NavigationMenuPrimitive.Root\n data-slot=\"navigation-menu\"\n data-viewport={viewport}\n className={cn(\"group/navigation-menu relative flex max-w-max flex-1 items-center justify-center\", className)}\n {...props}\n >\n {children}\n {viewport && <NavigationMenuViewport />}\n </NavigationMenuPrimitive.Root>\n );\n}\n\nexport type NavigationMenuListProps = React.ComponentProps<typeof NavigationMenuPrimitive.List>;\n\nfunction NavigationMenuList({ className, ...props }: NavigationMenuListProps) {\n return (\n <NavigationMenuPrimitive.List\n data-slot=\"navigation-menu-list\"\n className={cn(\"group flex flex-1 list-none items-center justify-center gap-1\", className)}\n {...props}\n />\n );\n}\n\nexport type NavigationMenuItemProps = React.ComponentProps<typeof NavigationMenuPrimitive.Item>;\n\nfunction NavigationMenuItem({ className, ...props }: NavigationMenuItemProps) {\n return (\n <NavigationMenuPrimitive.Item data-slot=\"navigation-menu-item\" className={cn(\"relative\", className)} {...props} />\n );\n}\n\n/** Style dùng lại cho mục điều hướng không có menu con (vd bọc thẻ <a>). */\nconst navigationMenuTriggerStyle = cva(\n \"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=open]:hover:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:focus:bg-accent data-[state=open]:bg-accent/50 outline-none transition-colors duration-150 ease-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background\"\n);\n\nexport type NavigationMenuTriggerProps = React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>;\n\nfunction NavigationMenuTrigger({ className, children, ...props }: NavigationMenuTriggerProps) {\n return (\n <NavigationMenuPrimitive.Trigger\n data-slot=\"navigation-menu-trigger\"\n className={cn(navigationMenuTriggerStyle(), \"group\", className)}\n {...props}\n >\n {children}{\" \"}\n <ChevronDownIcon\n className=\"relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180\"\n aria-hidden=\"true\"\n />\n </NavigationMenuPrimitive.Trigger>\n );\n}\n\nexport type NavigationMenuContentProps = React.ComponentProps<typeof NavigationMenuPrimitive.Content>;\n\nfunction NavigationMenuContent({ className, ...props }: NavigationMenuContentProps) {\n return (\n <NavigationMenuPrimitive.Content\n data-slot=\"navigation-menu-content\"\n className={cn(\n \"data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full p-2 pr-2.5 md:absolute md:w-auto\",\n \"group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow-overlay group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport type NavigationMenuViewportProps = React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>;\n\nfunction NavigationMenuViewport({ className, ...props }: NavigationMenuViewportProps) {\n return (\n <div className=\"absolute top-full left-0 isolate z-50 flex justify-center\">\n <NavigationMenuPrimitive.Viewport\n data-slot=\"navigation-menu-viewport\"\n className={cn(\n \"origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border shadow-overlay md:w-[var(--radix-navigation-menu-viewport-width)]\",\n className\n )}\n {...props}\n />\n </div>\n );\n}\n\nexport type NavigationMenuLinkProps = React.ComponentProps<typeof NavigationMenuPrimitive.Link>;\n\nfunction NavigationMenuLink({ className, ...props }: NavigationMenuLinkProps) {\n return (\n <NavigationMenuPrimitive.Link\n data-slot=\"navigation-menu-link\"\n className={cn(\n \"data-[active=true]:focus:bg-accent data-[active=true]:hover:bg-accent data-[active=true]:bg-accent/50 data-[active=true]:text-accent-foreground hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex flex-col gap-1 rounded-sm p-2 text-sm transition-colors duration-150 ease-out outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport type NavigationMenuIndicatorProps = React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>;\n\nfunction NavigationMenuIndicator({ className, ...props }: NavigationMenuIndicatorProps) {\n return (\n <NavigationMenuPrimitive.Indicator\n data-slot=\"navigation-menu-indicator\"\n className={cn(\n \"data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden\",\n className\n )}\n {...props}\n >\n {/*\n Hình vuông xoay 45° chỉ vào trigger ĐANG MỞ — theo Radix đây là dấu \"highlight the\n currently active trigger\", tức thông tin trạng thái, ngưỡng 3:1 (WCAG 1.4.11).\n `bg-border` là bậc TRANG TRÍ (1,16:1 light · 1,17:1 dark với --background) nên dấu này\n vốn không hiện ở cả hai theme. Dùng `--primary-ink` — cùng token mà `Tabs` dùng cho vạch\n active (`data-[state=active]:border-primary-ink`): 6,51:1 (light) · 12,67:1 (dark) trên\n --background, 6,09:1 · 10,80:1 trên --muted ✓\n */}\n <div className=\"bg-primary-ink relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md\" />\n </NavigationMenuPrimitive.Indicator>\n );\n}\n\nexport {\n NavigationMenu,\n NavigationMenuContent,\n NavigationMenuIndicator,\n NavigationMenuItem,\n NavigationMenuLink,\n NavigationMenuList,\n NavigationMenuTrigger,\n NavigationMenuViewport,\n navigationMenuTriggerStyle\n};\n"],"mappings":"AA0BI,SAOe,KAPf;AA1BJ,YAAY,6BAA6B;AACzC,SAAS,WAAW;AACpB,SAAS,uBAAuB;AAEhC,SAAS,UAAU;AAoBnB,SAAS,eAAe,EAAE,WAAW,UAAU,WAAW,MAAM,GAAG,MAAM,GAAwB;AAC/F,SACE;AAAA,IAAC,wBAAwB;AAAA,IAAxB;AAAA,MACC,aAAU;AAAA,MACV,iBAAe;AAAA,MACf,WAAW,GAAG,oFAAoF,SAAS;AAAA,MAC1G,GAAG;AAAA,MAEH;AAAA;AAAA,QACA,YAAY,oBAAC,0BAAuB;AAAA;AAAA;AAAA,EACvC;AAEJ;AAIA,SAAS,mBAAmB,EAAE,WAAW,GAAG,MAAM,GAA4B;AAC5E,SACE;AAAA,IAAC,wBAAwB;AAAA,IAAxB;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,iEAAiE,SAAS;AAAA,MACvF,GAAG;AAAA;AAAA,EACN;AAEJ;AAIA,SAAS,mBAAmB,EAAE,WAAW,GAAG,MAAM,GAA4B;AAC5E,SACE,oBAAC,wBAAwB,MAAxB,EAA6B,aAAU,wBAAuB,WAAW,GAAG,YAAY,SAAS,GAAI,GAAG,OAAO;AAEpH;AAGA,MAAM,6BAA6B;AAAA,EACjC;AACF;AAIA,SAAS,sBAAsB,EAAE,WAAW,UAAU,GAAG,MAAM,GAA+B;AAC5F,SACE;AAAA,IAAC,wBAAwB;AAAA,IAAxB;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,2BAA2B,GAAG,SAAS,SAAS;AAAA,MAC7D,GAAG;AAAA,MAEH;AAAA;AAAA,QAAU;AAAA,QACX;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,eAAY;AAAA;AAAA,QACd;AAAA;AAAA;AAAA,EACF;AAEJ;AAIA,SAAS,sBAAsB,EAAE,WAAW,GAAG,MAAM,GAA+B;AAClF,SACE;AAAA,IAAC,wBAAwB;AAAA,IAAxB;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAIA,SAAS,uBAAuB,EAAE,WAAW,GAAG,MAAM,GAAgC;AACpF,SACE,oBAAC,SAAI,WAAU,6DACb;AAAA,IAAC,wBAAwB;AAAA,IAAxB;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN,GACF;AAEJ;AAIA,SAAS,mBAAmB,EAAE,WAAW,GAAG,MAAM,GAA4B;AAC5E,SACE;AAAA,IAAC,wBAAwB;AAAA,IAAxB;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAIA,SAAS,wBAAwB,EAAE,WAAW,GAAG,MAAM,GAAiC;AACtF,SACE;AAAA,IAAC,wBAAwB;AAAA,IAAxB;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAUJ,8BAAC,SAAI,WAAU,+EAA8E;AAAA;AAAA,EAC/F;AAEJ;","names":[]}
@@ -31,7 +31,7 @@ function PaginationLink({ className, isActive, size = "icon", ...props }) {
31
31
  "data-active": isActive,
32
32
  className: cn(
33
33
  buttonVariants({ variant: "ghost", size }),
34
- isActive && "border-gold-300 bg-gold-50 font-semibold text-foreground hover:bg-gold-50 dark:bg-gold-500/10 dark:hover:bg-gold-500/10",
34
+ isActive && "border-primary-ink bg-gold-50 font-semibold text-foreground hover:bg-gold-50 dark:bg-gold-500/10 dark:hover:bg-gold-500/10",
35
35
  className
36
36
  ),
37
37
  ...props
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/ui/pagination.tsx"],"sourcesContent":["import { ChevronLeftIcon, ChevronRightIcon, MoreHorizontalIcon } from \"lucide-react\";\nimport type * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\nimport { type ButtonProps, buttonVariants } from \"./button.js\";\n\n/**\n * Pagination — primitive tĩnh (không \"use client\", dùng được trong RSC).\n * Đây là bộ khung thẻ `<a>`; bản \"biết đếm trang\" là composite `TablePagination`.\n *\n * Chuỗi hiển thị mặc định tiếng Việt, override qua prop `label` / `srLabel`.\n * Muốn dùng `next/link`: `<PaginationLink asChild>` chưa có ở primitive này —\n * truyền `href` trực tiếp hoặc bọc bằng composite ở tầng app.\n */\n\nexport type PaginationProps = React.ComponentProps<\"nav\">;\nexport type PaginationContentProps = React.ComponentProps<\"ul\">;\nexport type PaginationItemProps = React.ComponentProps<\"li\">;\nexport type PaginationLinkProps = {\n isActive?: boolean;\n} & Pick<ButtonProps, \"size\"> &\n React.ComponentProps<\"a\">;\nexport type PaginationPreviousProps = PaginationLinkProps & {\n /** Nhãn hiển thị (ẩn ở màn hình nhỏ). Mặc định \"Trước\". */\n label?: React.ReactNode;\n};\nexport type PaginationNextProps = PaginationLinkProps & {\n /** Nhãn hiển thị (ẩn ở màn hình nhỏ). Mặc định \"Sau\". */\n label?: React.ReactNode;\n};\nexport type PaginationEllipsisProps = React.ComponentProps<\"span\"> & {\n /** Nhãn chỉ dành cho trình đọc màn hình. */\n srLabel?: string;\n};\n\nfunction Pagination({ className, ...props }: PaginationProps) {\n return (\n // Thẻ <nav> đã ngầm mang role=\"navigation\" — bản comitor-ds khai lại là thừa, bỏ đi.\n <nav\n aria-label=\"Phân trang\"\n data-slot=\"pagination\"\n className={cn(\"mx-auto flex w-full justify-center\", className)}\n {...props}\n />\n );\n}\n\nfunction PaginationContent({ className, ...props }: PaginationContentProps) {\n return <ul data-slot=\"pagination-content\" className={cn(\"flex flex-row items-center gap-1\", className)} {...props} />;\n}\n\nfunction PaginationItem({ ...props }: PaginationItemProps) {\n return <li data-slot=\"pagination-item\" {...props} />;\n}\n\nfunction PaginationLink({ className, isActive, size = \"icon\", ...props }: PaginationLinkProps) {\n return (\n <a\n aria-current={isActive ? \"page\" : undefined}\n data-slot=\"pagination-link\"\n data-active={isActive}\n className={cn(\n buttonVariants({ variant: \"ghost\", size }),\n isActive &&\n \"border-gold-300 bg-gold-50 font-semibold text-foreground hover:bg-gold-50 dark:bg-gold-500/10 dark:hover:bg-gold-500/10\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction PaginationPrevious({ className, label = \"Trước\", ...props }: PaginationPreviousProps) {\n return (\n <PaginationLink\n aria-label=\"Về trang trước\"\n size=\"default\"\n className={cn(\"gap-1 px-2.5 sm:pl-2.5\", className)}\n {...props}\n >\n <ChevronLeftIcon />\n <span className=\"hidden sm:block\">{label}</span>\n </PaginationLink>\n );\n}\n\nfunction PaginationNext({ className, label = \"Sau\", ...props }: PaginationNextProps) {\n return (\n <PaginationLink\n aria-label=\"Tới trang sau\"\n size=\"default\"\n className={cn(\"gap-1 px-2.5 sm:pr-2.5\", className)}\n {...props}\n >\n <span className=\"hidden sm:block\">{label}</span>\n <ChevronRightIcon />\n </PaginationLink>\n );\n}\n\nfunction PaginationEllipsis({ className, srLabel = \"Thêm trang\", ...props }: PaginationEllipsisProps) {\n return (\n <span\n aria-hidden\n data-slot=\"pagination-ellipsis\"\n className={cn(\"flex size-9 items-center justify-center\", className)}\n {...props}\n >\n <MoreHorizontalIcon className=\"size-4\" />\n <span className=\"sr-only\">{srLabel}</span>\n </span>\n );\n}\n\nexport {\n Pagination,\n PaginationContent,\n PaginationEllipsis,\n PaginationItem,\n PaginationLink,\n PaginationNext,\n PaginationPrevious\n};\n"],"mappings":"AAqCI,cAoCA,YApCA;AArCJ,SAAS,iBAAiB,kBAAkB,0BAA0B;AAEtE,SAAS,UAAU;AACnB,SAA2B,sBAAsB;AA+BjD,SAAS,WAAW,EAAE,WAAW,GAAG,MAAM,GAAoB;AAC5D;AAAA;AAAA,IAEE;AAAA,MAAC;AAAA;AAAA,QACC,cAAW;AAAA,QACX,aAAU;AAAA,QACV,WAAW,GAAG,sCAAsC,SAAS;AAAA,QAC5D,GAAG;AAAA;AAAA,IACN;AAAA;AAEJ;AAEA,SAAS,kBAAkB,EAAE,WAAW,GAAG,MAAM,GAA2B;AAC1E,SAAO,oBAAC,QAAG,aAAU,sBAAqB,WAAW,GAAG,oCAAoC,SAAS,GAAI,GAAG,OAAO;AACrH;AAEA,SAAS,eAAe,EAAE,GAAG,MAAM,GAAwB;AACzD,SAAO,oBAAC,QAAG,aAAU,mBAAmB,GAAG,OAAO;AACpD;AAEA,SAAS,eAAe,EAAE,WAAW,UAAU,OAAO,QAAQ,GAAG,MAAM,GAAwB;AAC7F,SACE;AAAA,IAAC;AAAA;AAAA,MACC,gBAAc,WAAW,SAAS;AAAA,MAClC,aAAU;AAAA,MACV,eAAa;AAAA,MACb,WAAW;AAAA,QACT,eAAe,EAAE,SAAS,SAAS,KAAK,CAAC;AAAA,QACzC,YACE;AAAA,QACF;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,SAAS,mBAAmB,EAAE,WAAW,QAAQ,mBAAS,GAAG,MAAM,GAA4B;AAC7F,SACE;AAAA,IAAC;AAAA;AAAA,MACC,cAAW;AAAA,MACX,MAAK;AAAA,MACL,WAAW,GAAG,0BAA0B,SAAS;AAAA,MAChD,GAAG;AAAA,MAEJ;AAAA,4BAAC,mBAAgB;AAAA,QACjB,oBAAC,UAAK,WAAU,mBAAmB,iBAAM;AAAA;AAAA;AAAA,EAC3C;AAEJ;AAEA,SAAS,eAAe,EAAE,WAAW,QAAQ,OAAO,GAAG,MAAM,GAAwB;AACnF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,cAAW;AAAA,MACX,MAAK;AAAA,MACL,WAAW,GAAG,0BAA0B,SAAS;AAAA,MAChD,GAAG;AAAA,MAEJ;AAAA,4BAAC,UAAK,WAAU,mBAAmB,iBAAM;AAAA,QACzC,oBAAC,oBAAiB;AAAA;AAAA;AAAA,EACpB;AAEJ;AAEA,SAAS,mBAAmB,EAAE,WAAW,UAAU,iBAAc,GAAG,MAAM,GAA4B;AACpG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,eAAW;AAAA,MACX,aAAU;AAAA,MACV,WAAW,GAAG,2CAA2C,SAAS;AAAA,MACjE,GAAG;AAAA,MAEJ;AAAA,4BAAC,sBAAmB,WAAU,UAAS;AAAA,QACvC,oBAAC,UAAK,WAAU,WAAW,mBAAQ;AAAA;AAAA;AAAA,EACrC;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/ui/pagination.tsx"],"sourcesContent":["import { ChevronLeftIcon, ChevronRightIcon, MoreHorizontalIcon } from \"lucide-react\";\nimport type * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\nimport { type ButtonProps, buttonVariants } from \"./button.js\";\n\n/**\n * Pagination — primitive tĩnh (không \"use client\", dùng được trong RSC).\n * Đây là bộ khung thẻ `<a>`; bản \"biết đếm trang\" là composite `TablePagination`.\n *\n * Chuỗi hiển thị mặc định tiếng Việt, override qua prop `label` / `srLabel`.\n * Muốn dùng `next/link`: `<PaginationLink asChild>` chưa có ở primitive này —\n * truyền `href` trực tiếp hoặc bọc bằng composite ở tầng app.\n */\n\nexport type PaginationProps = React.ComponentProps<\"nav\">;\nexport type PaginationContentProps = React.ComponentProps<\"ul\">;\nexport type PaginationItemProps = React.ComponentProps<\"li\">;\nexport type PaginationLinkProps = {\n isActive?: boolean;\n} & Pick<ButtonProps, \"size\"> &\n React.ComponentProps<\"a\">;\nexport type PaginationPreviousProps = PaginationLinkProps & {\n /** Nhãn hiển thị (ẩn ở màn hình nhỏ). Mặc định \"Trước\". */\n label?: React.ReactNode;\n};\nexport type PaginationNextProps = PaginationLinkProps & {\n /** Nhãn hiển thị (ẩn ở màn hình nhỏ). Mặc định \"Sau\". */\n label?: React.ReactNode;\n};\nexport type PaginationEllipsisProps = React.ComponentProps<\"span\"> & {\n /** Nhãn chỉ dành cho trình đọc màn hình. */\n srLabel?: string;\n};\n\nfunction Pagination({ className, ...props }: PaginationProps) {\n return (\n // Thẻ <nav> đã ngầm mang role=\"navigation\" — bản comitor-ds khai lại là thừa, bỏ đi.\n <nav\n aria-label=\"Phân trang\"\n data-slot=\"pagination\"\n className={cn(\"mx-auto flex w-full justify-center\", className)}\n {...props}\n />\n );\n}\n\nfunction PaginationContent({ className, ...props }: PaginationContentProps) {\n return <ul data-slot=\"pagination-content\" className={cn(\"flex flex-row items-center gap-1\", className)} {...props} />;\n}\n\nfunction PaginationItem({ ...props }: PaginationItemProps) {\n return <li data-slot=\"pagination-item\" {...props} />;\n}\n\n/*\n * Trang ĐANG XEM: viền là `border-primary-ink`, không phải `border-gold-300`. Nền `bg-gold-50`\n * chỉ 1,09:1 so với nền trang, nên VIỀN mới là thứ mang tín hiệu \"đang ở trang này\" — ngưỡng\n * 3:1. gold-300 #E8B824 trên nền trắng chỉ **1,86:1** ✗ (dark 10,19:1 nên chỉ light hỏng).\n * `--primary-ink`: 6,51:1 với nền trang · 5,96:1 với chính nền `gold-50` (light); 12,67:1 ·\n * 11,91:1 (dark) ✓. `aria-current=\"page\"` vẫn giữ nguyên cho phần trợ năng phi thị giác.\n */\nfunction PaginationLink({ className, isActive, size = \"icon\", ...props }: PaginationLinkProps) {\n return (\n <a\n aria-current={isActive ? \"page\" : undefined}\n data-slot=\"pagination-link\"\n data-active={isActive}\n className={cn(\n buttonVariants({ variant: \"ghost\", size }),\n isActive &&\n \"border-primary-ink bg-gold-50 font-semibold text-foreground hover:bg-gold-50 dark:bg-gold-500/10 dark:hover:bg-gold-500/10\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction PaginationPrevious({ className, label = \"Trước\", ...props }: PaginationPreviousProps) {\n return (\n <PaginationLink\n aria-label=\"Về trang trước\"\n size=\"default\"\n className={cn(\"gap-1 px-2.5 sm:pl-2.5\", className)}\n {...props}\n >\n <ChevronLeftIcon />\n <span className=\"hidden sm:block\">{label}</span>\n </PaginationLink>\n );\n}\n\nfunction PaginationNext({ className, label = \"Sau\", ...props }: PaginationNextProps) {\n return (\n <PaginationLink\n aria-label=\"Tới trang sau\"\n size=\"default\"\n className={cn(\"gap-1 px-2.5 sm:pr-2.5\", className)}\n {...props}\n >\n <span className=\"hidden sm:block\">{label}</span>\n <ChevronRightIcon />\n </PaginationLink>\n );\n}\n\nfunction PaginationEllipsis({ className, srLabel = \"Thêm trang\", ...props }: PaginationEllipsisProps) {\n return (\n <span\n aria-hidden\n data-slot=\"pagination-ellipsis\"\n className={cn(\"flex size-9 items-center justify-center\", className)}\n {...props}\n >\n <MoreHorizontalIcon className=\"size-4\" />\n <span className=\"sr-only\">{srLabel}</span>\n </span>\n );\n}\n\nexport {\n Pagination,\n PaginationContent,\n PaginationEllipsis,\n PaginationItem,\n PaginationLink,\n PaginationNext,\n PaginationPrevious\n};\n"],"mappings":"AAqCI,cA2CA,YA3CA;AArCJ,SAAS,iBAAiB,kBAAkB,0BAA0B;AAEtE,SAAS,UAAU;AACnB,SAA2B,sBAAsB;AA+BjD,SAAS,WAAW,EAAE,WAAW,GAAG,MAAM,GAAoB;AAC5D;AAAA;AAAA,IAEE;AAAA,MAAC;AAAA;AAAA,QACC,cAAW;AAAA,QACX,aAAU;AAAA,QACV,WAAW,GAAG,sCAAsC,SAAS;AAAA,QAC5D,GAAG;AAAA;AAAA,IACN;AAAA;AAEJ;AAEA,SAAS,kBAAkB,EAAE,WAAW,GAAG,MAAM,GAA2B;AAC1E,SAAO,oBAAC,QAAG,aAAU,sBAAqB,WAAW,GAAG,oCAAoC,SAAS,GAAI,GAAG,OAAO;AACrH;AAEA,SAAS,eAAe,EAAE,GAAG,MAAM,GAAwB;AACzD,SAAO,oBAAC,QAAG,aAAU,mBAAmB,GAAG,OAAO;AACpD;AASA,SAAS,eAAe,EAAE,WAAW,UAAU,OAAO,QAAQ,GAAG,MAAM,GAAwB;AAC7F,SACE;AAAA,IAAC;AAAA;AAAA,MACC,gBAAc,WAAW,SAAS;AAAA,MAClC,aAAU;AAAA,MACV,eAAa;AAAA,MACb,WAAW;AAAA,QACT,eAAe,EAAE,SAAS,SAAS,KAAK,CAAC;AAAA,QACzC,YACE;AAAA,QACF;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,SAAS,mBAAmB,EAAE,WAAW,QAAQ,mBAAS,GAAG,MAAM,GAA4B;AAC7F,SACE;AAAA,IAAC;AAAA;AAAA,MACC,cAAW;AAAA,MACX,MAAK;AAAA,MACL,WAAW,GAAG,0BAA0B,SAAS;AAAA,MAChD,GAAG;AAAA,MAEJ;AAAA,4BAAC,mBAAgB;AAAA,QACjB,oBAAC,UAAK,WAAU,mBAAmB,iBAAM;AAAA;AAAA;AAAA,EAC3C;AAEJ;AAEA,SAAS,eAAe,EAAE,WAAW,QAAQ,OAAO,GAAG,MAAM,GAAwB;AACnF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,cAAW;AAAA,MACX,MAAK;AAAA,MACL,WAAW,GAAG,0BAA0B,SAAS;AAAA,MAChD,GAAG;AAAA,MAEJ;AAAA,4BAAC,UAAK,WAAU,mBAAmB,iBAAM;AAAA,QACzC,oBAAC,oBAAiB;AAAA;AAAA;AAAA,EACpB;AAEJ;AAEA,SAAS,mBAAmB,EAAE,WAAW,UAAU,iBAAc,GAAG,MAAM,GAA4B;AACpG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,eAAW;AAAA,MACX,aAAU;AAAA,MACV,WAAW,GAAG,2CAA2C,SAAS;AAAA,MACjE,GAAG;AAAA,MAEJ;AAAA,4BAAC,sBAAmB,WAAU,UAAS;AAAA,QACvC,oBAAC,UAAK,WAAU,WAAW,mBAAQ;AAAA;AAAA;AAAA,EACrC;AAEJ;","names":[]}
@@ -5,6 +5,32 @@ import * as React from 'react';
5
5
  * Progress (Radix) — thanh tiến trình xác định (`value` 0…100).
6
6
  * Bỏ trống `value` để về trạng thái indeterminate của Radix (thanh đứng yên ở 0);
7
7
  * animation cho indeterminate là việc của app.
8
+ *
9
+ * ─ MÀU: `--primary-ink`, KHÔNG PHẢI `--primary` ─
10
+ * Phần đã chạy là thông tin thị giác (WCAG 1.4.11, 3:1) và phải nổi trên CẢ nền trang lẫn phần
11
+ * rãnh còn lại. `bg-primary` gold #E8B824 ở light chỉ **1,86:1** với nền trắng và **1,64:1** với
12
+ * rãnh `bg-primary/20` — thanh 8px vàng nhạt trên nền trắng gần như không thấy. Bậc ink của
13
+ * brand đạt cả hai phía ở cả hai theme:
14
+ *
15
+ * đã chạy ⇄ nền trang : 6,51:1 (light) · 12,67:1 (dark) ✓
16
+ * đã chạy ⇄ rãnh `/20` : 4,85:1 (light) · 7,97:1 (dark) ✓
17
+ * (tệ nhất khi rãnh nằm trên --muted: 4,54:1 · 6,52:1 ✓)
18
+ *
19
+ * ─ RÃNH: TINT `/20` GIỮ NGUYÊN, NHƯNG PHẢI CÓ VIỀN `--control-edge` ─
20
+ * Bản trước chỉ có tint: rãnh ⇄ nền trang mới **1,34:1** (light) và **1,59–1,66:1** (dark), tức
21
+ * KHÔNG nhìn thấy mốc 100% — ở `value` nhỏ không đọc được TỈ LỆ, ở `value=0` cả control biến mất.
22
+ * Cách sửa hiển nhiên (đổi rãnh sang `bg-control-edge` như Slider/Switch) lại hỏng chỗ khác:
23
+ * `--primary-ink` ⇄ `--control-edge` chỉ **1,70:1** (light) / **2,57:1** (dark), mà Progress
24
+ * KHÔNG có núm nên ranh giới đã-chạy/chưa-chạy là thứ DUY NHẤT mang giá trị — mất nó là mất
25
+ * thông tin. Với nền trang trắng không tồn tại cặp màu nào vừa ≥3:1 với nhau vừa ≥3:1 với nền
26
+ * (chứng minh trong chú thích của `Slider`), nên hai ràng buộc phải tách ra hai lớp:
27
+ *
28
+ * khoanh vùng control → `border-control-edge` ⇄ nền trang : 3,83:1 (light) · 4,94:1 (dark) ✓
29
+ * ⇄ --muted : 3,58:1 · 4,21:1 ✓
30
+ * đọc tỉ lệ → đã chạy ⇄ rãnh `/20` : 4,85:1 · 7,97:1 ✓
31
+ *
32
+ * Đây đúng vai trò hợp đồng đặt cho `--control-edge` (styles.css §RANH GIỚI): "khi nền control
33
+ * trong suốt/nhạt thì VIỀN là thứ duy nhất khoanh vùng".
8
34
  */
9
35
  type ProgressProps = React.ComponentProps<typeof ProgressPrimitive.Root>;
10
36
  declare function Progress({ className, value, ...props }: ProgressProps): React.JSX.Element;
@@ -7,14 +7,17 @@ function Progress({ className, value, ...props }) {
7
7
  ProgressPrimitive.Root,
8
8
  {
9
9
  "data-slot": "progress",
10
- className: cn("bg-primary/20 relative h-2 w-full overflow-hidden rounded-full", className),
10
+ className: cn(
11
+ "bg-primary-ink/20 border border-control-edge relative h-2 w-full overflow-hidden rounded-full",
12
+ className
13
+ ),
11
14
  value,
12
15
  ...props,
13
16
  children: /* @__PURE__ */ jsx(
14
17
  ProgressPrimitive.Indicator,
15
18
  {
16
19
  "data-slot": "progress-indicator",
17
- className: "bg-primary h-full w-full flex-1 transition-transform duration-150 ease-out",
20
+ className: "bg-primary-ink h-full w-full flex-1 transition-transform duration-150 ease-out",
18
21
  style: { transform: `translateX(-${100 - (value || 0)}%)` }
19
22
  }
20
23
  )
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/ui/progress.tsx"],"sourcesContent":["\"use client\";\n\nimport * as ProgressPrimitive from \"@radix-ui/react-progress\";\nimport type * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\n/**\n * Progress (Radix) — thanh tiến trình xác định (`value` 0…100).\n * Bỏ trống `value` để về trạng thái indeterminate của Radix (thanh đứng yên ở 0);\n * animation cho indeterminate là việc của app.\n */\n\nexport type ProgressProps = React.ComponentProps<typeof ProgressPrimitive.Root>;\n\nfunction Progress({ className, value, ...props }: ProgressProps) {\n return (\n <ProgressPrimitive.Root\n data-slot=\"progress\"\n className={cn(\"bg-primary/20 relative h-2 w-full overflow-hidden rounded-full\", className)}\n value={value}\n {...props}\n >\n <ProgressPrimitive.Indicator\n data-slot=\"progress-indicator\"\n className=\"bg-primary h-full w-full flex-1 transition-transform duration-150 ease-out\"\n style={{ transform: `translateX(-${100 - (value || 0)}%)` }}\n />\n </ProgressPrimitive.Root>\n );\n}\n\nexport { Progress };\n"],"mappings":";AAsBM;AApBN,YAAY,uBAAuB;AAEnC,SAAS,UAAU;AAUnB,SAAS,SAAS,EAAE,WAAW,OAAO,GAAG,MAAM,GAAkB;AAC/D,SACE;AAAA,IAAC,kBAAkB;AAAA,IAAlB;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,kEAAkE,SAAS;AAAA,MACzF;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA,QAAC,kBAAkB;AAAA,QAAlB;AAAA,UACC,aAAU;AAAA,UACV,WAAU;AAAA,UACV,OAAO,EAAE,WAAW,eAAe,OAAO,SAAS,EAAE,KAAK;AAAA;AAAA,MAC5D;AAAA;AAAA,EACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/ui/progress.tsx"],"sourcesContent":["\"use client\";\n\nimport * as ProgressPrimitive from \"@radix-ui/react-progress\";\nimport type * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\n/**\n * Progress (Radix) — thanh tiến trình xác định (`value` 0…100).\n * Bỏ trống `value` để về trạng thái indeterminate của Radix (thanh đứng yên ở 0);\n * animation cho indeterminate là việc của app.\n *\n * ─ MÀU: `--primary-ink`, KHÔNG PHẢI `--primary` ─\n * Phần đã chạy là thông tin thị giác (WCAG 1.4.11, 3:1) và phải nổi trên CẢ nền trang lẫn phần\n * rãnh còn lại. `bg-primary` gold #E8B824 ở light chỉ **1,86:1** với nền trắng và **1,64:1** với\n * rãnh `bg-primary/20` — thanh 8px vàng nhạt trên nền trắng gần như không thấy. Bậc ink của\n * brand đạt cả hai phía ở cả hai theme:\n *\n * đã chạy ⇄ nền trang : 6,51:1 (light) · 12,67:1 (dark) ✓\n * đã chạy ⇄ rãnh `/20` : 4,85:1 (light) · 7,97:1 (dark) ✓\n * (tệ nhất khi rãnh nằm trên --muted: 4,54:1 · 6,52:1 ✓)\n *\n * ─ RÃNH: TINT `/20` GIỮ NGUYÊN, NHƯNG PHẢI CÓ VIỀN `--control-edge` ─\n * Bản trước chỉ có tint: rãnh ⇄ nền trang mới **1,34:1** (light) và **1,59–1,66:1** (dark), tức\n * KHÔNG nhìn thấy mốc 100% — ở `value` nhỏ không đọc được TỈ LỆ, ở `value=0` cả control biến mất.\n * Cách sửa hiển nhiên (đổi rãnh sang `bg-control-edge` như Slider/Switch) lại hỏng chỗ khác:\n * `--primary-ink` ⇄ `--control-edge` chỉ **1,70:1** (light) / **2,57:1** (dark), mà Progress\n * KHÔNG có núm nên ranh giới đã-chạy/chưa-chạy là thứ DUY NHẤT mang giá trị — mất nó là mất\n * thông tin. Với nền trang trắng không tồn tại cặp màu nào vừa ≥3:1 với nhau vừa ≥3:1 với nền\n * (chứng minh trong chú thích của `Slider`), nên hai ràng buộc phải tách ra hai lớp:\n *\n * khoanh vùng control → `border-control-edge` ⇄ nền trang : 3,83:1 (light) · 4,94:1 (dark) ✓\n * ⇄ --muted : 3,58:1 · 4,21:1 ✓\n * đọc tỉ lệ → đã chạy ⇄ rãnh `/20` : 4,85:1 · 7,97:1 ✓\n *\n * Đây đúng vai trò hợp đồng đặt cho `--control-edge` (styles.css §RANH GIỚI): \"khi nền control\n * trong suốt/nhạt thì VIỀN là thứ duy nhất khoanh vùng\".\n */\n\nexport type ProgressProps = React.ComponentProps<typeof ProgressPrimitive.Root>;\n\nfunction Progress({ className, value, ...props }: ProgressProps) {\n return (\n <ProgressPrimitive.Root\n data-slot=\"progress\"\n className={cn(\n \"bg-primary-ink/20 border border-control-edge relative h-2 w-full overflow-hidden rounded-full\",\n className\n )}\n value={value}\n {...props}\n >\n <ProgressPrimitive.Indicator\n data-slot=\"progress-indicator\"\n className=\"bg-primary-ink h-full w-full flex-1 transition-transform duration-150 ease-out\"\n style={{ transform: `translateX(-${100 - (value || 0)}%)` }}\n />\n </ProgressPrimitive.Root>\n );\n}\n\nexport { Progress };\n"],"mappings":";AAmDM;AAjDN,YAAY,uBAAuB;AAEnC,SAAS,UAAU;AAoCnB,SAAS,SAAS,EAAE,WAAW,OAAO,GAAG,MAAM,GAAkB;AAC/D,SACE;AAAA,IAAC,kBAAkB;AAAA,IAAlB;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACA;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA,QAAC,kBAAkB;AAAA,QAAlB;AAAA,UACC,aAAU;AAAA,UACV,WAAU;AAAA,UACV,OAAO,EAAE,WAAW,eAAe,OAAO,SAAS,EAAE,KAAK;AAAA;AAAA,MAC5D;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -12,7 +12,7 @@ function RadioGroupItem({ className, ...props }) {
12
12
  {
13
13
  "data-slot": "radio-group-item",
14
14
  className: cn(
15
- "border-input text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-colors duration-150 ease-out outline-none disabled:cursor-not-allowed disabled:opacity-50",
15
+ "border-input text-control-on focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive-ink aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-colors duration-150 ease-out outline-none disabled:cursor-not-allowed disabled:opacity-50",
16
16
  className
17
17
  ),
18
18
  ...props,
@@ -21,7 +21,7 @@ function RadioGroupItem({ className, ...props }) {
21
21
  {
22
22
  "data-slot": "radio-group-indicator",
23
23
  className: "relative flex items-center justify-center",
24
- children: /* @__PURE__ */ jsx(CircleIcon, { className: "fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" })
24
+ children: /* @__PURE__ */ jsx(CircleIcon, { className: "fill-control-on absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" })
25
25
  }
26
26
  )
27
27
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/ui/radio-group.tsx"],"sourcesContent":["\"use client\";\n\nimport * as RadioGroupPrimitive from \"@radix-ui/react-radio-group\";\nimport { CircleIcon } from \"lucide-react\";\nimport type * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\nexport type RadioGroupProps = React.ComponentProps<typeof RadioGroupPrimitive.Root>;\nexport type RadioGroupItemProps = React.ComponentProps<typeof RadioGroupPrimitive.Item>;\n\nfunction RadioGroup({ className, ...props }: RadioGroupProps) {\n return <RadioGroupPrimitive.Root data-slot=\"radio-group\" className={cn(\"grid gap-3\", className)} {...props} />;\n}\n\nfunction RadioGroupItem({ className, ...props }: RadioGroupItemProps) {\n return (\n <RadioGroupPrimitive.Item\n data-slot=\"radio-group-item\"\n className={cn(\n \"border-input text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-colors duration-150 ease-out outline-none disabled:cursor-not-allowed disabled:opacity-50\",\n className\n )}\n {...props}\n >\n <RadioGroupPrimitive.Indicator\n data-slot=\"radio-group-indicator\"\n className=\"relative flex items-center justify-center\"\n >\n <CircleIcon className=\"fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2\" />\n </RadioGroupPrimitive.Indicator>\n </RadioGroupPrimitive.Item>\n );\n}\n\nexport { RadioGroup, RadioGroupItem };\n"],"mappings":";AAWS;AATT,YAAY,yBAAyB;AACrC,SAAS,kBAAkB;AAE3B,SAAS,UAAU;AAKnB,SAAS,WAAW,EAAE,WAAW,GAAG,MAAM,GAAoB;AAC5D,SAAO,oBAAC,oBAAoB,MAApB,EAAyB,aAAU,eAAc,WAAW,GAAG,cAAc,SAAS,GAAI,GAAG,OAAO;AAC9G;AAEA,SAAS,eAAe,EAAE,WAAW,GAAG,MAAM,GAAwB;AACpE,SACE;AAAA,IAAC,oBAAoB;AAAA,IAApB;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA,QAAC,oBAAoB;AAAA,QAApB;AAAA,UACC,aAAU;AAAA,UACV,WAAU;AAAA,UAEV,8BAAC,cAAW,WAAU,mFAAkF;AAAA;AAAA,MAC1G;AAAA;AAAA,EACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/ui/radio-group.tsx"],"sourcesContent":["\"use client\";\n\nimport * as RadioGroupPrimitive from \"@radix-ui/react-radio-group\";\nimport { CircleIcon } from \"lucide-react\";\nimport type * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\n/*\n * ─ CHẤM ĐÃ CHỌN: `--control-on`, KHÔNG PHẢI `--primary` ─\n *\n * Ô radio để nền trong suốt, nên \"đã chọn hay chưa\" chỉ còn được kể bằng cái CHẤM ở giữa —\n * thông tin thị giác đúng nghĩa của WCAG 1.4.11, ngưỡng 3:1. `fill-primary` (gold #E8B824)\n * trên nền trắng chỉ **1,86:1** ✗: ở light người dùng nhìn ô đã chọn gần như y hệt ô chưa chọn.\n * (Ở dark thì lọt lưới nên lỗi này chỉ lộ ở một nửa số theme.)\n * `--control-on` (= `--primary-ink`) là token của vai trò \"control ở trạng thái BẬT/ĐÃ CHỌN\".\n * Đo với nền TRANG, lấy ca tệ nhất trong 5 nền (`--muted`) và ca thường gặp (`--background`):\n *\n * chấm ⇄ nền trang : 6,09–6,51:1 (light) · 10,80–12,67:1 (dark) ✓\n * viền ô `border-input` ⇄ nền trang : 3,83:1 (light) · 4,21–4,94:1 (dark) ✓\n *\n * ─ VÌ SAO BỎ `dark:bg-input/30` ─\n * `--input` là alias của `--control-edge` — token VIỀN control (hợp đồng màu §3), không phải bậc\n * tô nền. Đem nó tô ruột ở dark vừa sai vai trò, vừa nâng mặt nền LÊN SÁNG hơn mọi nền trang, kéo\n * `--muted-foreground` (token chữ, hiệu chỉnh theo nền TRANG — §6) xuống dưới ngưỡng ở mọi nền:\n * 3,81 · 3,54 · 3,22:1 ✗. Bỏ lớp tô thì ô radio vẫn được khoanh vùng bằng chính viền `border-input`\n * (4,21–4,94:1 ở dark, thừa ngưỡng 3:1), và chấm đã chọn còn nổi hơn trước (8,47 → 10,80–12,67:1).\n *\n * `text-control-on` trên Item giữ cho `fill-current`/icon do app truyền vào cũng đúng màu.\n * Viền lỗi dùng `--destructive-ink` (7,15:1 / 6,84:1) thay `--destructive` (5,03:1 nhưng chỉ\n * 3,76:1 ở dark) — xem hợp đồng màu §1: bậc đi lên nền TRANG luôn là `-ink`.\n */\nexport type RadioGroupProps = React.ComponentProps<typeof RadioGroupPrimitive.Root>;\nexport type RadioGroupItemProps = React.ComponentProps<typeof RadioGroupPrimitive.Item>;\n\nfunction RadioGroup({ className, ...props }: RadioGroupProps) {\n return <RadioGroupPrimitive.Root data-slot=\"radio-group\" className={cn(\"grid gap-3\", className)} {...props} />;\n}\n\nfunction RadioGroupItem({ className, ...props }: RadioGroupItemProps) {\n return (\n <RadioGroupPrimitive.Item\n data-slot=\"radio-group-item\"\n className={cn(\n \"border-input text-control-on focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive-ink aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-colors duration-150 ease-out outline-none disabled:cursor-not-allowed disabled:opacity-50\",\n className\n )}\n {...props}\n >\n <RadioGroupPrimitive.Indicator\n data-slot=\"radio-group-indicator\"\n className=\"relative flex items-center justify-center\"\n >\n <CircleIcon className=\"fill-control-on absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2\" />\n </RadioGroupPrimitive.Indicator>\n </RadioGroupPrimitive.Item>\n );\n}\n\nexport { RadioGroup, RadioGroupItem };\n"],"mappings":";AAmCS;AAjCT,YAAY,yBAAyB;AACrC,SAAS,kBAAkB;AAE3B,SAAS,UAAU;AA6BnB,SAAS,WAAW,EAAE,WAAW,GAAG,MAAM,GAAoB;AAC5D,SAAO,oBAAC,oBAAoB,MAApB,EAAyB,aAAU,eAAc,WAAW,GAAG,cAAc,SAAS,GAAI,GAAG,OAAO;AAC9G;AAEA,SAAS,eAAe,EAAE,WAAW,GAAG,MAAM,GAAwB;AACpE,SACE;AAAA,IAAC,oBAAoB;AAAA,IAApB;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA,QAAC,oBAAoB;AAAA,QAApB;AAAA,UACC,aAAU;AAAA,UACV,WAAU;AAAA,UAEV,8BAAC,cAAW,WAAU,sFAAqF;AAAA;AAAA,MAC7G;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -5,6 +5,20 @@ import * as ResizablePrimitive from 'react-resizable-panels';
5
5
  * Resizable (react-resizable-panels) — bố cục nhiều panel kéo giãn được.
6
6
  * Dùng cho layout 3 cột kiểu Linear/Lark (danh sách ▸ chi tiết ▸ panel phụ).
7
7
  * Muốn nhớ kích thước: đặt `autoSaveId` trên `ResizablePanelGroup`.
8
+ *
9
+ * ─ MÀU: `--control-edge`, KHÔNG PHẢI `--border` ─
10
+ * Thanh chia này là CONTROL THẬT chứ không phải đường kẻ trang trí: react-resizable-panels
11
+ * render nó với `role="separator"` + `tabIndex=0`, kéo được bằng chuột lẫn bàn phím. Nên nó
12
+ * chịu ngưỡng 3:1 của WCAG 1.4.11, và theo hợp đồng ở styles.css §RANH GIỚI thì ranh giới
13
+ * control phải đi qua `--control-edge` — `--border` là bậc TRANG TRÍ, cố ý nhạt:
14
+ *
15
+ * `bg-border` ⇄ --background 1,16:1 (light) · 1,17:1 (dark) ✗
16
+ * ⇄ --card 1,16:1 · 1,10:1 ✗ · ⇄ --muted 1,08:1 · 1,00:1 ✗
17
+ * `bg-control-edge` ⇄ --background 3,83:1 · 4,94:1 ✓ · ⇄ --card 3,83:1 · 4,64:1 ✓
18
+ * ⇄ --muted 3,58:1 · 4,21:1 ✓
19
+ *
20
+ * Núm `withHandle` cũng vậy: tô đặc `bg-control-edge` (tự khoanh vùng, không cần `border`),
21
+ * vạch grip bên trong lấy `--control-knob` (= nền trang) — 3,83:1 / 4,94:1 trên núm ✓.
8
22
  */
9
23
  type ResizablePanelGroupProps = React.ComponentProps<typeof ResizablePrimitive.PanelGroup>;
10
24
  type ResizablePanelProps = React.ComponentProps<typeof ResizablePrimitive.Panel>;
@@ -22,11 +22,11 @@ function ResizableHandle({ withHandle, className, ...props }) {
22
22
  {
23
23
  "data-slot": "resizable-handle",
24
24
  className: cn(
25
- "bg-border relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:translate-x-0 data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90",
25
+ "bg-control-edge relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:translate-x-0 data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90",
26
26
  className
27
27
  ),
28
28
  ...props,
29
- children: withHandle && /* @__PURE__ */ jsx("div", { className: "bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border", children: /* @__PURE__ */ jsx(GripVerticalIcon, { className: "size-2.5" }) })
29
+ children: withHandle && /* @__PURE__ */ jsx("div", { className: "bg-control-edge z-10 flex h-4 w-3 items-center justify-center rounded-xs [&_svg]:text-control-knob", children: /* @__PURE__ */ jsx(GripVerticalIcon, { className: "size-2.5" }) })
30
30
  }
31
31
  );
32
32
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/ui/resizable.tsx"],"sourcesContent":["\"use client\";\n\nimport { GripVerticalIcon } from \"lucide-react\";\nimport type * as React from \"react\";\nimport * as ResizablePrimitive from \"react-resizable-panels\";\nimport { cn } from \"../../lib/cn.js\";\n\n/**\n * Resizable (react-resizable-panels) — bố cục nhiều panel kéo giãn được.\n * Dùng cho layout 3 cột kiểu Linear/Lark (danh sách ▸ chi tiết ▸ panel phụ).\n * Muốn nhớ kích thước: đặt `autoSaveId` trên `ResizablePanelGroup`.\n */\n\nexport type ResizablePanelGroupProps = React.ComponentProps<typeof ResizablePrimitive.PanelGroup>;\nexport type ResizablePanelProps = React.ComponentProps<typeof ResizablePrimitive.Panel>;\nexport type ResizableHandleProps = React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {\n /** Hiện tay nắm có gạch dọc ở giữa thanh chia. */\n withHandle?: boolean;\n};\n\nfunction ResizablePanelGroup({ className, ...props }: ResizablePanelGroupProps) {\n return (\n <ResizablePrimitive.PanelGroup\n data-slot=\"resizable-panel-group\"\n className={cn(\"flex h-full w-full data-[panel-group-direction=vertical]:flex-col\", className)}\n {...props}\n />\n );\n}\n\nfunction ResizablePanel({ ...props }: ResizablePanelProps) {\n return <ResizablePrimitive.Panel data-slot=\"resizable-panel\" {...props} />;\n}\n\nfunction ResizableHandle({ withHandle, className, ...props }: ResizableHandleProps) {\n return (\n <ResizablePrimitive.PanelResizeHandle\n data-slot=\"resizable-handle\"\n className={cn(\n \"bg-border relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:translate-x-0 data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90\",\n className\n )}\n {...props}\n >\n {withHandle && (\n <div className=\"bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border\">\n <GripVerticalIcon className=\"size-2.5\" />\n </div>\n )}\n </ResizablePrimitive.PanelResizeHandle>\n );\n}\n\nexport { ResizableHandle, ResizablePanel, ResizablePanelGroup };\n"],"mappings":";AAsBI;AApBJ,SAAS,wBAAwB;AAEjC,YAAY,wBAAwB;AACpC,SAAS,UAAU;AAenB,SAAS,oBAAoB,EAAE,WAAW,GAAG,MAAM,GAA6B;AAC9E,SACE;AAAA,IAAC,mBAAmB;AAAA,IAAnB;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,qEAAqE,SAAS;AAAA,MAC3F,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,SAAS,eAAe,EAAE,GAAG,MAAM,GAAwB;AACzD,SAAO,oBAAC,mBAAmB,OAAnB,EAAyB,aAAU,mBAAmB,GAAG,OAAO;AAC1E;AAEA,SAAS,gBAAgB,EAAE,YAAY,WAAW,GAAG,MAAM,GAAyB;AAClF,SACE;AAAA,IAAC,mBAAmB;AAAA,IAAnB;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH,wBACC,oBAAC,SAAI,WAAU,6EACb,8BAAC,oBAAiB,WAAU,YAAW,GACzC;AAAA;AAAA,EAEJ;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/ui/resizable.tsx"],"sourcesContent":["\"use client\";\n\nimport { GripVerticalIcon } from \"lucide-react\";\nimport type * as React from \"react\";\nimport * as ResizablePrimitive from \"react-resizable-panels\";\nimport { cn } from \"../../lib/cn.js\";\n\n/**\n * Resizable (react-resizable-panels) — bố cục nhiều panel kéo giãn được.\n * Dùng cho layout 3 cột kiểu Linear/Lark (danh sách ▸ chi tiết ▸ panel phụ).\n * Muốn nhớ kích thước: đặt `autoSaveId` trên `ResizablePanelGroup`.\n *\n * ─ MÀU: `--control-edge`, KHÔNG PHẢI `--border` ─\n * Thanh chia này là CONTROL THẬT chứ không phải đường kẻ trang trí: react-resizable-panels\n * render nó với `role=\"separator\"` + `tabIndex=0`, kéo được bằng chuột lẫn bàn phím. Nên nó\n * chịu ngưỡng 3:1 của WCAG 1.4.11, và theo hợp đồng ở styles.css §RANH GIỚI thì ranh giới\n * control phải đi qua `--control-edge` — `--border` là bậc TRANG TRÍ, cố ý nhạt:\n *\n * `bg-border` ⇄ --background 1,16:1 (light) · 1,17:1 (dark) ✗\n * ⇄ --card 1,16:1 · 1,10:1 ✗ · ⇄ --muted 1,08:1 · 1,00:1 ✗\n * `bg-control-edge` ⇄ --background 3,83:1 · 4,94:1 ✓ · ⇄ --card 3,83:1 · 4,64:1 ✓\n * ⇄ --muted 3,58:1 · 4,21:1 ✓\n *\n * Núm `withHandle` cũng vậy: tô đặc `bg-control-edge` (tự khoanh vùng, không cần `border`),\n * vạch grip bên trong lấy `--control-knob` (= nền trang) — 3,83:1 / 4,94:1 trên núm ✓.\n */\n\nexport type ResizablePanelGroupProps = React.ComponentProps<typeof ResizablePrimitive.PanelGroup>;\nexport type ResizablePanelProps = React.ComponentProps<typeof ResizablePrimitive.Panel>;\nexport type ResizableHandleProps = React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {\n /** Hiện tay nắm có gạch dọc ở giữa thanh chia. */\n withHandle?: boolean;\n};\n\nfunction ResizablePanelGroup({ className, ...props }: ResizablePanelGroupProps) {\n return (\n <ResizablePrimitive.PanelGroup\n data-slot=\"resizable-panel-group\"\n className={cn(\"flex h-full w-full data-[panel-group-direction=vertical]:flex-col\", className)}\n {...props}\n />\n );\n}\n\nfunction ResizablePanel({ ...props }: ResizablePanelProps) {\n return <ResizablePrimitive.Panel data-slot=\"resizable-panel\" {...props} />;\n}\n\nfunction ResizableHandle({ withHandle, className, ...props }: ResizableHandleProps) {\n return (\n <ResizablePrimitive.PanelResizeHandle\n data-slot=\"resizable-handle\"\n className={cn(\n \"bg-control-edge relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:translate-x-0 data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90\",\n className\n )}\n {...props}\n >\n {withHandle && (\n <div className=\"bg-control-edge z-10 flex h-4 w-3 items-center justify-center rounded-xs [&_svg]:text-control-knob\">\n <GripVerticalIcon className=\"size-2.5\" />\n </div>\n )}\n </ResizablePrimitive.PanelResizeHandle>\n );\n}\n\nexport { ResizableHandle, ResizablePanel, ResizablePanelGroup };\n"],"mappings":";AAoCI;AAlCJ,SAAS,wBAAwB;AAEjC,YAAY,wBAAwB;AACpC,SAAS,UAAU;AA6BnB,SAAS,oBAAoB,EAAE,WAAW,GAAG,MAAM,GAA6B;AAC9E,SACE;AAAA,IAAC,mBAAmB;AAAA,IAAnB;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,qEAAqE,SAAS;AAAA,MAC3F,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,SAAS,eAAe,EAAE,GAAG,MAAM,GAAwB;AACzD,SAAO,oBAAC,mBAAmB,OAAnB,EAAyB,aAAU,mBAAmB,GAAG,OAAO;AAC1E;AAEA,SAAS,gBAAgB,EAAE,YAAY,WAAW,GAAG,MAAM,GAAyB;AAClF,SACE;AAAA,IAAC,mBAAmB;AAAA,IAAnB;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH,wBACC,oBAAC,SAAI,WAAU,sGACb,8BAAC,oBAAiB,WAAU,YAAW,GACzC;AAAA;AAAA,EAEJ;AAEJ;","names":[]}
@@ -33,7 +33,7 @@ function ScrollBar({ className, orientation = "vertical", ...props }) {
33
33
  ScrollAreaPrimitive.ScrollAreaThumb,
34
34
  {
35
35
  "data-slot": "scroll-area-thumb",
36
- className: "bg-border relative flex-1 rounded-full"
36
+ className: "bg-control-edge relative flex-1 rounded-full"
37
37
  }
38
38
  )
39
39
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/ui/scroll-area.tsx"],"sourcesContent":["\"use client\";\n\nimport * as ScrollAreaPrimitive from \"@radix-ui/react-scroll-area\";\nimport type * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\n/**\n * ScrollArea (Radix) — thanh cuộn tuỳ biến, hiện đè lên nội dung.\n * Cần chiều cao xác định trên `ScrollArea` (vd `className=\"h-72\"`) thì mới cuộn.\n */\n\nexport type ScrollAreaProps = React.ComponentProps<typeof ScrollAreaPrimitive.Root>;\nexport type ScrollBarProps = React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>;\n\nfunction ScrollArea({ className, children, ...props }: ScrollAreaProps) {\n return (\n <ScrollAreaPrimitive.Root data-slot=\"scroll-area\" className={cn(\"relative\", className)} {...props}>\n <ScrollAreaPrimitive.Viewport\n data-slot=\"scroll-area-viewport\"\n className=\"size-full rounded-[inherit] transition-colors duration-150 ease-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background\"\n >\n {children}\n </ScrollAreaPrimitive.Viewport>\n <ScrollBar />\n <ScrollAreaPrimitive.Corner />\n </ScrollAreaPrimitive.Root>\n );\n}\n\nfunction ScrollBar({ className, orientation = \"vertical\", ...props }: ScrollBarProps) {\n return (\n <ScrollAreaPrimitive.ScrollAreaScrollbar\n data-slot=\"scroll-area-scrollbar\"\n orientation={orientation}\n className={cn(\n \"flex touch-none p-px transition-colors duration-150 ease-out select-none\",\n orientation === \"vertical\" && \"h-full w-2.5 border-l border-l-transparent\",\n orientation === \"horizontal\" && \"h-2.5 flex-col border-t border-t-transparent\",\n className\n )}\n {...props}\n >\n <ScrollAreaPrimitive.ScrollAreaThumb\n data-slot=\"scroll-area-thumb\"\n className=\"bg-border relative flex-1 rounded-full\"\n />\n </ScrollAreaPrimitive.ScrollAreaScrollbar>\n );\n}\n\nexport { ScrollArea, ScrollBar };\n"],"mappings":";AAgBI,SACE,KADF;AAdJ,YAAY,yBAAyB;AAErC,SAAS,UAAU;AAUnB,SAAS,WAAW,EAAE,WAAW,UAAU,GAAG,MAAM,GAAoB;AACtE,SACE,qBAAC,oBAAoB,MAApB,EAAyB,aAAU,eAAc,WAAW,GAAG,YAAY,SAAS,GAAI,GAAG,OAC1F;AAAA;AAAA,MAAC,oBAAoB;AAAA,MAApB;AAAA,QACC,aAAU;AAAA,QACV,WAAU;AAAA,QAET;AAAA;AAAA,IACH;AAAA,IACA,oBAAC,aAAU;AAAA,IACX,oBAAC,oBAAoB,QAApB,EAA2B;AAAA,KAC9B;AAEJ;AAEA,SAAS,UAAU,EAAE,WAAW,cAAc,YAAY,GAAG,MAAM,GAAmB;AACpF,SACE;AAAA,IAAC,oBAAoB;AAAA,IAApB;AAAA,MACC,aAAU;AAAA,MACV;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA,gBAAgB,cAAc;AAAA,QAC9B,gBAAgB,gBAAgB;AAAA,QAChC;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA,QAAC,oBAAoB;AAAA,QAApB;AAAA,UACC,aAAU;AAAA,UACV,WAAU;AAAA;AAAA,MACZ;AAAA;AAAA,EACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/ui/scroll-area.tsx"],"sourcesContent":["\"use client\";\n\nimport * as ScrollAreaPrimitive from \"@radix-ui/react-scroll-area\";\nimport type * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\n/**\n * ScrollArea (Radix) — thanh cuộn tuỳ biến, hiện đè lên nội dung.\n * Cần chiều cao xác định trên `ScrollArea` (vd `className=\"h-72\"`) thì mới cuộn.\n */\n\nexport type ScrollAreaProps = React.ComponentProps<typeof ScrollAreaPrimitive.Root>;\nexport type ScrollBarProps = React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>;\n\nfunction ScrollArea({ className, children, ...props }: ScrollAreaProps) {\n return (\n <ScrollAreaPrimitive.Root data-slot=\"scroll-area\" className={cn(\"relative\", className)} {...props}>\n <ScrollAreaPrimitive.Viewport\n data-slot=\"scroll-area-viewport\"\n className=\"size-full rounded-[inherit] transition-colors duration-150 ease-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background\"\n >\n {children}\n </ScrollAreaPrimitive.Viewport>\n <ScrollBar />\n <ScrollAreaPrimitive.Corner />\n </ScrollAreaPrimitive.Root>\n );\n}\n\nfunction ScrollBar({ className, orientation = \"vertical\", ...props }: ScrollBarProps) {\n return (\n <ScrollAreaPrimitive.ScrollAreaScrollbar\n data-slot=\"scroll-area-scrollbar\"\n orientation={orientation}\n className={cn(\n \"flex touch-none p-px transition-colors duration-150 ease-out select-none\",\n orientation === \"vertical\" && \"h-full w-2.5 border-l border-l-transparent\",\n orientation === \"horizontal\" && \"h-2.5 flex-col border-t border-t-transparent\",\n className\n )}\n {...props}\n >\n {/*\n Núm cuộn là CONTROL (kéo được) và là dấu chỉ vị trí cuộn → ngưỡng 3:1, không phải đường\n kẻ trang trí. `bg-border` chỉ 1,16:1 (light) · 1,17:1 (dark) với --background — núm vô\n hình. `bg-control-edge`: 3,83:1 (light) · 4,94:1 (dark) trên --background, 3,58:1 ·\n 4,21:1 trên --muted ✓\n */}\n <ScrollAreaPrimitive.ScrollAreaThumb\n data-slot=\"scroll-area-thumb\"\n className=\"bg-control-edge relative flex-1 rounded-full\"\n />\n </ScrollAreaPrimitive.ScrollAreaScrollbar>\n );\n}\n\nexport { ScrollArea, ScrollBar };\n"],"mappings":";AAgBI,SACE,KADF;AAdJ,YAAY,yBAAyB;AAErC,SAAS,UAAU;AAUnB,SAAS,WAAW,EAAE,WAAW,UAAU,GAAG,MAAM,GAAoB;AACtE,SACE,qBAAC,oBAAoB,MAApB,EAAyB,aAAU,eAAc,WAAW,GAAG,YAAY,SAAS,GAAI,GAAG,OAC1F;AAAA;AAAA,MAAC,oBAAoB;AAAA,MAApB;AAAA,QACC,aAAU;AAAA,QACV,WAAU;AAAA,QAET;AAAA;AAAA,IACH;AAAA,IACA,oBAAC,aAAU;AAAA,IACX,oBAAC,oBAAoB,QAApB,EAA2B;AAAA,KAC9B;AAEJ;AAEA,SAAS,UAAU,EAAE,WAAW,cAAc,YAAY,GAAG,MAAM,GAAmB;AACpF,SACE;AAAA,IAAC,oBAAoB;AAAA,IAApB;AAAA,MACC,aAAU;AAAA,MACV;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA,gBAAgB,cAAc;AAAA,QAC9B,gBAAgB,gBAAgB;AAAA,QAChC;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAQJ;AAAA,QAAC,oBAAoB;AAAA,QAApB;AAAA,UACC,aAAU;AAAA,UACV,WAAU;AAAA;AAAA,MACZ;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -5,6 +5,21 @@ import * as React from 'react';
5
5
  * Select (Radix) theo style new-york + brand Comitor.
6
6
  * Trigger có 2 cỡ (`sm` = h-8, `default` = h-9); mục đang chọn tô nền gold nhạt.
7
7
  * Lưu ý: Radix KHÔNG nhận `value=""` cho SelectItem — dùng sentinel (vd `__empty__`).
8
+ *
9
+ * ─ VIỀN TRIGGER: BA TRẠNG THÁI, BA TOKEN ─
10
+ * 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
11
+ * trạng thái, và hover/focus phải ĐẬM HƠN lúc nghỉ chứ không nhạt đi:
12
+ *
13
+ * nghỉ `border-input` (= --control-edge) : 3,83:1 (light) · 4,94:1 (dark) ✓
14
+ * hover `border-control-edge-strong` : 10,55:1 (light) · 11,65:1 (dark) ✓
15
+ * focus `border-primary-ink` : 6,51:1 (light) · 12,67:1 (dark) ✓
16
+ * lỗi `border-destructive-ink` : 7,15:1 (light) · 6,84:1 (dark) ✓
17
+ *
18
+ * Bản trước đi SAI CHIỀU ở light: `hover:border-ash-300` chỉ **1,62:1** và
19
+ * `focus:border-gold-300` chỉ **1,86:1** — rê chuột hoặc focus vào là trigger biến mất, đúng
20
+ * 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à
21
+ * token TÔ NỀN đem làm viền; bậc `-ink` mới là bậc đứng trên nền trang.
22
+ * (`ring-*` giữ nguyên bậc `--destructive`/`--ring`: quầng ring là vai trò TÔ, đúng hợp đồng.)
8
23
  */
9
24
  type SelectProps = React.ComponentProps<typeof SelectPrimitive.Root>;
10
25
  type SelectGroupProps = React.ComponentProps<typeof SelectPrimitive.Group>;
@@ -25,6 +40,24 @@ declare function SelectValue({ ...props }: SelectValueProps): React.JSX.Element;
25
40
  declare function SelectTrigger({ className, size, children, ...props }: SelectTriggerProps): React.JSX.Element;
26
41
  declare function SelectContent({ className, children, position, ...props }: SelectContentProps): React.JSX.Element;
27
42
  declare function SelectLabel({ className, ...props }: SelectLabelProps): React.JSX.Element;
43
+ /**
44
+ * SelectItem — mục trong danh sách chọn.
45
+ *
46
+ * 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,
47
+ * `gold-500/10` ở dark) nên nền không phân biệt được hai thứ đó, mà tint thì quá nhạt để tự
48
+ * 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.
49
+ *
50
+ * 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:
51
+ * 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
52
+ * `--muted` (light); 12,67:1 / 10,80:1 (dark). Bề rộng 2px được ĐẶT SẴN bằng `border-l-transparent`
53
+ * 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
54
+ * 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),
55
+ * 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
56
+ * vào màu nền.
57
+ *
58
+ * Vì sao là `-ink` chứ không `--app-accent` trần: gold `#E8B824` chỉ 1,86:1 với nền trắng —
59
+ * đúng cái bẫy "token tô NỀN đem vẽ DẤU" mà hợp đồng màu §1 cấm.
60
+ */
28
61
  declare function SelectItem({ className, children, ...props }: SelectItemProps): React.JSX.Element;
29
62
  declare function SelectSeparator({ className, ...props }: SelectSeparatorProps): React.JSX.Element;
30
63
  declare function SelectScrollUpButton({ className, ...props }: SelectScrollUpButtonProps): React.JSX.Element;
@@ -19,7 +19,7 @@ function SelectTrigger({ className, size = "default", children, ...props }) {
19
19
  "data-slot": "select-trigger",
20
20
  "data-size": size,
21
21
  className: cn(
22
- "border-input bg-background text-foreground data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground hover:border-ash-300 dark:hover:border-ash-500 focus:outline-none focus:ring-2 focus:ring-ring/50 focus:border-gold-300 aria-invalid:border-red aria-invalid:ring-red/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",
22
+ "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",
23
23
  className
24
24
  ),
25
25
  ...props,
@@ -75,12 +75,12 @@ function SelectItem({ className, children, ...props }) {
75
75
  {
76
76
  "data-slot": "select-item",
77
77
  className: cn(
78
- "focus:bg-gold-50 dark:focus:bg-gold-500/10 data-[state=checked]:bg-gold-50 dark:data-[state=checked]:bg-gold-500/10 [&_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",
78
+ "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",
79
79
  className
80
80
  ),
81
81
  ...props,
82
82
  children: [
83
- /* @__PURE__ */ jsx("span", { className: "absolute right-2 flex size-3.5 items-center justify-center", children: /* @__PURE__ */ jsx(SelectPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx(CheckIcon, { className: "size-4" }) }) }),
83
+ /* @__PURE__ */ jsx("span", { className: "absolute right-2 flex size-3.5 items-center justify-center", children: /* @__PURE__ */ jsx(SelectPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx(CheckIcon, { className: "size-4 text-app-accent-ink" }) }) }),
84
84
  /* @__PURE__ */ jsx(SelectPrimitive.ItemText, { children })
85
85
  ]
86
86
  }
@@ -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\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-ash-300 dark:hover:border-ash-500 focus:outline-none focus:ring-2 focus:ring-ring/50 focus:border-gold-300 aria-invalid:border-red aria-invalid:ring-red/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\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 [&_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\" />\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":";AA4BS,cAaL,YAbK;AA1BT,YAAY,qBAAqB;AACjC,SAAS,WAAW,iBAAiB,qBAAqB;AAE1D,SAAS,UAAU;AAsBnB,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;AAEA,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,UAAS,GAChC,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 * 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":[]}
@@ -5,6 +5,25 @@ import * as React from 'react';
5
5
  * Slider (Radix) — hỗ trợ 1 hoặc nhiều thumb (range) và cả hướng dọc.
6
6
  * Số thumb suy ra từ `value`/`defaultValue`; không truyền gì thì mặc định 2 thumb
7
7
  * ứng với [min, max].
8
+ *
9
+ * ─ MÀU: CÙNG BỘ TOKEN VỚI `Switch`/`Checkbox` ─
10
+ *
11
+ * Thanh trượt là thành phần phi văn bản (WCAG 1.4.11, ngưỡng 3:1). Bản trước hỏng cả ba phía
12
+ * ở light: rãnh `bg-muted` chỉ **1,07:1** với nền trắng (không nhìn thấy cái slider), đoạn đã
13
+ * chọn `bg-primary` **1,86:1** với nền và **1,73:1** với rãnh, núm `bg-white` **1,86:1** với
14
+ * đoạn đã chọn — và `bg-white` cứng thì ở dark núm ăn 1,49:1 với đoạn gold sáng.
15
+ *
16
+ * Nay đúng bộ ba của hợp đồng màu (docs/hop-dong-mau.md §3–§4):
17
+ *
18
+ * rãnh `bg-control-edge` : 3,83:1 (light) · 4,94:1 (dark) với nền trang ✓
19
+ * đã chọn `bg-control-on` : 6,51:1 (light) · 12,67:1 (dark) với nền trang ✓
20
+ * núm `bg-control-knob` : 6,51 / 12,67:1 với đoạn đã chọn · 3,83 / 4,94:1 với rãnh ✓
21
+ * viền núm `border-control-on` : 6,51:1 (light) · 12,67:1 (dark) với nền trang ✓
22
+ *
23
+ * ⚠ Đoạn ĐÃ CHỌN ⇄ rãnh chỉ 1,70:1 (light) / 2,57:1 (dark) — giống hệt cặp rãnh BẬT ⇄ rãnh TẮT
24
+ * của `Switch`, và chấp nhận vì cùng lý do: VỊ TRÍ NÚM mới là thứ mang giá trị, còn hai màu kia
25
+ * đều tự đạt ≥3:1 với nền trang và với núm. Với nền trang trắng thì không tồn tại cặp màu nào
26
+ * vừa ≥3:1 với nhau vừa ≥3:1 với nền.
8
27
  */
9
28
  type SliderProps = React.ComponentProps<typeof SliderPrimitive.Root>;
10
29
  declare function Slider({ className, defaultValue, value, min, max, ...props }: SliderProps): React.JSX.Element;
@@ -26,12 +26,12 @@ function Slider({ className, defaultValue, value, min = 0, max = 100, ...props }
26
26
  SliderPrimitive.Track,
27
27
  {
28
28
  "data-slot": "slider-track",
29
- className: "bg-muted relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5",
29
+ className: "bg-control-edge relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5",
30
30
  children: /* @__PURE__ */ jsx(
31
31
  SliderPrimitive.Range,
32
32
  {
33
33
  "data-slot": "slider-range",
34
- className: "bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full"
34
+ className: "bg-control-on absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full"
35
35
  }
36
36
  )
37
37
  }
@@ -40,7 +40,7 @@ function Slider({ className, defaultValue, value, min = 0, max = 100, ...props }
40
40
  SliderPrimitive.Thumb,
41
41
  {
42
42
  "data-slot": "slider-thumb",
43
- className: "border-primary ring-ring/50 block size-4 shrink-0 rounded-full border bg-white shadow-sm transition-colors duration-150 ease-out hover:ring-4 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"
43
+ className: "border-control-on ring-ring/50 block size-4 shrink-0 rounded-full border bg-control-knob shadow-sm transition-colors duration-150 ease-out hover:ring-4 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"
44
44
  },
45
45
  index
46
46
  ))
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/ui/slider.tsx"],"sourcesContent":["\"use client\";\n\nimport * as SliderPrimitive from \"@radix-ui/react-slider\";\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\n/**\n * Slider (Radix) — hỗ trợ 1 hoặc nhiều thumb (range) và cả hướng dọc.\n * Số thumb suy ra từ `value`/`defaultValue`; không truyền gì thì mặc định 2 thumb\n * ứng với [min, max].\n */\n\nexport type SliderProps = React.ComponentProps<typeof SliderPrimitive.Root>;\n\nfunction Slider({ className, defaultValue, value, min = 0, max = 100, ...props }: SliderProps) {\n const _values = React.useMemo(\n () => (Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [min, max]),\n [value, defaultValue, min, max]\n );\n\n return (\n <SliderPrimitive.Root\n data-slot=\"slider\"\n defaultValue={defaultValue}\n value={value}\n min={min}\n max={max}\n className={cn(\n \"relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col\",\n className\n )}\n {...props}\n >\n <SliderPrimitive.Track\n data-slot=\"slider-track\"\n className=\"bg-muted relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5\"\n >\n <SliderPrimitive.Range\n data-slot=\"slider-range\"\n className=\"bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full\"\n />\n </SliderPrimitive.Track>\n {Array.from({ length: _values.length }, (_, index) => (\n <SliderPrimitive.Thumb\n data-slot=\"slider-thumb\"\n // biome-ignore lint/suspicious/noArrayIndexKey: thumb chỉ phân biệt bằng vị trí, không có id\n key={index}\n className=\"border-primary ring-ring/50 block size-4 shrink-0 rounded-full border bg-white shadow-sm transition-colors duration-150 ease-out hover:ring-4 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 />\n ))}\n </SliderPrimitive.Root>\n );\n}\n\nexport { Slider };\n"],"mappings":";AAqBI,SAgBI,KAhBJ;AAnBJ,YAAY,qBAAqB;AACjC,YAAY,WAAW;AACvB,SAAS,UAAU;AAUnB,SAAS,OAAO,EAAE,WAAW,cAAc,OAAO,MAAM,GAAG,MAAM,KAAK,GAAG,MAAM,GAAgB;AAC7F,QAAM,UAAU,MAAM;AAAA,IACpB,MAAO,MAAM,QAAQ,KAAK,IAAI,QAAQ,MAAM,QAAQ,YAAY,IAAI,eAAe,CAAC,KAAK,GAAG;AAAA,IAC5F,CAAC,OAAO,cAAc,KAAK,GAAG;AAAA,EAChC;AAEA,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA;AAAA,UAAC,gBAAgB;AAAA,UAAhB;AAAA,YACC,aAAU;AAAA,YACV,WAAU;AAAA,YAEV;AAAA,cAAC,gBAAgB;AAAA,cAAhB;AAAA,gBACC,aAAU;AAAA,gBACV,WAAU;AAAA;AAAA,YACZ;AAAA;AAAA,QACF;AAAA,QACC,MAAM,KAAK,EAAE,QAAQ,QAAQ,OAAO,GAAG,CAAC,GAAG,UAC1C;AAAA,UAAC,gBAAgB;AAAA,UAAhB;AAAA,YACC,aAAU;AAAA,YAGV,WAAU;AAAA;AAAA,UADL;AAAA,QAEP,CACD;AAAA;AAAA;AAAA,EACH;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/ui/slider.tsx"],"sourcesContent":["\"use client\";\n\nimport * as SliderPrimitive from \"@radix-ui/react-slider\";\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\n/**\n * Slider (Radix) — hỗ trợ 1 hoặc nhiều thumb (range) và cả hướng dọc.\n * Số thumb suy ra từ `value`/`defaultValue`; không truyền gì thì mặc định 2 thumb\n * ứng với [min, max].\n *\n * ─ MÀU: CÙNG BỘ TOKEN VỚI `Switch`/`Checkbox` ─\n *\n * Thanh trượt là thành phần phi văn bản (WCAG 1.4.11, ngưỡng 3:1). Bản trước hỏng cả ba phía\n * ở light: rãnh `bg-muted` chỉ **1,07:1** với nền trắng (không nhìn thấy cái slider), đoạn đã\n * chọn `bg-primary` **1,86:1** với nền và **1,73:1** với rãnh, núm `bg-white` **1,86:1** với\n * đoạn đã chọn — và `bg-white` cứng thì ở dark núm ăn 1,49:1 với đoạn gold sáng.\n *\n * Nay đúng bộ ba của hợp đồng màu (docs/hop-dong-mau.md §3–§4):\n *\n * rãnh `bg-control-edge` : 3,83:1 (light) · 4,94:1 (dark) với nền trang ✓\n * đã chọn `bg-control-on` : 6,51:1 (light) · 12,67:1 (dark) với nền trang ✓\n * núm `bg-control-knob` : 6,51 / 12,67:1 với đoạn đã chọn · 3,83 / 4,94:1 với rãnh ✓\n * viền núm `border-control-on` : 6,51:1 (light) · 12,67:1 (dark) với nền trang ✓\n *\n * ⚠ Đoạn ĐÃ CHỌN ⇄ rãnh chỉ 1,70:1 (light) / 2,57:1 (dark) — giống hệt cặp rãnh BẬT ⇄ rãnh TẮT\n * của `Switch`, và chấp nhận vì cùng lý do: VỊ TRÍ NÚM mới là thứ mang giá trị, còn hai màu kia\n * đều tự đạt ≥3:1 với nền trang và với núm. Với nền trang trắng thì không tồn tại cặp màu nào\n * vừa ≥3:1 với nhau vừa ≥3:1 với nền.\n */\n\nexport type SliderProps = React.ComponentProps<typeof SliderPrimitive.Root>;\n\nfunction Slider({ className, defaultValue, value, min = 0, max = 100, ...props }: SliderProps) {\n const _values = React.useMemo(\n () => (Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [min, max]),\n [value, defaultValue, min, max]\n );\n\n return (\n <SliderPrimitive.Root\n data-slot=\"slider\"\n defaultValue={defaultValue}\n value={value}\n min={min}\n max={max}\n className={cn(\n \"relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col\",\n className\n )}\n {...props}\n >\n <SliderPrimitive.Track\n data-slot=\"slider-track\"\n className=\"bg-control-edge relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5\"\n >\n <SliderPrimitive.Range\n data-slot=\"slider-range\"\n className=\"bg-control-on absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full\"\n />\n </SliderPrimitive.Track>\n {Array.from({ length: _values.length }, (_, index) => (\n <SliderPrimitive.Thumb\n data-slot=\"slider-thumb\"\n // biome-ignore lint/suspicious/noArrayIndexKey: thumb chỉ phân biệt bằng vị trí, không có id\n key={index}\n className=\"border-control-on ring-ring/50 block size-4 shrink-0 rounded-full border bg-control-knob shadow-sm transition-colors duration-150 ease-out hover:ring-4 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 />\n ))}\n </SliderPrimitive.Root>\n );\n}\n\nexport { Slider };\n"],"mappings":";AAwCI,SAgBI,KAhBJ;AAtCJ,YAAY,qBAAqB;AACjC,YAAY,WAAW;AACvB,SAAS,UAAU;AA6BnB,SAAS,OAAO,EAAE,WAAW,cAAc,OAAO,MAAM,GAAG,MAAM,KAAK,GAAG,MAAM,GAAgB;AAC7F,QAAM,UAAU,MAAM;AAAA,IACpB,MAAO,MAAM,QAAQ,KAAK,IAAI,QAAQ,MAAM,QAAQ,YAAY,IAAI,eAAe,CAAC,KAAK,GAAG;AAAA,IAC5F,CAAC,OAAO,cAAc,KAAK,GAAG;AAAA,EAChC;AAEA,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA;AAAA,UAAC,gBAAgB;AAAA,UAAhB;AAAA,YACC,aAAU;AAAA,YACV,WAAU;AAAA,YAEV;AAAA,cAAC,gBAAgB;AAAA,cAAhB;AAAA,gBACC,aAAU;AAAA,gBACV,WAAU;AAAA;AAAA,YACZ;AAAA;AAAA,QACF;AAAA,QACC,MAAM,KAAK,EAAE,QAAQ,QAAQ,OAAO,GAAG,CAAC,GAAG,UAC1C;AAAA,UAAC,gBAAgB;AAAA,UAAhB;AAAA,YACC,aAAU;AAAA,YAGV,WAAU;AAAA;AAAA,UADL;AAAA,QAEP,CACD;AAAA;AAAA;AAAA,EACH;AAEJ;","names":[]}
@@ -1,9 +1,27 @@
1
1
  import * as React from 'react';
2
2
  import { ToasterProps } from 'sonner';
3
- export { ToasterProps } from 'sonner';
4
3
 
5
- /** Alias tránh đụng tên với composite `Toaster` của Tier 2 khi gom barrel. */
4
+ /**
5
+ * Toaster — bọc `sonner` và nối màu vào token popover của Comitor.
6
+ *
7
+ * Phụ thuộc `next-themes` (peer OPTIONAL) để đọc theme hiện tại; app nào không
8
+ * dùng next-themes thì đừng render component này — hoặc render `<Sonner>` trực tiếp.
9
+ *
10
+ * Đặt duy nhất MỘT `<Toaster />` ở gốc app; gọi `toast()` của `sonner` ở bất kỳ đâu.
11
+ * Bản có icon theo trạng thái (success/warning/info) là composite `Toaster` ở Tier 2.
12
+ */
13
+ /**
14
+ * Cửa DUY NHẤT của file này: `SonnerToaster` + `SonnerToasterProps`.
15
+ *
16
+ * Tên trần `Toaster`/`ToasterProps` cố ý KHÔNG xuất, dù chúng là tên tự nhiên ở đây: composite
17
+ * `Toaster`/`ToasterProps` (`composite/toast.tsx`) đã chiếm đúng hai tên đó ở entry `.`. Nếu để
18
+ * chúng xuất ra thì (a) `shell/index.ts` chỉ lấy bản `Sonner*` nên chúng là export CHẾT, và
19
+ * (b) ai thêm `export * from "./components/ui/sonner.js"` vào `src/index.ts` sẽ vỡ build ngay,
20
+ * còn thêm vào `shell/index.ts` thì im lặng dựng thêm một cửa `Toaster` thứ hai.
21
+ * Giữ hai toaster là quyết định có chủ ý (docs/quyet-dinh-tich-hop.md §31) — nhưng "hai toaster,
22
+ * hai tên" chỉ đúng khi tên trần không còn lối ra.
23
+ */
6
24
  type SonnerToasterProps = ToasterProps;
7
25
  declare const Toaster: ({ ...props }: ToasterProps) => React.JSX.Element;
8
26
 
9
- export { Toaster as SonnerToaster, type SonnerToasterProps, Toaster };
27
+ export { Toaster as SonnerToaster, type SonnerToasterProps };
@@ -19,7 +19,6 @@ const Toaster = ({ ...props }) => {
19
19
  );
20
20
  };
21
21
  export {
22
- Toaster as SonnerToaster,
23
- Toaster
22
+ Toaster as SonnerToaster
24
23
  };
25
24
  //# sourceMappingURL=sonner.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/ui/sonner.tsx"],"sourcesContent":["\"use client\";\n\nimport { useTheme } from \"next-themes\";\nimport type * as React from \"react\";\nimport { Toaster as Sonner, type ToasterProps } from \"sonner\";\n\n/**\n * Toaster — bọc `sonner` và nối màu vào token popover của Comitor.\n *\n * Phụ thuộc `next-themes` (peer OPTIONAL) để đọc theme hiện tại; app nào không\n * dùng next-themes thì đừng render component này — hoặc render `<Sonner>` trực tiếp.\n *\n * Đặt duy nhất MỘT `<Toaster />` ở gốc app; gọi `toast()` của `sonner` ở bất kỳ đâu.\n * Bản có icon theo trạng thái (success/warning/info) là composite `Toaster` ở Tier 2.\n */\n\nexport type { ToasterProps };\n/** Alias tránh đụng tên với composite `Toaster` của Tier 2 khi gom barrel. */\nexport type SonnerToasterProps = ToasterProps;\n\nconst Toaster = ({ ...props }: ToasterProps) => {\n const { theme = \"system\" } = useTheme();\n\n return (\n <Sonner\n theme={theme as ToasterProps[\"theme\"]}\n className=\"toaster group\"\n style={\n {\n \"--normal-bg\": \"var(--popover)\",\n \"--normal-text\": \"var(--popover-foreground)\",\n \"--normal-border\": \"var(--border)\"\n } as React.CSSProperties\n }\n {...props}\n />\n );\n};\n\nexport { Toaster, Toaster as SonnerToaster };\n"],"mappings":";AAwBI;AAtBJ,SAAS,gBAAgB;AAEzB,SAAS,WAAW,cAAiC;AAgBrD,MAAM,UAAU,CAAC,EAAE,GAAG,MAAM,MAAoB;AAC9C,QAAM,EAAE,QAAQ,SAAS,IAAI,SAAS;AAEtC,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,WAAU;AAAA,MACV,OACE;AAAA,QACE,eAAe;AAAA,QACf,iBAAiB;AAAA,QACjB,mBAAmB;AAAA,MACrB;AAAA,MAED,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/ui/sonner.tsx"],"sourcesContent":["\"use client\";\n\nimport { useTheme } from \"next-themes\";\nimport type * as React from \"react\";\nimport { Toaster as Sonner, type ToasterProps } from \"sonner\";\n\n/**\n * Toaster — bọc `sonner` và nối màu vào token popover của Comitor.\n *\n * Phụ thuộc `next-themes` (peer OPTIONAL) để đọc theme hiện tại; app nào không\n * dùng next-themes thì đừng render component này — hoặc render `<Sonner>` trực tiếp.\n *\n * Đặt duy nhất MỘT `<Toaster />` ở gốc app; gọi `toast()` của `sonner` ở bất kỳ đâu.\n * Bản có icon theo trạng thái (success/warning/info) là composite `Toaster` ở Tier 2.\n */\n\n/**\n * Cửa DUY NHẤT của file này: `SonnerToaster` + `SonnerToasterProps`.\n *\n * Tên trần `Toaster`/`ToasterProps` cố ý KHÔNG xuất, dù chúng là tên tự nhiên ở đây: composite\n * `Toaster`/`ToasterProps` (`composite/toast.tsx`) đã chiếm đúng hai tên đó ở entry `.`. Nếu để\n * chúng xuất ra thì (a) `shell/index.ts` chỉ lấy bản `Sonner*` nên chúng là export CHẾT, và\n * (b) ai thêm `export * from \"./components/ui/sonner.js\"` vào `src/index.ts` sẽ vỡ build ngay,\n * còn thêm vào `shell/index.ts` thì im lặng dựng thêm một cửa `Toaster` thứ hai.\n * Giữ hai toaster là quyết định có chủ ý (docs/quyet-dinh-tich-hop.md §31) — nhưng \"hai toaster,\n * hai tên\" chỉ đúng khi tên trần không còn lối ra.\n */\nexport type SonnerToasterProps = ToasterProps;\n\nconst Toaster = ({ ...props }: ToasterProps) => {\n const { theme = \"system\" } = useTheme();\n\n return (\n <Sonner\n theme={theme as ToasterProps[\"theme\"]}\n className=\"toaster group\"\n style={\n {\n \"--normal-bg\": \"var(--popover)\",\n \"--normal-text\": \"var(--popover-foreground)\",\n \"--normal-border\": \"var(--border)\"\n } as React.CSSProperties\n }\n {...props}\n />\n );\n};\n\nexport { Toaster as SonnerToaster };\n"],"mappings":";AAiCI;AA/BJ,SAAS,gBAAgB;AAEzB,SAAS,WAAW,cAAiC;AAyBrD,MAAM,UAAU,CAAC,EAAE,GAAG,MAAM,MAAoB;AAC9C,QAAM,EAAE,QAAQ,SAAS,IAAI,SAAS;AAEtC,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,WAAU;AAAA,MACV,OACE;AAAA,QACE,eAAe;AAAA,QACf,iBAAiB;AAAA,QACjB,mBAAmB;AAAA,MACrB;AAAA,MAED,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
@@ -20,7 +20,7 @@ function Switch({ className, size = "md", label, description, id, ...props }) {
20
20
  "data-slot": "switch",
21
21
  id: switchId,
22
22
  className: cn(
23
- "peer inline-flex shrink-0 items-center rounded-full bg-ash-300 dark:bg-ash-700 data-[state=checked]:bg-primary",
23
+ "peer inline-flex shrink-0 items-center rounded-full bg-control-edge data-[state=checked]:bg-control-on",
24
24
  "transition-colors duration-150 ease-out",
25
25
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background",
26
26
  "disabled:cursor-not-allowed disabled:opacity-50",
@@ -33,7 +33,7 @@ function Switch({ className, size = "md", label, description, id, ...props }) {
33
33
  {
34
34
  "data-slot": "switch-thumb",
35
35
  className: cn(
36
- "pointer-events-none block translate-x-0.5 rounded-full bg-white shadow-sm ring-0",
36
+ "pointer-events-none block translate-x-0.5 rounded-full bg-control-knob shadow-sm ring-0",
37
37
  "transition-transform duration-150 ease-out",
38
38
  thumbSizes[size]
39
39
  )