@brumaombra/ui-vintage 0.7.8 → 0.7.10
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 +308 -48
- package/module.mjs +1 -1
- package/package.json +2 -1
- package/src/components/background-grid/BackgroundGrid.vue +11 -2
- package/src/components/background-grid/index.ts +2 -2
- package/src/components/blog/AllPostsSection.vue +25 -9
- package/src/components/blog/BlogContentRenderer.vue +133 -0
- package/src/components/blog/BlogFAQSection.vue +8 -3
- package/src/components/blog/BlogHeaderSection.vue +14 -11
- package/src/components/blog/BlogInfoSection.vue +13 -11
- package/src/components/blog/BlogProgressBar.vue +77 -0
- package/src/components/blog/BlogSectionTitle.vue +5 -5
- package/src/components/blog/CategoriesList.vue +11 -3
- package/src/components/blog/CategoriesSection.vue +11 -3
- package/src/components/blog/CategoryCard.vue +10 -8
- package/src/components/blog/HeaderCarousel.vue +24 -15
- package/src/components/blog/PostCard.vue +16 -11
- package/src/components/blog/PostsList.vue +12 -3
- package/src/components/blog/SocialShareSidebar.vue +43 -41
- package/src/components/blog/TableOfContents.vue +29 -13
- package/src/components/blog/index.ts +1 -0
- package/src/components/busy-indicator/BusyIndicator.vue +2 -2
- package/src/components/busy-indicator/busy-indicator-state.ts +11 -12
- package/src/components/busy-indicator/index.ts +3 -3
- package/src/components/card-grid/CardGrid.vue +25 -11
- package/src/components/chip/Chip.vue +15 -15
- package/src/components/chip/index.ts +2 -2
- package/src/components/confirm-dialog/ConfirmDialog.vue +9 -9
- package/src/components/confirm-dialog/confirm-dialog-state.ts +15 -14
- package/src/components/confirm-dialog/index.ts +3 -12
- package/src/components/content/BlogList.vue +11 -8
- package/src/components/content/BlogTable.vue +18 -14
- package/src/components/content/ProseHr.vue +10 -7
- package/src/components/content/Terminal.vue +70 -0
- package/src/components/content/index.ts +2 -1
- package/src/components/dashboard-shell/DashboardShell.vue +43 -64
- package/src/components/dashboard-shell/index.ts +2 -2
- package/src/components/data-list/DataList.vue +11 -13
- package/src/components/data-list/DataListItem.vue +9 -11
- package/src/components/data-list/DataListLabel.vue +9 -11
- package/src/components/data-list/DataListText.vue +9 -11
- package/src/components/data-list/DataListValue.vue +9 -11
- package/src/components/data-list/index.ts +5 -5
- package/src/components/empty-state-card/EmptyStateCard.vue +21 -11
- package/src/components/error-page/ErrorPage.vue +24 -27
- package/src/components/error-page/index.ts +2 -2
- package/src/components/info-card/InfoCard.vue +17 -11
- package/src/components/landing/index.ts +6 -6
- package/src/components/landing/landing-content/LandingContent.vue +5 -7
- package/src/components/landing/landing-content/index.ts +2 -2
- package/src/components/landing/landing-footer/LandingFooter.vue +26 -29
- package/src/components/landing/landing-footer/index.ts +3 -3
- package/src/components/landing/landing-navbar/LandingNavbar.vue +51 -19
- package/src/components/landing/landing-navbar/index.ts +2 -2
- package/src/components/landing/landing-shell/LandingShell.vue +12 -15
- package/src/components/landing/landing-shell/index.ts +2 -2
- package/src/components/language-flag/LanguageFlag.vue +6 -5
- package/src/components/language-flag/index.ts +1 -0
- package/src/components/language-selector/LanguageSelector.vue +20 -21
- package/src/components/load-more-button/LoadMoreButton.vue +18 -14
- package/src/components/loading-state-card/LoadingStateCard.vue +13 -9
- package/src/components/message-dialog/MessageDialog.vue +19 -19
- package/src/components/message-dialog/index.ts +3 -11
- package/src/components/message-dialog/message-dialog-state.ts +12 -11
- package/src/components/message-toast/MessageToast.vue +16 -16
- package/src/components/message-toast/index.ts +2 -9
- package/src/components/message-toast/message-toast-state.ts +9 -9
- package/src/components/page-header/PageHeader.vue +8 -5
- package/src/components/progress-component/ProgressComponent.vue +13 -10
- package/src/components/single-value-card/SingleValueCard.vue +22 -19
- package/src/components/slider-form-component/SliderFormComponent.vue +11 -20
- package/src/components/slider-form-component/index.ts +2 -2
- package/src/components/switch-form-component/SwitchFormComponent.vue +7 -9
- package/src/components/switch-form-component/index.ts +2 -2
- package/src/components/text-link/TextLink.vue +11 -7
- package/src/components/ui/accordion/Accordion.vue +10 -13
- package/src/components/ui/accordion/index.ts +1 -1
- package/src/components/ui/alert/Alert.vue +11 -10
- package/src/components/ui/alert/AlertDescription.vue +9 -8
- package/src/components/ui/alert/AlertTitle.vue +9 -8
- package/src/components/ui/alert/index.ts +10 -10
- package/src/components/ui/alert-dialog/AlertDialog.vue +13 -9
- package/src/components/ui/alert-dialog/AlertDialogAction.vue +14 -12
- package/src/components/ui/alert-dialog/AlertDialogCancel.vue +14 -16
- package/src/components/ui/alert-dialog/AlertDialogContent.vue +24 -27
- package/src/components/ui/alert-dialog/AlertDialogDescription.vue +13 -13
- package/src/components/ui/alert-dialog/AlertDialogFooter.vue +9 -12
- package/src/components/ui/alert-dialog/AlertDialogHeader.vue +9 -8
- package/src/components/ui/alert-dialog/AlertDialogTitle.vue +13 -11
- package/src/components/ui/alert-dialog/AlertDialogTrigger.vue +8 -7
- package/src/components/ui/alert-dialog/index.ts +9 -9
- package/src/components/ui/badge/Badge.vue +11 -12
- package/src/components/ui/badge/index.ts +1 -1
- package/src/components/ui/breadcrumb/Breadcrumb.vue +9 -12
- package/src/components/ui/breadcrumb/BreadcrumbEllipsis.vue +15 -19
- package/src/components/ui/breadcrumb/BreadcrumbItem.vue +10 -12
- package/src/components/ui/breadcrumb/BreadcrumbLink.vue +13 -17
- package/src/components/ui/breadcrumb/BreadcrumbList.vue +10 -12
- package/src/components/ui/breadcrumb/BreadcrumbPage.vue +10 -15
- package/src/components/ui/breadcrumb/BreadcrumbSeparator.vue +14 -18
- package/src/components/ui/breadcrumb/index.ts +7 -7
- package/src/components/ui/busy/Busy.vue +36 -29
- package/src/components/ui/busy/index.ts +1 -1
- package/src/components/ui/button/Button.vue +18 -19
- package/src/components/ui/button/index.ts +54 -35
- package/src/components/ui/calendar/Calendar.vue +167 -121
- package/src/components/ui/calendar/CalendarCell.vue +18 -17
- package/src/components/ui/calendar/CalendarCellTrigger.vue +31 -33
- package/src/components/ui/calendar/CalendarGrid.vue +14 -17
- package/src/components/ui/calendar/CalendarGridBody.vue +9 -11
- package/src/components/ui/calendar/CalendarGridHead.vue +10 -12
- package/src/components/ui/calendar/CalendarGridRow.vue +14 -16
- package/src/components/ui/calendar/CalendarHeadCell.vue +18 -17
- package/src/components/ui/calendar/CalendarHeader.vue +18 -17
- package/src/components/ui/calendar/CalendarHeading.vue +18 -22
- package/src/components/ui/calendar/CalendarNextButton.vue +29 -26
- package/src/components/ui/calendar/CalendarPrevButton.vue +29 -26
- package/src/components/ui/calendar/index.ts +17 -13
- package/src/components/ui/card/Card.vue +21 -13
- package/src/components/ui/card/CardAction.vue +12 -7
- package/src/components/ui/card/CardContent.vue +8 -7
- package/src/components/ui/card/CardDescription.vue +16 -7
- package/src/components/ui/card/CardFooter.vue +8 -7
- package/src/components/ui/card/CardHeader.vue +12 -7
- package/src/components/ui/card/CardText.vue +12 -7
- package/src/components/ui/card/CardTitle.vue +9 -7
- package/src/components/ui/card/index.ts +8 -8
- package/src/components/ui/command/Command.vue +71 -61
- package/src/components/ui/command/CommandDialog.vue +33 -23
- package/src/components/ui/command/CommandEmpty.vue +19 -20
- package/src/components/ui/command/CommandGroup.vue +32 -35
- package/src/components/ui/command/CommandInput.vue +25 -21
- package/src/components/ui/command/CommandItem.vue +62 -59
- package/src/components/ui/command/CommandList.vue +21 -18
- package/src/components/ui/command/CommandSeparator.vue +13 -11
- package/src/components/ui/command/CommandShortcut.vue +13 -8
- package/src/components/ui/command/index.ts +24 -20
- package/src/components/ui/date-picker/DatePicker.vue +46 -37
- package/src/components/ui/date-picker/index.ts +2 -2
- package/src/components/ui/date-time-picker/DateTimePicker.vue +99 -95
- package/src/components/ui/date-time-picker/index.ts +2 -2
- package/src/components/ui/dialog/Dialog.vue +12 -9
- package/src/components/ui/dialog/DialogClose.vue +8 -10
- package/src/components/ui/dialog/DialogContent.vue +36 -42
- package/src/components/ui/dialog/DialogDescription.vue +16 -11
- package/src/components/ui/dialog/DialogFooter.vue +13 -12
- package/src/components/ui/dialog/DialogHeader.vue +9 -8
- package/src/components/ui/dialog/DialogOverlay.vue +16 -11
- package/src/components/ui/dialog/DialogScrollContent.vue +47 -45
- package/src/components/ui/dialog/DialogTitle.vue +13 -12
- package/src/components/ui/dialog/DialogTrigger.vue +8 -7
- package/src/components/ui/dialog/index.ts +10 -10
- package/src/components/ui/field/Field.vue +12 -14
- package/src/components/ui/field/FieldContent.vue +13 -11
- package/src/components/ui/field/FieldDescription.vue +15 -13
- package/src/components/ui/field/FieldError.vue +43 -40
- package/src/components/ui/field/FieldGroup.vue +13 -11
- package/src/components/ui/field/FieldLabel.vue +16 -14
- package/src/components/ui/field/FieldLegend.vue +16 -14
- package/src/components/ui/field/FieldSeparator.vue +17 -15
- package/src/components/ui/field/FieldSet.vue +14 -12
- package/src/components/ui/field/FieldTitle.vue +13 -11
- package/src/components/ui/field/index.ts +36 -36
- package/src/components/ui/input/Input.vue +23 -20
- package/src/components/ui/input/index.ts +1 -1
- package/src/components/ui/label/Label.vue +16 -15
- package/src/components/ui/label/index.ts +1 -1
- package/src/components/ui/native-select/NativeSelect.vue +31 -41
- package/src/components/ui/native-select/NativeSelectOptGroup.vue +8 -7
- package/src/components/ui/native-select/NativeSelectOption.vue +8 -7
- package/src/components/ui/native-select/index.ts +3 -3
- package/src/components/ui/popover/Popover.vue +12 -9
- package/src/components/ui/popover/PopoverAnchor.vue +8 -7
- package/src/components/ui/popover/PopoverContent.vue +27 -31
- package/src/components/ui/popover/PopoverTrigger.vue +8 -7
- package/src/components/ui/popover/index.ts +4 -4
- package/src/components/ui/progress/Progress.vue +18 -32
- package/src/components/ui/progress/index.ts +1 -1
- package/src/components/ui/scroll-area/ScrollArea.vue +23 -26
- package/src/components/ui/scroll-area/ScrollBar.vue +22 -26
- package/src/components/ui/scroll-area/index.ts +2 -2
- package/src/components/ui/select/Select.vue +12 -9
- package/src/components/ui/select/SelectContent.vue +39 -40
- package/src/components/ui/select/SelectGroup.vue +8 -7
- package/src/components/ui/select/SelectItem.vue +36 -41
- package/src/components/ui/select/SelectItemContent.vue +22 -15
- package/src/components/ui/select/SelectItemText.vue +8 -7
- package/src/components/ui/select/SelectLabel.vue +10 -9
- package/src/components/ui/select/SelectScrollDownButton.vue +21 -17
- package/src/components/ui/select/SelectScrollUpButton.vue +21 -17
- package/src/components/ui/select/SelectSeparator.vue +11 -9
- package/src/components/ui/select/SelectTrigger.vue +27 -25
- package/src/components/ui/select/SelectValue.vue +8 -7
- package/src/components/ui/select/SelectValueContent.vue +30 -18
- package/src/components/ui/select/index.ts +13 -13
- package/src/components/ui/separator/Separator.vue +17 -18
- package/src/components/ui/separator/index.ts +1 -1
- package/src/components/ui/sheet/Sheet.vue +12 -9
- package/src/components/ui/sheet/SheetClose.vue +8 -7
- package/src/components/ui/sheet/SheetContent.vue +36 -37
- package/src/components/ui/sheet/SheetDescription.vue +12 -11
- package/src/components/ui/sheet/SheetFooter.vue +8 -7
- package/src/components/ui/sheet/SheetHeader.vue +8 -7
- package/src/components/ui/sheet/SheetOverlay.vue +16 -11
- package/src/components/ui/sheet/SheetTitle.vue +12 -11
- package/src/components/ui/sheet/SheetTrigger.vue +8 -7
- package/src/components/ui/sheet/index.ts +8 -8
- package/src/components/ui/sidebar/Sidebar.vue +68 -61
- package/src/components/ui/sidebar/SidebarContent.vue +13 -8
- package/src/components/ui/sidebar/SidebarFooter.vue +13 -8
- package/src/components/ui/sidebar/SidebarGroup.vue +13 -8
- package/src/components/ui/sidebar/SidebarGroupAction.vue +16 -16
- package/src/components/ui/sidebar/SidebarGroupContent.vue +9 -8
- package/src/components/ui/sidebar/SidebarGroupLabel.vue +15 -14
- package/src/components/ui/sidebar/SidebarHeader.vue +13 -8
- package/src/components/ui/sidebar/SidebarInput.vue +14 -12
- package/src/components/ui/sidebar/SidebarInset.vue +14 -12
- package/src/components/ui/sidebar/SidebarMenu.vue +9 -8
- package/src/components/ui/sidebar/SidebarMenuAction.vue +25 -23
- package/src/components/ui/sidebar/SidebarMenuBadge.vue +19 -17
- package/src/components/ui/sidebar/SidebarMenuButton.vue +33 -32
- package/src/components/ui/sidebar/SidebarMenuButtonChild.vue +19 -18
- package/src/components/ui/sidebar/SidebarMenuItem.vue +9 -8
- package/src/components/ui/sidebar/SidebarMenuSkeleton.vue +17 -12
- package/src/components/ui/sidebar/SidebarMenuSub.vue +14 -12
- package/src/components/ui/sidebar/SidebarMenuSubButton.vue +24 -21
- package/src/components/ui/sidebar/SidebarMenuSubItem.vue +9 -8
- package/src/components/ui/sidebar/SidebarProvider.vue +63 -53
- package/src/components/ui/sidebar/SidebarRail.vue +20 -18
- package/src/components/ui/sidebar/SidebarSeparator.vue +10 -9
- package/src/components/ui/sidebar/SidebarTrigger.vue +15 -14
- package/src/components/ui/sidebar/index.ts +62 -58
- package/src/components/ui/sidebar/utils.ts +16 -16
- package/src/components/ui/skeleton/Skeleton.vue +12 -9
- package/src/components/ui/skeleton/index.ts +1 -1
- package/src/components/ui/slider/Slider.vue +24 -19
- package/src/components/ui/slider/index.ts +1 -1
- package/src/components/ui/switch/Switch.vue +26 -23
- package/src/components/ui/switch/index.ts +1 -1
- package/src/components/ui/table/Table.vue +11 -10
- package/src/components/ui/table/TableBody.vue +9 -11
- package/src/components/ui/table/TableCaption.vue +9 -11
- package/src/components/ui/table/TableCell.vue +13 -16
- package/src/components/ui/table/TableEmpty.vue +22 -25
- package/src/components/ui/table/TableFooter.vue +13 -11
- package/src/components/ui/table/TableHead.vue +13 -11
- package/src/components/ui/table/TableHeader.vue +9 -11
- package/src/components/ui/table/TableRow.vue +13 -11
- package/src/components/ui/table/index.ts +9 -9
- package/src/components/ui/tabs/Tabs.vue +18 -18
- package/src/components/ui/tabs/TabsContent.vue +14 -15
- package/src/components/ui/tabs/TabsList.vue +18 -18
- package/src/components/ui/tabs/TabsTrigger.vue +19 -20
- package/src/components/ui/tabs/index.ts +4 -4
- package/src/components/ui/textarea/Textarea.vue +23 -20
- package/src/components/ui/textarea/index.ts +1 -1
- package/src/components/ui/time-picker/TimePicker.vue +33 -38
- package/src/components/ui/time-picker/index.ts +2 -2
- package/src/components/ui/tooltip/Tooltip.vue +12 -9
- package/src/components/ui/tooltip/TooltipContent.vue +28 -20
- package/src/components/ui/tooltip/TooltipProvider.vue +9 -8
- package/src/components/ui/tooltip/TooltipTrigger.vue +8 -7
- package/src/components/ui/tooltip/index.ts +4 -4
- package/src/i18n/de.json +5 -0
- package/src/i18n/en.json +5 -0
- package/src/i18n/es.json +5 -0
- package/src/i18n/fr.json +5 -0
- package/src/i18n/it.json +5 -0
- package/src/i18n/ja.json +5 -0
- package/src/i18n/pt.json +5 -0
- package/src/i18n/ru.json +5 -0
- package/src/i18n/zh.json +5 -0
- package/src/lib/common-types.ts +2 -0
- package/src/lib/i18n.ts +0 -5
- package/src/runtime/{ui-vintage-i18n-plugin.js → ui-vintage-i18n-plugin.ts} +30 -8
|
@@ -1,19 +1,22 @@
|
|
|
1
|
-
<script setup>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ToneColor } from '../../lib/color-tokens';
|
|
2
3
|
import { Badge } from '../ui/badge';
|
|
3
4
|
|
|
4
5
|
// Props
|
|
5
|
-
const props = defineProps
|
|
6
|
-
badges
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
title:
|
|
11
|
-
description:
|
|
6
|
+
const props = withDefaults(defineProps<{
|
|
7
|
+
badges?: Array<{
|
|
8
|
+
color: ToneColor;
|
|
9
|
+
text: string;
|
|
10
|
+
}>;
|
|
11
|
+
title: string;
|
|
12
|
+
description: string;
|
|
13
|
+
}>(), {
|
|
14
|
+
badges: () => []
|
|
12
15
|
});
|
|
13
16
|
</script>
|
|
14
17
|
|
|
15
18
|
<template>
|
|
16
|
-
<div class="flex flex-col space-y-6 md
|
|
19
|
+
<div class="flex flex-col space-y-6 md:space-y-8!">
|
|
17
20
|
<!-- Badges -->
|
|
18
21
|
<div v-if="props.badges.length > 0" class="flex flex-wrap gap-3 mb-6 md:mb-8">
|
|
19
22
|
<Badge v-for="badge in props.badges"
|
|
@@ -23,12 +26,12 @@ const props = defineProps({
|
|
|
23
26
|
</div>
|
|
24
27
|
|
|
25
28
|
<!-- Title -->
|
|
26
|
-
<h1 class="text-3xl md:text-6xl font-bold text-
|
|
29
|
+
<h1 class="text-3xl md:text-6xl font-bold text-(--text-primary-light) dark:text-(--text-primary-dark) leading-tight">
|
|
27
30
|
{{ props.title }}
|
|
28
31
|
</h1>
|
|
29
32
|
|
|
30
33
|
<!-- Description -->
|
|
31
|
-
<p class="text-sm md:text-xl text-
|
|
34
|
+
<p class="text-sm md:text-xl text-(--text-secondary-light) dark:text-(--text-secondary-dark) leading-relaxed">
|
|
32
35
|
{{ props.description }}
|
|
33
36
|
</p>
|
|
34
37
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script setup>
|
|
1
|
+
<script setup lang="ts">
|
|
2
2
|
import { NuxtImg } from '#components';
|
|
3
3
|
import { computed } from 'vue';
|
|
4
4
|
import { useI18n } from 'vue-i18n';
|
|
@@ -6,12 +6,14 @@ import { Card, CardContent } from '../ui/card';
|
|
|
6
6
|
import { formatDateLongItalyTimezone } from '../../lib/utils';
|
|
7
7
|
|
|
8
8
|
// Props
|
|
9
|
-
const props = defineProps
|
|
10
|
-
author:
|
|
11
|
-
authorUrl:
|
|
12
|
-
authorImageUrl:
|
|
13
|
-
datePublished:
|
|
14
|
-
dateModified
|
|
9
|
+
const props = withDefaults(defineProps<{
|
|
10
|
+
author: string;
|
|
11
|
+
authorUrl: string;
|
|
12
|
+
authorImageUrl: string;
|
|
13
|
+
datePublished: string;
|
|
14
|
+
dateModified?: string | null;
|
|
15
|
+
}>(), {
|
|
16
|
+
dateModified: null
|
|
15
17
|
});
|
|
16
18
|
|
|
17
19
|
const { t, locale } = useI18n();
|
|
@@ -36,9 +38,9 @@ const formattedModifiedDate = computed(() => formatDateLongItalyTimezone(props.d
|
|
|
36
38
|
|
|
37
39
|
<!-- Author name -->
|
|
38
40
|
<div>
|
|
39
|
-
<p class="text-sm text-
|
|
40
|
-
<p class="font-bold text-
|
|
41
|
-
<NuxtLink :to="props.authorUrl" target="_blank" rel="noopener noreferrer" class="hover:text-
|
|
41
|
+
<p class="text-sm text-(--text-secondary-light) dark:text-(--text-secondary-dark)">{{ t('uiVintage.blog.author') }}</p>
|
|
42
|
+
<p class="font-bold text-(--text-primary-light) dark:text-(--text-primary-dark)">
|
|
43
|
+
<NuxtLink :to="props.authorUrl" target="_blank" rel="noopener noreferrer" class="hover:text-(--text-secondary-light) dark:hover:text-(--text-secondary-dark) transition-colors">
|
|
42
44
|
{{ props.author }}
|
|
43
45
|
</NuxtLink>
|
|
44
46
|
</p>
|
|
@@ -47,7 +49,7 @@ const formattedModifiedDate = computed(() => formatDateLongItalyTimezone(props.d
|
|
|
47
49
|
</div>
|
|
48
50
|
|
|
49
51
|
<!-- Date information -->
|
|
50
|
-
<div class="flex flex-col sm:items-end text-xs md:text-sm text-
|
|
52
|
+
<div class="flex flex-col sm:items-end text-xs md:text-sm text-(--text-secondary-light) dark:text-(--text-secondary-dark)">
|
|
51
53
|
<!-- Published date -->
|
|
52
54
|
<div v-if="props.datePublished" class="mb-1">
|
|
53
55
|
<span class="font-semibold">{{ t('uiVintage.blog.publishedDate') }}:</span>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { onMounted, onUnmounted, ref } from 'vue';
|
|
3
|
+
import type { HTMLAttributes } from 'vue';
|
|
4
|
+
import { cn } from '../../lib/utils';
|
|
5
|
+
|
|
6
|
+
// Props
|
|
7
|
+
const props = withDefaults(defineProps<{
|
|
8
|
+
class?: HTMLAttributes['class'];
|
|
9
|
+
ariaLabel?: string;
|
|
10
|
+
}>(), {
|
|
11
|
+
ariaLabel: 'Reading progress'
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const progress = ref(0);
|
|
15
|
+
let animationFrame = 0;
|
|
16
|
+
|
|
17
|
+
// Update progress based on scroll position
|
|
18
|
+
const updateProgress = () => {
|
|
19
|
+
const documentHeight = document.documentElement.scrollHeight - window.innerHeight;
|
|
20
|
+
progress.value = documentHeight > 0 ? Math.min(100, Math.max(0, (window.scrollY / documentHeight) * 100)) : 100;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Handle scroll event with requestAnimationFrame for performance
|
|
24
|
+
const handleScroll = () => {
|
|
25
|
+
cancelAnimationFrame(animationFrame);
|
|
26
|
+
animationFrame = requestAnimationFrame(updateProgress);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// On component mounted
|
|
30
|
+
onMounted(() => {
|
|
31
|
+
updateProgress();
|
|
32
|
+
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
33
|
+
window.addEventListener('resize', updateProgress);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// On component unmounted
|
|
37
|
+
onUnmounted(() => {
|
|
38
|
+
cancelAnimationFrame(animationFrame);
|
|
39
|
+
window.removeEventListener('scroll', handleScroll);
|
|
40
|
+
window.removeEventListener('resize', updateProgress);
|
|
41
|
+
});
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<template>
|
|
45
|
+
<div :class="cn('relative h-1 w-full border-t border-(--border-light) bg-(--bg-selected-light) dark:border-(--border-dark) dark:bg-(--bg-selected-dark) landing-navbar-progress', props.class)"
|
|
46
|
+
role="progressbar"
|
|
47
|
+
:aria-label="props.ariaLabel"
|
|
48
|
+
aria-valuemin="0"
|
|
49
|
+
aria-valuemax="100"
|
|
50
|
+
:aria-valuenow="Math.round(progress)">
|
|
51
|
+
<div class="h-full origin-left bg-primary transition-[width] duration-150 ease-out" :style="{ width: `${progress}%` }" />
|
|
52
|
+
</div>
|
|
53
|
+
</template>
|
|
54
|
+
|
|
55
|
+
<style scoped>
|
|
56
|
+
.landing-navbar-progress {
|
|
57
|
+
animation: landing-navbar-progress-fade linear both;
|
|
58
|
+
animation-range: 0 160px;
|
|
59
|
+
animation-timeline: scroll(root);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@keyframes landing-navbar-progress-fade {
|
|
63
|
+
from {
|
|
64
|
+
opacity: 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
to {
|
|
68
|
+
opacity: 1;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@supports not (animation-timeline: scroll()) {
|
|
73
|
+
.landing-navbar-progress {
|
|
74
|
+
opacity: 1;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
</style>
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
<script setup>
|
|
1
|
+
<script setup lang="ts">
|
|
2
2
|
// Props
|
|
3
|
-
const props = defineProps
|
|
4
|
-
title:
|
|
5
|
-
});
|
|
3
|
+
const props = defineProps<{
|
|
4
|
+
title: string;
|
|
5
|
+
}>();
|
|
6
6
|
</script>
|
|
7
7
|
|
|
8
8
|
<template>
|
|
9
|
-
<h2 class="text-xl md:text-2xl font-bold text-
|
|
9
|
+
<h2 class="text-xl md:text-2xl font-bold text-(--text-primary-light) dark:text-(--text-primary-dark) mb-5">
|
|
10
10
|
{{ props.title }}
|
|
11
11
|
</h2>
|
|
12
12
|
</template>
|
|
@@ -1,9 +1,17 @@
|
|
|
1
|
-
<script setup>
|
|
1
|
+
<script setup lang="ts">
|
|
2
2
|
import CategoryCard from './CategoryCard.vue';
|
|
3
3
|
|
|
4
4
|
// Props
|
|
5
|
-
const props = defineProps
|
|
6
|
-
categories
|
|
5
|
+
const props = withDefaults(defineProps<{
|
|
6
|
+
categories?: Array<{
|
|
7
|
+
path: string;
|
|
8
|
+
slug?: string;
|
|
9
|
+
name: string;
|
|
10
|
+
image?: string;
|
|
11
|
+
count: number;
|
|
12
|
+
}>;
|
|
13
|
+
}>(), {
|
|
14
|
+
categories: () => []
|
|
7
15
|
});
|
|
8
16
|
</script>
|
|
9
17
|
|
|
@@ -1,9 +1,17 @@
|
|
|
1
|
-
<script setup>
|
|
1
|
+
<script setup lang="ts">
|
|
2
2
|
import CategoriesList from './CategoriesList.vue';
|
|
3
3
|
|
|
4
4
|
// Props
|
|
5
|
-
const props = defineProps
|
|
6
|
-
categories
|
|
5
|
+
const props = withDefaults(defineProps<{
|
|
6
|
+
categories?: Array<{
|
|
7
|
+
path: string;
|
|
8
|
+
slug?: string;
|
|
9
|
+
name: string;
|
|
10
|
+
image?: string;
|
|
11
|
+
count: number;
|
|
12
|
+
}>;
|
|
13
|
+
}>(), {
|
|
14
|
+
categories: () => []
|
|
7
15
|
});
|
|
8
16
|
</script>
|
|
9
17
|
|
|
@@ -1,19 +1,21 @@
|
|
|
1
|
-
<script setup>
|
|
1
|
+
<script setup lang="ts">
|
|
2
2
|
import { NuxtImg } from '#components';
|
|
3
3
|
import { Badge } from '../ui/badge';
|
|
4
4
|
import { Card, CardContent } from '../ui/card';
|
|
5
5
|
|
|
6
6
|
// Props
|
|
7
|
-
const props = defineProps
|
|
8
|
-
name:
|
|
9
|
-
image
|
|
10
|
-
count:
|
|
7
|
+
const props = withDefaults(defineProps<{
|
|
8
|
+
name: string;
|
|
9
|
+
image?: string;
|
|
10
|
+
count: number;
|
|
11
|
+
}>(), {
|
|
12
|
+
image: ''
|
|
11
13
|
});
|
|
12
14
|
</script>
|
|
13
15
|
|
|
14
16
|
<template>
|
|
15
|
-
<Card class="
|
|
16
|
-
<CardContent class="gap-0
|
|
17
|
+
<Card class="p-0! overflow-hidden group cursor-pointer transition-transform duration-300 ease-out hover:-translate-y-1 motion-reduce:transition-none motion-reduce:hover:translate-y-0">
|
|
18
|
+
<CardContent class="gap-0 p-0!">
|
|
17
19
|
<!-- Background Image -->
|
|
18
20
|
<div v-if="props.image" class="relative w-full h-36 overflow-hidden">
|
|
19
21
|
<NuxtImg :src="props.image" :alt="props.name" height="225" width="400" format="avif" quality="35" :sizes="{ 480: '480px', 1280: '400px' }" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110" />
|
|
@@ -22,7 +24,7 @@ const props = defineProps({
|
|
|
22
24
|
<!-- Content -->
|
|
23
25
|
<div class="p-4 flex items-center justify-between">
|
|
24
26
|
<!-- Name -->
|
|
25
|
-
<span class="text-sm font-bold text-
|
|
27
|
+
<span class="text-sm font-bold text-(--text-primary-light) dark:text-(--text-primary-dark) truncate mr-3">
|
|
26
28
|
{{ props.name }}
|
|
27
29
|
</span>
|
|
28
30
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script setup>
|
|
1
|
+
<script setup lang="ts">
|
|
2
2
|
import { NuxtImg } from '#components';
|
|
3
3
|
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
|
4
4
|
import { useI18n } from 'vue-i18n';
|
|
@@ -11,15 +11,24 @@ import { Card, CardContent } from '../ui/card';
|
|
|
11
11
|
const { t } = useI18n();
|
|
12
12
|
|
|
13
13
|
// Props
|
|
14
|
-
const props = defineProps
|
|
15
|
-
featuredPosts
|
|
14
|
+
const props = withDefaults(defineProps<{
|
|
15
|
+
featuredPosts?: Array<{
|
|
16
|
+
path: string;
|
|
17
|
+
title: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
image?: string;
|
|
20
|
+
categoryText?: string;
|
|
21
|
+
categoryPath?: string;
|
|
22
|
+
}>;
|
|
23
|
+
}>(), {
|
|
24
|
+
featuredPosts: () => []
|
|
16
25
|
});
|
|
17
26
|
|
|
18
27
|
// Carousel state
|
|
19
28
|
const currentSlide = ref(0);
|
|
20
29
|
const slideCount = computed(() => props.featuredPosts.length || 0);
|
|
21
|
-
const intervalId = ref(null);
|
|
22
|
-
const direction = ref('forward');
|
|
30
|
+
const intervalId = ref<ReturnType<typeof setInterval> | null>(null);
|
|
31
|
+
const direction = ref<'forward' | 'backward'>('forward');
|
|
23
32
|
|
|
24
33
|
// Start auto-play functionality
|
|
25
34
|
const startAutoPlay = () => {
|
|
@@ -44,7 +53,7 @@ const prevSlide = () => {
|
|
|
44
53
|
};
|
|
45
54
|
|
|
46
55
|
// Go to specific slide
|
|
47
|
-
const goToSlide = index => {
|
|
56
|
+
const goToSlide = (index: number) => {
|
|
48
57
|
// Determine direction
|
|
49
58
|
if (index > currentSlide.value) {
|
|
50
59
|
direction.value = 'forward';
|
|
@@ -73,8 +82,8 @@ onUnmounted(() => {
|
|
|
73
82
|
<div class="relative overflow-hidden rounded">
|
|
74
83
|
<TransitionGroup name="slide">
|
|
75
84
|
<div v-for="(post, index) in props.featuredPosts" :id="`carousel-slide-${index}`" :key="post.path || post.title || index" v-show="currentSlide === index" class="w-full" :data-direction="direction">
|
|
76
|
-
<Card class="
|
|
77
|
-
<CardContent class="gap-0
|
|
85
|
+
<Card class="p-0! overflow-hidden cursor-pointer">
|
|
86
|
+
<CardContent class="gap-0 p-0!">
|
|
78
87
|
<!-- Slide image -->
|
|
79
88
|
<NuxtLink :to="post.path">
|
|
80
89
|
<div class="relative w-full h-56 md:h-80 overflow-hidden">
|
|
@@ -91,13 +100,13 @@ onUnmounted(() => {
|
|
|
91
100
|
|
|
92
101
|
<!-- Title -->
|
|
93
102
|
<NuxtLink :to="post.path">
|
|
94
|
-
<h3 class="text-base md:text-3xl font-bold text-
|
|
103
|
+
<h3 class="text-base md:text-3xl font-bold text-(--text-primary-light) dark:text-(--text-primary-dark) mb-3 line-clamp-2 hover:opacity-75 transition-opacity duration-200">
|
|
95
104
|
{{ post.title }}
|
|
96
105
|
</h3>
|
|
97
106
|
</NuxtLink>
|
|
98
107
|
|
|
99
108
|
<!-- Description -->
|
|
100
|
-
<p class="text-xs md
|
|
109
|
+
<p class="text-xs md:text-base! text-(--text-secondary-light) dark:text-(--text-secondary-dark) mb-6 line-clamp-2 leading-relaxed">
|
|
101
110
|
{{ post.description }}
|
|
102
111
|
</p>
|
|
103
112
|
|
|
@@ -116,7 +125,7 @@ onUnmounted(() => {
|
|
|
116
125
|
<button v-for="(_, idx) in props.featuredPosts"
|
|
117
126
|
:key="idx"
|
|
118
127
|
@click="goToSlide(idx)"
|
|
119
|
-
:class="['h-5 w-5 sm
|
|
128
|
+
:class="['h-5 w-5 sm:h-4! sm:w-4! shrink-0 rounded-[2px] transition-all duration-300 cursor-pointer bg-(--border-light) dark:bg-(--border-dark)', currentSlide === idx ? 'w-8! sm:w-7! bg-(--text-primary-light)! dark:bg-(--text-primary-dark)!' : '']"
|
|
120
129
|
:aria-label="t('uiVintage.blog.goToSlide', { index: idx + 1 })"
|
|
121
130
|
:aria-current="currentSlide === idx"
|
|
122
131
|
role="tab"
|
|
@@ -135,16 +144,16 @@ onUnmounted(() => {
|
|
|
135
144
|
<button v-if="props.featuredPosts.length > 1"
|
|
136
145
|
@click="prevSlide"
|
|
137
146
|
:aria-label="t('uiVintage.blog.previousSlide')"
|
|
138
|
-
class="absolute left-2 top-[calc(50%-4rem)] -translate-y-1/2 z-10 border border-
|
|
139
|
-
<HugeiconsIcon :icon="ArrowLeft01Icon" class="size-4 text-
|
|
147
|
+
class="absolute left-2 top-[calc(50%-4rem)] -translate-y-1/2 z-10 border border-(--border-light) dark:border-(--border-dark) bg-(--bg-card-light) dark:bg-(--bg-card-dark) px-3 py-3 rounded-[3px] transition-colors duration-200 hover:border-(--border-hover-light) dark:hover:border-(--border-hover-dark) cursor-pointer">
|
|
148
|
+
<HugeiconsIcon :icon="ArrowLeft01Icon" class="size-4 text-(--text-secondary-light) dark:text-(--text-secondary-dark)" />
|
|
140
149
|
</button>
|
|
141
150
|
|
|
142
151
|
<!-- Next slide -->
|
|
143
152
|
<button v-if="props.featuredPosts.length > 1"
|
|
144
153
|
@click="nextSlide"
|
|
145
154
|
:aria-label="t('uiVintage.blog.nextSlide')"
|
|
146
|
-
class="absolute right-2 top-[calc(50%-4rem)] -translate-y-1/2 z-10 border border-
|
|
147
|
-
<HugeiconsIcon :icon="ArrowRight01Icon" class="size-4 text-
|
|
155
|
+
class="absolute right-2 top-[calc(50%-4rem)] -translate-y-1/2 z-10 border border-(--border-light) dark:border-(--border-dark) bg-(--bg-card-light) dark:bg-(--bg-card-dark) px-3 py-3 rounded-[3px] transition-colors duration-200 hover:border-(--border-hover-light) dark:hover:border-(--border-hover-dark) cursor-pointer">
|
|
156
|
+
<HugeiconsIcon :icon="ArrowRight01Icon" class="size-4 text-(--text-secondary-light) dark:text-(--text-secondary-dark)" />
|
|
148
157
|
</button>
|
|
149
158
|
</div>
|
|
150
159
|
</template>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script setup>
|
|
1
|
+
<script setup lang="ts">
|
|
2
2
|
import { NuxtImg } from '#components';
|
|
3
3
|
import { useI18n } from 'vue-i18n';
|
|
4
4
|
import { ArrowRight01Icon } from '@hugeicons/core-free-icons';
|
|
@@ -10,36 +10,41 @@ import { Card, CardContent } from '../ui/card';
|
|
|
10
10
|
const { t } = useI18n();
|
|
11
11
|
|
|
12
12
|
// Props
|
|
13
|
-
const props = defineProps
|
|
14
|
-
image
|
|
15
|
-
category
|
|
16
|
-
title
|
|
17
|
-
description
|
|
13
|
+
const props = withDefaults(defineProps<{
|
|
14
|
+
image?: string;
|
|
15
|
+
category?: string;
|
|
16
|
+
title?: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
}>(), {
|
|
19
|
+
image: '',
|
|
20
|
+
category: '',
|
|
21
|
+
title: '',
|
|
22
|
+
description: ''
|
|
18
23
|
});
|
|
19
24
|
</script>
|
|
20
25
|
|
|
21
26
|
<template>
|
|
22
|
-
<Card class="
|
|
23
|
-
<CardContent class="h-full gap-0
|
|
27
|
+
<Card class="p-0! overflow-hidden group cursor-pointer transition-transform duration-300 ease-out hover:-translate-y-1 motion-reduce:transition-none motion-reduce:hover:translate-y-0">
|
|
28
|
+
<CardContent class="h-full gap-0 p-0!">
|
|
24
29
|
<!-- Featured image -->
|
|
25
30
|
<div v-if="props.image" class="relative w-full h-48 overflow-hidden">
|
|
26
31
|
<NuxtImg :src="props.image" :alt="props.title" height="225" width="400" format="avif" quality="35" :sizes="{ 480: '480px', 1280: '400px' }" loading="lazy" decoding="async" class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110" />
|
|
27
32
|
</div>
|
|
28
33
|
|
|
29
34
|
<!-- Content -->
|
|
30
|
-
<div class="p-6 flex flex-col
|
|
35
|
+
<div class="p-6 flex flex-col grow">
|
|
31
36
|
<!-- Category Badge -->
|
|
32
37
|
<div v-if="props.category" class="mb-3">
|
|
33
38
|
<Badge color="gray" :text="props.category" />
|
|
34
39
|
</div>
|
|
35
40
|
|
|
36
41
|
<!-- Post Title -->
|
|
37
|
-
<h2 class="text-base font-bold text-
|
|
42
|
+
<h2 class="text-base font-bold text-(--text-primary-light) dark:text-(--text-primary-dark) mb-2 line-clamp-2">
|
|
38
43
|
{{ props.title }}
|
|
39
44
|
</h2>
|
|
40
45
|
|
|
41
46
|
<!-- Post Description -->
|
|
42
|
-
<p class="text-xs md:text-sm text-
|
|
47
|
+
<p class="text-xs md:text-sm text-(--text-secondary-light) dark:text-(--text-secondary-dark) leading-relaxed mb-4 line-clamp-3 grow">
|
|
43
48
|
{{ props.description }}
|
|
44
49
|
</p>
|
|
45
50
|
|
|
@@ -1,9 +1,18 @@
|
|
|
1
|
-
<script setup>
|
|
1
|
+
<script setup lang="ts">
|
|
2
2
|
import PostCard from './PostCard.vue';
|
|
3
3
|
|
|
4
4
|
// Props
|
|
5
|
-
const props = defineProps
|
|
6
|
-
posts
|
|
5
|
+
const props = withDefaults(defineProps<{
|
|
6
|
+
posts?: Array<{
|
|
7
|
+
path: string;
|
|
8
|
+
title: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
image?: string;
|
|
11
|
+
categoryText?: string;
|
|
12
|
+
categoryPath?: string;
|
|
13
|
+
}>;
|
|
14
|
+
}>(), {
|
|
15
|
+
posts: () => []
|
|
7
16
|
});
|
|
8
17
|
</script>
|
|
9
18
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script setup>
|
|
1
|
+
<script setup lang="ts">
|
|
2
2
|
import { computed } from 'vue';
|
|
3
3
|
import { useI18n } from 'vue-i18n';
|
|
4
4
|
import { Facebook01Icon, Linkedin01Icon, NewTwitterIcon, Share08Icon, TelegramIcon, WhatsappIcon } from '@hugeicons/core-free-icons';
|
|
@@ -6,10 +6,10 @@ import { HugeiconsIcon } from '@hugeicons/vue';
|
|
|
6
6
|
import { Card, CardContent } from '../ui/card';
|
|
7
7
|
|
|
8
8
|
// Props
|
|
9
|
-
const props = defineProps
|
|
10
|
-
title:
|
|
11
|
-
url:
|
|
12
|
-
});
|
|
9
|
+
const props = defineProps<{
|
|
10
|
+
title: string;
|
|
11
|
+
url: string;
|
|
12
|
+
}>();
|
|
13
13
|
|
|
14
14
|
const { t } = useI18n();
|
|
15
15
|
|
|
@@ -53,42 +53,44 @@ const socialPlatforms = computed(() => {
|
|
|
53
53
|
</script>
|
|
54
54
|
|
|
55
55
|
<template>
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
<
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
<Teleport to="body">
|
|
57
|
+
<!-- Desktop sidebar on the right -->
|
|
58
|
+
<div class="fixed right-[calc(50%-600px)] top-35 z-20 hidden xl:block">
|
|
59
|
+
<Card class="p-5! flex flex-col space-y-4 items-center">
|
|
60
|
+
<CardContent class="p-0! gap-4 items-center">
|
|
61
|
+
<!-- Share icon -->
|
|
62
|
+
<HugeiconsIcon :icon="Share08Icon" class="size-5 text-(--text-secondary-light) dark:text-(--text-secondary-dark) mb-2" />
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
64
|
+
<!-- Social share links (desktop) -->
|
|
65
|
+
<template v-for="platform in socialPlatforms" :key="platform.name">
|
|
66
|
+
<NuxtLink :to="platform.url"
|
|
67
|
+
target="_blank"
|
|
68
|
+
rel="noopener noreferrer"
|
|
69
|
+
:aria-label="platform.label"
|
|
70
|
+
:class="['w-10 h-10 rounded flex items-center justify-center text-white transition-all duration-200 hover:opacity-80 hover:scale-105', platform.color]">
|
|
71
|
+
<HugeiconsIcon :icon="platform.icon" class="size-4" />
|
|
72
|
+
</NuxtLink>
|
|
73
|
+
</template>
|
|
74
|
+
</CardContent>
|
|
75
|
+
</Card>
|
|
76
|
+
</div>
|
|
76
77
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
78
|
+
<!-- Mobile view (floating bar at bottom) -->
|
|
79
|
+
<div class="fixed bottom-4 left-1/2 z-20 -translate-x-1/2 xl:hidden">
|
|
80
|
+
<Card class="p-3! w-fit">
|
|
81
|
+
<CardContent class="flex-row! p-0! items-center justify-center gap-3">
|
|
82
|
+
<!-- Social share links (mobile) -->
|
|
83
|
+
<template v-for="platform in socialPlatforms" :key="platform.name">
|
|
84
|
+
<NuxtLink :to="platform.url"
|
|
85
|
+
target="_blank"
|
|
86
|
+
rel="noopener noreferrer"
|
|
87
|
+
:aria-label="platform.label"
|
|
88
|
+
:class="['w-10 h-10 rounded flex items-center justify-center text-white transition-all duration-200 hover:opacity-80 hover:scale-105', platform.color]">
|
|
89
|
+
<HugeiconsIcon :icon="platform.icon" class="size-4" />
|
|
90
|
+
</NuxtLink>
|
|
91
|
+
</template>
|
|
92
|
+
</CardContent>
|
|
93
|
+
</Card>
|
|
94
|
+
</div>
|
|
95
|
+
</Teleport>
|
|
94
96
|
</template>
|
|
@@ -1,13 +1,29 @@
|
|
|
1
|
-
<script setup>
|
|
1
|
+
<script setup lang="ts">
|
|
2
2
|
import { computed } from 'vue';
|
|
3
3
|
import { useI18n } from 'vue-i18n';
|
|
4
4
|
import { Bookmark01Icon } from '@hugeicons/core-free-icons';
|
|
5
5
|
import { Accordion } from '../ui/accordion';
|
|
6
6
|
|
|
7
|
+
interface BlogTocLink {
|
|
8
|
+
id?: string;
|
|
9
|
+
text?: string;
|
|
10
|
+
depth?: number;
|
|
11
|
+
children?: BlogTocLink[];
|
|
12
|
+
}
|
|
13
|
+
|
|
7
14
|
// Props
|
|
8
|
-
const props = defineProps
|
|
9
|
-
content: {
|
|
10
|
-
|
|
15
|
+
const props = defineProps<{
|
|
16
|
+
content: {
|
|
17
|
+
body?: {
|
|
18
|
+
toc?: {
|
|
19
|
+
links?: BlogTocLink[];
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
toc?: {
|
|
23
|
+
links?: BlogTocLink[];
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
}>();
|
|
11
27
|
|
|
12
28
|
const { t } = useI18n();
|
|
13
29
|
|
|
@@ -16,13 +32,13 @@ const headings = computed(() => {
|
|
|
16
32
|
const tocLinks = props.content?.body?.toc?.links || props.content?.toc?.links || [];
|
|
17
33
|
|
|
18
34
|
// Recursive function to flatten the nested links
|
|
19
|
-
const flattenLinks = links => {
|
|
35
|
+
const flattenLinks = (links: BlogTocLink[]): Array<{ id: string; text: string; level: number }> => {
|
|
20
36
|
return links.flatMap(link => {
|
|
21
37
|
// Extract the current heading
|
|
22
38
|
const currentHeading = {
|
|
23
|
-
id: link.id,
|
|
24
|
-
text: link.text,
|
|
25
|
-
level: link.depth
|
|
39
|
+
id: link.id ?? '',
|
|
40
|
+
text: link.text ?? '',
|
|
41
|
+
level: link.depth ?? 0
|
|
26
42
|
};
|
|
27
43
|
|
|
28
44
|
// Recursively flatten the children links
|
|
@@ -36,7 +52,7 @@ const headings = computed(() => {
|
|
|
36
52
|
});
|
|
37
53
|
|
|
38
54
|
// Smooth scroll to heading
|
|
39
|
-
const scrollToHeading = id => {
|
|
55
|
+
const scrollToHeading = (id: string) => {
|
|
40
56
|
const element = document.getElementById(id);
|
|
41
57
|
if (element) {
|
|
42
58
|
element.scrollIntoView({
|
|
@@ -48,16 +64,16 @@ const scrollToHeading = id => {
|
|
|
48
64
|
};
|
|
49
65
|
|
|
50
66
|
// Get classes for heading button
|
|
51
|
-
const getHeadingButtonClasses =
|
|
67
|
+
const getHeadingButtonClasses = (level: number) => {
|
|
52
68
|
// Base classes for all titles
|
|
53
69
|
const baseClasses = 'block w-full text-left px-0 py-2 md:px-3 md:py-2 rounded transition-all duration-200 hover:bg-(--bg-selected-light) dark:hover:bg-(--bg-selected-dark) cursor-pointer';
|
|
54
70
|
|
|
55
71
|
// Add classes based on heading level
|
|
56
72
|
let levelClasses = '';
|
|
57
|
-
if (
|
|
73
|
+
if (level === 2) {
|
|
58
74
|
levelClasses = 'text-(--text-primary-light) dark:text-(--text-primary-dark) font-semibold text-xs md:text-sm';
|
|
59
75
|
} else {
|
|
60
|
-
levelClasses = 'text-(--text-secondary-light) dark:text-(--text-secondary-dark)
|
|
76
|
+
levelClasses = 'text-(--text-secondary-light) dark:text-(--text-secondary-dark) pl-6! text-xs md:text-sm';
|
|
61
77
|
}
|
|
62
78
|
|
|
63
79
|
// Return combined classes
|
|
@@ -68,7 +84,7 @@ const getHeadingButtonClasses = heading => {
|
|
|
68
84
|
<template>
|
|
69
85
|
<Accordion v-if="headings.length > 0" :title="t('uiVintage.blog.tableOfContents')" :icon="Bookmark01Icon">
|
|
70
86
|
<nav class="space-y-1">
|
|
71
|
-
<button v-for="heading in headings" :key="heading.id" @click="scrollToHeading(heading.id)" :class="getHeadingButtonClasses(heading)">
|
|
87
|
+
<button v-for="heading in headings" :key="heading.id" @click="scrollToHeading(heading.id)" :class="getHeadingButtonClasses(heading.level)">
|
|
72
88
|
{{ heading.text }}
|
|
73
89
|
</button>
|
|
74
90
|
</nav>
|