@lupinum/ginko-docs 0.2.2 → 0.2.3
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 +130 -73
- package/app/app.config.ts +14 -12
- package/app/app.vue +2 -2
- package/app/assets/css/prose.css +2203 -149
- package/app/assets/css/tailwind.css +60 -38
- package/app/assets/css/theme-palettes.css +309 -0
- package/app/components/content/DocumentPageShell.vue +1 -1
- package/app/components/content/Feedback.vue +12 -11
- package/app/components/content/ImageZoomDialog.vue +84 -0
- package/app/components/content/PageMarkdownCopy.vue +2 -0
- package/app/components/content/imageZoom.ts +25 -0
- package/app/components/mdc/MdcAccordion.vue +6 -3
- package/app/components/mdc/MdcAccordionItem.vue +7 -16
- package/app/components/mdc/MdcApi.vue +159 -0
- package/app/components/mdc/MdcAside.vue +6 -10
- package/app/components/mdc/MdcCallout.vue +9 -10
- package/app/components/mdc/MdcCard.vue +36 -47
- package/app/components/mdc/MdcCards.vue +30 -0
- package/app/components/mdc/MdcCodeGroup.vue +4 -0
- package/app/components/mdc/MdcCodeTree.vue +234 -0
- package/app/components/mdc/MdcCollapse.vue +61 -0
- package/app/components/mdc/MdcError.vue +8 -1
- package/app/components/mdc/MdcExcerpt.vue +26 -0
- package/app/components/mdc/MdcFigure.vue +54 -20
- package/app/components/mdc/MdcFiles.vue +120 -0
- package/app/components/mdc/MdcIdea.vue +2 -1
- package/app/components/mdc/MdcInfo.vue +2 -1
- package/app/components/mdc/MdcInlineToc.vue +51 -0
- package/app/components/{ui/dialog/DialogHeader.vue → mdc/MdcKbd.vue} +2 -4
- package/app/components/mdc/MdcNote.vue +2 -1
- package/app/components/mdc/MdcQuiz.vue +43 -83
- package/app/components/mdc/MdcQuizQuestion.vue +104 -109
- package/app/components/mdc/MdcReadMore.vue +57 -77
- package/app/components/mdc/MdcSteps.vue +45 -11
- package/app/components/mdc/MdcSuccess.vue +8 -1
- package/app/components/mdc/MdcTabs.vue +44 -26
- package/app/components/mdc/MdcTimeline.vue +4 -1
- package/app/components/mdc/MdcTimelineItem.vue +18 -27
- package/app/components/mdc/MdcWarning.vue +8 -1
- package/app/components/mdc/api.utils.ts +132 -0
- package/app/components/mdc/code-tree.utils.ts +22 -0
- package/app/components/mdc/quiz.utils.ts +38 -0
- package/app/components/prose/ProseBlockquote.vue +17 -1
- package/app/components/prose/ProseH1.vue +1 -1
- package/app/components/prose/ProseH2.vue +7 -2
- package/app/components/prose/ProseH3.vue +7 -2
- package/app/components/prose/ProseH4.vue +17 -2
- package/app/components/{ui/kbd/KbdGroup.vue → prose/ProseH5.vue} +4 -3
- package/app/components/{ui/card/CardTitle.vue → prose/ProseH6.vue} +4 -3
- package/app/components/prose/ProseImg.vue +19 -112
- package/app/components/prose/ProsePre.vue +63 -100
- package/app/components/prose/ProseTable.vue +5 -3
- package/app/components/site/ModeToggle.vue +12 -3
- package/app/components/site/SiteBanner.vue +43 -2
- package/app/components/site/SiteFooter.vue +3 -5
- package/app/components/site/SiteHeader.vue +255 -132
- package/app/components/site/SiteHeroCode.vue +164 -0
- package/app/components/site/SiteLocaleSwitcher.vue +22 -4
- package/app/components/ui/badge/Badge.vue +1 -1
- package/app/components/ui/button/Button.vue +1 -1
- package/app/components/ui/collapsible/CollapsibleContent.vue +1 -1
- package/app/components/ui/dropdown-menu/DropdownMenuContent.vue +14 -4
- package/app/components/ui/dropdown-menu/DropdownMenuItem.vue +1 -1
- package/app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +1 -1
- package/app/components/ui/dropdown-menu/DropdownMenuSeparator.vue +1 -1
- package/app/components/ui/dropdown-menu/index.ts +0 -9
- package/app/components/ui/kbd/Kbd.vue +1 -1
- package/app/components/ui/kbd/index.ts +0 -1
- package/app/components/ui/scroll-area/ScrollArea.vue +1 -1
- package/app/components/ui/scroll-area/ScrollBar.vue +1 -1
- package/app/components/ui/separator/Separator.vue +1 -1
- package/app/components/ui/sheet/SheetContent.vue +1 -1
- package/app/components/ui/sheet/SheetDescription.vue +1 -1
- package/app/components/ui/sheet/SheetOverlay.vue +1 -1
- package/app/components/ui/sheet/SheetTitle.vue +1 -1
- package/app/components/ui/sheet/index.ts +0 -3
- package/app/components/ui/tabs/Tabs.vue +1 -1
- package/app/components/ui/tabs/TabsList.vue +1 -1
- package/app/components/ui/tabs/TabsTrigger.vue +1 -1
- package/app/components/ui/tabs/index.ts +0 -1
- package/app/composables/site-navigation.utils.ts +29 -11
- package/app/composables/useContentRouteAlternates.ts +13 -21
- package/app/composables/useLocalizedRouteSwitch.ts +1 -1
- package/app/composables/useMetaKey.ts +18 -0
- package/app/composables/useProseAppearance.ts +24 -0
- package/app/composables/useSiteNavigation.ts +3 -18
- package/app/config/site.utils.ts +1 -1
- package/app/error.vue +1 -5
- package/app/features/docs/components/DocsBreadcrumb.vue +37 -0
- package/app/features/docs/components/DocsContributeLinks.vue +28 -18
- package/app/features/docs/components/DocsMobileToc.vue +12 -8
- package/app/features/docs/components/DocsPageContent.vue +84 -47
- package/app/features/docs/components/DocsPageNav.vue +1 -1
- package/app/features/docs/components/DocsSidebar.vue +30 -20
- package/app/features/docs/components/DocsSidebarItem.vue +53 -100
- package/app/features/docs/components/DocsSidebarRow.vue +27 -0
- package/app/features/docs/components/DocsSidebarTabs.vue +46 -22
- package/app/features/docs/components/DocsToc.vue +80 -34
- package/app/features/docs/composables/useDocsNavigation.ts +2 -2
- package/app/features/docs/composables/useDocsNavigationData.ts +4 -1
- package/app/features/docs/composables/useRevealActive.ts +58 -0
- package/app/features/docs/composables/useScrollspy.ts +87 -19
- package/app/features/docs/docs-navigation.ts +17 -30
- package/app/features/docs/toc-context.ts +6 -0
- package/app/features/search/command-center.ts +11 -90
- package/app/features/search/components/SiteCommandCenter.vue +58 -38
- package/app/features/search/useCommandCenter.ts +46 -46
- package/app/layouts/docs.vue +1 -1
- package/app/lib/errors.ts +2 -49
- package/app/pages/blog/[slug].vue +16 -8
- package/app/pages/blog/index.vue +26 -13
- package/app/pages/docs/index.vue +10 -14
- package/app/pages/index.vue +174 -59
- package/app/plugins/ginko-docs-theme.ts +12 -0
- package/app/utils/content.ts +11 -0
- package/app/utils/file-icons.ts +90 -0
- package/app/utils/index.ts +7 -1
- package/app/utils/repository.ts +20 -0
- package/content.js +23 -26
- package/content.ts +30 -28
- package/i18n/messages/global/blog.ts +4 -0
- package/i18n/messages/global/command.ts +6 -2
- package/i18n/messages/global/docs.ts +3 -0
- package/i18n/messages/global/nav.ts +6 -2
- package/i18n/messages/index.ts +3 -3
- package/icon-bundle.ts +5 -0
- package/index.d.ts +7 -1
- package/nuxt.config.ts +2 -2
- package/package.json +9 -6
- package/runtime/server/agent-markdown.ts +0 -2
- package/shared/theme-palettes.ts +37 -0
- package/shared/types/app-config.ts +63 -6
- package/tags.ts +47 -59
- package/app/components/mdc/MdcAlert.vue +0 -34
- package/app/components/mdc/MdcCardGroup.vue +0 -47
- package/app/components/mdc/MdcDocImg.vue +0 -25
- package/app/components/mdc/MdcField.vue +0 -62
- package/app/components/mdc/MdcFieldGroup.vue +0 -18
- package/app/components/mdc/MdcPassage.vue +0 -32
- package/app/components/mdc/MdcQuizOption.vue +0 -91
- package/app/components/mdc/MdcReadMoreGroup.vue +0 -18
- package/app/components/mdc/MdcShortcut.vue +0 -46
- package/app/components/mdc/MdcStep.vue +0 -42
- package/app/components/ui/accordion/Accordion.vue +0 -15
- package/app/components/ui/accordion/AccordionContent.vue +0 -23
- package/app/components/ui/accordion/AccordionItem.vue +0 -24
- package/app/components/ui/accordion/AccordionTrigger.vue +0 -35
- package/app/components/ui/accordion/index.ts +0 -4
- package/app/components/ui/alert/Alert.vue +0 -21
- package/app/components/ui/alert/AlertDescription.vue +0 -22
- package/app/components/ui/alert/AlertTitle.vue +0 -22
- package/app/components/ui/alert/index.ts +0 -34
- package/app/components/ui/card/Card.vue +0 -16
- package/app/components/ui/card/CardAction.vue +0 -17
- package/app/components/ui/card/CardContent.vue +0 -14
- package/app/components/ui/card/CardDescription.vue +0 -14
- package/app/components/ui/card/CardFooter.vue +0 -14
- package/app/components/ui/card/CardHeader.vue +0 -22
- package/app/components/ui/card/card-variants.ts +0 -15
- package/app/components/ui/card/index.ts +0 -8
- package/app/components/ui/checkbox/Checkbox.vue +0 -35
- package/app/components/ui/checkbox/index.ts +0 -1
- package/app/components/ui/dialog/Dialog.vue +0 -15
- package/app/components/ui/dialog/DialogClose.vue +0 -12
- package/app/components/ui/dialog/DialogContent.vue +0 -69
- package/app/components/ui/dialog/DialogDescription.vue +0 -21
- package/app/components/ui/dialog/DialogFooter.vue +0 -15
- package/app/components/ui/dialog/DialogOverlay.vue +0 -26
- package/app/components/ui/dialog/DialogPortal.vue +0 -12
- package/app/components/ui/dialog/DialogTitle.vue +0 -21
- package/app/components/ui/dialog/DialogTrigger.vue +0 -12
- package/app/components/ui/dialog/index.ts +0 -10
- package/app/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +0 -36
- package/app/components/ui/dropdown-menu/DropdownMenuGroup.vue +0 -12
- package/app/components/ui/dropdown-menu/DropdownMenuLabel.vue +0 -25
- package/app/components/ui/dropdown-menu/DropdownMenuShortcut.vue +0 -17
- package/app/components/ui/dropdown-menu/DropdownMenuSub.vue +0 -15
- package/app/components/ui/dropdown-menu/DropdownMenuSubContent.vue +0 -29
- package/app/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +0 -31
- package/app/components/ui/input/Input.vue +0 -23
- package/app/components/ui/input/index.ts +0 -1
- package/app/components/ui/label/Label.vue +0 -14
- package/app/components/ui/label/index.ts +0 -1
- package/app/components/ui/sheet/SheetClose.vue +0 -12
- package/app/components/ui/sheet/SheetFooter.vue +0 -12
- package/app/components/ui/sheet/SheetHeader.vue +0 -12
- package/app/components/ui/switch/Switch.vue +0 -39
- package/app/components/ui/switch/index.ts +0 -1
- package/app/components/ui/tabs/TabsContent.vue +0 -21
- package/app/components/ui/textarea/Textarea.vue +0 -31
- package/app/components/ui/textarea/index.ts +0 -1
- package/app/features/docs/composables/useDocsEntryPath.ts +0 -13
- package/app/lib/utils.ts +0 -7
- package/i18n/messages/source.ts +0 -5
- package/runtime/markdown/inline-code-highlight.ts +0 -155
- package/runtime/utils.ts +0 -7
|
@@ -8,14 +8,14 @@ import {
|
|
|
8
8
|
SheetTitle,
|
|
9
9
|
SheetTrigger,
|
|
10
10
|
} from "#ginko-docs/components/ui/sheet";
|
|
11
|
-
import { cn } from "#ginko-docs/
|
|
12
|
-
import type {
|
|
11
|
+
import { cn } from "#ginko-docs/utils";
|
|
12
|
+
import type { FlatTocItem } from "#ginko-docs/utils/content";
|
|
13
13
|
import DocsToc from "./DocsToc.vue";
|
|
14
14
|
import { useI18n, useRoute } from "#imports";
|
|
15
15
|
|
|
16
16
|
const props = defineProps<{
|
|
17
|
-
items:
|
|
18
|
-
|
|
17
|
+
items: FlatTocItem[];
|
|
18
|
+
activeIds?: string[];
|
|
19
19
|
pageTitle?: string;
|
|
20
20
|
class?: HTMLAttributes["class"];
|
|
21
21
|
}>();
|
|
@@ -53,7 +53,7 @@ watch(
|
|
|
53
53
|
<SheetTrigger as-child>
|
|
54
54
|
<button
|
|
55
55
|
type="button"
|
|
56
|
-
class="-ml-1 flex h-8 min-w-0 items-center gap-2 rounded-lg px-2 text-sm font-medium text-foreground transition-colors hover:bg-muted"
|
|
56
|
+
class="-ml-1 flex h-8 min-w-0 items-center gap-2 rounded-lg px-2 text-sm font-medium text-foreground transition-colors hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
|
|
57
57
|
:aria-label="t('docs.pageNavigation')"
|
|
58
58
|
>
|
|
59
59
|
<Icon
|
|
@@ -65,7 +65,10 @@ watch(
|
|
|
65
65
|
</button>
|
|
66
66
|
</SheetTrigger>
|
|
67
67
|
|
|
68
|
-
<SheetContent
|
|
68
|
+
<SheetContent
|
|
69
|
+
side="left"
|
|
70
|
+
class="flex w-[var(--docs-sidebar-width)] max-w-[85vw] flex-col gap-0 p-0"
|
|
71
|
+
>
|
|
69
72
|
<div class="border-b border-border px-4 py-3">
|
|
70
73
|
<SheetTitle class="text-sm font-semibold">{{ t("docs.label") }}</SheetTitle>
|
|
71
74
|
<SheetDescription class="sr-only">{{ t("docs.pageNavigation") }}</SheetDescription>
|
|
@@ -79,7 +82,8 @@ watch(
|
|
|
79
82
|
<Sheet v-if="items.length" v-model:open="tocOpen">
|
|
80
83
|
<SheetTrigger as-child>
|
|
81
84
|
<button
|
|
82
|
-
|
|
85
|
+
type="button"
|
|
86
|
+
class="-mr-1 flex h-7 shrink-0 items-center gap-1.5 rounded-md px-2.5 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
|
|
83
87
|
>
|
|
84
88
|
<Icon name="lucide:list" class="size-3.5" aria-hidden="true" />
|
|
85
89
|
<span>{{ t("docs.toc") }}</span>
|
|
@@ -96,7 +100,7 @@ watch(
|
|
|
96
100
|
</div>
|
|
97
101
|
|
|
98
102
|
<div class="flex-1 overflow-y-auto px-6 py-5" @click="tocOpen = false">
|
|
99
|
-
<DocsToc :items="items" :active-
|
|
103
|
+
<DocsToc :items="items" :active-ids="activeIds" :show-title="false" />
|
|
100
104
|
</div>
|
|
101
105
|
</SheetContent>
|
|
102
106
|
</Sheet>
|
|
@@ -1,23 +1,33 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
import { filterTocByDepth, flattenTocLinks } from "#ginko-docs/utils/content";
|
|
2
|
+
import { filterTocByDepth, flattenTocLinks, getMarkdownTocLinks } from "#ginko-docs/utils/content";
|
|
4
3
|
import ContentFeedback from "#ginko-docs/components/content/Feedback.vue";
|
|
4
|
+
import DocsBreadcrumb from "./DocsBreadcrumb.vue";
|
|
5
5
|
import DocsMobileToc from "./DocsMobileToc.vue";
|
|
6
6
|
import DocsPageNav from "./DocsPageNav.vue";
|
|
7
7
|
import DocsToc from "./DocsToc.vue";
|
|
8
8
|
import DocsContributeLinks from "./DocsContributeLinks.vue";
|
|
9
|
-
import { computed, watch } from "vue";
|
|
10
|
-
import {
|
|
9
|
+
import { computed, provide, ref, watch } from "vue";
|
|
10
|
+
import { docsTocKey } from "#ginko-docs/features/docs/toc-context";
|
|
11
|
+
import {
|
|
12
|
+
createError,
|
|
13
|
+
useAppConfig,
|
|
14
|
+
useContentPage,
|
|
15
|
+
useHead,
|
|
16
|
+
useI18n,
|
|
17
|
+
useRoute,
|
|
18
|
+
useSeoMeta,
|
|
19
|
+
} from "#imports";
|
|
11
20
|
import { useCanonicalUrl } from "#ginko-docs/composables/useCanonicalUrl";
|
|
12
21
|
import { useGinkoOgImage } from "#ginko-docs/composables/useGinkoOgImage";
|
|
13
22
|
import { useScrollspy } from "#ginko-docs/features/docs/composables/useScrollspy";
|
|
23
|
+
import { useRevealActive } from "#ginko-docs/features/docs/composables/useRevealActive";
|
|
14
24
|
import PageMarkdownCopy from "#ginko-docs/components/content/PageMarkdownCopy.vue";
|
|
15
25
|
import { Separator } from "#ginko-docs/components/ui/separator";
|
|
16
26
|
import { getLocalizedSiteText } from "#ginko-docs/config/site.utils";
|
|
17
27
|
import { useSchemaJsonLd } from "#ginko-docs/composables/useSchemaJsonLd";
|
|
18
28
|
import { useLocalizedPath } from "#ginko-docs/composables/useLocalizedPath";
|
|
19
29
|
import { useDocsNavigation } from "#ginko-docs/features/docs/composables/useDocsNavigation";
|
|
20
|
-
import {
|
|
30
|
+
import { useContentRouteAlternates } from "#ginko-docs/composables/useContentRouteAlternates";
|
|
21
31
|
|
|
22
32
|
type DocsNavLink = {
|
|
23
33
|
path: string;
|
|
@@ -34,25 +44,26 @@ const { locale, t } = useI18n();
|
|
|
34
44
|
const config = useAppConfig().ginkoDocs;
|
|
35
45
|
const route = useRoute();
|
|
36
46
|
const localizedPath = useLocalizedPath();
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
next: nextContent,
|
|
41
|
-
} = await useContentPage("docs", {
|
|
42
|
-
locale: () => locale.value,
|
|
47
|
+
const canonicalUrl = useCanonicalUrl();
|
|
48
|
+
const { sync: syncContentRouteAlternates } = useContentRouteAlternates();
|
|
49
|
+
const contentPageResult = useContentPage("docs", {
|
|
43
50
|
fallback: true,
|
|
44
51
|
surround: true,
|
|
45
52
|
});
|
|
46
|
-
|
|
53
|
+
const docsNavigationResult = useDocsNavigation();
|
|
54
|
+
const [{ page, previous, next: nextContent, error }, { trail }] = await Promise.all([
|
|
55
|
+
contentPageResult,
|
|
56
|
+
docsNavigationResult,
|
|
57
|
+
]);
|
|
58
|
+
if (error.value) throw error.value;
|
|
59
|
+
if (!page.value) throw createError({ statusCode: 404, statusMessage: "Not Found" });
|
|
47
60
|
syncContentRouteAlternates(page);
|
|
48
|
-
const { trail } = await useDocsNavigation();
|
|
49
61
|
|
|
50
62
|
const siteName = computed(() => getLocalizedSiteText(config.site.name, locale.value));
|
|
51
63
|
const pageTitle = computed(() => page.value?.title ?? t("docs.fallbackTitle"));
|
|
52
64
|
const pageDescription = computed(() => page.value?.description ?? t("docs.fallbackDescription"));
|
|
53
|
-
const canonicalUrl = useCanonicalUrl();
|
|
54
65
|
const tocItems = computed(() =>
|
|
55
|
-
filterTocByDepth(flattenTocLinks(page.value?.body
|
|
66
|
+
filterTocByDepth(flattenTocLinks(getMarkdownTocLinks(page.value?.body)), config.toc?.depth ?? 3),
|
|
56
67
|
);
|
|
57
68
|
const prev = computed(() => toDocsNavLink(previous.value));
|
|
58
69
|
const next = computed(() => toDocsNavLink(nextContent.value));
|
|
@@ -63,12 +74,24 @@ const schemaBreadcrumbs = computed(() => [
|
|
|
63
74
|
.filter((item) => item.path)
|
|
64
75
|
.map((item) => ({ name: item.title, path: item.path! })),
|
|
65
76
|
]);
|
|
66
|
-
|
|
77
|
+
provide(docsTocKey, tocItems);
|
|
78
|
+
const { activeIds, refresh } = useScrollspy(computed(() => tocItems.value.map((item) => item.id)));
|
|
67
79
|
|
|
68
80
|
watch(tocItems, () => {
|
|
69
81
|
void refresh();
|
|
70
82
|
});
|
|
71
83
|
|
|
84
|
+
// Keep the active TOC region visible when a long TOC overflows the sticky
|
|
85
|
+
// aside.
|
|
86
|
+
const tocAside = ref<HTMLElement | null>(null);
|
|
87
|
+
const { reveal: revealActiveTocLink } = useRevealActive(tocAside, "[data-toc-active]", {
|
|
88
|
+
topPad: 16,
|
|
89
|
+
bottomPad: 44,
|
|
90
|
+
});
|
|
91
|
+
watch(activeIds, () => {
|
|
92
|
+
requestAnimationFrame(revealActiveTocLink);
|
|
93
|
+
});
|
|
94
|
+
|
|
72
95
|
useSeoMeta({
|
|
73
96
|
title: computed(() => `${pageTitle.value} - ${siteName.value}`),
|
|
74
97
|
description: pageDescription,
|
|
@@ -113,6 +136,11 @@ useHead(() => ({
|
|
|
113
136
|
link: [{ key: "canonical", rel: "canonical", href: canonicalUrl.value }],
|
|
114
137
|
meta: [{ property: "og:type", content: "article" }],
|
|
115
138
|
}));
|
|
139
|
+
|
|
140
|
+
function scrollToTop() {
|
|
141
|
+
const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
142
|
+
window.scrollTo({ top: 0, behavior: reducedMotion ? "auto" : "smooth" });
|
|
143
|
+
}
|
|
116
144
|
</script>
|
|
117
145
|
|
|
118
146
|
<template>
|
|
@@ -121,45 +149,27 @@ useHead(() => ({
|
|
|
121
149
|
<DocsMobileToc
|
|
122
150
|
class="order-1"
|
|
123
151
|
:items="tocItems"
|
|
124
|
-
:active-
|
|
152
|
+
:active-ids="activeIds"
|
|
125
153
|
:page-title="pageTitle"
|
|
126
154
|
/>
|
|
127
155
|
|
|
128
156
|
<main
|
|
129
157
|
id="main-content"
|
|
130
|
-
class="order-2 mx-auto w-full max-w-
|
|
158
|
+
class="order-2 mx-auto w-full max-w-[72ch] flex-1 px-4 py-8 md:px-6 xl:py-14"
|
|
131
159
|
>
|
|
132
160
|
<article v-if="page">
|
|
133
161
|
<div class="flex items-start justify-between gap-4 sm:gap-6">
|
|
134
162
|
<div class="min-w-0">
|
|
135
|
-
<
|
|
136
|
-
v-if="visibleTrail.length"
|
|
137
|
-
class="mb-2 flex flex-wrap items-center gap-1.5 text-sm text-muted-foreground"
|
|
138
|
-
:aria-label="t('docs.breadcrumbs')"
|
|
139
|
-
>
|
|
140
|
-
<template v-for="(item, index) in visibleTrail" :key="item.id">
|
|
141
|
-
<Icon
|
|
142
|
-
v-if="index > 0"
|
|
143
|
-
name="lucide:chevron-right"
|
|
144
|
-
class="size-3.5 text-border"
|
|
145
|
-
aria-hidden="true"
|
|
146
|
-
/>
|
|
147
|
-
<NuxtLink
|
|
148
|
-
v-if="item.path"
|
|
149
|
-
:to="item.path"
|
|
150
|
-
class="rounded-sm transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
151
|
-
>
|
|
152
|
-
{{ item.title }}
|
|
153
|
-
</NuxtLink>
|
|
154
|
-
<span v-else>{{ item.title }}</span>
|
|
155
|
-
</template>
|
|
156
|
-
</nav>
|
|
163
|
+
<DocsBreadcrumb class="mb-2" :items="visibleTrail" />
|
|
157
164
|
<h1
|
|
158
|
-
class="font-heading text-[
|
|
165
|
+
class="font-heading text-[27px] leading-[1.15] font-semibold tracking-[-0.02em] text-balance text-foreground"
|
|
159
166
|
>
|
|
160
167
|
{{ page.title }}
|
|
161
168
|
</h1>
|
|
162
|
-
<p
|
|
169
|
+
<p
|
|
170
|
+
v-if="page.description"
|
|
171
|
+
class="mt-2 text-base leading-[1.55] text-muted-foreground"
|
|
172
|
+
>
|
|
163
173
|
{{ page.description }}
|
|
164
174
|
</p>
|
|
165
175
|
</div>
|
|
@@ -171,18 +181,45 @@ useHead(() => ({
|
|
|
171
181
|
<ContentRenderer :value="page" />
|
|
172
182
|
</div>
|
|
173
183
|
|
|
174
|
-
<
|
|
175
|
-
|
|
176
|
-
|
|
184
|
+
<footer class="mt-12 border-t border-border pt-6">
|
|
185
|
+
<div class="flex flex-wrap items-center justify-between gap-x-6 gap-y-4">
|
|
186
|
+
<ContentFeedback :label="t('feedback.label')" />
|
|
187
|
+
<DocsContributeLinks
|
|
188
|
+
class="xl:hidden"
|
|
189
|
+
:stem="page.stem"
|
|
190
|
+
:extension="page.extension"
|
|
191
|
+
:title="pageTitle"
|
|
192
|
+
/>
|
|
193
|
+
</div>
|
|
194
|
+
<DocsPageNav class="mt-8" :prev="prev" :next="next" />
|
|
195
|
+
</footer>
|
|
177
196
|
</article>
|
|
178
197
|
</main>
|
|
179
198
|
</div>
|
|
180
199
|
|
|
181
200
|
<aside
|
|
182
|
-
|
|
201
|
+
ref="tocAside"
|
|
202
|
+
class="sticky top-[var(--site-header-height)] hidden h-[calc(100dvh-var(--site-header-height)-var(--site-banner-height,0px))] w-[var(--docs-toc-width)] shrink-0 flex-col overflow-y-auto pt-10 pr-4 pb-6 xl:flex"
|
|
183
203
|
:aria-label="t('docs.toc')"
|
|
184
204
|
>
|
|
185
|
-
<DocsToc :items="tocItems" :active-
|
|
205
|
+
<DocsToc class="mb-6" :items="tocItems" :active-ids="activeIds" />
|
|
206
|
+
|
|
207
|
+
<div class="mt-auto flex flex-col gap-2.5 border-t border-border/70 pt-5">
|
|
208
|
+
<DocsContributeLinks
|
|
209
|
+
variant="rail"
|
|
210
|
+
:stem="page?.stem"
|
|
211
|
+
:extension="page?.extension"
|
|
212
|
+
:title="pageTitle"
|
|
213
|
+
/>
|
|
214
|
+
<button
|
|
215
|
+
type="button"
|
|
216
|
+
class="inline-flex items-center gap-1.5 self-start text-sm font-medium text-muted-foreground transition-colors hover:text-foreground focus-visible:rounded-sm focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
|
|
217
|
+
@click="scrollToTop"
|
|
218
|
+
>
|
|
219
|
+
<Icon name="lucide:arrow-up" class="size-4" aria-hidden="true" />
|
|
220
|
+
{{ t("docs.backToTop") }}
|
|
221
|
+
</button>
|
|
222
|
+
</div>
|
|
186
223
|
</aside>
|
|
187
224
|
</div>
|
|
188
225
|
</template>
|
|
@@ -11,7 +11,7 @@ const { t } = useI18n();
|
|
|
11
11
|
<template>
|
|
12
12
|
<nav
|
|
13
13
|
v-if="prev || next"
|
|
14
|
-
class="
|
|
14
|
+
class="grid grid-cols-1 gap-3 sm:grid-cols-2"
|
|
15
15
|
:aria-label="t('docs.pageNavigation')"
|
|
16
16
|
>
|
|
17
17
|
<!-- Previous -->
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { cn } from "#ginko-docs/
|
|
2
|
+
import { cn } from "#ginko-docs/utils";
|
|
3
3
|
import { useDocsNavigation } from "#ginko-docs/features/docs/composables/useDocsNavigation";
|
|
4
|
-
import { findFirstNavigationPage } from "@lupinum/ginko-content/client";
|
|
5
4
|
import {
|
|
6
5
|
docsNavigationSectionContainsPath,
|
|
7
6
|
getDocsNavigationGroups,
|
|
7
|
+
resolveDocsSectionTargetPath,
|
|
8
8
|
type DocsNavigationSection,
|
|
9
9
|
} from "#ginko-docs/features/docs/docs-navigation";
|
|
10
|
+
import { ScrollArea } from "#ginko-docs/components/ui/scroll-area";
|
|
11
|
+
import { useRevealActive } from "#ginko-docs/features/docs/composables/useRevealActive";
|
|
10
12
|
import DocsSidebarItem from "./DocsSidebarItem.vue";
|
|
11
13
|
import DocsSidebarDropdown from "./DocsSidebarDropdown.vue";
|
|
12
14
|
import DocsSidebarList from "./DocsSidebarList.vue";
|
|
13
15
|
import DocsSidebarTabs from "./DocsSidebarTabs.vue";
|
|
14
|
-
import { computed, ref, watch } from "vue";
|
|
16
|
+
import { computed, nextTick, onMounted, ref, watch } from "vue";
|
|
15
17
|
import { navigateTo, useAppConfig, useI18n, useRoute } from "#imports";
|
|
16
18
|
|
|
17
19
|
const props = withDefaults(
|
|
@@ -54,16 +56,32 @@ watch(
|
|
|
54
56
|
);
|
|
55
57
|
|
|
56
58
|
function setActiveSection(id: string) {
|
|
59
|
+
// Re-selecting the active section must not navigate away from the current page.
|
|
60
|
+
if (id === activeSectionId.value) return;
|
|
57
61
|
selectedSectionId.value = id;
|
|
58
62
|
const section = sections.value.find((entry) => entry.id === id);
|
|
59
|
-
|
|
60
|
-
// instead of only swapping the sidebar contents.
|
|
61
|
-
const path = section?.path ?? findFirstNavigationPage(section?.items)?.path;
|
|
63
|
+
const path = section ? resolveDocsSectionTargetPath(section) : undefined;
|
|
62
64
|
if (path) void navigateTo(path);
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
const scrollViewportClass =
|
|
66
|
-
"
|
|
68
|
+
"size-full rounded-[inherit] p-4 pt-2 overscroll-contain [mask-image:linear-gradient(to_bottom,transparent,white_12px,white_calc(100%-12px),transparent)]";
|
|
69
|
+
|
|
70
|
+
// Deep links can land on an item far outside the visible band — center it
|
|
71
|
+
// once on mount and on section switches.
|
|
72
|
+
const scrollArea = ref<{ $el?: HTMLElement } | null>(null);
|
|
73
|
+
|
|
74
|
+
const { reveal: revealActiveItem } = useRevealActive(
|
|
75
|
+
() => scrollArea.value?.$el?.querySelector<HTMLElement>("[data-slot='scroll-area-viewport']"),
|
|
76
|
+
"[data-active='true']",
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
onMounted(() => {
|
|
80
|
+
void nextTick(revealActiveItem);
|
|
81
|
+
});
|
|
82
|
+
watch(activeSectionId, () => {
|
|
83
|
+
void nextTick(revealActiveItem);
|
|
84
|
+
});
|
|
67
85
|
|
|
68
86
|
const asideClass = computed(() =>
|
|
69
87
|
cn(
|
|
@@ -76,13 +94,7 @@ const asideClass = computed(() =>
|
|
|
76
94
|
</script>
|
|
77
95
|
|
|
78
96
|
<template>
|
|
79
|
-
<aside
|
|
80
|
-
data-hovered="false"
|
|
81
|
-
:data-variant="variant"
|
|
82
|
-
:id="variant === 'desktop' ? 'nd-sidebar' : undefined"
|
|
83
|
-
:aria-label="t('docs.label')"
|
|
84
|
-
:class="asideClass"
|
|
85
|
-
>
|
|
97
|
+
<aside :data-variant="variant" :aria-label="t('docs.label')" :class="asideClass">
|
|
86
98
|
<div v-if="switcherSections.length > 1" class="flex flex-col gap-3 p-4 pb-2">
|
|
87
99
|
<DocsSidebarTabs
|
|
88
100
|
v-if="sidebarSwitcher === 'tabs'"
|
|
@@ -104,15 +116,13 @@ const asideClass = computed(() =>
|
|
|
104
116
|
/>
|
|
105
117
|
</div>
|
|
106
118
|
|
|
107
|
-
<
|
|
108
|
-
:class="[scrollViewportClass, switcherSections.length > 1 ? 'pt-2' : 'pt-4']"
|
|
109
|
-
:data-switcher="switcherSections.length > 1"
|
|
110
|
-
>
|
|
119
|
+
<ScrollArea ref="scrollArea" class="min-h-0 flex-1" :viewport-class="scrollViewportClass">
|
|
111
120
|
<div class="flex min-w-full flex-col gap-0.5">
|
|
112
121
|
<template v-for="group in groups" :key="group.id">
|
|
113
122
|
<p
|
|
114
123
|
v-if="group.title"
|
|
115
|
-
|
|
124
|
+
data-slot="docs-sidebar-group-title"
|
|
125
|
+
class="mt-5 mb-1.5 inline-flex items-center gap-2 px-2 text-[11px] font-semibold tracking-wider text-muted-foreground uppercase first:mt-0"
|
|
116
126
|
>
|
|
117
127
|
{{ group.title }}
|
|
118
128
|
</p>
|
|
@@ -124,6 +134,6 @@ const asideClass = computed(() =>
|
|
|
124
134
|
/>
|
|
125
135
|
</template>
|
|
126
136
|
</div>
|
|
127
|
-
</
|
|
137
|
+
</ScrollArea>
|
|
128
138
|
</aside>
|
|
129
139
|
</template>
|
|
@@ -2,12 +2,18 @@
|
|
|
2
2
|
import { computed, ref, watch } from "vue";
|
|
3
3
|
import { useRoute } from "vue-router";
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
} from "#ginko-docs/features/docs/docs-navigation";
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
5
|
+
navigationItemContainsPath,
|
|
6
|
+
normalizeNavigationPath,
|
|
7
|
+
} from "@lupinum/ginko-content/navigation";
|
|
8
|
+
import type { DocsNavigationItem } from "#ginko-docs/features/docs/docs-navigation";
|
|
9
|
+
import { Button } from "#ginko-docs/components/ui/button";
|
|
10
|
+
import DocsSidebarRow from "./DocsSidebarRow.vue";
|
|
11
|
+
import {
|
|
12
|
+
Collapsible,
|
|
13
|
+
CollapsibleContent,
|
|
14
|
+
CollapsibleTrigger,
|
|
15
|
+
} from "#ginko-docs/components/ui/collapsible";
|
|
16
|
+
import { cn } from "#ginko-docs/utils";
|
|
11
17
|
|
|
12
18
|
defineOptions({
|
|
13
19
|
name: "DocsSidebarItem",
|
|
@@ -21,44 +27,36 @@ const props = defineProps<{
|
|
|
21
27
|
const route = useRoute();
|
|
22
28
|
|
|
23
29
|
function itemIsExactActive(path: string, item: DocsNavigationItem): boolean {
|
|
24
|
-
return Boolean(
|
|
25
|
-
item.path && normalizeDocsNavigationPath(path) === normalizeDocsNavigationPath(item.path),
|
|
26
|
-
);
|
|
30
|
+
return Boolean(item.path && normalizeNavigationPath(path) === normalizeNavigationPath(item.path));
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
function folderShouldBeOpen(path: string, item: DocsNavigationItem): boolean {
|
|
30
|
-
return item.children.length > 0 &&
|
|
34
|
+
return item.children.length > 0 && navigationItemContainsPath(item, path);
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
const isExactActive = computed(() => itemIsExactActive(route.path, props.item));
|
|
34
38
|
|
|
35
39
|
const paddingClass = computed(() => {
|
|
36
|
-
const steps = ["ps-
|
|
37
|
-
return steps[Math.min(props.depth, steps.length - 1)]!;
|
|
40
|
+
const steps = ["ps-[13px]", "ps-[25px]", "ps-[37px]"] as const;
|
|
41
|
+
return steps[Math.min(Math.max(props.depth - 1, 0), steps.length - 1)]!;
|
|
38
42
|
});
|
|
39
43
|
|
|
44
|
+
// Depth 0: compact pill rows. Depth > 0: each row carries the rail as its own
|
|
45
|
+
// start border so the active segment recolors it — aligned by construction.
|
|
40
46
|
const linkRowClass = computed(() =>
|
|
41
47
|
cn(
|
|
42
|
-
"group relative flex
|
|
48
|
+
"group relative flex w-full flex-row items-center gap-2 text-start [overflow-wrap:anywhere] text-muted-foreground transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
43
49
|
"[&_svg]:size-4 [&_svg]:shrink-0",
|
|
44
50
|
"hover:bg-accent/50 hover:text-accent-foreground/80 hover:transition-none",
|
|
45
|
-
"data-[active=true]:bg-
|
|
46
|
-
props.depth
|
|
47
|
-
? "
|
|
48
|
-
:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
cn(
|
|
55
|
-
"group relative flex min-h-10 w-full flex-row items-center rounded-lg text-start [overflow-wrap:anywhere] text-muted-foreground transition-colors outline-none",
|
|
56
|
-
"hover:bg-accent/50 hover:text-accent-foreground/80 hover:transition-none",
|
|
57
|
-
"data-[active=true]:bg-accent data-[active=true]:font-medium data-[active=true]:text-foreground data-[active=true]:hover:transition-colors",
|
|
58
|
-
props.depth > 0
|
|
59
|
-
? "data-[active=true]:before:absolute data-[active=true]:before:inset-y-2.5 data-[active=true]:before:start-2.5 data-[active=true]:before:w-px data-[active=true]:before:bg-primary data-[active=true]:before:content-['']"
|
|
60
|
-
: undefined,
|
|
61
|
-
paddingClass.value,
|
|
51
|
+
"data-[active=true]:bg-primary/10 data-[active=true]:text-primary data-[active=true]:font-medium data-[active=true]:hover:transition-colors",
|
|
52
|
+
props.depth === 0
|
|
53
|
+
? "rounded-[7px] px-2 py-[5.5px] text-[13px]"
|
|
54
|
+
: cn(
|
|
55
|
+
"rounded-e-[7px] border-s border-border py-[5px] pe-2 text-[12.5px]",
|
|
56
|
+
"hover:border-s-muted-foreground/40",
|
|
57
|
+
"data-[active=true]:border-s-primary",
|
|
58
|
+
paddingClass.value,
|
|
59
|
+
),
|
|
62
60
|
),
|
|
63
61
|
);
|
|
64
62
|
|
|
@@ -73,82 +71,45 @@ watch(
|
|
|
73
71
|
},
|
|
74
72
|
);
|
|
75
73
|
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
// No gap between child rows: their start borders stack into one continuous
|
|
75
|
+
// rail. Deeper levels indent via padding only, sharing the same rail.
|
|
76
|
+
const contentRailClass = computed(() =>
|
|
77
|
+
props.depth === 0 ? "ms-[15.5px] mt-0.5 mb-1 flex flex-col" : "flex flex-col",
|
|
78
|
+
);
|
|
78
79
|
</script>
|
|
79
80
|
|
|
80
81
|
<template>
|
|
81
|
-
<
|
|
82
|
-
<
|
|
83
|
-
v-if="item.path"
|
|
84
|
-
:class="splitFolderRowClass"
|
|
85
|
-
:data-active="isExactActive ? 'true' : 'false'"
|
|
86
|
-
>
|
|
82
|
+
<Collapsible v-if="item.children.length > 0" v-model:open="folderOpen" :unmount-on-hide="false">
|
|
83
|
+
<CollapsibleTrigger v-if="item.path" as-child>
|
|
87
84
|
<NuxtLink
|
|
88
85
|
:to="item.path"
|
|
86
|
+
:class="linkRowClass"
|
|
89
87
|
:aria-current="isExactActive ? 'page' : undefined"
|
|
90
|
-
|
|
91
|
-
data-slot="docs-sidebar-folder-link"
|
|
88
|
+
:data-active="isExactActive ? 'true' : 'false'"
|
|
92
89
|
>
|
|
93
|
-
<
|
|
94
|
-
<span class="min-w-0 truncate">{{ item.title }}</span>
|
|
95
|
-
<Badge
|
|
96
|
-
v-if="item.badge"
|
|
97
|
-
variant="secondary"
|
|
98
|
-
class="ms-auto h-4 shrink-0 rounded-sm px-1.5 py-0 text-[10px]"
|
|
99
|
-
>
|
|
100
|
-
{{ item.badge }}
|
|
101
|
-
</Badge>
|
|
90
|
+
<DocsSidebarRow :item="item" show-chevron />
|
|
102
91
|
</NuxtLink>
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
:
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
>
|
|
111
|
-
<Icon
|
|
112
|
-
name="lucide:chevron-down"
|
|
113
|
-
class="size-4 shrink-0 -rotate-90 transition-transform duration-200 group-data-[state=open]:rotate-0 rtl:rotate-90"
|
|
114
|
-
aria-hidden="true"
|
|
115
|
-
/>
|
|
116
|
-
<span class="sr-only">{{ item.title }}</span>
|
|
117
|
-
</button>
|
|
118
|
-
</div>
|
|
119
|
-
<button
|
|
120
|
-
v-else
|
|
121
|
-
:class="linkRowClass"
|
|
122
|
-
:data-active="isExactActive ? 'true' : 'false'"
|
|
123
|
-
:data-state="folderOpen ? 'open' : 'closed'"
|
|
124
|
-
:aria-expanded="folderOpen"
|
|
125
|
-
type="button"
|
|
126
|
-
@click="folderOpen = !folderOpen"
|
|
127
|
-
>
|
|
128
|
-
<Icon v-if="item.icon" :name="item.icon" class="size-4 shrink-0" aria-hidden="true" />
|
|
129
|
-
<span class="min-w-0 truncate">{{ item.title }}</span>
|
|
130
|
-
<Badge
|
|
131
|
-
v-if="item.badge"
|
|
132
|
-
variant="secondary"
|
|
133
|
-
class="ms-auto h-4 shrink-0 rounded-sm px-1.5 py-0 text-[10px]"
|
|
92
|
+
</CollapsibleTrigger>
|
|
93
|
+
<CollapsibleTrigger v-else as-child>
|
|
94
|
+
<Button
|
|
95
|
+
variant="ghost"
|
|
96
|
+
:class="linkRowClass"
|
|
97
|
+
class="h-auto justify-start"
|
|
98
|
+
:data-active="isExactActive ? 'true' : 'false'"
|
|
134
99
|
>
|
|
135
|
-
|
|
136
|
-
</
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
aria-hidden="true"
|
|
141
|
-
/>
|
|
142
|
-
</button>
|
|
143
|
-
<div v-show="folderOpen" :class="contentRailClass">
|
|
100
|
+
<DocsSidebarRow :item="item" show-chevron />
|
|
101
|
+
</Button>
|
|
102
|
+
</CollapsibleTrigger>
|
|
103
|
+
|
|
104
|
+
<CollapsibleContent :class="contentRailClass">
|
|
144
105
|
<DocsSidebarItem
|
|
145
106
|
v-for="child in item.children"
|
|
146
107
|
:key="child.path ?? child.title"
|
|
147
108
|
:item="child"
|
|
148
109
|
:depth="depth + 1"
|
|
149
110
|
/>
|
|
150
|
-
</
|
|
151
|
-
</
|
|
111
|
+
</CollapsibleContent>
|
|
112
|
+
</Collapsible>
|
|
152
113
|
<NuxtLink
|
|
153
114
|
v-else-if="item.path"
|
|
154
115
|
:to="item.path"
|
|
@@ -156,14 +117,6 @@ const contentRailClass =
|
|
|
156
117
|
:class="linkRowClass"
|
|
157
118
|
:data-active="isExactActive ? 'true' : 'false'"
|
|
158
119
|
>
|
|
159
|
-
<
|
|
160
|
-
<span class="min-w-0 truncate">{{ item.title }}</span>
|
|
161
|
-
<Badge
|
|
162
|
-
v-if="item.badge"
|
|
163
|
-
variant="secondary"
|
|
164
|
-
class="ms-auto h-4 shrink-0 rounded-sm px-1.5 py-0 text-[10px]"
|
|
165
|
-
>
|
|
166
|
-
{{ item.badge }}
|
|
167
|
-
</Badge>
|
|
120
|
+
<DocsSidebarRow :item="item" />
|
|
168
121
|
</NuxtLink>
|
|
169
122
|
</template>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { DocsNavigationItem } from "#ginko-docs/features/docs/docs-navigation";
|
|
3
|
+
import { Badge } from "#ginko-docs/components/ui/badge";
|
|
4
|
+
|
|
5
|
+
defineProps<{
|
|
6
|
+
item: DocsNavigationItem;
|
|
7
|
+
showChevron?: boolean;
|
|
8
|
+
}>();
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<template>
|
|
12
|
+
<Icon v-if="item.icon" :name="item.icon" class="size-4 shrink-0" aria-hidden="true" />
|
|
13
|
+
<span class="min-w-0 truncate">{{ item.title }}</span>
|
|
14
|
+
<Badge
|
|
15
|
+
v-if="item.badge"
|
|
16
|
+
variant="secondary"
|
|
17
|
+
class="ms-auto h-4 shrink-0 rounded-full px-1.5 py-0 text-[10px]"
|
|
18
|
+
>
|
|
19
|
+
{{ item.badge }}
|
|
20
|
+
</Badge>
|
|
21
|
+
<Icon
|
|
22
|
+
v-if="showChevron"
|
|
23
|
+
name="lucide:chevron-down"
|
|
24
|
+
class="ms-auto size-3.5 shrink-0 -rotate-90 text-muted-foreground/70 transition-transform duration-200 ease-out group-hover:text-muted-foreground group-data-[state=open]:rotate-0 group-data-[active=true]:text-primary/70 rtl:rotate-90"
|
|
25
|
+
aria-hidden="true"
|
|
26
|
+
/>
|
|
27
|
+
</template>
|