@lupinum/ginko-docs 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/README.md +130 -73
  2. package/app/app.config.ts +14 -12
  3. package/app/app.vue +2 -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 +19 -112
  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 +255 -132
  57. package/app/components/site/SiteHeroCode.vue +164 -0
  58. package/app/components/site/SiteLocaleSwitcher.vue +22 -4
  59. package/app/components/ui/badge/Badge.vue +1 -1
  60. package/app/components/ui/button/Button.vue +1 -1
  61. package/app/components/ui/collapsible/CollapsibleContent.vue +1 -1
  62. package/app/components/ui/dropdown-menu/DropdownMenuContent.vue +14 -4
  63. package/app/components/ui/dropdown-menu/DropdownMenuItem.vue +1 -1
  64. package/app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +1 -1
  65. package/app/components/ui/dropdown-menu/DropdownMenuSeparator.vue +1 -1
  66. package/app/components/ui/dropdown-menu/index.ts +0 -9
  67. package/app/components/ui/kbd/Kbd.vue +1 -1
  68. package/app/components/ui/kbd/index.ts +0 -1
  69. package/app/components/ui/scroll-area/ScrollArea.vue +1 -1
  70. package/app/components/ui/scroll-area/ScrollBar.vue +1 -1
  71. package/app/components/ui/separator/Separator.vue +1 -1
  72. package/app/components/ui/sheet/SheetContent.vue +1 -1
  73. package/app/components/ui/sheet/SheetDescription.vue +1 -1
  74. package/app/components/ui/sheet/SheetOverlay.vue +1 -1
  75. package/app/components/ui/sheet/SheetTitle.vue +1 -1
  76. package/app/components/ui/sheet/index.ts +0 -3
  77. package/app/components/ui/tabs/Tabs.vue +1 -1
  78. package/app/components/ui/tabs/TabsList.vue +1 -1
  79. package/app/components/ui/tabs/TabsTrigger.vue +1 -1
  80. package/app/components/ui/tabs/index.ts +0 -1
  81. package/app/composables/site-navigation.utils.ts +29 -11
  82. package/app/composables/useContentRouteAlternates.ts +13 -21
  83. package/app/composables/useLocalizedRouteSwitch.ts +1 -1
  84. package/app/composables/useMetaKey.ts +18 -0
  85. package/app/composables/useProseAppearance.ts +24 -0
  86. package/app/composables/useSiteNavigation.ts +3 -18
  87. package/app/config/site.utils.ts +1 -1
  88. package/app/error.vue +1 -5
  89. package/app/features/docs/components/DocsBreadcrumb.vue +37 -0
  90. package/app/features/docs/components/DocsContributeLinks.vue +28 -18
  91. package/app/features/docs/components/DocsMobileToc.vue +12 -8
  92. package/app/features/docs/components/DocsPageContent.vue +84 -47
  93. package/app/features/docs/components/DocsPageNav.vue +1 -1
  94. package/app/features/docs/components/DocsSidebar.vue +30 -20
  95. package/app/features/docs/components/DocsSidebarItem.vue +53 -100
  96. package/app/features/docs/components/DocsSidebarRow.vue +27 -0
  97. package/app/features/docs/components/DocsSidebarTabs.vue +46 -22
  98. package/app/features/docs/components/DocsToc.vue +80 -34
  99. package/app/features/docs/composables/useDocsNavigation.ts +2 -2
  100. package/app/features/docs/composables/useDocsNavigationData.ts +4 -1
  101. package/app/features/docs/composables/useRevealActive.ts +58 -0
  102. package/app/features/docs/composables/useScrollspy.ts +87 -19
  103. package/app/features/docs/docs-navigation.ts +17 -30
  104. package/app/features/docs/toc-context.ts +6 -0
  105. package/app/features/search/command-center.ts +11 -90
  106. package/app/features/search/components/SiteCommandCenter.vue +58 -38
  107. package/app/features/search/useCommandCenter.ts +46 -46
  108. package/app/layouts/docs.vue +1 -1
  109. package/app/lib/errors.ts +2 -49
  110. package/app/pages/blog/[slug].vue +16 -8
  111. package/app/pages/blog/index.vue +26 -13
  112. package/app/pages/docs/index.vue +10 -14
  113. package/app/pages/index.vue +174 -59
  114. package/app/plugins/ginko-docs-theme.ts +12 -0
  115. package/app/utils/content.ts +11 -0
  116. package/app/utils/file-icons.ts +90 -0
  117. package/app/utils/index.ts +7 -1
  118. package/app/utils/repository.ts +20 -0
  119. package/content.js +23 -26
  120. package/content.ts +30 -28
  121. package/i18n/messages/global/blog.ts +4 -0
  122. package/i18n/messages/global/command.ts +6 -2
  123. package/i18n/messages/global/docs.ts +3 -0
  124. package/i18n/messages/global/nav.ts +6 -2
  125. package/i18n/messages/index.ts +3 -3
  126. package/icon-bundle.ts +5 -0
  127. package/index.d.ts +7 -1
  128. package/nuxt.config.ts +2 -2
  129. package/package.json +9 -6
  130. package/runtime/server/agent-markdown.ts +0 -2
  131. package/shared/theme-palettes.ts +37 -0
  132. package/shared/types/app-config.ts +63 -6
  133. package/tags.ts +47 -59
  134. package/app/components/mdc/MdcAlert.vue +0 -34
  135. package/app/components/mdc/MdcCardGroup.vue +0 -47
  136. package/app/components/mdc/MdcDocImg.vue +0 -25
  137. package/app/components/mdc/MdcField.vue +0 -62
  138. package/app/components/mdc/MdcFieldGroup.vue +0 -18
  139. package/app/components/mdc/MdcPassage.vue +0 -32
  140. package/app/components/mdc/MdcQuizOption.vue +0 -91
  141. package/app/components/mdc/MdcReadMoreGroup.vue +0 -18
  142. package/app/components/mdc/MdcShortcut.vue +0 -46
  143. package/app/components/mdc/MdcStep.vue +0 -42
  144. package/app/components/ui/accordion/Accordion.vue +0 -15
  145. package/app/components/ui/accordion/AccordionContent.vue +0 -23
  146. package/app/components/ui/accordion/AccordionItem.vue +0 -24
  147. package/app/components/ui/accordion/AccordionTrigger.vue +0 -35
  148. package/app/components/ui/accordion/index.ts +0 -4
  149. package/app/components/ui/alert/Alert.vue +0 -21
  150. package/app/components/ui/alert/AlertDescription.vue +0 -22
  151. package/app/components/ui/alert/AlertTitle.vue +0 -22
  152. package/app/components/ui/alert/index.ts +0 -34
  153. package/app/components/ui/card/Card.vue +0 -16
  154. package/app/components/ui/card/CardAction.vue +0 -17
  155. package/app/components/ui/card/CardContent.vue +0 -14
  156. package/app/components/ui/card/CardDescription.vue +0 -14
  157. package/app/components/ui/card/CardFooter.vue +0 -14
  158. package/app/components/ui/card/CardHeader.vue +0 -22
  159. package/app/components/ui/card/card-variants.ts +0 -15
  160. package/app/components/ui/card/index.ts +0 -8
  161. package/app/components/ui/checkbox/Checkbox.vue +0 -35
  162. package/app/components/ui/checkbox/index.ts +0 -1
  163. package/app/components/ui/dialog/Dialog.vue +0 -15
  164. package/app/components/ui/dialog/DialogClose.vue +0 -12
  165. package/app/components/ui/dialog/DialogContent.vue +0 -69
  166. package/app/components/ui/dialog/DialogDescription.vue +0 -21
  167. package/app/components/ui/dialog/DialogFooter.vue +0 -15
  168. package/app/components/ui/dialog/DialogOverlay.vue +0 -26
  169. package/app/components/ui/dialog/DialogPortal.vue +0 -12
  170. package/app/components/ui/dialog/DialogTitle.vue +0 -21
  171. package/app/components/ui/dialog/DialogTrigger.vue +0 -12
  172. package/app/components/ui/dialog/index.ts +0 -10
  173. package/app/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +0 -36
  174. package/app/components/ui/dropdown-menu/DropdownMenuGroup.vue +0 -12
  175. package/app/components/ui/dropdown-menu/DropdownMenuLabel.vue +0 -25
  176. package/app/components/ui/dropdown-menu/DropdownMenuShortcut.vue +0 -17
  177. package/app/components/ui/dropdown-menu/DropdownMenuSub.vue +0 -15
  178. package/app/components/ui/dropdown-menu/DropdownMenuSubContent.vue +0 -29
  179. package/app/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +0 -31
  180. package/app/components/ui/input/Input.vue +0 -23
  181. package/app/components/ui/input/index.ts +0 -1
  182. package/app/components/ui/label/Label.vue +0 -14
  183. package/app/components/ui/label/index.ts +0 -1
  184. package/app/components/ui/sheet/SheetClose.vue +0 -12
  185. package/app/components/ui/sheet/SheetFooter.vue +0 -12
  186. package/app/components/ui/sheet/SheetHeader.vue +0 -12
  187. package/app/components/ui/switch/Switch.vue +0 -39
  188. package/app/components/ui/switch/index.ts +0 -1
  189. package/app/components/ui/tabs/TabsContent.vue +0 -21
  190. package/app/components/ui/textarea/Textarea.vue +0 -31
  191. package/app/components/ui/textarea/index.ts +0 -1
  192. package/app/features/docs/composables/useDocsEntryPath.ts +0 -13
  193. package/app/lib/utils.ts +0 -7
  194. package/i18n/messages/source.ts +0 -5
  195. package/runtime/markdown/inline-code-highlight.ts +0 -155
  196. package/runtime/utils.ts +0 -7
