@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,291 @@
|
|
|
1
|
+
<script setup lang="ts" generic="T extends Record<string, unknown>">
|
|
2
|
+
import type { ClassProps } from '../../lib/types'
|
|
3
|
+
import type { ChartConfig, ChartValueFormatter } from './types'
|
|
4
|
+
import { computed, ref } from 'vue'
|
|
5
|
+
import { cn } from '../../lib/utils'
|
|
6
|
+
import ChartContainer from './ChartContainer.vue'
|
|
7
|
+
import ChartLegend from './ChartLegend.vue'
|
|
8
|
+
import { polarToCartesian, polygonPoints, toPointsAttr } from './polar'
|
|
9
|
+
import { formatChartValue } from './tooltip'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Radar: mỗi hàng dữ liệu là một nan hoa, mỗi chuỗi là một đa giác.
|
|
13
|
+
*
|
|
14
|
+
* Vẽ tay — Unovis không có radar. Hình học giống SpeedDial: chia đều 360° cho
|
|
15
|
+
* số nan, rồi đặt điểm theo (góc, bán kính tỉ lệ với giá trị).
|
|
16
|
+
*
|
|
17
|
+
* MỘT CẢNH BÁO ĐI KÈM, và nó là thật chứ không phải câu khách sáo: radar đánh
|
|
18
|
+
* lừa mắt theo hai kiểu. Diện tích tăng theo BÌNH PHƯƠNG bán kính, nên một
|
|
19
|
+
* chuỗi hơn 40% trông như hơn gấp đôi; và đổi THỨ TỰ các nan là đổi luôn hình
|
|
20
|
+
* dạng dù dữ liệu y nguyên. Chỉ dùng khi các trục thật sự cùng đơn vị và thứ tự
|
|
21
|
+
* của chúng có nghĩa; còn lại thì cột nhóm đọc chính xác hơn.
|
|
22
|
+
*/
|
|
23
|
+
const props = withDefaults(defineProps<ClassProps & {
|
|
24
|
+
data: T[]
|
|
25
|
+
/** Khoá làm nhãn của từng nan hoa. */
|
|
26
|
+
index: Extract<keyof T, string>
|
|
27
|
+
/** Các chuỗi cần vẽ. Bỏ trống thì lấy các khoá trong `config`. */
|
|
28
|
+
categories?: Extract<keyof T, string>[]
|
|
29
|
+
config: ChartConfig
|
|
30
|
+
/** Giá trị ứng với mép ngoài. Bỏ trống thì lấy giá trị lớn nhất trong dữ liệu. */
|
|
31
|
+
max?: number
|
|
32
|
+
/** Số vòng lưới. @default 4 */
|
|
33
|
+
gridLevels?: number
|
|
34
|
+
/**
|
|
35
|
+
* Bán kính vòng ngoài cùng, pixel trong khung 340×260.
|
|
36
|
+
*
|
|
37
|
+
* Hạ xuống khi nhãn nan hoa cần nhiều hơn một dòng: khung là cố định, nên chỗ
|
|
38
|
+
* cho nhãn chỉ lấy được từ bán kính. Với nhãn hai dòng thì 86 là vừa — nan
|
|
39
|
+
* dưới cùng rơi vào y = 130 + 86 + 16 = 232, còn đủ chỗ cho dòng thứ hai.
|
|
40
|
+
* @default 100
|
|
41
|
+
*/
|
|
42
|
+
radius?: number
|
|
43
|
+
/**
|
|
44
|
+
* Hình vòng lưới. `polygon` nối thẳng giữa các nan nên đếm được số trục;
|
|
45
|
+
* `circle` là đường tròn, gọn hơn nhưng giấu mất số trục.
|
|
46
|
+
* @default 'polygon'
|
|
47
|
+
*/
|
|
48
|
+
gridShape?: 'polygon' | 'circle'
|
|
49
|
+
/** Vẽ các vòng lưới. @default true */
|
|
50
|
+
showGrid?: boolean
|
|
51
|
+
/** Vẽ nan hoa từ tâm ra. @default true */
|
|
52
|
+
showSpokes?: boolean
|
|
53
|
+
/**
|
|
54
|
+
* Tô nền vùng lưới bằng **màu của chuỗi đầu** ở 20%.
|
|
55
|
+
*
|
|
56
|
+
* Không phải `--muted`: shadcn khai `fill-(--color-desktop) opacity-20`, tức
|
|
57
|
+
* nền lưới ăn theo chính màu đang vẽ. Dùng màu trung tính thì hai ô
|
|
58
|
+
* `Grid Filled` và `Grid Circle Filled` trông y hệt bản không tô, chỉ xám hơn
|
|
59
|
+
* một chút — mất hẳn ý nghĩa của biến thể.
|
|
60
|
+
* @default false
|
|
61
|
+
*/
|
|
62
|
+
gridFill?: boolean
|
|
63
|
+
/** Tô nền đa giác. @default true */
|
|
64
|
+
showArea?: boolean
|
|
65
|
+
/**
|
|
66
|
+
* Độ đục của nền đa giác.
|
|
67
|
+
*
|
|
68
|
+
* 0,6 là mặc định của `<Radar>` bên Recharts, tức thứ mà mọi biến thể radar của
|
|
69
|
+
* shadcn nhìn ra — trừ hai ô `*-fill` (0,5). Con số 0,22 mà bản trước dùng là
|
|
70
|
+
* tôi tự đặt, không đo đạc gì.
|
|
71
|
+
* @default 0.6
|
|
72
|
+
*/
|
|
73
|
+
fillOpacity?: number
|
|
74
|
+
/** Chấm tại mỗi đỉnh. @default false */
|
|
75
|
+
showDots?: boolean
|
|
76
|
+
/** @default true */
|
|
77
|
+
showLegend?: boolean
|
|
78
|
+
valueFormatter?: ChartValueFormatter
|
|
79
|
+
}>(), {
|
|
80
|
+
gridLevels: 4,
|
|
81
|
+
radius: 100,
|
|
82
|
+
gridShape: 'polygon',
|
|
83
|
+
showGrid: true,
|
|
84
|
+
showSpokes: true,
|
|
85
|
+
gridFill: false,
|
|
86
|
+
showArea: true,
|
|
87
|
+
fillOpacity: 0.6,
|
|
88
|
+
showDots: false,
|
|
89
|
+
showLegend: true,
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Khung vẽ CỐ Ý rộng hơn cao. Nhãn nan hoa nằm ngang nên chỗ thiếu là chiều
|
|
94
|
+
* NGANG, không phải chiều dọc: với khung vuông 260px, "Support" và "Uptime" ở
|
|
95
|
+
* hai mép trái/phải bị xén mất chữ đầu — đã dính đúng vậy.
|
|
96
|
+
* Đặt ngắn nhãn nan hoa; khung này chừa khoảng 54px mỗi bên.
|
|
97
|
+
*/
|
|
98
|
+
const VIEW_W = 340
|
|
99
|
+
const VIEW_H = 260
|
|
100
|
+
const CX = VIEW_W / 2
|
|
101
|
+
const CY = VIEW_H / 2
|
|
102
|
+
/** Khoảng cách từ mép lưới ra tới nhãn. */
|
|
103
|
+
const LABEL_GAP = 16
|
|
104
|
+
const RADIUS = computed(() => props.radius)
|
|
105
|
+
|
|
106
|
+
const hidden = ref<string[]>([])
|
|
107
|
+
|
|
108
|
+
const categories = computed(() => (props.categories ?? Object.keys(props.config)) as string[])
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Màu nền vùng lưới: chuỗi ĐẦU ở 20%.
|
|
112
|
+
*
|
|
113
|
+
* Chuỗi đầu chứ không phải trộn nhiều chuỗi — cả hai ô dùng `gridFill` bên
|
|
114
|
+
* shadcn đều chỉ có một chuỗi, và với nhiều chuỗi thì nền lưới mang màu của một
|
|
115
|
+
* chuỗi cụ thể sẽ đọc ra như chuỗi đó được ưu tiên. Rơi về `--muted` khi chưa
|
|
116
|
+
* có chuỗi nào để không vẽ ra một mảng trong suốt.
|
|
117
|
+
*/
|
|
118
|
+
const gridFillColor = computed(() => {
|
|
119
|
+
const key = categories.value[0]
|
|
120
|
+
return key === undefined
|
|
121
|
+
? 'var(--muted)'
|
|
122
|
+
: `color-mix(in oklab, var(--color-${key}) 20%, transparent)`
|
|
123
|
+
})
|
|
124
|
+
const axes = computed(() => props.data.map(row => String(row[props.index])))
|
|
125
|
+
const axisCount = computed(() => axes.value.length)
|
|
126
|
+
|
|
127
|
+
const maxValue = computed(() => {
|
|
128
|
+
if (props.max !== undefined)
|
|
129
|
+
return props.max
|
|
130
|
+
const values = props.data.flatMap(row => categories.value.map(key => Number(row[key]) || 0))
|
|
131
|
+
return Math.max(...values, 1)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Vòng lưới. Mặc định là ĐA GIÁC chứ không phải đường tròn: cạnh thẳng nối giữa
|
|
136
|
+
* hai nan cho mắt đếm được số trục, còn đường tròn thì giấu mất điều đó.
|
|
137
|
+
* `gridShape="circle"` trả về bán kính để vẽ `<circle>`.
|
|
138
|
+
*/
|
|
139
|
+
const gridRings = computed(() => Array.from({ length: props.gridLevels }, (_, i) => {
|
|
140
|
+
const r = (RADIUS.value * (i + 1)) / props.gridLevels
|
|
141
|
+
return { r, points: toPointsAttr(polygonPoints(CX, CY, r, Math.max(3, axisCount.value))) }
|
|
142
|
+
}))
|
|
143
|
+
|
|
144
|
+
const spokes = computed(() => axes.value.map((label, i) => {
|
|
145
|
+
const angle = (360 / axisCount.value) * i
|
|
146
|
+
const end = polarToCartesian(CX, CY, RADIUS.value, angle)
|
|
147
|
+
// Nhãn đẩy ra ngoài mép một chút, căn theo phía để không đè lên lưới.
|
|
148
|
+
const at = polarToCartesian(CX, CY, RADIUS.value + LABEL_GAP, angle)
|
|
149
|
+
const anchor = Math.abs(angle % 360) < 1 || Math.abs((angle % 360) - 180) < 1
|
|
150
|
+
? 'middle'
|
|
151
|
+
: (angle % 360) < 180 ? 'start' : 'end'
|
|
152
|
+
return { label, end, labelAt: at, anchor, row: props.data[i] as T, index: i }
|
|
153
|
+
}))
|
|
154
|
+
|
|
155
|
+
const series = computed(() => categories.value.map((key) => {
|
|
156
|
+
const points = props.data.map((row, i) => {
|
|
157
|
+
const raw = Number(row[key]) || 0
|
|
158
|
+
const r = maxValue.value === 0 ? 0 : (RADIUS.value * Math.min(1, raw / maxValue.value))
|
|
159
|
+
return { ...polarToCartesian(CX, CY, r, (360 / axisCount.value) * i), raw }
|
|
160
|
+
})
|
|
161
|
+
return {
|
|
162
|
+
key,
|
|
163
|
+
label: props.config[key]?.label ?? key,
|
|
164
|
+
color: `var(--color-${key})`,
|
|
165
|
+
inactive: hidden.value.includes(key),
|
|
166
|
+
points: toPointsAttr(points),
|
|
167
|
+
dots: points,
|
|
168
|
+
}
|
|
169
|
+
}))
|
|
170
|
+
|
|
171
|
+
function toggle(key: string): void {
|
|
172
|
+
hidden.value = hidden.value.includes(key)
|
|
173
|
+
? hidden.value.filter(k => k !== key)
|
|
174
|
+
: [...hidden.value, key]
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function describe(label: string, raw: number, key: string): string {
|
|
178
|
+
const v = props.valueFormatter ? props.valueFormatter(raw, key) : formatChartValue(raw)
|
|
179
|
+
return `${label}: ${v}`
|
|
180
|
+
}
|
|
181
|
+
</script>
|
|
182
|
+
|
|
183
|
+
<template>
|
|
184
|
+
<ChartContainer :config="props.config" :class="cn(props.class)">
|
|
185
|
+
<div class="flex min-h-0 flex-1 items-center justify-center">
|
|
186
|
+
<svg
|
|
187
|
+
:viewBox="`0 0 ${VIEW_W} ${VIEW_H}`"
|
|
188
|
+
class="h-full max-h-full w-auto max-w-full"
|
|
189
|
+
role="img"
|
|
190
|
+
aria-label="Radar chart"
|
|
191
|
+
>
|
|
192
|
+
<!-- Nền lưới vẽ TRƯỚC để nằm dưới các vòng kẻ. -->
|
|
193
|
+
<template v-if="props.gridFill">
|
|
194
|
+
<circle v-if="props.gridShape === 'circle'" :cx="CX" :cy="CY" :r="RADIUS" :fill="gridFillColor" />
|
|
195
|
+
<polygon v-else :points="gridRings[gridRings.length - 1]?.points" :fill="gridFillColor" />
|
|
196
|
+
</template>
|
|
197
|
+
|
|
198
|
+
<!-- Lưới và nan hoa dùng --border, cùng màu với kẻ lưới của chart XY. -->
|
|
199
|
+
<template v-if="props.showGrid && props.gridShape === 'circle'">
|
|
200
|
+
<circle
|
|
201
|
+
v-for="(ring, i) in gridRings"
|
|
202
|
+
:key="`ring-${i}`"
|
|
203
|
+
:cx="CX"
|
|
204
|
+
:cy="CY"
|
|
205
|
+
:r="ring.r"
|
|
206
|
+
fill="none"
|
|
207
|
+
stroke="var(--border)"
|
|
208
|
+
stroke-width="1"
|
|
209
|
+
/>
|
|
210
|
+
</template>
|
|
211
|
+
<template v-else-if="props.showGrid">
|
|
212
|
+
<polygon
|
|
213
|
+
v-for="(ring, i) in gridRings"
|
|
214
|
+
:key="`poly-${i}`"
|
|
215
|
+
:points="ring.points"
|
|
216
|
+
fill="none"
|
|
217
|
+
stroke="var(--border)"
|
|
218
|
+
stroke-width="1"
|
|
219
|
+
/>
|
|
220
|
+
</template>
|
|
221
|
+
<line
|
|
222
|
+
v-for="(spoke, i) in (props.showSpokes ? spokes : [])"
|
|
223
|
+
:key="`spoke-${i}`"
|
|
224
|
+
:x1="CX"
|
|
225
|
+
:y1="CY"
|
|
226
|
+
:x2="spoke.end.x"
|
|
227
|
+
:y2="spoke.end.y"
|
|
228
|
+
stroke="var(--border)"
|
|
229
|
+
stroke-width="1"
|
|
230
|
+
/>
|
|
231
|
+
|
|
232
|
+
<!--
|
|
233
|
+
Nhãn nan hoa mở thành slot có phạm vi — đúng vai của prop `tick` mà
|
|
234
|
+
Recharts nhận: biến thể "custom label" của shadcn thay hẳn nội dung
|
|
235
|
+
nhãn bằng hai dòng (số/số rồi tên tháng), không phải chỉ đổi cách viết
|
|
236
|
+
một dòng. Slot thì đoạn mã hiện trên trang docs NÓI ĐÚNG thứ đang vẽ.
|
|
237
|
+
|
|
238
|
+
Nhãn nhiều dòng thì hạ `radius` để lấy chỗ — xem prop đó.
|
|
239
|
+
-->
|
|
240
|
+
<template v-for="(spoke, i) in spokes" :key="`label-${i}`">
|
|
241
|
+
<slot
|
|
242
|
+
name="spokeLabel"
|
|
243
|
+
:row="spoke.row"
|
|
244
|
+
:index="i"
|
|
245
|
+
:label="spoke.label"
|
|
246
|
+
:x="spoke.labelAt.x"
|
|
247
|
+
:y="spoke.labelAt.y"
|
|
248
|
+
:anchor="spoke.anchor"
|
|
249
|
+
>
|
|
250
|
+
<text
|
|
251
|
+
:x="spoke.labelAt.x"
|
|
252
|
+
:y="spoke.labelAt.y"
|
|
253
|
+
:text-anchor="spoke.anchor"
|
|
254
|
+
dominant-baseline="middle"
|
|
255
|
+
class="fill-muted-foreground text-[11px]"
|
|
256
|
+
>{{ spoke.label }}</text>
|
|
257
|
+
</slot>
|
|
258
|
+
</template>
|
|
259
|
+
|
|
260
|
+
<g v-for="s in series" :key="s.key" :opacity="s.inactive ? 0.12 : 1">
|
|
261
|
+
<polygon
|
|
262
|
+
:points="s.points"
|
|
263
|
+
:fill="props.showArea ? s.color : 'none'"
|
|
264
|
+
:fill-opacity="props.showArea ? props.fillOpacity : 0"
|
|
265
|
+
:stroke="s.color"
|
|
266
|
+
stroke-width="2"
|
|
267
|
+
stroke-linejoin="round"
|
|
268
|
+
/>
|
|
269
|
+
<circle
|
|
270
|
+
v-for="(dot, i) in (props.showDots ? s.dots : [])"
|
|
271
|
+
:key="i"
|
|
272
|
+
:cx="dot.x"
|
|
273
|
+
:cy="dot.y"
|
|
274
|
+
r="3"
|
|
275
|
+
:fill="s.color"
|
|
276
|
+
>
|
|
277
|
+
<title>{{ describe(s.label, dot.raw, s.key) }}</title>
|
|
278
|
+
</circle>
|
|
279
|
+
</g>
|
|
280
|
+
</svg>
|
|
281
|
+
</div>
|
|
282
|
+
|
|
283
|
+
<ChartLegend
|
|
284
|
+
v-if="props.showLegend"
|
|
285
|
+
:categories="categories"
|
|
286
|
+
:hidden="hidden"
|
|
287
|
+
interactive
|
|
288
|
+
@toggle="toggle"
|
|
289
|
+
/>
|
|
290
|
+
</ChartContainer>
|
|
291
|
+
</template>
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
<script setup lang="ts" generic="T extends Record<string, unknown>">
|
|
2
|
+
import type { ClassProps } from '../../lib/types'
|
|
3
|
+
import type { ChartConfig, ChartTooltipOptions, ChartTooltipRow, ChartValueFormatter } from './types'
|
|
4
|
+
import { computed, ref } from 'vue'
|
|
5
|
+
import { cn } from '../../lib/utils'
|
|
6
|
+
import ChartContainer from './ChartContainer.vue'
|
|
7
|
+
import ChartLegend from './ChartLegend.vue'
|
|
8
|
+
import ChartTooltipCard from './ChartTooltipCard.vue'
|
|
9
|
+
import { arcBounds, arcPath, polarToCartesian } from './polar'
|
|
10
|
+
import { formatChartValue } from './tooltip'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Radial bar. Hai cách đọc, chọn bằng `stacked`:
|
|
14
|
+
*
|
|
15
|
+
* - mặc định — mỗi HÀNG dữ liệu một vòng đồng tâm, độ dài cung theo giá trị;
|
|
16
|
+
* - `stacked` — MỘT vòng duy nhất, mỗi chuỗi một đoạn cung nối tiếp nhau.
|
|
17
|
+
*
|
|
18
|
+
* Hình học lấy theo `RadialBarChart` của Recharts, tức đúng thứ shadcn dùng:
|
|
19
|
+
* khai `innerRadius` / `outerRadius` cho CẢ biểu đồ rồi chia dải đó cho N vòng,
|
|
20
|
+
* chứ không khai bề dày từng vòng. Khác biệt quan trọng nhất: luôn có LỖ Ở GIỮA
|
|
21
|
+
* (mặc định bán kính 30) — bản trước vẽ kín tới tâm nên ra hình cái bia bắn.
|
|
22
|
+
*
|
|
23
|
+
* Vẽ tay vì Unovis không có loại này: `nested-donut` của nó là sunburst chia
|
|
24
|
+
* theo GÓC, còn radial bar chia theo BÁN KÍNH.
|
|
25
|
+
*/
|
|
26
|
+
const props = withDefaults(defineProps<ClassProps & {
|
|
27
|
+
data: T[]
|
|
28
|
+
/** Khoá chứa tên vòng. Bỏ qua khi `stacked`. */
|
|
29
|
+
index: Extract<keyof T, string>
|
|
30
|
+
/** Khoá chứa giá trị. Bỏ qua khi `stacked`. */
|
|
31
|
+
value: Extract<keyof T, string>
|
|
32
|
+
config: ChartConfig
|
|
33
|
+
/** Gộp mọi chuỗi vào MỘT vòng. Khi đó chỉ đọc hàng đầu của `data`. @default false */
|
|
34
|
+
stacked?: boolean
|
|
35
|
+
/** Các khoá làm đoạn cung khi `stacked`. Bỏ trống thì lấy khoá của `config`. */
|
|
36
|
+
categories?: Extract<keyof T, string>[]
|
|
37
|
+
/** Giá trị ứng với cung đầy. Bỏ trống thì lấy giá trị lớn nhất trong dữ liệu. */
|
|
38
|
+
max?: number
|
|
39
|
+
/** Bán kính lỗ giữa. @default 30 */
|
|
40
|
+
innerRadius?: number
|
|
41
|
+
/** Bán kính mép ngoài. @default 110 */
|
|
42
|
+
outerRadius?: number
|
|
43
|
+
/** Bo tròn hai đầu cung. Số nào lớn hơn 0 cũng cho đầu tròn. @default 0 */
|
|
44
|
+
cornerRadius?: number
|
|
45
|
+
/** Góc bắt đầu, độ, 0 ở đỉnh. @default 0 */
|
|
46
|
+
startAngle?: number
|
|
47
|
+
/** Góc kết thúc. Để 180 cho bán nguyệt. @default 360 */
|
|
48
|
+
endAngle?: number
|
|
49
|
+
/** Vệt nền mờ cho phần cung còn thiếu. @default true */
|
|
50
|
+
showTrack?: boolean
|
|
51
|
+
/** Vòng kẻ tròn làm mốc. @default false */
|
|
52
|
+
showGrid?: boolean
|
|
53
|
+
/** In tên hạng mục bên trong cung. @default false */
|
|
54
|
+
showLabels?: boolean
|
|
55
|
+
/** Chữ lớn ở giữa. */
|
|
56
|
+
centralLabel?: string
|
|
57
|
+
/** Dòng nhỏ dưới `centralLabel`. */
|
|
58
|
+
centralSubLabel?: string
|
|
59
|
+
/** @default true */
|
|
60
|
+
showTooltip?: boolean
|
|
61
|
+
/** @default false */
|
|
62
|
+
showLegend?: boolean
|
|
63
|
+
valueFormatter?: ChartValueFormatter
|
|
64
|
+
tooltip?: ChartTooltipOptions
|
|
65
|
+
}>(), {
|
|
66
|
+
stacked: false,
|
|
67
|
+
innerRadius: 30,
|
|
68
|
+
outerRadius: 110,
|
|
69
|
+
cornerRadius: 0,
|
|
70
|
+
startAngle: 0,
|
|
71
|
+
endAngle: 360,
|
|
72
|
+
showTrack: true,
|
|
73
|
+
showGrid: false,
|
|
74
|
+
showLabels: false,
|
|
75
|
+
showTooltip: true,
|
|
76
|
+
showLegend: false,
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
/** Lề quanh mép ngoài, đủ để nét vẽ không bị viewBox cắt. */
|
|
80
|
+
const PAD = 8
|
|
81
|
+
|
|
82
|
+
const hidden = ref<string[]>([])
|
|
83
|
+
const hoverKey = ref<string | null>(null)
|
|
84
|
+
const pointer = ref({ x: 0, y: 0 })
|
|
85
|
+
|
|
86
|
+
const center = computed(() => props.outerRadius + PAD)
|
|
87
|
+
|
|
88
|
+
const categories = computed(() => (props.categories ?? Object.keys(props.config)) as string[])
|
|
89
|
+
|
|
90
|
+
const names = computed(() => (props.stacked
|
|
91
|
+
? categories.value
|
|
92
|
+
: props.data.map(row => String(row[props.index]))))
|
|
93
|
+
|
|
94
|
+
function colorOf(name: string): string {
|
|
95
|
+
return `var(--color-${name})`
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function labelOf(name: string): string {
|
|
99
|
+
return props.config[name]?.label ?? name
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function formatValue(raw: number, name: string): string {
|
|
103
|
+
return props.valueFormatter ? props.valueFormatter(raw, name) : formatChartValue(raw)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const sweep = computed(() => props.endAngle - props.startAngle)
|
|
107
|
+
|
|
108
|
+
const maxValue = computed(() => {
|
|
109
|
+
if (props.max !== undefined)
|
|
110
|
+
return props.max
|
|
111
|
+
if (props.stacked) {
|
|
112
|
+
const row = props.data[0]
|
|
113
|
+
return categories.value.reduce((sum, key) => sum + (Number(row?.[key]) || 0), 0) || 1
|
|
114
|
+
}
|
|
115
|
+
return Math.max(...props.data.map(row => Number(row[props.value]) || 0), 1)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Chia dải `[innerRadius, outerRadius]` cho N vòng, vòng ĐẦU nằm ngoài cùng —
|
|
120
|
+
* đúng thứ tự Recharts xếp. Chừa 12% làm khe giữa hai vòng.
|
|
121
|
+
*/
|
|
122
|
+
function band(i: number, count: number): { mid: number, width: number } {
|
|
123
|
+
const per = (props.outerRadius - props.innerRadius) / Math.max(1, count)
|
|
124
|
+
const outer = props.outerRadius - i * per
|
|
125
|
+
const width = per * 0.88
|
|
126
|
+
return { mid: outer - width / 2, width }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
interface Segment {
|
|
130
|
+
key: string
|
|
131
|
+
label: string
|
|
132
|
+
color: string
|
|
133
|
+
value: string
|
|
134
|
+
raw: number
|
|
135
|
+
mid: number
|
|
136
|
+
width: number
|
|
137
|
+
trackPath: string
|
|
138
|
+
valuePath: string
|
|
139
|
+
labelAt: { x: number, y: number }
|
|
140
|
+
labelFits: boolean
|
|
141
|
+
inactive: boolean
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const segments = computed<Segment[]>(() => {
|
|
145
|
+
const c = center.value
|
|
146
|
+
|
|
147
|
+
if (props.stacked) {
|
|
148
|
+
// MỘT vòng, các đoạn nối tiếp: đoạn sau bắt đầu ở chỗ đoạn trước dừng lại.
|
|
149
|
+
const row = props.data[0]
|
|
150
|
+
const b = band(0, 1)
|
|
151
|
+
let acc = 0
|
|
152
|
+
return categories.value.map((key, i) => {
|
|
153
|
+
const raw = Number(row?.[key]) || 0
|
|
154
|
+
const from = props.startAngle + (acc / maxValue.value) * sweep.value
|
|
155
|
+
acc += raw
|
|
156
|
+
const to = props.startAngle + (acc / maxValue.value) * sweep.value
|
|
157
|
+
const arcLen = ((to - from) / 360) * 2 * Math.PI * b.mid
|
|
158
|
+
return {
|
|
159
|
+
key,
|
|
160
|
+
label: labelOf(key),
|
|
161
|
+
color: colorOf(key),
|
|
162
|
+
raw,
|
|
163
|
+
value: formatValue(raw, key),
|
|
164
|
+
mid: b.mid,
|
|
165
|
+
width: b.width,
|
|
166
|
+
trackPath: i === 0 ? arcPath(c, c, b.mid, props.startAngle, props.endAngle) : '',
|
|
167
|
+
valuePath: arcPath(c, c, b.mid, from, to),
|
|
168
|
+
labelAt: polarToCartesian(c, c, b.mid, (from + to) / 2),
|
|
169
|
+
labelFits: arcLen > 46,
|
|
170
|
+
inactive: hidden.value.includes(key),
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return props.data.map((row, i) => {
|
|
176
|
+
const name = String(row[props.index])
|
|
177
|
+
const raw = Number(row[props.value]) || 0
|
|
178
|
+
const ratio = maxValue.value === 0 ? 0 : Math.min(1, raw / maxValue.value)
|
|
179
|
+
const b = band(i, props.data.length)
|
|
180
|
+
// Nhãn đặt ở ĐẦU cung, thụt vào một chút — đúng `insideStart` của shadcn.
|
|
181
|
+
// Mọi vòng cùng góc bắt đầu nhưng khác bán kính nên chúng xếp thành một cột
|
|
182
|
+
// theo hướng đó. HƯỚNG ẤY PHẢI LÀ DỌC: đặt góc bắt đầu ở 9 hoặc 3 giờ thì
|
|
183
|
+
// các nhãn nằm ngang cùng một hàng và đè lên nhau (bán kính chỉ cách nhau
|
|
184
|
+
// ~17px trong khi chữ rộng ~35px). Ví dụ Label vì vậy bắt đầu ở 6 giờ.
|
|
185
|
+
const arcLen = ((sweep.value * ratio) / 360) * 2 * Math.PI * b.mid
|
|
186
|
+
const insetDeg = (10 / (2 * Math.PI * b.mid)) * 360
|
|
187
|
+
const labelAngle = props.startAngle + insetDeg
|
|
188
|
+
return {
|
|
189
|
+
key: name,
|
|
190
|
+
label: labelOf(name),
|
|
191
|
+
color: colorOf(name),
|
|
192
|
+
raw,
|
|
193
|
+
value: formatValue(raw, name),
|
|
194
|
+
mid: b.mid,
|
|
195
|
+
width: b.width,
|
|
196
|
+
trackPath: arcPath(c, c, b.mid, props.startAngle, props.endAngle),
|
|
197
|
+
valuePath: arcPath(c, c, b.mid, props.startAngle, props.startAngle + sweep.value * ratio),
|
|
198
|
+
labelAt: polarToCartesian(c, c, b.mid, labelAngle),
|
|
199
|
+
labelFits: arcLen > 46,
|
|
200
|
+
inactive: hidden.value.includes(name),
|
|
201
|
+
}
|
|
202
|
+
})
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
/** Vòng kẻ tròn ở mép ngoài của từng vòng dữ liệu. */
|
|
206
|
+
const gridRadii = computed(() => (props.showGrid
|
|
207
|
+
? segments.value.map(s => s.mid + s.width / 2)
|
|
208
|
+
: []))
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* viewBox cắt theo cung THẬT SỰ được vẽ, cộng lề.
|
|
212
|
+
* Để nguyên khung vuông thì biểu đồ bán nguyệt dạt lên trên và bỏ trống nửa
|
|
213
|
+
* dưới — trông như bị lệch chứ không phải như một cái đồng hồ đo.
|
|
214
|
+
*/
|
|
215
|
+
const bounds = computed(() => {
|
|
216
|
+
const c = center.value
|
|
217
|
+
return arcBounds(c, c, props.outerRadius, props.innerRadius, props.startAngle, props.endAngle)
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
const viewBox = computed(() => {
|
|
221
|
+
const b = bounds.value
|
|
222
|
+
return `${b.x - PAD} ${b.y - PAD} ${b.width + PAD * 2} ${b.height + PAD * 2}`
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Chữ giữa đặt ở tâm KHUNG NHÌN THẤY, không phải tâm cực.
|
|
227
|
+
*
|
|
228
|
+
* Với cung không trọn vòng, viewBox bị cắt lệch nên tâm cực rơi ra sát mép —
|
|
229
|
+
* biểu đồ bán nguyệt vẽ ra là chữ "1.830" nằm ngoài khung và bị xén mất nửa.
|
|
230
|
+
* Lấy tâm của khung đã cắt thì chữ luôn nằm giữa phần nhìn thấy.
|
|
231
|
+
*/
|
|
232
|
+
const labelCenter = computed(() => {
|
|
233
|
+
const b = bounds.value
|
|
234
|
+
return { x: b.x + b.width / 2, y: b.y + b.height / 2 }
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
const hovered = computed(() => segments.value.find(s => s.key === hoverKey.value) ?? null)
|
|
238
|
+
|
|
239
|
+
const tooltipRows = computed<ChartTooltipRow[]>(() => {
|
|
240
|
+
const s = hovered.value
|
|
241
|
+
return s ? [{ key: s.key, label: s.label, color: s.color, value: s.value, raw: s.raw }] : []
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
function onMove(event: MouseEvent): void {
|
|
245
|
+
const host = (event.currentTarget as HTMLElement).getBoundingClientRect()
|
|
246
|
+
pointer.value = { x: event.clientX - host.left, y: event.clientY - host.top }
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function toggle(name: string): void {
|
|
250
|
+
hidden.value = hidden.value.includes(name)
|
|
251
|
+
? hidden.value.filter(n => n !== name)
|
|
252
|
+
: [...hidden.value, name]
|
|
253
|
+
}
|
|
254
|
+
</script>
|
|
255
|
+
|
|
256
|
+
<template>
|
|
257
|
+
<ChartContainer :config="props.config" :class="cn(props.class)">
|
|
258
|
+
<div class="relative flex min-h-0 flex-1 items-center justify-center" @mousemove="onMove">
|
|
259
|
+
<svg
|
|
260
|
+
:viewBox="viewBox"
|
|
261
|
+
class="h-full max-h-full w-auto max-w-full"
|
|
262
|
+
role="img"
|
|
263
|
+
:aria-label="props.centralLabel ?? 'Radial chart'"
|
|
264
|
+
>
|
|
265
|
+
<circle
|
|
266
|
+
v-for="(r, i) in gridRadii"
|
|
267
|
+
:key="`grid-${i}`"
|
|
268
|
+
:cx="center"
|
|
269
|
+
:cy="center"
|
|
270
|
+
:r="r"
|
|
271
|
+
fill="none"
|
|
272
|
+
stroke="var(--border)"
|
|
273
|
+
stroke-width="1"
|
|
274
|
+
/>
|
|
275
|
+
|
|
276
|
+
<g
|
|
277
|
+
v-for="segment in segments"
|
|
278
|
+
:key="segment.key"
|
|
279
|
+
@mouseenter="hoverKey = segment.key"
|
|
280
|
+
@mouseleave="hoverKey = null"
|
|
281
|
+
>
|
|
282
|
+
<!--
|
|
283
|
+
Cung vẽ bằng NÉT DÀY chạy dọc đường tâm của vành, không phải hình
|
|
284
|
+
quạt được tô. Nhờ vậy `stroke-linecap="round"` cho hai đầu bo tròn
|
|
285
|
+
mà không phải dựng path bo góc.
|
|
286
|
+
Vệt nền dùng --muted chứ không phải màu chuỗi pha loãng: pha loãng
|
|
287
|
+
thì phần thiếu vẫn mang sắc của chuỗi và đọc ra như đã đạt.
|
|
288
|
+
-->
|
|
289
|
+
<path
|
|
290
|
+
v-if="props.showTrack && segment.trackPath"
|
|
291
|
+
:d="segment.trackPath"
|
|
292
|
+
fill="none"
|
|
293
|
+
stroke="var(--muted)"
|
|
294
|
+
:stroke-width="segment.width"
|
|
295
|
+
:stroke-linecap="props.cornerRadius > 0 ? 'round' : 'butt'"
|
|
296
|
+
/>
|
|
297
|
+
<path
|
|
298
|
+
v-if="segment.valuePath"
|
|
299
|
+
:d="segment.valuePath"
|
|
300
|
+
fill="none"
|
|
301
|
+
:stroke="segment.color"
|
|
302
|
+
:stroke-width="segment.width"
|
|
303
|
+
:stroke-linecap="props.cornerRadius > 0 ? 'round' : 'butt'"
|
|
304
|
+
:opacity="segment.inactive ? 0.15 : 1"
|
|
305
|
+
>
|
|
306
|
+
<title>{{ segment.label }}: {{ segment.value }}</title>
|
|
307
|
+
</path>
|
|
308
|
+
<text
|
|
309
|
+
v-if="props.showLabels && segment.labelFits && !segment.inactive"
|
|
310
|
+
:x="segment.labelAt.x"
|
|
311
|
+
:y="segment.labelAt.y"
|
|
312
|
+
text-anchor="middle"
|
|
313
|
+
dominant-baseline="middle"
|
|
314
|
+
class="pointer-events-none text-[10px] font-medium"
|
|
315
|
+
:fill="`var(--color-${segment.key}-foreground)`"
|
|
316
|
+
>{{ segment.label }}</text>
|
|
317
|
+
</g>
|
|
318
|
+
|
|
319
|
+
<text
|
|
320
|
+
v-if="props.centralLabel"
|
|
321
|
+
:x="labelCenter.x"
|
|
322
|
+
:y="props.centralSubLabel ? labelCenter.y - 4 : labelCenter.y"
|
|
323
|
+
text-anchor="middle"
|
|
324
|
+
dominant-baseline="middle"
|
|
325
|
+
class="fill-foreground text-[24px] font-semibold tabular-nums"
|
|
326
|
+
>{{ props.centralLabel }}</text>
|
|
327
|
+
<text
|
|
328
|
+
v-if="props.centralSubLabel"
|
|
329
|
+
:x="labelCenter.x"
|
|
330
|
+
:y="labelCenter.y + 18"
|
|
331
|
+
text-anchor="middle"
|
|
332
|
+
dominant-baseline="middle"
|
|
333
|
+
class="fill-muted-foreground text-[12px]"
|
|
334
|
+
>{{ props.centralSubLabel }}</text>
|
|
335
|
+
</svg>
|
|
336
|
+
|
|
337
|
+
<div
|
|
338
|
+
v-if="props.showTooltip && hovered"
|
|
339
|
+
class="pointer-events-none absolute z-(--z-raised)"
|
|
340
|
+
:style="{ left: `${pointer.x + 14}px`, top: `${pointer.y + 14}px` }"
|
|
341
|
+
>
|
|
342
|
+
<ChartTooltipCard :rows="tooltipRows" :options="{ hideLabel: true, ...props.tooltip }" />
|
|
343
|
+
</div>
|
|
344
|
+
</div>
|
|
345
|
+
|
|
346
|
+
<ChartLegend
|
|
347
|
+
v-if="props.showLegend"
|
|
348
|
+
:categories="names"
|
|
349
|
+
:hidden="hidden"
|
|
350
|
+
interactive
|
|
351
|
+
@toggle="toggle"
|
|
352
|
+
/>
|
|
353
|
+
</ChartContainer>
|
|
354
|
+
</template>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export { default as AreaChart } from './AreaChart.vue'
|
|
2
|
+
export { default as BarChart } from './BarChart.vue'
|
|
3
|
+
export { default as ChartContainer } from './ChartContainer.vue'
|
|
4
|
+
export { default as ChartCrosshair } from './ChartCrosshair.vue'
|
|
5
|
+
export { default as ChartLegend } from './ChartLegend.vue'
|
|
6
|
+
export { default as ChartTooltipCard } from './ChartTooltipCard.vue'
|
|
7
|
+
export { default as LineChart } from './LineChart.vue'
|
|
8
|
+
export { default as PieChart } from './PieChart.vue'
|
|
9
|
+
export { annularArcPath, arcBounds, arcPath, polarToCartesian, polygonPoints, toPointsAttr } from './polar'
|
|
10
|
+
export type { Point } from './polar'
|
|
11
|
+
|
|
12
|
+
export { default as RadarChart } from './RadarChart.vue'
|
|
13
|
+
export { default as RadialChart } from './RadialChart.vue'
|
|
14
|
+
export { escapeHtml, formatChartValue, renderTooltip } from './tooltip'
|
|
15
|
+
export type {
|
|
16
|
+
ChartBaseProps,
|
|
17
|
+
ChartConfig,
|
|
18
|
+
ChartContext,
|
|
19
|
+
ChartCurve,
|
|
20
|
+
ChartLabelKey,
|
|
21
|
+
ChartSeriesConfig,
|
|
22
|
+
ChartTickFormatter,
|
|
23
|
+
ChartTooltipOptions,
|
|
24
|
+
ChartTooltipRow,
|
|
25
|
+
ChartValueFormatter,
|
|
26
|
+
} from './types'
|
|
27
|
+
export { CHART_CONTEXT_KEY } from './types'
|
|
28
|
+
export { toCurveType, useChartSeries } from './useChartSeries'
|
|
29
|
+
export { xyLabelHtml } from './xy-label'
|
|
30
|
+
export type { XYLabelOffset } from './xy-label'
|