@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,211 @@
1
+ <script setup lang="ts" generic="TItem">
2
+ import type { ClassProps } from '../../lib/types'
3
+ import { computed, ref } from 'vue'
4
+ import { useI18n } from '../../composables/useI18n'
5
+ import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from '../../lib/icons'
6
+ import { cn } from '../../lib/utils'
7
+ import { moveItem } from '../../variants/lib/list-move'
8
+ import Button from '../button/Button.vue'
9
+ import SelectableList from '../order-list/SelectableList.vue'
10
+
11
+ const props = withDefaults(
12
+ defineProps<ClassProps & {
13
+ itemKey: (item: TItem, index: number) => string | number
14
+ sourceHeader?: string
15
+ targetHeader?: string
16
+ disabled?: boolean
17
+ dragging?: boolean
18
+ maxHeight?: string
19
+ }>(),
20
+ { disabled: false, dragging: true },
21
+ )
22
+
23
+ const source = defineModel<TItem[]>('source', { default: () => [] })
24
+ const target = defineModel<TItem[]>('target', { default: () => [] })
25
+
26
+ const { t } = useI18n()
27
+
28
+ const sourceKeys = computed(() => source.value.map((item, index) => props.itemKey(item, index)))
29
+ const targetKeys = computed(() => target.value.map((item, index) => props.itemKey(item, index)))
30
+
31
+ const sourceSelection = ref<(string | number)[]>([])
32
+ const targetSelection = ref<(string | number)[]>([])
33
+ const announcement = ref('')
34
+
35
+ function pickedFrom(list: TItem[], selection: (string | number)[]): number[] {
36
+ return list
37
+ .map((item, index) => (selection.includes(props.itemKey(item, index)) ? index : -1))
38
+ .filter(index => index >= 0)
39
+ }
40
+
41
+ const canSend = computed(() => pickedFrom(source.value, sourceSelection.value).length > 0)
42
+ const canReturn = computed(() => pickedFrom(target.value, targetSelection.value).length > 0)
43
+
44
+ /**
45
+ * Chuyển giữa hai danh sách.
46
+ *
47
+ * Bỏ lựa chọn ở bên gửi sau khi chuyển: khoá vẫn còn đó nhưng mục thì đã sang
48
+ * danh sách kia, nên giữ lại là mấy cái nút vẫn sáng trong khi không còn gì để
49
+ * gửi — một nút bấm vào không làm gì.
50
+ */
51
+ function transfer(direction: 'send' | 'return', all: boolean): void {
52
+ const fromList = direction === 'send' ? source : target
53
+ const toList = direction === 'send' ? target : source
54
+ const selection = direction === 'send' ? sourceSelection : targetSelection
55
+
56
+ const indexes = all
57
+ ? fromList.value.map((_, index) => index)
58
+ : pickedFrom(fromList.value, selection.value)
59
+
60
+ if (indexes.length === 0)
61
+ return
62
+
63
+ const picked = indexes.map(index => fromList.value[index])
64
+ const set = new Set(indexes)
65
+
66
+ fromList.value = fromList.value.filter((_, index) => !set.has(index))
67
+ toList.value = [...toList.value, ...picked]
68
+ selection.value = []
69
+
70
+ announcement.value = t(
71
+ direction === 'send' ? 'ui.pickList.sent' : 'ui.pickList.returned',
72
+ { count: picked.length },
73
+ )
74
+ }
75
+
76
+ /**
77
+ * Thả: cùng danh sách thì sắp lại, khác danh sách thì chuyển sang.
78
+ *
79
+ * `SelectableList` báo cả `fromListId`, nên một hàm lo được cả hai — và đó
80
+ * cũng chính là hai nghĩa mà người dùng vẫn hiểu khi họ kéo một mục.
81
+ */
82
+ function onDrop(into: 'source' | 'target', payload: { fromListId: string, fromIndex: number, toIndex: number }): void {
83
+ const intoList = into === 'source' ? source : target
84
+
85
+ if (payload.fromListId === into) {
86
+ const to = payload.toIndex > payload.fromIndex ? payload.toIndex - 1 : payload.toIndex
87
+ intoList.value = moveItem(intoList.value, payload.fromIndex, to)
88
+ return
89
+ }
90
+
91
+ const fromList = into === 'source' ? target : source
92
+ const moved = fromList.value[payload.fromIndex]
93
+ if (moved === undefined)
94
+ return
95
+
96
+ fromList.value = fromList.value.filter((_, index) => index !== payload.fromIndex)
97
+ const next = [...intoList.value]
98
+ next.splice(Math.min(payload.toIndex, next.length), 0, moved)
99
+ intoList.value = next
100
+
101
+ announcement.value = t(
102
+ into === 'target' ? 'ui.pickList.sent' : 'ui.pickList.returned',
103
+ { count: 1 },
104
+ )
105
+ }
106
+ </script>
107
+
108
+ <template>
109
+ <div :class="cn('flex flex-col gap-2', props.class)">
110
+ <div class="flex items-stretch gap-3">
111
+ <div class="flex min-w-0 flex-1 flex-col gap-2">
112
+ <p class="text-sm font-medium">
113
+ {{ props.sourceHeader ?? t('ui.pickList.source') }}
114
+ </p>
115
+ <SelectableList
116
+ v-model:selection="sourceSelection"
117
+ :items="source"
118
+ :keys="sourceKeys"
119
+ list-id="source"
120
+ :label="props.sourceHeader ?? t('ui.pickList.source')"
121
+ :disabled="props.disabled"
122
+ :dragging="props.dragging"
123
+ :max-height="props.maxHeight"
124
+ :empty-message="t('ui.table.empty')"
125
+ class="flex-1"
126
+ @move="onDrop('source', $event)"
127
+ >
128
+ <template #item="slotProps">
129
+ <slot name="item" v-bind="slotProps" list="source" />
130
+ </template>
131
+ </SelectableList>
132
+ </div>
133
+
134
+ <!--
135
+ Mấy cái nút chuyển nằm GIỮA, và chúng là cơ chế chính — kéo thả là phần
136
+ thêm. WCAG 2.5.7: thao tác kéo nào cũng phải có đường một-chạm.
137
+ -->
138
+ <div class="flex flex-col justify-center gap-1">
139
+ <Button
140
+ type="button"
141
+ variant="outline"
142
+ size="icon-sm"
143
+ :disabled="props.disabled || !canSend"
144
+ :aria-label="t('ui.pickList.send')"
145
+ @click="transfer('send', false)"
146
+ >
147
+ <ChevronRight aria-hidden="true" />
148
+ </Button>
149
+ <Button
150
+ type="button"
151
+ variant="outline"
152
+ size="icon-sm"
153
+ :disabled="props.disabled || source.length === 0"
154
+ :aria-label="t('ui.pickList.sendAll')"
155
+ @click="transfer('send', true)"
156
+ >
157
+ <ChevronsRight aria-hidden="true" />
158
+ </Button>
159
+ <Button
160
+ type="button"
161
+ variant="outline"
162
+ size="icon-sm"
163
+ :disabled="props.disabled || !canReturn"
164
+ :aria-label="t('ui.pickList.return')"
165
+ @click="transfer('return', false)"
166
+ >
167
+ <ChevronLeft aria-hidden="true" />
168
+ </Button>
169
+ <Button
170
+ type="button"
171
+ variant="outline"
172
+ size="icon-sm"
173
+ :disabled="props.disabled || target.length === 0"
174
+ :aria-label="t('ui.pickList.returnAll')"
175
+ @click="transfer('return', true)"
176
+ >
177
+ <ChevronsLeft aria-hidden="true" />
178
+ </Button>
179
+ </div>
180
+
181
+ <div class="flex min-w-0 flex-1 flex-col gap-2">
182
+ <p class="text-sm font-medium">
183
+ {{ props.targetHeader ?? t('ui.pickList.target') }}
184
+ </p>
185
+ <SelectableList
186
+ v-model:selection="targetSelection"
187
+ :items="target"
188
+ :keys="targetKeys"
189
+ list-id="target"
190
+ :label="props.targetHeader ?? t('ui.pickList.target')"
191
+ :disabled="props.disabled"
192
+ :dragging="props.dragging"
193
+ :max-height="props.maxHeight"
194
+ :empty-message="t('ui.table.empty')"
195
+ class="flex-1"
196
+ @move="onDrop('target', $event)"
197
+ >
198
+ <template #item="slotProps">
199
+ <slot name="item" v-bind="slotProps" list="target" />
200
+ </template>
201
+ </SelectableList>
202
+ </div>
203
+ </div>
204
+
205
+ <!-- Chuyển một mục sang danh sách kia là thay đổi không nhìn thấy được nếu
206
+ bạn không nhìn: focus ở lại trên nút, chỉ có hai danh sách đổi nội dung. -->
207
+ <p class="sr-only" role="status" aria-live="polite">
208
+ {{ announcement }}
209
+ </p>
210
+ </div>
211
+ </template>
@@ -0,0 +1 @@
1
+ export { default as PickList } from './PickList.vue'
@@ -0,0 +1,15 @@
1
+ <script setup lang="ts">
2
+ import type { PopoverRootEmits, PopoverRootProps } from 'reka-ui'
3
+ import { PopoverRoot, useForwardPropsEmits } from 'reka-ui'
4
+
5
+ const props = defineProps<PopoverRootProps>()
6
+ const emits = defineEmits<PopoverRootEmits>()
7
+
8
+ const forwarded = useForwardPropsEmits(props, emits)
9
+ </script>
10
+
11
+ <template>
12
+ <PopoverRoot v-bind="forwarded">
13
+ <slot />
14
+ </PopoverRoot>
15
+ </template>
@@ -0,0 +1,12 @@
1
+ <script setup lang="ts">
2
+ import type { PopoverAnchorProps } from 'reka-ui'
3
+ import { PopoverAnchor } from 'reka-ui'
4
+
5
+ const props = defineProps<PopoverAnchorProps>()
6
+ </script>
7
+
8
+ <template>
9
+ <PopoverAnchor v-bind="props">
10
+ <slot />
11
+ </PopoverAnchor>
12
+ </template>
@@ -0,0 +1,39 @@
1
+ <script setup lang="ts">
2
+ import type { PopoverContentEmits, PopoverContentProps } from 'reka-ui'
3
+ import type { ClassProps } from '../../lib/types'
4
+ import { PopoverContent, PopoverPortal, useForwardPropsEmits } from 'reka-ui'
5
+ import { computed } from 'vue'
6
+ import { cn } from '../../lib/utils'
7
+
8
+ const props = withDefaults(
9
+ defineProps<PopoverContentProps & ClassProps>(),
10
+ { align: 'center', sideOffset: 6, collisionPadding: 8 },
11
+ )
12
+ const emits = defineEmits<PopoverContentEmits>()
13
+
14
+ const delegated = computed(() => {
15
+ const { class: _class, ...rest } = props
16
+ return rest
17
+ })
18
+
19
+ const forwarded = useForwardPropsEmits(delegated, emits)
20
+ </script>
21
+
22
+ <template>
23
+ <PopoverPortal>
24
+ <PopoverContent
25
+ v-bind="forwarded"
26
+ :class="cn(
27
+ 'surface z-(--z-overlay) w-72 rounded-md border border-border bg-popover p-4 text-popover-foreground shadow-md outline-none',
28
+ 'data-[state=open]:animate-in data-[state=closed]:animate-out',
29
+ 'data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0',
30
+ 'data-[state=open]:zoom-in-95 data-[state=closed]:zoom-out-95',
31
+ 'data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2',
32
+ 'data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2',
33
+ props.class,
34
+ )"
35
+ >
36
+ <slot />
37
+ </PopoverContent>
38
+ </PopoverPortal>
39
+ </template>
@@ -0,0 +1,12 @@
1
+ <script setup lang="ts">
2
+ import type { PopoverTriggerProps } from 'reka-ui'
3
+ import { PopoverTrigger } from 'reka-ui'
4
+
5
+ const props = defineProps<PopoverTriggerProps>()
6
+ </script>
7
+
8
+ <template>
9
+ <PopoverTrigger v-bind="props">
10
+ <slot />
11
+ </PopoverTrigger>
12
+ </template>
@@ -0,0 +1,4 @@
1
+ export { default as Popover } from './Popover.vue'
2
+ export { default as PopoverAnchor } from './PopoverAnchor.vue'
3
+ export { default as PopoverContent } from './PopoverContent.vue'
4
+ export { default as PopoverTrigger } from './PopoverTrigger.vue'
@@ -0,0 +1,39 @@
1
+ <script setup lang="ts">
2
+ import type { ClassProps } from '../../lib/types'
3
+ import { computed } from 'vue'
4
+ import { cn } from '../../lib/utils'
5
+
6
+ const props = withDefaults(
7
+ defineProps<ClassProps & {
8
+ /** Giá trị hiện tại, tính theo `max`. */
9
+ value?: number
10
+ max?: number
11
+ /** Màu thanh chạy. Mặc định lấy `--primary`. */
12
+ indicatorClass?: string
13
+ }>(),
14
+ { value: 0, max: 100 },
15
+ )
16
+
17
+ const percent = computed(() => {
18
+ if (props.max <= 0)
19
+ return 0
20
+
21
+ return Math.min(100, Math.max(0, (props.value / props.max) * 100))
22
+ })
23
+ </script>
24
+
25
+ <template>
26
+ <div
27
+ role="progressbar"
28
+ :aria-valuenow="props.value"
29
+ :aria-valuemin="0"
30
+ :aria-valuemax="props.max"
31
+ :class="cn('h-1.5 w-full overflow-hidden rounded-full bg-secondary', props.class)"
32
+ >
33
+ <div
34
+ class="h-full rounded-full transition-[width] duration-(--duration-slow)"
35
+ :class="cn('bg-primary', props.indicatorClass)"
36
+ :style="{ width: `${percent}%` }"
37
+ />
38
+ </div>
39
+ </template>
@@ -0,0 +1 @@
1
+ export { default as Progress } from './Progress.vue'
@@ -0,0 +1,62 @@
1
+ <script setup lang="ts">
2
+ import type { ClassProps } from '../../lib/types'
3
+ import { cn } from '../../lib/utils'
4
+ import { DocText } from '../doc-text'
5
+
6
+ /** Một dòng trong bảng tham chiếu API: tên, mô tả, và cột phụ (kiểu / payload). */
7
+ export interface PropDoc {
8
+ name: string
9
+ description?: string
10
+ /** Ghi đúng như trong type, ví dụ `'sm' | 'default' | 'lg' — default 'default'`. */
11
+ meta?: string
12
+ }
13
+
14
+ const props = withDefaults(
15
+ defineProps<ClassProps & {
16
+ rows: PropDoc[]
17
+ /** Ba nhãn cột ghép lại thành dòng tiêu đề, ví dụ `Prop · description · type`. */
18
+ heading: string
19
+ /**
20
+ * `stack` xếp dọc tên / mô tả / kiểu (bảng Props, Emits, Slots, Tokens).
21
+ * `split` chia hai cột cho bảng phím tắt.
22
+ */
23
+ layout?: 'stack' | 'split'
24
+ }>(),
25
+ { layout: 'stack' },
26
+ )
27
+ </script>
28
+
29
+ <template>
30
+ <div :class="cn('overflow-hidden rounded-xl border border-border', props.class)">
31
+ <div
32
+ class="border-b border-border bg-sidebar px-3.5 py-[9px] text-[10.5px] font-medium uppercase tracking-[0.05em] text-muted-foreground"
33
+ :class="props.layout === 'split' && 'grid grid-cols-[120px_minmax(0,1fr)] gap-3'"
34
+ >
35
+ <template v-if="props.layout === 'split'">
36
+ <span v-for="label in props.heading.split(' · ')" :key="label">{{ label }}</span>
37
+ </template>
38
+ <span v-else>{{ props.heading }}</span>
39
+ </div>
40
+
41
+ <div
42
+ v-for="row in props.rows"
43
+ :key="row.name"
44
+ class="border-b border-border last:border-b-0"
45
+ :class="props.layout === 'split'
46
+ ? 'grid grid-cols-[120px_minmax(0,1fr)] items-start gap-3 px-3.5 py-[11px]'
47
+ : 'flex min-w-0 flex-col gap-[5px] px-3.5 py-3'"
48
+ >
49
+ <span class="font-mono text-[12.5px] [overflow-wrap:anywhere]" :class="props.layout === 'split' ? 'font-medium' : 'font-semibold'">
50
+ {{ row.name }}
51
+ </span>
52
+
53
+ <span v-if="row.description" class="min-w-0 text-pretty text-[12.5px] leading-[1.55] text-muted-foreground [overflow-wrap:anywhere]">
54
+ <DocText :text="row.description" />
55
+ </span>
56
+
57
+ <span v-if="row.meta" class="font-mono text-[11.5px] leading-[1.6] text-muted-foreground [overflow-wrap:anywhere] [word-break:break-word]">
58
+ {{ row.meta }}
59
+ </span>
60
+ </div>
61
+ </div>
62
+ </template>
@@ -0,0 +1,2 @@
1
+ export type { PropDoc } from './PropsTable.vue'
2
+ export { default as PropsTable } from './PropsTable.vue'
@@ -0,0 +1,52 @@
1
+ <script setup lang="ts">
2
+ import type { RadioGroupRootEmits, RadioGroupRootProps } from 'reka-ui'
3
+ import type { ClassProps } from '../../lib/types'
4
+ import type { RadioGroupItemVariants } from './index'
5
+ import { RadioGroupRoot, useForwardPropsEmits } from 'reka-ui'
6
+ import { computed } from 'vue'
7
+ import { cn } from '../../lib/utils'
8
+ import { provideRadioGroupContext } from './index'
9
+
10
+ const props = withDefaults(
11
+ defineProps<RadioGroupRootProps & ClassProps & {
12
+ size?: RadioGroupItemVariants['size']
13
+ tone?: RadioGroupItemVariants['tone']
14
+ invalid?: boolean
15
+ }>(),
16
+ { orientation: 'vertical', invalid: false },
17
+ )
18
+
19
+ const emits = defineEmits<RadioGroupRootEmits>()
20
+
21
+ const delegated = computed(() => {
22
+ const { class: _class, size: _size, tone: _tone, invalid: _invalid, ...rest } = props
23
+ return rest
24
+ })
25
+
26
+ const forwarded = useForwardPropsEmits(delegated, emits)
27
+
28
+ // Cỡ và kiểu tô khai một lần ở nhóm, từng nút con khỏi lặp lại.
29
+ provideRadioGroupContext({
30
+ size: computed(() => props.size),
31
+ tone: computed(() => props.tone),
32
+ invalid: computed(() => props.invalid),
33
+ })
34
+ </script>
35
+
36
+ <template>
37
+ <!--
38
+ KHÔNG hạ `rovingFocus` như CheckboxGroup đã làm. Ở đây mặc định `true` của
39
+ reka mới đúng: chọn cái này là bỏ cái kia, nên WAI-ARIA APG xếp cả nhóm vào
40
+ **một** điểm dừng Tab và di chuyển giữa các lựa chọn bằng phím mũi tên.
41
+ -->
42
+ <RadioGroupRoot
43
+ v-bind="forwarded"
44
+ :class="cn(
45
+ 'grid gap-2.5',
46
+ props.orientation === 'horizontal' && 'grid-flow-col auto-cols-max gap-5',
47
+ props.class,
48
+ )"
49
+ >
50
+ <slot />
51
+ </RadioGroupRoot>
52
+ </template>
@@ -0,0 +1,80 @@
1
+ <script setup lang="ts">
2
+ import type { RadioGroupItemEmits, RadioGroupItemProps } from 'reka-ui'
3
+ import type { ClassProps } from '../../lib/types'
4
+ import type { RadioGroupItemVariants } from './index'
5
+ import { RadioGroupIndicator, RadioGroupItem, useForwardPropsEmits } from 'reka-ui'
6
+ import { computed } from 'vue'
7
+ import { cn } from '../../lib/utils'
8
+ import { injectRadioGroupContext, radioGroupItemVariants } from './index'
9
+
10
+ const props = withDefaults(defineProps<RadioGroupItemProps & ClassProps & {
11
+ size?: RadioGroupItemVariants['size']
12
+ tone?: RadioGroupItemVariants['tone']
13
+ /** Viền đỏ + `aria-invalid`. Trong `FormField` thì không cần khai — xem dưới. */
14
+ invalid?: boolean
15
+ }>(), {
16
+ /**
17
+ * PHẢI khai mặc định là `undefined`, không được để trống.
18
+ *
19
+ * Vue ép prop kiểu Boolean vắng mặt thành `false` chứ không phải
20
+ * `undefined`, nên `props.invalid ?? group.invalid` sẽ luôn dừng ở `false`
21
+ * và nhóm không bao giờ truyền được trạng thái lỗi xuống. Khai mặc định
22
+ * tường minh thì Vue bỏ phép ép đó đi.
23
+ */
24
+ invalid: undefined,
25
+ })
26
+
27
+ const emits = defineEmits<RadioGroupItemEmits>()
28
+
29
+ // Nằm trong RadioGroup thì kế thừa cỡ, kiểu tô và trạng thái lỗi của nhóm.
30
+ // Prop khai thẳng trên từng nút vẫn thắng.
31
+ const group = injectRadioGroupContext(null)
32
+
33
+ const size = computed(() => props.size ?? group?.size.value)
34
+ const tone = computed(() => props.tone ?? group?.tone.value)
35
+
36
+ /**
37
+ * `v-bind="field"` của FormField truyền `aria-invalid` xuống dưới dạng attr, nên
38
+ * đọc luôn ở đây: control trong form tự đỏ viền mà không phải nối tay `invalid`.
39
+ *
40
+ * Nhận `attrs` làm tham số để gọi thẳng trong template — `useAttrs()` **không
41
+ * phản ứng**, bọc vào computed là giá trị đứng im ở lần render đầu.
42
+ */
43
+ function resolveInvalid(attrs: Record<string, unknown>): boolean {
44
+ if (props.invalid !== undefined)
45
+ return props.invalid
46
+
47
+ if (group?.invalid.value)
48
+ return true
49
+
50
+ return attrs['aria-invalid'] === true || attrs['aria-invalid'] === 'true'
51
+ }
52
+
53
+ // Tách `class` và ba prop trình bày ra khỏi props trước khi forward: chúng
54
+ // không thuộc RadioGroupItem, để lọt xuống sẽ thành thuộc tính lạ trên DOM.
55
+ const delegated = computed(() => {
56
+ const { class: _class, size: _size, tone: _tone, invalid: _invalid, ...rest } = props
57
+ return rest
58
+ })
59
+
60
+ const forwarded = useForwardPropsEmits(delegated, emits)
61
+ </script>
62
+
63
+ <template>
64
+ <RadioGroupItem
65
+ v-bind="forwarded"
66
+ :aria-invalid="resolveInvalid($attrs) || undefined"
67
+ :data-invalid="resolveInvalid($attrs) || undefined"
68
+ :class="cn(radioGroupItemVariants({ size, tone, invalid: resolveInvalid($attrs) }), props.class)"
69
+ >
70
+ <!-- Indicator chỉ tồn tại khi đã chọn nên zoom-in chạy đúng một lần lúc nó
71
+ xuất hiện — không cần transition-group hay forceMount. -->
72
+ <RadioGroupIndicator
73
+ class="flex size-full animate-in items-center justify-center zoom-in-50 duration-(--duration-fast) motion-reduce:animate-none"
74
+ >
75
+ <!-- Chấm là một div chứ không phải icon: ở cỡ 6px thì nét vẽ của icon
76
+ tròn bị làm mảnh đi và trông xám hơn nền, không còn ra hình tròn đặc. -->
77
+ <span data-dot class="rounded-full bg-primary-foreground" />
78
+ </RadioGroupIndicator>
79
+ </RadioGroupItem>
80
+ </template>
@@ -0,0 +1,128 @@
1
+ <script setup lang="ts">
2
+ import type { RadioGroupItemEmits, RadioGroupItemProps } from 'reka-ui'
3
+ import type { ClassProps } from '../../lib/types'
4
+ import type { RadioGroupItemVariants } from './index'
5
+ import { useForwardPropsEmits } from 'reka-ui'
6
+ import { computed, useId } from 'vue'
7
+ import { cn } from '../../lib/utils'
8
+ import Label from '../label/Label.vue'
9
+ import { injectRadioGroupContext } from './index'
10
+ import RadioGroupItem from './RadioGroupItem.vue'
11
+
12
+ /**
13
+ * Attr rơi tự do phải đi xuống NÚT, không đọng lại ở div bọc ngoài: `v-bind="field"`
14
+ * của FormField truyền `aria-invalid` và `aria-describedby` dưới dạng attr, mà
15
+ * cả hai đều tả cái nút chứ không tả cái hàng.
16
+ */
17
+ defineOptions({ inheritAttrs: false })
18
+
19
+ const props = withDefaults(defineProps<RadioGroupItemProps & ClassProps & {
20
+ size?: RadioGroupItemVariants['size']
21
+ tone?: RadioGroupItemVariants['tone']
22
+ invalid?: boolean
23
+ /** Nhãn. Cần đánh dấu chữ (ví dụ chèn link) thì dùng slot mặc định. */
24
+ label?: string
25
+ /** Dòng phụ bên dưới nhãn. */
26
+ description?: string
27
+ }>(), {
28
+ /**
29
+ * PHẢI khai mặc định là `undefined`, không được để trống.
30
+ *
31
+ * Vue ép prop kiểu Boolean vắng mặt thành `false` chứ không phải
32
+ * `undefined`, nên `props.invalid ?? group.invalid` sẽ luôn dừng ở `false`
33
+ * và nhóm không bao giờ truyền được trạng thái lỗi xuống. Khai mặc định
34
+ * tường minh thì Vue bỏ phép ép đó đi.
35
+ */
36
+ invalid: undefined,
37
+ })
38
+
39
+ const emits = defineEmits<RadioGroupItemEmits>()
40
+
41
+ defineSlots<{
42
+ default?: () => unknown
43
+ description?: () => unknown
44
+ }>()
45
+
46
+ const group = injectRadioGroupContext(null)
47
+
48
+ const fallbackId = useId()
49
+ const controlId = computed(() => props.id ?? fallbackId)
50
+ const descriptionId = computed(() => `${controlId.value}-description`)
51
+
52
+ const hasDescription = computed(() => Boolean(props.description))
53
+
54
+ /**
55
+ * Nối chứ không ghi đè: field trong form đã trỏ sang thông báo lỗi rồi.
56
+ *
57
+ * Nhận `attrs` làm tham số để gọi thẳng trong template — `useAttrs()` không phản
58
+ * ứng, bọc vào computed là giá trị đứng im ở lần render đầu.
59
+ */
60
+ function resolveDescribedBy(attrs: Record<string, unknown>): string | undefined {
61
+ const external = typeof attrs['aria-describedby'] === 'string' ? attrs['aria-describedby'] : undefined
62
+ const ids = [external, hasDescription.value ? descriptionId.value : undefined].filter(Boolean)
63
+ return ids.length > 0 ? ids.join(' ') : undefined
64
+ }
65
+
66
+ const size = computed(() => props.size ?? group?.size.value ?? 'md')
67
+
68
+ /**
69
+ * Đẩy nút xuống cho tâm nó thẳng hàng với dòng chữ ĐẦU TIÊN của nhãn, không
70
+ * phải với cả khối. Nhãn là `text-sm` nên dòng cao 20px, tâm ở 10px: nút 14px
71
+ * lệch 3px, nút 16px lệch 2px, nút 20px vừa khít.
72
+ */
73
+ const alignClass = computed(() => ({
74
+ sm: 'mt-[3px]',
75
+ md: 'mt-0.5',
76
+ lg: 'mt-0',
77
+ }[size.value ?? 'md']))
78
+
79
+ const delegated = computed(() => {
80
+ const {
81
+ class: _class,
82
+ size: _size,
83
+ tone: _tone,
84
+ invalid: _invalid,
85
+ label: _label,
86
+ description: _description,
87
+ ...rest
88
+ } = props
89
+ return rest
90
+ })
91
+
92
+ const forwarded = useForwardPropsEmits(delegated, emits)
93
+ </script>
94
+
95
+ <template>
96
+ <div :class="cn('flex items-start gap-2.5', props.class)">
97
+ <RadioGroupItem
98
+ v-bind="{ ...forwarded, ...$attrs }"
99
+ :id="controlId"
100
+ :size="props.size"
101
+ :tone="props.tone"
102
+ :invalid="props.invalid"
103
+ :aria-describedby="resolveDescribedBy($attrs)"
104
+ :class="alignClass"
105
+ />
106
+
107
+ <div class="flex flex-col gap-1">
108
+ <!-- `for` trỏ vào chính nút: <button> là labelable element nên bấm vào chữ
109
+ cũng chọn được, không phải bọc nút vào trong <label>. -->
110
+ <Label
111
+ :for="controlId"
112
+ :class="props.disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'"
113
+ >
114
+ <slot>{{ props.label }}</slot>
115
+ </Label>
116
+
117
+ <p
118
+ v-if="hasDescription || $slots.description"
119
+ :id="descriptionId"
120
+ :class="cn('text-xs leading-snug text-muted-foreground', props.disabled && 'opacity-50')"
121
+ >
122
+ <slot name="description">
123
+ {{ props.description }}
124
+ </slot>
125
+ </p>
126
+ </div>
127
+ </div>
128
+ </template>