@lupinum/ginko-docs 0.2.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.
Files changed (240) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +112 -0
  3. package/app/app.config.ts +49 -0
  4. package/app/app.vue +71 -0
  5. package/app/assets/css/prose.css +1154 -0
  6. package/app/assets/css/tailwind.css +365 -0
  7. package/app/components/OgImage/GinkoDocs.satori.vue +117 -0
  8. package/app/components/content/DocumentPageShell.vue +55 -0
  9. package/app/components/content/Feedback.vue +97 -0
  10. package/app/components/content/PageMarkdownCopy.vue +102 -0
  11. package/app/components/mdc/MdcAccordion.vue +64 -0
  12. package/app/components/mdc/MdcAccordionItem.vue +51 -0
  13. package/app/components/mdc/MdcAlert.vue +34 -0
  14. package/app/components/mdc/MdcAside.vue +27 -0
  15. package/app/components/mdc/MdcCallout.vue +62 -0
  16. package/app/components/mdc/MdcCard.vue +186 -0
  17. package/app/components/mdc/MdcCardGroup.vue +47 -0
  18. package/app/components/mdc/MdcCenter.vue +71 -0
  19. package/app/components/mdc/MdcCodeGroup.vue +193 -0
  20. package/app/components/mdc/MdcColumn.vue +70 -0
  21. package/app/components/mdc/MdcDocImg.vue +25 -0
  22. package/app/components/mdc/MdcDropcap.vue +84 -0
  23. package/app/components/mdc/MdcError.vue +16 -0
  24. package/app/components/mdc/MdcField.vue +62 -0
  25. package/app/components/mdc/MdcFieldGroup.vue +18 -0
  26. package/app/components/mdc/MdcFigure.vue +51 -0
  27. package/app/components/mdc/MdcIdea.vue +16 -0
  28. package/app/components/mdc/MdcInfo.vue +16 -0
  29. package/app/components/mdc/MdcLayout.vue +57 -0
  30. package/app/components/mdc/MdcNote.vue +16 -0
  31. package/app/components/mdc/MdcPassage.vue +32 -0
  32. package/app/components/mdc/MdcQuiz.vue +281 -0
  33. package/app/components/mdc/MdcQuizOption.vue +91 -0
  34. package/app/components/mdc/MdcQuizQuestion.vue +226 -0
  35. package/app/components/mdc/MdcReadMore.vue +90 -0
  36. package/app/components/mdc/MdcReadMoreGroup.vue +18 -0
  37. package/app/components/mdc/MdcShortcut.vue +46 -0
  38. package/app/components/mdc/MdcStep.vue +42 -0
  39. package/app/components/mdc/MdcSteps.vue +25 -0
  40. package/app/components/mdc/MdcSuccess.vue +16 -0
  41. package/app/components/mdc/MdcTab.vue +17 -0
  42. package/app/components/mdc/MdcTabs.vue +132 -0
  43. package/app/components/mdc/MdcTimeline.vue +14 -0
  44. package/app/components/mdc/MdcTimelineItem.vue +49 -0
  45. package/app/components/mdc/MdcWarning.vue +16 -0
  46. package/app/components/mdc/icons.ts +10 -0
  47. package/app/components/prose/ProseA.vue +27 -0
  48. package/app/components/prose/ProseBlockquote.vue +14 -0
  49. package/app/components/prose/ProseCode.vue +14 -0
  50. package/app/components/prose/ProseEm.vue +14 -0
  51. package/app/components/prose/ProseH1.vue +18 -0
  52. package/app/components/prose/ProseH2.vue +24 -0
  53. package/app/components/prose/ProseH3.vue +24 -0
  54. package/app/components/prose/ProseH4.vue +14 -0
  55. package/app/components/prose/ProseHr.vue +12 -0
  56. package/app/components/prose/ProseImg.vue +120 -0
  57. package/app/components/prose/ProseLi.vue +14 -0
  58. package/app/components/prose/ProseOl.vue +14 -0
  59. package/app/components/prose/ProseP.vue +14 -0
  60. package/app/components/prose/ProsePre.vue +217 -0
  61. package/app/components/prose/ProseStrong.vue +14 -0
  62. package/app/components/prose/ProseTable.vue +16 -0
  63. package/app/components/prose/ProseTbody.vue +14 -0
  64. package/app/components/prose/ProseTd.vue +14 -0
  65. package/app/components/prose/ProseTh.vue +14 -0
  66. package/app/components/prose/ProseThead.vue +14 -0
  67. package/app/components/prose/ProseTr.vue +14 -0
  68. package/app/components/prose/ProseUl.vue +14 -0
  69. package/app/components/site/ModeToggle.vue +123 -0
  70. package/app/components/site/SiteBanner.vue +56 -0
  71. package/app/components/site/SiteFooter.vue +80 -0
  72. package/app/components/site/SiteHeader.vue +250 -0
  73. package/app/components/site/SiteInteractionLayer.vue +35 -0
  74. package/app/components/site/SiteLocaleSwitcher.vue +268 -0
  75. package/app/components/site/SiteLogoMark.vue +12 -0
  76. package/app/components/site/SiteSkipLink.vue +14 -0
  77. package/app/components/ui/accordion/Accordion.vue +15 -0
  78. package/app/components/ui/accordion/AccordionContent.vue +23 -0
  79. package/app/components/ui/accordion/AccordionItem.vue +24 -0
  80. package/app/components/ui/accordion/AccordionTrigger.vue +35 -0
  81. package/app/components/ui/accordion/index.ts +4 -0
  82. package/app/components/ui/alert/Alert.vue +21 -0
  83. package/app/components/ui/alert/AlertDescription.vue +22 -0
  84. package/app/components/ui/alert/AlertTitle.vue +22 -0
  85. package/app/components/ui/alert/index.ts +34 -0
  86. package/app/components/ui/badge/Badge.vue +28 -0
  87. package/app/components/ui/badge/index.ts +24 -0
  88. package/app/components/ui/button/Button.vue +31 -0
  89. package/app/components/ui/button/index.ts +35 -0
  90. package/app/components/ui/card/Card.vue +16 -0
  91. package/app/components/ui/card/CardAction.vue +17 -0
  92. package/app/components/ui/card/CardContent.vue +14 -0
  93. package/app/components/ui/card/CardDescription.vue +14 -0
  94. package/app/components/ui/card/CardFooter.vue +14 -0
  95. package/app/components/ui/card/CardHeader.vue +22 -0
  96. package/app/components/ui/card/CardTitle.vue +14 -0
  97. package/app/components/ui/card/card-variants.ts +15 -0
  98. package/app/components/ui/card/index.ts +8 -0
  99. package/app/components/ui/checkbox/Checkbox.vue +35 -0
  100. package/app/components/ui/checkbox/index.ts +1 -0
  101. package/app/components/ui/collapsible/Collapsible.vue +15 -0
  102. package/app/components/ui/collapsible/CollapsibleContent.vue +25 -0
  103. package/app/components/ui/collapsible/CollapsibleTrigger.vue +12 -0
  104. package/app/components/ui/collapsible/index.ts +3 -0
  105. package/app/components/ui/dialog/Dialog.vue +15 -0
  106. package/app/components/ui/dialog/DialogClose.vue +12 -0
  107. package/app/components/ui/dialog/DialogContent.vue +69 -0
  108. package/app/components/ui/dialog/DialogDescription.vue +21 -0
  109. package/app/components/ui/dialog/DialogFooter.vue +15 -0
  110. package/app/components/ui/dialog/DialogHeader.vue +12 -0
  111. package/app/components/ui/dialog/DialogOverlay.vue +26 -0
  112. package/app/components/ui/dialog/DialogPortal.vue +12 -0
  113. package/app/components/ui/dialog/DialogTitle.vue +21 -0
  114. package/app/components/ui/dialog/DialogTrigger.vue +12 -0
  115. package/app/components/ui/dialog/index.ts +10 -0
  116. package/app/components/ui/dropdown-menu/DropdownMenu.vue +15 -0
  117. package/app/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +36 -0
  118. package/app/components/ui/dropdown-menu/DropdownMenuContent.vue +40 -0
  119. package/app/components/ui/dropdown-menu/DropdownMenuGroup.vue +12 -0
  120. package/app/components/ui/dropdown-menu/DropdownMenuItem.vue +41 -0
  121. package/app/components/ui/dropdown-menu/DropdownMenuLabel.vue +25 -0
  122. package/app/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +15 -0
  123. package/app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +37 -0
  124. package/app/components/ui/dropdown-menu/DropdownMenuSeparator.vue +23 -0
  125. package/app/components/ui/dropdown-menu/DropdownMenuShortcut.vue +17 -0
  126. package/app/components/ui/dropdown-menu/DropdownMenuSub.vue +15 -0
  127. package/app/components/ui/dropdown-menu/DropdownMenuSubContent.vue +29 -0
  128. package/app/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +31 -0
  129. package/app/components/ui/dropdown-menu/DropdownMenuTrigger.vue +14 -0
  130. package/app/components/ui/dropdown-menu/index.ts +16 -0
  131. package/app/components/ui/input/Input.vue +23 -0
  132. package/app/components/ui/input/index.ts +1 -0
  133. package/app/components/ui/kbd/Kbd.vue +23 -0
  134. package/app/components/ui/kbd/KbdGroup.vue +14 -0
  135. package/app/components/ui/kbd/index.ts +22 -0
  136. package/app/components/ui/label/Label.vue +14 -0
  137. package/app/components/ui/label/index.ts +1 -0
  138. package/app/components/ui/scroll-area/ScrollArea.vue +39 -0
  139. package/app/components/ui/scroll-area/ScrollBar.vue +33 -0
  140. package/app/components/ui/scroll-area/index.ts +2 -0
  141. package/app/components/ui/separator/Separator.vue +27 -0
  142. package/app/components/ui/separator/index.ts +1 -0
  143. package/app/components/ui/sheet/Sheet.vue +15 -0
  144. package/app/components/ui/sheet/SheetClose.vue +12 -0
  145. package/app/components/ui/sheet/SheetContent.vue +59 -0
  146. package/app/components/ui/sheet/SheetDescription.vue +21 -0
  147. package/app/components/ui/sheet/SheetFooter.vue +12 -0
  148. package/app/components/ui/sheet/SheetHeader.vue +12 -0
  149. package/app/components/ui/sheet/SheetOverlay.vue +26 -0
  150. package/app/components/ui/sheet/SheetTitle.vue +21 -0
  151. package/app/components/ui/sheet/SheetTrigger.vue +12 -0
  152. package/app/components/ui/sheet/index.ts +8 -0
  153. package/app/components/ui/switch/Switch.vue +39 -0
  154. package/app/components/ui/switch/index.ts +1 -0
  155. package/app/components/ui/tabs/Tabs.vue +24 -0
  156. package/app/components/ui/tabs/TabsContent.vue +21 -0
  157. package/app/components/ui/tabs/TabsList.vue +26 -0
  158. package/app/components/ui/tabs/TabsTrigger.vue +28 -0
  159. package/app/components/ui/tabs/index.ts +4 -0
  160. package/app/components/ui/textarea/Textarea.vue +31 -0
  161. package/app/components/ui/textarea/index.ts +1 -0
  162. package/app/composables/site-navigation.utils.ts +80 -0
  163. package/app/composables/useCanonicalUrl.ts +9 -0
  164. package/app/composables/useContentRouteAlternates.ts +43 -0
  165. package/app/composables/useGinkoAnalytics.ts +37 -0
  166. package/app/composables/useGinkoDocsConfig.ts +6 -0
  167. package/app/composables/useGinkoOgImage.ts +20 -0
  168. package/app/composables/useLocalizedPath.ts +12 -0
  169. package/app/composables/useLocalizedRouteSwitch.ts +31 -0
  170. package/app/composables/usePageMarkdownActions.ts +79 -0
  171. package/app/composables/useSchemaJsonLd.ts +25 -0
  172. package/app/composables/useSiteNavigation.ts +86 -0
  173. package/app/config/site.utils.ts +36 -0
  174. package/app/error.vue +125 -0
  175. package/app/features/docs/components/DocsContributeLinks.vue +65 -0
  176. package/app/features/docs/components/DocsMobileToc.vue +104 -0
  177. package/app/features/docs/components/DocsPageContent.vue +188 -0
  178. package/app/features/docs/components/DocsPageNav.vue +57 -0
  179. package/app/features/docs/components/DocsSidebar.vue +129 -0
  180. package/app/features/docs/components/DocsSidebarDropdown.vue +70 -0
  181. package/app/features/docs/components/DocsSidebarItem.vue +169 -0
  182. package/app/features/docs/components/DocsSidebarList.vue +35 -0
  183. package/app/features/docs/components/DocsSidebarTabs.vue +35 -0
  184. package/app/features/docs/components/DocsToc.vue +60 -0
  185. package/app/features/docs/composables/useDocsEntryPath.ts +13 -0
  186. package/app/features/docs/composables/useDocsNavigation.ts +29 -0
  187. package/app/features/docs/composables/useDocsNavigationData.ts +15 -0
  188. package/app/features/docs/composables/useScrollspy.ts +67 -0
  189. package/app/features/docs/docs-navigation.ts +170 -0
  190. package/app/features/search/command-center.ts +156 -0
  191. package/app/features/search/components/SiteCommandCenter.vue +323 -0
  192. package/app/features/search/components/SiteSearchPageHighlight.vue +124 -0
  193. package/app/features/search/useCommandCenter.ts +300 -0
  194. package/app/layouts/blog.vue +15 -0
  195. package/app/layouts/default.vue +12 -0
  196. package/app/layouts/docs.vue +20 -0
  197. package/app/lib/errors.ts +84 -0
  198. package/app/lib/schema-org.ts +38 -0
  199. package/app/lib/utils.ts +7 -0
  200. package/app/pages/blog/[slug].vue +211 -0
  201. package/app/pages/blog/index.vue +106 -0
  202. package/app/pages/docs/[...slug].vue +10 -0
  203. package/app/pages/docs/index.vue +23 -0
  204. package/app/pages/index.vue +140 -0
  205. package/app/plugins/ssr-width.ts +6 -0
  206. package/app/utils/content.ts +56 -0
  207. package/app/utils/index.ts +1 -0
  208. package/components.ts +9 -0
  209. package/content.js +175 -0
  210. package/content.ts +148 -0
  211. package/i18n/i18n.config.ts +10 -0
  212. package/i18n/locales.ts +81 -0
  213. package/i18n/messages/global/banner.ts +8 -0
  214. package/i18n/messages/global/blog.ts +14 -0
  215. package/i18n/messages/global/command.ts +44 -0
  216. package/i18n/messages/global/docs.ts +32 -0
  217. package/i18n/messages/global/errors.ts +36 -0
  218. package/i18n/messages/global/feedback.ts +11 -0
  219. package/i18n/messages/global/index.ts +21 -0
  220. package/i18n/messages/global/nav.ts +18 -0
  221. package/i18n/messages/global/site.ts +4 -0
  222. package/i18n/messages/global/theme.ts +7 -0
  223. package/i18n/messages/index.ts +20 -0
  224. package/i18n/messages/localize.ts +35 -0
  225. package/i18n/messages/source.ts +5 -0
  226. package/i18n/routes.ts +33 -0
  227. package/icon-bundle.ts +141 -0
  228. package/index.d.ts +13 -0
  229. package/modules/feature-routing.ts +32 -0
  230. package/nuxt.config.ts +194 -0
  231. package/package.json +91 -0
  232. package/runtime/markdown/inline-code-highlight.ts +155 -0
  233. package/runtime/server/agent-markdown.ts +21 -0
  234. package/runtime/utils.ts +7 -0
  235. package/server/mcp/tools/get-page.ts +14 -0
  236. package/server/mcp/tools/list-pages.ts +10 -0
  237. package/server/plugins/agent-markdown.ts +6 -0
  238. package/shared/route-slugs.ts +5 -0
  239. package/shared/types/app-config.ts +100 -0
  240. package/tags.ts +204 -0
