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