@lupinum/ginko-docs 0.2.2 → 0.2.5

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 (206) hide show
  1. package/README.md +130 -73
  2. package/app/app.config.ts +15 -13
  3. package/app/app.vue +4 -2
  4. package/app/assets/css/prose.css +2203 -149
  5. package/app/assets/css/tailwind.css +60 -38
  6. package/app/assets/css/theme-palettes.css +309 -0
  7. package/app/components/content/DocumentPageShell.vue +1 -1
  8. package/app/components/content/Feedback.vue +12 -11
  9. package/app/components/content/ImageZoomDialog.vue +84 -0
  10. package/app/components/content/PageMarkdownCopy.vue +2 -0
  11. package/app/components/content/imageZoom.ts +25 -0
  12. package/app/components/mdc/MdcAccordion.vue +6 -3
  13. package/app/components/mdc/MdcAccordionItem.vue +7 -16
  14. package/app/components/mdc/MdcApi.vue +159 -0
  15. package/app/components/mdc/MdcAside.vue +6 -10
  16. package/app/components/mdc/MdcCallout.vue +9 -10
  17. package/app/components/mdc/MdcCard.vue +36 -47
  18. package/app/components/mdc/MdcCards.vue +30 -0
  19. package/app/components/mdc/MdcCodeGroup.vue +4 -0
  20. package/app/components/mdc/MdcCodeTree.vue +234 -0
  21. package/app/components/mdc/MdcCollapse.vue +61 -0
  22. package/app/components/mdc/MdcError.vue +8 -1
  23. package/app/components/mdc/MdcExcerpt.vue +26 -0
  24. package/app/components/mdc/MdcFigure.vue +54 -20
  25. package/app/components/mdc/MdcFiles.vue +120 -0
  26. package/app/components/mdc/MdcIdea.vue +2 -1
  27. package/app/components/mdc/MdcInfo.vue +2 -1
  28. package/app/components/mdc/MdcInlineToc.vue +51 -0
  29. package/app/components/{ui/dialog/DialogHeader.vue → mdc/MdcKbd.vue} +2 -4
  30. package/app/components/mdc/MdcNote.vue +2 -1
  31. package/app/components/mdc/MdcQuiz.vue +43 -83
  32. package/app/components/mdc/MdcQuizQuestion.vue +104 -109
  33. package/app/components/mdc/MdcReadMore.vue +57 -77
  34. package/app/components/mdc/MdcSteps.vue +45 -11
  35. package/app/components/mdc/MdcSuccess.vue +8 -1
  36. package/app/components/mdc/MdcTabs.vue +44 -26
  37. package/app/components/mdc/MdcTimeline.vue +4 -1
  38. package/app/components/mdc/MdcTimelineItem.vue +18 -27
  39. package/app/components/mdc/MdcWarning.vue +8 -1
  40. package/app/components/mdc/api.utils.ts +132 -0
  41. package/app/components/mdc/code-tree.utils.ts +22 -0
  42. package/app/components/mdc/quiz.utils.ts +38 -0
  43. package/app/components/prose/ProseBlockquote.vue +17 -1
  44. package/app/components/prose/ProseH1.vue +1 -1
  45. package/app/components/prose/ProseH2.vue +7 -2
  46. package/app/components/prose/ProseH3.vue +7 -2
  47. package/app/components/prose/ProseH4.vue +17 -2
  48. package/app/components/{ui/kbd/KbdGroup.vue → prose/ProseH5.vue} +4 -3
  49. package/app/components/{ui/card/CardTitle.vue → prose/ProseH6.vue} +4 -3
  50. package/app/components/prose/ProseImg.vue +34 -99
  51. package/app/components/prose/ProsePre.vue +63 -100
  52. package/app/components/prose/ProseTable.vue +5 -3
  53. package/app/components/site/ModeToggle.vue +12 -3
  54. package/app/components/site/SiteBanner.vue +43 -2
  55. package/app/components/site/SiteFooter.vue +3 -5
  56. package/app/components/site/SiteHeader.vue +266 -132
  57. package/app/components/site/SiteHeroCode.vue +164 -0
  58. package/app/components/site/SiteLocaleSwitcher.vue +25 -9
  59. package/app/components/site/SiteSocialLinks.vue +31 -0
  60. package/app/components/ui/badge/Badge.vue +1 -1
  61. package/app/components/ui/button/Button.vue +1 -1
  62. package/app/components/ui/collapsible/CollapsibleContent.vue +1 -1
  63. package/app/components/ui/dropdown-menu/DropdownMenuContent.vue +14 -4
  64. package/app/components/ui/dropdown-menu/DropdownMenuItem.vue +1 -1
  65. package/app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +1 -1
  66. package/app/components/ui/dropdown-menu/DropdownMenuSeparator.vue +1 -1
  67. package/app/components/ui/dropdown-menu/index.ts +0 -9
  68. package/app/components/ui/kbd/Kbd.vue +1 -1
  69. package/app/components/ui/kbd/index.ts +0 -1
  70. package/app/components/ui/scroll-area/ScrollArea.vue +1 -1
  71. package/app/components/ui/scroll-area/ScrollBar.vue +1 -1
  72. package/app/components/ui/separator/Separator.vue +1 -1
  73. package/app/components/ui/sheet/SheetContent.vue +1 -1
  74. package/app/components/ui/sheet/SheetDescription.vue +1 -1
  75. package/app/components/ui/sheet/SheetOverlay.vue +1 -1
  76. package/app/components/ui/sheet/SheetTitle.vue +1 -1
  77. package/app/components/ui/sheet/index.ts +0 -3
  78. package/app/components/ui/tabs/Tabs.vue +1 -1
  79. package/app/components/ui/tabs/TabsList.vue +1 -1
  80. package/app/components/ui/tabs/TabsTrigger.vue +1 -1
  81. package/app/components/ui/tabs/index.ts +0 -1
  82. package/app/composables/site-navigation.utils.ts +69 -12
  83. package/app/composables/useContentRouteAlternates.ts +13 -21
  84. package/app/composables/useLocalizedRouteSwitch.ts +1 -1
  85. package/app/composables/useMetaKey.ts +18 -0
  86. package/app/composables/useProseAppearance.ts +24 -0
  87. package/app/composables/useSiteNavigation.ts +10 -40
  88. package/app/config/site.utils.ts +1 -1
  89. package/app/error.vue +1 -5
  90. package/app/features/docs/components/DocsBreadcrumb.vue +37 -0
  91. package/app/features/docs/components/DocsContributeLinks.vue +28 -18
  92. package/app/features/docs/components/DocsMobileToc.vue +12 -8
  93. package/app/features/docs/components/DocsPageContent.vue +93 -47
  94. package/app/features/docs/components/DocsPageNav.vue +1 -1
  95. package/app/features/docs/components/DocsSidebar.vue +37 -21
  96. package/app/features/docs/components/DocsSidebarItem.vue +53 -100
  97. package/app/features/docs/components/DocsSidebarRow.vue +27 -0
  98. package/app/features/docs/components/DocsSidebarTabs.vue +46 -22
  99. package/app/features/docs/components/DocsToc.vue +80 -34
  100. package/app/features/docs/composables/useDocsNavigation.ts +2 -2
  101. package/app/features/docs/composables/useDocsNavigationData.ts +4 -1
  102. package/app/features/docs/composables/useRevealActive.ts +58 -0
  103. package/app/features/docs/composables/useScrollspy.ts +87 -19
  104. package/app/features/docs/docs-navigation.ts +17 -30
  105. package/app/features/docs/toc-context.ts +6 -0
  106. package/app/features/search/command-center.ts +11 -90
  107. package/app/features/search/components/SiteCommandCenter.vue +58 -38
  108. package/app/features/search/useCommandCenter.ts +48 -48
  109. package/app/layouts/docs.vue +1 -1
  110. package/app/lib/errors.ts +2 -49
  111. package/app/pages/blog/[slug].vue +26 -9
  112. package/app/pages/blog/index.vue +38 -14
  113. package/app/pages/docs/index.vue +10 -14
  114. package/app/pages/index.vue +180 -61
  115. package/app/plugins/ginko-docs-theme.ts +12 -0
  116. package/app/utils/content.ts +11 -0
  117. package/app/utils/file-icons.ts +90 -0
  118. package/app/utils/index.ts +7 -1
  119. package/app/utils/repository.ts +20 -0
  120. package/content.js +68 -50
  121. package/content.ts +51 -32
  122. package/i18n/messages/global/blog.ts +4 -0
  123. package/i18n/messages/global/command.ts +6 -2
  124. package/i18n/messages/global/docs.ts +4 -0
  125. package/i18n/messages/global/nav.ts +6 -3
  126. package/i18n/messages/index.ts +3 -3
  127. package/icon-bundle.ts +9 -56
  128. package/index.d.ts +7 -1
  129. package/modules/feature-routing.ts +15 -0
  130. package/nuxt.config.ts +21 -7
  131. package/package.json +29 -26
  132. package/runtime/server/agent-markdown.ts +0 -2
  133. package/server/api/_ginko-docs/redirects.get.ts +28 -0
  134. package/server/middleware/redirects.ts +22 -0
  135. package/server/routes/[locale]/blog/rss.xml.get.ts +13 -0
  136. package/server/routes/blog/rss.xml.get.ts +5 -0
  137. package/server/utils/blog-feed.ts +49 -0
  138. package/server/utils/feed.ts +70 -0
  139. package/server/utils/redirects.ts +38 -0
  140. package/server/utils/redirects.utils.ts +58 -0
  141. package/shared/theme-palettes.ts +37 -0
  142. package/shared/types/app-config.ts +75 -10
  143. package/tags.ts +47 -59
  144. package/app/components/mdc/MdcAlert.vue +0 -34
  145. package/app/components/mdc/MdcCardGroup.vue +0 -47
  146. package/app/components/mdc/MdcDocImg.vue +0 -25
  147. package/app/components/mdc/MdcField.vue +0 -62
  148. package/app/components/mdc/MdcFieldGroup.vue +0 -18
  149. package/app/components/mdc/MdcPassage.vue +0 -32
  150. package/app/components/mdc/MdcQuizOption.vue +0 -91
  151. package/app/components/mdc/MdcReadMoreGroup.vue +0 -18
  152. package/app/components/mdc/MdcShortcut.vue +0 -46
  153. package/app/components/mdc/MdcStep.vue +0 -42
  154. package/app/components/ui/accordion/Accordion.vue +0 -15
  155. package/app/components/ui/accordion/AccordionContent.vue +0 -23
  156. package/app/components/ui/accordion/AccordionItem.vue +0 -24
  157. package/app/components/ui/accordion/AccordionTrigger.vue +0 -35
  158. package/app/components/ui/accordion/index.ts +0 -4
  159. package/app/components/ui/alert/Alert.vue +0 -21
  160. package/app/components/ui/alert/AlertDescription.vue +0 -22
  161. package/app/components/ui/alert/AlertTitle.vue +0 -22
  162. package/app/components/ui/alert/index.ts +0 -34
  163. package/app/components/ui/card/Card.vue +0 -16
  164. package/app/components/ui/card/CardAction.vue +0 -17
  165. package/app/components/ui/card/CardContent.vue +0 -14
  166. package/app/components/ui/card/CardDescription.vue +0 -14
  167. package/app/components/ui/card/CardFooter.vue +0 -14
  168. package/app/components/ui/card/CardHeader.vue +0 -22
  169. package/app/components/ui/card/card-variants.ts +0 -15
  170. package/app/components/ui/card/index.ts +0 -8
  171. package/app/components/ui/checkbox/Checkbox.vue +0 -35
  172. package/app/components/ui/checkbox/index.ts +0 -1
  173. package/app/components/ui/dialog/Dialog.vue +0 -15
  174. package/app/components/ui/dialog/DialogClose.vue +0 -12
  175. package/app/components/ui/dialog/DialogContent.vue +0 -69
  176. package/app/components/ui/dialog/DialogDescription.vue +0 -21
  177. package/app/components/ui/dialog/DialogFooter.vue +0 -15
  178. package/app/components/ui/dialog/DialogOverlay.vue +0 -26
  179. package/app/components/ui/dialog/DialogPortal.vue +0 -12
  180. package/app/components/ui/dialog/DialogTitle.vue +0 -21
  181. package/app/components/ui/dialog/DialogTrigger.vue +0 -12
  182. package/app/components/ui/dialog/index.ts +0 -10
  183. package/app/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +0 -36
  184. package/app/components/ui/dropdown-menu/DropdownMenuGroup.vue +0 -12
  185. package/app/components/ui/dropdown-menu/DropdownMenuLabel.vue +0 -25
  186. package/app/components/ui/dropdown-menu/DropdownMenuShortcut.vue +0 -17
  187. package/app/components/ui/dropdown-menu/DropdownMenuSub.vue +0 -15
  188. package/app/components/ui/dropdown-menu/DropdownMenuSubContent.vue +0 -29
  189. package/app/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +0 -31
  190. package/app/components/ui/input/Input.vue +0 -23
  191. package/app/components/ui/input/index.ts +0 -1
  192. package/app/components/ui/label/Label.vue +0 -14
  193. package/app/components/ui/label/index.ts +0 -1
  194. package/app/components/ui/sheet/SheetClose.vue +0 -12
  195. package/app/components/ui/sheet/SheetFooter.vue +0 -12
  196. package/app/components/ui/sheet/SheetHeader.vue +0 -12
  197. package/app/components/ui/switch/Switch.vue +0 -39
  198. package/app/components/ui/switch/index.ts +0 -1
  199. package/app/components/ui/tabs/TabsContent.vue +0 -21
  200. package/app/components/ui/textarea/Textarea.vue +0 -31
  201. package/app/components/ui/textarea/index.ts +0 -1
  202. package/app/features/docs/composables/useDocsEntryPath.ts +0 -13
  203. package/app/lib/utils.ts +0 -7
  204. package/i18n/messages/source.ts +0 -5
  205. package/runtime/markdown/inline-code-highlight.ts +0 -155
  206. package/runtime/utils.ts +0 -7