@@ -0,0 +1,14 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from "vue";
3
+ import { cn } from "../../utils";
4
+
5
+ const props = defineProps<{
6
+ class?: HTMLAttributes["class"];
7
+ }>();
8
+ </script>
9
+
10
+ <template>
11
+ <li :class="cn(props.class)">
12
+ <slot />
13
+ </li>
14
+ </template>
@@ -0,0 +1,14 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from "vue";
3
+ import { cn } from "../../utils";
4
+
5
+ const props = defineProps<{
6
+ class?: HTMLAttributes["class"];
7
+ }>();
8
+ </script>
9
+
10
+ <template>
11
+ <ol :class="cn(props.class)">
12
+ <slot />
13
+ </ol>
14
+ </template>
@@ -0,0 +1,14 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from "vue";
3
+ import { cn } from "../../utils";
4
+
5
+ const props = defineProps<{
6
+ class?: HTMLAttributes["class"];
7
+ }>();
8
+ </script>
9
+
10
+ <template>
11
+ <p :class="cn(props.class)">
12
+ <slot />
13
+ </p>
14
+ </template>
@@ -0,0 +1,217 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from "vue";
3
+ import { computed, ref, useId } from "vue";
4
+ import { useClipboard } from "@vueuse/core";
5
+ import { useI18n } from "#imports";
6
+ import { cn } from "../../utils";
7
+
8
+ const props = defineProps<{
9
+ code?: string;
10
+ language?: string | null;
11
+ filename?: string | null;
12
+ highlights?: number[];
13
+ meta?: string | null;
14
+ icon?: string | null;
15
+ inGroup?: boolean;
16
+ class?: HTMLAttributes["class"];
17
+ }>();
18
+
19
+ const { copy, copied } = useClipboard();
20
+ const { t } = useI18n();
21
+ const codeElement = ref<HTMLElement | null>(null);
22
+ // Landmarks with role="region" need unique accessible names; the same
23
+ // filename can appear in several code blocks on one page.
24
+ const regionId = useId();
25
+
26
+ const HIDDEN_LANGUAGES = new Set(["text", "plaintext", "txt", "plain"]);
27
+ const FALLBACK_FILE_ICON = "lucide:file";
28
+
29
+ const FILENAME_ICONS: Record<string, string> = {
30
+ ".env": "lucide:file-key",
31
+ ".env.example": "lucide:file-key",
32
+ ".gitignore": "lucide:git-branch",
33
+ "components.json": "lucide:blocks",
34
+ "eslint.config.cjs": "lucide:badge-check",
35
+ "eslint.config.js": "lucide:badge-check",
36
+ "eslint.config.mjs": "lucide:badge-check",
37
+ "nuxt.config.js": "logos:nuxt-icon",
38
+ "nuxt.config.ts": "logos:nuxt-icon",
39
+ "package.json": "lucide:package",
40
+ "pnpm-lock.yaml": "lucide:package",
41
+ "pnpm-workspace.yaml": "lucide:package",
42
+ "tailwind.config.js": "lucide:palette",
43
+ "tailwind.config.ts": "lucide:palette",
44
+ "tsconfig.json": "lucide:file-cog",
45
+ "vite.config.js": "lucide:zap",
46
+ "vite.config.ts": "lucide:zap",
47
+ };
48
+
49
+ const EXTENSION_ICONS: Record<string, string> = {
50
+ bash: "lucide:terminal",
51
+ cjs: "lucide:file-json",
52
+ css: "lucide:braces",
53
+ csv: "lucide:table",
54
+ htm: "lucide:code-xml",
55
+ html: "lucide:code-xml",
56
+ js: "lucide:file-json",
57
+ json: "lucide:braces",
58
+ jsx: "lucide:atom",
59
+ md: "lucide:file-text",
60
+ mdc: "lucide:file-text",
61
+ mjs: "lucide:file-json",
62
+ mts: "lucide:file-code",
63
+ scss: "lucide:braces",
64
+ sh: "lucide:terminal",
65
+ ts: "logos:typescript-icon",
66
+ tsx: "lucide:atom",
67
+ txt: "lucide:file-text",
68
+ vue: "logos:vue",
69
+ yaml: "lucide:file-cog",
70
+ yml: "lucide:file-cog",
71
+ };
72
+
73
+ const LANGUAGE_ICONS: Record<string, string> = {
74
+ bash: "lucide:terminal",
75
+ css: "lucide:braces",
76
+ html: "lucide:code-xml",
77
+ javascript: "lucide:file-json",
78
+ js: "lucide:file-json",
79
+ json: "lucide:braces",
80
+ jsx: "lucide:atom",
81
+ markdown: "lucide:file-text",
82
+ mdc: "lucide:file-text",
83
+ shell: "lucide:terminal",
84
+ sh: "lucide:terminal",
85
+ ts: "logos:typescript-icon",
86
+ tsx: "lucide:atom",
87
+ typescript: "logos:typescript-icon",
88
+ vue: "logos:vue",
89
+ "vue-html": "logos:vue",
90
+ yaml: "lucide:file-cog",
91
+ yml: "lucide:file-cog",
92
+ };
93
+
94
+ function parseIconFromMeta(meta: string | null | undefined): string | null {
95
+ if (!meta) return null;
96
+ const match = meta.match(/(?:^|\s)icon=(\S+)/);
97
+ return match?.[1] ?? null;
98
+ }
99
+
100
+ function normalizedFilename(filename: string | null | undefined): string | null {
101
+ return filename?.trim().replace(/^\.\//, "").toLowerCase() || null;
102
+ }
103
+
104
+ function filenameExtension(filename: string | null | undefined): string | null {
105
+ const normalized = normalizedFilename(filename);
106
+ const basename = normalized?.split("/").pop();
107
+ const index = basename?.lastIndexOf(".") ?? -1;
108
+
109
+ if (!basename || index < 0 || index === basename.length - 1) return null;
110
+ return basename.slice(index + 1);
111
+ }
112
+
113
+ function normalizedLanguage(language: string | null | undefined): string | null {
114
+ return language?.trim().toLowerCase() || null;
115
+ }
116
+
117
+ const label = computed(() => {
118
+ if (props.filename) return props.filename;
119
+
120
+ const language = normalizedLanguage(props.language);
121
+ if (language && !HIDDEN_LANGUAGES.has(language)) return props.language;
122
+
123
+ return "";
124
+ });
125
+
126
+ const iconName = computed(() => {
127
+ const explicitIcon = props.icon || parseIconFromMeta(props.meta);
128
+ if (explicitIcon) return explicitIcon;
129
+
130
+ const filename = normalizedFilename(props.filename);
131
+ if (filename && FILENAME_ICONS[filename]) return FILENAME_ICONS[filename];
132
+
133
+ const extension = filenameExtension(props.filename);
134
+ if (extension && EXTENSION_ICONS[extension]) return EXTENSION_ICONS[extension];
135
+
136
+ const language = normalizedLanguage(props.language);
137
+ if (language && LANGUAGE_ICONS[language]) return LANGUAGE_ICONS[language];
138
+
139
+ return FALLBACK_FILE_ICON;
140
+ });
141
+
142
+ async function copyCode() {
143
+ await copy(props.code || codeElement.value?.innerText || "");
144
+ }
145
+ </script>
146
+
147
+ <template>
148
+ <figure
149
+ dir="ltr"
150
+ data-fd-codeblock
151
+ tabindex="-1"
152
+ :class="
153
+ cn(
154
+ 'content-codeblock group shiki not-prose relative',
155
+ props.inGroup
156
+ ? 'content-codeblock-in-group'
157
+ : label
158
+ ? 'content-codeblock-with-caption'
159
+ : 'content-codeblock-plain',
160
+ )
161
+ "
162
+ >
163
+ <figcaption v-if="label && !props.inGroup" data-fd-caption>
164
+ <Icon :name="iconName" mode="svg" class="content-codeblock-caption-icon" aria-hidden="true" />
165
+ <span class="flex-1 truncate">{{ label }}</span>
166
+ <button
167
+ type="button"
168
+ class="content-codeblock-copy-button ml-auto"
169
+ :aria-label="copied ? t('docs.copiedText') : t('docs.copyText')"
170
+ @click="copyCode"
171
+ >
172
+ <Icon
173
+ :name="copied ? 'lucide:check' : 'lucide:clipboard'"
174
+ mode="svg"
175
+ class="size-3.5"
176
+ aria-hidden="true"
177
+ />
178
+ </button>
179
+ </figcaption>
180
+
181
+ <div v-if="!label && !props.inGroup" class="absolute top-2 right-2 z-10" data-fd-copy-float>
182
+ <button
183
+ type="button"
184
+ class="content-codeblock-copy-button content-codeblock-copy-button-floating"
185
+ :aria-label="copied ? t('docs.copiedText') : t('docs.copyText')"
186
+ @click="copyCode"
187
+ >
188
+ <Icon
189
+ :name="copied ? 'lucide:check' : 'lucide:clipboard'"
190
+ mode="svg"
191
+ class="size-3.5"
192
+ aria-hidden="true"
193
+ />
194
+ </button>
195
+ </div>
196
+
197
+ <div
198
+ role="region"
199
+ tabindex="0"
200
+ :aria-label="
201
+ (label ? `${t('docs.codeSample')}: ${label}` : t('docs.codeSample')) + ` (${regionId})`
202
+ "
203
+ :class="
204
+ cn(
205
+ 'fd-scroll-container max-h-[600px] overflow-auto focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none focus-visible:ring-inset',
206
+ !label && !props.inGroup && 'content-codeblock-inline-copy',
207
+ )
208
+ "
209
+ >
210
+ <pre
211
+ ref="codeElement"
212
+ :class="cn('w-max min-w-full', props.class)"
213
+ v-bind="$attrs"
214
+ ><slot /></pre>
215
+ </div>
216
+ </figure>
217
+ </template>
@@ -0,0 +1,14 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from "vue";
3
+ import { cn } from "../../utils";
4
+
5
+ const props = defineProps<{
6
+ class?: HTMLAttributes["class"];
7
+ }>();
8
+ </script>
9
+
10
+ <template>
11
+ <strong :class="cn(props.class)">
12
+ <slot />
13
+ </strong>
14
+ </template>
@@ -0,0 +1,16 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from "vue";
3
+ import { cn } from "../../utils";
4
+
5
+ const props = defineProps<{
6
+ class?: HTMLAttributes["class"];
7
+ }>();
8
+ </script>
9
+
10
+ <template>
11
+ <div :class="cn('content-table', props.class)">
12
+ <table>
13
+ <slot />
14
+ </table>
15
+ </div>
16
+ </template>
@@ -0,0 +1,14 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from "vue";
3
+ import { cn } from "../../utils";
4
+
5
+ const props = defineProps<{
6
+ class?: HTMLAttributes["class"];
7
+ }>();
8
+ </script>
9
+
10
+ <template>
11
+ <tbody :class="cn(props.class)">
12
+ <slot />
13
+ </tbody>
14
+ </template>
@@ -0,0 +1,14 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from "vue";
3
+ import { cn } from "../../utils";
4
+
5
+ const props = defineProps<{
6
+ class?: HTMLAttributes["class"];
7
+ }>();
8
+ </script>
9
+
10
+ <template>
11
+ <td :class="cn(props.class)">
12
+ <slot />
13
+ </td>
14
+ </template>
@@ -0,0 +1,14 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from "vue";
3
+ import { cn } from "../../utils";
4
+
5
+ const props = defineProps<{
6
+ class?: HTMLAttributes["class"];
7
+ }>();
8
+ </script>
9
+
10
+ <template>
11
+ <th :class="cn(props.class)">
12
+ <slot />
13
+ </th>
14
+ </template>
@@ -0,0 +1,14 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from "vue";
3
+ import { cn } from "../../utils";
4
+
5
+ const props = defineProps<{
6
+ class?: HTMLAttributes["class"];
7
+ }>();
8
+ </script>
9
+
10
+ <template>
11
+ <thead :class="cn(props.class)">
12
+ <slot />
13
+ </thead>
14
+ </template>
@@ -0,0 +1,14 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from "vue";
3
+ import { cn } from "../../utils";
4
+
5
+ const props = defineProps<{
6
+ class?: HTMLAttributes["class"];
7
+ }>();
8
+ </script>
9
+
10
+ <template>
11
+ <tr :class="cn(props.class)">
12
+ <slot />
13
+ </tr>
14
+ </template>
@@ -0,0 +1,14 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from "vue";
3
+ import { cn } from "../../utils";
4
+
5
+ const props = defineProps<{
6
+ class?: HTMLAttributes["class"];
7
+ }>();
8
+ </script>
9
+
10
+ <template>
11
+ <ul :class="cn(props.class)">
12
+ <slot />
13
+ </ul>
14
+ </template>
@@ -0,0 +1,123 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from "vue";
3
+ import { Button } from "#ginko-docs/components/ui/button";
4
+ import { computed } from "vue";
5
+ import { useColorMode, useI18n } from "#imports";
6
+ import { cn } from "#ginko-docs/lib/utils";
7
+ import {
8
+ DropdownMenu,
9
+ DropdownMenuContent,
10
+ DropdownMenuRadioGroup,
11
+ DropdownMenuRadioItem,
12
+ DropdownMenuTrigger,
13
+ } from "#ginko-docs/components/ui/dropdown-menu";
14
+
15
+ const props = withDefaults(
16
+ defineProps<{
17
+ variant?: "icon" | "menu-row" | "menu-tile";
18
+ class?: HTMLAttributes["class"];
19
+ }>(),
20
+ {
21
+ variant: "icon",
22
+ },
23
+ );
24
+
25
+ const colorMode = useColorMode();
26
+ const { t } = useI18n();
27
+
28
+ const colorModeOptions = [
29
+ { value: "light", labelKey: "theme.light", icon: "lucide:sun" },
30
+ { value: "dark", labelKey: "theme.dark", icon: "lucide:moon" },
31
+ { value: "system", labelKey: "theme.system", icon: "lucide:monitor" },
32
+ ] as const;
33
+
34
+ type ColorModePreference = (typeof colorModeOptions)[number]["value"];
35
+
36
+ const selectedColorModeOption = computed(
37
+ () =>
38
+ colorModeOptions.find((option) => option.value === colorMode.preference) ??
39
+ colorModeOptions.find((option) => option.value === "system")!,
40
+ );
41
+ const triggerIcon = computed(() => selectedColorModeOption.value.icon);
42
+ const nextExplicitColorModeIcon = computed(() =>
43
+ colorMode.value === "dark" ? "lucide:sun" : "lucide:moon",
44
+ );
45
+ const triggerLabel = computed(() => t(selectedColorModeOption.value.labelKey));
46
+
47
+ function isColorModePreference(value: string): value is ColorModePreference {
48
+ return colorModeOptions.some((option) => option.value === value);
49
+ }
50
+
51
+ function setColorModePreference(value: unknown) {
52
+ if (typeof value === "string" && isColorModePreference(value)) {
53
+ colorMode.preference = value;
54
+ }
55
+ }
56
+ </script>
57
+
58
+ <template>
59
+ <DropdownMenu>
60
+ <DropdownMenuTrigger as-child>
61
+ <Button
62
+ v-if="variant === 'menu-row' || variant === 'menu-tile'"
63
+ variant="ghost"
64
+ :class="
65
+ cn(
66
+ variant === 'menu-tile'
67
+ ? 'h-14 w-full justify-between rounded-none px-3.5 text-sm font-semibold hover:bg-muted/40'
68
+ : 'h-[4.25rem] w-full justify-between rounded-none px-5 text-base font-semibold hover:bg-transparent',
69
+ props.class,
70
+ )
71
+ "
72
+ >
73
+ <span
74
+ class="flex min-w-0 items-center"
75
+ :class="variant === 'menu-tile' ? 'gap-3' : 'gap-4'"
76
+ >
77
+ <span
78
+ class="flex shrink-0 items-center justify-center rounded-lg bg-muted/60 text-foreground"
79
+ :class="variant === 'menu-tile' ? 'size-8' : 'size-11'"
80
+ >
81
+ <Icon
82
+ :name="triggerIcon"
83
+ :class="variant === 'menu-tile' ? 'size-4' : 'size-5'"
84
+ aria-hidden="true"
85
+ />
86
+ </span>
87
+ <span class="truncate">{{
88
+ variant === "menu-tile" ? triggerLabel : t("theme.label")
89
+ }}</span>
90
+ </span>
91
+ <Icon
92
+ :name="variant === 'menu-tile' ? 'lucide:chevron-down' : 'lucide:chevron-right'"
93
+ :class="variant === 'menu-tile' ? 'size-4' : 'size-5'"
94
+ class="text-muted-foreground"
95
+ aria-hidden="true"
96
+ />
97
+ </Button>
98
+ <Button v-else variant="outline" size="icon" :class="cn('relative shrink-0', props.class)">
99
+ <Icon :name="nextExplicitColorModeIcon" class="size-[1.2rem]" aria-hidden="true" />
100
+ <span class="sr-only">{{ t("theme.toggle") }}</span>
101
+ </Button>
102
+ </DropdownMenuTrigger>
103
+ <DropdownMenuContent align="end" class="w-44">
104
+ <DropdownMenuRadioGroup
105
+ :model-value="colorMode.preference"
106
+ @update:model-value="setColorModePreference"
107
+ >
108
+ <DropdownMenuRadioItem
109
+ v-for="option in colorModeOptions"
110
+ :key="option.value"
111
+ :value="option.value"
112
+ class="gap-3"
113
+ >
114
+ <template #indicator-icon>
115
+ <Icon name="lucide:check" class="size-4" aria-hidden="true" />
116
+ </template>
117
+ <Icon :name="option.icon" class="size-4" aria-hidden="true" />
118
+ <span>{{ t(option.labelKey) }}</span>
119
+ </DropdownMenuRadioItem>
120
+ </DropdownMenuRadioGroup>
121
+ </DropdownMenuContent>
122
+ </DropdownMenu>
123
+ </template>
@@ -0,0 +1,56 @@
1
+ <script setup lang="ts">
2
+ import { useLocalStorage } from "@vueuse/core";
3
+ import { computed, ref, onMounted } from "vue";
4
+ import { useI18n } from "#imports";
5
+ import { useGinkoDocsConfig } from "#ginko-docs/composables/useGinkoDocsConfig";
6
+ import { useSiteNavigation } from "#ginko-docs/composables/useSiteNavigation";
7
+
8
+ const props = defineProps<{
9
+ landing?: boolean;
10
+ }>();
11
+
12
+ const { banner } = useSiteNavigation();
13
+ const { t } = useI18n();
14
+ const config = useGinkoDocsConfig();
15
+
16
+ const dismissed = useLocalStorage(banner.value.storageKey, false);
17
+ // Avoid a hydration mismatch: localStorage is client-only, so reveal after mount.
18
+ const hydrated = ref(false);
19
+ onMounted(() => {
20
+ hydrated.value = true;
21
+ });
22
+ const visible = computed(
23
+ () =>
24
+ banner.value.show &&
25
+ (!props.landing || config.banner.showOnLanding) &&
26
+ (!hydrated.value ? true : !dismissed.value),
27
+ );
28
+ </script>
29
+
30
+ <template>
31
+ <div
32
+ v-if="visible"
33
+ role="region"
34
+ :aria-label="banner.text"
35
+ class="relative z-30 bg-primary px-10 py-2.5 text-center text-sm leading-5 font-medium text-primary-foreground"
36
+ >
37
+ <span class="mx-auto block max-w-[min(100%,52rem)]">
38
+ {{ banner.text }}
39
+ <NuxtLink
40
+ v-if="banner.linkHref"
41
+ :to="banner.linkHref"
42
+ class="ml-1.5 inline-block underline underline-offset-2 opacity-90 transition-opacity hover:opacity-100"
43
+ >
44
+ {{ banner.linkLabel }}
45
+ </NuxtLink>
46
+ </span>
47
+ <button
48
+ type="button"
49
+ class="absolute top-1/2 right-2 flex size-8 -translate-y-1/2 items-center justify-center rounded p-1 opacity-70 transition-opacity hover:opacity-100"
50
+ :aria-label="t('banner.dismiss')"
51
+ @click="dismissed = true"
52
+ >
53
+ <Icon name="lucide:x" class="size-3.5" aria-hidden="true" />
54
+ </button>
55
+ </div>
56
+ </template>
@@ -0,0 +1,80 @@
1
+ <script setup lang="ts">
2
+ import { computed } from "vue";
3
+ import { useI18n } from "#imports";
4
+ import { useLocalizedPath } from "#ginko-docs/composables/useLocalizedPath";
5
+ import { useSiteNavigation } from "#ginko-docs/composables/useSiteNavigation";
6
+ import { useDocsEntryPath } from "#ginko-docs/features/docs/composables/useDocsEntryPath";
7
+
8
+ const { site, footerNav } = useSiteNavigation();
9
+ const { t } = useI18n();
10
+ const localizedPath = useLocalizedPath();
11
+ const docsEntryPath = await useDocsEntryPath();
12
+
13
+ const currentYearDate = new Date();
14
+ const resources = computed(() => [
15
+ { label: t("nav.documentation"), href: docsEntryPath.value, external: false },
16
+ ...footerNav.value.resources,
17
+ ]);
18
+ </script>
19
+
20
+ <template>
21
+ <footer class="border-t border-border bg-muted/30" :aria-label="t('nav.company')">
22
+ <div class="mx-auto max-w-5xl px-4 py-12 md:px-6 md:py-16">
23
+ <div class="flex flex-col gap-8 md:flex-row md:items-start md:justify-between">
24
+ <div class="max-w-sm">
25
+ <NuxtLink
26
+ :to="localizedPath('home')"
27
+ class="flex items-center gap-2 font-semibold text-foreground"
28
+ >
29
+ <SiteLogoMark />
30
+ <span>{{ site.name }}</span>
31
+ </NuxtLink>
32
+ <p class="mt-3 text-sm leading-relaxed text-muted-foreground">
33
+ {{ site.description }}
34
+ </p>
35
+ </div>
36
+ <div>
37
+ <h3 class="mb-3 text-sm font-semibold text-foreground">{{ t("nav.resources") }}</h3>
38
+ <ul class="space-y-2.5">
39
+ <li v-for="item in resources" :key="item.href">
40
+ <NuxtLink
41
+ :to="item.href"
42
+ :target="item.external ? '_blank' : undefined"
43
+ :rel="item.external ? 'noopener noreferrer' : undefined"
44
+ class="inline-flex items-center gap-1 text-sm text-muted-foreground transition-colors hover:text-foreground"
45
+ >
46
+ {{ item.label }}
47
+ <Icon
48
+ v-if="item.external"
49
+ name="lucide:arrow-up-right"
50
+ class="size-3 opacity-60"
51
+ aria-hidden="true"
52
+ />
53
+ </NuxtLink>
54
+ </li>
55
+ </ul>
56
+ </div>
57
+ </div>
58
+
59
+ <div
60
+ class="mt-10 flex flex-col items-center justify-between gap-3 border-t border-border pt-6 text-sm text-muted-foreground sm:flex-row"
61
+ >
62
+ <p>
63
+ © <NuxtTime :datetime="currentYearDate" year="numeric" /> {{ site.name }}.
64
+ {{ t("site.footer") }}
65
+ </p>
66
+ <p v-if="site.lupinumAttribution">
67
+ {{ t("site.openSourceBy") }}
68
+ <a
69
+ href="https://lupinum.com"
70
+ target="_blank"
71
+ rel="noopener noreferrer"
72
+ class="font-medium text-foreground underline decoration-border underline-offset-4 transition-colors hover:decoration-foreground"
73
+ >
74
+ Lupinum<span class="sr-only"> ({{ t("nav.externalLink") }})</span> </a
75
+ >.
76
+ </p>
77
+ </div>
78
+ </div>
79
+ </footer>
80
+ </template>