@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,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Khuôn nhập liệu: phân tích chuỗi khuôn, lọc dữ liệu thô, và dựng lại chuỗi
|
|
3
|
+
* đã định dạng.
|
|
4
|
+
*
|
|
5
|
+
* Bốn hàm này KHÔNG dính framework nào — cùng lý do `lib/highlight` và
|
|
6
|
+
* `lib/menu-styles` nằm ở đây. Trước đó chúng ở trong cây Vue, nên bản React
|
|
7
|
+
* hoặc phải chép lại (rồi để hai bên trôi khỏi nhau) hoặc phải phụ thuộc ngược
|
|
8
|
+
* vào `@vestow/vue`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Một ô trong khuôn: hoặc là ký tự cố định, hoặc là chỗ nhận dữ liệu. */
|
|
12
|
+
export interface MaskSlot {
|
|
13
|
+
literal?: string
|
|
14
|
+
accepts?: (char: string) => boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Ba ký hiệu, đúng bộ của PrimeVue. Mọi ký tự khác là ký tự CỐ ĐỊNH.
|
|
19
|
+
*
|
|
20
|
+
* Cố tình không thêm ký hiệu nào: khuôn nào cần nhiều hơn ba loại thì thứ nó
|
|
21
|
+
* thật sự cần là một hàm kiểm tra, không phải một bảng chữ cái dài hơn.
|
|
22
|
+
*/
|
|
23
|
+
const TOKENS: Record<string, (char: string) => boolean> = {
|
|
24
|
+
'9': char => /\d/.test(char),
|
|
25
|
+
'a': char => /[a-z]/i.test(char),
|
|
26
|
+
'*': char => /[a-z0-9]/i.test(char),
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function parseMask(mask: string): MaskSlot[] {
|
|
30
|
+
return [...mask].map(char =>
|
|
31
|
+
char in TOKENS ? { accepts: TOKENS[char] } : { literal: char },
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Lọc chuỗi bất kỳ xuống còn các ký tự DỮ LIỆU, theo đúng thứ tự các ô nhận.
|
|
37
|
+
*
|
|
38
|
+
* Ký tự cố định và ký tự không hợp lệ đều rơi ra ở đây, nên cùng một hàm lo
|
|
39
|
+
* được cả việc gõ từng phím lẫn việc dán cả một chuỗi đã có định dạng: dán
|
|
40
|
+
* `(024) 555-0100` hay gõ `0245550100` đều ra một kết quả.
|
|
41
|
+
*
|
|
42
|
+
* Cái bẫy còn lại: nếu khuôn dùng một ký tự cố định mà chính nó cũng hợp lệ ở
|
|
43
|
+
* ô kế tiếp (khuôn `99a99` với dấu ngăn là một chữ cái) thì nó bị nuốt thành
|
|
44
|
+
* dữ liệu. Hiếm, và nói ra ở đây còn hơn là chống bằng một luật đoán mò.
|
|
45
|
+
*/
|
|
46
|
+
export function toRaw(text: string, slots: MaskSlot[]): string {
|
|
47
|
+
const inputs = slots.filter(slot => slot.accepts !== undefined)
|
|
48
|
+
let raw = ''
|
|
49
|
+
for (const char of text) {
|
|
50
|
+
if (raw.length >= inputs.length)
|
|
51
|
+
break
|
|
52
|
+
const accepts = inputs[raw.length].accepts
|
|
53
|
+
if (accepts !== undefined && accepts(char))
|
|
54
|
+
raw += char
|
|
55
|
+
}
|
|
56
|
+
return raw
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface FormatResult {
|
|
60
|
+
/** Chuỗi đầy đủ, ô chưa điền thì lấy `slotChar`. */
|
|
61
|
+
text: string
|
|
62
|
+
/** Chuỗi cắt tới ô đã điền cuối cùng — không mang ký tự giữ chỗ nào. */
|
|
63
|
+
filled: string
|
|
64
|
+
/** Vị trí con trỏ ngay sau ô dữ liệu thứ `raw.length`. */
|
|
65
|
+
caret: number
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function formatMask(raw: string, slots: MaskSlot[], slotChar: string): FormatResult {
|
|
69
|
+
let text = ''
|
|
70
|
+
let filled = ''
|
|
71
|
+
let caret = 0
|
|
72
|
+
let index = 0
|
|
73
|
+
|
|
74
|
+
for (const slot of slots) {
|
|
75
|
+
if (slot.literal !== undefined) {
|
|
76
|
+
text += slot.literal
|
|
77
|
+
// Ký tự cố định chỉ theo vào phần đã điền khi còn dữ liệu đứng sau nó —
|
|
78
|
+
// nếu không thì gõ xong số vùng sẽ ra `(024) ` với một dấu cách thừa.
|
|
79
|
+
if (index < raw.length)
|
|
80
|
+
filled += slot.literal
|
|
81
|
+
continue
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (index < raw.length) {
|
|
85
|
+
text += raw[index]
|
|
86
|
+
filled += raw[index]
|
|
87
|
+
index += 1
|
|
88
|
+
caret = text.length
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
text += slotChar
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return { text, filled, caret }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Số ô DỮ LIỆU nằm trước vị trí `position` trong chuỗi đã định dạng. */
|
|
99
|
+
export function dataBefore(slots: MaskSlot[], position: number): number {
|
|
100
|
+
let count = 0
|
|
101
|
+
for (let i = 0; i < position && i < slots.length; i += 1) {
|
|
102
|
+
if (slots[i].accepts !== undefined)
|
|
103
|
+
count += 1
|
|
104
|
+
}
|
|
105
|
+
return count
|
|
106
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phần thuần của một trình phát media — không DOM, không framework.
|
|
3
|
+
*
|
|
4
|
+
* Nằm ở đây chứ không ở `vue/packages/ui` vì cùng lý do với `lib/upload`: bản
|
|
5
|
+
* React sẽ cần đúng những phép này, mà chép lại thì hai cây có hai cách định
|
|
6
|
+
* dạng thời gian và hai cách kẹp mốc tua, không có gì phát hiện khi chúng lệch.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* `m:ss`, hoặc `h:mm:ss` khi dài hơn một giờ.
|
|
11
|
+
*
|
|
12
|
+
* Cố ý KHÔNG dùng `Intl`: đây là một KHOẢNG, không phải mốc thời gian trong
|
|
13
|
+
* ngày, nên nó không có bản địa hoá — `1:07` đọc giống nhau ở mọi ngôn ngữ.
|
|
14
|
+
*
|
|
15
|
+
* Chặn giá trị không hữu hạn ở đầu hàm là bắt buộc chứ không phải phòng xa:
|
|
16
|
+
* `video.duration` là `NaN` cho tới khi trình duyệt đọc xong metadata, và
|
|
17
|
+
* `Infinity` với luồng phát trực tiếp.
|
|
18
|
+
*/
|
|
19
|
+
export function formatMediaTime(value: number): string {
|
|
20
|
+
if (!Number.isFinite(value) || value < 0)
|
|
21
|
+
return '0:00'
|
|
22
|
+
|
|
23
|
+
const total = Math.floor(value)
|
|
24
|
+
const seconds = total % 60
|
|
25
|
+
const minutes = Math.floor(total / 60) % 60
|
|
26
|
+
const hours = Math.floor(total / 3600)
|
|
27
|
+
const pad = (n: number): string => n.toString().padStart(2, '0')
|
|
28
|
+
|
|
29
|
+
return hours > 0 ? `${hours}:${pad(minutes)}:${pad(seconds)}` : `${minutes}:${pad(seconds)}`
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Kẹp một mốc tua vào `[0, duration]`.
|
|
34
|
+
*
|
|
35
|
+
* Gán `currentTime` âm thì trình duyệt ném `IndexSizeError`, còn gán quá
|
|
36
|
+
* `duration` thì nó nhảy tới cuối và phát `ended` — hai kiểu hỏng khác nhau cho
|
|
37
|
+
* cùng một cú bấm. `duration` chưa hữu hạn thì chỉ chặn cận dưới, vì lúc đó
|
|
38
|
+
* chưa biết cận trên là bao nhiêu.
|
|
39
|
+
*/
|
|
40
|
+
export function clampMediaTime(value: number, duration: number): number {
|
|
41
|
+
const lower = Math.max(value, 0)
|
|
42
|
+
return Number.isFinite(duration) ? Math.min(lower, duration) : lower
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Ba nấc để chọn icon loa.
|
|
47
|
+
*
|
|
48
|
+
* Phải đọc CẢ `muted` lẫn giá trị âm lượng: kéo thanh về 0 thì `muted` vẫn là
|
|
49
|
+
* `false`, hiện icon loa đầy trong khi không nghe thấy gì là nói dối người dùng.
|
|
50
|
+
*/
|
|
51
|
+
export function mediaVolumeLevel(volume: number, muted: boolean): 'off' | 'low' | 'high' {
|
|
52
|
+
if (muted || volume <= 0)
|
|
53
|
+
return 'off'
|
|
54
|
+
|
|
55
|
+
return volume < 0.5 ? 'low' : 'high'
|
|
56
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Class dùng chung cho MỌI loại menu: DropdownMenu, ContextMenu, và bất cứ menu
|
|
3
|
+
* nào thêm sau.
|
|
4
|
+
*
|
|
5
|
+
* Reka tách chúng thành các primitive riêng vì **cách mở** khác nhau — bấm nút
|
|
6
|
+
* so với bấm chuột phải, cách định vị cũng khác. Nhưng phần *nội dung* thì y
|
|
7
|
+
* hệt: cùng tấm nền, cùng chiều cao hàng, cùng ô đánh dấu 16px ở lề trái.
|
|
8
|
+
*
|
|
9
|
+
* Chép hai bản là đảm bảo có ngày chúng lệch nhau — sửa màu focus ở một bên rồi
|
|
10
|
+
* quên bên kia. Khai một lần ở đây, hai bộ wrapper cùng đọc.
|
|
11
|
+
*
|
|
12
|
+
* Chỉ là chuỗi class, không phải component: mỗi primitive vẫn tự render thẻ của
|
|
13
|
+
* nó với đúng hành vi và ARIA riêng.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* `ui-menu` là MÓC CSS, không phải class tiện ích — cùng khuôn với `ui-button`
|
|
18
|
+
* mà `surfaces.css` bám vào. Hai trục `data-menu-color` và
|
|
19
|
+
* `data-menu-appearance` khai lại token ngay trên phần tử mang móc này, nên
|
|
20
|
+
* phạm vi của chúng đúng bằng tấm menu; xem `menus.css`.
|
|
21
|
+
*
|
|
22
|
+
* `bg-menu-surface` chứ không `bg-popover`: trục vật liệu phải hạ được độ đục
|
|
23
|
+
* của RIÊNG menu, mà `--popover` thì Popover và Tooltip cũng đọc.
|
|
24
|
+
*/
|
|
25
|
+
export const menuContentClass = [
|
|
26
|
+
'ui-menu surface z-(--z-overlay) min-w-[10rem] overflow-hidden rounded-md border border-border bg-menu-surface p-1 text-popover-foreground shadow-md',
|
|
27
|
+
'data-[state=open]:animate-in data-[state=closed]:animate-out',
|
|
28
|
+
'data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0',
|
|
29
|
+
'data-[state=open]:zoom-in-95 data-[state=closed]:zoom-out-95',
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
export const menuSubContentClass = [
|
|
33
|
+
'ui-menu surface z-(--z-overlay) min-w-[8rem] overflow-hidden rounded-md border border-border bg-menu-surface p-1 text-popover-foreground shadow-lg',
|
|
34
|
+
'data-[state=open]:animate-in data-[state=closed]:animate-out',
|
|
35
|
+
'data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0',
|
|
36
|
+
'data-[state=open]:zoom-in-95 data-[state=closed]:zoom-out-95',
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
export const menuItemClass = [
|
|
40
|
+
'relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors',
|
|
41
|
+
'focus:bg-menu-accent focus:text-menu-accent-foreground',
|
|
42
|
+
'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
43
|
+
'data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10',
|
|
44
|
+
'[&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:text-muted-foreground',
|
|
45
|
+
'data-[variant=destructive]:[&_svg]:text-destructive',
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
/** Mục có ô đánh dấu: lề trái 32px chừa chỗ cho dấu tick hoặc chấm tròn. */
|
|
49
|
+
export const menuIndicatorItemClass = [
|
|
50
|
+
'relative flex cursor-default select-none items-center gap-2 rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors',
|
|
51
|
+
'focus:bg-menu-accent focus:text-menu-accent-foreground',
|
|
52
|
+
'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
export const menuIndicatorSlotClass = 'absolute left-2 flex size-4 items-center justify-center'
|
|
56
|
+
|
|
57
|
+
export const menuSubTriggerClass = [
|
|
58
|
+
'flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none',
|
|
59
|
+
'focus:bg-menu-accent focus:text-menu-accent-foreground',
|
|
60
|
+
'data-[state=open]:bg-menu-accent data-[state=open]:text-menu-accent-foreground',
|
|
61
|
+
'[&_svg]:size-4 [&_svg]:shrink-0',
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
export const menuLabelClass = 'px-2 py-1.5 text-xs font-medium text-muted-foreground'
|
|
65
|
+
|
|
66
|
+
export const menuSeparatorClass = '-mx-1 my-1 h-px bg-border'
|
|
67
|
+
|
|
68
|
+
export const menuShortcutClass = 'ml-auto text-xs tracking-widest text-muted-foreground'
|
|
69
|
+
|
|
70
|
+
/** Thụt vào cho ngang hàng với mục có ô đánh dấu. */
|
|
71
|
+
export const menuInsetClass = 'pl-8'
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Máy đang chạy có phải macOS (hoặc iOS) không.
|
|
3
|
+
*
|
|
4
|
+
* Chỉ dùng để CHỌN NHÃN phím tắt: macOS hiện `⌘`, còn lại hiện `Ctrl`. Bản
|
|
5
|
+
* thân trình xử lý phím thì luôn nhận cả hai (`event.metaKey || event.ctrlKey`)
|
|
6
|
+
* — đoán sai nền tảng thì cùng lắm là hiện nhầm nhãn, không bao giờ làm phím
|
|
7
|
+
* tắt chết.
|
|
8
|
+
*
|
|
9
|
+
* `navigator.platform` tuy đã deprecated nhưng vẫn là thứ chạy ở mọi trình
|
|
10
|
+
* duyệt hiện có; `userAgentData` mới hơn lại chưa có trên Safari, tức đúng cái
|
|
11
|
+
* nền tảng cần nhận ra nhất. Có `userAgent` đỡ phía sau.
|
|
12
|
+
*
|
|
13
|
+
* KHÔNG gọi ở tầng module: ngoài trình duyệt thì không có `navigator`.
|
|
14
|
+
*/
|
|
15
|
+
export function isMacPlatform(): boolean {
|
|
16
|
+
if (typeof navigator === 'undefined')
|
|
17
|
+
return false
|
|
18
|
+
|
|
19
|
+
return /mac|iphone|ipad|ipod/i.test(navigator.platform || navigator.userAgent)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Nhãn của phím bổ trợ chính: `⌘` trên macOS, `Ctrl` ở nơi khác.
|
|
24
|
+
*
|
|
25
|
+
* Có hàm riêng thay vì để mỗi chỗ tự viết `isMacPlatform() ? '⌘' : 'Ctrl'`: đó
|
|
26
|
+
* đúng là chuỗi đã bị chép ở `KitHeader`, ở `KitShowcase` và ở 23 file ví dụ,
|
|
27
|
+
* và ký tự `⌘` thì không gõ lại được bằng bàn phím nên mỗi bản chép là một cơ
|
|
28
|
+
* hội dán nhầm.
|
|
29
|
+
*
|
|
30
|
+
* `String.fromCharCode` chứ không viết thẳng ký tự: file nguồn của ví dụ được
|
|
31
|
+
* hiện NGUYÊN VĂN lên trang tài liệu, mà một ký tự ngoài BMP trong đoạn code
|
|
32
|
+
* thì người đọc không gõ lại được.
|
|
33
|
+
*
|
|
34
|
+
* KHÔNG gọi ở tầng module, cùng lý do với `isMacPlatform`.
|
|
35
|
+
*/
|
|
36
|
+
export function metaKeyLabel(): string {
|
|
37
|
+
return isMacPlatform() ? String.fromCharCode(0x2318) : 'Ctrl'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Nhãn đầy đủ của một tổ hợp phím, theo quy ước của TỪNG nền tảng.
|
|
42
|
+
*
|
|
43
|
+
* Hai nền tảng viết khác nhau ở cả ký hiệu lẫn dấu nối, nên đổi mỗi `⌘` thành
|
|
44
|
+
* `Ctrl` là chưa đủ:
|
|
45
|
+
*
|
|
46
|
+
* | | macOS | nơi khác |
|
|
47
|
+
* |---|---|---|
|
|
48
|
+
* | `['Mod', 'Z']` | `⌘Z` | `Ctrl+Z` |
|
|
49
|
+
* | `['Mod', 'Shift', 'Z']` | `⌘⇧Z` | `Ctrl+Shift+Z` |
|
|
50
|
+
* | `['Shift', 'R']` | `⇧R` | `Shift+R` |
|
|
51
|
+
*
|
|
52
|
+
* macOS ghép liền vì mỗi phím bổ trợ là một ký hiệu riêng; Windows và Linux
|
|
53
|
+
* dùng CHỮ nên phải có dấu `+`, không thì ra `CtrlShiftZ` dính một cục.
|
|
54
|
+
*
|
|
55
|
+
* `Mod` là tên trung tính của phím bổ trợ chính — viết `Mod` thay vì `Cmd` hay
|
|
56
|
+
* `Ctrl` để chỗ gọi khỏi phải tự phân nhánh.
|
|
57
|
+
*/
|
|
58
|
+
export function shortcutLabel(keys: readonly string[]): string {
|
|
59
|
+
const mac = isMacPlatform()
|
|
60
|
+
|
|
61
|
+
const symbols: Record<string, string> = mac
|
|
62
|
+
? { Alt: '⌥', Ctrl: '⌃', Mod: '⌘', Shift: '⇧' }
|
|
63
|
+
: { Alt: 'Alt', Ctrl: 'Ctrl', Mod: 'Ctrl', Shift: 'Shift' }
|
|
64
|
+
|
|
65
|
+
const parts = keys.map(key => symbols[key] ?? key)
|
|
66
|
+
|
|
67
|
+
return mac ? parts.join('') : parts.join('+')
|
|
68
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Khuôn dữ liệu cây, và hai phép tra trên nó.
|
|
3
|
+
*
|
|
4
|
+
* KHÔNG dính framework nào — cùng lý do `lib/mask` nằm ở đây. `Tree`,
|
|
5
|
+
* `TreeSelect` và `CascadeSelect` của cả hai cây đều nhận đúng hình dạng này,
|
|
6
|
+
* vì chúng đang nói về cùng một thứ; khai ba kiểu nút y hệt nhau sẽ buộc người
|
|
7
|
+
* dùng chuyển đổi qua lại chỉ để đổi cách trình bày.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Khuôn dữ liệu tối thiểu cho một cây.
|
|
12
|
+
*
|
|
13
|
+
* Đệ quy theo chính nó (`children?: TreeNode[]`) chứ không generic: nút con
|
|
14
|
+
* phải cùng kiểu nút cha thì phép làm phẳng mới đi được. Muốn mang thêm trường
|
|
15
|
+
* thì mở rộng interface này.
|
|
16
|
+
*/
|
|
17
|
+
export interface TreeNode {
|
|
18
|
+
/** Khoá duy nhất trong toàn cây. */
|
|
19
|
+
id: string
|
|
20
|
+
label: string
|
|
21
|
+
children?: TreeNode[]
|
|
22
|
+
disabled?: boolean
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Đường đi từ gốc tới `id`, kể cả chính nó. Rỗng nếu không tìm thấy. */
|
|
26
|
+
export function findPath(items: TreeNode[], id: string): TreeNode[] {
|
|
27
|
+
for (const item of items) {
|
|
28
|
+
if (item.id === id)
|
|
29
|
+
return [item]
|
|
30
|
+
if (item.children !== undefined) {
|
|
31
|
+
const deeper = findPath(item.children, id)
|
|
32
|
+
if (deeper.length > 0)
|
|
33
|
+
return [item, ...deeper]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return []
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Nút có con hay không. Lá thì chọn được, nhánh thì mở ra thêm một tầng. */
|
|
40
|
+
export function isBranch(node: TreeNode): boolean {
|
|
41
|
+
return node.children !== undefined && node.children.length > 0
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Một dòng của cây sau khi làm phẳng — nút, cấp, và trạng thái để vẽ. */
|
|
45
|
+
export interface TreeRow {
|
|
46
|
+
node: TreeNode
|
|
47
|
+
/** Đếm từ 1, đúng thang `aria-level`. */
|
|
48
|
+
level: number
|
|
49
|
+
hasChildren: boolean
|
|
50
|
+
expanded: boolean
|
|
51
|
+
selected: boolean
|
|
52
|
+
/** Vị trí trong nhóm anh em, đếm từ 1 — `aria-posinset`. */
|
|
53
|
+
position: number
|
|
54
|
+
/** Số anh em cùng nhóm — `aria-setsize`. */
|
|
55
|
+
setSize: number
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Làm phẳng những nút ĐANG THẤY thành một danh sách tuyến tính.
|
|
60
|
+
*
|
|
61
|
+
* Điều hướng bàn phím của một cây chạy trên thứ tự nhìn thấy, không chạy trên
|
|
62
|
+
* cấu trúc lồng nhau: `ArrowDown` từ nút cuối của một nhánh đang mở phải sang
|
|
63
|
+
* nút kế của nhánh CHA. Đệ quy qua component lồng nhau thì mỗi bước như vậy là
|
|
64
|
+
* một phép đi ngược cây; làm phẳng trước thì nó chỉ là `index + 1`.
|
|
65
|
+
*
|
|
66
|
+
* Đây cũng là thứ `TreeRoot` của reka trả về qua slot bên bản Vue, nên hai cây
|
|
67
|
+
* cùng một mô hình dù một bên tự dựng.
|
|
68
|
+
*/
|
|
69
|
+
export function flattenTree(
|
|
70
|
+
items: TreeNode[],
|
|
71
|
+
expanded: readonly string[],
|
|
72
|
+
selected: readonly string[],
|
|
73
|
+
): TreeRow[] {
|
|
74
|
+
const rows: TreeRow[] = []
|
|
75
|
+
|
|
76
|
+
function walk(nodes: TreeNode[], level: number): void {
|
|
77
|
+
nodes.forEach((node, index) => {
|
|
78
|
+
const hasChildren = isBranch(node)
|
|
79
|
+
const isExpanded = hasChildren && expanded.includes(node.id)
|
|
80
|
+
|
|
81
|
+
rows.push({
|
|
82
|
+
node,
|
|
83
|
+
level,
|
|
84
|
+
hasChildren,
|
|
85
|
+
expanded: isExpanded,
|
|
86
|
+
selected: selected.includes(node.id),
|
|
87
|
+
position: index + 1,
|
|
88
|
+
setSize: nodes.length,
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
if (isExpanded && node.children !== undefined)
|
|
92
|
+
walk(node.children, level + 1)
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
walk(items, 1)
|
|
97
|
+
return rows
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Mọi id trong nhánh, kể cả chính nó. Dùng cho chế độ chọn lan xuống con. */
|
|
101
|
+
export function branchIds(node: TreeNode): string[] {
|
|
102
|
+
const ids = [node.id]
|
|
103
|
+
for (const child of node.children ?? [])
|
|
104
|
+
ids.push(...branchIds(child))
|
|
105
|
+
return ids
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Mọi nút trong nhánh, kể cả chính nó. Đối ứng của `branchIds`. */
|
|
109
|
+
export function branchNodes(node: TreeNode): TreeNode[] {
|
|
110
|
+
const nodes = [node]
|
|
111
|
+
for (const child of node.children ?? [])
|
|
112
|
+
nodes.push(...branchNodes(child))
|
|
113
|
+
return nodes
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Tra một nút theo id trong cả cây. */
|
|
117
|
+
export function findNode(items: TreeNode[], id: string): TreeNode | undefined {
|
|
118
|
+
return findPath(items, id).at(-1)
|
|
119
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Một tệp trong danh sách tải lên, kèm trạng thái.
|
|
3
|
+
*
|
|
4
|
+
* `progress` và `error` do **bên gọi** cập nhật — component không tự tải lên.
|
|
5
|
+
* Nó chỉ là phần giao diện: chọn tệp, kéo thả, hiện danh sách, gỡ ra. Việc gửi
|
|
6
|
+
* đi phụ thuộc hoàn toàn vào backend (một lần / nhiều phần / ký trước URL), nên
|
|
7
|
+
* gắn cứng một cách vào đây là bên nào cũng phải chống lại nó.
|
|
8
|
+
*/
|
|
9
|
+
export interface UploadFile {
|
|
10
|
+
id: string
|
|
11
|
+
file: File
|
|
12
|
+
/** 0–100. `undefined` nghĩa là chưa bắt đầu. */
|
|
13
|
+
progress?: number
|
|
14
|
+
error?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Đổi số byte sang chuỗi đọc được. Dùng nhị phân (KiB) như hệ điều hành. */
|
|
18
|
+
export function formatFileSize(bytes: number): string {
|
|
19
|
+
if (bytes < 1024)
|
|
20
|
+
return `${bytes} B`
|
|
21
|
+
|
|
22
|
+
const units = ['KB', 'MB', 'GB']
|
|
23
|
+
let value = bytes / 1024
|
|
24
|
+
let unit = 0
|
|
25
|
+
|
|
26
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
27
|
+
value /= 1024
|
|
28
|
+
unit++
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Một chữ số thập phân là đủ; "1.4 MB" đọc nhanh hơn "1.44 MB".
|
|
32
|
+
return `${value.toFixed(value < 10 ? 1 : 0)} ${units[unit]}`
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Khoá nhận dạng một tệp. Tên + cỡ + thời điểm sửa là đủ để coi hai lần kéo
|
|
37
|
+
* cùng một tệp vào là thao tác nhầm, chứ không phải ý định tải hai bản.
|
|
38
|
+
*/
|
|
39
|
+
export function uploadFileId(file: File): string {
|
|
40
|
+
return `${file.name}-${file.size}-${file.lastModified}`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Tách danh sách tệp vừa nhận thành phần nhận được và phần quá lớn.
|
|
45
|
+
*
|
|
46
|
+
* Tách ra khỏi component vì cả hai cây chạy đúng một luật, và vì luật đó có
|
|
47
|
+
* một ca dễ viết sai: `maxSize` bằng 0 nghĩa là KHÔNG giới hạn, không phải
|
|
48
|
+
* "không nhận tệp nào".
|
|
49
|
+
*/
|
|
50
|
+
export function splitBySize(files: File[], maxSize: number): { accepted: File[], tooBig: File[] } {
|
|
51
|
+
if (maxSize <= 0)
|
|
52
|
+
return { accepted: files, tooBig: [] }
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
accepted: files.filter(file => file.size <= maxSize),
|
|
56
|
+
tooBig: files.filter(file => file.size > maxSize),
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ClassValue } from 'clsx'
|
|
2
|
+
import { clsx } from 'clsx'
|
|
3
|
+
import { twMerge } from 'tailwind-merge'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Gộp class Tailwind, class sau thắng class trước khi cùng nhóm utility.
|
|
7
|
+
* Bắt buộc dùng cho mọi component nhận prop `class` từ bên ngoài.
|
|
8
|
+
*/
|
|
9
|
+
export function cn(...inputs: ClassValue[]): string {
|
|
10
|
+
return twMerge(clsx(inputs))
|
|
11
|
+
}
|