@@ -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";
@@ -6,6 +6,7 @@ export interface NavItem {
6
6
  href: string;
7
7
  external?: boolean;
8
8
  icon?: string;
9
+ description?: string;
9
10
  }
10
11
 
11
12
  export interface MainNavContext {
@@ -15,6 +16,8 @@ export interface MainNavContext {
15
16
  blogExists: boolean;
16
17
  docsLabel: string;
17
18
  blogLabel: string;
19
+ docsDescription?: string;
20
+ blogDescription?: string;
18
21
  }
19
22
 
20
23
  const isExternalHref = (href: string) => /^https?:\/\//.test(href);
@@ -30,21 +33,35 @@ export function resolveMainNav(
30
33
  label: getLocalizedSiteText(link.label, ctx.locale),
31
34
  href,
32
35
  external: isExternalHref(href),
36
+ icon: link.icon,
37
+ description: link.description
38
+ ? getLocalizedSiteText(link.description, ctx.locale)
39
+ : undefined,
33
40
  };
34
41
  });
35
42
  }
36
43
 
37
- const items: NavItem[] = [{ label: ctx.docsLabel, href: ctx.docsPath }];
44
+ const items: NavItem[] = [
45
+ {
46
+ label: ctx.docsLabel,
47
+ href: ctx.docsPath,
48
+ icon: "lucide:book-open",
49
+ description: ctx.docsDescription,
50
+ },
51
+ ];
38
52
  if (ctx.blogExists) {
39
- items.push({ label: ctx.blogLabel, href: ctx.blogPath });
53
+ items.push({
54
+ label: ctx.blogLabel,
55
+ href: ctx.blogPath,
56
+ icon: "lucide:file-text",
57
+ description: ctx.blogDescription,
58
+ });
40
59
  }
