@vestow/vue 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (491) hide show
  1. package/README.md +233 -0
  2. package/index.ts +8 -0
  3. package/package.json +68 -0
  4. package/src/components/accordion/Accordion.vue +45 -0
  5. package/src/components/accordion/AccordionContent.vue +31 -0
  6. package/src/components/accordion/AccordionItem.vue +26 -0
  7. package/src/components/accordion/AccordionTrigger.vue +39 -0
  8. package/src/components/accordion/index.ts +18 -0
  9. package/src/components/alert/Alert.vue +17 -0
  10. package/src/components/alert/AlertDescription.vue +12 -0
  11. package/src/components/alert/AlertTitle.vue +12 -0
  12. package/src/components/alert/index.ts +5 -0
  13. package/src/components/avatar/Avatar.vue +32 -0
  14. package/src/components/avatar/AvatarFallback.vue +23 -0
  15. package/src/components/avatar/AvatarImage.vue +18 -0
  16. package/src/components/avatar/index.ts +3 -0
  17. package/src/components/badge/Badge.vue +25 -0
  18. package/src/components/badge/index.ts +3 -0
  19. package/src/components/breadcrumb/Breadcrumb.vue +14 -0
  20. package/src/components/breadcrumb/BreadcrumbItem.vue +12 -0
  21. package/src/components/breadcrumb/BreadcrumbLink.vue +19 -0
  22. package/src/components/breadcrumb/BreadcrumbPage.vue +12 -0
  23. package/src/components/breadcrumb/BreadcrumbSeparator.vue +15 -0
  24. package/src/components/breadcrumb/index.ts +5 -0
  25. package/src/components/button/Button.vue +55 -0
  26. package/src/components/button/index.ts +3 -0
  27. package/src/components/button-group/ButtonGroup.vue +29 -0
  28. package/src/components/button-group/ButtonGroupSeparator.vue +30 -0
  29. package/src/components/button-group/ButtonGroupText.vue +29 -0
  30. package/src/components/button-group/SplitButton.vue +103 -0
  31. package/src/components/button-group/SplitButtonItems.vue +50 -0
  32. package/src/components/button-group/index.ts +34 -0
  33. package/src/components/calendar/Calendar.vue +120 -0
  34. package/src/components/calendar/RangeCalendar.vue +127 -0
  35. package/src/components/calendar/index.ts +2 -0
  36. package/src/components/card/Card.vue +12 -0
  37. package/src/components/card/CardAction.vue +12 -0
  38. package/src/components/card/CardContent.vue +12 -0
  39. package/src/components/card/CardDescription.vue +12 -0
  40. package/src/components/card/CardFooter.vue +12 -0
  41. package/src/components/card/CardHeader.vue +15 -0
  42. package/src/components/card/CardTitle.vue +12 -0
  43. package/src/components/card/index.ts +7 -0
  44. package/src/components/carousel/Carousel.vue +164 -0
  45. package/src/components/carousel/CarouselContent.vue +43 -0
  46. package/src/components/carousel/CarouselDots.vue +42 -0
  47. package/src/components/carousel/CarouselItem.vue +58 -0
  48. package/src/components/carousel/CarouselNext.vue +45 -0
  49. package/src/components/carousel/CarouselPrevious.vue +45 -0
  50. package/src/components/carousel/index.ts +44 -0
  51. package/src/components/cascade-select/CascadeSelect.vue +314 -0
  52. package/src/components/cascade-select/index.ts +11 -0
  53. package/src/components/chart/AreaChart.vue +201 -0
  54. package/src/components/chart/BarChart.vue +389 -0
  55. package/src/components/chart/ChartContainer.vue +127 -0
  56. package/src/components/chart/ChartCrosshair.vue +92 -0
  57. package/src/components/chart/ChartLegend.vue +63 -0
  58. package/src/components/chart/ChartTooltipCard.vue +49 -0
  59. package/src/components/chart/LineChart.vue +214 -0
  60. package/src/components/chart/PieChart.vue +280 -0
  61. package/src/components/chart/RadarChart.vue +291 -0
  62. package/src/components/chart/RadialChart.vue +354 -0
  63. package/src/components/chart/index.ts +30 -0
  64. package/src/components/chart/polar.ts +5 -0
  65. package/src/components/chart/tooltip.ts +5 -0
  66. package/src/components/chart/types.ts +52 -0
  67. package/src/components/chart/useChartSeries.ts +138 -0
  68. package/src/components/chart/xy-label.ts +5 -0
  69. package/src/components/checkbox/Checkbox.vue +81 -0
  70. package/src/components/checkbox/CheckboxField.vue +129 -0
  71. package/src/components/checkbox/CheckboxGroup.vue +62 -0
  72. package/src/components/checkbox/index.ts +21 -0
  73. package/src/components/chip/Chip.vue +73 -0
  74. package/src/components/chip/index.ts +3 -0
  75. package/src/components/code-block/CodeBlock.vue +100 -0
  76. package/src/components/code-block/index.ts +4 -0
  77. package/src/components/collapsible/Collapsible.vue +15 -0
  78. package/src/components/collapsible/CollapsibleContent.vue +26 -0
  79. package/src/components/collapsible/CollapsibleTrigger.vue +12 -0
  80. package/src/components/collapsible/index.ts +3 -0
  81. package/src/components/combobox/Combobox.vue +23 -0
  82. package/src/components/combobox/ComboboxAnchor.vue +20 -0
  83. package/src/components/combobox/ComboboxEmpty.vue +21 -0
  84. package/src/components/combobox/ComboboxGroup.vue +23 -0
  85. package/src/components/combobox/ComboboxInput.vue +32 -0
  86. package/src/components/combobox/ComboboxItem.vue +38 -0
  87. package/src/components/combobox/ComboboxList.vue +43 -0
  88. package/src/components/combobox/ComboboxSeparator.vue +18 -0
  89. package/src/components/combobox/ComboboxTrigger.vue +12 -0
  90. package/src/components/combobox/index.ts +9 -0
  91. package/src/components/command/Command.vue +54 -0
  92. package/src/components/command/CommandDialog.vue +41 -0
  93. package/src/components/command/CommandEmpty.vue +19 -0
  94. package/src/components/command/CommandGroup.vue +28 -0
  95. package/src/components/command/CommandInput.vue +25 -0
  96. package/src/components/command/CommandItem.vue +40 -0
  97. package/src/components/command/CommandList.vue +13 -0
  98. package/src/components/command/CommandSeparator.vue +11 -0
  99. package/src/components/command/CommandShortcut.vue +13 -0
  100. package/src/components/command/index.ts +34 -0
  101. package/src/components/command-block/CommandBlock.vue +90 -0
  102. package/src/components/command-block/index.ts +2 -0
  103. package/src/components/confirm-dialog/ConfirmDialog.vue +37 -0
  104. package/src/components/confirm-dialog/index.ts +1 -0
  105. package/src/components/confirm-popup/ConfirmPopup.vue +123 -0
  106. package/src/components/confirm-popup/index.ts +1 -0
  107. package/src/components/context-menu/ContextMenu.vue +15 -0
  108. package/src/components/context-menu/ContextMenuCheckboxItem.vue +30 -0
  109. package/src/components/context-menu/ContextMenuContent.vue +36 -0
  110. package/src/components/context-menu/ContextMenuGroup.vue +12 -0
  111. package/src/components/context-menu/ContextMenuItem.vue +32 -0
  112. package/src/components/context-menu/ContextMenuLabel.vue +21 -0
  113. package/src/components/context-menu/ContextMenuRadioGroup.vue +15 -0
  114. package/src/components/context-menu/ContextMenuRadioItem.vue +30 -0
  115. package/src/components/context-menu/ContextMenuSeparator.vue +19 -0
  116. package/src/components/context-menu/ContextMenuShortcut.vue +13 -0
  117. package/src/components/context-menu/ContextMenuSub.vue +15 -0
  118. package/src/components/context-menu/ContextMenuSubContent.vue +26 -0
  119. package/src/components/context-menu/ContextMenuSubTrigger.vue +26 -0
  120. package/src/components/context-menu/ContextMenuTrigger.vue +12 -0
  121. package/src/components/context-menu/index.ts +14 -0
  122. package/src/components/data-table/DataTable.vue +458 -0
  123. package/src/components/data-table/DataTableFacetedFilter.vue +97 -0
  124. package/src/components/data-table/DataTablePagination.vue +103 -0
  125. package/src/components/data-table/DataTableViewOptions.vue +69 -0
  126. package/src/components/data-table/features.ts +87 -0
  127. package/src/components/data-table/index.ts +27 -0
  128. package/src/components/data-table/pagination-adapter.ts +67 -0
  129. package/src/components/data-table/selection-column.ts +48 -0
  130. package/src/components/data-table/types.ts +36 -0
  131. package/src/components/data-view/DataView.vue +136 -0
  132. package/src/components/data-view/index.ts +1 -0
  133. package/src/components/date-picker/DatePicker.vue +117 -0
  134. package/src/components/date-picker/DateRangePicker.vue +122 -0
  135. package/src/components/date-picker/index.ts +2 -0
  136. package/src/components/deferred/Deferred.vue +80 -0
  137. package/src/components/deferred/index.ts +1 -0
  138. package/src/components/dialog/Dialog.vue +15 -0
  139. package/src/components/dialog/DialogClose.vue +12 -0
  140. package/src/components/dialog/DialogContent.vue +46 -0
  141. package/src/components/dialog/DialogDescription.vue +19 -0
  142. package/src/components/dialog/DialogFooter.vue +12 -0
  143. package/src/components/dialog/DialogHeader.vue +12 -0
  144. package/src/components/dialog/DialogTitle.vue +19 -0
  145. package/src/components/dialog/DialogTrigger.vue +12 -0
  146. package/src/components/dialog/index.ts +10 -0
  147. package/src/components/doc-example/DocExample.vue +41 -0
  148. package/src/components/doc-example/index.ts +1 -0
  149. package/src/components/doc-text/DocText.vue +64 -0
  150. package/src/components/doc-text/index.ts +1 -0
  151. package/src/components/dropdown-menu/DropdownMenu.vue +15 -0
  152. package/src/components/dropdown-menu/DropdownMenuCheckboxItem.vue +33 -0
  153. package/src/components/dropdown-menu/DropdownMenuContent.vue +36 -0
  154. package/src/components/dropdown-menu/DropdownMenuGroup.vue +12 -0
  155. package/src/components/dropdown-menu/DropdownMenuItem.vue +32 -0
  156. package/src/components/dropdown-menu/DropdownMenuLabel.vue +24 -0
  157. package/src/components/dropdown-menu/DropdownMenuRadioGroup.vue +15 -0
  158. package/src/components/dropdown-menu/DropdownMenuRadioItem.vue +33 -0
  159. package/src/components/dropdown-menu/DropdownMenuSeparator.vue +19 -0
  160. package/src/components/dropdown-menu/DropdownMenuShortcut.vue +13 -0
  161. package/src/components/dropdown-menu/DropdownMenuSub.vue +15 -0
  162. package/src/components/dropdown-menu/DropdownMenuSubContent.vue +29 -0
  163. package/src/components/dropdown-menu/DropdownMenuSubTrigger.vue +26 -0
  164. package/src/components/dropdown-menu/DropdownMenuTrigger.vue +12 -0
  165. package/src/components/dropdown-menu/index.ts +14 -0
  166. package/src/components/editable/Editable.vue +41 -0
  167. package/src/components/editable/EditableArea.vue +22 -0
  168. package/src/components/editable/EditableCancelTrigger.vue +40 -0
  169. package/src/components/editable/EditableEditTrigger.vue +40 -0
  170. package/src/components/editable/EditableInput.vue +28 -0
  171. package/src/components/editable/EditablePreview.vue +34 -0
  172. package/src/components/editable/EditableSubmitTrigger.vue +40 -0
  173. package/src/components/editable/index.ts +17 -0
  174. package/src/components/empty/Empty.vue +17 -0
  175. package/src/components/empty/EmptyActions.vue +14 -0
  176. package/src/components/empty/EmptyDescription.vue +12 -0
  177. package/src/components/empty/EmptyMedia.vue +16 -0
  178. package/src/components/empty/EmptyTitle.vue +17 -0
  179. package/src/components/empty/index.ts +7 -0
  180. package/src/components/field/Field.vue +22 -0
  181. package/src/components/field/FieldDescription.vue +20 -0
  182. package/src/components/field/FieldGroup.vue +22 -0
  183. package/src/components/field/FieldLabel.vue +27 -0
  184. package/src/components/field/FieldSeparator.vue +28 -0
  185. package/src/components/field/index.ts +7 -0
  186. package/src/components/fieldset/Fieldset.vue +126 -0
  187. package/src/components/fieldset/index.ts +1 -0
  188. package/src/components/file-upload/FileUpload.vue +149 -0
  189. package/src/components/file-upload/index.ts +8 -0
  190. package/src/components/float-label/FloatLabel.vue +26 -0
  191. package/src/components/float-label/index.ts +3 -0
  192. package/src/components/form/Form.vue +126 -0
  193. package/src/components/form/FormDescription.vue +12 -0
  194. package/src/components/form/FormField.vue +127 -0
  195. package/src/components/form/FormItem.vue +12 -0
  196. package/src/components/form/FormLabel.vue +14 -0
  197. package/src/components/form/FormMessage.vue +31 -0
  198. package/src/components/form/context.ts +54 -0
  199. package/src/components/form/errors.ts +6 -0
  200. package/src/components/form/index.ts +7 -0
  201. package/src/components/form/types.ts +55 -0
  202. package/src/components/hover-card/HoverCard.vue +27 -0
  203. package/src/components/hover-card/HoverCardContent.vue +36 -0
  204. package/src/components/hover-card/HoverCardTrigger.vue +12 -0
  205. package/src/components/hover-card/index.ts +3 -0
  206. package/src/components/ifta-label/IftaLabel.vue +26 -0
  207. package/src/components/ifta-label/index.ts +1 -0
  208. package/src/components/index.ts +89 -0
  209. package/src/components/input/Input.vue +87 -0
  210. package/src/components/input/index.ts +1 -0
  211. package/src/components/input-color/ColorArea.vue +36 -0
  212. package/src/components/input-color/ColorField.vue +32 -0
  213. package/src/components/input-color/ColorSlider.vue +31 -0
  214. package/src/components/input-color/InputColor.vue +65 -0
  215. package/src/components/input-color/index.ts +16 -0
  216. package/src/components/input-group/InputGroup.vue +26 -0
  217. package/src/components/input-group/InputGroupAddon.vue +32 -0
  218. package/src/components/input-group/InputGroupInput.vue +33 -0
  219. package/src/components/input-group/index.ts +17 -0
  220. package/src/components/input-number/InputNumber.vue +82 -0
  221. package/src/components/input-number/index.ts +1 -0
  222. package/src/components/input-otp/InputOtp.vue +186 -0
  223. package/src/components/input-otp/index.ts +1 -0
  224. package/src/components/input-password/InputPassword.vue +116 -0
  225. package/src/components/input-password/index.ts +5 -0
  226. package/src/components/input-tags/InputTags.vue +38 -0
  227. package/src/components/input-tags/InputTagsClear.vue +39 -0
  228. package/src/components/input-tags/InputTagsInput.vue +27 -0
  229. package/src/components/input-tags/InputTagsItem.vue +40 -0
  230. package/src/components/input-tags/InputTagsItemDelete.vue +32 -0
  231. package/src/components/input-tags/InputTagsItemText.vue +18 -0
  232. package/src/components/input-tags/index.ts +16 -0
  233. package/src/components/kbd/Kbd.vue +17 -0
  234. package/src/components/kbd/KbdGroup.vue +15 -0
  235. package/src/components/kbd/index.ts +4 -0
  236. package/src/components/label/Label.vue +23 -0
  237. package/src/components/label/index.ts +1 -0
  238. package/src/components/listbox/Listbox.vue +42 -0
  239. package/src/components/listbox/ListboxContent.vue +22 -0
  240. package/src/components/listbox/ListboxFilter.vue +34 -0
  241. package/src/components/listbox/ListboxGroup.vue +31 -0
  242. package/src/components/listbox/ListboxItem.vue +46 -0
  243. package/src/components/listbox/index.ts +19 -0
  244. package/src/components/mask/Mask.vue +205 -0
  245. package/src/components/mask/index.ts +15 -0
  246. package/src/components/menubar/Menubar.vue +26 -0
  247. package/src/components/menubar/MenubarCheckboxItem.vue +30 -0
  248. package/src/components/menubar/MenubarContent.vue +52 -0
  249. package/src/components/menubar/MenubarGroup.vue +12 -0
  250. package/src/components/menubar/MenubarItem.vue +32 -0
  251. package/src/components/menubar/MenubarLabel.vue +21 -0
  252. package/src/components/menubar/MenubarMenu.vue +12 -0
  253. package/src/components/menubar/MenubarRadioGroup.vue +15 -0
  254. package/src/components/menubar/MenubarRadioItem.vue +30 -0
  255. package/src/components/menubar/MenubarSeparator.vue +12 -0
  256. package/src/components/menubar/MenubarShortcut.vue +13 -0
  257. package/src/components/menubar/MenubarSub.vue +15 -0
  258. package/src/components/menubar/MenubarSubContent.vue +26 -0
  259. package/src/components/menubar/MenubarSubTrigger.vue +26 -0
  260. package/src/components/menubar/MenubarTrigger.vue +30 -0
  261. package/src/components/menubar/index.ts +29 -0
  262. package/src/components/navigation-menu/NavigationMenu.vue +43 -0
  263. package/src/components/navigation-menu/NavigationMenuContent.vue +63 -0
  264. package/src/components/navigation-menu/NavigationMenuIndicator.vue +31 -0
  265. package/src/components/navigation-menu/NavigationMenuItem.vue +20 -0
  266. package/src/components/navigation-menu/NavigationMenuLink.vue +34 -0
  267. package/src/components/navigation-menu/NavigationMenuList.vue +23 -0
  268. package/src/components/navigation-menu/NavigationMenuTrigger.vue +38 -0
  269. package/src/components/navigation-menu/NavigationMenuViewport.vue +34 -0
  270. package/src/components/navigation-menu/index.ts +18 -0
  271. package/src/components/order-list/OrderList.vue +168 -0
  272. package/src/components/order-list/SelectableList.vue +200 -0
  273. package/src/components/order-list/index.ts +1 -0
  274. package/src/components/pagination/Pagination.vue +133 -0
  275. package/src/components/pagination/index.ts +1 -0
  276. package/src/components/panel/Panel.vue +69 -0
  277. package/src/components/panel/index.ts +1 -0
  278. package/src/components/pick-list/PickList.vue +211 -0
  279. package/src/components/pick-list/index.ts +1 -0
  280. package/src/components/popover/Popover.vue +15 -0
  281. package/src/components/popover/PopoverAnchor.vue +12 -0
  282. package/src/components/popover/PopoverContent.vue +39 -0
  283. package/src/components/popover/PopoverTrigger.vue +12 -0
  284. package/src/components/popover/index.ts +4 -0
  285. package/src/components/progress/Progress.vue +39 -0
  286. package/src/components/progress/index.ts +1 -0
  287. package/src/components/props-table/PropsTable.vue +62 -0
  288. package/src/components/props-table/index.ts +2 -0
  289. package/src/components/radio-group/RadioGroup.vue +52 -0
  290. package/src/components/radio-group/RadioGroupItem.vue +80 -0
  291. package/src/components/radio-group/RadioGroupItemField.vue +128 -0
  292. package/src/components/radio-group/index.ts +21 -0
  293. package/src/components/rating/Rating.vue +70 -0
  294. package/src/components/rating/RatingItem.vue +26 -0
  295. package/src/components/rating/RatingItemIndicator.vue +28 -0
  296. package/src/components/rating/RatingStar.vue +16 -0
  297. package/src/components/rating/index.ts +13 -0
  298. package/src/components/scroll-area/ScrollArea.vue +52 -0
  299. package/src/components/scroll-area/index.ts +1 -0
  300. package/src/components/search-input/SearchInput.vue +46 -0
  301. package/src/components/search-input/index.ts +1 -0
  302. package/src/components/select/Select.vue +15 -0
  303. package/src/components/select/SelectContent.vue +67 -0
  304. package/src/components/select/SelectGroup.vue +20 -0
  305. package/src/components/select/SelectItem.vue +59 -0
  306. package/src/components/select/SelectLabel.vue +20 -0
  307. package/src/components/select/SelectSeparator.vue +18 -0
  308. package/src/components/select/SelectTrigger.vue +101 -0
  309. package/src/components/select/SelectValue.vue +46 -0
  310. package/src/components/select/index.ts +8 -0
  311. package/src/components/separator/Separator.vue +25 -0
  312. package/src/components/separator/index.ts +1 -0
  313. package/src/components/sheet/Sheet.vue +15 -0
  314. package/src/components/sheet/SheetClose.vue +12 -0
  315. package/src/components/sheet/SheetContent.vue +45 -0
  316. package/src/components/sheet/SheetDescription.vue +19 -0
  317. package/src/components/sheet/SheetFooter.vue +12 -0
  318. package/src/components/sheet/SheetHeader.vue +12 -0
  319. package/src/components/sheet/SheetTitle.vue +19 -0
  320. package/src/components/sheet/SheetTrigger.vue +12 -0
  321. package/src/components/sheet/index.ts +10 -0
  322. package/src/components/sidebar/Sidebar.vue +108 -0
  323. package/src/components/sidebar/SidebarContent.vue +19 -0
  324. package/src/components/sidebar/SidebarFooter.vue +19 -0
  325. package/src/components/sidebar/SidebarGroup.vue +19 -0
  326. package/src/components/sidebar/SidebarGroupAction.vue +28 -0
  327. package/src/components/sidebar/SidebarGroupContent.vue +19 -0
  328. package/src/components/sidebar/SidebarGroupLabel.vue +25 -0
  329. package/src/components/sidebar/SidebarHeader.vue +19 -0
  330. package/src/components/sidebar/SidebarInput.vue +15 -0
  331. package/src/components/sidebar/SidebarInset.vue +22 -0
  332. package/src/components/sidebar/SidebarMenu.vue +19 -0
  333. package/src/components/sidebar/SidebarMenuAction.vue +41 -0
  334. package/src/components/sidebar/SidebarMenuBadge.vue +24 -0
  335. package/src/components/sidebar/SidebarMenuButton.vue +94 -0
  336. package/src/components/sidebar/SidebarMenuItem.vue +19 -0
  337. package/src/components/sidebar/SidebarMenuSkeleton.vue +36 -0
  338. package/src/components/sidebar/SidebarMenuSub.vue +22 -0
  339. package/src/components/sidebar/SidebarMenuSubButton.vue +39 -0
  340. package/src/components/sidebar/SidebarMenuSubItem.vue +19 -0
  341. package/src/components/sidebar/SidebarProvider.vue +114 -0
  342. package/src/components/sidebar/SidebarRail.vue +37 -0
  343. package/src/components/sidebar/SidebarSeparator.vue +15 -0
  344. package/src/components/sidebar/SidebarTrigger.vue +27 -0
  345. package/src/components/sidebar/context.ts +39 -0
  346. package/src/components/sidebar/index.ts +34 -0
  347. package/src/components/skeleton/Skeleton.vue +10 -0
  348. package/src/components/skeleton/index.ts +1 -0
  349. package/src/components/skip-link/SkipLink.vue +45 -0
  350. package/src/components/skip-link/index.ts +1 -0
  351. package/src/components/slider/Slider.vue +57 -0
  352. package/src/components/slider/index.ts +3 -0
  353. package/src/components/speed-dial/SpeedDial.vue +362 -0
  354. package/src/components/speed-dial/index.ts +28 -0
  355. package/src/components/spinner/Spinner.vue +20 -0
  356. package/src/components/spinner/index.ts +1 -0
  357. package/src/components/splitter/Splitter.vue +24 -0
  358. package/src/components/splitter/SplitterHandle.vue +51 -0
  359. package/src/components/splitter/SplitterPanel.vue +30 -0
  360. package/src/components/splitter/index.ts +14 -0
  361. package/src/components/stepper/Stepper.vue +34 -0
  362. package/src/components/stepper/StepperItem.vue +113 -0
  363. package/src/components/stepper/index.ts +2 -0
  364. package/src/components/switch/Switch.vue +35 -0
  365. package/src/components/switch/index.ts +1 -0
  366. package/src/components/table/Table.vue +24 -0
  367. package/src/components/table/TableBody.vue +12 -0
  368. package/src/components/table/TableCell.vue +12 -0
  369. package/src/components/table/TableEmpty.vue +20 -0
  370. package/src/components/table/TableHead.vue +17 -0
  371. package/src/components/table/TableHeader.vue +12 -0
  372. package/src/components/table/TableRow.vue +19 -0
  373. package/src/components/table/index.ts +7 -0
  374. package/src/components/tabs/Tabs.vue +23 -0
  375. package/src/components/tabs/TabsContent.vue +23 -0
  376. package/src/components/tabs/TabsList.vue +26 -0
  377. package/src/components/tabs/TabsTrigger.vue +30 -0
  378. package/src/components/tabs/index.ts +4 -0
  379. package/src/components/textarea/Textarea.vue +25 -0
  380. package/src/components/textarea/index.ts +1 -0
  381. package/src/components/theme-controls/LocaleMenu.vue +95 -0
  382. package/src/components/theme-controls/PaletteMenu.vue +155 -0
  383. package/src/components/theme-controls/ThemeToggle.vue +21 -0
  384. package/src/components/theme-controls/index.ts +15 -0
  385. package/src/components/timeline/Timeline.vue +20 -0
  386. package/src/components/timeline/TimelineItem.vue +68 -0
  387. package/src/components/timeline/index.ts +15 -0
  388. package/src/components/toast/Toaster.vue +62 -0
  389. package/src/components/toast/index.ts +4 -0
  390. package/src/components/toast/toast.ts +57 -0
  391. package/src/components/toggle/Toggle.vue +32 -0
  392. package/src/components/toggle/ToggleGroup.vue +56 -0
  393. package/src/components/toggle/ToggleGroupItem.vue +33 -0
  394. package/src/components/toggle/index.ts +18 -0
  395. package/src/components/token-table/TokenTable.vue +67 -0
  396. package/src/components/token-table/index.ts +2 -0
  397. package/src/components/toolbar/Toolbar.vue +34 -0
  398. package/src/components/toolbar/ToolbarButton.vue +33 -0
  399. package/src/components/toolbar/ToolbarSeparator.vue +21 -0
  400. package/src/components/toolbar/index.ts +3 -0
  401. package/src/components/tooltip/Tooltip.vue +15 -0
  402. package/src/components/tooltip/TooltipContent.vue +38 -0
  403. package/src/components/tooltip/TooltipProvider.vue +16 -0
  404. package/src/components/tooltip/TooltipTrigger.vue +12 -0
  405. package/src/components/tooltip/index.ts +4 -0
  406. package/src/components/tree/Tree.vue +53 -0
  407. package/src/components/tree/TreeItem.vue +59 -0
  408. package/src/components/tree/index.ts +21 -0
  409. package/src/components/tree-select/TreeSelect.vue +141 -0
  410. package/src/components/tree-select/index.ts +1 -0
  411. package/src/components/tree-table/TreeTable.vue +302 -0
  412. package/src/components/tree-table/features.ts +49 -0
  413. package/src/components/tree-table/index.ts +3 -0
  414. package/src/components/video-player/VideoPlayer.vue +472 -0
  415. package/src/components/video-player/index.ts +20 -0
  416. package/src/composables/index.ts +15 -0
  417. package/src/composables/useBase.ts +32 -0
  418. package/src/composables/useChartPalette.ts +33 -0
  419. package/src/composables/useColorMode.ts +29 -0
  420. package/src/composables/useConfirm.ts +81 -0
  421. package/src/composables/useDelayedFlag.ts +62 -0
  422. package/src/composables/useI18n.ts +107 -0
  423. package/src/composables/useIsMobile.ts +34 -0
  424. package/src/composables/useMenuAccent.ts +32 -0
  425. package/src/composables/useMenuAppearance.ts +31 -0
  426. package/src/composables/useMenuColor.ts +31 -0
  427. package/src/composables/usePalette.ts +30 -0
  428. package/src/composables/useStyle.ts +35 -0
  429. package/src/composables/useSurface.ts +30 -0
  430. package/src/composables/useTableQuery.ts +73 -0
  431. package/src/composables/useThemeController.ts +48 -0
  432. package/src/i18n/README.md +68 -0
  433. package/src/i18n/controller.ts +233 -0
  434. package/src/i18n/dictionary.ts +43 -0
  435. package/src/i18n/index.ts +34 -0
  436. package/src/i18n/registry.ts +64 -0
  437. package/src/i18n/ui/en/ui.json +114 -0
  438. package/src/i18n/ui/en/zod.json +16 -0
  439. package/src/i18n/ui/index.ts +48 -0
  440. package/src/i18n/ui/vi/ui.json +114 -0
  441. package/src/i18n/ui/vi/zod.json +16 -0
  442. package/src/lib/date.ts +24 -0
  443. package/src/lib/focus.ts +12 -0
  444. package/src/lib/icons.ts +345 -0
  445. package/src/lib/menu-styles.ts +16 -0
  446. package/src/lib/menu.ts +20 -0
  447. package/src/lib/platform.ts +5 -0
  448. package/src/lib/types.ts +6 -0
  449. package/src/lib/utils.ts +9 -0
  450. package/src/lib/zod-messages.ts +127 -0
  451. package/src/styles/chart.css +30 -0
  452. package/src/styles/index.css +18 -0
  453. package/src/variants/README.md +55 -0
  454. package/src/variants/components/accordion.ts +71 -0
  455. package/src/variants/components/alert.ts +20 -0
  456. package/src/variants/components/badge.ts +76 -0
  457. package/src/variants/components/button-group.ts +136 -0
  458. package/src/variants/components/button.ts +160 -0
  459. package/src/variants/components/checkbox.ts +67 -0
  460. package/src/variants/components/chip.ts +51 -0
  461. package/src/variants/components/code-block.ts +22 -0
  462. package/src/variants/components/dialog.ts +28 -0
  463. package/src/variants/components/empty.ts +60 -0
  464. package/src/variants/components/field.ts +35 -0
  465. package/src/variants/components/float-label.ts +95 -0
  466. package/src/variants/components/input-group.ts +73 -0
  467. package/src/variants/components/input-password.ts +68 -0
  468. package/src/variants/components/kbd.ts +47 -0
  469. package/src/variants/components/picker-trigger.ts +30 -0
  470. package/src/variants/components/radio-group.ts +63 -0
  471. package/src/variants/components/sheet.ts +23 -0
  472. package/src/variants/components/sidebar.ts +33 -0
  473. package/src/variants/components/slider.ts +49 -0
  474. package/src/variants/components/timeline.ts +45 -0
  475. package/src/variants/components/toggle.ts +58 -0
  476. package/src/variants/index.ts +35 -0
  477. package/src/variants/lib/chart/polar.ts +139 -0
  478. package/src/variants/lib/chart/tooltip.ts +101 -0
  479. package/src/variants/lib/chart/types.ts +172 -0
  480. package/src/variants/lib/chart/xy-label.ts +42 -0
  481. package/src/variants/lib/focus.ts +66 -0
  482. package/src/variants/lib/form-errors.ts +59 -0
  483. package/src/variants/lib/highlight.ts +548 -0
  484. package/src/variants/lib/list-move.ts +63 -0
  485. package/src/variants/lib/mask.ts +106 -0
  486. package/src/variants/lib/media.ts +56 -0
  487. package/src/variants/lib/menu-styles.ts +71 -0
  488. package/src/variants/lib/platform.ts +68 -0
  489. package/src/variants/lib/tree.ts +119 -0
  490. package/src/variants/lib/upload.ts +58 -0
  491. package/src/variants/lib/utils.ts +11 -0
