@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.
- package/README.md +233 -0
- package/index.ts +8 -0
- package/package.json +68 -0
- package/src/components/accordion/Accordion.vue +45 -0
- package/src/components/accordion/AccordionContent.vue +31 -0
- package/src/components/accordion/AccordionItem.vue +26 -0
- package/src/components/accordion/AccordionTrigger.vue +39 -0
- package/src/components/accordion/index.ts +18 -0
- package/src/components/alert/Alert.vue +17 -0
- package/src/components/alert/AlertDescription.vue +12 -0
- package/src/components/alert/AlertTitle.vue +12 -0
- package/src/components/alert/index.ts +5 -0
- package/src/components/avatar/Avatar.vue +32 -0
- package/src/components/avatar/AvatarFallback.vue +23 -0
- package/src/components/avatar/AvatarImage.vue +18 -0
- package/src/components/avatar/index.ts +3 -0
- package/src/components/badge/Badge.vue +25 -0
- package/src/components/badge/index.ts +3 -0
- package/src/components/breadcrumb/Breadcrumb.vue +14 -0
- package/src/components/breadcrumb/BreadcrumbItem.vue +12 -0
- package/src/components/breadcrumb/BreadcrumbLink.vue +19 -0
- package/src/components/breadcrumb/BreadcrumbPage.vue +12 -0
- package/src/components/breadcrumb/BreadcrumbSeparator.vue +15 -0
- package/src/components/breadcrumb/index.ts +5 -0
- package/src/components/button/Button.vue +55 -0
- package/src/components/button/index.ts +3 -0
- package/src/components/button-group/ButtonGroup.vue +29 -0
- package/src/components/button-group/ButtonGroupSeparator.vue +30 -0
- package/src/components/button-group/ButtonGroupText.vue +29 -0
- package/src/components/button-group/SplitButton.vue +103 -0
- package/src/components/button-group/SplitButtonItems.vue +50 -0
- package/src/components/button-group/index.ts +34 -0
- package/src/components/calendar/Calendar.vue +120 -0
- package/src/components/calendar/RangeCalendar.vue +127 -0
- package/src/components/calendar/index.ts +2 -0
- package/src/components/card/Card.vue +12 -0
- package/src/components/card/CardAction.vue +12 -0
- package/src/components/card/CardContent.vue +12 -0
- package/src/components/card/CardDescription.vue +12 -0
- package/src/components/card/CardFooter.vue +12 -0
- package/src/components/card/CardHeader.vue +15 -0
- package/src/components/card/CardTitle.vue +12 -0
- package/src/components/card/index.ts +7 -0
- package/src/components/carousel/Carousel.vue +164 -0
- package/src/components/carousel/CarouselContent.vue +43 -0
- package/src/components/carousel/CarouselDots.vue +42 -0
- package/src/components/carousel/CarouselItem.vue +58 -0
- package/src/components/carousel/CarouselNext.vue +45 -0
- package/src/components/carousel/CarouselPrevious.vue +45 -0
- package/src/components/carousel/index.ts +44 -0
- package/src/components/cascade-select/CascadeSelect.vue +314 -0
- package/src/components/cascade-select/index.ts +11 -0
- package/src/components/chart/AreaChart.vue +201 -0
- package/src/components/chart/BarChart.vue +389 -0
- package/src/components/chart/ChartContainer.vue +127 -0
- package/src/components/chart/ChartCrosshair.vue +92 -0
- package/src/components/chart/ChartLegend.vue +63 -0
- package/src/components/chart/ChartTooltipCard.vue +49 -0
- package/src/components/chart/LineChart.vue +214 -0
- package/src/components/chart/PieChart.vue +280 -0
- package/src/components/chart/RadarChart.vue +291 -0
- package/src/components/chart/RadialChart.vue +354 -0
- package/src/components/chart/index.ts +30 -0
- package/src/components/chart/polar.ts +5 -0
- package/src/components/chart/tooltip.ts +5 -0
- package/src/components/chart/types.ts +52 -0
- package/src/components/chart/useChartSeries.ts +138 -0
- package/src/components/chart/xy-label.ts +5 -0
- package/src/components/checkbox/Checkbox.vue +81 -0
- package/src/components/checkbox/CheckboxField.vue +129 -0
- package/src/components/checkbox/CheckboxGroup.vue +62 -0
- package/src/components/checkbox/index.ts +21 -0
- package/src/components/chip/Chip.vue +73 -0
- package/src/components/chip/index.ts +3 -0
- package/src/components/code-block/CodeBlock.vue +100 -0
- package/src/components/code-block/index.ts +4 -0
- package/src/components/collapsible/Collapsible.vue +15 -0
- package/src/components/collapsible/CollapsibleContent.vue +26 -0
- package/src/components/collapsible/CollapsibleTrigger.vue +12 -0
- package/src/components/collapsible/index.ts +3 -0
- package/src/components/combobox/Combobox.vue +23 -0
- package/src/components/combobox/ComboboxAnchor.vue +20 -0
- package/src/components/combobox/ComboboxEmpty.vue +21 -0
- package/src/components/combobox/ComboboxGroup.vue +23 -0
- package/src/components/combobox/ComboboxInput.vue +32 -0
- package/src/components/combobox/ComboboxItem.vue +38 -0
- package/src/components/combobox/ComboboxList.vue +43 -0
- package/src/components/combobox/ComboboxSeparator.vue +18 -0
- package/src/components/combobox/ComboboxTrigger.vue +12 -0
- package/src/components/combobox/index.ts +9 -0
- package/src/components/command/Command.vue +54 -0
- package/src/components/command/CommandDialog.vue +41 -0
- package/src/components/command/CommandEmpty.vue +19 -0
- package/src/components/command/CommandGroup.vue +28 -0
- package/src/components/command/CommandInput.vue +25 -0
- package/src/components/command/CommandItem.vue +40 -0
- package/src/components/command/CommandList.vue +13 -0
- package/src/components/command/CommandSeparator.vue +11 -0
- package/src/components/command/CommandShortcut.vue +13 -0
- package/src/components/command/index.ts +34 -0
- package/src/components/command-block/CommandBlock.vue +90 -0
- package/src/components/command-block/index.ts +2 -0
- package/src/components/confirm-dialog/ConfirmDialog.vue +37 -0
- package/src/components/confirm-dialog/index.ts +1 -0
- package/src/components/confirm-popup/ConfirmPopup.vue +123 -0
- package/src/components/confirm-popup/index.ts +1 -0
- package/src/components/context-menu/ContextMenu.vue +15 -0
- package/src/components/context-menu/ContextMenuCheckboxItem.vue +30 -0
- package/src/components/context-menu/ContextMenuContent.vue +36 -0
- package/src/components/context-menu/ContextMenuGroup.vue +12 -0
- package/src/components/context-menu/ContextMenuItem.vue +32 -0
- package/src/components/context-menu/ContextMenuLabel.vue +21 -0
- package/src/components/context-menu/ContextMenuRadioGroup.vue +15 -0
- package/src/components/context-menu/ContextMenuRadioItem.vue +30 -0
- package/src/components/context-menu/ContextMenuSeparator.vue +19 -0
- package/src/components/context-menu/ContextMenuShortcut.vue +13 -0
- package/src/components/context-menu/ContextMenuSub.vue +15 -0
- package/src/components/context-menu/ContextMenuSubContent.vue +26 -0
- package/src/components/context-menu/ContextMenuSubTrigger.vue +26 -0
- package/src/components/context-menu/ContextMenuTrigger.vue +12 -0
- package/src/components/context-menu/index.ts +14 -0
- package/src/components/data-table/DataTable.vue +458 -0
- package/src/components/data-table/DataTableFacetedFilter.vue +97 -0
- package/src/components/data-table/DataTablePagination.vue +103 -0
- package/src/components/data-table/DataTableViewOptions.vue +69 -0
- package/src/components/data-table/features.ts +87 -0
- package/src/components/data-table/index.ts +27 -0
- package/src/components/data-table/pagination-adapter.ts +67 -0
- package/src/components/data-table/selection-column.ts +48 -0
- package/src/components/data-table/types.ts +36 -0
- package/src/components/data-view/DataView.vue +136 -0
- package/src/components/data-view/index.ts +1 -0
- package/src/components/date-picker/DatePicker.vue +117 -0
- package/src/components/date-picker/DateRangePicker.vue +122 -0
- package/src/components/date-picker/index.ts +2 -0
- package/src/components/deferred/Deferred.vue +80 -0
- package/src/components/deferred/index.ts +1 -0
- package/src/components/dialog/Dialog.vue +15 -0
- package/src/components/dialog/DialogClose.vue +12 -0
- package/src/components/dialog/DialogContent.vue +46 -0
- package/src/components/dialog/DialogDescription.vue +19 -0
- package/src/components/dialog/DialogFooter.vue +12 -0
- package/src/components/dialog/DialogHeader.vue +12 -0
- package/src/components/dialog/DialogTitle.vue +19 -0
- package/src/components/dialog/DialogTrigger.vue +12 -0
- package/src/components/dialog/index.ts +10 -0
- package/src/components/doc-example/DocExample.vue +41 -0
- package/src/components/doc-example/index.ts +1 -0
- package/src/components/doc-text/DocText.vue +64 -0
- package/src/components/doc-text/index.ts +1 -0
- package/src/components/dropdown-menu/DropdownMenu.vue +15 -0
- package/src/components/dropdown-menu/DropdownMenuCheckboxItem.vue +33 -0
- package/src/components/dropdown-menu/DropdownMenuContent.vue +36 -0
- package/src/components/dropdown-menu/DropdownMenuGroup.vue +12 -0
- package/src/components/dropdown-menu/DropdownMenuItem.vue +32 -0
- package/src/components/dropdown-menu/DropdownMenuLabel.vue +24 -0
- package/src/components/dropdown-menu/DropdownMenuRadioGroup.vue +15 -0
- package/src/components/dropdown-menu/DropdownMenuRadioItem.vue +33 -0
- package/src/components/dropdown-menu/DropdownMenuSeparator.vue +19 -0
- package/src/components/dropdown-menu/DropdownMenuShortcut.vue +13 -0
- package/src/components/dropdown-menu/DropdownMenuSub.vue +15 -0
- package/src/components/dropdown-menu/DropdownMenuSubContent.vue +29 -0
- package/src/components/dropdown-menu/DropdownMenuSubTrigger.vue +26 -0
- package/src/components/dropdown-menu/DropdownMenuTrigger.vue +12 -0
- package/src/components/dropdown-menu/index.ts +14 -0
- package/src/components/editable/Editable.vue +41 -0
- package/src/components/editable/EditableArea.vue +22 -0
- package/src/components/editable/EditableCancelTrigger.vue +40 -0
- package/src/components/editable/EditableEditTrigger.vue +40 -0
- package/src/components/editable/EditableInput.vue +28 -0
- package/src/components/editable/EditablePreview.vue +34 -0
- package/src/components/editable/EditableSubmitTrigger.vue +40 -0
- package/src/components/editable/index.ts +17 -0
- package/src/components/empty/Empty.vue +17 -0
- package/src/components/empty/EmptyActions.vue +14 -0
- package/src/components/empty/EmptyDescription.vue +12 -0
- package/src/components/empty/EmptyMedia.vue +16 -0
- package/src/components/empty/EmptyTitle.vue +17 -0
- package/src/components/empty/index.ts +7 -0
- package/src/components/field/Field.vue +22 -0
- package/src/components/field/FieldDescription.vue +20 -0
- package/src/components/field/FieldGroup.vue +22 -0
- package/src/components/field/FieldLabel.vue +27 -0
- package/src/components/field/FieldSeparator.vue +28 -0
- package/src/components/field/index.ts +7 -0
- package/src/components/fieldset/Fieldset.vue +126 -0
- package/src/components/fieldset/index.ts +1 -0
- package/src/components/file-upload/FileUpload.vue +149 -0
- package/src/components/file-upload/index.ts +8 -0
- package/src/components/float-label/FloatLabel.vue +26 -0
- package/src/components/float-label/index.ts +3 -0
- package/src/components/form/Form.vue +126 -0
- package/src/components/form/FormDescription.vue +12 -0
- package/src/components/form/FormField.vue +127 -0
- package/src/components/form/FormItem.vue +12 -0
- package/src/components/form/FormLabel.vue +14 -0
- package/src/components/form/FormMessage.vue +31 -0
- package/src/components/form/context.ts +54 -0
- package/src/components/form/errors.ts +6 -0
- package/src/components/form/index.ts +7 -0
- package/src/components/form/types.ts +55 -0
- package/src/components/hover-card/HoverCard.vue +27 -0
- package/src/components/hover-card/HoverCardContent.vue +36 -0
- package/src/components/hover-card/HoverCardTrigger.vue +12 -0
- package/src/components/hover-card/index.ts +3 -0
- package/src/components/ifta-label/IftaLabel.vue +26 -0
- package/src/components/ifta-label/index.ts +1 -0
- package/src/components/index.ts +89 -0
- package/src/components/input/Input.vue +87 -0
- package/src/components/input/index.ts +1 -0
- package/src/components/input-color/ColorArea.vue +36 -0
- package/src/components/input-color/ColorField.vue +32 -0
- package/src/components/input-color/ColorSlider.vue +31 -0
- package/src/components/input-color/InputColor.vue +65 -0
- package/src/components/input-color/index.ts +16 -0
- package/src/components/input-group/InputGroup.vue +26 -0
- package/src/components/input-group/InputGroupAddon.vue +32 -0
- package/src/components/input-group/InputGroupInput.vue +33 -0
- package/src/components/input-group/index.ts +17 -0
- package/src/components/input-number/InputNumber.vue +82 -0
- package/src/components/input-number/index.ts +1 -0
- package/src/components/input-otp/InputOtp.vue +186 -0
- package/src/components/input-otp/index.ts +1 -0
- package/src/components/input-password/InputPassword.vue +116 -0
- package/src/components/input-password/index.ts +5 -0
- package/src/components/input-tags/InputTags.vue +38 -0
- package/src/components/input-tags/InputTagsClear.vue +39 -0
- package/src/components/input-tags/InputTagsInput.vue +27 -0
- package/src/components/input-tags/InputTagsItem.vue +40 -0
- package/src/components/input-tags/InputTagsItemDelete.vue +32 -0
- package/src/components/input-tags/InputTagsItemText.vue +18 -0
- package/src/components/input-tags/index.ts +16 -0
- package/src/components/kbd/Kbd.vue +17 -0
- package/src/components/kbd/KbdGroup.vue +15 -0
- package/src/components/kbd/index.ts +4 -0
- package/src/components/label/Label.vue +23 -0
- package/src/components/label/index.ts +1 -0
- package/src/components/listbox/Listbox.vue +42 -0
- package/src/components/listbox/ListboxContent.vue +22 -0
- package/src/components/listbox/ListboxFilter.vue +34 -0
- package/src/components/listbox/ListboxGroup.vue +31 -0
- package/src/components/listbox/ListboxItem.vue +46 -0
- package/src/components/listbox/index.ts +19 -0
- package/src/components/mask/Mask.vue +205 -0
- package/src/components/mask/index.ts +15 -0
- package/src/components/menubar/Menubar.vue +26 -0
- package/src/components/menubar/MenubarCheckboxItem.vue +30 -0
- package/src/components/menubar/MenubarContent.vue +52 -0
- package/src/components/menubar/MenubarGroup.vue +12 -0
- package/src/components/menubar/MenubarItem.vue +32 -0
- package/src/components/menubar/MenubarLabel.vue +21 -0
- package/src/components/menubar/MenubarMenu.vue +12 -0
- package/src/components/menubar/MenubarRadioGroup.vue +15 -0
- package/src/components/menubar/MenubarRadioItem.vue +30 -0
- package/src/components/menubar/MenubarSeparator.vue +12 -0
- package/src/components/menubar/MenubarShortcut.vue +13 -0
- package/src/components/menubar/MenubarSub.vue +15 -0
- package/src/components/menubar/MenubarSubContent.vue +26 -0
- package/src/components/menubar/MenubarSubTrigger.vue +26 -0
- package/src/components/menubar/MenubarTrigger.vue +30 -0
- package/src/components/menubar/index.ts +29 -0
- package/src/components/navigation-menu/NavigationMenu.vue +43 -0
- package/src/components/navigation-menu/NavigationMenuContent.vue +63 -0
- package/src/components/navigation-menu/NavigationMenuIndicator.vue +31 -0
- package/src/components/navigation-menu/NavigationMenuItem.vue +20 -0
- package/src/components/navigation-menu/NavigationMenuLink.vue +34 -0
- package/src/components/navigation-menu/NavigationMenuList.vue +23 -0
- package/src/components/navigation-menu/NavigationMenuTrigger.vue +38 -0
- package/src/components/navigation-menu/NavigationMenuViewport.vue +34 -0
- package/src/components/navigation-menu/index.ts +18 -0
- package/src/components/order-list/OrderList.vue +168 -0
- package/src/components/order-list/SelectableList.vue +200 -0
- package/src/components/order-list/index.ts +1 -0
- package/src/components/pagination/Pagination.vue +133 -0
- package/src/components/pagination/index.ts +1 -0
- package/src/components/panel/Panel.vue +69 -0
- package/src/components/panel/index.ts +1 -0
- package/src/components/pick-list/PickList.vue +211 -0
- package/src/components/pick-list/index.ts +1 -0
- package/src/components/popover/Popover.vue +15 -0
- package/src/components/popover/PopoverAnchor.vue +12 -0
- package/src/components/popover/PopoverContent.vue +39 -0
- package/src/components/popover/PopoverTrigger.vue +12 -0
- package/src/components/popover/index.ts +4 -0
- package/src/components/progress/Progress.vue +39 -0
- package/src/components/progress/index.ts +1 -0
- package/src/components/props-table/PropsTable.vue +62 -0
- package/src/components/props-table/index.ts +2 -0
- package/src/components/radio-group/RadioGroup.vue +52 -0
- package/src/components/radio-group/RadioGroupItem.vue +80 -0
- package/src/components/radio-group/RadioGroupItemField.vue +128 -0
- package/src/components/radio-group/index.ts +21 -0
- package/src/components/rating/Rating.vue +70 -0
- package/src/components/rating/RatingItem.vue +26 -0
- package/src/components/rating/RatingItemIndicator.vue +28 -0
- package/src/components/rating/RatingStar.vue +16 -0
- package/src/components/rating/index.ts +13 -0
- package/src/components/scroll-area/ScrollArea.vue +52 -0
- package/src/components/scroll-area/index.ts +1 -0
- package/src/components/search-input/SearchInput.vue +46 -0
- package/src/components/search-input/index.ts +1 -0
- package/src/components/select/Select.vue +15 -0
- package/src/components/select/SelectContent.vue +67 -0
- package/src/components/select/SelectGroup.vue +20 -0
- package/src/components/select/SelectItem.vue +59 -0
- package/src/components/select/SelectLabel.vue +20 -0
- package/src/components/select/SelectSeparator.vue +18 -0
- package/src/components/select/SelectTrigger.vue +101 -0
- package/src/components/select/SelectValue.vue +46 -0
- package/src/components/select/index.ts +8 -0
- package/src/components/separator/Separator.vue +25 -0
- package/src/components/separator/index.ts +1 -0
- package/src/components/sheet/Sheet.vue +15 -0
- package/src/components/sheet/SheetClose.vue +12 -0
- package/src/components/sheet/SheetContent.vue +45 -0
- package/src/components/sheet/SheetDescription.vue +19 -0
- package/src/components/sheet/SheetFooter.vue +12 -0
- package/src/components/sheet/SheetHeader.vue +12 -0
- package/src/components/sheet/SheetTitle.vue +19 -0
- package/src/components/sheet/SheetTrigger.vue +12 -0
- package/src/components/sheet/index.ts +10 -0
- package/src/components/sidebar/Sidebar.vue +108 -0
- package/src/components/sidebar/SidebarContent.vue +19 -0
- package/src/components/sidebar/SidebarFooter.vue +19 -0
- package/src/components/sidebar/SidebarGroup.vue +19 -0
- package/src/components/sidebar/SidebarGroupAction.vue +28 -0
- package/src/components/sidebar/SidebarGroupContent.vue +19 -0
- package/src/components/sidebar/SidebarGroupLabel.vue +25 -0
- package/src/components/sidebar/SidebarHeader.vue +19 -0
- package/src/components/sidebar/SidebarInput.vue +15 -0
- package/src/components/sidebar/SidebarInset.vue +22 -0
- package/src/components/sidebar/SidebarMenu.vue +19 -0
- package/src/components/sidebar/SidebarMenuAction.vue +41 -0
- package/src/components/sidebar/SidebarMenuBadge.vue +24 -0
- package/src/components/sidebar/SidebarMenuButton.vue +94 -0
- package/src/components/sidebar/SidebarMenuItem.vue +19 -0
- package/src/components/sidebar/SidebarMenuSkeleton.vue +36 -0
- package/src/components/sidebar/SidebarMenuSub.vue +22 -0
- package/src/components/sidebar/SidebarMenuSubButton.vue +39 -0
- package/src/components/sidebar/SidebarMenuSubItem.vue +19 -0
- package/src/components/sidebar/SidebarProvider.vue +114 -0
- package/src/components/sidebar/SidebarRail.vue +37 -0
- package/src/components/sidebar/SidebarSeparator.vue +15 -0
- package/src/components/sidebar/SidebarTrigger.vue +27 -0
- package/src/components/sidebar/context.ts +39 -0
- package/src/components/sidebar/index.ts +34 -0
- package/src/components/skeleton/Skeleton.vue +10 -0
- package/src/components/skeleton/index.ts +1 -0
- package/src/components/skip-link/SkipLink.vue +45 -0
- package/src/components/skip-link/index.ts +1 -0
- package/src/components/slider/Slider.vue +57 -0
- package/src/components/slider/index.ts +3 -0
- package/src/components/speed-dial/SpeedDial.vue +362 -0
- package/src/components/speed-dial/index.ts +28 -0
- package/src/components/spinner/Spinner.vue +20 -0
- package/src/components/spinner/index.ts +1 -0
- package/src/components/splitter/Splitter.vue +24 -0
- package/src/components/splitter/SplitterHandle.vue +51 -0
- package/src/components/splitter/SplitterPanel.vue +30 -0
- package/src/components/splitter/index.ts +14 -0
- package/src/components/stepper/Stepper.vue +34 -0
- package/src/components/stepper/StepperItem.vue +113 -0
- package/src/components/stepper/index.ts +2 -0
- package/src/components/switch/Switch.vue +35 -0
- package/src/components/switch/index.ts +1 -0
- package/src/components/table/Table.vue +24 -0
- package/src/components/table/TableBody.vue +12 -0
- package/src/components/table/TableCell.vue +12 -0
- package/src/components/table/TableEmpty.vue +20 -0
- package/src/components/table/TableHead.vue +17 -0
- package/src/components/table/TableHeader.vue +12 -0
- package/src/components/table/TableRow.vue +19 -0
- package/src/components/table/index.ts +7 -0
- package/src/components/tabs/Tabs.vue +23 -0
- package/src/components/tabs/TabsContent.vue +23 -0
- package/src/components/tabs/TabsList.vue +26 -0
- package/src/components/tabs/TabsTrigger.vue +30 -0
- package/src/components/tabs/index.ts +4 -0
- package/src/components/textarea/Textarea.vue +25 -0
- package/src/components/textarea/index.ts +1 -0
- package/src/components/theme-controls/LocaleMenu.vue +95 -0
- package/src/components/theme-controls/PaletteMenu.vue +155 -0
- package/src/components/theme-controls/ThemeToggle.vue +21 -0
- package/src/components/theme-controls/index.ts +15 -0
- package/src/components/timeline/Timeline.vue +20 -0
- package/src/components/timeline/TimelineItem.vue +68 -0
- package/src/components/timeline/index.ts +15 -0
- package/src/components/toast/Toaster.vue +62 -0
- package/src/components/toast/index.ts +4 -0
- package/src/components/toast/toast.ts +57 -0
- package/src/components/toggle/Toggle.vue +32 -0
- package/src/components/toggle/ToggleGroup.vue +56 -0
- package/src/components/toggle/ToggleGroupItem.vue +33 -0
- package/src/components/toggle/index.ts +18 -0
- package/src/components/token-table/TokenTable.vue +67 -0
- package/src/components/token-table/index.ts +2 -0
- package/src/components/toolbar/Toolbar.vue +34 -0
- package/src/components/toolbar/ToolbarButton.vue +33 -0
- package/src/components/toolbar/ToolbarSeparator.vue +21 -0
- package/src/components/toolbar/index.ts +3 -0
- package/src/components/tooltip/Tooltip.vue +15 -0
- package/src/components/tooltip/TooltipContent.vue +38 -0
- package/src/components/tooltip/TooltipProvider.vue +16 -0
- package/src/components/tooltip/TooltipTrigger.vue +12 -0
- package/src/components/tooltip/index.ts +4 -0
- package/src/components/tree/Tree.vue +53 -0
- package/src/components/tree/TreeItem.vue +59 -0
- package/src/components/tree/index.ts +21 -0
- package/src/components/tree-select/TreeSelect.vue +141 -0
- package/src/components/tree-select/index.ts +1 -0
- package/src/components/tree-table/TreeTable.vue +302 -0
- package/src/components/tree-table/features.ts +49 -0
- package/src/components/tree-table/index.ts +3 -0
- package/src/components/video-player/VideoPlayer.vue +472 -0
- package/src/components/video-player/index.ts +20 -0
- package/src/composables/index.ts +15 -0
- package/src/composables/useBase.ts +32 -0
- package/src/composables/useChartPalette.ts +33 -0
- package/src/composables/useColorMode.ts +29 -0
- package/src/composables/useConfirm.ts +81 -0
- package/src/composables/useDelayedFlag.ts +62 -0
- package/src/composables/useI18n.ts +107 -0
- package/src/composables/useIsMobile.ts +34 -0
- package/src/composables/useMenuAccent.ts +32 -0
- package/src/composables/useMenuAppearance.ts +31 -0
- package/src/composables/useMenuColor.ts +31 -0
- package/src/composables/usePalette.ts +30 -0
- package/src/composables/useStyle.ts +35 -0
- package/src/composables/useSurface.ts +30 -0
- package/src/composables/useTableQuery.ts +73 -0
- package/src/composables/useThemeController.ts +48 -0
- package/src/i18n/README.md +68 -0
- package/src/i18n/controller.ts +233 -0
- package/src/i18n/dictionary.ts +43 -0
- package/src/i18n/index.ts +34 -0
- package/src/i18n/registry.ts +64 -0
- package/src/i18n/ui/en/ui.json +114 -0
- package/src/i18n/ui/en/zod.json +16 -0
- package/src/i18n/ui/index.ts +48 -0
- package/src/i18n/ui/vi/ui.json +114 -0
- package/src/i18n/ui/vi/zod.json +16 -0
- package/src/lib/date.ts +24 -0
- package/src/lib/focus.ts +12 -0
- package/src/lib/icons.ts +345 -0
- package/src/lib/menu-styles.ts +16 -0
- package/src/lib/menu.ts +20 -0
- package/src/lib/platform.ts +5 -0
- package/src/lib/types.ts +6 -0
- package/src/lib/utils.ts +9 -0
- package/src/lib/zod-messages.ts +127 -0
- package/src/styles/chart.css +30 -0
- package/src/styles/index.css +18 -0
- package/src/variants/README.md +55 -0
- package/src/variants/components/accordion.ts +71 -0
- package/src/variants/components/alert.ts +20 -0
- package/src/variants/components/badge.ts +76 -0
- package/src/variants/components/button-group.ts +136 -0
- package/src/variants/components/button.ts +160 -0
- package/src/variants/components/checkbox.ts +67 -0
- package/src/variants/components/chip.ts +51 -0
- package/src/variants/components/code-block.ts +22 -0
- package/src/variants/components/dialog.ts +28 -0
- package/src/variants/components/empty.ts +60 -0
- package/src/variants/components/field.ts +35 -0
- package/src/variants/components/float-label.ts +95 -0
- package/src/variants/components/input-group.ts +73 -0
- package/src/variants/components/input-password.ts +68 -0
- package/src/variants/components/kbd.ts +47 -0
- package/src/variants/components/picker-trigger.ts +30 -0
- package/src/variants/components/radio-group.ts +63 -0
- package/src/variants/components/sheet.ts +23 -0
- package/src/variants/components/sidebar.ts +33 -0
- package/src/variants/components/slider.ts +49 -0
- package/src/variants/components/timeline.ts +45 -0
- package/src/variants/components/toggle.ts +58 -0
- package/src/variants/index.ts +35 -0
- package/src/variants/lib/chart/polar.ts +139 -0
- package/src/variants/lib/chart/tooltip.ts +101 -0
- package/src/variants/lib/chart/types.ts +172 -0
- package/src/variants/lib/chart/xy-label.ts +42 -0
- package/src/variants/lib/focus.ts +66 -0
- package/src/variants/lib/form-errors.ts +59 -0
- package/src/variants/lib/highlight.ts +548 -0
- package/src/variants/lib/list-move.ts +63 -0
- package/src/variants/lib/mask.ts +106 -0
- package/src/variants/lib/media.ts +56 -0
- package/src/variants/lib/menu-styles.ts +71 -0
- package/src/variants/lib/platform.ts +68 -0
- package/src/variants/lib/tree.ts +119 -0
- package/src/variants/lib/upload.ts +58 -0
- package/src/variants/lib/utils.ts +11 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ClassProps } from '../../lib/types'
|
|
3
|
+
import type { TreeNode } from './index'
|
|
4
|
+
import { TreeRoot } from 'reka-ui'
|
|
5
|
+
import { cn } from '../../lib/utils'
|
|
6
|
+
|
|
7
|
+
const props = withDefaults(
|
|
8
|
+
defineProps<ClassProps & {
|
|
9
|
+
items: TreeNode[]
|
|
10
|
+
multiple?: boolean
|
|
11
|
+
/**
|
|
12
|
+
* Chọn một nút cha thì chọn luôn toàn bộ con.
|
|
13
|
+
*
|
|
14
|
+
* Đúng cho cây phân quyền ("cấp quyền cả nhánh"), sai cho cây thư mục
|
|
15
|
+
* ("mở thư mục" khác "chọn mọi file trong đó"). Nên mặc định tắt.
|
|
16
|
+
*/
|
|
17
|
+
propagateSelect?: boolean
|
|
18
|
+
}>(),
|
|
19
|
+
{ multiple: false, propagateSelect: false },
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
const model = defineModel<TreeNode | TreeNode[] | undefined>()
|
|
23
|
+
const expanded = defineModel<string[]>('expanded', { default: () => [] })
|
|
24
|
+
|
|
25
|
+
function getKey(item: TreeNode): string {
|
|
26
|
+
return item.id
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getChildren(item: TreeNode): TreeNode[] | undefined {
|
|
30
|
+
return item.children
|
|
31
|
+
}
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<template>
|
|
35
|
+
<TreeRoot
|
|
36
|
+
v-slot="{ flattenItems }"
|
|
37
|
+
v-model="model"
|
|
38
|
+
v-model:expanded="expanded"
|
|
39
|
+
:items="props.items"
|
|
40
|
+
:multiple="props.multiple"
|
|
41
|
+
:propagate-select="props.propagateSelect"
|
|
42
|
+
:get-key="getKey"
|
|
43
|
+
:get-children="getChildren"
|
|
44
|
+
:class="cn('flex w-full select-none flex-col gap-0.5', props.class)"
|
|
45
|
+
>
|
|
46
|
+
<!--
|
|
47
|
+
Reka trả về danh sách đã **làm phẳng**: mỗi nút đang thấy là một dòng, kèm
|
|
48
|
+
`level` để thụt lề. Nhờ vậy điều hướng bàn phím chạy trên một danh sách
|
|
49
|
+
tuyến tính thay vì phải đệ quy qua các component lồng nhau.
|
|
50
|
+
-->
|
|
51
|
+
<slot :items="flattenItems" />
|
|
52
|
+
</TreeRoot>
|
|
53
|
+
</template>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { TreeItemProps } from 'reka-ui'
|
|
3
|
+
import type { ClassProps } from '../../lib/types'
|
|
4
|
+
import type { TreeNode } from './index'
|
|
5
|
+
import { TreeItem } from 'reka-ui'
|
|
6
|
+
import { computed } from 'vue'
|
|
7
|
+
import { ChevronRight } from '../../lib/icons'
|
|
8
|
+
import { cn } from '../../lib/utils'
|
|
9
|
+
|
|
10
|
+
const props = defineProps<TreeItemProps<TreeNode> & ClassProps & {
|
|
11
|
+
/** Nút này có con hay không — quyết định có vẽ mũi tên. */
|
|
12
|
+
hasChildren?: boolean
|
|
13
|
+
}>()
|
|
14
|
+
|
|
15
|
+
const delegated = computed(() => {
|
|
16
|
+
const { class: _class, hasChildren: _hasChildren, ...rest } = props
|
|
17
|
+
return rest
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Thụt lề bằng padding tính ra px, không dùng thang `pl-*` của Tailwind.
|
|
22
|
+
*
|
|
23
|
+
* Cây sâu tuỳ ý; khai sẵn một class cho mỗi cấp là không khả thi, mà
|
|
24
|
+
* `pl-[calc(...)]` với biến thì Tailwind không sinh ra được vì nó cần biết giá
|
|
25
|
+
* trị lúc build.
|
|
26
|
+
*/
|
|
27
|
+
const indent = computed(() => ({ paddingLeft: `${(props.level - 1) * 18 + 8}px` }))
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<template>
|
|
31
|
+
<TreeItem
|
|
32
|
+
v-slot="{ isExpanded, isSelected }"
|
|
33
|
+
v-bind="delegated"
|
|
34
|
+
:style="indent"
|
|
35
|
+
:class="cn(
|
|
36
|
+
'flex h-8 items-center gap-1.5 rounded-md pr-2 text-sm outline-none transition-colors',
|
|
37
|
+
'focus-visible:ring-2 focus-visible:ring-ring',
|
|
38
|
+
'hover:bg-accent/50',
|
|
39
|
+
'data-[selected]:bg-accent data-[selected]:text-accent-foreground',
|
|
40
|
+
'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
41
|
+
props.class,
|
|
42
|
+
)"
|
|
43
|
+
>
|
|
44
|
+
<!--
|
|
45
|
+
Lá vẫn chừa đúng chỗ của mũi tên, chỉ để trống. Bỏ hẳn thì nhãn của lá
|
|
46
|
+
thụt sang trái so với nhãn của nhánh cùng cấp và cột chữ gãy.
|
|
47
|
+
-->
|
|
48
|
+
<span class="grid size-4 shrink-0 place-items-center text-muted-foreground">
|
|
49
|
+
<ChevronRight
|
|
50
|
+
v-if="props.hasChildren"
|
|
51
|
+
class="size-3.5 transition-transform duration-(--duration-fast) motion-reduce:transition-none"
|
|
52
|
+
:class="isExpanded && 'rotate-90'"
|
|
53
|
+
aria-hidden="true"
|
|
54
|
+
/>
|
|
55
|
+
</span>
|
|
56
|
+
|
|
57
|
+
<slot :is-expanded="isExpanded" :is-selected="isSelected" />
|
|
58
|
+
</TreeItem>
|
|
59
|
+
</template>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Khuôn dữ liệu và hai phép tra nằm ở `src/variants/lib/tree`, KHÔNG khai
|
|
3
|
+
* ở đây.
|
|
4
|
+
*
|
|
5
|
+
* Cùng lý do `lib/mask` chuyển lên đó: bản React nhận đúng hình dạng nút này,
|
|
6
|
+
* và một `TreeNode` khai hai lần là hai lần để chúng trôi khỏi nhau. Re-export
|
|
7
|
+
* để 20 chỗ đang `import type { TreeNode } from '../tree'` không phải sửa.
|
|
8
|
+
*/
|
|
9
|
+
export {
|
|
10
|
+
branchIds,
|
|
11
|
+
branchNodes,
|
|
12
|
+
findNode,
|
|
13
|
+
findPath,
|
|
14
|
+
flattenTree,
|
|
15
|
+
isBranch,
|
|
16
|
+
type TreeNode,
|
|
17
|
+
type TreeRow,
|
|
18
|
+
} from '../../variants/lib/tree'
|
|
19
|
+
export { default as Tree } from './Tree.vue'
|
|
20
|
+
|
|
21
|
+
export { default as TreeItem } from './TreeItem.vue'
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ClassProps } from '../../lib/types'
|
|
3
|
+
import type { TreeNode } from '../tree'
|
|
4
|
+
import { computed, ref } from 'vue'
|
|
5
|
+
import { useI18n } from '../../composables/useI18n'
|
|
6
|
+
import { focusRing, invalidRing } from '../../lib/focus'
|
|
7
|
+
import { ChevronDown } from '../../lib/icons'
|
|
8
|
+
import { cn } from '../../lib/utils'
|
|
9
|
+
import { pickerTriggerVariants } from '../../variants/components/picker-trigger'
|
|
10
|
+
import { Popover, PopoverContent, PopoverTrigger } from '../popover'
|
|
11
|
+
import { Tree, TreeItem } from '../tree'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Gốc của component là `Popover`, mà `PopoverRoot` không vẽ ra DOM nào — nên
|
|
15
|
+
* thuộc tính rơi tự do KHÔNG có chỗ đáp và biến mất lặng lẽ. `id` là cái mất
|
|
16
|
+
* đau nhất: `<Label for>` trỏ vào một id không tồn tại thì nhãn vẫn hiện ra
|
|
17
|
+
* bình thường và chẳng làm gì cả.
|
|
18
|
+
*
|
|
19
|
+
* Chuyển tay xuống chính cái nút, cùng cách `Input.vue` làm.
|
|
20
|
+
*/
|
|
21
|
+
defineOptions({ inheritAttrs: false })
|
|
22
|
+
|
|
23
|
+
const props = withDefaults(
|
|
24
|
+
defineProps<ClassProps & {
|
|
25
|
+
items: TreeNode[]
|
|
26
|
+
multiple?: boolean
|
|
27
|
+
placeholder?: string
|
|
28
|
+
disabled?: boolean
|
|
29
|
+
invalid?: boolean
|
|
30
|
+
size?: 'sm' | 'default'
|
|
31
|
+
/** Chọn nút cha thì chọn luôn cả nhánh. Chuyển thẳng xuống `Tree`. */
|
|
32
|
+
propagateSelect?: boolean
|
|
33
|
+
/** Chiều cao tối đa của bảng trước khi nó cuộn. */
|
|
34
|
+
maxHeight?: string
|
|
35
|
+
}>(),
|
|
36
|
+
{
|
|
37
|
+
multiple: false,
|
|
38
|
+
disabled: false,
|
|
39
|
+
invalid: false,
|
|
40
|
+
size: 'default',
|
|
41
|
+
propagateSelect: false,
|
|
42
|
+
maxHeight: '260px',
|
|
43
|
+
},
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
const model = defineModel<TreeNode | TreeNode[] | undefined>()
|
|
47
|
+
const expanded = defineModel<string[]>('expanded', { default: () => [] })
|
|
48
|
+
|
|
49
|
+
const { t } = useI18n()
|
|
50
|
+
|
|
51
|
+
const open = ref(false)
|
|
52
|
+
|
|
53
|
+
const placeholder = computed(() => props.placeholder ?? t('ui.treeSelect.placeholder'))
|
|
54
|
+
|
|
55
|
+
const selected = computed<TreeNode[]>(() => {
|
|
56
|
+
if (model.value === undefined)
|
|
57
|
+
return []
|
|
58
|
+
return Array.isArray(model.value) ? model.value : [model.value]
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
const label = computed(() =>
|
|
62
|
+
selected.value.length === 0
|
|
63
|
+
? placeholder.value
|
|
64
|
+
: selected.value.map(node => node.label).join(', '),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Chế độ một lựa chọn thì chọn xong là đóng; chế độ nhiều thì KHÔNG.
|
|
69
|
+
*
|
|
70
|
+
* Đóng sau mỗi lần tick trong chế độ nhiều nghĩa là mỗi mục phải mở lại bảng
|
|
71
|
+
* một lần — và người ta bật chế độ nhiều chính vì sắp chọn nhiều thứ.
|
|
72
|
+
*/
|
|
73
|
+
function onUpdate(value: TreeNode | TreeNode[] | undefined): void {
|
|
74
|
+
model.value = value
|
|
75
|
+
if (!props.multiple && value !== undefined)
|
|
76
|
+
open.value = false
|
|
77
|
+
}
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<template>
|
|
81
|
+
<Popover v-model:open="open">
|
|
82
|
+
<PopoverTrigger as-child>
|
|
83
|
+
<!--
|
|
84
|
+
Nút mang `role="combobox"`, đúng khuôn "combobox chỉ để chọn" của APG.
|
|
85
|
+
|
|
86
|
+
`aria-haspopup` để nguyên `dialog` chứ KHÔNG đổi thành `tree`: reka
|
|
87
|
+
hard-code `role="dialog"` cho `PopoverContentImpl` và không thuộc tính
|
|
88
|
+
nào đè được, nên cái cây thật sự nằm TRONG một dialog. Khai `tree` ở nút
|
|
89
|
+
là tự mâu thuẫn với chính DOM của mình.
|
|
90
|
+
-->
|
|
91
|
+
<button
|
|
92
|
+
v-bind="$attrs"
|
|
93
|
+
type="button"
|
|
94
|
+
role="combobox"
|
|
95
|
+
aria-haspopup="dialog"
|
|
96
|
+
:aria-expanded="open"
|
|
97
|
+
:disabled="props.disabled"
|
|
98
|
+
:aria-invalid="props.invalid || undefined"
|
|
99
|
+
:data-invalid="props.invalid || undefined"
|
|
100
|
+
:data-placeholder="selected.length === 0 ? '' : undefined"
|
|
101
|
+
:class="cn(pickerTriggerVariants({ size: props.size }), focusRing, invalidRing, props.class)"
|
|
102
|
+
>
|
|
103
|
+
<span class="truncate text-left">{{ label }}</span>
|
|
104
|
+
<ChevronDown class="size-4 shrink-0 text-muted-foreground" aria-hidden="true" />
|
|
105
|
+
</button>
|
|
106
|
+
</PopoverTrigger>
|
|
107
|
+
|
|
108
|
+
<!--
|
|
109
|
+
`w-(--reka-popover-trigger-width)` bám đúng bề rộng của nút, giống cách
|
|
110
|
+
Select làm: một bảng rộng hơn hay hẹp hơn ô của nó đọc ra như đang thuộc
|
|
111
|
+
về thứ khác.
|
|
112
|
+
-->
|
|
113
|
+
<PopoverContent
|
|
114
|
+
class="w-(--reka-popover-trigger-width) p-1"
|
|
115
|
+
align="start"
|
|
116
|
+
:style="{ maxHeight: props.maxHeight }"
|
|
117
|
+
>
|
|
118
|
+
<div class="overflow-y-auto" :style="{ maxHeight: `calc(${props.maxHeight} - 0.5rem)` }">
|
|
119
|
+
<Tree
|
|
120
|
+
v-slot="{ items: rows }"
|
|
121
|
+
v-model:expanded="expanded"
|
|
122
|
+
:model-value="model"
|
|
123
|
+
:items="props.items"
|
|
124
|
+
:multiple="props.multiple"
|
|
125
|
+
:propagate-select="props.propagateSelect"
|
|
126
|
+
@update:model-value="onUpdate"
|
|
127
|
+
>
|
|
128
|
+
<TreeItem
|
|
129
|
+
v-for="row in rows"
|
|
130
|
+
:key="row._id"
|
|
131
|
+
v-bind="row.bind"
|
|
132
|
+
:disabled="row.value.disabled"
|
|
133
|
+
:has-children="!!row.value.children?.length"
|
|
134
|
+
>
|
|
135
|
+
{{ row.value.label }}
|
|
136
|
+
</TreeItem>
|
|
137
|
+
</Tree>
|
|
138
|
+
</div>
|
|
139
|
+
</PopoverContent>
|
|
140
|
+
</Popover>
|
|
141
|
+
</template>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as TreeSelect } from './TreeSelect.vue'
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
<script setup lang="ts" generic="TData extends RowData">
|
|
2
|
+
import type { ExpandedState, RowData, Updater } from '@tanstack/vue-table'
|
|
3
|
+
import type { ClassProps } from '../../lib/types'
|
|
4
|
+
import type { DataTableColumnMeta } from '../data-table/features'
|
|
5
|
+
import type { TreeTableColumn } from './features'
|
|
6
|
+
import { FlexRender, useTable } from '@tanstack/vue-table'
|
|
7
|
+
import { computed, ref, toRef } from 'vue'
|
|
8
|
+
import { ChevronRight } from '../../lib/icons'
|
|
9
|
+
import { cn } from '../../lib/utils'
|
|
10
|
+
import Table from '../table/Table.vue'
|
|
11
|
+
import TableBody from '../table/TableBody.vue'
|
|
12
|
+
import TableCell from '../table/TableCell.vue'
|
|
13
|
+
import TableEmpty from '../table/TableEmpty.vue'
|
|
14
|
+
import TableHead from '../table/TableHead.vue'
|
|
15
|
+
import TableHeader from '../table/TableHeader.vue'
|
|
16
|
+
import TableRow from '../table/TableRow.vue'
|
|
17
|
+
import { treeTableFeatures } from './features'
|
|
18
|
+
|
|
19
|
+
const props = withDefaults(
|
|
20
|
+
defineProps<ClassProps & {
|
|
21
|
+
columns: TreeTableColumn<TData>[]
|
|
22
|
+
data: TData[]
|
|
23
|
+
/**
|
|
24
|
+
* Lấy nút con. Mặc định đọc trường `children`.
|
|
25
|
+
*
|
|
26
|
+
* Khai riêng thay vì ép dữ liệu phải có đúng tên đó — cây từ API hay gọi nó
|
|
27
|
+
* là `items`, `nodes`, `subRows`, và đổi tên cả một cây chỉ để hợp một
|
|
28
|
+
* component là cái giá không đáng.
|
|
29
|
+
*/
|
|
30
|
+
getSubRows?: (row: TData) => TData[] | undefined
|
|
31
|
+
getRowId?: (row: TData, index: number) => string
|
|
32
|
+
/** Cột mang mũi tên và phần thụt lề. Mặc định là cột đầu tiên. */
|
|
33
|
+
expandColumn?: string
|
|
34
|
+
loading?: boolean
|
|
35
|
+
emptyMessage?: string
|
|
36
|
+
stickyHeader?: boolean
|
|
37
|
+
maxHeight?: string
|
|
38
|
+
}>(),
|
|
39
|
+
{ loading: false, stickyHeader: true },
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
const expanded = defineModel<ExpandedState>('expanded', { default: () => ({}) })
|
|
43
|
+
|
|
44
|
+
function defaultSubRows(row: TData): TData[] | undefined {
|
|
45
|
+
const children = (row as { children?: unknown }).children
|
|
46
|
+
return Array.isArray(children) ? (children as TData[]) : undefined
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const tableState = computed(() => ({ expanded: expanded.value }))
|
|
50
|
+
|
|
51
|
+
function onExpandedChange(updater: Updater<ExpandedState>): void {
|
|
52
|
+
expanded.value = typeof updater === 'function'
|
|
53
|
+
? (updater as (old: ExpandedState) => ExpandedState)(expanded.value)
|
|
54
|
+
: updater
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const table = useTable({
|
|
58
|
+
features: treeTableFeatures,
|
|
59
|
+
columns: toRef(props, 'columns'),
|
|
60
|
+
data: toRef(props, 'data'),
|
|
61
|
+
state: tableState,
|
|
62
|
+
getRowId: props.getRowId,
|
|
63
|
+
getSubRows: props.getSubRows ?? defaultSubRows,
|
|
64
|
+
onExpandedChange,
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
/** Dòng đang THẤY được, đã làm phẳng theo trạng thái mở. */
|
|
68
|
+
const rows = computed(() => table.getRowModel().rows)
|
|
69
|
+
|
|
70
|
+
const expanderId = computed(() =>
|
|
71
|
+
props.expandColumn ?? table.getAllLeafColumns()[0]?.id,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Chỉ ép bề rộng cho cột MÀ NGƯỜI VIẾT khai `size`.
|
|
76
|
+
*
|
|
77
|
+
* Không đọc `column.columnDef.size`: `columnSizingFeature` nhét sẵn `size: 150`
|
|
78
|
+
* vào mọi cột, nên đọc từ đó thì cột nào cũng cứng và không còn cột nào co giãn.
|
|
79
|
+
* Cùng cái bẫy đã ghi cho DataTable.
|
|
80
|
+
*/
|
|
81
|
+
const declared = computed(() => {
|
|
82
|
+
const widths = new Map<string, number>()
|
|
83
|
+
const aligns = new Map<string, NonNullable<DataTableColumnMeta['align']>>()
|
|
84
|
+
|
|
85
|
+
for (const column of props.columns) {
|
|
86
|
+
const id = column.id ?? ('accessorKey' in column ? String(column.accessorKey) : undefined)
|
|
87
|
+
if (id === undefined)
|
|
88
|
+
continue
|
|
89
|
+
if (typeof column.size === 'number')
|
|
90
|
+
widths.set(id, column.size)
|
|
91
|
+
if (column.meta?.align !== undefined)
|
|
92
|
+
aligns.set(id, column.meta.align)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return { widths, aligns }
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
function widthStyle(columnId: string): { width: string } | undefined {
|
|
99
|
+
const width = declared.value.widths.get(columnId)
|
|
100
|
+
return width === undefined ? undefined : { width: `${width}px` }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Cột số căn phải VÀ tabular figures — xem chú thích ở `DataTableColumnMeta`. */
|
|
104
|
+
function alignClass(columnId: string): string | undefined {
|
|
105
|
+
const align = declared.value.aligns.get(columnId)
|
|
106
|
+
if (align === 'number')
|
|
107
|
+
return 'text-right tabular-nums'
|
|
108
|
+
if (align === 'right')
|
|
109
|
+
return 'text-right'
|
|
110
|
+
if (align === 'center')
|
|
111
|
+
return 'text-center'
|
|
112
|
+
return undefined
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Roving tabindex chạy trên DÒNG, không chạy trên ô.
|
|
117
|
+
*
|
|
118
|
+
* APG cho phép cả hai với `treegrid`; ở đây chọn dòng vì thứ người dùng đang
|
|
119
|
+
* duyệt là một cây, còn các cột chỉ là thuộc tính của nút. Điều hướng tới từng
|
|
120
|
+
* ô sẽ là một lời hứa khác hẳn — và nếu khai `treegrid` rồi chỉ làm một nửa thì
|
|
121
|
+
* lời hứa đó là lời hứa sai.
|
|
122
|
+
*/
|
|
123
|
+
const focusedId = ref<string>()
|
|
124
|
+
|
|
125
|
+
const activeId = computed(() => {
|
|
126
|
+
const list = rows.value
|
|
127
|
+
if (list.length === 0)
|
|
128
|
+
return undefined
|
|
129
|
+
return list.some(row => row.id === focusedId.value) ? focusedId.value : list[0].id
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Vùng chứa lấy từ CHÍNH sự kiện, không lấy từ một template ref.
|
|
134
|
+
*
|
|
135
|
+
* `ref` đặt trên `<TableBody>` trả về instance component chứ không phải
|
|
136
|
+
* element, nên `querySelector` trên đó ném lỗi — mà `vue-tsc` không bắt được
|
|
137
|
+
* vì ref được khai kiểu `HTMLElement`. Còn `currentTarget` của `keydown` thì
|
|
138
|
+
* đúng là cái `<tbody>` thật.
|
|
139
|
+
*/
|
|
140
|
+
function focusRow(container: HTMLElement, id: string | undefined): void {
|
|
141
|
+
if (id === undefined)
|
|
142
|
+
return
|
|
143
|
+
focusedId.value = id
|
|
144
|
+
container.querySelector<HTMLElement>(`[data-row="${CSS.escape(id)}"]`)?.focus()
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function onKeydown(event: KeyboardEvent): void {
|
|
148
|
+
const container = event.currentTarget as HTMLElement
|
|
149
|
+
const list = rows.value
|
|
150
|
+
const at = list.findIndex(row => row.id === activeId.value)
|
|
151
|
+
if (at < 0)
|
|
152
|
+
return
|
|
153
|
+
const row = list[at]
|
|
154
|
+
|
|
155
|
+
switch (event.key) {
|
|
156
|
+
case 'ArrowDown':
|
|
157
|
+
event.preventDefault()
|
|
158
|
+
focusRow(container, list[Math.min(at + 1, list.length - 1)]?.id)
|
|
159
|
+
break
|
|
160
|
+
|
|
161
|
+
case 'ArrowUp':
|
|
162
|
+
event.preventDefault()
|
|
163
|
+
focusRow(container, list[Math.max(at - 1, 0)]?.id)
|
|
164
|
+
break
|
|
165
|
+
|
|
166
|
+
case 'ArrowRight':
|
|
167
|
+
event.preventDefault()
|
|
168
|
+
// Đóng thì mở ra; đã mở rồi thì đi vào con đầu tiên. Hai nhịp cho hai
|
|
169
|
+
// việc khác nhau, đúng như cây thư mục.
|
|
170
|
+
if (row.getCanExpand() && !row.getIsExpanded())
|
|
171
|
+
row.toggleExpanded(true)
|
|
172
|
+
else if (row.getIsExpanded())
|
|
173
|
+
focusRow(container, list[at + 1]?.id)
|
|
174
|
+
break
|
|
175
|
+
|
|
176
|
+
case 'ArrowLeft':
|
|
177
|
+
event.preventDefault()
|
|
178
|
+
if (row.getIsExpanded()) {
|
|
179
|
+
row.toggleExpanded(false)
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
// Lên nút cha: dòng gần nhất phía trên có độ sâu nhỏ hơn.
|
|
183
|
+
for (let i = at - 1; i >= 0; i -= 1) {
|
|
184
|
+
if (list[i].depth < row.depth) {
|
|
185
|
+
focusRow(container, list[i].id)
|
|
186
|
+
break
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
break
|
|
191
|
+
|
|
192
|
+
case 'Home':
|
|
193
|
+
event.preventDefault()
|
|
194
|
+
focusRow(container, list[0]?.id)
|
|
195
|
+
break
|
|
196
|
+
|
|
197
|
+
case 'End':
|
|
198
|
+
event.preventDefault()
|
|
199
|
+
focusRow(container, list[list.length - 1]?.id)
|
|
200
|
+
break
|
|
201
|
+
|
|
202
|
+
case 'Enter':
|
|
203
|
+
case ' ':
|
|
204
|
+
if (row.getCanExpand()) {
|
|
205
|
+
event.preventDefault()
|
|
206
|
+
row.toggleExpanded()
|
|
207
|
+
}
|
|
208
|
+
break
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
</script>
|
|
212
|
+
|
|
213
|
+
<template>
|
|
214
|
+
<!--
|
|
215
|
+
`role="treegrid"` đặt lên chính `<table>`.
|
|
216
|
+
|
|
217
|
+
Không phải đặt thêm role cho `<tr>` và `<td>`: HTML-AAM nói `<td>` ánh xạ
|
|
218
|
+
thành `gridcell` khi bảng tổ tiên là `grid` hoặc `treegrid`, và thành `cell`
|
|
219
|
+
khi là `table`. Đổi đúng một thuộc tính ở gốc là cả bảng đổi theo.
|
|
220
|
+
-->
|
|
221
|
+
<Table
|
|
222
|
+
role="treegrid"
|
|
223
|
+
:aria-busy="props.loading || undefined"
|
|
224
|
+
:class="cn(props.class)"
|
|
225
|
+
:container-class="props.maxHeight ? 'overflow-auto' : undefined"
|
|
226
|
+
:style="props.maxHeight ? { maxHeight: props.maxHeight } : undefined"
|
|
227
|
+
>
|
|
228
|
+
<TableHeader>
|
|
229
|
+
<TableRow v-for="group in table.getHeaderGroups()" :key="group.id">
|
|
230
|
+
<TableHead
|
|
231
|
+
v-for="header in group.headers"
|
|
232
|
+
:key="header.id"
|
|
233
|
+
:style="widthStyle(header.column.id)"
|
|
234
|
+
:class="cn(props.stickyHeader && 'sticky top-0 z-(--z-raised) bg-background', alignClass(header.column.id))"
|
|
235
|
+
>
|
|
236
|
+
<FlexRender
|
|
237
|
+
v-if="!header.isPlaceholder"
|
|
238
|
+
:render="header.column.columnDef.header"
|
|
239
|
+
:props="header.getContext()"
|
|
240
|
+
/>
|
|
241
|
+
</TableHead>
|
|
242
|
+
</TableRow>
|
|
243
|
+
</TableHeader>
|
|
244
|
+
|
|
245
|
+
<TableBody @keydown="onKeydown">
|
|
246
|
+
<!-- `TableEmpty` không có slot: nội dung con bị bỏ im lặng, phải đi qua prop `message`. -->
|
|
247
|
+
<TableEmpty v-if="rows.length === 0" :colspan="props.columns.length" :message="props.emptyMessage" />
|
|
248
|
+
|
|
249
|
+
<TableRow
|
|
250
|
+
v-for="row in rows"
|
|
251
|
+
v-else
|
|
252
|
+
:key="row.id"
|
|
253
|
+
:data-row="row.id"
|
|
254
|
+
:aria-level="row.depth + 1"
|
|
255
|
+
:aria-expanded="row.getCanExpand() ? row.getIsExpanded() : undefined"
|
|
256
|
+
:tabindex="row.id === activeId ? 0 : -1"
|
|
257
|
+
class="outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
|
|
258
|
+
@focus="focusedId = row.id"
|
|
259
|
+
>
|
|
260
|
+
<TableCell
|
|
261
|
+
v-for="cell in row.getAllCells()"
|
|
262
|
+
:key="cell.id"
|
|
263
|
+
:class="alignClass(cell.column.id)"
|
|
264
|
+
>
|
|
265
|
+
<div
|
|
266
|
+
v-if="cell.column.id === expanderId"
|
|
267
|
+
class="flex items-center gap-1"
|
|
268
|
+
:style="{ paddingLeft: `${row.depth * 18}px` }"
|
|
269
|
+
>
|
|
270
|
+
<!--
|
|
271
|
+
Mũi tên là `tabindex="-1"`: chuỗi Tab đi qua DÒNG, không qua từng
|
|
272
|
+
nút gập. Một cây trăm nút mà mỗi nút là một điểm dừng Tab thì bàn
|
|
273
|
+
phím không đi qua nổi.
|
|
274
|
+
-->
|
|
275
|
+
<button
|
|
276
|
+
v-if="row.getCanExpand()"
|
|
277
|
+
type="button"
|
|
278
|
+
tabindex="-1"
|
|
279
|
+
aria-hidden="true"
|
|
280
|
+
class="grid size-4 shrink-0 place-items-center rounded text-muted-foreground hover:text-foreground"
|
|
281
|
+
@click.stop="row.toggleExpanded()"
|
|
282
|
+
>
|
|
283
|
+
<ChevronRight
|
|
284
|
+
class="size-3.5 transition-transform duration-(--duration-fast) motion-reduce:transition-none"
|
|
285
|
+
:class="row.getIsExpanded() && 'rotate-90'"
|
|
286
|
+
/>
|
|
287
|
+
</button>
|
|
288
|
+
<span v-else class="size-4 shrink-0" />
|
|
289
|
+
|
|
290
|
+
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
|
|
291
|
+
</div>
|
|
292
|
+
|
|
293
|
+
<FlexRender
|
|
294
|
+
v-else
|
|
295
|
+
:render="cell.column.columnDef.cell"
|
|
296
|
+
:props="cell.getContext()"
|
|
297
|
+
/>
|
|
298
|
+
</TableCell>
|
|
299
|
+
</TableRow>
|
|
300
|
+
</TableBody>
|
|
301
|
+
</Table>
|
|
302
|
+
</template>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { CellData, ColumnDef, RowData } from '@tanstack/vue-table'
|
|
2
|
+
import type { DataTableColumnMeta } from '../data-table/features'
|
|
3
|
+
import {
|
|
4
|
+
columnSizingFeature,
|
|
5
|
+
createExpandedRowModel,
|
|
6
|
+
createSortedRowModel,
|
|
7
|
+
rowExpandingFeature,
|
|
8
|
+
rowSelectionFeature,
|
|
9
|
+
rowSortingFeature,
|
|
10
|
+
sortFn_alphanumeric,
|
|
11
|
+
sortFn_basic,
|
|
12
|
+
sortFn_datetime,
|
|
13
|
+
sortFn_text,
|
|
14
|
+
tableFeatures,
|
|
15
|
+
} from '@tanstack/vue-table'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Bộ feature riêng của TreeTable — KHÔNG dùng lại bộ của DataTable.
|
|
19
|
+
*
|
|
20
|
+
* v9 không bật sẵn feature nào, và bộ của DataTable cố tình không đăng ký
|
|
21
|
+
* expanding để bundle của nó khỏi kéo theo. Ngược lại ở đây không có phân trang:
|
|
22
|
+
* cắt một cái cây theo trang thì một nút cha nằm cuối trang 1 còn con nó ở đầu
|
|
23
|
+
* trang 2, và cái thụt lề bên trái thành ra vô nghĩa.
|
|
24
|
+
*
|
|
25
|
+
* `columnMeta` dùng chung `DataTableColumnMeta` — cùng một chuyện căn chữ, khai
|
|
26
|
+
* hai lần là hai chỗ để chúng trôi khỏi nhau.
|
|
27
|
+
*/
|
|
28
|
+
export const treeTableFeatures = tableFeatures({
|
|
29
|
+
columnSizingFeature,
|
|
30
|
+
rowExpandingFeature,
|
|
31
|
+
expandedRowModel: createExpandedRowModel(),
|
|
32
|
+
rowSortingFeature,
|
|
33
|
+
sortedRowModel: createSortedRowModel(),
|
|
34
|
+
sortFns: {
|
|
35
|
+
alphanumeric: sortFn_alphanumeric,
|
|
36
|
+
text: sortFn_text,
|
|
37
|
+
datetime: sortFn_datetime,
|
|
38
|
+
basic: sortFn_basic,
|
|
39
|
+
},
|
|
40
|
+
rowSelectionFeature,
|
|
41
|
+
|
|
42
|
+
columnMeta: {} as DataTableColumnMeta,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
export type TreeTableFeatures = typeof treeTableFeatures
|
|
46
|
+
|
|
47
|
+
/** Column def đã gắn sẵn feature set — dùng ở app thay cho `ColumnDef` thô. */
|
|
48
|
+
export type TreeTableColumn<TData extends RowData, TValue extends CellData = CellData>
|
|
49
|
+
= ColumnDef<TreeTableFeatures, TData, TValue>
|