41
60
  return items;
42
61
  }
43
62
 
44
63
  export interface BannerContext {
45
64
  locale: string;
46
- blogPath: string;
47
- blogExists: boolean;
48
65
  defaultText: string;
49
66
  defaultLinkLabel: string;
50
67
  }
@@ -65,16 +82,17 @@ export function resolveBanner(
65
82
  config: GinkoDocsAppConfig["banner"],
66
83
  ctx: BannerContext,
67
84
  ): ResolvedBanner {
68
- const show = config.enabled === "auto" ? ctx.blogExists : config.enabled;
69
85
  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;
86
+ const linkHref = config.link?.to ? getLocalizedSiteText(config.link.to, ctx.locale) : undefined;
75
87
  const linkLabel = config.link?.label
76
88
  ? getLocalizedSiteText(config.link.label, ctx.locale)
77
89
  : ctx.defaultLinkLabel;
78
90
 
79
- return { show, text, linkLabel, linkHref, storageKey: bannerStorageKey(config.id) };
91
+ return {
92
+ show: config.enabled,
93
+ text,
94
+ linkLabel,
95
+ linkHref,
96
+ storageKey: bannerStorageKey(config.id),
97
+ };
80
98
  }
@@ -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
+ }
@@ -5,11 +5,6 @@ import { computed } from "vue";
5
5
  import { useGinkoDocsConfig } from "./useGinkoDocsConfig";
