@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,472 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ClassProps } from '../../lib/types'
|
|
3
|
+
import type { VideoPlayerControl, VideoPlayerPlacement, VideoPlayerVolumeLayout } from './index'
|
|
4
|
+
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
|
5
|
+
import { useI18n } from '../../composables/useI18n'
|
|
6
|
+
import {
|
|
7
|
+
Gauge,
|
|
8
|
+
Maximize,
|
|
9
|
+
Minimize,
|
|
10
|
+
Pause,
|
|
11
|
+
Play,
|
|
12
|
+
RotateCcw,
|
|
13
|
+
RotateCw,
|
|
14
|
+
Volume1,
|
|
15
|
+
Volume2,
|
|
16
|
+
VolumeX,
|
|
17
|
+
} from '../../lib/icons'
|
|
18
|
+
import { cn } from '../../lib/utils'
|
|
19
|
+
import { clampMediaTime, formatMediaTime, mediaVolumeLevel } from '../../variants/lib/media'
|
|
20
|
+
import { Button } from '../button'
|
|
21
|
+
import {
|
|
22
|
+
DropdownMenu,
|
|
23
|
+
DropdownMenuContent,
|
|
24
|
+
DropdownMenuLabel,
|
|
25
|
+
DropdownMenuRadioGroup,
|
|
26
|
+
DropdownMenuRadioItem,
|
|
27
|
+
DropdownMenuSeparator,
|
|
28
|
+
DropdownMenuTrigger,
|
|
29
|
+
} from '../dropdown-menu'
|
|
30
|
+
import { Slider } from '../slider'
|
|
31
|
+
|
|
32
|
+
const props = withDefaults(
|
|
33
|
+
defineProps<ClassProps & {
|
|
34
|
+
src: string
|
|
35
|
+
/**
|
|
36
|
+
* Ảnh bìa. Có bìa thì player mở ra với một nút chạy lớn giữa khung và
|
|
37
|
+
* THANH ĐIỀU KHIỂN ẨN cho tới lần chạy đầu — trước đó nó không có gì để nói.
|
|
38
|
+
*/
|
|
39
|
+
poster?: string
|
|
40
|
+
/** Hiện trên thanh điều khiển, cắt bớt khi hẹp. */
|
|
41
|
+
title?: string
|
|
42
|
+
autoplay?: boolean
|
|
43
|
+
loop?: boolean
|
|
44
|
+
muted?: boolean
|
|
45
|
+
preload?: 'none' | 'metadata' | 'auto'
|
|
46
|
+
/** Mục nào hiện trên thanh. Mặc định là đủ cả bảy. */
|
|
47
|
+
controls?: VideoPlayerControl[]
|
|
48
|
+
placement?: VideoPlayerPlacement
|
|
49
|
+
volumeLayout?: VideoPlayerVolumeLayout
|
|
50
|
+
skipSeconds?: number
|
|
51
|
+
rates?: number[]
|
|
52
|
+
}>(),
|
|
53
|
+
{
|
|
54
|
+
autoplay: false,
|
|
55
|
+
loop: false,
|
|
56
|
+
muted: false,
|
|
57
|
+
preload: 'metadata',
|
|
58
|
+
controls: () => ['play', 'skip', 'seek', 'time', 'rate', 'volume', 'fullscreen'],
|
|
59
|
+
placement: 'overlay',
|
|
60
|
+
volumeLayout: 'inline',
|
|
61
|
+
skipSeconds: 10,
|
|
62
|
+
rates: () => [0.5, 0.75, 1, 1.25, 1.5, 2],
|
|
63
|
+
},
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
const emit = defineEmits<{
|
|
67
|
+
play: []
|
|
68
|
+
pause: []
|
|
69
|
+
ended: []
|
|
70
|
+
timeupdate: [seconds: number]
|
|
71
|
+
}>()
|
|
72
|
+
|
|
73
|
+
const { t } = useI18n()
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Xin toàn màn hình cho CÁI VỎ, không cho thẻ `<video>`.
|
|
77
|
+
*
|
|
78
|
+
* Đưa chính thẻ video vào toàn màn hình thì trình duyệt thay bằng bộ điều khiển
|
|
79
|
+
* mặc định của nó và thanh của mình biến mất — đúng thứ component này đang dựng.
|
|
80
|
+
*/
|
|
81
|
+
const shell = ref<HTMLElement>()
|
|
82
|
+
const video = ref<HTMLVideoElement>()
|
|
83
|
+
|
|
84
|
+
const playing = ref(false)
|
|
85
|
+
/**
|
|
86
|
+
* `started` KHÁC `playing`: nó chỉ bật một lần và không tắt lại.
|
|
87
|
+
*
|
|
88
|
+
* Dùng `playing` để ẩn tấm bìa thì bấm tạm dừng là bìa quay lại đè lên đúng
|
|
89
|
+
* khung hình người ta vừa dừng để nhìn.
|
|
90
|
+
*/
|
|
91
|
+
const started = ref(false)
|
|
92
|
+
const currentTime = ref(0)
|
|
93
|
+
const duration = ref(0)
|
|
94
|
+
const volume = ref(1)
|
|
95
|
+
const muted = ref(props.muted)
|
|
96
|
+
const rate = ref('1')
|
|
97
|
+
const fullscreen = ref(false)
|
|
98
|
+
|
|
99
|
+
function has(control: VideoPlayerControl): boolean {
|
|
100
|
+
return props.controls.includes(control)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Bìa chỉ che khi chưa từng chạy; sau đó không bao giờ quay lại. */
|
|
104
|
+
const showPoster = computed(() => props.poster !== undefined && !started.value)
|
|
105
|
+
/** Có bìa thì thanh im lặng cho tới lần chạy đầu. */
|
|
106
|
+
const showBar = computed(() => props.controls.length > 0 && !showPoster.value)
|
|
107
|
+
|
|
108
|
+
const volumeIcon = computed(() => {
|
|
109
|
+
const level = mediaVolumeLevel(volume.value, muted.value)
|
|
110
|
+
if (level === 'off')
|
|
111
|
+
return VolumeX
|
|
112
|
+
|
|
113
|
+
return level === 'low' ? Volume1 : Volume2
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Tắt thanh tua thì hai mốc thời gian gộp làm một ô `0:12 / 1:00`; bật thì
|
|
118
|
+
* chúng tách ra hai đầu thanh, đúng bố cục quen thuộc của một trình phát.
|
|
119
|
+
*/
|
|
120
|
+
const splitTime = computed(() => has('seek'))
|
|
121
|
+
|
|
122
|
+
function toggle(): void {
|
|
123
|
+
const el = video.value
|
|
124
|
+
if (!el)
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
if (el.paused)
|
|
128
|
+
void el.play().catch(() => {})
|
|
129
|
+
else
|
|
130
|
+
el.pause()
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function skip(seconds: number): void {
|
|
134
|
+
const el = video.value
|
|
135
|
+
if (!el)
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
el.currentTime = clampMediaTime(el.currentTime + seconds, el.duration)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function seek(value: number[] | undefined): void {
|
|
142
|
+
const el = video.value
|
|
143
|
+
if (!el || value?.[0] === undefined)
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
el.currentTime = clampMediaTime(value[0], el.duration)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Kéo âm lượng lên thì BỎ tắt tiếng luôn — không ai kéo thanh đó để vẫn im. */
|
|
150
|
+
function changeVolume(value: number[] | undefined): void {
|
|
151
|
+
const el = video.value
|
|
152
|
+
if (!el || value?.[0] === undefined)
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
el.volume = value[0]
|
|
156
|
+
el.muted = value[0] === 0
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function toggleMute(): void {
|
|
160
|
+
const el = video.value
|
|
161
|
+
if (el)
|
|
162
|
+
el.muted = !el.muted
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Nhận `unknown` chứ không phải `string`: radio group của Reka phát ra
|
|
167
|
+
* `AcceptableValue`, trong đó có cả `null`.
|
|
168
|
+
*/
|
|
169
|
+
function changeRate(value: unknown): void {
|
|
170
|
+
if (typeof value !== 'string')
|
|
171
|
+
return
|
|
172
|
+
|
|
173
|
+
rate.value = value
|
|
174
|
+
if (video.value)
|
|
175
|
+
video.value.playbackRate = Number(value)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Nuốt lỗi là cố ý: `requestFullscreen()` bị từ chối khi tài liệu không có
|
|
180
|
+
* quyền — trong iframe thiếu `allowfullscreen` chẳng hạn — và một lỗi chưa bắt
|
|
181
|
+
* thì console đỏ lên trong khi người dùng chẳng có gì để làm.
|
|
182
|
+
*/
|
|
183
|
+
function toggleFullscreen(): void {
|
|
184
|
+
if (document.fullscreenElement)
|
|
185
|
+
void document.exitFullscreen()
|
|
186
|
+
else
|
|
187
|
+
void shell.value?.requestFullscreen().catch(() => {})
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function syncFullscreen(): void {
|
|
191
|
+
fullscreen.value = document.fullscreenElement === shell.value
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function onTimeUpdate(event: Event): void {
|
|
195
|
+
currentTime.value = (event.target as HTMLVideoElement).currentTime
|
|
196
|
+
emit('timeupdate', currentTime.value)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function onVolumeChange(event: Event): void {
|
|
200
|
+
const el = event.target as HTMLVideoElement
|
|
201
|
+
volume.value = el.volume
|
|
202
|
+
muted.value = el.muted
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function onPlay(): void {
|
|
206
|
+
started.value = true
|
|
207
|
+
playing.value = true
|
|
208
|
+
emit('play')
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function onPause(): void {
|
|
212
|
+
playing.value = false
|
|
213
|
+
emit('pause')
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Tự chạy bằng `play()` chứ KHÔNG bằng thuộc tính `autoplay`.
|
|
218
|
+
*
|
|
219
|
+
* Thuộc tính đó không hỏi được `prefers-reduced-motion`, mà một vòng lặp tự
|
|
220
|
+
* chạy đúng là thứ tuỳ chọn ấy sinh ra để tắt. `play()` còn trả về Promise nên
|
|
221
|
+
* bắt được lúc trình duyệt từ chối — im lặng ở đây là đúng, người dùng vẫn còn
|
|
222
|
+
* khung hình đầu để nhìn.
|
|
223
|
+
*
|
|
224
|
+
* Gọi lúc mount là ĐỦ, dù lúc đó `readyState` còn 0 và `currentSrc` còn rỗng.
|
|
225
|
+
* Có lúc tưởng không đủ và đã thêm một lượt gọi lại ở `canplay`; đo kỹ thì đó
|
|
226
|
+
* là lỗi PHÉP ĐO chứ không phải của component — tab đang ẩn
|
|
227
|
+
* (`document.visibilityState === 'hidden'`) thì Chrome cho `play` và `playing`
|
|
228
|
+
* bắn ra rồi tự phát `pause` sau 2ms, không qua JS nào và Promise vẫn resolve.
|
|
229
|
+
* Đưa tab ra trước là chạy đúng. Đã gỡ lượt gọi thừa đó.
|
|
230
|
+
*/
|
|
231
|
+
function tryAutoplay(): void {
|
|
232
|
+
if (!props.autoplay || started.value)
|
|
233
|
+
return
|
|
234
|
+
|
|
235
|
+
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches)
|
|
236
|
+
return
|
|
237
|
+
|
|
238
|
+
void video.value?.play().catch(() => {})
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
onMounted(() => {
|
|
242
|
+
document.addEventListener('fullscreenchange', syncFullscreen)
|
|
243
|
+
tryAutoplay()
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
onBeforeUnmount(() => document.removeEventListener('fullscreenchange', syncFullscreen))
|
|
247
|
+
</script>
|
|
248
|
+
|
|
249
|
+
<template>
|
|
250
|
+
<div
|
|
251
|
+
ref="shell"
|
|
252
|
+
:class="cn('relative overflow-hidden rounded-xl border border-border bg-card', props.class)"
|
|
253
|
+
>
|
|
254
|
+
<div class="relative aspect-video w-full bg-muted">
|
|
255
|
+
<video
|
|
256
|
+
ref="video"
|
|
257
|
+
:src="props.src"
|
|
258
|
+
:loop="props.loop"
|
|
259
|
+
:muted="props.muted"
|
|
260
|
+
:preload="props.preload"
|
|
261
|
+
class="h-full w-full object-cover"
|
|
262
|
+
playsinline
|
|
263
|
+
@click="toggle"
|
|
264
|
+
@play="onPlay"
|
|
265
|
+
@pause="onPause"
|
|
266
|
+
@ended="emit('ended')"
|
|
267
|
+
@timeupdate="onTimeUpdate"
|
|
268
|
+
@loadedmetadata="duration = ($event.target as HTMLVideoElement).duration"
|
|
269
|
+
@volumechange="onVolumeChange"
|
|
270
|
+
/>
|
|
271
|
+
|
|
272
|
+
<!--
|
|
273
|
+
`<video>` phải TỰ ĐÓNG. Để nó mở thì mọi thứ viết sau trở thành con của
|
|
274
|
+
nó, mà con của `<video>` là nội dung DỰ PHÒNG — chỉ hiện khi trình duyệt
|
|
275
|
+
không phát được. Tức tấm bìa và slot sẽ không bao giờ vẽ ra, và không có
|
|
276
|
+
lỗi nào báo. Đã dính một lần; chính lần eslint --fix thụt lại dòng mới
|
|
277
|
+
làm nó lộ ra.
|
|
278
|
+
-->
|
|
279
|
+
|
|
280
|
+
<!-- Nội dung tự do đặt trên khung hình: tiêu đề hero, nhãn, huy hiệu… -->
|
|
281
|
+
<slot name="overlay" />
|
|
282
|
+
|
|
283
|
+
<!--
|
|
284
|
+
Bìa vẽ bằng `<img>` chứ không bằng thuộc tính `poster`. Thuộc tính đó
|
|
285
|
+
không tự tắt được bằng state, và filter đặt lên `<video>` để làm dịu bìa
|
|
286
|
+
ở chế độ tối sẽ dính cả khung hình lúc đang chạy.
|
|
287
|
+
-->
|
|
288
|
+
<div v-if="showPoster" class="absolute inset-0">
|
|
289
|
+
<img :src="props.poster" alt="" class="h-full w-full object-cover">
|
|
290
|
+
|
|
291
|
+
<div class="absolute inset-0 grid place-items-center">
|
|
292
|
+
<Button
|
|
293
|
+
size="icon-lg"
|
|
294
|
+
class="size-16 rounded-full [&_svg]:size-6"
|
|
295
|
+
:aria-label="t('ui.videoPlayer.play')"
|
|
296
|
+
@click="toggle"
|
|
297
|
+
>
|
|
298
|
+
<Play class="translate-x-0.5 fill-current" />
|
|
299
|
+
</Button>
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
</div>
|
|
303
|
+
|
|
304
|
+
<!--
|
|
305
|
+
MỘT thanh duy nhất cho cả hai kiểu đặt, đổi bằng class chứ không nhân đôi
|
|
306
|
+
markup: nó là con của vỏ `relative`, nên `absolute` thì nổi lên khung hình
|
|
307
|
+
còn tĩnh thì tự cộng thêm chiều cao bên dưới.
|
|
308
|
+
|
|
309
|
+
Nền `bg-background/85` chứ không `bg-black/60`: đó là mặt nền của theme
|
|
310
|
+
nên nó đi theo bảng màu, thay vì là mảng duy nhất đứng im khi đổi theme.
|
|
311
|
+
-->
|
|
312
|
+
<div
|
|
313
|
+
v-if="showBar"
|
|
314
|
+
:class="cn(
|
|
315
|
+
'flex items-center gap-2',
|
|
316
|
+
props.placement === 'overlay'
|
|
317
|
+
? 'absolute inset-x-2 bottom-2 rounded-lg border border-border bg-background/85 px-3 py-2 backdrop-blur-sm'
|
|
318
|
+
: 'border-t border-border px-3 py-2.5',
|
|
319
|
+
)"
|
|
320
|
+
>
|
|
321
|
+
<Button
|
|
322
|
+
v-if="has('skip')"
|
|
323
|
+
size="icon-sm"
|
|
324
|
+
variant="ghost"
|
|
325
|
+
:aria-label="t('ui.videoPlayer.skipBack', { seconds: props.skipSeconds })"
|
|
326
|
+
@click="skip(-props.skipSeconds)"
|
|
327
|
+
>
|
|
328
|
+
<RotateCcw />
|
|
329
|
+
</Button>
|
|
330
|
+
|
|
331
|
+
<Button
|
|
332
|
+
v-if="has('play')"
|
|
333
|
+
size="icon-sm"
|
|
334
|
+
variant="ghost"
|
|
335
|
+
:aria-label="playing ? t('ui.videoPlayer.pause') : t('ui.videoPlayer.play')"
|
|
336
|
+
@click="toggle"
|
|
337
|
+
>
|
|
338
|
+
<Pause v-if="playing" />
|
|
339
|
+
<Play v-else />
|
|
340
|
+
</Button>
|
|
341
|
+
|
|
342
|
+
<Button
|
|
343
|
+
v-if="has('skip')"
|
|
344
|
+
size="icon-sm"
|
|
345
|
+
variant="ghost"
|
|
346
|
+
:aria-label="t('ui.videoPlayer.skipForward', { seconds: props.skipSeconds })"
|
|
347
|
+
@click="skip(props.skipSeconds)"
|
|
348
|
+
>
|
|
349
|
+
<RotateCw />
|
|
350
|
+
</Button>
|
|
351
|
+
|
|
352
|
+
<p v-if="props.title" class="min-w-0 flex-1 truncate text-sm text-muted-foreground">
|
|
353
|
+
{{ props.title }}
|
|
354
|
+
</p>
|
|
355
|
+
|
|
356
|
+
<span
|
|
357
|
+
v-if="has('time')"
|
|
358
|
+
class="shrink-0 font-mono text-xs tabular-nums text-muted-foreground"
|
|
359
|
+
>
|
|
360
|
+
{{ formatMediaTime(currentTime) }}<template v-if="!splitTime"> / {{ formatMediaTime(duration) }}</template>
|
|
361
|
+
</span>
|
|
362
|
+
|
|
363
|
+
<Slider
|
|
364
|
+
v-if="has('seek')"
|
|
365
|
+
size="sm"
|
|
366
|
+
class="flex-1"
|
|
367
|
+
:model-value="[currentTime]"
|
|
368
|
+
:max="duration || 100"
|
|
369
|
+
:step="0.1"
|
|
370
|
+
:aria-label="t('ui.videoPlayer.seek')"
|
|
371
|
+
@update:model-value="seek"
|
|
372
|
+
/>
|
|
373
|
+
|
|
374
|
+
<span
|
|
375
|
+
v-if="has('time') && splitTime"
|
|
376
|
+
class="shrink-0 font-mono text-xs tabular-nums text-muted-foreground"
|
|
377
|
+
>
|
|
378
|
+
{{ formatMediaTime(duration) }}
|
|
379
|
+
</span>
|
|
380
|
+
|
|
381
|
+
<DropdownMenu v-if="has('rate')">
|
|
382
|
+
<DropdownMenuTrigger as-child>
|
|
383
|
+
<Button
|
|
384
|
+
size="sm"
|
|
385
|
+
variant="ghost"
|
|
386
|
+
class="shrink-0 tabular-nums"
|
|
387
|
+
:aria-label="t('ui.videoPlayer.playbackSpeed')"
|
|
388
|
+
>
|
|
389
|
+
<Gauge />
|
|
390
|
+
{{ rate }}×
|
|
391
|
+
</Button>
|
|
392
|
+
</DropdownMenuTrigger>
|
|
393
|
+
|
|
394
|
+
<DropdownMenuContent align="end" class="w-36">
|
|
395
|
+
<DropdownMenuLabel>{{ t('ui.videoPlayer.playbackSpeed') }}</DropdownMenuLabel>
|
|
396
|
+
<DropdownMenuSeparator />
|
|
397
|
+
<DropdownMenuRadioGroup :model-value="rate" @update:model-value="changeRate">
|
|
398
|
+
<DropdownMenuRadioItem v-for="value in props.rates" :key="value" :value="String(value)">
|
|
399
|
+
{{ value }}×
|
|
400
|
+
</DropdownMenuRadioItem>
|
|
401
|
+
</DropdownMenuRadioGroup>
|
|
402
|
+
</DropdownMenuContent>
|
|
403
|
+
</DropdownMenu>
|
|
404
|
+
|
|
405
|
+
<div v-if="has('volume')" class="group/volume relative flex shrink-0 items-center gap-2">
|
|
406
|
+
<Button
|
|
407
|
+
size="icon-sm"
|
|
408
|
+
variant="ghost"
|
|
409
|
+
:aria-label="muted ? t('ui.videoPlayer.unmute') : t('ui.videoPlayer.mute')"
|
|
410
|
+
@click="toggleMute"
|
|
411
|
+
>
|
|
412
|
+
<component :is="volumeIcon" />
|
|
413
|
+
</Button>
|
|
414
|
+
|
|
415
|
+
<Slider
|
|
416
|
+
v-if="props.volumeLayout === 'inline'"
|
|
417
|
+
size="sm"
|
|
418
|
+
class="w-20"
|
|
419
|
+
:model-value="[muted ? 0 : volume]"
|
|
420
|
+
:max="1"
|
|
421
|
+
:step="0.01"
|
|
422
|
+
:aria-label="t('ui.videoPlayer.volume')"
|
|
423
|
+
@update:model-value="changeVolume"
|
|
424
|
+
/>
|
|
425
|
+
|
|
426
|
+
<!--
|
|
427
|
+
Bảng dọc ẩn bằng `opacity-0` + `pointer-events-none`, KHÔNG `invisible`.
|
|
428
|
+
Đã đo: `visibility: hidden` loại phần tử khỏi thứ tự Tab y hệt
|
|
429
|
+
`display: none`, nên núm trượt không nhận được tiêu điểm và
|
|
430
|
+
`group-focus-within` không bao giờ xảy ra — vế bàn phím tự vô hiệu hoá
|
|
431
|
+
chính nó.
|
|
432
|
+
|
|
433
|
+
Khoảng hở 8px nằm ở `pb-2` của LỚP NGOÀI chứ không ở `mb-2`: hộp định
|
|
434
|
+
vị phải chạm vào nút, nếu không con trỏ đi qua khe sẽ rời cả nhóm và
|
|
435
|
+
bảng chớp tắt giữa đường.
|
|
436
|
+
-->
|
|
437
|
+
<div
|
|
438
|
+
v-else
|
|
439
|
+
class="pointer-events-none absolute bottom-full left-1/2 -translate-x-1/2 pb-2 opacity-0 transition-opacity duration-(--duration-base) group-focus-within/volume:pointer-events-auto group-focus-within/volume:opacity-100 group-hover/volume:pointer-events-auto group-hover/volume:opacity-100"
|
|
440
|
+
>
|
|
441
|
+
<div class="flex flex-col items-center gap-2 rounded-lg border border-border bg-popover p-3 shadow-md">
|
|
442
|
+
<Slider
|
|
443
|
+
size="sm"
|
|
444
|
+
orientation="vertical"
|
|
445
|
+
class="data-[orientation=vertical]:h-24"
|
|
446
|
+
:model-value="[muted ? 0 : volume]"
|
|
447
|
+
:max="1"
|
|
448
|
+
:step="0.01"
|
|
449
|
+
:aria-label="t('ui.videoPlayer.volume')"
|
|
450
|
+
@update:model-value="changeVolume"
|
|
451
|
+
/>
|
|
452
|
+
<span class="font-mono text-xs tabular-nums text-muted-foreground">
|
|
453
|
+
{{ Math.round((muted ? 0 : volume) * 100) }}
|
|
454
|
+
</span>
|
|
455
|
+
</div>
|
|
456
|
+
</div>
|
|
457
|
+
</div>
|
|
458
|
+
|
|
459
|
+
<Button
|
|
460
|
+
v-if="has('fullscreen')"
|
|
461
|
+
size="icon-sm"
|
|
462
|
+
variant="ghost"
|
|
463
|
+
class="shrink-0"
|
|
464
|
+
:aria-label="fullscreen ? t('ui.videoPlayer.exitFullscreen') : t('ui.videoPlayer.enterFullscreen')"
|
|
465
|
+
@click="toggleFullscreen"
|
|
466
|
+
>
|
|
467
|
+
<Minimize v-if="fullscreen" />
|
|
468
|
+
<Maximize v-else />
|
|
469
|
+
</Button>
|
|
470
|
+
</div>
|
|
471
|
+
</div>
|
|
472
|
+
</template>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export { clampMediaTime, formatMediaTime, mediaVolumeLevel } from '../../variants/lib/media'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Một mục trên thanh điều khiển.
|
|
5
|
+
*
|
|
6
|
+
* Bật/tắt bằng CÁCH CÓ MẶT trong mảng `controls`, không phải bằng bảy prop
|
|
7
|
+
* boolean riêng: một mảng đọc ra ngay là player này có gì, còn bảy boolean thì
|
|
8
|
+
* phải đọc cả bảy mới biết. Thứ tự trong mảng không quyết định thứ tự hiển thị
|
|
9
|
+
* — bố cục thanh là cố định để tám cách cấu hình vẫn ra một sản phẩm nhìn giống
|
|
10
|
+
* nhau, đó là điểm của việc gom về một component.
|
|
11
|
+
*/
|
|
12
|
+
export type VideoPlayerControl = 'play' | 'skip' | 'seek' | 'time' | 'rate' | 'volume' | 'fullscreen'
|
|
13
|
+
|
|
14
|
+
/** Thanh điều khiển nổi trên khung hình, hay nằm dưới nó. */
|
|
15
|
+
export type VideoPlayerPlacement = 'overlay' | 'below'
|
|
16
|
+
|
|
17
|
+
/** Thanh âm lượng nằm ngay cạnh nút loa, hay ẩn trong một bảng dọc. */
|
|
18
|
+
export type VideoPlayerVolumeLayout = 'inline' | 'popover'
|
|
19
|
+
|
|
20
|
+
export { default as VideoPlayer } from './VideoPlayer.vue'
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export * from './useBase'
|
|
2
|
+
export * from './useChartPalette'
|
|
3
|
+
export * from './useColorMode'
|
|
4
|
+
export * from './useConfirm'
|
|
5
|
+
export * from './useDelayedFlag'
|
|
6
|
+
export * from './useI18n'
|
|
7
|
+
export * from './useIsMobile'
|
|
8
|
+
export * from './useMenuAccent'
|
|
9
|
+
export * from './useMenuAppearance'
|
|
10
|
+
export * from './useMenuColor'
|
|
11
|
+
export * from './usePalette'
|
|
12
|
+
export * from './useStyle'
|
|
13
|
+
export * from './useSurface'
|
|
14
|
+
export * from './useTableQuery'
|
|
15
|
+
export * from './useThemeController'
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { BaseId, BaseOption } from '@vestow/theme'
|
|
2
|
+
import type { ComputedRef } from 'vue'
|
|
3
|
+
import { bases, themeController } from '@vestow/theme'
|
|
4
|
+
import { computed } from 'vue'
|
|
5
|
+
import { useThemeState } from './useThemeController'
|
|
6
|
+
|
|
7
|
+
export type { BaseId, BaseOption }
|
|
8
|
+
// Danh sách họ trung tính là DỮ LIỆU, ở `@vestow/theme`.
|
|
9
|
+
export { bases }
|
|
10
|
+
|
|
11
|
+
export interface UseBaseReturn {
|
|
12
|
+
base: ComputedRef<BaseId>
|
|
13
|
+
bases: readonly BaseOption[]
|
|
14
|
+
setBase: (value: BaseId) => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Họ màu của nhóm trung tính. Vỏ bọc Vue quanh `themeController`, cùng khuôn
|
|
19
|
+
* với `usePalette` / `useStyle` / `useChartPalette`.
|
|
20
|
+
*
|
|
21
|
+
* Trục này chỉ đổi `--tint-hue` và `--tint-scale` — xem đầu `bases.css` để biết
|
|
22
|
+
* vì sao hai token đó là đủ cho cả nhóm trung tính.
|
|
23
|
+
*/
|
|
24
|
+
export function useBase(): UseBaseReturn {
|
|
25
|
+
const state = useThemeState()
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
base: computed(() => state.value.base),
|
|
29
|
+
bases,
|
|
30
|
+
setBase: themeController.setBase,
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ChartId, ChartOption } from '@vestow/theme'
|
|
2
|
+
import type { ComputedRef } from 'vue'
|
|
3
|
+
import { chartPalettes, themeController } from '@vestow/theme'
|
|
4
|
+
import { computed } from 'vue'
|
|
5
|
+
import { useThemeState } from './useThemeController'
|
|
6
|
+
|
|
7
|
+
export type { ChartId, ChartOption }
|
|
8
|
+
// Danh sách bộ màu biểu đồ là DỮ LIỆU, ở `@vestow/theme`. Re-export để chỗ
|
|
9
|
+
// gọi không phải nhớ nó nằm ở package nào.
|
|
10
|
+
export { chartPalettes }
|
|
11
|
+
|
|
12
|
+
export interface UseChartPaletteReturn {
|
|
13
|
+
chart: ComputedRef<ChartId>
|
|
14
|
+
chartPalettes: readonly ChartOption[]
|
|
15
|
+
setChart: (value: ChartId) => void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Bộ màu cho năm chuỗi dữ liệu của biểu đồ. Vỏ bọc Vue quanh `themeController`
|
|
20
|
+
* của `@vestow/theme`, cùng khuôn với `usePalette` / `useStyle`.
|
|
21
|
+
*
|
|
22
|
+
* Giá trị mặc định `theme` nghĩa là ĐI THEO bảng màu — controller gỡ hẳn
|
|
23
|
+
* `data-chart` khi ở giá trị đó, nên `palettes.css` lại là bên quyết định.
|
|
24
|
+
*/
|
|
25
|
+
export function useChartPalette(): UseChartPaletteReturn {
|
|
26
|
+
const state = useThemeState()
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
chart: computed(() => state.value.chart),
|
|
30
|
+
chartPalettes,
|
|
31
|
+
setChart: themeController.setChart,
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ColorMode } from '@vestow/theme'
|
|
2
|
+
import type { ComputedRef } from 'vue'
|
|
3
|
+
import { themeController } from '@vestow/theme'
|
|
4
|
+
import { computed } from 'vue'
|
|
5
|
+
import { useThemeState } from './useThemeController'
|
|
6
|
+
|
|
7
|
+
export type { ColorMode }
|
|
8
|
+
|
|
9
|
+
export interface UseColorModeReturn {
|
|
10
|
+
mode: ComputedRef<ColorMode>
|
|
11
|
+
isDark: ComputedRef<boolean>
|
|
12
|
+
setMode: (value: ColorMode) => void
|
|
13
|
+
toggle: () => void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Chế độ sáng/tối. Vỏ bọc Vue quanh `themeController` của `@vestow/theme` —
|
|
18
|
+
* xem `useThemeController.ts` để biết vì sao logic không nằm ở đây.
|
|
19
|
+
*/
|
|
20
|
+
export function useColorMode(): UseColorModeReturn {
|
|
21
|
+
const state = useThemeState()
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
mode: computed(() => state.value.colorMode),
|
|
25
|
+
isDark: computed(() => state.value.isDark),
|
|
26
|
+
setMode: themeController.setColorMode,
|
|
27
|
+
toggle: themeController.toggleColorMode,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Ref, ShallowRef } from 'vue'
|
|
2
|
+
import type { ButtonVariants } from '../components/button'
|
|
3
|
+
import { ref, shallowRef } from 'vue'
|
|
4
|
+
|
|
5
|
+
export interface ConfirmOptions {
|
|
6
|
+
title: string
|
|
7
|
+
description?: string
|
|
8
|
+
/**
|
|
9
|
+
* Nhãn nút xác nhận — **bắt buộc**, và phải là ĐỘNG TỪ nói rõ việc sắp xảy
|
|
10
|
+
* ra: `'Xoá 12 tệp'`, không phải `'Xác nhận'`.
|
|
11
|
+
*
|
|
12
|
+
* Trước đây trường này có mặc định `'Xác nhận'`. Bỏ đi vì mặc định luôn là
|
|
13
|
+
* thứ được dùng nhiều nhất, mà `'Xác nhận'` không nói người dùng đang đồng ý
|
|
14
|
+
* với cái gì — đúng kiểu nhãn mà hộp thoại phá huỷ không được dùng. Bắt khai
|
|
15
|
+
* tường minh thì người viết buộc phải nghĩ ra câu đó một lần.
|
|
16
|
+
*
|
|
17
|
+
* `cancelLabel` thì vẫn có mặc định: nó là đường AN TOÀN, bấm nhầm không mất
|
|
18
|
+
* gì, nên `'Huỷ'` đủ rõ.
|
|
19
|
+
*/
|
|
20
|
+
confirmLabel: string
|
|
21
|
+
cancelLabel?: string
|
|
22
|
+
/** destructive cho hành động xoá / thu hồi quyền */
|
|
23
|
+
variant?: ButtonVariants['variant']
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface ConfirmState extends ConfirmOptions {
|
|
27
|
+
resolve: (value: boolean) => void
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// State ở module scope: chỉ có 1 ConfirmDialog trong toàn app.
|
|
31
|
+
const open = ref(false)
|
|
32
|
+
const state = shallowRef<ConfirmState | null>(null)
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Hỏi xác nhận bằng Promise thay vì callback:
|
|
36
|
+
*
|
|
37
|
+
* if (await confirm({ title: 'Xoá người dùng?', variant: 'destructive' }))
|
|
38
|
+
* await api.deleteUser(id)
|
|
39
|
+
*
|
|
40
|
+
* Yêu cầu: đặt <ConfirmDialog /> một lần trong layout gốc.
|
|
41
|
+
*/
|
|
42
|
+
export interface UseConfirmReturn {
|
|
43
|
+
confirm: (options: ConfirmOptions) => Promise<boolean>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function useConfirm(): UseConfirmReturn {
|
|
47
|
+
function confirm(options: ConfirmOptions): Promise<boolean> {
|
|
48
|
+
// Nếu đang có 1 confirm mở, từ chối cái cũ để không treo Promise.
|
|
49
|
+
state.value?.resolve(false)
|
|
50
|
+
|
|
51
|
+
return new Promise<boolean>((resolve) => {
|
|
52
|
+
state.value = { ...options, resolve }
|
|
53
|
+
open.value = true
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { confirm }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Chỉ ConfirmDialog dùng — không phải API công khai. */
|
|
61
|
+
export interface ConfirmInternals {
|
|
62
|
+
open: Ref<boolean>
|
|
63
|
+
state: ShallowRef<ConfirmState | null>
|
|
64
|
+
settle: (result: boolean) => void
|
|
65
|
+
onOpenChange: (value: boolean) => void
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function useConfirmInternals(): ConfirmInternals {
|
|
69
|
+
function settle(result: boolean): void {
|
|
70
|
+
state.value?.resolve(result)
|
|
71
|
+
state.value = null
|
|
72
|
+
open.value = false
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function onOpenChange(value: boolean): void {
|
|
76
|
+
if (!value)
|
|
77
|
+
settle(false)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { open, state, settle, onOpenChange }
|
|
81
|
+
}
|