@brumaombra/ui-vintage 0.9.0 → 0.9.2
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/AGENTS.md +1 -1
- package/COMPONENTS.md +1 -1
- package/README.md +3 -3
- package/module.mjs +43 -43
- package/package.json +175 -175
- package/src/components/background-grid/BackgroundGrid.vue +4 -4
- package/src/components/blog/AllPostsSection.vue +33 -33
- package/src/components/blog/BlogContentRenderer.vue +111 -111
- package/src/components/blog/BlogFAQSection.vue +20 -20
- package/src/components/blog/BlogHeaderSection.vue +81 -81
- package/src/components/blog/BlogInfoSection.vue +50 -48
- package/src/components/blog/BlogPostHeader.vue +135 -135
- package/src/components/blog/BlogProgressBar.vue +33 -33
- package/src/components/blog/BlogSectionTitle.vue +22 -22
- package/src/components/blog/BlogTagsSection.vue +25 -25
- package/src/components/blog/CategoriesList.vue +14 -14
- package/src/components/blog/CategoriesSection.vue +11 -11
- package/src/components/blog/CategoryCard.vue +27 -26
- package/src/components/blog/HeaderCarousel.vue +99 -98
- package/src/components/blog/PostCard.vue +34 -34
- package/src/components/blog/PostsList.vue +15 -15
- package/src/components/blog/SocialShareSidebar.vue +92 -85
- package/src/components/blog/TableOfContents.vue +310 -308
- package/src/components/busy-indicator/BusyIndicator.vue +7 -7
- package/src/components/busy-indicator/busy-indicator-state.ts +56 -56
- package/src/components/card-grid/CardGrid.vue +35 -35
- package/src/components/chip/Chip.vue +16 -16
- package/src/components/confirm-dialog/ConfirmDialog.vue +51 -51
- package/src/components/confirm-dialog/confirm-dialog-state.ts +131 -131
- package/src/components/content/BlogList.vue +32 -31
- package/src/components/content/BlogTable.vue +36 -36
- package/src/components/content/CodeBlock.vue +139 -137
- package/src/components/content/Flow.vue +60 -60
- package/src/components/content/ProseHr.vue +16 -16
- package/src/components/content/ProsePre.vue +12 -12
- package/src/components/content/Terminal.vue +62 -60
- package/src/components/cookie-consent/CookieConsent.vue +36 -36
- package/src/components/cookie-consent/useCookieConsent.ts +37 -37
- package/src/components/dashboard-shell/DashboardShell.vue +171 -171
- package/src/components/data-list/DataList.vue +6 -6
- package/src/components/data-list/DataListItem.vue +4 -4
- package/src/components/data-list/DataListLabel.vue +4 -4
- package/src/components/data-list/DataListText.vue +4 -4
- package/src/components/data-list/DataListValue.vue +4 -4
- package/src/components/empty-state-card/EmptyStateCard.vue +41 -41
- package/src/components/error-page/ErrorPage.vue +73 -73
- package/src/components/info-card/InfoCard.vue +31 -31
- package/src/components/landing/landing-content/LandingContent.vue +4 -4
- package/src/components/landing/landing-footer/LandingFooter.vue +129 -129
- package/src/components/landing/landing-navbar/LandingNavbar.vue +50 -50
- package/src/components/landing/landing-shell/LandingShell.vue +36 -36
- package/src/components/language-flag/LanguageFlag.vue +25 -25
- package/src/components/language-flag/flags/ChineseFlag.vue +8 -8
- package/src/components/language-flag/flags/EnglishFlag.vue +7 -7
- package/src/components/language-flag/flags/FrenchFlag.vue +5 -5
- package/src/components/language-flag/flags/GermanFlag.vue +5 -5
- package/src/components/language-flag/flags/ItalianFlag.vue +7 -7
- package/src/components/language-flag/flags/JapaneseFlag.vue +4 -4
- package/src/components/language-flag/flags/PortugueseFlag.vue +57 -57
- package/src/components/language-flag/flags/RussianFlag.vue +5 -5
- package/src/components/language-flag/flags/SpanishFlag.vue +4 -4
- package/src/components/language-selector/LanguageSelector.vue +77 -77
- package/src/components/load-more-button/LoadMoreButton.vue +19 -18
- package/src/components/loading-state-card/LoadingStateCard.vue +29 -29
- package/src/components/message-dialog/MessageDialog.vue +62 -60
- package/src/components/message-dialog/message-dialog-state.ts +105 -105
- package/src/components/message-toast/MessageToast.vue +153 -150
- package/src/components/message-toast/message-toast-state.ts +146 -146
- package/src/components/page-header/PageHeader.vue +34 -34
- package/src/components/progress-component/ProgressComponent.vue +33 -33
- package/src/components/single-value-card/SingleValueCard.vue +59 -58
- package/src/components/slider-form-component/SliderFormComponent.vue +36 -36
- package/src/components/stat-strip/StatStrip.vue +22 -22
- package/src/components/switch-form-component/SwitchFormComponent.vue +20 -20
- package/src/components/text-link/TextLink.vue +14 -14
- package/src/components/theme-selector/ThemeSelector.vue +65 -62
- package/src/components/theme-selector/theme-transition.ts +63 -63
- package/src/components/ui/accordion/Accordion.vue +39 -39
- package/src/components/ui/alert/Alert.vue +5 -5
- package/src/components/ui/alert/AlertDescription.vue +4 -4
- package/src/components/ui/alert/AlertTitle.vue +4 -4
- package/src/components/ui/alert/index.ts +2 -2
- package/src/components/ui/alert-dialog/AlertDialog.vue +3 -3
- package/src/components/ui/alert-dialog/AlertDialogAction.vue +3 -3
- package/src/components/ui/alert-dialog/AlertDialogCancel.vue +3 -3
- package/src/components/ui/alert-dialog/AlertDialogContent.vue +7 -7
- package/src/components/ui/alert-dialog/AlertDialogDescription.vue +3 -3
- package/src/components/ui/alert-dialog/AlertDialogFooter.vue +4 -4
- package/src/components/ui/alert-dialog/AlertDialogHeader.vue +4 -4
- package/src/components/ui/alert-dialog/AlertDialogTitle.vue +3 -3
- package/src/components/ui/alert-dialog/AlertDialogTrigger.vue +3 -3
- package/src/components/ui/animated-number/AnimatedNumber.vue +49 -48
- package/src/components/ui/avatar/Avatar.vue +28 -28
- package/src/components/ui/avatar/AvatarFallback.vue +6 -6
- package/src/components/ui/avatar/AvatarGroup.vue +40 -40
- package/src/components/ui/avatar/AvatarImage.vue +3 -3
- package/src/components/ui/avatar/index.ts +17 -17
- package/src/components/ui/badge/Badge.vue +20 -20
- package/src/components/ui/breadcrumb/Breadcrumb.vue +4 -4
- package/src/components/ui/breadcrumb/BreadcrumbEllipsis.vue +10 -10
- package/src/components/ui/breadcrumb/BreadcrumbItem.vue +4 -4
- package/src/components/ui/breadcrumb/BreadcrumbLink.vue +4 -4
- package/src/components/ui/breadcrumb/BreadcrumbList.vue +4 -4
- package/src/components/ui/breadcrumb/BreadcrumbPage.vue +4 -4
- package/src/components/ui/breadcrumb/BreadcrumbSeparator.vue +4 -4
- package/src/components/ui/busy/Busy.vue +33 -33
- package/src/components/ui/button/Button.vue +23 -23
- package/src/components/ui/button/index.ts +56 -56
- package/src/components/ui/calendar/Calendar.vue +138 -138
- package/src/components/ui/calendar/CalendarCell.vue +7 -7
- package/src/components/ui/calendar/CalendarCellTrigger.vue +18 -18
- package/src/components/ui/calendar/CalendarGrid.vue +3 -3
- package/src/components/ui/calendar/CalendarGridBody.vue +3 -3
- package/src/components/ui/calendar/CalendarGridHead.vue +3 -3
- package/src/components/ui/calendar/CalendarGridRow.vue +3 -3
- package/src/components/ui/calendar/CalendarHeadCell.vue +7 -7
- package/src/components/ui/calendar/CalendarHeader.vue +7 -7
- package/src/components/ui/calendar/CalendarHeading.vue +6 -6
- package/src/components/ui/calendar/CalendarNextButton.vue +16 -16
- package/src/components/ui/calendar/CalendarPrevButton.vue +16 -16
- package/src/components/ui/calendar/index.ts +4 -4
- package/src/components/ui/card/Card.vue +20 -20
- package/src/components/ui/card/CardAction.vue +8 -8
- package/src/components/ui/card/CardContent.vue +4 -4
- package/src/components/ui/card/CardDescription.vue +12 -12
- package/src/components/ui/card/CardFooter.vue +4 -4
- package/src/components/ui/card/CardHeader.vue +8 -8
- package/src/components/ui/card/CardText.vue +8 -8
- package/src/components/ui/card/CardTitle.vue +8 -8
- package/src/components/ui/checkbox/Checkbox.vue +27 -27
- package/src/components/ui/collapsible/Collapsible.vue +3 -3
- package/src/components/ui/collapsible/CollapsibleContent.vue +3 -3
- package/src/components/ui/collapsible/CollapsibleTrigger.vue +3 -3
- package/src/components/ui/combobox/Combobox.vue +4 -4
- package/src/components/ui/combobox/ComboboxAnchor.vue +16 -16
- package/src/components/ui/combobox/ComboboxEmpty.vue +3 -3
- package/src/components/ui/combobox/ComboboxGroup.vue +9 -9
- package/src/components/ui/combobox/ComboboxInput.vue +6 -6
- package/src/components/ui/combobox/ComboboxItem.vue +18 -18
- package/src/components/ui/combobox/ComboboxList.vue +19 -19
- package/src/components/ui/combobox/ComboboxSelect.vue +79 -78
- package/src/components/ui/combobox/ComboboxSeparator.vue +1 -1
- package/src/components/ui/combobox/ComboboxTrigger.vue +11 -11
- package/src/components/ui/combobox/types.ts +5 -5
- package/src/components/ui/command/Command.vue +48 -48
- package/src/components/ui/command/CommandDialog.vue +14 -14
- package/src/components/ui/command/CommandEmpty.vue +4 -4
- package/src/components/ui/command/CommandGroup.vue +23 -23
- package/src/components/ui/command/CommandInput.vue +10 -10
- package/src/components/ui/command/CommandItem.vue +40 -40
- package/src/components/ui/command/CommandList.vue +9 -9
- package/src/components/ui/command/CommandSeparator.vue +3 -3
- package/src/components/ui/command/CommandShortcut.vue +8 -8
- package/src/components/ui/command/index.ts +11 -11
- package/src/components/ui/data-table/DataTable.vue +147 -146
- package/src/components/ui/data-table/index.ts +11 -11
- package/src/components/ui/date-picker/DatePicker.vue +32 -32
- package/src/components/ui/date-time-picker/DateTimePicker.vue +97 -97
- package/src/components/ui/dialog/Dialog.vue +3 -3
- package/src/components/ui/dialog/DialogClose.vue +3 -3
- package/src/components/ui/dialog/DialogContent.vue +22 -22
- package/src/components/ui/dialog/DialogDescription.vue +7 -7
- package/src/components/ui/dialog/DialogFooter.vue +8 -8
- package/src/components/ui/dialog/DialogHeader.vue +4 -4
- package/src/components/ui/dialog/DialogOverlay.vue +7 -7
- package/src/components/ui/dialog/DialogScrollContent.vue +33 -33
- package/src/components/ui/dialog/DialogTitle.vue +3 -3
- package/src/components/ui/dialog/DialogTrigger.vue +3 -3
- package/src/components/ui/dropdown-menu/DropdownMenu.vue +3 -3
- package/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +12 -12
- package/src/components/ui/dropdown-menu/DropdownMenuContent.vue +15 -15
- package/src/components/ui/dropdown-menu/DropdownMenuGroup.vue +3 -3
- package/src/components/ui/dropdown-menu/DropdownMenuItem.vue +12 -12
- package/src/components/ui/dropdown-menu/DropdownMenuLabel.vue +9 -9
- package/src/components/ui/dropdown-menu/DropdownMenuPortal.vue +3 -3
- package/src/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +3 -3
- package/src/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +11 -11
- package/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue +1 -1
- package/src/components/ui/dropdown-menu/DropdownMenuShortcut.vue +4 -4
- package/src/components/ui/dropdown-menu/DropdownMenuSub.vue +3 -3
- package/src/components/ui/dropdown-menu/DropdownMenuSubContent.vue +15 -15
- package/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +16 -16
- package/src/components/ui/dropdown-menu/DropdownMenuTrigger.vue +3 -3
- package/src/components/ui/dropdown-menu/classes.ts +16 -16
- package/src/components/ui/field/Field.vue +5 -5
- package/src/components/ui/field/FieldContent.vue +8 -8
- package/src/components/ui/field/FieldDescription.vue +10 -10
- package/src/components/ui/field/FieldError.vue +39 -39
- package/src/components/ui/field/FieldGroup.vue +8 -8
- package/src/components/ui/field/FieldLabel.vue +10 -10
- package/src/components/ui/field/FieldLegend.vue +11 -11
- package/src/components/ui/field/FieldSeparator.vue +11 -11
- package/src/components/ui/field/FieldSet.vue +9 -9
- package/src/components/ui/field/FieldTitle.vue +8 -8
- package/src/components/ui/field/field-context.ts +36 -36
- package/src/components/ui/field/index.ts +22 -22
- package/src/components/ui/file-dropzone/FileDropzone.vue +163 -154
- package/src/components/ui/file-dropzone/index.ts +19 -19
- package/src/components/ui/hover-card/HoverCard.vue +5 -5
- package/src/components/ui/hover-card/HoverCardContent.vue +13 -13
- package/src/components/ui/hover-card/HoverCardTrigger.vue +3 -3
- package/src/components/ui/input/Input.vue +13 -13
- package/src/components/ui/kbd/Kbd.vue +10 -10
- package/src/components/ui/kbd/KbdGroup.vue +49 -49
- package/src/components/ui/label/Label.vue +7 -7
- package/src/components/ui/native-select/NativeSelect.vue +17 -17
- package/src/components/ui/native-select/NativeSelectOptGroup.vue +3 -3
- package/src/components/ui/native-select/NativeSelectOption.vue +3 -3
- package/src/components/ui/number-field/NumberField.vue +90 -89
- package/src/components/ui/pagination/Pagination.vue +4 -4
- package/src/components/ui/pagination/PaginationContent.vue +4 -4
- package/src/components/ui/pagination/PaginationEllipsis.vue +10 -10
- package/src/components/ui/pagination/PaginationFirst.vue +9 -9
- package/src/components/ui/pagination/PaginationItem.vue +14 -14
- package/src/components/ui/pagination/PaginationLast.vue +9 -9
- package/src/components/ui/pagination/PaginationNext.vue +9 -9
- package/src/components/ui/pagination/PaginationPrevious.vue +9 -9
- package/src/components/ui/pagination/SimplePagination.vue +25 -25
- package/src/components/ui/pin-input/PinInput.vue +20 -20
- package/src/components/ui/pin-input/PinInputInput.vue +17 -17
- package/src/components/ui/pin-input/context.ts +1 -1
- package/src/components/ui/popover/Popover.vue +3 -3
- package/src/components/ui/popover/PopoverAnchor.vue +3 -3
- package/src/components/ui/popover/PopoverContent.vue +12 -12
- package/src/components/ui/popover/PopoverTrigger.vue +3 -3
- package/src/components/ui/progress/Progress.vue +16 -16
- package/src/components/ui/radio-group/RadioGroup.vue +7 -7
- package/src/components/ui/radio-group/RadioGroupCard.vue +39 -39
- package/src/components/ui/radio-group/RadioGroupItem.vue +20 -20
- package/src/components/ui/scroll-area/ScrollArea.vue +14 -14
- package/src/components/ui/scroll-area/ScrollBar.vue +12 -12
- package/src/components/ui/select/Select.vue +3 -3
- package/src/components/ui/select/SelectContent.vue +25 -25
- package/src/components/ui/select/SelectGroup.vue +3 -3
- package/src/components/ui/select/SelectItem.vue +25 -25
- package/src/components/ui/select/SelectItemContent.vue +32 -32
- package/src/components/ui/select/SelectItemText.vue +3 -3
- package/src/components/ui/select/SelectLabel.vue +3 -3
- package/src/components/ui/select/SelectScrollDownButton.vue +10 -10
- package/src/components/ui/select/SelectScrollUpButton.vue +10 -10
- package/src/components/ui/select/SelectSeparator.vue +2 -2
- package/src/components/ui/select/SelectTrigger.vue +15 -15
- package/src/components/ui/select/SelectValue.vue +3 -3
- package/src/components/ui/select/SelectValueContent.vue +39 -39
- package/src/components/ui/separator/Separator.vue +7 -7
- package/src/components/ui/sheet/Sheet.vue +3 -3
- package/src/components/ui/sheet/SheetClose.vue +3 -3
- package/src/components/ui/sheet/SheetContent.vue +22 -22
- package/src/components/ui/sheet/SheetDescription.vue +3 -3
- package/src/components/ui/sheet/SheetFooter.vue +3 -3
- package/src/components/ui/sheet/SheetHeader.vue +3 -3
- package/src/components/ui/sheet/SheetOverlay.vue +7 -7
- package/src/components/ui/sheet/SheetTitle.vue +3 -3
- package/src/components/ui/sheet/SheetTrigger.vue +3 -3
- package/src/components/ui/sidebar/Sidebar.vue +57 -57
- package/src/components/ui/sidebar/SidebarContent.vue +8 -8
- package/src/components/ui/sidebar/SidebarFooter.vue +8 -8
- package/src/components/ui/sidebar/SidebarGroup.vue +8 -8
- package/src/components/ui/sidebar/SidebarGroupAction.vue +9 -9
- package/src/components/ui/sidebar/SidebarGroupContent.vue +4 -4
- package/src/components/ui/sidebar/SidebarGroupLabel.vue +8 -8
- package/src/components/ui/sidebar/SidebarHeader.vue +8 -8
- package/src/components/ui/sidebar/SidebarInput.vue +8 -8
- package/src/components/ui/sidebar/SidebarInset.vue +9 -9
- package/src/components/ui/sidebar/SidebarMenu.vue +4 -4
- package/src/components/ui/sidebar/SidebarMenuAction.vue +18 -18
- package/src/components/ui/sidebar/SidebarMenuBadge.vue +14 -14
- package/src/components/ui/sidebar/SidebarMenuButton.vue +21 -21
- package/src/components/ui/sidebar/SidebarMenuButtonChild.vue +10 -10
- package/src/components/ui/sidebar/SidebarMenuItem.vue +4 -4
- package/src/components/ui/sidebar/SidebarMenuSkeleton.vue +10 -10
- package/src/components/ui/sidebar/SidebarMenuSub.vue +9 -9
- package/src/components/ui/sidebar/SidebarMenuSubButton.vue +17 -17
- package/src/components/ui/sidebar/SidebarMenuSubItem.vue +4 -4
- package/src/components/ui/sidebar/SidebarProvider.vue +46 -46
- package/src/components/ui/sidebar/SidebarRail.vue +13 -13
- package/src/components/ui/sidebar/SidebarSeparator.vue +4 -4
- package/src/components/ui/sidebar/SidebarTrigger.vue +6 -6
- package/src/components/ui/sidebar/index.ts +29 -29
- package/src/components/ui/sidebar/utils.ts +7 -7
- package/src/components/ui/skeleton/Skeleton.vue +6 -6
- package/src/components/ui/slider/Slider.vue +14 -14
- package/src/components/ui/spinner/Spinner.vue +21 -21
- package/src/components/ui/stepper/SimpleStepper.vue +27 -27
- package/src/components/ui/stepper/Stepper.vue +6 -6
- package/src/components/ui/stepper/StepperDescription.vue +5 -5
- package/src/components/ui/stepper/StepperIndicator.vue +18 -18
- package/src/components/ui/stepper/StepperItem.vue +4 -4
- package/src/components/ui/stepper/StepperSeparator.vue +7 -7
- package/src/components/ui/stepper/StepperTitle.vue +5 -5
- package/src/components/ui/stepper/StepperTrigger.vue +4 -4
- package/src/components/ui/stepper/index.ts +3 -3
- package/src/components/ui/switch/Switch.vue +15 -15
- package/src/components/ui/table/Table.vue +6 -6
- package/src/components/ui/table/TableBody.vue +4 -4
- package/src/components/ui/table/TableCaption.vue +4 -4
- package/src/components/ui/table/TableCell.vue +8 -8
- package/src/components/ui/table/TableEmpty.vue +14 -14
- package/src/components/ui/table/TableFooter.vue +8 -8
- package/src/components/ui/table/TableHead.vue +8 -8
- package/src/components/ui/table/TableHeader.vue +4 -4
- package/src/components/ui/table/TableRow.vue +8 -8
- package/src/components/ui/tabs/Tabs.vue +4 -4
- package/src/components/ui/tabs/TabsContent.vue +4 -4
- package/src/components/ui/tabs/TabsList.vue +15 -15
- package/src/components/ui/tabs/TabsTrigger.vue +10 -10
- package/src/components/ui/tags-input/TagsInput.vue +17 -17
- package/src/components/ui/tags-input/TagsInputInput.vue +6 -6
- package/src/components/ui/tags-input/TagsInputItem.vue +15 -15
- package/src/components/ui/tags-input/TagsInputItemDelete.vue +13 -13
- package/src/components/ui/tags-input/TagsInputItemText.vue +4 -4
- package/src/components/ui/textarea/Textarea.vue +12 -12
- package/src/components/ui/time-picker/TimePicker.vue +25 -25
- package/src/components/ui/toggle-group/Toggle.vue +10 -10
- package/src/components/ui/toggle-group/ToggleGroup.vue +38 -38
- package/src/components/ui/toggle-group/ToggleGroupItem.vue +9 -9
- package/src/components/ui/toggle-group/index.ts +46 -46
- package/src/components/ui/tooltip/Tooltip.vue +3 -3
- package/src/components/ui/tooltip/TooltipContent.vue +13 -13
- package/src/components/ui/tooltip/TooltipProvider.vue +4 -4
- package/src/components/ui/tooltip/TooltipTrigger.vue +3 -3
- package/src/i18n/de.json +185 -185
- package/src/i18n/en.json +185 -185
- package/src/i18n/es.json +185 -185
- package/src/i18n/fr.json +185 -185
- package/src/i18n/it.json +185 -185
- package/src/i18n/ja.json +185 -185
- package/src/i18n/pt.json +185 -185
- package/src/i18n/ru.json +185 -185
- package/src/i18n/zh.json +185 -185
- package/src/lib/color-tokens.ts +24 -24
- package/src/lib/i18n.ts +65 -65
- package/src/lib/utils.ts +33 -33
- package/src/nuxt-components.d.ts +2 -2
- package/src/runtime/ui-vintage-i18n-plugin.ts +49 -49
- package/src/shims-vue.d.ts +3 -3
- package/src/styles.css +377 -377
|
@@ -4,17 +4,17 @@ import { cn } from '../../../lib/utils';
|
|
|
4
4
|
|
|
5
5
|
// Props
|
|
6
6
|
const props = defineProps<{
|
|
7
|
-
|
|
7
|
+
class?: HTMLAttributes['class'];
|
|
8
8
|
}>();
|
|
9
9
|
</script>
|
|
10
10
|
|
|
11
11
|
<template>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
<fieldset data-slot="field-set" :class="cn(
|
|
13
|
+
'flex flex-col gap-6',
|
|
14
|
+
'has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3',
|
|
15
|
+
props.class
|
|
16
|
+
)
|
|
17
|
+
">
|
|
18
|
+
<slot />
|
|
19
|
+
</fieldset>
|
|
20
20
|
</template>
|
|
@@ -4,16 +4,16 @@ import { cn } from '../../../lib/utils';
|
|
|
4
4
|
|
|
5
5
|
// Props
|
|
6
6
|
const props = defineProps<{
|
|
7
|
-
|
|
7
|
+
class?: HTMLAttributes['class'];
|
|
8
8
|
}>();
|
|
9
9
|
</script>
|
|
10
10
|
|
|
11
11
|
<template>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
<div data-slot="field-label" :class="cn(
|
|
13
|
+
'flex w-fit items-center gap-2 text-sm leading-snug font-semibold group-data-[disabled=true]/field:opacity-50',
|
|
14
|
+
props.class
|
|
15
|
+
)
|
|
16
|
+
">
|
|
17
|
+
<slot />
|
|
18
|
+
</div>
|
|
19
19
|
</template>
|
|
@@ -2,61 +2,61 @@ import type { InjectionKey, Ref } from 'vue';
|
|
|
2
2
|
import { computed, inject, onBeforeUnmount, provide, ref, useAttrs, useId } from 'vue';
|
|
3
3
|
|
|
4
4
|
interface FieldContext {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
// Id of the control the field label points to (undefined until a control registers)
|
|
6
|
+
controlId: Ref<string | undefined>;
|
|
7
|
+
// Id of the field label, for controls that combine it with their own text
|
|
8
|
+
labelId: Ref<string | undefined>;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
const fieldContextKey: InjectionKey<FieldContext> = Symbol('uv-field');
|
|
12
12
|
|
|
13
13
|
// Share the label target with the label and the control inside a Field
|
|
14
14
|
export const provideFieldContext = () => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
const context: FieldContext = { controlId: ref(undefined), labelId: ref(undefined) };
|
|
16
|
+
provide(fieldContextKey, context);
|
|
17
|
+
return context;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
// Id the FieldLabel should point to, if the field has a control, and the id the label registers for itself
|
|
21
21
|
export const useFieldLabel = () => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
22
|
+
const field = inject(fieldContextKey, null);
|
|
23
|
+
const labelId = `uv-field-label-${useId()}`;
|
|
24
|
+
if (field && !field.labelId.value) {
|
|
25
|
+
field.labelId.value = labelId;
|
|
26
|
+
onBeforeUnmount(() => {
|
|
27
|
+
if (field.labelId.value === labelId) field.labelId.value = undefined;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
labelId: field ? labelId : undefined,
|
|
32
|
+
target: computed(() => field?.controlId.value)
|
|
33
|
+
};
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
// Id of the surrounding Field label, if any
|
|
37
37
|
export const useFieldLabelId = () => {
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
const field = inject(fieldContextKey, null);
|
|
39
|
+
return computed(() => field?.labelId.value);
|
|
40
40
|
};
|
|
41
41
|
|
|
42
42
|
// Give the first control inside a Field an id its FieldLabel points to (an explicit id on the control wins)
|
|
43
43
|
export const useFieldControlId = (explicitId?: () => string | undefined) => {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
const field = inject(fieldContextKey, null);
|
|
45
|
+
const attrs = useAttrs();
|
|
46
|
+
const generatedId = `uv-field-control-${useId()}`;
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
// Only the first control claims the label, so composite controls (like DateTimePicker) don't fight over it
|
|
49
|
+
if (!field || field.controlId.value) {
|
|
50
|
+
return computed(() => explicitId?.());
|
|
51
|
+
}
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
const id = (explicitId?.() ?? (attrs.id as string | undefined)) || generatedId;
|
|
54
|
+
field.controlId.value = id;
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
// Release the label when the control goes away
|
|
57
|
+
onBeforeUnmount(() => {
|
|
58
|
+
if (field.controlId.value === id) field.controlId.value = undefined;
|
|
59
|
+
});
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
};
|
|
61
|
+
return computed(() => id);
|
|
62
|
+
};
|
|
@@ -2,31 +2,31 @@ import type { ClassProp } from 'class-variance-authority/types';
|
|
|
2
2
|
import { cva } from 'class-variance-authority';
|
|
3
3
|
|
|
4
4
|
export type FieldVariants = {
|
|
5
|
-
|
|
5
|
+
orientation?: 'vertical' | 'horizontal' | 'responsive' | null | undefined;
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
export const fieldVariants: (props?: FieldVariants & ClassProp) => string = cva(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
9
|
+
'group/field flex w-full gap-3 data-[invalid=true]:text-destructive',
|
|
10
|
+
{
|
|
11
|
+
variants: {
|
|
12
|
+
orientation: {
|
|
13
|
+
vertical: ['flex-col [&>*]:w-full [&>.sr-only]:w-auto'],
|
|
14
|
+
horizontal: [
|
|
15
|
+
'flex-row items-center',
|
|
16
|
+
'[&>[data-slot=field-label]]:flex-auto',
|
|
17
|
+
'has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px'
|
|
18
|
+
],
|
|
19
|
+
responsive: [
|
|
20
|
+
'flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto',
|
|
21
|
+
'@md/field-group:[&>[data-slot=field-label]]:flex-auto',
|
|
22
|
+
'@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px'
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
defaultVariants: {
|
|
27
|
+
orientation: 'vertical'
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
30
|
);
|
|
31
31
|
|
|
32
32
|
export { useFieldControlId } from './field-context';
|
|
@@ -2,7 +2,16 @@
|
|
|
2
2
|
import type { HTMLAttributes } from 'vue';
|
|
3
3
|
import type { HugeiconsIconDefinition } from '../../../lib/common-types';
|
|
4
4
|
import type { FileRejection } from '.';
|
|
5
|
-
import
|
|
5
|
+
import Cancel01Icon from '@hugeicons/core-free-icons/Cancel01Icon';
|
|
6
|
+
import CloudUploadIcon from '@hugeicons/core-free-icons/CloudUploadIcon';
|
|
7
|
+
import Doc01Icon from '@hugeicons/core-free-icons/Doc01Icon';
|
|
8
|
+
import File01Icon from '@hugeicons/core-free-icons/File01Icon';
|
|
9
|
+
import FileZipIcon from '@hugeicons/core-free-icons/FileZipIcon';
|
|
10
|
+
import Image01Icon from '@hugeicons/core-free-icons/Image01Icon';
|
|
11
|
+
import MusicNote01Icon from '@hugeicons/core-free-icons/MusicNote01Icon';
|
|
12
|
+
import Pdf01Icon from '@hugeicons/core-free-icons/Pdf01Icon';
|
|
13
|
+
import Video01Icon from '@hugeicons/core-free-icons/Video01Icon';
|
|
14
|
+
import Xls01Icon from '@hugeicons/core-free-icons/Xls01Icon';
|
|
6
15
|
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
7
16
|
import { computed, onBeforeUnmount, ref } from 'vue';
|
|
8
17
|
import { useI18n } from 'vue-i18n';
|
|
@@ -12,22 +21,22 @@ import { formatFileSize, isFileAccepted } from '.';
|
|
|
12
21
|
|
|
13
22
|
// Props
|
|
14
23
|
const props = withDefaults(defineProps<{
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
accept?: string;
|
|
25
|
+
multiple?: boolean;
|
|
26
|
+
maxSize?: number;
|
|
27
|
+
maxFiles?: number;
|
|
28
|
+
disabled?: boolean;
|
|
29
|
+
title?: string;
|
|
30
|
+
description?: string;
|
|
31
|
+
class?: HTMLAttributes['class'];
|
|
23
32
|
}>(), {
|
|
24
|
-
|
|
25
|
-
|
|
33
|
+
multiple: true,
|
|
34
|
+
disabled: false,
|
|
26
35
|
});
|
|
27
36
|
|
|
28
37
|
// Emits
|
|
29
38
|
const emits = defineEmits<{
|
|
30
|
-
|
|
39
|
+
reject: [rejections: FileRejection[]];
|
|
31
40
|
}>();
|
|
32
41
|
|
|
33
42
|
// Selected files (v-model)
|
|
@@ -47,204 +56,204 @@ const resolvedDescription = computed(() => props.description || t('uiVintage.fil
|
|
|
47
56
|
|
|
48
57
|
// Constraint hints shown under the description
|
|
49
58
|
const hints = computed(() => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
59
|
+
const list: string[] = [];
|
|
60
|
+
if (props.maxSize) list.push(t('uiVintage.fileDropzone.maxSize', { size: formatFileSize(props.maxSize) }));
|
|
61
|
+
if (props.multiple && props.maxFiles) list.push(t('uiVintage.fileDropzone.maxFiles', { count: props.maxFiles }));
|
|
62
|
+
return list;
|
|
54
63
|
});
|
|
55
64
|
|
|
56
65
|
// Stable keys for the file list
|
|
57
66
|
const fileIds = new WeakMap<File, number>();
|
|
58
67
|
let nextFileId = 0;
|
|
59
68
|
function getFileId(file: File) {
|
|
60
|
-
|
|
61
|
-
|
|
69
|
+
if (!fileIds.has(file)) fileIds.set(file, nextFileId++);
|
|
70
|
+
return fileIds.get(file)!;
|
|
62
71
|
}
|
|
63
72
|
|
|
64
73
|
// Pick an icon from the file type or extension
|
|
65
74
|
function getFileIcon(file: File): HugeiconsIconDefinition {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
const type = file.type.toLowerCase();
|
|
76
|
+
const extension = file.name.split('.').pop()?.toLowerCase() ?? '';
|
|
77
|
+
if (type.startsWith('image/')) return Image01Icon;
|
|
78
|
+
if (type.startsWith('video/')) return Video01Icon;
|
|
79
|
+
if (type.startsWith('audio/')) return MusicNote01Icon;
|
|
80
|
+
if (type === 'application/pdf' || extension === 'pdf') return Pdf01Icon;
|
|
81
|
+
if (['zip', 'rar', '7z', 'tar', 'gz'].includes(extension)) return FileZipIcon;
|
|
82
|
+
if (['doc', 'docx', 'odt', 'rtf', 'txt', 'md'].includes(extension)) return Doc01Icon;
|
|
83
|
+
if (['xls', 'xlsx', 'ods', 'csv'].includes(extension)) return Xls01Icon;
|
|
84
|
+
return File01Icon;
|
|
76
85
|
}
|
|
77
86
|
|
|
78
87
|
// Briefly shake the drop zone when something is refused
|
|
79
88
|
function flashRejected() {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
89
|
+
isRejected.value = false;
|
|
90
|
+
clearTimeout(rejectTimer);
|
|
91
|
+
requestAnimationFrame(() => {
|
|
92
|
+
isRejected.value = true;
|
|
93
|
+
rejectTimer = setTimeout(() => (isRejected.value = false), 450);
|
|
94
|
+
});
|
|
86
95
|
}
|
|
87
96
|
|
|
88
97
|
// Validate incoming files and merge them into the model
|
|
89
98
|
function addFiles(incoming: File[]) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
99
|
+
if (props.disabled || incoming.length === 0) return;
|
|
100
|
+
const rejections: FileRejection[] = [];
|
|
101
|
+
const accepted: File[] = [];
|
|
102
|
+
|
|
103
|
+
// Type and size checks
|
|
104
|
+
for (const file of incoming) {
|
|
105
|
+
if (!isFileAccepted(file, props.accept)) rejections.push({ file, reason: 'type' });
|
|
106
|
+
else if (props.maxSize !== undefined && file.size > props.maxSize) rejections.push({ file, reason: 'size' });
|
|
107
|
+
else accepted.push(file);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let next: File[];
|
|
111
|
+
if (!props.multiple) {
|
|
112
|
+
// Single mode keeps only the first valid file
|
|
113
|
+
next = accepted.slice(0, 1);
|
|
114
|
+
for (const file of accepted.slice(1)) rejections.push({ file, reason: 'count' });
|
|
115
|
+
if (next.length === 0) next = [...files.value];
|
|
116
|
+
} else {
|
|
117
|
+
// Skip exact duplicates already in the list
|
|
118
|
+
const isDuplicate = (file: File) => files.value.some(existing => existing.name === file.name && existing.size === file.size && existing.lastModified === file.lastModified);
|
|
119
|
+
const fresh = accepted.filter(file => !isDuplicate(file));
|
|
120
|
+
|
|
121
|
+
// Enforce the max file count
|
|
122
|
+
const room = props.maxFiles !== undefined ? Math.max(0, props.maxFiles - files.value.length) : fresh.length;
|
|
123
|
+
for (const file of fresh.slice(room)) rejections.push({ file, reason: 'count' });
|
|
124
|
+
next = [...files.value, ...fresh.slice(0, room)];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (next.length !== files.value.length || next.some((file, index) => file !== files.value[index])) files.value = next;
|
|
128
|
+
if (rejections.length > 0) {
|
|
129
|
+
emits('reject', rejections);
|
|
130
|
+
flashRejected();
|
|
131
|
+
}
|
|
123
132
|
}
|
|
124
133
|
|
|
125
134
|
// Remove a file from the list
|
|
126
135
|
function removeFile(file: File) {
|
|
127
|
-
|
|
136
|
+
files.value = files.value.filter(existing => existing !== file);
|
|
128
137
|
}
|
|
129
138
|
|
|
130
139
|
// Open the native file picker
|
|
131
140
|
function openPicker() {
|
|
132
|
-
|
|
133
|
-
|
|
141
|
+
if (props.disabled) return;
|
|
142
|
+
inputRef.value?.click();
|
|
134
143
|
}
|
|
135
144
|
|
|
136
145
|
// Native picker selection
|
|
137
146
|
function onInputChange(event: Event) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
147
|
+
const input = event.target as HTMLInputElement;
|
|
148
|
+
addFiles(Array.from(input.files ?? []));
|
|
149
|
+
input.value = '';
|
|
141
150
|
}
|
|
142
151
|
|
|
143
152
|
// Drag tracking (depth counter avoids flicker when crossing child elements)
|
|
144
153
|
function onDragEnter(event: DragEvent) {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
154
|
+
if (props.disabled || !event.dataTransfer?.types.includes('Files')) return;
|
|
155
|
+
dragDepth++;
|
|
156
|
+
isDragging.value = true;
|
|
148
157
|
}
|
|
149
158
|
|
|
150
159
|
function onDragOver(event: DragEvent) {
|
|
151
|
-
|
|
152
|
-
|
|
160
|
+
if (props.disabled || !event.dataTransfer) return;
|
|
161
|
+
event.dataTransfer.dropEffect = 'copy';
|
|
153
162
|
}
|
|
154
163
|
|
|
155
164
|
function onDragLeave() {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
165
|
+
if (!isDragging.value) return;
|
|
166
|
+
dragDepth = Math.max(0, dragDepth - 1);
|
|
167
|
+
if (dragDepth === 0) isDragging.value = false;
|
|
159
168
|
}
|
|
160
169
|
|
|
161
170
|
function onDrop(event: DragEvent) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
171
|
+
dragDepth = 0;
|
|
172
|
+
isDragging.value = false;
|
|
173
|
+
if (props.disabled) return;
|
|
174
|
+
addFiles(Array.from(event.dataTransfer?.files ?? []));
|
|
166
175
|
}
|
|
167
176
|
|
|
168
177
|
onBeforeUnmount(() => clearTimeout(rejectTimer));
|
|
169
178
|
</script>
|
|
170
179
|
|
|
171
180
|
<template>
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
181
|
+
<div data-slot="file-dropzone" :class="cn('flex w-full flex-col gap-3', props.class)">
|
|
182
|
+
<!-- Drop area -->
|
|
183
|
+
<div role="button" :tabindex="props.disabled ? -1 : 0" :aria-disabled="props.disabled || undefined" :aria-label="resolvedTitle" :data-dragging="isDragging ? '' : undefined" :data-disabled="props.disabled ? '' : undefined" :class="cn(
|
|
184
|
+
'group/dropzone relative isolate flex min-h-44 cursor-pointer select-none flex-col items-center justify-center gap-3 rounded border-2 border-dashed border-border bg-surface/50 px-6 py-8 text-center outline-none',
|
|
185
|
+
'[transition:border-color_150ms,background-color_220ms,box-shadow_220ms,transform_420ms_var(--ease-spring)]',
|
|
186
|
+
'hover:border-border-strong hover:bg-surface focus-visible:ring-[3px] focus-visible:ring-ring/45 active:scale-[0.99] active:duration-75',
|
|
187
|
+
'data-dragging:scale-[1.01] data-dragging:border-transparent data-dragging:bg-primary/5 data-dragging:hover:border-transparent data-dragging:hover:bg-primary/5',
|
|
188
|
+
'data-disabled:cursor-not-allowed data-disabled:opacity-50 data-disabled:hover:border-border data-disabled:hover:bg-surface/50 data-disabled:active:scale-100',
|
|
189
|
+
isRejected && 'animate-uv-shake border-destructive/70'
|
|
190
|
+
)" @click="openPicker" @keydown.enter.prevent="openPicker" @keydown.space.prevent="openPicker" @dragenter.prevent="onDragEnter" @dragover.prevent="onDragOver" @dragleave="onDragLeave" @drop.prevent="onDrop">
|
|
191
|
+
<!-- Marching dashed border while dragging -->
|
|
192
|
+
<span aria-hidden="true" class="uv-dropzone-ants pointer-events-none absolute -inset-0.5 rounded opacity-0 transition-opacity duration-150 group-data-dragging/dropzone:opacity-100" />
|
|
193
|
+
|
|
194
|
+
<!-- Upload icon (lifts and grows while dragging) -->
|
|
195
|
+
<span aria-hidden="true" class="relative flex size-14 items-center justify-center rounded border border-border bg-card text-muted-foreground shadow-elevated-sm transition-[translate,scale,color,border-color,box-shadow] duration-420 ease-bounce group-hover/dropzone:-translate-y-0.5 group-hover/dropzone:text-foreground group-data-dragging/dropzone:-translate-y-2 group-data-dragging/dropzone:scale-115 group-data-dragging/dropzone:border-primary/50 group-data-dragging/dropzone:text-primary group-data-dragging/dropzone:shadow-elevated-md">
|
|
196
|
+
<HugeiconsIcon :icon="CloudUploadIcon" class="size-6" />
|
|
197
|
+
</span>
|
|
198
|
+
|
|
199
|
+
<!-- Texts -->
|
|
200
|
+
<div class="flex flex-col items-center gap-1">
|
|
201
|
+
<p class="text-xs font-semibold text-foreground sm:text-sm">
|
|
202
|
+
{{ isDragging ? t('uiVintage.fileDropzone.dropActive') : resolvedTitle }}
|
|
203
|
+
</p>
|
|
204
|
+
<p class="text-xs text-muted-foreground">{{ resolvedDescription }}</p>
|
|
205
|
+
<p v-if="hints.length" class="text-xs text-muted-foreground/80">{{ hints.join(' · ') }}</p>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
<!-- Browse hint (decorative, the whole zone is the button) -->
|
|
209
|
+
<span aria-hidden="true" :class="cn(buttonVariants({ variant: 'secondary', size: 'sm' }), 'pointer-events-none transition-opacity group-data-dragging/dropzone:opacity-0')">
|
|
210
|
+
{{ t('uiVintage.fileDropzone.browse') }}
|
|
211
|
+
</span>
|
|
212
|
+
|
|
213
|
+
<!-- Hidden native input -->
|
|
214
|
+
<input ref="inputRef" type="file" class="hidden" tabindex="-1" :accept="props.accept" :multiple="props.multiple" :disabled="props.disabled" @change="onInputChange" @click.stop>
|
|
215
|
+
</div>
|
|
216
|
+
|
|
217
|
+
<!-- Selected files -->
|
|
218
|
+
<TransitionGroup tag="ul" data-slot="file-dropzone-list" :aria-label="t('uiVintage.fileDropzone.files')" class="flex flex-col" enter-active-class="transition-[opacity,translate,filter] duration-420 ease-out-expo" enter-from-class="translate-y-2.5 opacity-0 blur-[3px]" leave-active-class="transition-[grid-template-rows,opacity,scale] duration-220 ease-snappy" leave-to-class="grid-rows-[0fr] scale-[0.98] opacity-0">
|
|
219
|
+
<li v-for="file in files" :key="getFileId(file)" class="grid grid-rows-[1fr]">
|
|
220
|
+
<div class="min-h-0 overflow-hidden">
|
|
221
|
+
<div class="pb-2">
|
|
222
|
+
<!-- File row -->
|
|
223
|
+
<div data-slot="file-dropzone-item" class="flex items-center gap-3 rounded border border-border bg-card py-2 pr-2 pl-3 shadow-elevated-sm">
|
|
224
|
+
<span class="flex size-9 shrink-0 items-center justify-center rounded-sm bg-surface text-muted-foreground">
|
|
225
|
+
<HugeiconsIcon :icon="getFileIcon(file)" class="size-4.5" />
|
|
226
|
+
</span>
|
|
227
|
+
<div class="flex min-w-0 flex-1 flex-col">
|
|
228
|
+
<span class="truncate text-xs font-semibold text-foreground sm:text-sm" :title="file.name">{{ file.name }}</span>
|
|
229
|
+
<span class="text-xs text-muted-foreground tabular-nums">{{ formatFileSize(file.size) }}</span>
|
|
230
|
+
</div>
|
|
231
|
+
<Button variant="ghost" size="icon-sm" :disabled="props.disabled" :aria-label="t('uiVintage.fileDropzone.remove', { name: file.name })" class="hover:text-destructive" @click="removeFile(file)">
|
|
232
|
+
<HugeiconsIcon :icon="Cancel01Icon" class="size-4" />
|
|
233
|
+
</Button>
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
</li>
|
|
238
|
+
</TransitionGroup>
|
|
239
|
+
</div>
|
|
231
240
|
</template>
|
|
232
241
|
|
|
233
242
|
<style>
|
|
234
243
|
/* Animated dashed border shown while files are dragged over the drop zone */
|
|
235
244
|
.uv-dropzone-ants {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
+
background-image:
|
|
246
|
+
linear-gradient(90deg, var(--primary) 55%, transparent 0),
|
|
247
|
+
linear-gradient(90deg, var(--primary) 55%, transparent 0),
|
|
248
|
+
linear-gradient(0deg, var(--primary) 55%, transparent 0),
|
|
249
|
+
linear-gradient(0deg, var(--primary) 55%, transparent 0);
|
|
250
|
+
background-size: 16px 2px, 16px 2px, 2px 16px, 2px 16px;
|
|
251
|
+
background-position: 0 0, 0 100%, 0 0, 100% 0;
|
|
252
|
+
background-repeat: repeat-x, repeat-x, repeat-y, repeat-y;
|
|
253
|
+
animation: uv-dropzone-march 0.7s linear infinite;
|
|
245
254
|
}
|
|
246
255
|
|
|
247
256
|
@keyframes uv-dropzone-march {
|
|
248
|
-
|
|
257
|
+
to { background-position: 16px 0, -16px 100%, 0 -16px, 100% 16px; }
|
|
249
258
|
}
|
|
250
259
|
</style>
|