6
6
  import { resolveBanner, resolveMainNav, type NavItem } from "./site-navigation.utils";
7
7
 
8
- export interface SidebarSection {
9
- title: string;
10
- items: { label: string; href: string; badge?: string }[];
11
- }
12
-
13
8
  export const useSiteNavigation = () => {
14
9
  const config = useGinkoDocsConfig();
15
10
  const { locale, t } = useI18n();
@@ -32,8 +27,6 @@ export const useSiteNavigation = () => {
32
27
  const banner = computed(() =>
33
28
  resolveBanner(config.banner, {
34
29
  locale: locale.value,
35
- blogPath: blogPath.value,
36
- blogExists: blogExists.value,
37
30
  defaultText: t("banner.text"),
38
31
  defaultLinkLabel: t("banner.linkLabel"),
39
32
  }),
@@ -69,18 +62,10 @@ export const useSiteNavigation = () => {
69
62
  blogExists: blogExists.value,
70
63
  docsLabel: t("nav.documentation"),
71
64
  blogLabel: t("nav.blog"),
65
+ docsDescription: t("nav.docsDescription"),
66
+ blogDescription: t("nav.blogDescription"),
72
67
  }),
73
68
  );
74
69
 
75
- const footerNav = computed<{
76
- product: NavItem[];
77
- resources: NavItem[];
78
- company: NavItem[];
79
- }>(() => ({
80
- product: [],
81
- resources: [...socialLinks.value],
82
- company: [],
83
- }));
84
-
85
- return { site, banner, mainNav, footerNav, socialLinks };
70
+ return { site, banner, mainNav, socialLinks };
86
71
  };
@@ -2,7 +2,7 @@ import type { GinkoDocsLocalizedText } from "../../shared/types/app-config";
2
2
  import { defaultLocale, type LocaleCode } from "../../i18n/locales";
3
3
 
