@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,28 @@
1
+ <script setup lang="ts">
2
+ import type { ClassProps } from '../../lib/types'
3
+ import { useSlots } from 'vue'
4
+ import { cn } from '../../lib/utils'
5
+ import Separator from '../separator/Separator.vue'
6
+
7
+ const props = defineProps<ClassProps>()
8
+ const slots = useSlots()
9
+ </script>
10
+
11
+ <template>
12
+ <!-- Chữ nằm ĐÈ lên đường kẻ và tự che nó bằng nền — không cắt đường kẻ làm
13
+ hai đoạn, nên chữ dài ngắn thế nào cũng không phải tính lại. -->
14
+ <div
15
+ data-slot="field-separator"
16
+ :data-content="Boolean(slots.default)"
17
+ :class="cn('relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2', props.class)"
18
+ >
19
+ <Separator class="absolute inset-0 top-1/2" />
20
+ <span
21
+ v-if="slots.default"
22
+ data-slot="field-separator-content"
23
+ class="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"
24
+ >
25
+ <slot />
26
+ </span>
27
+ </div>
28
+ </template>
@@ -0,0 +1,7 @@
1
+ export { fieldVariants, type FieldVariants } from '../../variants/components/field'
2
+ export { default as Field } from './Field.vue'
3
+ export { default as FieldDescription } from './FieldDescription.vue'
4
+ export { default as FieldGroup } from './FieldGroup.vue'
5
+ export { default as FieldLabel } from './FieldLabel.vue'
6
+
7
+ export { default as FieldSeparator } from './FieldSeparator.vue'
@@ -0,0 +1,126 @@
1
+ <script setup lang="ts">
2
+ import type { ClassProps } from '../../lib/types'
3
+ import { computed, ref, useId } from 'vue'
4
+ import { ChevronDown } from '../../lib/icons'
5
+ import { cn } from '../../lib/utils'
6
+
7
+ const props = withDefaults(
8
+ defineProps<ClassProps & {
9
+ legend?: string
10
+ /** Cho gập lại. Không bật thì phần chú giải không phải là nút. */
11
+ collapsible?: boolean
12
+ defaultOpen?: boolean
13
+ /** Điều khiển từ ngoài. `v-model:open`. */
14
+ open?: boolean
15
+ /**
16
+ * Khoá MỌI control bên trong — thuộc tính thật của `<fieldset>`, không phải
17
+ * một class. Không một cái `<div>` nào làm được việc này.
18
+ */
19
+ disabled?: boolean
20
+ }>(),
21
+ {
22
+ collapsible: false,
23
+ defaultOpen: true,
24
+ disabled: false,
25
+ /**
26
+ * Dòng này là bắt buộc, không phải cho gọn.
27
+ *
28
+ * Prop kiểu Boolean vắng mặt bị Vue ép thành `false`, nên thiếu nó thì mọi
29
+ * `<Fieldset>` không truyền `open` đều bị hiểu là "đang được điều khiển và
30
+ * đang đóng" — đúng cái bẫy đã ghi cho `SidebarProvider`, và cũng im lặng
31
+ * y như vậy. `defineModel` không nhận được default này nên phải khai tay
32
+ * prop cộng emit.
33
+ */
34
+ open: undefined,
35
+ },
36
+ )
37
+
38
+ const emit = defineEmits<{ 'update:open': [value: boolean] }>()
39
+
40
+ const internal = ref(props.defaultOpen)
41
+
42
+ const isOpen = computed({
43
+ get: () => props.open ?? internal.value,
44
+ set: (value: boolean) => {
45
+ internal.value = value
46
+ emit('update:open', value)
47
+ },
48
+ })
49
+
50
+ const contentId = useId()
51
+ </script>
52
+
53
+ <template>
54
+ <fieldset
55
+ :disabled="props.disabled || undefined"
56
+ :class="cn(
57
+ // `min-w-0` là BẮT BUỘC. Mặc định của trình duyệt cho `<fieldset>` là
58
+ // `min-inline-size: min-content`, nên một bảng rộng hay một chuỗi không
59
+ // ngắt được nằm bên trong sẽ nong cả nhóm ra rồi đẩy tràn cả biểu mẫu.
60
+ // Không `<div>` nào có luật đó, nên nó rất dễ bị bỏ sót.
61
+ 'min-w-0 rounded-xl border border-border px-4 pb-4',
62
+ props.disabled && 'opacity-60',
63
+ props.class,
64
+ )"
65
+ >
66
+ <!--
67
+ `<legend>` phải là con TRỰC TIẾP đầu tiên của `<fieldset>`.
68
+
69
+ Đó là điều kiện để trình duyệt cắt một khoảng trống trên đường viền cho
70
+ nó, và cũng là điều kiện để trình đọc màn hình ghép chú giải này vào TỪNG
71
+ control bên trong. Bọc nó vào một `<div>` là mất cả hai, mà nhìn thì chỉ
72
+ thấy mất cái khoảng trống.
73
+ -->
74
+ <legend class="px-1.5 text-sm font-medium">
75
+ <button
76
+ v-if="props.collapsible"
77
+ type="button"
78
+ :aria-expanded="isOpen"
79
+ :aria-controls="contentId"
80
+ class="-mx-1 flex items-center gap-1.5 rounded-md px-1 py-0.5 transition-colors hover:text-foreground/80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
81
+ @click="isOpen = !isOpen"
82
+ >
83
+ <ChevronDown
84
+ class="size-4 shrink-0 text-muted-foreground transition-transform duration-(--duration-base) motion-reduce:transition-none"
85
+ :class="!isOpen && '-rotate-90'"
86
+ aria-hidden="true"
87
+ />
88
+ <slot name="legend">
89
+ {{ props.legend }}
90
+ </slot>
91
+ </button>
92
+
93
+ <template v-else>
94
+ <slot name="legend">
95
+ {{ props.legend }}
96
+ </slot>
97
+ </template>
98
+ </legend>
99
+
100
+ <!--
101
+ Gập bằng `grid-template-rows: 0fr → 1fr`, KHÔNG dùng Collapsible của reka.
102
+
103
+ Không phải vì thích tự làm: `CollapsibleTrigger` bắt buộc nằm trong
104
+ `CollapsibleRoot`, mà cái nút thì phải nằm trong `<legend>`, còn `<legend>`
105
+ phải là con trực tiếp của `<fieldset>`. Ba ràng buộc đó không cùng thoả
106
+ được. Mẹo `0fr → 1fr` bù lại còn không cần đo chiều cao bằng JS.
107
+
108
+ `inert` chứ không phải `hidden`: hộp vẫn phải vẽ ra thì mới có gì để thu
109
+ lại, nhưng nội dung đang gập thì không được nhận Tab và không được đọc lên.
110
+ -->
111
+ <div
112
+ :id="contentId"
113
+ class="grid transition-[grid-template-rows] duration-(--duration-base) motion-reduce:transition-none"
114
+ :class="props.collapsible && !isOpen ? 'grid-rows-[0fr]' : 'grid-rows-[1fr]'"
115
+ :inert="props.collapsible && !isOpen ? true : undefined"
116
+ >
117
+ <!-- Khoảng đệm trên nằm ở ĐÂY chứ không ở `<fieldset>`: đặt ngoài thì
118
+ lúc gập nó không co lại và cái hộp vẫn cao lù lù một dải trống. -->
119
+ <div class="min-h-0 overflow-hidden">
120
+ <div class="pt-2">
121
+ <slot />
122
+ </div>
123
+ </div>
124
+ </div>
125
+ </fieldset>
126
+ </template>
@@ -0,0 +1 @@
1
+ export { default as Fieldset } from './Fieldset.vue'
@@ -0,0 +1,149 @@
1
+ <script setup lang="ts">
2
+ import type { ClassProps } from '../../lib/types'
3
+ import type { UploadFile } from '../../variants/lib/upload'
4
+ import { computed, ref } from 'vue'
5
+ import { useI18n } from '../../composables/useI18n'
6
+ import { focusRingWithin } from '../../lib/focus'
7
+ import { File as FileIcon, Upload, X } from '../../lib/icons'
8
+ import { cn } from '../../lib/utils'
9
+ import { formatFileSize, splitBySize, uploadFileId } from '../../variants/lib/upload'
10
+ import { Button } from '../button'
11
+ import { Progress } from '../progress'
12
+
13
+ const props = withDefaults(
14
+ defineProps<ClassProps & {
15
+ /** Giống thuộc tính `accept` của input, ví dụ `image/*,.pdf`. */
16
+ accept?: string
17
+ multiple?: boolean
18
+ /** Chặn tệp lớn hơn mức này, tính bằng byte. 0 nghĩa là không giới hạn. */
19
+ maxSize?: number
20
+ disabled?: boolean
21
+ hint?: string
22
+ }>(),
23
+ { multiple: false, maxSize: 0, disabled: false },
24
+ )
25
+ const emit = defineEmits<{
26
+ /** Tệp bị loại vì quá lớn — bên gọi tự quyết báo lỗi thế nào. */
27
+ reject: [files: File[]]
28
+ remove: [file: UploadFile]
29
+ }>()
30
+ const { t } = useI18n()
31
+ const files = defineModel<UploadFile[]>({ default: () => [] })
32
+
33
+ const dragging = ref(false)
34
+
35
+ const hintText = computed(() => {
36
+ if (props.hint)
37
+ return props.hint
38
+ const parts: string[] = []
39
+ if (props.accept)
40
+ parts.push(props.accept)
41
+ if (props.maxSize)
42
+ parts.push(t('ui.fileUpload.maxSize', { size: formatFileSize(props.maxSize) }))
43
+ return parts.join(' · ')
44
+ })
45
+
46
+ function add(incoming: FileList | null): void {
47
+ if (!incoming?.length)
48
+ return
49
+
50
+ const { accepted, tooBig } = splitBySize(Array.from(incoming), props.maxSize)
51
+
52
+ if (tooBig.length)
53
+ emit('reject', tooBig)
54
+
55
+ const next = accepted.map(file => ({ id: uploadFileId(file), file }))
56
+
57
+ // Lọc trùng theo tên + cỡ + thời điểm sửa: kéo cùng một tệp vào hai lần là
58
+ // thao tác nhầm, không phải ý định tải hai bản.
59
+ files.value = props.multiple
60
+ ? [...files.value, ...next.filter(n => !files.value.some(f => f.id === n.id))]
61
+ : next.slice(0, 1)
62
+ }
63
+
64
+ function onDrop(event: DragEvent): void {
65
+ dragging.value = false
66
+ if (props.disabled)
67
+ return
68
+ add(event.dataTransfer?.files ?? null)
69
+ }
70
+
71
+ function onPick(event: Event): void {
72
+ const target = event.target as HTMLInputElement
73
+ add(target.files)
74
+ // Xoá giá trị để chọn lại đúng tệp vừa gỡ vẫn kích hoạt `change`.
75
+ target.value = ''
76
+ }
77
+
78
+ function remove(file: UploadFile): void {
79
+ files.value = files.value.filter(f => f.id !== file.id)
80
+ emit('remove', file)
81
+ }
82
+ </script>
83
+
84
+ <template>
85
+ <div :class="cn('flex w-full flex-col gap-3', props.class)">
86
+ <!--
87
+ Vùng thả là `<label>`, không phải `<div>` gắn click.
88
+ Nhờ vậy bấm vào đâu trong vùng cũng mở hộp chọn tệp, và cái `<input>` bên
89
+ trong vẫn là control thật — vào được chuỗi Tab, nhận được Enter, và trình
90
+ đọc màn hình đọc ra "chọn tệp" chứ không phải một khối trống.
91
+ -->
92
+ <label
93
+ :class="cn(
94
+ 'flex cursor-pointer flex-col items-center justify-center gap-2 rounded-xl border border-dashed px-6 py-8 text-center transition-colors',
95
+ focusRingWithin,
96
+ dragging ? 'border-primary bg-primary/5' : 'border-border hover:bg-accent/40',
97
+ props.disabled && 'pointer-events-none opacity-50',
98
+ )"
99
+ @dragover.prevent="dragging = true"
100
+ @dragleave.prevent="dragging = false"
101
+ @drop.prevent="onDrop"
102
+ >
103
+ <span class="grid size-10 place-items-center rounded-full bg-muted text-muted-foreground">
104
+ <Upload class="size-4" />
105
+ </span>
106
+ <span class="text-sm font-medium">
107
+ <slot name="label">{{ t('ui.fileUpload.label') }}</slot>
108
+ </span>
109
+ <span v-if="hintText" class="text-xs text-muted-foreground">{{ hintText }}</span>
110
+
111
+ <input
112
+ type="file"
113
+ class="sr-only"
114
+ :accept="props.accept"
115
+ :multiple="props.multiple"
116
+ :disabled="props.disabled"
117
+ @change="onPick"
118
+ >
119
+ </label>
120
+
121
+ <ul v-if="files.length" class="flex flex-col gap-2">
122
+ <li
123
+ v-for="item in files"
124
+ :key="item.id"
125
+ class="flex items-center gap-3 rounded-lg border border-border bg-card px-3 py-2"
126
+ >
127
+ <FileIcon class="size-4 shrink-0 text-muted-foreground" />
128
+
129
+ <div class="flex min-w-0 flex-1 flex-col gap-1">
130
+ <div class="flex items-baseline justify-between gap-3">
131
+ <span class="truncate text-sm">{{ item.file.name }}</span>
132
+ <span class="shrink-0 text-xs tabular-nums text-muted-foreground">{{ formatFileSize(item.file.size) }}</span>
133
+ </div>
134
+ <Progress v-if="item.progress !== undefined && !item.error" :model-value="item.progress" class="h-1" />
135
+ <span v-if="item.error" class="text-xs text-destructive">{{ item.error }}</span>
136
+ </div>
137
+
138
+ <Button
139
+ variant="ghost"
140
+ size="icon-sm"
141
+ :aria-label="t('ui.fileUpload.remove', { name: item.file.name })"
142
+ @click="remove(item)"
143
+ >
144
+ <X />
145
+ </Button>
146
+ </li>
147
+ </ul>
148
+ </div>
149
+ </template>
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Kiểu và hai hàm thuần lên `src/variants` — bản React chép lại chúng thì
3
+ * hai cây có hai cách đặt id tệp và hai cách làm tròn cỡ tệp, mà không có gì
4
+ * phát hiện khi chúng lệch.
5
+ */
6
+ export { formatFileSize, splitBySize, type UploadFile, uploadFileId } from '../../variants/lib/upload'
7
+
8
+ export { default as FileUpload } from './FileUpload.vue'
@@ -0,0 +1,26 @@
1
+ <script setup lang="ts">
2
+ import type { ClassProps } from '../../lib/types'
3
+ import type { FloatLabelVariants } from './index'
4
+ import { cn } from '../../lib/utils'
5
+ import { floatLabelVariants } from './index'
6
+
7
+ const props = withDefaults(
8
+ defineProps<ClassProps & {
9
+ variant?: FloatLabelVariants['variant']
10
+ }>(),
11
+ { variant: 'over' },
12
+ )
13
+ </script>
14
+
15
+ <template>
16
+ <!--
17
+ Ô nhập trước, nhãn sau — thứ tự trong DOM là thứ tự đọc.
18
+
19
+ Nhãn được đặt tuyệt đối nên nhìn thì nó ở trên, nhưng `<label for>` mới là
20
+ thứ nối hai bên lại, và trình đọc màn hình đọc theo cái nối đó chứ không
21
+ theo toạ độ. Đảo lại cho "đúng thứ tự nhìn thấy" là thừa.
22
+ -->
23
+ <div :class="cn(floatLabelVariants({ variant: props.variant }), props.class)">
24
+ <slot />
25
+ </div>
26
+ </template>
@@ -0,0 +1,3 @@
1
+ export { floatLabelVariants, type FloatLabelVariants } from '../../variants/components/float-label'
2
+
3
+ export { default as FloatLabel } from './FloatLabel.vue'
@@ -0,0 +1,126 @@
1
+ <script setup lang="ts" generic="TValues extends Record<string, unknown>">
2
+ import type { ZodType } from 'zod'
3
+ import type { ClassProps } from '../../lib/types'
4
+ import type { FieldErrorMap } from './errors'
5
+ import { useForm } from '@tanstack/vue-form'
6
+ import { computed, ref } from 'vue'
7
+ import { cn } from '../../lib/utils'
8
+ import { provideFormContext } from './context'
9
+ import { firstErrorMessage } from './errors'
10
+
11
+ const props = defineProps<ClassProps & {
12
+ /**
13
+ * Schema zod. Truyền thẳng, không qua adapter nào: TanStack Form đọc
14
+ * **Standard Schema**, mà zod đã cài giao diện đó từ 3.24.
15
+ *
16
+ * PHẢI viết đủ hai tham số. Ô thứ hai của `ZodType` đổi nghĩa giữa hai bản
17
+ * lớn: v3 là `Def` nội bộ và `Input` nằm ở ô thứ ba, mặc định bằng `Output`;
18
+ * v4 ô thứ hai CHÍNH LÀ `Input`, và mặc định là `unknown` chứ không còn ăn
19
+ * theo `Output`. Nên `ZodType<TValues>` vẫn biên dịch được ở cả hai bản mà
20
+ * nghĩa đã khác hẳn — ở v4 nó là "nhận vào bất cứ thứ gì", trong khi
21
+ * TanStack Form đòi `StandardSchemaV1<TValues, unknown>`, tức đầu vào phải
22
+ * đúng `TValues`.
23
+ */
24
+ schema: ZodType<TValues, TValues>
25
+ initialValues?: Record<string, unknown>
26
+ }>()
27
+
28
+ const emit = defineEmits<{
29
+ submit: [values: TValues]
30
+ }>()
31
+
32
+ const form = useForm({
33
+ defaultValues: (props.initialValues ?? {}) as TValues,
34
+ /**
35
+ * Chỉ khai `onChange` là đủ cho cả lúc bấm gửi: logic mặc định của form-core
36
+ * chạy onChange + onBlur + onSubmit khi nguyên nhân là `submit`.
37
+ *
38
+ * `onChange` ở đây KHÔNG có nghĩa là người dùng bị báo lỗi từng phím gõ —
39
+ * `FormField` chỉ hiện lỗi sau khi field đã blur hoặc form đã gửi. Tính sớm
40
+ * mà hiện muộn là cố ý: nhờ tính sớm nên lúc đang sửa, lỗi biến mất ngay
41
+ * khoảnh khắc gõ đúng. Xem chú thích `showValidation` trong `FormField.vue`.
42
+ */
43
+ validators: { onChange: props.schema },
44
+ onSubmit: ({ value }) => {
45
+ emit('submit', value)
46
+ },
47
+ })
48
+
49
+ /**
50
+ * Lỗi do server trả về giữ **ở đây**, không ghi vào errorMap của TanStack.
51
+ *
52
+ * Thư viện có sẵn khoá `onServer` cho đúng việc này, nhưng kiểu của nó suy ra
53
+ * từ tham số generic `TOnServer`, mà `TOnServer` chỉ đến từ tham số type tường
54
+ * minh — `FormValidators` không có trường `onServer` để suy ra. Muốn gọi
55
+ * `setErrorMap({ onServer })` thì phải viết đủ **12 tham số generic** cho
56
+ * `useForm`. Một `ref` đứng ngoài rẻ hơn nhiều.
57
+ *
58
+ * Còn được thêm một thứ: `setErrorMap` chỉ ghi vào field **đang mount** và bỏ
59
+ * qua tên không khớp, nên lỗi cho field nằm trong tab chưa mở sẽ rơi mất.
60
+ * Map riêng thì field nhận được lỗi ngay khi nó xuất hiện.
61
+ */
62
+ const serverErrors = ref<Record<string, string>>({})
63
+
64
+ const submissionAttempts = form.useSelector(state => state.submissionAttempts)
65
+ const submitted = computed(() => submissionAttempts.value > 0)
66
+
67
+ provideFormContext({ api: form, serverErrors, submitted })
68
+
69
+ const isSubmitting = form.useSelector(state => state.isSubmitting)
70
+ const values = form.useSelector(state => state.values)
71
+ const fieldMeta = form.useSelector(state => state.fieldMeta)
72
+
73
+ /** Gộp về `{ tên field: thông báo }` — giữ nguyên hình dạng slot prop cũ. */
74
+ const errors = computed<Record<string, string | undefined>>(() => {
75
+ const result: Record<string, string | undefined> = { ...serverErrors.value }
76
+ const entries = Object.entries(fieldMeta.value) as Array<[string, { errorMap?: FieldErrorMap }]>
77
+
78
+ for (const [name, meta] of entries) {
79
+ if (result[name])
80
+ continue
81
+
82
+ const message = firstErrorMessage(meta.errorMap)
83
+ if (message)
84
+ result[name] = message
85
+ }
86
+
87
+ return result
88
+ })
89
+
90
+ function onSubmit(event: Event): void {
91
+ event.preventDefault()
92
+ void form.handleSubmit()
93
+ }
94
+
95
+ /**
96
+ * Map lỗi validation của .NET (ModelState / ProblemDetails.errors) ngược về
97
+ * từng field.
98
+ */
99
+ function setErrors(fields: Record<string, string>): void {
100
+ serverErrors.value = { ...serverErrors.value, ...fields }
101
+ }
102
+
103
+ function setFieldError(name: string, message: string): void {
104
+ setErrors({ [name]: message })
105
+ }
106
+
107
+ function resetForm(): void {
108
+ serverErrors.value = {}
109
+ form.reset()
110
+ }
111
+
112
+ defineExpose({
113
+ resetForm,
114
+ setErrors,
115
+ setFieldError,
116
+ values,
117
+ errors,
118
+ isSubmitting,
119
+ })
120
+ </script>
121
+
122
+ <template>
123
+ <form novalidate :class="cn('flex flex-col gap-4', props.class)" @submit="onSubmit">
124
+ <slot :is-submitting="isSubmitting" :errors="errors" :values="values" />
125
+ </form>
126
+ </template>
@@ -0,0 +1,12 @@
1
+ <script setup lang="ts">
2
+ import type { ClassProps } from '../../lib/types'
3
+ import { cn } from '../../lib/utils'
4
+
5
+ const props = defineProps<ClassProps & { id?: string }>()
6
+ </script>
7
+
8
+ <template>
9
+ <p :id="props.id" :class="cn('text-xs text-muted-foreground', props.class)">
10
+ <slot />
11
+ </p>
12
+ </template>
@@ -0,0 +1,127 @@
1
+ <script setup lang="ts" generic="T = string">
2
+ import type { ClassProps } from '../../lib/types'
3
+ import type { FieldBindings } from './types'
4
+ import { useField } from '@tanstack/vue-form'
5
+ import { computed, useId } from 'vue'
6
+ import { cn } from '../../lib/utils'
7
+ import { useFormContext } from './context'
8
+ import { firstErrorMessage } from './errors'
9
+ import FormDescription from './FormDescription.vue'
10
+ import FormItem from './FormItem.vue'
11
+ import FormLabel from './FormLabel.vue'
12
+ import FormMessage from './FormMessage.vue'
13
+
14
+ const props = defineProps<ClassProps & {
15
+ /** Tên field trong schema. Hỗ trợ path lồng nhau: "address.city" */
16
+ name: string
17
+ label?: string
18
+ description?: string
19
+ required?: boolean
20
+ /** Giá trị khởi tạo. Cũng là chỗ để TS suy ra kiểu T cho slot prop. */
21
+ defaultValue?: T
22
+ }>()
23
+
24
+ const { api: form, serverErrors, submitted } = useFormContext()
25
+
26
+ /**
27
+ * Đọc `props` **một lần**: `useField` trải opts vào `new FieldApi()` ngay lúc
28
+ * gọi, và cái `watch` bên trong nó theo dõi chính object opts nên object tĩnh
29
+ * không bao giờ kích hoạt. Đổi `name` giữa chừng sẽ không đổi field — muốn vậy
30
+ * thì đặt `:key` để dựng lại component.
31
+ */
32
+ const { api } = useField({
33
+ form,
34
+ name: props.name,
35
+ defaultValue: props.defaultValue,
36
+ })
37
+
38
+ const controlId = useId()
39
+ const descriptionId = computed(() => (props.description ? `${controlId}-description` : undefined))
40
+
41
+ /**
42
+ * Lỗi được TÍNH liên tục nhưng chỉ được HIỆN sau khi field đã rời focus một
43
+ * lần, hoặc sau khi form đã bấm gửi.
44
+ *
45
+ * Đây là chỗ đặt "thời điểm validate", và nó nằm ở tầng hiển thị chứ không
46
+ * phải ở validator. Kết quả ra đúng ba giai đoạn:
47
+ *
48
+ * | lúc | người dùng thấy |
49
+ * |---|---|
50
+ * | đang gõ lần đầu | **không gì cả** — chưa gõ xong đã báo sai là quấy rầy |
51
+ * | vừa rời ô | lỗi hiện ra |
52
+ * | đang sửa lỗi | lỗi **biến mất ngay** khi gõ đúng, không phải chờ blur |
53
+ *
54
+ * Giai đoạn ba là lý do không chuyển thẳng validator sang `onBlur`: làm vậy
55
+ * thì lúc sửa, người dùng gõ đúng rồi mà thông báo đỏ vẫn đứng đó tới khi họ
56
+ * bấm ra ngoài — không biết mình đã sửa xong chưa.
57
+ *
58
+ * Cách này còn tránh được một chuyện: validator ở TanStack là **cấp form**,
59
+ * nên không thể bật `onChange` cho riêng một field. Gate ở tầng hiển thị thì
60
+ * mỗi FormField tự quyết định phần của mình.
61
+ */
62
+ const showValidation = computed(() => api.state.meta.isBlurred || submitted.value)
63
+
64
+ // Lỗi server thắng: nó là thông tin mà client không tự suy ra được, và nó chỉ
65
+ // đến sau một lần gửi nên không bao giờ là "quá sớm".
66
+ const errorMessage = computed(() => (
67
+ serverErrors.value[props.name]
68
+ ?? (showValidation.value ? firstErrorMessage(api.state.meta.errorMap) : undefined)
69
+ ))
70
+
71
+ const messageId = computed(() => (errorMessage.value ? `${controlId}-message` : undefined))
72
+ const invalid = computed(() => Boolean(errorMessage.value))
73
+
74
+ const value = computed(() => api.state.value as T)
75
+
76
+ const describedBy = computed(() => {
77
+ const ids = [descriptionId.value, messageId.value].filter(Boolean)
78
+ return ids.length > 0 ? ids.join(' ') : undefined
79
+ })
80
+
81
+ function setValue(next: unknown): void {
82
+ api.handleChange(next)
83
+
84
+ /**
85
+ * Lỗi server phải mất ngay khi người dùng sửa field, nếu không "email đã tồn
86
+ * tại" bám lại cả sau khi họ gõ email khác. Client không có cách nào biết lỗi
87
+ * đó còn đúng hay không, nên bỏ đi là lựa chọn duy nhất trung thực.
88
+ */
89
+ if (serverErrors.value[props.name] !== undefined) {
90
+ const { [props.name]: _removed, ...rest } = serverErrors.value
91
+ serverErrors.value = rest
92
+ }
93
+ }
94
+
95
+ const field = computed<FieldBindings<T>>(() => ({
96
+ 'modelValue': value.value,
97
+ 'onUpdate:modelValue': setValue,
98
+ 'onBlur': () => api.handleBlur(),
99
+ 'name': props.name,
100
+ 'id': controlId,
101
+ 'aria-describedby': describedBy.value,
102
+ 'aria-invalid': invalid.value ? true : undefined,
103
+ }))
104
+ </script>
105
+
106
+ <template>
107
+ <FormItem :class="cn(props.class)">
108
+ <FormLabel v-if="props.label" :for="controlId" :required="props.required">
109
+ {{ props.label }}
110
+ </FormLabel>
111
+
112
+ <slot
113
+ :id="controlId"
114
+ :field="field"
115
+ :value="value"
116
+ :set-value="setValue"
117
+ :error-message="errorMessage"
118
+ :invalid="invalid"
119
+ />
120
+
121
+ <FormDescription v-if="props.description" :id="descriptionId">
122
+ {{ props.description }}
123
+ </FormDescription>
124
+
125
+ <FormMessage :id="messageId" :message="errorMessage" />
126
+ </FormItem>
127
+ </template>
@@ -0,0 +1,12 @@
1
+ <script setup lang="ts">
2
+ import type { ClassProps } from '../../lib/types'
3
+ import { cn } from '../../lib/utils'
4
+
5
+ const props = defineProps<ClassProps>()
6
+ </script>
7
+
8
+ <template>
9
+ <div :class="cn('flex flex-col gap-1.5', props.class)">
10
+ <slot />
11
+ </div>
12
+ </template>
@@ -0,0 +1,14 @@
1
+ <script setup lang="ts">
2
+ import type { ClassProps } from '../../lib/types'
3
+ import { cn } from '../../lib/utils'
4
+ import Label from '../label/Label.vue'
5
+
6
+ const props = defineProps<ClassProps & { for?: string, required?: boolean }>()
7
+ </script>
8
+
9
+ <template>
10
+ <Label :for="props.for" :class="cn('text-sm', props.class)">
11
+ <slot />
12
+ <span v-if="props.required" class="ml-0.5 text-destructive" aria-hidden="true">*</span>
13
+ </Label>
14
+ </template>