@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
|
@@ -1,98 +1,98 @@
|
|
|
1
1
|
export type ThemeMode = 'light' | 'dark' | 'auto';
|
|
2
2
|
|
|
3
3
|
export interface ThemeTransitionOrigin {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
const THEME_STORAGE_KEY = 'theme';
|
|
9
9
|
|
|
10
10
|
type DocumentWithViewTransition = Document & {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
startViewTransition?: (callback: () => void) => {
|
|
12
|
+
ready: Promise<void>;
|
|
13
|
+
finished: Promise<void>;
|
|
14
|
+
};
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
// Check if the system prefers dark mode
|
|
18
18
|
const isSystemDarkPreferred = () => {
|
|
19
|
-
|
|
19
|
+
return typeof window !== 'undefined' && window.matchMedia?.('(prefers-color-scheme: dark)').matches;
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
// Resolve whether a theme mode ends up dark
|
|
23
23
|
export const isDarkTheme = (theme: ThemeMode) => {
|
|
24
|
-
|
|
24
|
+
return theme === 'dark' || (theme === 'auto' && isSystemDarkPreferred());
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
// Read the persisted theme mode
|
|
28
28
|
export const getStoredTheme = (): ThemeMode => {
|
|
29
|
-
|
|
29
|
+
if (typeof localStorage === 'undefined') return 'auto';
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
// Storage can throw when the browser blocks it (privacy settings, sandboxed frames)
|
|
32
|
+
try {
|
|
33
|
+
const storedTheme = localStorage.getItem(THEME_STORAGE_KEY);
|
|
34
|
+
return storedTheme === 'light' || storedTheme === 'dark' ? storedTheme : 'auto';
|
|
35
|
+
} catch {
|
|
36
|
+
return 'auto';
|
|
37
|
+
}
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
// Apply the selected theme to the document and persist it
|
|
41
41
|
export const applyTheme = (theme: ThemeMode) => {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
if (typeof document === 'undefined') return;
|
|
43
|
+
const root = document.documentElement;
|
|
44
|
+
root.classList.remove('light', 'dark');
|
|
45
|
+
root.classList.add(isDarkTheme(theme) ? 'dark' : 'light');
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
// Persisting is best effort: the theme still applies when storage is blocked
|
|
48
|
+
try {
|
|
49
|
+
localStorage.setItem(THEME_STORAGE_KEY, theme);
|
|
50
|
+
} catch {
|
|
51
|
+
// Ignore storage errors
|
|
52
|
+
}
|
|
53
53
|
};
|
|
54
54
|
|
|
55
55
|
// Apply a theme with a circular reveal that grows from the origin point (View Transitions API)
|
|
56
56
|
export const applyThemeWithTransition = async (theme: ThemeMode, origin?: ThemeTransitionOrigin | Element | null) => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
const doc = document as DocumentWithViewTransition;
|
|
58
|
+
const root = document.documentElement;
|
|
59
|
+
const willChange = root.classList.contains('dark') !== isDarkTheme(theme);
|
|
60
|
+
const prefersReducedMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
// Fall back to an instant switch when the transition is unsupported or pointless
|
|
63
|
+
if (!doc.startViewTransition || !willChange || prefersReducedMotion) {
|
|
64
|
+
applyTheme(theme);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
68
|
+
// Resolve the reveal origin (defaults to the top-right corner)
|
|
69
|
+
let x = window.innerWidth;
|
|
70
|
+
let y = 0;
|
|
71
|
+
if (origin instanceof Element) {
|
|
72
|
+
const rect = origin.getBoundingClientRect();
|
|
73
|
+
x = rect.left + rect.width / 2;
|
|
74
|
+
y = rect.top + rect.height / 2;
|
|
75
|
+
} else if (origin) {
|
|
76
|
+
x = origin.x;
|
|
77
|
+
y = origin.y;
|
|
78
|
+
}
|
|
79
|
+
const radius = Math.hypot(Math.max(x, window.innerWidth - x), Math.max(y, window.innerHeight - y));
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
// Freeze CSS transitions so the snapshots capture the final colors
|
|
82
|
+
root.classList.add('uv-theme-switching');
|
|
83
|
+
const transition = doc.startViewTransition(() => applyTheme(theme));
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
85
|
+
try {
|
|
86
|
+
await transition.ready;
|
|
87
|
+
root.animate({
|
|
88
|
+
clipPath: [`circle(0px at ${x}px ${y}px)`, `circle(${radius}px at ${x}px ${y}px)`]
|
|
89
|
+
}, {
|
|
90
|
+
duration: 620,
|
|
91
|
+
easing: 'cubic-bezier(0.16, 1, 0.3, 1)',
|
|
92
|
+
pseudoElement: '::view-transition-new(root)'
|
|
93
|
+
});
|
|
94
|
+
await transition.finished;
|
|
95
|
+
} finally {
|
|
96
|
+
root.classList.remove('uv-theme-switching');
|
|
97
|
+
}
|
|
98
98
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { HTMLAttributes } from 'vue';
|
|
3
3
|
import { computed, ref, useId } from 'vue';
|
|
4
|
-
import
|
|
4
|
+
import ArrowDown01Icon from '@hugeicons/core-free-icons/ArrowDown01Icon';
|
|
5
5
|
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
6
6
|
import { Card } from '../card';
|
|
7
7
|
import { cn } from '../../../lib/utils';
|
|
@@ -9,20 +9,20 @@ import type { HugeiconsIconDefinition } from '../../../lib/common-types';
|
|
|
9
9
|
|
|
10
10
|
// Props
|
|
11
11
|
const props = withDefaults(defineProps<{
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
title: string;
|
|
13
|
+
initiallyExpanded?: boolean;
|
|
14
|
+
open?: boolean;
|
|
15
|
+
icon?: HugeiconsIconDefinition | null;
|
|
16
|
+
iconClass?: HTMLAttributes['class'];
|
|
17
|
+
class?: HTMLAttributes['class'];
|
|
18
18
|
}>(), {
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
initiallyExpanded: false,
|
|
20
|
+
open: undefined
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
// Emits
|
|
24
24
|
const emits = defineEmits<{
|
|
25
|
-
|
|
25
|
+
'update:open': [value: boolean];
|
|
26
26
|
}>();
|
|
27
27
|
|
|
28
28
|
const contentId = useId();
|
|
@@ -34,40 +34,40 @@ const expanded = computed(() => props.open ?? internalExpanded.value);
|
|
|
34
34
|
|
|
35
35
|
// Toggle expanded state
|
|
36
36
|
const toggleExpanded = () => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
const nextValue = !expanded.value;
|
|
38
|
+
internalExpanded.value = nextValue;
|
|
39
|
+
emits('update:open', nextValue);
|
|
40
40
|
};
|
|
41
41
|
</script>
|
|
42
42
|
|
|
43
43
|
<template>
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
<Card :data-state="expanded ? 'open' : 'closed'" :class="cn('group/accordion gap-0! overflow-hidden p-0!', props.class)">
|
|
45
|
+
<!-- Header -->
|
|
46
|
+
<button :id="triggerId" type="button" :aria-expanded="expanded" :aria-controls="contentId" class="group/trigger flex w-full cursor-pointer items-center gap-3 px-5 py-3.5 text-left outline-none focus-visible:bg-surface focus-visible:ring-[3px] focus-visible:ring-inset focus-visible:ring-ring/40" @click="toggleExpanded">
|
|
47
|
+
<!-- Icon -->
|
|
48
|
+
<span v-if="props.icon" class="flex size-7 shrink-0 items-center justify-center rounded border border-primary/30 text-primary">
|
|
49
|
+
<HugeiconsIcon :icon="props.icon" :class="cn('size-3.5', props.iconClass)" />
|
|
50
|
+
</span>
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
<!-- Title -->
|
|
53
|
+
<span class="min-w-0 flex-1 text-sm font-semibold text-foreground">
|
|
54
|
+
{{ props.title }}
|
|
55
|
+
</span>
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
<!-- Optional content before the toggle icon -->
|
|
58
|
+
<slot name="trailing" />
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
<!-- Toggle icon -->
|
|
61
|
+
<HugeiconsIcon :icon="ArrowDown01Icon" class="size-4 shrink-0 text-muted-foreground transition-[rotate,color] duration-[420ms] ease-spring group-hover/trigger:text-primary group-data-[state=open]/accordion:rotate-180 group-data-[state=open]/accordion:text-primary" />
|
|
62
|
+
</button>
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
</template>
|
|
64
|
+
<!-- Content -->
|
|
65
|
+
<div :id="contentId" role="region" :aria-labelledby="triggerId" :aria-hidden="!expanded" :inert="!expanded || undefined" :class="['grid transition-[grid-template-rows,opacity] duration-[380ms] ease-out-expo', expanded ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0']">
|
|
66
|
+
<div class="overflow-hidden">
|
|
67
|
+
<div :class="['border-t border-border px-5 py-4 transition-[translate,filter] duration-[420ms] ease-spring', expanded ? 'translate-y-0 blur-none' : '-translate-y-2 blur-[2px]']">
|
|
68
|
+
<slot />
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</Card>
|
|
73
|
+
</template>
|
|
@@ -6,13 +6,13 @@ import { alertClasses, type AlertColors } from '.';
|
|
|
6
6
|
|
|
7
7
|
// Props
|
|
8
8
|
const props = defineProps<{
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
class?: HTMLAttributes['class'];
|
|
10
|
+
color?: AlertColors['color'];
|
|
11
11
|
}>();
|
|
12
12
|
</script>
|
|
13
13
|
|
|
14
14
|
<template>
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
<div data-slot="alert" role="alert" :class="cn(alertClasses(), getSurfaceToneClasses(props.color ?? 'gray'), props.class)">
|
|
16
|
+
<slot />
|
|
17
|
+
</div>
|
|
18
18
|
</template>
|
|
@@ -4,12 +4,12 @@ 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
|
-
|
|
12
|
+
<div data-slot="alert-description" :class="cn('col-start-2 grid justify-items-start gap-1 [&_p]:leading-relaxed', props.class)">
|
|
13
|
+
<slot />
|
|
14
|
+
</div>
|
|
15
15
|
</template>
|
|
@@ -4,12 +4,12 @@ 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
|
-
|
|
12
|
+
<div data-slot="alert-title" :class="cn('col-start-2 line-clamp-1 min-h-4 font-semibold tracking-tight text-current', props.class)">
|
|
13
|
+
<slot />
|
|
14
|
+
</div>
|
|
15
15
|
</template>
|
|
@@ -7,9 +7,9 @@ export { default as AlertDescription } from './AlertDescription.vue';
|
|
|
7
7
|
export { default as AlertTitle } from './AlertTitle.vue';
|
|
8
8
|
|
|
9
9
|
export type AlertColors = {
|
|
10
|
-
|
|
10
|
+
color?: ToneColor | null | undefined;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
export const alertClasses: (props?: ClassProp) => string = cva(
|
|
14
|
-
|
|
14
|
+
'relative w-full rounded px-3 py-2 text-xs transition-colors duration-300 ease-in-out grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 content-start items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current sm:px-4 sm:py-3 sm:text-sm',
|
|
15
15
|
);
|
|
@@ -13,7 +13,7 @@ const forwarded = useForwardPropsEmits(props, emits);
|
|
|
13
13
|
</script>
|
|
14
14
|
|
|
15
15
|
<template>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
<AlertDialogRoot v-slot="slotProps" data-slot="alert-dialog" v-bind="forwarded">
|
|
17
|
+
<slot v-bind="slotProps" />
|
|
18
|
+
</AlertDialogRoot>
|
|
19
19
|
</template>
|
|
@@ -14,7 +14,7 @@ const delegatedProps = reactiveOmit(props, 'class');
|
|
|
14
14
|
</script>
|
|
15
15
|
|
|
16
16
|
<template>
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
<AlertDialogAction v-bind="delegatedProps" :class="cn(buttonVariants(), props.class)">
|
|
18
|
+
<slot />
|
|
19
|
+
</AlertDialogAction>
|
|
20
20
|
</template>
|
|
@@ -14,7 +14,7 @@ const delegatedProps = reactiveOmit(props, 'class');
|
|
|
14
14
|
</script>
|
|
15
15
|
|
|
16
16
|
<template>
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
<AlertDialogCancel v-bind="delegatedProps" :class="cn(buttonVariants({ variant: 'secondary' }), 'mt-2 sm:mt-0', props.class)">
|
|
18
|
+
<slot />
|
|
19
|
+
</AlertDialogCancel>
|
|
20
20
|
</template>
|
|
@@ -7,7 +7,7 @@ import { cn } from '../../../lib/utils';
|
|
|
7
7
|
|
|
8
8
|
// Keep attrs on the dialog content element
|
|
9
9
|
defineOptions({
|
|
10
|
-
|
|
10
|
+
inheritAttrs: false
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
// Props
|
|
@@ -24,10 +24,10 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|
|
24
24
|
</script>
|
|
25
25
|
|
|
26
26
|
<template>
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
<AlertDialogPortal>
|
|
28
|
+
<AlertDialogOverlay data-slot="alert-dialog-overlay" class="uv-overlay-motion fixed inset-0 z-50 bg-black/40 backdrop-blur-[3px] dark:bg-black/60" />
|
|
29
|
+
<AlertDialogContent data-slot="alert-dialog-content" v-bind="{ ...$attrs, ...forwarded }" :class="cn('uv-modal-motion bg-card text-card-foreground fixed top-[50%] left-[50%] z-50 grid w-[95%] max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-0 overflow-hidden rounded border border-border shadow-elevated-xl sm:max-w-lg', props.class)">
|
|
30
|
+
<slot />
|
|
31
|
+
</AlertDialogContent>
|
|
32
|
+
</AlertDialogPortal>
|
|
33
33
|
</template>
|
|
@@ -13,7 +13,7 @@ const delegatedProps = reactiveOmit(props, 'class');
|
|
|
13
13
|
</script>
|
|
14
14
|
|
|
15
15
|
<template>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
<AlertDialogDescription data-slot="alert-dialog-description" v-bind="delegatedProps" :class="cn('text-xs leading-6 text-muted-foreground sm:text-sm', props.class)">
|
|
17
|
+
<slot />
|
|
18
|
+
</AlertDialogDescription>
|
|
19
19
|
</template>
|
|
@@ -4,12 +4,12 @@ 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
|
-
|
|
12
|
+
<div data-slot="alert-dialog-footer" :class="cn('flex flex-col-reverse gap-3 border-t border-border bg-surface/60 px-5 py-4 sm:flex-row-reverse sm:px-6', props.class)">
|
|
13
|
+
<slot />
|
|
14
|
+
</div>
|
|
15
15
|
</template>
|
|
@@ -4,12 +4,12 @@ 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
|
-
|
|
12
|
+
<div data-slot="alert-dialog-header" :class="cn('flex flex-col gap-1.5 text-center sm:text-left', props.class)">
|
|
13
|
+
<slot />
|
|
14
|
+
</div>
|
|
15
15
|
</template>
|
|
@@ -13,7 +13,7 @@ const delegatedProps = reactiveOmit(props, 'class');
|
|
|
13
13
|
</script>
|
|
14
14
|
|
|
15
15
|
<template>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
<AlertDialogTitle data-slot="alert-dialog-title" v-bind="delegatedProps" :class="cn('text-base font-semibold', props.class)">
|
|
17
|
+
<slot />
|
|
18
|
+
</AlertDialogTitle>
|
|
19
19
|
</template>
|
|
@@ -7,7 +7,7 @@ const props = defineProps<AlertDialogTriggerProps>();
|
|
|
7
7
|
</script>
|
|
8
8
|
|
|
9
9
|
<template>
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
<AlertDialogTrigger data-slot="alert-dialog-trigger" v-bind="props">
|
|
11
|
+
<slot />
|
|
12
|
+
</AlertDialogTrigger>
|
|
13
13
|
</template>
|
|
@@ -5,21 +5,21 @@ import { cn } from '../../../lib/utils';
|
|
|
5
5
|
|
|
6
6
|
// Props
|
|
7
7
|
const props = withDefaults(defineProps<{
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
value: number;
|
|
9
|
+
duration?: number;
|
|
10
|
+
locale?: string;
|
|
11
|
+
formatOptions?: Intl.NumberFormatOptions;
|
|
12
|
+
prefix?: string;
|
|
13
|
+
suffix?: string;
|
|
14
|
+
animateOnMount?: boolean;
|
|
15
|
+
class?: HTMLAttributes['class'];
|
|
16
16
|
}>(), {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
duration: 900,
|
|
18
|
+
locale: undefined,
|
|
19
|
+
formatOptions: undefined,
|
|
20
|
+
prefix: '',
|
|
21
|
+
suffix: '',
|
|
22
|
+
animateOnMount: true
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
// The server always renders the final value (search engines, link previews and the first paint never see a 0), the count-up only runs for components created in the browser
|
|
@@ -32,9 +32,9 @@ let frame: number | null = null;
|
|
|
32
32
|
|
|
33
33
|
// Format the displayed value
|
|
34
34
|
const formatter = computed(() => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
// Default to the precision of the target so intermediate frames never show stray decimals
|
|
36
|
+
const decimals = (String(props.value).split('.')[1] ?? '').length;
|
|
37
|
+
return new Intl.NumberFormat(props.locale, props.formatOptions ?? { minimumFractionDigits: decimals, maximumFractionDigits: decimals });
|
|
38
38
|
});
|
|
39
39
|
const formattedValue = computed(() => `${props.prefix}${formatter.value.format(displayValue.value)}${props.suffix}`);
|
|
40
40
|
|
|
@@ -46,32 +46,32 @@ const easeOutExpo = (progress: number) => progress === 1 ? 1 : 1 - Math.pow(2, -
|
|
|
46
46
|
|
|
47
47
|
// Tween from the current displayed value to the target value
|
|
48
48
|
const animateTo = (target: number) => {
|
|
49
|
-
|
|
49
|
+
if (frame !== null) cancelAnimationFrame(frame);
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
// Jump straight to the value when motion is reduced or unavailable
|
|
52
|
+
const prefersReducedMotion = typeof window !== 'undefined' && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
|
|
53
|
+
if (typeof requestAnimationFrame === 'undefined' || prefersReducedMotion || props.duration <= 0) {
|
|
54
|
+
displayValue.value = target;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
const from = displayValue.value;
|
|
59
|
+
const start = performance.now();
|
|
60
|
+
direction.value = target >= from ? 'up' : 'down';
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
62
|
+
// Advance one frame of the tween
|
|
63
|
+
const step = (now: number) => {
|
|
64
|
+
const progress = Math.min(1, (now - start) / props.duration);
|
|
65
|
+
displayValue.value = from + (target - from) * easeOutExpo(progress);
|
|
66
|
+
if (progress < 1) {
|
|
67
|
+
frame = requestAnimationFrame(step);
|
|
68
|
+
} else {
|
|
69
|
+
displayValue.value = target;
|
|
70
|
+
direction.value = null;
|
|
71
|
+
frame = null;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
frame = requestAnimationFrame(step);
|
|
75
75
|
};
|
|
76
76
|
|
|
77
77
|
// Re-animate whenever the value changes
|
|
@@ -79,24 +79,25 @@ watch(() => props.value, value => animateTo(value));
|
|
|
79
79
|
|
|
80
80
|
// When hydrating server HTML (the element already exists before the first render), keep the final value the server rendered, so there is no mismatch and no flash to 0
|
|
81
81
|
onBeforeMount(() => {
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
isHydrated = !!instance?.vnode.el;
|
|
83
|
+
if (isHydrated) displayValue.value = props.value;
|
|
84
84
|
});
|
|
85
85
|
|
|
86
86
|
// Count up on mount (only for components created in the browser, server-rendered ones already show the final value)
|
|
87
87
|
onMounted(() => {
|
|
88
|
-
|
|
88
|
+
if (props.animateOnMount && !isHydrated) animateTo(props.value);
|
|
89
89
|
});
|
|
90
90
|
|
|
91
91
|
// Stop any pending frame
|
|
92
92
|
onBeforeUnmount(() => {
|
|
93
|
-
|
|
93
|
+
if (frame !== null) cancelAnimationFrame(frame);
|
|
94
94
|
});
|
|
95
95
|
</script>
|
|
96
96
|
|
|
97
97
|
<template>
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
<!-- The server and the browser can format the same number differently (e.g. Node writes "1000" and Chrome "1.000" in Italian, their locale data differ), so a text mismatch is expected and the browser's formatting wins -->
|
|
99
|
+
<span data-slot="animated-number" :data-direction="direction ?? undefined" :class="cn('inline-block tabular-nums', props.class)">
|
|
100
|
+
<span aria-hidden="true" data-allow-mismatch="text">{{ formattedValue }}</span>
|
|
101
|
+
<span class="sr-only" data-allow-mismatch="text">{{ accessibleValue }}</span>
|
|
102
|
+
</span>
|
|
102
103
|
</template>
|