@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,27 @@
1
+ <script setup lang="ts">
2
+ import type { SeparatorProps } from "reka-ui";
3
+ import type { HTMLAttributes } from "vue";
4
+ import { reactiveOmit } from "@vueuse/core";
5
+ import { Separator } from "reka-ui";
6
+ import { cn } from "#ginko-docs/lib/utils";
7
+
8
+ const props = withDefaults(defineProps<SeparatorProps & { class?: HTMLAttributes["class"] }>(), {
9
+ orientation: "horizontal",
10
+ decorative: true,
11
+ });
12
+
13
+ const delegatedProps = reactiveOmit(props, "class");
14
+ </script>
15
+
16
+ <template>
17
+ <Separator
18
+ data-slot="separator"
19
+ v-bind="delegatedProps"
20
+ :class="
21
+ cn(
22
+ 'shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px',
23
+ props.class,
24
+ )
25
+ "
26
+ />
27
+ </template>
@@ -0,0 +1 @@
1
+ export { default as Separator } from "./Separator.vue";
@@ -0,0 +1,15 @@
1
+ <script setup lang="ts">
2
+ import type { DialogRootEmits, DialogRootProps } from "reka-ui";
3
+ import { DialogRoot, useForwardPropsEmits } from "reka-ui";
4
+
5
+ const props = defineProps<DialogRootProps>();
6
+ const emits = defineEmits<DialogRootEmits>();
7
+
8
+ const forwarded = useForwardPropsEmits(props, emits);
9
+ </script>
10
+
11
+ <template>
12
+ <DialogRoot v-slot="slotProps" data-slot="sheet" v-bind="forwarded">
13
+ <slot v-bind="slotProps" />
14
+ </DialogRoot>
15
+ </template>
@@ -0,0 +1,12 @@
1
+ <script setup lang="ts">
2
+ import type { DialogCloseProps } from "reka-ui";
3
+ import { DialogClose } from "reka-ui";
4
+
5
+ const props = defineProps<DialogCloseProps>();
6
+ </script>
7
+
8
+ <template>
9
+ <DialogClose data-slot="sheet-close" v-bind="props">
10
+ <slot />
11
+ </DialogClose>
12
+ </template>
@@ -0,0 +1,59 @@
1
+ <script setup lang="ts">
2
+ import type { DialogContentEmits, DialogContentProps } from "reka-ui";
3
+ import type { HTMLAttributes } from "vue";
4
+ import { reactiveOmit } from "@vueuse/core";
5
+ import { DialogClose, DialogContent, DialogPortal, useForwardPropsEmits } from "reka-ui";
6
+ import { cn } from "#ginko-docs/lib/utils";
7
+ import SheetOverlay from "./SheetOverlay.vue";
8
+
9
+ interface SheetContentProps extends DialogContentProps {
10
+ class?: HTMLAttributes["class"];
11
+ side?: "top" | "right" | "bottom" | "left";
12
+ }
13
+
14
+ defineOptions({
15
+ inheritAttrs: false,
16
+ });
17
+
18
+ const props = withDefaults(defineProps<SheetContentProps>(), {
19
+ side: "right",
20
+ });
21
+ const emits = defineEmits<DialogContentEmits>();
22
+
23
+ const delegatedProps = reactiveOmit(props, "class", "side");
24
+
25
+ const forwarded = useForwardPropsEmits(delegatedProps, emits);
26
+ </script>
27
+
28
+ <template>
29
+ <DialogPortal>
30
+ <SheetOverlay />
31
+ <DialogContent
32
+ data-slot="sheet-content"
33
+ :class="
34
+ cn(
35
+ 'fixed z-[70] flex flex-col gap-4 bg-background shadow-lg transition ease-in-out data-[state=closed]:animate-out data-[state=closed]:duration-200 data-[state=open]:animate-in data-[state=open]:duration-200',
36
+ side === 'right' &&
37
+ 'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
38
+ side === 'left' &&
39
+ 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
40
+ side === 'top' &&
41
+ 'inset-x-0 top-0 h-auto border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
42
+ side === 'bottom' &&
43
+ 'inset-x-0 bottom-0 h-auto border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
44
+ props.class,
45
+ )
46
+ "
47
+ v-bind="{ ...$attrs, ...forwarded }"
48
+ >
49
+ <slot />
50
+
51
+ <DialogClose
52
+ class="absolute top-4 right-4 inline-flex size-8 items-center justify-center rounded-md text-muted-foreground transition-colors outline-none hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none"
53
+ >
54
+ <Icon name="lucide:x" class="size-4" aria-hidden="true" />
55
+ <span class="sr-only">Close</span>
56
+ </DialogClose>
57
+ </DialogContent>
58
+ </DialogPortal>
59
+ </template>
@@ -0,0 +1,21 @@
1
+ <script setup lang="ts">
2
+ import type { DialogDescriptionProps } from "reka-ui";
3
+ import type { HTMLAttributes } from "vue";
4
+ import { reactiveOmit } from "@vueuse/core";
5
+ import { DialogDescription } from "reka-ui";
6
+ import { cn } from "#ginko-docs/lib/utils";
7
+
8
+ const props = defineProps<DialogDescriptionProps & { class?: HTMLAttributes["class"] }>();
9
+
10
+ const delegatedProps = reactiveOmit(props, "class");
11
+ </script>
12
+
13
+ <template>
14
+ <DialogDescription
15
+ data-slot="sheet-description"
16
+ :class="cn('text-sm text-muted-foreground', props.class)"
17
+ v-bind="delegatedProps"
18
+ >
19
+ <slot />
20
+ </DialogDescription>
21
+ </template>
@@ -0,0 +1,12 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from "vue";
3
+ import { cn } from "#ginko-docs/lib/utils";
4
+
5
+ const props = defineProps<{ class?: HTMLAttributes["class"] }>();
6
+ </script>
7
+
8
+ <template>
9
+ <div data-slot="sheet-footer" :class="cn('mt-auto flex flex-col gap-2 p-4', props.class)">
10
+ <slot />
11
+ </div>
12
+ </template>
@@ -0,0 +1,12 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from "vue";
3
+ import { cn } from "#ginko-docs/lib/utils";
4
+
5
+ const props = defineProps<{ class?: HTMLAttributes["class"] }>();
6
+ </script>
7
+
8
+ <template>
9
+ <div data-slot="sheet-header" :class="cn('flex flex-col gap-1.5 p-4', props.class)">
10
+ <slot />
11
+ </div>
12
+ </template>
@@ -0,0 +1,26 @@
1
+ <script setup lang="ts">
2
+ import type { DialogOverlayProps } from "reka-ui";
3
+ import type { HTMLAttributes } from "vue";
4
+ import { reactiveOmit } from "@vueuse/core";
5
+ import { DialogOverlay } from "reka-ui";
6
+ import { cn } from "#ginko-docs/lib/utils";
7
+
8
+ const props = defineProps<DialogOverlayProps & { class?: HTMLAttributes["class"] }>();
9
+
10
+ const delegatedProps = reactiveOmit(props, "class");
11
+ </script>
12
+
13
+ <template>
14
+ <DialogOverlay
15
+ data-slot="sheet-overlay"
16
+ :class="
17
+ cn(
18
+ 'fixed inset-0 z-[60] bg-overlay data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0',
19
+ props.class,
20
+ )
21
+ "
22
+ v-bind="delegatedProps"
23
+ >
24
+ <slot />
25
+ </DialogOverlay>
26
+ </template>
@@ -0,0 +1,21 @@
1
+ <script setup lang="ts">
2
+ import type { DialogTitleProps } from "reka-ui";
3
+ import type { HTMLAttributes } from "vue";
4
+ import { reactiveOmit } from "@vueuse/core";
5
+ import { DialogTitle } from "reka-ui";
6
+ import { cn } from "#ginko-docs/lib/utils";
7
+
8
+ const props = defineProps<DialogTitleProps & { class?: HTMLAttributes["class"] }>();
9
+
10
+ const delegatedProps = reactiveOmit(props, "class");
11
+ </script>
12
+
13
+ <template>
14
+ <DialogTitle
15
+ data-slot="sheet-title"
16
+ :class="cn('font-semibold text-foreground', props.class)"
17
+ v-bind="delegatedProps"
18
+ >
19
+ <slot />
20
+ </DialogTitle>
21
+ </template>
@@ -0,0 +1,12 @@
1
+ <script setup lang="ts">
2
+ import type { DialogTriggerProps } from "reka-ui";
3
+ import { DialogTrigger } from "reka-ui";
4
+
5
+ const props = defineProps<DialogTriggerProps>();
6
+ </script>
7
+
8
+ <template>
9
+ <DialogTrigger data-slot="sheet-trigger" v-bind="props">
10
+ <slot />
11
+ </DialogTrigger>
12
+ </template>
@@ -0,0 +1,8 @@
1
+ export { default as Sheet } from "./Sheet.vue";
2
+ export { default as SheetClose } from "./SheetClose.vue";
3
+ export { default as SheetContent } from "./SheetContent.vue";
4
+ export { default as SheetDescription } from "./SheetDescription.vue";
5
+ export { default as SheetFooter } from "./SheetFooter.vue";
6
+ export { default as SheetHeader } from "./SheetHeader.vue";
7
+ export { default as SheetTitle } from "./SheetTitle.vue";
8
+ export { default as SheetTrigger } from "./SheetTrigger.vue";
@@ -0,0 +1,39 @@
1
+ <script setup lang="ts">
2
+ import type { SwitchRootEmits, SwitchRootProps } from "reka-ui";
3
+ import type { HTMLAttributes } from "vue";
4
+ import { reactiveOmit } from "@vueuse/core";
5
+ import { SwitchRoot, SwitchThumb, useForwardPropsEmits } from "reka-ui";
6
+ import { cn } from "#ginko-docs/lib/utils";
7
+
8
+ interface Props extends SwitchRootProps {
9
+ class?: HTMLAttributes["class"];
10
+ }
11
+
12
+ const props = defineProps<Props>();
13
+ const emits = defineEmits<SwitchRootEmits>();
14
+
15
+ const delegatedProps = reactiveOmit(props, "class");
16
+ const forwarded = useForwardPropsEmits(delegatedProps, emits);
17
+ </script>
18
+
19
+ <template>
20
+ <SwitchRoot
21
+ data-slot="switch"
22
+ v-bind="forwarded"
23
+ :class="
24
+ cn(
25
+ 'peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-xs transition-colors focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
26
+ props.class,
27
+ )
28
+ "
29
+ >
30
+ <SwitchThumb
31
+ data-slot="switch-thumb"
32
+ :class="
33
+ cn(
34
+ 'pointer-events-none block size-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0',
35
+ )
36
+ "
37
+ />
38
+ </SwitchRoot>
39
+ </template>
@@ -0,0 +1 @@
1
+ export { default as Switch } from "./Switch.vue";
@@ -0,0 +1,24 @@
1
+ <script setup lang="ts">
2
+ import type { TabsRootEmits, TabsRootProps } from "reka-ui";
3
+ import type { HTMLAttributes } from "vue";
4
+ import { reactiveOmit } from "@vueuse/core";
5
+ import { TabsRoot, useForwardPropsEmits } from "reka-ui";
6
+ import { cn } from "#ginko-docs/lib/utils";
7
+
8
+ const props = defineProps<TabsRootProps & { class?: HTMLAttributes["class"] }>();
9
+ const emits = defineEmits<TabsRootEmits>();
10
+
11
+ const delegatedProps = reactiveOmit(props, "class");
12
+ const forwarded = useForwardPropsEmits(delegatedProps, emits);
13
+ </script>
14
+
15
+ <template>
16
+ <TabsRoot
17
+ v-slot="slotProps"
18
+ data-slot="tabs"
19
+ v-bind="forwarded"
20
+ :class="cn('flex flex-col gap-2', props.class)"
21
+ >
22
+ <slot v-bind="slotProps" />
23
+ </TabsRoot>
24
+ </template>
@@ -0,0 +1,21 @@
1
+ <script setup lang="ts">
2
+ import type { TabsContentProps } from "reka-ui";
3
+ import type { HTMLAttributes } from "vue";
4
+ import { reactiveOmit } from "@vueuse/core";
5
+ import { TabsContent } from "reka-ui";
6
+ import { cn } from "#ginko-docs/lib/utils";
7
+
8
+ const props = defineProps<TabsContentProps & { class?: HTMLAttributes["class"] }>();
9
+
10
+ const delegatedProps = reactiveOmit(props, "class");
11
+ </script>
12
+
13
+ <template>
14
+ <TabsContent
15
+ data-slot="tabs-content"
16
+ :class="cn('flex-1 outline-none', props.class)"
17
+ v-bind="delegatedProps"
18
+ >
19
+ <slot />
20
+ </TabsContent>
21
+ </template>
@@ -0,0 +1,26 @@
1
+ <script setup lang="ts">
2
+ import type { TabsListProps } from "reka-ui";
3
+ import type { HTMLAttributes } from "vue";
4
+ import { reactiveOmit } from "@vueuse/core";
5
+ import { TabsList } from "reka-ui";
6
+ import { cn } from "#ginko-docs/lib/utils";
7
+
8
+ const props = defineProps<TabsListProps & { class?: HTMLAttributes["class"] }>();
9
+
10
+ const delegatedProps = reactiveOmit(props, "class");
11
+ </script>
12
+
13
+ <template>
14
+ <TabsList
15
+ data-slot="tabs-list"
16
+ v-bind="delegatedProps"
17
+ :class="
18
+ cn(
19
+ 'inline-flex h-9 w-fit items-center justify-center rounded-lg bg-muted p-[3px] text-muted-foreground',
20
+ props.class,
21
+ )
22
+ "
23
+ >
24
+ <slot />
25
+ </TabsList>
26
+ </template>
@@ -0,0 +1,28 @@
1
+ <script setup lang="ts">
2
+ import type { TabsTriggerProps } from "reka-ui";
3
+ import type { HTMLAttributes } from "vue";
4
+ import { reactiveOmit } from "@vueuse/core";
5
+ import { TabsTrigger, useForwardProps } from "reka-ui";
6
+ import { cn } from "#ginko-docs/lib/utils";
7
+
8
+ const props = defineProps<TabsTriggerProps & { class?: HTMLAttributes["class"] }>();
9
+
10
+ const delegatedProps = reactiveOmit(props, "class");
11
+
12
+ const forwardedProps = useForwardProps(delegatedProps);
13
+ </script>
14
+
15
+ <template>
16
+ <TabsTrigger
17
+ data-slot="tabs-trigger"
18
+ :class="
19
+ cn(
20
+ 'inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:shadow-sm dark:text-muted-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 dark:data-[state=active]:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
21
+ props.class,
22
+ )
23
+ "
24
+ v-bind="forwardedProps"
25
+ >
26
+ <slot />
27
+ </TabsTrigger>
28
+ </template>
@@ -0,0 +1,4 @@
1
+ export { default as Tabs } from "./Tabs.vue";
2
+ export { default as TabsContent } from "./TabsContent.vue";
3
+ export { default as TabsList } from "./TabsList.vue";
4
+ export { default as TabsTrigger } from "./TabsTrigger.vue";
@@ -0,0 +1,31 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from "vue";
3
+ import { ref } from "vue";
4
+ import { cn } from "#ginko-docs/lib/utils";
5
+ import { h } from "vue";
6
+
7
+ const model = defineModel<string>();
8
+ const textarea = ref<HTMLTextAreaElement | null>(null);
9
+
10
+ const props = defineProps<{
11
+ class?: HTMLAttributes["class"];
12
+ }>();
13
+
14
+ defineExpose({
15
+ focus: () => textarea.value?.focus(),
16
+ });
17
+ </script>
18
+
19
+ <template>
20
+ <textarea
21
+ ref="textarea"
22
+ v-model="model"
23
+ data-slot="textarea"
24
+ :class="
25
+ cn(
26
+ 'min-h-36 w-full resize-y rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground shadow-xs transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40',
27
+ props.class,
28
+ )
29
+ "
30
+ />
31
+ </template>
@@ -0,0 +1 @@
1
+ export { default as Textarea } from "./Textarea.vue";
@@ -0,0 +1,80 @@
1
+ import type { GinkoDocsAppConfig, GinkoDocsLink } from "../../shared/types/app-config";
2
+ import { getLocalizedSiteText } from "../config/site.utils";
3
+
4
+ export interface NavItem {
5
+ label: string;
6
+ href: string;
7
+ external?: boolean;
8
+ icon?: string;
9
+ }
10
+
11
+ export interface MainNavContext {
12
+ locale: string;
13
+ docsPath: string;
14
+ blogPath: string;
15
+ blogExists: boolean;
16
+ docsLabel: string;
17
+ blogLabel: string;
18
+ }
19
+
20
+ const isExternalHref = (href: string) => /^https?:\/\//.test(href);
21
+
22
+ export function resolveMainNav(
23
+ links: GinkoDocsAppConfig["nav"]["links"],
24
+ ctx: MainNavContext,
25
+ ): NavItem[] {
26
+ if (Array.isArray(links)) {
27
+ return links.map((link: GinkoDocsLink) => {
28
+ const href = getLocalizedSiteText(link.to, ctx.locale);
29
+ return {
30
+ label: getLocalizedSiteText(link.label, ctx.locale),
31
+ href,
32
+ external: isExternalHref(href),
33
+ };
34
+ });
35
+ }
36
+
37
+ const items: NavItem[] = [{ label: ctx.docsLabel, href: ctx.docsPath }];
38
+ if (ctx.blogExists) {
39
+ items.push({ label: ctx.blogLabel, href: ctx.blogPath });
40
+ }
41
+ return items;
42
+ }
43
+
44
+ export interface BannerContext {
45
+ locale: string;
46
+ blogPath: string;
47
+ blogExists: boolean;
48
+ defaultText: string;
49
+ defaultLinkLabel: string;
50
+ }
51
+
52
+ export interface ResolvedBanner {
53
+ show: boolean;
54
+ text: string;
55
+ linkLabel: string;
56
+ linkHref?: string;
57
+ storageKey: string;
58
+ }
59
+
60
+ export function bannerStorageKey(id: string) {
61
+ return `ginko-docs:banner:${id}`;
62
+ }
63
+
64
+ export function resolveBanner(
65
+ config: GinkoDocsAppConfig["banner"],
66
+ ctx: BannerContext,
67
+ ): ResolvedBanner {
68
+ const show = config.enabled === "auto" ? ctx.blogExists : config.enabled;
69
+ const text = config.text ? getLocalizedSiteText(config.text, ctx.locale) : ctx.defaultText;
70
+ const linkHref = config.link?.to
71
+ ? getLocalizedSiteText(config.link.to, ctx.locale)
72
+ : ctx.blogExists
73
+ ? ctx.blogPath
74
+ : undefined;
75
+ const linkLabel = config.link?.label
76
+ ? getLocalizedSiteText(config.link.label, ctx.locale)
77
+ : ctx.defaultLinkLabel;
78
+
79
+ return { show, text, linkLabel, linkHref, storageKey: bannerStorageKey(config.id) };
80
+ }
@@ -0,0 +1,9 @@
1
+ import { computed } from "vue";
2
+ import { useAppConfig, useRoute } from "#imports";
3
+
4
+ export function useCanonicalUrl() {
5
+ const route = useRoute();
6
+ const config = useAppConfig().ginkoDocs;
7
+
8
+ return computed(() => new URL(route.path, config.site.url).toString());
9
+ }
@@ -0,0 +1,43 @@
1
+ import type { ContentAlternate } from "@lupinum/ginko-content/client";
2
+ import { computed, watch, type Ref } from "vue";
3
+ import { useRoute, useState } from "#imports";
4
+
5
+ type ContentRouteAlternatesState = {
6
+ sourcePath: string;
7
+ alternates: ContentAlternate[];
8
+ };
9
+
10
+ export function useContentRouteAlternates() {
11
+ const route = useRoute();
12
+ const state = useState<ContentRouteAlternatesState>("content-route-alternates", () => ({
13
+ sourcePath: "",
14
+ alternates: [],
15
+ }));
16
+
17
+ const current = computed(() =>
18
+ state.value.sourcePath === route.path ? state.value.alternates : [],
19
+ );
20
+
21
+ return current;
22
+ }
23
+
24
+ export function syncContentRouteAlternates(
25
+ page: Ref<{ route?: { alternates?: ContentAlternate[] } } | null | undefined>,
26
+ ) {
27
+ const route = useRoute();
28
+ const state = useState<ContentRouteAlternatesState>("content-route-alternates", () => ({
29
+ sourcePath: "",
30
+ alternates: [],
31
+ }));
32
+
33
+ watch(
34
+ page,
35
+ (document) => {
36
+ state.value = {
37
+ sourcePath: route.path,
38
+ alternates: document?.route?.alternates ?? [],
39
+ };
40
+ },
41
+ { immediate: true },
42
+ );
43
+ }
@@ -0,0 +1,37 @@
1
+ import { useScriptPlausibleAnalytics } from "#imports";
2
+ import { useGinkoDocsConfig } from "./useGinkoDocsConfig";
3
+
4
+ export type AnalyticsProps = Record<string, string>;
5
+
6
+ export interface GinkoAnalytics {
7
+ enabled: boolean;
8
+ track: (event: string, props?: AnalyticsProps) => void;
9
+ }
10
+
11
+ /**
12
+ * Plausible analytics via the Nuxt Scripts registry script. Fully disabled
13
+ * unless `ginkoDocs.analytics.plausible.domain` is configured — without a
14
+ * domain nothing loads and `track()` is a no-op, so callers never guard.
15
+ */
16
+ export function useGinkoAnalytics(): GinkoAnalytics {
17
+ const config = useGinkoDocsConfig();
18
+ const plausible = config.analytics?.plausible;
19
+
20
+ if (!plausible?.domain) {
21
+ return { enabled: false, track: () => {} };
22
+ }
23
+
24
+ const script = useScriptPlausibleAnalytics({
25
+ domain: plausible.domain,
26
+ extension: plausible.extensions ?? ["outbound-links"],
27
+ ...(plausible.scriptSrc ? { scriptInput: { src: plausible.scriptSrc } } : {}),
28
+ });
29
+
30
+ return {
31
+ enabled: true,
32
+ track: (event, props) => {
33
+ if (!import.meta.client) return;
34
+ script.proxy.plausible(event, props ? { props } : undefined);
35
+ },
36
+ };
37
+ }
@@ -0,0 +1,6 @@
1
+ import { useAppConfig } from "#imports";
2
+ import type { GinkoDocsAppConfig } from "../../shared/types/app-config";
3
+
4
+ export function useGinkoDocsConfig(): GinkoDocsAppConfig {
5
+ return useAppConfig().ginkoDocs as GinkoDocsAppConfig;
6
+ }
@@ -0,0 +1,20 @@
1
+ import { defineOgImage } from "#imports";
2
+ import { useGinkoDocsConfig } from "./useGinkoDocsConfig";
3
+
4
+ export interface GinkoOgImageProps {
5
+ title?: string;
6
+ description?: string;
7
+ locale?: string;
8
+ [key: string]: unknown;
9
+ }
10
+
11
+ /**
12
+ * Declares the page's social image using the configured OgImage template.
13
+ * No-op when the consumer sets `ginkoDocs.ogImage.enabled: false`.
14
+ */
15
+ export function useGinkoOgImage(props?: GinkoOgImageProps) {
16
+ const config = useGinkoDocsConfig();
17
+ if (config.ogImage?.enabled === false) return;
18
+ const component = config.ogImage?.component ?? "GinkoDocs";
19
+ defineOgImage(component as never, props as never);
20
+ }
@@ -0,0 +1,12 @@
1
+ import { useI18n } from "#imports";
2
+ import { isLocaleCode, localizedPath } from "../../i18n/locales";
3
+ import type { LocalizedRouteKey } from "../../i18n/routes";
4
+
5
+ export function useLocalizedPath() {
6
+ const { locale, t } = useI18n();
7
+
8
+ return (key: LocalizedRouteKey) => {
9
+ const path = t(`routes.${key}`);
10
+ return isLocaleCode(locale.value) ? localizedPath(locale.value, path) : path;
11
+ };
12
+ }