@@ -7,13 +7,13 @@ import {
7
7
  DropdownMenuItem,
8
8
  DropdownMenuTrigger,
9
9
  } from "#ginko-docs/components/ui/dropdown-menu";
10
- import { cn } from "#ginko-docs/lib/utils";
10
+ import { cn } from "#ginko-docs/utils";
11
11
  import { computed } from "vue";
12
12
  import { useI18n } from "#imports";
13
13
  import { useLocalizedRouteSwitch } from "#ginko-docs/composables/useLocalizedRouteSwitch";
14
14
  import { locales as configuredLocales } from "../../../i18n/locales";
15
15
 
16
- type LocaleSwitcherVariant = "dropdown" | "menu-row" | "menu-tile" | "segmented";
16
+ type LocaleSwitcherVariant = "dropdown" | "menu-row" | "menu-tile" | "segmented" | "pill";
17
17
 
18
18
  type LocaleEntry = {
19
19
  code: string;
@@ -105,7 +105,7 @@ function trackLocaleNavigation(_entry: { code: string; current: boolean; to: unk
105
105
  <ClientOnly>
106
106
  <DropdownMenu>
107
107
  <DropdownMenuTrigger as-child>
108
- <Button variant="outline" class="h-9 gap-1.5 px-2.5" :aria-label="t('nav.language')">
108
+ <Button variant="ghost" class="h-9 gap-1.5 px-2.5" :aria-label="t('nav.language')">
109
109
  <Icon
110
110
  v-if="currentLocale?.flagIcon"
111
111
  :name="currentLocale.flagIcon"
@@ -151,21 +151,37 @@ function trackLocaleNavigation(_entry: { code: string; current: boolean; to: unk
151
151
  </DropdownMenu>
152
152
 
153
153
  <template #fallback>
154
- <span
155
- class="inline-flex h-9 w-[4.25rem] rounded-md border border-border bg-background"
156
- aria-hidden="true"
157
- />
154
+ <!-- Reserves the trigger's box before hydration. -->
155
+ <span class="inline-flex h-9 w-[4.25rem] rounded-md" aria-hidden="true" />
158
156
  </template>
159
157
  </ClientOnly>
160
158
  </div>
161
159
 
162
160
  <div
163
- v-else-if="localeLinks.length > 1 && (variant === 'menu-row' || variant === 'menu-tile')"
161
+ v-else-if="
162
+ localeLinks.length > 1 &&
163
+ (variant === 'menu-row' || variant === 'menu-tile' || variant === 'pill')
164
+ "
164
165
  :class="cn('items-center', props.class)"
165
166
  >
166
167
  <DropdownMenu>
167
168
  <DropdownMenuTrigger as-child>
168
169
  <Button
170
+ v-if="variant === 'pill'"
171
+ variant="outline"
172
+ class="h-9 gap-2 rounded-full px-4 text-sm font-semibold"
173
+ :aria-label="t('nav.language')"
174
+ >
175
+ <Icon
176
+ v-if="currentLocale?.flagIcon"
177
+ :name="currentLocale.flagIcon"
178
+ class="size-4 rounded-full"
179
+ aria-hidden="true"
180
+ />
181
+ <span>{{ currentLocale?.shortLabel }}</span>
182
+ </Button>
183
+ <Button
184
+ v-else
169
185
  variant="ghost"
170
186
  :class="
171
187
  cn(
@@ -204,7 +220,7 @@ function trackLocaleNavigation(_entry: { code: string; current: boolean; to: unk
204
220
  />
205
221
  </Button>
206
222
  </DropdownMenuTrigger>
207
- <DropdownMenuContent align="end" class="w-44">
223
+ <DropdownMenuContent align="end" class="w-44" portal-disabled>
208
224
  <DropdownMenuItem v-for="entry in localeLinks" :key="entry.code" as-child>
209
225
  <NuxtLink
210
226
  :to="entry.to"
@@ -0,0 +1,31 @@
1
+ <script setup lang="ts">
2
+ import { Button } from "#ginko-docs/components/ui/button";
3
+ import { useSiteNavigation } from "#ginko-docs/composables/useSiteNavigation";
4
+
5
+ const { socialLinks } = useSiteNavigation();
6
+
7
+ defineEmits<{ navigate: [] }>();
8
+ </script>
9
+
10
+ <template>
11
+ <div v-if="socialLinks.length" class="flex items-center">
12
+ <Button
13
+ v-for="link in socialLinks"
14
+ :key="link.href"
15
+ as-child
16
+ variant="ghost"
17
+ size="icon"
18
+ class="text-muted-foreground hover:text-foreground"
19
+ >
20
+ <NuxtLink
21
+ :to="link.href"
22
+ target="_blank"
23
+ rel="noopener noreferrer"
24
+ :aria-label="link.label"
25
+ @click="$emit('navigate')"
26
+ >
27
+ <Icon :name="link.icon ?? 'lucide:link'" class="size-[18px]" aria-hidden="true" />
28
+ </NuxtLink>
29
+ </Button>
30
+ </div>
31
+ </template>
@@ -4,7 +4,7 @@ import type { HTMLAttributes } from "vue";
4
4
  import type { BadgeVariants } from ".";
5
5
  import { reactiveOmit } from "@vueuse/core";
6
6
  import { Primitive } from "reka-ui";
7
- import { cn } from "#ginko-docs/lib/utils";
7
+ import { cn } from "#ginko-docs/utils";
8
8
  import { badgeVariants } from ".";
9
9
 
10
10
  const props = defineProps<
@@ -3,7 +3,7 @@ import type { PrimitiveProps } from "reka-ui";
3
3
  import type { HTMLAttributes } from "vue";
4
4
  import type { ButtonVariants } from ".";
5
5
  import { Primitive } from "reka-ui";
6
- import { cn } from "#ginko-docs/lib/utils";
6
+ import { cn } from "#ginko-docs/utils";
7
7
  import { buttonVariants } from ".";
8
8
 
9
9
  interface Props extends PrimitiveProps {
@@ -3,7 +3,7 @@ import type { CollapsibleContentProps } from "reka-ui";
3
3
  import type { HTMLAttributes } from "vue";
4
4
  import { reactiveOmit } from "@vueuse/core";
5
5
  import { CollapsibleContent } from "reka-ui";
6
- import { cn } from "#ginko-docs/lib/utils";
6
+ import { cn } from "#ginko-docs/utils";
7
7
 
8
8
  const props = defineProps<CollapsibleContentProps & { class?: HTMLAttributes["class"] }>();
9
9
 
@@ -3,27 +3,37 @@ import type { DropdownMenuContentEmits, DropdownMenuContentProps } from "reka-ui
3
3
  import type { HTMLAttributes } from "vue";
4
4
  import { reactiveOmit } from "@vueuse/core";
5
5
  import { DropdownMenuContent, DropdownMenuPortal, useForwardPropsEmits } from "reka-ui";
6
- import { cn } from "#ginko-docs/lib/utils";
6
+ import { cn } from "#ginko-docs/utils";
7
7
 
8
8
  defineOptions({
9
9
  inheritAttrs: false,
10
10
  });
11
11
 
12
12
  const props = withDefaults(
13
- defineProps<DropdownMenuContentProps & { class?: HTMLAttributes["class"] }>(),
13
+ defineProps<
14
+ DropdownMenuContentProps & {
15
+ class?: HTMLAttributes["class"];
16
+ /**
17
+ * Render in place instead of teleporting to body. Required inside modal
18
+ * dialogs (e.g. the mobile menu sheet), where body-level portals are
19
+ * blocked by the dialog's pointer-events lock.
20
+ */
21
+ portalDisabled?: boolean;
22
+ }
23
+ >(),
14
24
  {
15
25
  sideOffset: 4,
16
26
  },
17
27
  );
18
28
  const emits = defineEmits<DropdownMenuContentEmits>();
19
29
 
20
- const delegatedProps = reactiveOmit(props, "class");
30
+ const delegatedProps = reactiveOmit(props, "class", "portalDisabled");
21
31
 
22
32
  const forwarded = useForwardPropsEmits(delegatedProps, emits);
23
33
  </script>
24
34
 
25
35
  <template>
26
- <DropdownMenuPortal>
36
+ <DropdownMenuPortal :disabled="props.portalDisabled">
27
37
  <DropdownMenuContent
28
38
  data-slot="dropdown-menu-content"
29
39
  v-bind="{ ...$attrs, ...forwarded }"
@@ -3,7 +3,7 @@ import type { DropdownMenuItemProps } from "reka-ui";
3
3
  import type { HTMLAttributes } from "vue";
4
4
  import { reactiveOmit } from "@vueuse/core";
5
5
  import { DropdownMenuItem, useForwardProps } from "reka-ui";
6
- import { cn } from "#ginko-docs/lib/utils";
6
+ import { cn } from "#ginko-docs/utils";
7
7
 
8
8
  const props = withDefaults(
9
9
  defineProps<
@@ -3,7 +3,7 @@ import type { DropdownMenuRadioItemEmits, DropdownMenuRadioItemProps } from "rek
3
3
  import type { HTMLAttributes } from "vue";
4
4
  import { reactiveOmit } from "@vueuse/core";
5
5
  import { DropdownMenuItemIndicator, DropdownMenuRadioItem, useForwardPropsEmits } from "reka-ui";
6
- import { cn } from "#ginko-docs/lib/utils";
6
+ import { cn } from "#ginko-docs/utils";
7
7
 
8
8
  const props = defineProps<DropdownMenuRadioItemProps & { class?: HTMLAttributes["class"] }>();
9
9
 
@@ -3,7 +3,7 @@ import type { DropdownMenuSeparatorProps } from "reka-ui";
3
3
  import type { HTMLAttributes } from "vue";
4
4
  import { reactiveOmit } from "@vueuse/core";
5
5
  import { DropdownMenuSeparator } from "reka-ui";
6
- import { cn } from "#ginko-docs/lib/utils";
6
+ import { cn } from "#ginko-docs/utils";
7
7
 
8
8
  const props = defineProps<
9
9
  DropdownMenuSeparatorProps & {
@@ -1,16 +1,7 @@
1
1
  export { default as DropdownMenu } from "./DropdownMenu.vue";
2
-
3
- export { default as DropdownMenuCheckboxItem } from "./DropdownMenuCheckboxItem.vue";
4
2
  export { default as DropdownMenuContent } from "./DropdownMenuContent.vue";
5
- export { default as DropdownMenuGroup } from "./DropdownMenuGroup.vue";
6
3
  export { default as DropdownMenuItem } from "./DropdownMenuItem.vue";
7
- export { default as DropdownMenuLabel } from "./DropdownMenuLabel.vue";
8
4
  export { default as DropdownMenuRadioGroup } from "./DropdownMenuRadioGroup.vue";
9
5
  export { default as DropdownMenuRadioItem } from "./DropdownMenuRadioItem.vue";
10
6
  export { default as DropdownMenuSeparator } from "./DropdownMenuSeparator.vue";
11
- export { default as DropdownMenuShortcut } from "./DropdownMenuShortcut.vue";
12
- export { default as DropdownMenuSub } from "./DropdownMenuSub.vue";
13
- export { default as DropdownMenuSubContent } from "./DropdownMenuSubContent.vue";
14
- export { default as DropdownMenuSubTrigger } from "./DropdownMenuSubTrigger.vue";
15
7
  export { default as DropdownMenuTrigger } from "./DropdownMenuTrigger.vue";
16
- export { DropdownMenuPortal } from "reka-ui";
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import type { HTMLAttributes } from "vue";
3
- import { cn } from "#ginko-docs/lib/utils";
3
+ import { cn } from "#ginko-docs/utils";
4
4
 
5
5
  const props = defineProps<{
6
6
  class?: HTMLAttributes["class"];
@@ -2,7 +2,6 @@ import type { VariantProps } from "class-variance-authority";
2
2
  import { cva } from "class-variance-authority";
3
3
 
4
4
  export { default as Kbd } from "./Kbd.vue";
5
- export { default as KbdGroup } from "./KbdGroup.vue";
6
5
 
7
6
  export const kbdVariants = cva(
8
7
  "bg-muted text-muted-foreground ring-1 ring-border pointer-events-none inline-flex w-fit items-center justify-center gap-1 rounded-sm font-sans font-medium select-none [&_svg:not([class*='size-'])]:size-3 [[data-slot=tooltip-content]_&]:bg-background/20 [[data-slot=tooltip-content]_&]:text-background dark:[[data-slot=tooltip-content]_&]:bg-background/10",
@@ -3,7 +3,7 @@ import type { ScrollAreaRootProps } from "reka-ui";
3
3
  import type { HTMLAttributes } from "vue";
4
4
  import { reactiveOmit } from "@vueuse/core";
5
5
  import { ScrollAreaCorner, ScrollAreaRoot, ScrollAreaViewport } from "reka-ui";
6
- import { cn } from "#ginko-docs/lib/utils";
6
+ import { cn } from "#ginko-docs/utils";
7
7
  import ScrollBar from "./ScrollBar.vue";
8
8
 
9
9
  const props = defineProps<
@@ -3,7 +3,7 @@ import type { ScrollAreaScrollbarProps } from "reka-ui";
3
3
  import type { HTMLAttributes } from "vue";
4
4
  import { reactiveOmit } from "@vueuse/core";
5
5
  import { ScrollAreaScrollbar, ScrollAreaThumb } from "reka-ui";
6
- import { cn } from "#ginko-docs/lib/utils";
6
+ import { cn } from "#ginko-docs/utils";
7
7
 
8
8
  const props = withDefaults(
9
9
  defineProps<ScrollAreaScrollbarProps & { class?: HTMLAttributes["class"] }>(),
@@ -3,7 +3,7 @@ import type { SeparatorProps } from "reka-ui";
3
3
  import type { HTMLAttributes } from "vue";
4
4
  import { reactiveOmit } from "@vueuse/core";
5
5
  import { Separator } from "reka-ui";
6
- import { cn } from "#ginko-docs/lib/utils";
6
+ import { cn } from "#ginko-docs/utils";
7
7
 
8
8
  const props = withDefaults(defineProps<SeparatorProps & { class?: HTMLAttributes["class"] }>(), {
9
9
  orientation: "horizontal",
@@ -3,7 +3,7 @@ import type { DialogContentEmits, DialogContentProps } from "reka-ui";
3
3
  import type { HTMLAttributes } from "vue";
4
4
  import { reactiveOmit } from "@vueuse/core";
5
5
  import { DialogClose, DialogContent, DialogPortal, useForwardPropsEmits } from "reka-ui";
6
- import { cn } from "#ginko-docs/lib/utils";
6
+ import { cn } from "#ginko-docs/utils";
7
7
  import SheetOverlay from "./SheetOverlay.vue";
8
8
 
9
9
  interface SheetContentProps extends DialogContentProps {
@@ -3,7 +3,7 @@ import type { DialogDescriptionProps } from "reka-ui";
3
3
  import type { HTMLAttributes } from "vue";
4
4
  import { reactiveOmit } from "@vueuse/core";
5
5
  import { DialogDescription } from "reka-ui";
6
- import { cn } from "#ginko-docs/lib/utils";
6
+ import { cn } from "#ginko-docs/utils";
7
7
 
8
8
  const props = defineProps<DialogDescriptionProps & { class?: HTMLAttributes["class"] }>();
9
9
 
@@ -3,7 +3,7 @@ import type { DialogOverlayProps } from "reka-ui";
3
3
  import type { HTMLAttributes } from "vue";
4
4
  import { reactiveOmit } from "@vueuse/core";
5
5
  import { DialogOverlay } from "reka-ui";
6
- import { cn } from "#ginko-docs/lib/utils";
6
+ import { cn } from "#ginko-docs/utils";
7
7
 
8
8
  const props = defineProps<DialogOverlayProps & { class?: HTMLAttributes["class"] }>();
9
9
 
@@ -3,7 +3,7 @@ import type { DialogTitleProps } from "reka-ui";
3
3
  import type { HTMLAttributes } from "vue";
4
4
  import { reactiveOmit } from "@vueuse/core";
5
5
  import { DialogTitle } from "reka-ui";
6
- import { cn } from "#ginko-docs/lib/utils";
6
+ import { cn } from "#ginko-docs/utils";
7
7
 
8
8
  const props = defineProps<DialogTitleProps & { class?: HTMLAttributes["class"] }>();
9
9
 
@@ -1,8 +1,5 @@
1
1
  export { default as Sheet } from "./Sheet.vue";
2
- export { default as SheetClose } from "./SheetClose.vue";
3
2
  export { default as SheetContent } from "./SheetContent.vue";
4
3
  export { default as SheetDescription } from "./SheetDescription.vue";
5
- export { default as SheetFooter } from "./SheetFooter.vue";
6
- export { default as SheetHeader } from "./SheetHeader.vue";
7
4
  export { default as SheetTitle } from "./SheetTitle.vue";
8
5
  export { default as SheetTrigger } from "./SheetTrigger.vue";
@@ -3,7 +3,7 @@ import type { TabsRootEmits, TabsRootProps } from "reka-ui";
3
3
  import type { HTMLAttributes } from "vue";
4
4
  import { reactiveOmit } from "@vueuse/core";
5
5
  import { TabsRoot, useForwardPropsEmits } from "reka-ui";
6
- import { cn } from "#ginko-docs/lib/utils";
6
+ import { cn } from "#ginko-docs/utils";
7
7
 
8
8
  const props = defineProps<TabsRootProps & { class?: HTMLAttributes["class"] }>();
9
9
  const emits = defineEmits<TabsRootEmits>();
@@ -3,7 +3,7 @@ import type { TabsListProps } from "reka-ui";
3
3
  import type { HTMLAttributes } from "vue";
4
4
  import { reactiveOmit } from "@vueuse/core";
5
5
  import { TabsList } from "reka-ui";
6
- import { cn } from "#ginko-docs/lib/utils";
6
+ import { cn } from "#ginko-docs/utils";
7
7
 
8
8
  const props = defineProps<TabsListProps & { class?: HTMLAttributes["class"] }>();
9
9
 
@@ -3,7 +3,7 @@ import type { TabsTriggerProps } from "reka-ui";
3
3
  import type { HTMLAttributes } from "vue";
4
4
  import { reactiveOmit } from "@vueuse/core";
5
5
  import { TabsTrigger, useForwardProps } from "reka-ui";
6
- import { cn } from "#ginko-docs/lib/utils";
6
+ import { cn } from "#ginko-docs/utils";
7
7
 
8
8
  const props = defineProps<TabsTriggerProps & { class?: HTMLAttributes["class"] }>();
9
9
 
@@ -1,4 +1,3 @@
1
1
  export { default as Tabs } from "./Tabs.vue";
2
- export { default as TabsContent } from "./TabsContent.vue";
3
2
  export { default as TabsList } from "./TabsList.vue";
4
3
  export { default as TabsTrigger } from "./TabsTrigger.vue";
@@ -1,4 +1,8 @@
1
- import type { GinkoDocsAppConfig, GinkoDocsLink } from "../../shared/types/app-config";
1
+ import type {
2
+ GinkoDocsAppConfig,
3
+ GinkoDocsLink,
4
+ GinkoDocsSocialPlatform,
5
+ } from "../../shared/types/app-config";
2
6
  import { getLocalizedSiteText } from "../config/site.utils";
3
7
 
4
8
  export interface NavItem {
@@ -6,6 +10,9 @@ export interface NavItem {
6
10
  href: string;
7
11
  external?: boolean;
8
12
  icon?: string;
13
+ description?: string;
14
+ /** Set on social links so features can find a platform without matching its icon. */
15
+ platform?: GinkoDocsSocialPlatform;
9
16
  }
10
17
 
11
18
  export interface MainNavContext {
@@ -15,6 +22,8 @@ export interface MainNavContext {
15
22
  blogExists: boolean;
16
23
  docsLabel: string;
17
24
  blogLabel: string;
25
+ docsDescription?: string;
26
+ blogDescription?: string;
18
27
  }
19
28
 
20
29
  const isExternalHref = (href: string) => /^https?:\/\//.test(href);
@@ -30,21 +39,68 @@ export function resolveMainNav(
30
39
  label: getLocalizedSiteText(link.label, ctx.locale),
31
40
  href,
32
41
  external: isExternalHref(href),
42
+ icon: link.icon,
43
+ description: link.description
44
+ ? getLocalizedSiteText(link.description, ctx.locale)
45
+ : undefined,
33
46
  };
34
47
  });
35
48
  }
36
49
 
37
- const items: NavItem[] = [{ label: ctx.docsLabel, href: ctx.docsPath }];
50
+ const items: NavItem[] = [
51
+ {
52
+ label: ctx.docsLabel,
53
+ href: ctx.docsPath,
54
+ icon: "lucide:book-open",
55
+ description: ctx.docsDescription,
56
+ },
57
+ ];
38
58
  if (ctx.blogExists) {
39
- items.push({ label: ctx.blogLabel, href: ctx.blogPath });
59
+ items.push({
60
+ label: ctx.blogLabel,
61
+ href: ctx.blogPath,
62
+ icon: "lucide:file-text",
63
+ description: ctx.blogDescription,
64
+ });
40
65
  }
41
66
  return items;
42
67
  }
43
68
 
69
+ /**
70
+ * Brand names are not translated, so the labels are literals. The icons are
71
+ * bundled defaults — Lucide has no Discord mark, so chat is the closest generic
72
+ * stand-in and sites that ship the real one override `icon` per entry.
73
+ */
74
+ const socialDefaults: Record<GinkoDocsSocialPlatform, { label: string; icon: string }> = {
75
+ github: { label: "GitHub", icon: "lucide:github" },
76
+ discord: { label: "Discord", icon: "lucide:message-circle" },
77
+ linkedin: { label: "LinkedIn", icon: "lucide:linkedin" },
78
+ };
79
+
80
+ export function resolveSocialLinks(social: GinkoDocsAppConfig["social"]): NavItem[] {
81
+ // Config order is render order, so a site can list Discord ahead of GitHub.
82
+ return Object.entries(social).flatMap<NavItem>(([key, entry]) => {
83
+ const platform = key as GinkoDocsSocialPlatform;
84
+ const defaults = socialDefaults[platform];
85
+ if (!defaults || !entry) return [];
86
+
87
+ const link = typeof entry === "string" ? { href: entry } : entry;
88
+ if (!link.href) return [];
89
+
90
+ return [
91
+ {
92
+ label: link.label ?? defaults.label,
93
+ href: link.href,
94
+ external: true,
95
+ icon: link.icon ?? defaults.icon,
96
+ platform,
97
+ },
98
+ ];
99
+ });
100
+ }
101
+
44
102
  export interface BannerContext {
45
103
  locale: string;
46
- blogPath: string;
47
- blogExists: boolean;
48
104
  defaultText: string;
49
105
  defaultLinkLabel: string;
50
106
  }
@@ -65,16 +121,17 @@ export function resolveBanner(
65
121
  config: GinkoDocsAppConfig["banner"],
66
122
  ctx: BannerContext,
67
123
  ): ResolvedBanner {
68
- const show = config.enabled === "auto" ? ctx.blogExists : config.enabled;
69
124
  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;
125
+ const linkHref = config.link?.to ? getLocalizedSiteText(config.link.to, ctx.locale) : undefined;
75
126
  const linkLabel = config.link?.label
76
127
  ? getLocalizedSiteText(config.link.label, ctx.locale)
77
128
  : ctx.defaultLinkLabel;
78
129
 
79
- return { show, text, linkLabel, linkHref, storageKey: bannerStorageKey(config.id) };
130
+ return {
131
+ show: config.enabled,
132
+ text,
133
+ linkLabel,
134
+ linkHref,
135
+ storageKey: bannerStorageKey(config.id),
136
+ };
80
137
  }
@@ -18,26 +18,18 @@ export function useContentRouteAlternates() {
18
18
  state.value.sourcePath === route.path ? state.value.alternates : [],
19
19
  );
20
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
- }));
21
+ function sync(page: Ref<{ route?: { alternates?: ContentAlternate[] } } | null | undefined>) {
22
+ watch(
23
+ page,
24
+ (document) => {
25
+ state.value = {
26
+ sourcePath: route.path,
27
+ alternates: document?.route?.alternates ?? [],
28
+ };
29
+ },
30
+ { immediate: true },
31
+ );
32
+ }
32
33
 
33
- watch(
34
- page,
35
- (document) => {
36
- state.value = {
37
- sourcePath: route.path,
38
- alternates: document?.route?.alternates ?? [],
39
- };
40
- },
41
- { immediate: true },
42
- );
34
+ return { current, sync };
43
35
  }
@@ -6,7 +6,7 @@ import { isLocaleCode } from "../../i18n/locales";
6
6
  export function useLocalizedRouteSwitch() {
7
7
  const route = useRoute();
8
8
  const switchLocalePath = useSwitchLocalePath();
9
- const alternates = useContentRouteAlternates();
9
+ const { current: alternates } = useContentRouteAlternates();
10
10
 
11
11
  function switchPathname(targetLocale: string) {
12
12
  if (!isLocaleCode(targetLocale)) {
@@ -0,0 +1,18 @@
1
+ import { onMounted, ref, type Ref } from "vue";
2
+
3
+ /**
4
+ * Platform-aware modifier key label. Defaults to "Ctrl" during SSR and
5
+ * upgrades to "⌘" on Apple platforms after mount (onMounted is client-only,
6
+ * so hydration stays consistent).
7
+ */
8
+ export function useMetaKey(): Ref<string> {
9
+ const metaSymbol = ref("Ctrl");
10
+
11
+ onMounted(() => {
12
+ if (typeof navigator !== "undefined" && navigator.userAgent.includes("Macintosh")) {
13
+ metaSymbol.value = "⌘";
14
+ }
15
+ });
16
+
17
+ return metaSymbol;
18
+ }
@@ -0,0 +1,24 @@
1
+ import { computed, type MaybeRefOrGetter, toValue } from "vue";
2
+ import type { GinkoDocsProseAppearance, GinkoDocsProseFamily } from "../../shared/types/app-config";
3
+ import { useGinkoDocsConfig } from "./useGinkoDocsConfig";
4
+
5
+ const DEFAULT_APPEARANCE: GinkoDocsProseAppearance = "quiet";
6
+
7
+ function normalizeAppearance(value: unknown): GinkoDocsProseAppearance | undefined {
8
+ return value === "quiet" || value === "tint" ? value : undefined;
9
+ }
10
+
11
+ export function useProseAppearance(
12
+ family: GinkoDocsProseFamily,
13
+ appearance?: MaybeRefOrGetter<string | undefined>,
14
+ ) {
15
+ const config = useGinkoDocsConfig();
16
+
17
+ return computed<GinkoDocsProseAppearance>(
18
+ () =>
19
+ normalizeAppearance(toValue(appearance)) ??
20
+ normalizeAppearance(config.prose?.components?.[family]) ??
21
+ normalizeAppearance(config.prose?.appearance) ??
22
+ DEFAULT_APPEARANCE,
23
+ );
24
+ }