4
4
  export function getLocalizedSiteText(
5
- value: GinkoDocsLocalizedText | string,
5
+ value: GinkoDocsLocalizedText,
6
6
  locale: string,
7
7
  fallbackLocale: LocaleCode = defaultLocale,
8
8
  ): string {
package/app/error.vue CHANGED
@@ -4,7 +4,6 @@ import { messages } from "../i18n/messages/index";
4
4
  import { normalizeAppError } from "./lib/errors";
5
5
  import { computed } from "vue";
6
6
  import { clearError, useHead } from "#imports";
7
- import { useDocsEntryPath } from "#ginko-docs/features/docs/composables/useDocsEntryPath";
8
7
  import { localeFromPath, localizedPath } from "../i18n/locales";
9
8
 
10
9
  type NuxtErrorLike = {
@@ -29,8 +28,8 @@ const copy = computed(() => {
29
28
  const catalog = messages[locale.value].errors;
30
29
  return catalog[normalized.value.kind] ?? catalog.unavailable;
31
30
  });
32
- const docsPath = await useDocsEntryPath();
33
31
  const homePath = computed(() => localizedPath(locale.value, localizedRoutes[locale.value].home));
32
+ const docsPath = computed(() => localizedPath(locale.value, localizedRoutes[locale.value].docs));
34
33
  const title = computed(() => `${normalized.value.statusCode} - ${copy.value.title}`);
35
34
  // Keep the full site chrome for content-level errors (404 and friends); hard
36
35
  // server failures fall back to the bare page so the error screen cannot crash.
@@ -59,9 +58,6 @@ const handleError = () => clearError({ redirect: homePath.value });
59
58
  {{ normalized.statusCode }}
60
59
  </p>
61
60
 
62
- <p class="mb-3 text-sm font-medium tracking-wider text-primary uppercase">
63
- {{ normalized.statusMessage }}
64
- </p>
65
61
  <h1 class="mb-4 text-3xl font-semibold tracking-tight text-foreground sm:text-4xl">
66
62
  {{ copy.title }}
67
63
  </h1>
@@ -0,0 +1,37 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from "vue";
3
+ import { cn } from "#ginko-docs/utils";
4
+ import { useI18n } from "#imports";
5
+
6
+ const props = defineProps<{
7
+ items: { title: string; path?: string }[];
8
+ class?: HTMLAttributes["class"];
9
+ }>();
10
+
11
+ const { t } = useI18n();
12
+ </script>
13
+
14
+ <template>
15
+ <nav
16
+ v-if="items.length"
17
+ :class="cn('flex flex-wrap items-center gap-1.5 text-sm text-muted-foreground', props.class)"
18
+ :aria-label="t('docs.breadcrumbs')"
19
+ >
20
+ <template v-for="(item, index) in items" :key="item.path ?? item.title">
21
+ <Icon
22
+ v-if="index > 0"
23
+ name="lucide:chevron-right"
24
+ class="size-3.5 text-border"
25
+ aria-hidden="true"
26
+ />
27
+ <NuxtLink
28
+ v-if="item.path"
29
+ :to="item.path"
30
+ class="rounded-sm transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
31
+ >
32
+ {{ item.title }}
33
+ </NuxtLink>
34
+ <span v-else>{{ item.title }}</span>
35
+ </template>
36
+ </nav>
37
+ </template>
@@ -2,12 +2,19 @@
2
2
  import type { GinkoDocsAppConfig } from "../../../../shared/types/app-config";
3
3
  import { computed } from "vue";
4
4
  import { useAppConfig, useI18n, useRoute } from "#imports";
5
+ import { buildRepoEditUrl, buildRepoIssueUrl } from "#ginko-docs/utils/repository";
5
6
 
6
- const props = defineProps<{
7
- stem?: string;
8
- extension?: string;
9
- title: string;
10
- }>();
7
+ const props = withDefaults(
8
+ defineProps<{
9
+ stem?: string;
10
+ extension?: string;
11
+ title: string;
12
+ variant?: "article" | "rail";
13
+ }>(),
14
+ {
15
+ variant: "article",
16
+ },
17
+ );
11
18
 
12
19
  const route = useRoute();
13
20
  const { t } = useI18n();
@@ -20,25 +27,28 @@ const sourcePath = computed(() => {
20
27
  return `${directory}/${props.stem}.${extension}`;
21
28
  });
22
29
 
23
- const editUrl = computed(() => {
24
- if (!repository || !sourcePath.value) return null;
25
- const branch = repository.branch || "main";
26
- return `${repository.url.replace(/\/$/, "")}/edit/${encodeURIComponent(branch)}/${sourcePath.value}`;
27
- });
30
+ const editUrl = computed(() =>
31
+ repository && sourcePath.value ? buildRepoEditUrl(repository, sourcePath.value) : null,
32
+ );
28
33
 
29
- const issueUrl = computed(() => {
30
- if (!repository) return null;
31
- const url = new URL(`${repository.url.replace(/\/$/, "")}/issues/new`);
32
- url.searchParams.set("title", t("docs.issueTitle", { title: props.title }));
33
- url.searchParams.set("body", t("docs.issueBody", { path: route.path }));
34
- return url.toString();
35
- });
34
+ const issueUrl = computed(() =>
35
+ repository
36
+ ? buildRepoIssueUrl(repository, {
37
+ title: t("docs.issueTitle", { title: props.title }),
38
+ body: t("docs.issueBody", { path: route.path }),
39
+ })
40
+ : null,
41
+ );
36
42
  </script>
37
43
 
38
44
  <template>
39
45
  <div
40
46
  v-if="repository"
41
- class="mt-12 flex flex-wrap items-center gap-x-4 gap-y-2 border-t border-border pt-5 text-sm"
47
+ :class="
48
+ variant === 'rail'
49
+ ? 'flex flex-col gap-2.5 text-sm'
50
+ : 'flex flex-wrap items-center gap-x-4 gap-y-2 text-sm'
51
+ "
42
52
  :aria-label="t('docs.contribute')"
43
53
  >
44
54
  <a