@@ -0,0 +1,458 @@
1
+ <script setup lang="ts" generic="TData extends RowData">
2
+ import type {
3
+ ColumnFiltersState,
4
+ ColumnVisibilityState,
5
+ PaginationState,
6
+ RowData,
7
+ RowSelectionState,
8
+ SortingState,
9
+ Updater,
10
+ } from '@tanstack/vue-table'
11
+ import type { ClassProps } from '../../lib/types'
12
+ import type { DataTableColumn, DataTableColumnMeta } from './features'
13
+ import type { TableQuery } from './types'
14
+ import { FlexRender, useTable } from '@tanstack/vue-table'
15
+ import { useVirtualizer } from '@tanstack/vue-virtual'
16
+ import { computed, ref, toRef, useTemplateRef } from 'vue'
17
+ import { useDelayedFlag } from '../../composables/useDelayedFlag'
18
+ import { ArrowDown, ArrowUp, ChevronsUpDown } from '../../lib/icons'
19
+ import { cn } from '../../lib/utils'
20
+ import Spinner from '../spinner/Spinner.vue'
21
+ import TableEmpty from '../table/TableEmpty.vue'
22
+ import DataTablePagination from './DataTablePagination.vue'
23
+ import DataTableViewOptions from './DataTableViewOptions.vue'
24
+ import { dataTableFeatures } from './features'
25
+
26
+ const props = withDefaults(
27
+ defineProps<ClassProps & {
28
+ columns: DataTableColumn<TData>[]
29
+ data: TData[]
30
+ loading?: boolean
31
+ /**
32
+ * true = backend lo sort/filter/paginate. DataTable chỉ phát event,
33
+ * không tự cắt dữ liệu. Khi đó `totalRecords` là bắt buộc.
34
+ */
35
+ serverSide?: boolean
36
+ totalRecords?: number
37
+ pageSize?: number
38
+ pageSizeOptions?: number[]
39
+ /**
40
+ * Trang và sort lúc mở, dùng để khôi phục từ URL.
41
+ *
42
+ * Đọc **một lần** ở setup, cố ý không phải `v-model`: bảng tự giữ trạng
43
+ * thái của nó rồi emit ra. Biến hai chiều thì mỗi lần đổi trang sẽ đi một
44
+ * vòng qua URL rồi quay lại, và bất kỳ độ trễ nào trên đường đó cũng thành
45
+ * một nhịp giật ở phân trang.
46
+ */
47
+ initialPageIndex?: number
48
+ initialSorting?: SortingState
49
+ enableRowSelection?: boolean
50
+ enableColumnVisibility?: boolean
51
+ enablePagination?: boolean
52
+ stickyHeader?: boolean
53
+ /**
54
+ * Đóng băng N cột đầu khi cuộn NGANG — cặp đôi của `stickyHeader` cho trục
55
+ * còn lại. Cuộn dọc mà mất tên cột, hay cuộn ngang mà mất tên dòng, đều là
56
+ * cùng một chuyện: mất mất cái mốc để biết ô đang nhìn là ô nào.
57
+ *
58
+ * Cột bị đóng băng **bắt buộc khai `size`**: vị trí `left` của cột thứ hai
59
+ * là tổng bề rộng các cột trước nó, mà bề rộng đó chỉ biết được nếu người
60
+ * viết khai. Cột không khai sẽ bị bỏ qua cùng mọi cột sau nó.
61
+ */
62
+ freezeColumns?: number
63
+ /**
64
+ * 'fixed' = table-layout: fixed. Bắt buộc khi muốn một cột co giãn còn
65
+ * các cột khác có độ rộng cố định, và khi cần truncate nội dung trong ô.
66
+ */
67
+ layout?: 'auto' | 'fixed'
68
+ emptyMessage?: string
69
+ getRowId?: (row: TData, index: number) => string
70
+ /** Ảo hoá dòng — dùng khi 1 trang có hàng nghìn dòng. */
71
+ virtual?: boolean
72
+ estimateRowHeight?: number
73
+ /** Bắt buộc khi virtual = true, vì virtualizer cần 1 vùng cuộn có chiều cao. */
74
+ maxHeight?: string
75
+ }>(),
76
+ {
77
+ loading: false,
78
+ serverSide: false,
79
+ pageSize: 20,
80
+ pageSizeOptions: () => [10, 20, 50, 100],
81
+ enableRowSelection: false,
82
+ enableColumnVisibility: true,
83
+ enablePagination: true,
84
+ stickyHeader: true,
85
+ freezeColumns: 0,
86
+ layout: 'auto',
87
+ virtual: false,
88
+ estimateRowHeight: 41,
89
+ },
90
+ )
91
+
92
+ const emit = defineEmits<{
93
+ 'page-change': [query: TableQuery]
94
+ 'sort-change': [query: TableQuery]
95
+ 'filter-change': [query: TableQuery]
96
+ 'row-click': [row: TData]
97
+ 'update:rowSelection': [selection: RowSelectionState]
98
+ }>()
99
+
100
+ // --- state: giữ bên ngoài để có thể emit và (nếu cần) đồng bộ lên URL ---
101
+ const sorting = ref<SortingState>(props.initialSorting ?? [])
102
+ const columnFilters = ref<ColumnFiltersState>([])
103
+ const columnVisibility = ref<ColumnVisibilityState>({})
104
+ const rowSelection = ref<RowSelectionState>({})
105
+ const pagination = ref<PaginationState>({
106
+ pageIndex: props.initialPageIndex ?? 0,
107
+ pageSize: props.pageSize,
108
+ })
109
+
110
+ const tableState = computed(() => ({
111
+ sorting: sorting.value,
112
+ columnFilters: columnFilters.value,
113
+ columnVisibility: columnVisibility.value,
114
+ rowSelection: rowSelection.value,
115
+ pagination: pagination.value,
116
+ }))
117
+
118
+ function resolve<T>(updater: Updater<T>, current: T): T {
119
+ return typeof updater === 'function' ? (updater as (old: T) => T)(current) : updater
120
+ }
121
+
122
+ function currentQuery(): TableQuery {
123
+ return {
124
+ pageIndex: pagination.value.pageIndex,
125
+ pageSize: pagination.value.pageSize,
126
+ sorting: sorting.value,
127
+ filters: columnFilters.value,
128
+ }
129
+ }
130
+
131
+ function onSortingChange(updater: Updater<SortingState>): void {
132
+ sorting.value = resolve(updater, sorting.value)
133
+ // Đổi sort thì về trang 1, nếu không người dùng đang ở trang 7 sẽ thấy trống.
134
+ pagination.value = { ...pagination.value, pageIndex: 0 }
135
+ emit('sort-change', currentQuery())
136
+ }
137
+
138
+ function onColumnFiltersChange(updater: Updater<ColumnFiltersState>): void {
139
+ columnFilters.value = resolve(updater, columnFilters.value)
140
+ pagination.value = { ...pagination.value, pageIndex: 0 }
141
+ emit('filter-change', currentQuery())
142
+ }
143
+
144
+ function onPaginationChange(updater: Updater<PaginationState>): void {
145
+ pagination.value = resolve(updater, pagination.value)
146
+ emit('page-change', currentQuery())
147
+ }
148
+
149
+ function onColumnVisibilityChange(updater: Updater<ColumnVisibilityState>): void {
150
+ columnVisibility.value = resolve(updater, columnVisibility.value)
151
+ }
152
+
153
+ function onRowSelectionChange(updater: Updater<RowSelectionState>): void {
154
+ rowSelection.value = resolve(updater, rowSelection.value)
155
+ emit('update:rowSelection', rowSelection.value)
156
+ }
157
+
158
+ /**
159
+ * serverSide là cấu hình tĩnh của bảng, đọc một lần là đủ.
160
+ *
161
+ * QUAN TRỌNG: không bao giờ truyền một computed có thể trả về `undefined`
162
+ * vào option của useTable. mergeProxy của adapter bỏ qua giá trị `undefined`
163
+ * và rơi ngược về source gốc — tức chính ComputedRef chưa unwrap — khiến
164
+ * option nhận được một object thay vì số (getPageCount() ra NaN).
165
+ * Vì vậy `rowCount` chỉ được thêm vào khi thật sự chạy server-side.
166
+ */
167
+ const isServerSide = props.serverSide
168
+
169
+ const table = useTable({
170
+ features: dataTableFeatures,
171
+ columns: toRef(props, 'columns'),
172
+ data: toRef(props, 'data'),
173
+ state: tableState,
174
+ getRowId: props.getRowId,
175
+ enableRowSelection: computed(() => props.enableRowSelection),
176
+ manualSorting: isServerSide,
177
+ manualFiltering: isServerSide,
178
+ manualPagination: isServerSide,
179
+ ...(isServerSide ? { rowCount: computed(() => props.totalRecords ?? 0) } : {}),
180
+ onSortingChange,
181
+ onColumnFiltersChange,
182
+ onPaginationChange,
183
+ onColumnVisibilityChange,
184
+ onRowSelectionChange,
185
+ })
186
+
187
+ /**
188
+ * Chỉ ép chiều rộng cho cột MÀ NGƯỜI VIẾT khai `size`.
189
+ *
190
+ * Không đọc `header.column.columnDef.size`: columnSizingFeature nhét sẵn
191
+ * `size: 150` vào mọi cột, nên đọc từ đó thì cột nào cũng có width cố định
192
+ * và không còn cột nào co giãn được.
193
+ */
194
+ const declaredWidths = computed(() => {
195
+ const widths = new Map<string, number>()
196
+
197
+ for (const column of props.columns) {
198
+ const id = column.id ?? ('accessorKey' in column ? String(column.accessorKey) : undefined)
199
+
200
+ if (id !== undefined && typeof column.size === 'number')
201
+ widths.set(id, column.size)
202
+ }
203
+
204
+ return widths
205
+ })
206
+
207
+ function widthStyle(columnId: string): { width: string } | undefined {
208
+ const width = declaredWidths.value.get(columnId)
209
+ return width === undefined ? undefined : { width: `${width}px` }
210
+ }
211
+
212
+ /** Căn chữ khai qua `meta.align`, áp cho cả header và ô để không lệch nhau. */
213
+ const declaredAlign = computed(() => {
214
+ const alignments = new Map<string, NonNullable<DataTableColumnMeta['align']>>()
215
+
216
+ for (const column of props.columns) {
217
+ const id = column.id ?? ('accessorKey' in column ? String(column.accessorKey) : undefined)
218
+ const align = column.meta?.align
219
+
220
+ if (id !== undefined && align !== undefined)
221
+ alignments.set(id, align)
222
+ }
223
+
224
+ return alignments
225
+ })
226
+
227
+ function alignClass(columnId: string): string | undefined {
228
+ const align = declaredAlign.value.get(columnId)
229
+
230
+ // Cột số: căn phải VÀ tabular figures — thiếu vế sau thì các chữ số vẫn
231
+ // không thẳng hàng, xem chú thích ở `DataTableColumnMeta['align']`.
232
+ if (align === 'number')
233
+ return 'text-right tabular-nums'
234
+ if (align === 'right')
235
+ return 'text-right'
236
+ if (align === 'center')
237
+ return 'text-center'
238
+
239
+ return undefined
240
+ }
241
+
242
+ /**
243
+ * Lớp phủ tải chỉ hiện khi lần chờ vượt 300ms.
244
+ *
245
+ * Dữ liệu đã cache trả về gần như tức thì, mà nháy spinner đúng một khung hình
246
+ * rồi tắt thì đọc ra giống lỗi render hơn là phản hồi. Xem `useDelayedFlag`.
247
+ *
248
+ * `showEmpty` bên dưới cố ý vẫn đọc `props.loading` chứ không đọc cờ đã hoãn:
249
+ * trong 300ms đầu chưa có spinner, nếu `showEmpty` cũng chờ theo thì bảng sẽ
250
+ * chớp qua trạng thái "không có dữ liệu" ngay trước khi dữ liệu về.
251
+ */
252
+ const showLoading = useDelayedFlag(() => props.loading)
253
+
254
+ /**
255
+ * Vị trí `left` của từng cột bị đóng băng, cộng dồn từ bề rộng đã khai.
256
+ *
257
+ * Dừng ngay ở cột đầu tiên không khai `size`: đoán bừa một con số thì cột sau
258
+ * sẽ chồng lên cột trước hoặc chừa ra một khe — sai kiểu khó thấy hơn hẳn so
259
+ * với việc cột đó đơn giản là không dính.
260
+ */
261
+ const frozenOffsets = computed(() => {
262
+ const offsets = new Map<string, number>()
263
+
264
+ if (props.freezeColumns < 1)
265
+ return offsets
266
+
267
+ let left = 0
268
+
269
+ for (const column of table.getVisibleLeafColumns().slice(0, props.freezeColumns)) {
270
+ const width = declaredWidths.value.get(column.id)
271
+
272
+ if (width === undefined)
273
+ break
274
+
275
+ offsets.set(column.id, left)
276
+ left += width
277
+ }
278
+
279
+ return offsets
280
+ })
281
+
282
+ function frozenStyle(columnId: string): { left: string } | undefined {
283
+ const left = frozenOffsets.value.get(columnId)
284
+ return left === undefined ? undefined : { left: `${left}px` }
285
+ }
286
+
287
+ /** Chỉ cột đóng băng CUỐI CÙNG mới kẻ vạch — đó là chỗ mặt phẳng bị cắt. */
288
+ function isLastFrozen(columnId: string): boolean {
289
+ const ids = [...frozenOffsets.value.keys()]
290
+ return ids[ids.length - 1] === columnId
291
+ }
292
+
293
+ const headerGroups = computed(() => table.getHeaderGroups())
294
+ const rows = computed(() => table.getRowModel().rows)
295
+ const visibleColumnCount = computed(() => table.getVisibleLeafColumns().length)
296
+
297
+ // --- virtualization ---
298
+ const scrollRef = useTemplateRef<HTMLDivElement>('scrollEl')
299
+
300
+ const virtualizer = useVirtualizer(computed(() => ({
301
+ count: props.virtual ? rows.value.length : 0,
302
+ getScrollElement: () => scrollRef.value,
303
+ estimateSize: () => props.estimateRowHeight,
304
+ overscan: 8,
305
+ })))
306
+
307
+ const virtualItems = computed(() => (props.virtual ? virtualizer.value.getVirtualItems() : []))
308
+
309
+ const paddingTop = computed(() => virtualItems.value[0]?.start ?? 0)
310
+
311
+ const paddingBottom = computed(() => {
312
+ const items = virtualItems.value
313
+ const last = items[items.length - 1]
314
+ return last ? virtualizer.value.getTotalSize() - last.end : 0
315
+ })
316
+
317
+ const visibleRows = computed(() =>
318
+ props.virtual ? virtualItems.value.map(item => rows.value[item.index]) : rows.value,
319
+ )
320
+
321
+ const showEmpty = computed(() => !props.loading && rows.value.length === 0)
322
+
323
+ function resetSelection(): void {
324
+ rowSelection.value = {}
325
+ }
326
+
327
+ defineExpose({ table, resetSelection })
328
+ </script>
329
+
330
+ <template>
331
+ <!-- Toolbar, bảng và footer nằm TRONG cùng một card — đúng theo thiết kế. -->
332
+ <div
333
+ :class="cn(
334
+ 'surface relative overflow-hidden rounded-lg border border-border bg-card shadow-sm',
335
+ props.class,
336
+ )"
337
+ >
338
+ <div
339
+ v-if="props.enableColumnVisibility || $slots.toolbar"
340
+ class="flex items-center gap-2 border-b border-border px-3.5 py-3"
341
+ >
342
+ <slot name="toolbar" :table="table" />
343
+ <DataTableViewOptions v-if="props.enableColumnVisibility" :table="table" class="ml-auto" />
344
+ </div>
345
+
346
+ <div
347
+ ref="scrollEl"
348
+ class="w-full overflow-auto"
349
+ :style="props.maxHeight ? { maxHeight: props.maxHeight } : undefined"
350
+ >
351
+ <table
352
+ :class="cn(
353
+ 'w-full caption-bottom border-separate border-spacing-0 text-[12.5px]',
354
+ props.layout === 'fixed' && 'table-fixed',
355
+ )"
356
+ >
357
+ <!--
358
+ z-index KHÔNG đặt ở đây mà đặt thẳng trên `<th>`. Biến thể
359
+ `[&_th]:z-(--z-raised)` sinh ra selector con nên specificity (0,1,1), thắng
360
+ luôn `z-(--z-elevated)` viết thẳng trên ô (0,1,0) — ô đóng băng sẽ nằm cùng lớp
361
+ với các ô khác và bị ô sau nó trong DOM vẽ đè lên. Đã dính đúng lỗi
362
+ đó: header cột đóng băng bị header cột kế tiếp che mất.
363
+ -->
364
+ <thead :class="props.stickyHeader ? '[&_th]:sticky [&_th]:top-0' : undefined">
365
+ <tr v-for="group in headerGroups" :key="group.id">
366
+ <th
367
+ v-for="header in group.headers"
368
+ :key="header.id"
369
+ :style="[widthStyle(header.column.id), frozenStyle(header.column.id)]"
370
+ :class="cn(
371
+ 'h-9 whitespace-nowrap border-b border-border bg-muted/50 px-3.5 text-left align-middle text-[11px] font-medium text-muted-foreground',
372
+ alignClass(header.column.id),
373
+ props.stickyHeader && 'z-(--z-raised)',
374
+ // Góc trên-trái dính cả hai chiều nên phải nằm trên cả hai lớp
375
+ // kia, và nền phải ĐỤC — `bg-muted/50` trong suốt sẽ để nội
376
+ // dung cuộn qua bên dưới hiện lên xuyên qua nó.
377
+ frozenOffsets.has(header.column.id) && [
378
+ 'sticky z-(--z-elevated) bg-[color-mix(in_oklab,var(--muted)_50%,var(--card))]',
379
+ isLastFrozen(header.column.id) && 'border-r border-r-border',
380
+ ],
381
+ )"
382
+ >
383
+ <template v-if="!header.isPlaceholder">
384
+ <button
385
+ v-if="header.column.getCanSort()"
386
+ type="button"
387
+ class="-mx-1 inline-flex max-w-full items-center gap-1 rounded px-1 py-0.5 transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
388
+ @click="header.column.getToggleSortingHandler()?.($event)"
389
+ >
390
+ <FlexRender :header="header" />
391
+ <ArrowUp v-if="header.column.getIsSorted() === 'asc'" class="size-3 shrink-0" />
392
+ <ArrowDown v-else-if="header.column.getIsSorted() === 'desc'" class="size-3 shrink-0" />
393
+ <ChevronsUpDown v-else class="size-3 shrink-0 opacity-40" />
394
+ </button>
395
+ <FlexRender v-else :header="header" />
396
+ </template>
397
+ </th>
398
+ </tr>
399
+ </thead>
400
+
401
+ <tbody>
402
+ <tr v-if="props.virtual && paddingTop > 0" :style="{ height: `${paddingTop}px` }">
403
+ <td :colspan="visibleColumnCount" class="p-0" />
404
+ </tr>
405
+
406
+ <TableEmpty v-if="showEmpty" :colspan="visibleColumnCount" :message="props.emptyMessage" />
407
+
408
+ <tr
409
+ v-for="row in visibleRows"
410
+ :key="row.id"
411
+ :data-state="row.getIsSelected() ? 'selected' : undefined"
412
+ class="group/row h-[42px] transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted/50"
413
+ @click="emit('row-click', row.original)"
414
+ >
415
+ <td
416
+ v-for="cell in row.getAllCells()"
417
+ :key="cell.id"
418
+ :style="frozenStyle(cell.column.id)"
419
+ :class="cn(
420
+ 'min-w-0 border-b border-border px-3.5 align-middle',
421
+ alignClass(cell.column.id),
422
+ // Nền của DÒNG nằm trên `<tr>`; ô dính mà để trong suốt thì
423
+ // nội dung cuộn qua sẽ hiện lên xuyên qua nó. Vì vậy ô tự tô
424
+ // nền đục, và tự bắt lại hover/selected của dòng qua `group`.
425
+ frozenOffsets.has(cell.column.id) && [
426
+ 'sticky z-(--z-base) bg-card',
427
+ 'group-hover/row:bg-[color-mix(in_oklab,var(--muted)_50%,var(--card))]',
428
+ 'group-data-[state=selected]/row:bg-[color-mix(in_oklab,var(--muted)_50%,var(--card))]',
429
+ isLastFrozen(cell.column.id) && 'border-r border-r-border',
430
+ ],
431
+ )"
432
+ >
433
+ <FlexRender :cell="cell" />
434
+ </td>
435
+ </tr>
436
+
437
+ <tr v-if="props.virtual && paddingBottom > 0" :style="{ height: `${paddingBottom}px` }">
438
+ <td :colspan="visibleColumnCount" class="p-0" />
439
+ </tr>
440
+ </tbody>
441
+ </table>
442
+ </div>
443
+
444
+ <DataTablePagination
445
+ v-if="props.enablePagination"
446
+ :table="table"
447
+ :page-size-options="props.pageSizeOptions"
448
+ :show-selection="props.enableRowSelection"
449
+ />
450
+
451
+ <div
452
+ v-if="showLoading"
453
+ class="absolute inset-0 grid place-items-center bg-background/60 backdrop-blur-[1px]"
454
+ >
455
+ <Spinner class="text-primary" />
456
+ </div>
457
+ </div>
458
+ </template>
@@ -0,0 +1,97 @@
1
+ <script setup lang="ts">
2
+ import type { ClassProps } from '../../lib/types'
3
+ import { computed } from 'vue'
4
+ import { useI18n } from '../../composables/useI18n'
5
+ import { Check } from '../../lib/icons'
6
+ import { cn } from '../../lib/utils'
7
+ import DropdownMenu from '../dropdown-menu/DropdownMenu.vue'
8
+ import DropdownMenuCheckboxItem from '../dropdown-menu/DropdownMenuCheckboxItem.vue'
9
+ import DropdownMenuContent from '../dropdown-menu/DropdownMenuContent.vue'
10
+ import DropdownMenuItem from '../dropdown-menu/DropdownMenuItem.vue'
11
+ import DropdownMenuSeparator from '../dropdown-menu/DropdownMenuSeparator.vue'
12
+ import DropdownMenuTrigger from '../dropdown-menu/DropdownMenuTrigger.vue'
13
+
14
+ export interface FacetOption {
15
+ label: string
16
+ value: string
17
+ }
18
+
19
+ /**
20
+ * Gốc là `DropdownMenu`, một provider KHÔNG vẽ ra DOM — thuộc tính rơi tự do
21
+ * không có chỗ đáp và biến mất hẳn. Chuyển tay xuống chính cái trigger.
22
+ */
23
+ defineOptions({ inheritAttrs: false })
24
+
25
+ const props = defineProps<ClassProps & {
26
+ label: string
27
+ options: FacetOption[]
28
+ }>()
29
+
30
+ const { t } = useI18n()
31
+
32
+ /**
33
+ * Bộ lọc dạng chip viền nét đứt. Chưa chọn gì thì chỉ hiện nhãn mờ;
34
+ * có lựa chọn thì thêm separator dọc + badge đếm — đúng theo thiết kế.
35
+ */
36
+ const selected = defineModel<string[]>({ default: () => [] })
37
+
38
+ const selectedSet = computed(() => new Set(selected.value))
39
+
40
+ function toggle(value: string, checked: boolean): void {
41
+ const next = new Set(selected.value)
42
+
43
+ if (checked)
44
+ next.add(value)
45
+ else
46
+ next.delete(value)
47
+
48
+ selected.value = [...next]
49
+ }
50
+
51
+ function clear(): void {
52
+ selected.value = []
53
+ }
54
+ </script>
55
+
56
+ <template>
57
+ <DropdownMenu>
58
+ <DropdownMenuTrigger
59
+ v-bind="$attrs"
60
+ :class="cn(
61
+ 'flex h-(--ui-field-height-sm) items-center gap-1.5 rounded-md border border-dashed border-border px-(--ui-field-padding-x-sm)',
62
+ 'text-[11.5px] font-medium transition-colors hover:bg-muted',
63
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
64
+ selectedSet.size === 0 && 'text-muted-foreground',
65
+ props.class,
66
+ )"
67
+ >
68
+ {{ props.label }}
69
+ <template v-if="selectedSet.size > 0">
70
+ <span class="h-3.5 w-px bg-border" aria-hidden="true" />
71
+ <span class="rounded-sm bg-muted px-1.5 py-px font-mono text-[10.5px] font-medium">
72
+ {{ selectedSet.size }}
73
+ </span>
74
+ </template>
75
+ </DropdownMenuTrigger>
76
+
77
+ <DropdownMenuContent align="start" class="w-48">
78
+ <DropdownMenuCheckboxItem
79
+ v-for="option in props.options"
80
+ :key="option.value"
81
+ :model-value="selectedSet.has(option.value)"
82
+ @select="(event: Event) => event.preventDefault()"
83
+ @update:model-value="(checked: boolean) => toggle(option.value, checked)"
84
+ >
85
+ {{ option.label }}
86
+ </DropdownMenuCheckboxItem>
87
+
88
+ <template v-if="selectedSet.size > 0">
89
+ <DropdownMenuSeparator />
90
+ <DropdownMenuItem class="justify-center text-[11.5px]" @select="clear">
91
+ <Check class="opacity-0" />
92
+ {{ t('ui.table.clearFilter') }}
93
+ </DropdownMenuItem>
94
+ </template>
95
+ </DropdownMenuContent>
96
+ </DropdownMenu>
97
+ </template>
@@ -0,0 +1,103 @@
1
+ <script setup lang="ts" generic="TData extends RowData">
2
+ import type { RowData, Table } from '@tanstack/vue-table'
3
+ import type { ClassProps } from '../../lib/types'
4
+ import type { DataTableFeatures } from './features'
5
+ import { computed } from 'vue'
6
+ import { useI18n } from '../../composables/useI18n'
7
+ import { ChevronLeft, ChevronRight } from '../../lib/icons'
8
+ import { cn } from '../../lib/utils'
9
+ import Select from '../select/Select.vue'
10
+ import SelectContent from '../select/SelectContent.vue'
11
+ import SelectItem from '../select/SelectItem.vue'
12
+ import SelectTrigger from '../select/SelectTrigger.vue'
13
+ import SelectValue from '../select/SelectValue.vue'
14
+
15
+ const props = defineProps<ClassProps & {
16
+ table: Table<DataTableFeatures, TData>
17
+ pageSizeOptions: number[]
18
+ showSelection?: boolean
19
+ }>()
20
+
21
+ const pageIndex = computed(() => props.table.atoms.pagination.get().pageIndex)
22
+ const pageSize = computed(() => props.table.atoms.pagination.get().pageSize)
23
+ const pageCount = computed(() => props.table.getPageCount())
24
+ const rowCount = computed(() => props.table.getRowCount())
25
+
26
+ const selectedCount = computed(() => Object.keys(props.table.atoms.rowSelection.get()).length)
27
+
28
+ /**
29
+ * Luôn chèn pageSize hiện tại vào danh sách: nếu nó không có trong options
30
+ * (vd pageSize 25 nhưng options là 10/20/50/100) thì SelectValue không tìm
31
+ * được item khớp và ô chọn hiện ra rỗng.
32
+ */
33
+ const { t } = useI18n()
34
+
35
+ const sizeOptions = computed(() =>
36
+ [...new Set([...props.pageSizeOptions, pageSize.value])].sort((a, b) => a - b),
37
+ )
38
+
39
+ const summary = computed(() =>
40
+ props.showSelection
41
+ ? t('ui.table.summarySelected', { selected: selectedCount.value, count: rowCount.value })
42
+ : t('ui.table.summary', { count: rowCount.value }),
43
+ )
44
+
45
+ function onPageSizeChange(value: unknown): void {
46
+ if (typeof value === 'string')
47
+ props.table.setPageSize(Number(value))
48
+ }
49
+
50
+ // 26px — kích thước nút điều hướng trong thiết kế, nhỏ hơn cả size="icon-sm".
51
+ const navButton = cn(
52
+ 'grid size-[26px] place-items-center rounded-md border border-border text-muted-foreground transition-colors',
53
+ 'hover:bg-muted hover:text-foreground',
54
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
55
+ 'disabled:pointer-events-none disabled:opacity-50',
56
+ )
57
+ </script>
58
+
59
+ <template>
60
+ <div :class="cn('flex flex-wrap items-center gap-3 px-3.5 py-[11px] text-[11.5px]', props.class)">
61
+ <span class="mr-auto text-muted-foreground">{{ summary }}</span>
62
+
63
+ <div class="flex items-center gap-2 text-muted-foreground">
64
+ <span class="whitespace-nowrap">{{ t('ui.table.rowsPerPage') }}</span>
65
+ <Select :model-value="String(pageSize)" @update:model-value="onPageSizeChange">
66
+ <!-- px-2 thay vì px-3 mặc định: ô chỉ chứa 2-3 chữ số, padding rộng làm tràn chữ -->
67
+ <SelectTrigger size="sm" class="h-[26px] w-[62px] px-2 text-[11.5px]">
68
+ <SelectValue />
69
+ </SelectTrigger>
70
+ <SelectContent>
71
+ <SelectItem v-for="option in sizeOptions" :key="option" :value="String(option)">
72
+ {{ option }}
73
+ </SelectItem>
74
+ </SelectContent>
75
+ </Select>
76
+ </div>
77
+
78
+ <span class="whitespace-nowrap text-muted-foreground">
79
+ {{ t('ui.pagination.page', { page: pageCount === 0 ? 0 : pageIndex + 1, total: pageCount }) }}
80
+ </span>
81
+
82
+ <div class="flex gap-1">
83
+ <button
84
+ type="button"
85
+ :class="navButton"
86
+ :disabled="!props.table.getCanPreviousPage()"
87
+ :aria-label="t('ui.pagination.previous')"
88
+ @click="props.table.previousPage()"
89
+ >
90
+ <ChevronLeft class="size-3" />
91
+ </button>
92
+ <button
93
+ type="button"
94
+ :class="navButton"
95
+ :disabled="!props.table.getCanNextPage()"
96
+ :aria-label="t('ui.pagination.next')"
97
+ @click="props.table.nextPage()"
98
+ >
99
+ <ChevronRight class="size-3" />
100
+ </button>
101
+ </div>
102
+ </div>
103
+ </template>