@baybreezy/docd 0.0.1

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 (268) hide show
  1. package/app/app.config.ts +239 -0
  2. package/app/app.vue +19 -0
  3. package/app/assets/css/tailwind.css +335 -0
  4. package/app/assets/css/theme.css +1171 -0
  5. package/app/components/OgImage/Docs.takumi.vue +69 -0
  6. package/app/components/OgImage/Landing.takumi.vue +64 -0
  7. package/app/components/OgImage/Site.takumi.vue +32 -0
  8. package/app/components/Ui/Avatar/Avatar.vue +75 -0
  9. package/app/components/Ui/Avatar/Badge.vue +34 -0
  10. package/app/components/Ui/Avatar/Fallback.vue +30 -0
  11. package/app/components/Ui/Avatar/Group.vue +29 -0
  12. package/app/components/Ui/Avatar/GroupCount.vue +29 -0
  13. package/app/components/Ui/Avatar/Image.vue +29 -0
  14. package/app/components/Ui/Badge.vue +86 -0
  15. package/app/components/Ui/BadgeGroup.vue +314 -0
  16. package/app/components/Ui/BgPattern/Grid.vue +137 -0
  17. package/app/components/Ui/BorderBeam.vue +97 -0
  18. package/app/components/Ui/Breadcrumbs.vue +103 -0
  19. package/app/components/Ui/Button.vue +169 -0
  20. package/app/components/Ui/ButtonGroup/ButtonGroup.vue +54 -0
  21. package/app/components/Ui/ButtonGroup/Separator.vue +38 -0
  22. package/app/components/Ui/ButtonGroup/Text.vue +33 -0
  23. package/app/components/Ui/Card/Action.vue +31 -0
  24. package/app/components/Ui/Card/Card.vue +58 -0
  25. package/app/components/Ui/Card/Content.vue +34 -0
  26. package/app/components/Ui/Card/Description.vue +36 -0
  27. package/app/components/Ui/Card/Footer.vue +31 -0
  28. package/app/components/Ui/Card/Header.vue +31 -0
  29. package/app/components/Ui/Card/Title.vue +37 -0
  30. package/app/components/Ui/Collapsible/Collapsible.vue +16 -0
  31. package/app/components/Ui/Collapsible/Content.vue +27 -0
  32. package/app/components/Ui/Collapsible/Trigger.vue +13 -0
  33. package/app/components/Ui/Command/Command.vue +115 -0
  34. package/app/components/Ui/Command/Dialog.vue +65 -0
  35. package/app/components/Ui/Command/Empty.vue +32 -0
  36. package/app/components/Ui/Command/Footer.vue +40 -0
  37. package/app/components/Ui/Command/Group.vue +58 -0
  38. package/app/components/Ui/Command/Input.vue +57 -0
  39. package/app/components/Ui/Command/Item.vue +102 -0
  40. package/app/components/Ui/Command/List.vue +33 -0
  41. package/app/components/Ui/Command/Separator.vue +30 -0
  42. package/app/components/Ui/Command/Shortcut.vue +28 -0
  43. package/app/components/Ui/Container.vue +46 -0
  44. package/app/components/Ui/ContextMenu/Arrow.vue +43 -0
  45. package/app/components/Ui/ContextMenu/CheckboxItem.vue +42 -0
  46. package/app/components/Ui/ContextMenu/Content.vue +53 -0
  47. package/app/components/Ui/ContextMenu/ContextMenu.vue +14 -0
  48. package/app/components/Ui/ContextMenu/Group.vue +12 -0
  49. package/app/components/Ui/ContextMenu/Item.vue +65 -0
  50. package/app/components/Ui/ContextMenu/ItemIndicator.vue +30 -0
  51. package/app/components/Ui/ContextMenu/Label.vue +35 -0
  52. package/app/components/Ui/ContextMenu/Portal.vue +12 -0
  53. package/app/components/Ui/ContextMenu/RadioGroup.vue +16 -0
  54. package/app/components/Ui/ContextMenu/RadioItem.vue +40 -0
  55. package/app/components/Ui/ContextMenu/Separator.vue +25 -0
  56. package/app/components/Ui/ContextMenu/Shortcut.vue +30 -0
  57. package/app/components/Ui/ContextMenu/Sub.vue +15 -0
  58. package/app/components/Ui/ContextMenu/SubContent.vue +55 -0
  59. package/app/components/Ui/ContextMenu/SubTrigger.vue +42 -0
  60. package/app/components/Ui/ContextMenu/Trigger.vue +12 -0
  61. package/app/components/Ui/Dialog/Close.vue +12 -0
  62. package/app/components/Ui/Dialog/Content.vue +79 -0
  63. package/app/components/Ui/Dialog/Description.vue +29 -0
  64. package/app/components/Ui/Dialog/Dialog.vue +14 -0
  65. package/app/components/Ui/Dialog/Footer.vue +32 -0
  66. package/app/components/Ui/Dialog/Header.vue +32 -0
  67. package/app/components/Ui/Dialog/Overlay.vue +25 -0
  68. package/app/components/Ui/Dialog/Portal.vue +12 -0
  69. package/app/components/Ui/Dialog/Title.vue +29 -0
  70. package/app/components/Ui/Dialog/Trigger.vue +12 -0
  71. package/app/components/Ui/Divider.vue +102 -0
  72. package/app/components/Ui/Drawer/Close.vue +12 -0
  73. package/app/components/Ui/Drawer/Content.vue +67 -0
  74. package/app/components/Ui/Drawer/Description.vue +33 -0
  75. package/app/components/Ui/Drawer/Drawer.vue +17 -0
  76. package/app/components/Ui/Drawer/Footer.vue +35 -0
  77. package/app/components/Ui/Drawer/Header.vue +34 -0
  78. package/app/components/Ui/Drawer/Overlay.vue +20 -0
  79. package/app/components/Ui/Drawer/Portal.vue +12 -0
  80. package/app/components/Ui/Drawer/Title.vue +35 -0
  81. package/app/components/Ui/Drawer/Trigger.vue +12 -0
  82. package/app/components/Ui/DropdownMenu/Arrow.vue +43 -0
  83. package/app/components/Ui/DropdownMenu/CheckboxItem.vue +41 -0
  84. package/app/components/Ui/DropdownMenu/Content.vue +56 -0
  85. package/app/components/Ui/DropdownMenu/DropdownMenu.vue +15 -0
  86. package/app/components/Ui/DropdownMenu/Group.vue +12 -0
  87. package/app/components/Ui/DropdownMenu/Item.vue +64 -0
  88. package/app/components/Ui/DropdownMenu/ItemIndicator.vue +32 -0
  89. package/app/components/Ui/DropdownMenu/Label.vue +34 -0
  90. package/app/components/Ui/DropdownMenu/Portal.vue +12 -0
  91. package/app/components/Ui/DropdownMenu/RadioGroup.vue +16 -0
  92. package/app/components/Ui/DropdownMenu/RadioItem.vue +40 -0
  93. package/app/components/Ui/DropdownMenu/Separator.vue +25 -0
  94. package/app/components/Ui/DropdownMenu/Shortcut.vue +44 -0
  95. package/app/components/Ui/DropdownMenu/Sub.vue +14 -0
  96. package/app/components/Ui/DropdownMenu/SubContent.vue +56 -0
  97. package/app/components/Ui/DropdownMenu/SubTrigger.vue +47 -0
  98. package/app/components/Ui/DropdownMenu/Trigger.vue +12 -0
  99. package/app/components/Ui/FancyIcon.vue +243 -0
  100. package/app/components/Ui/GradientDivider.vue +26 -0
  101. package/app/components/Ui/IframeLazy.vue +97 -0
  102. package/app/components/Ui/Input.vue +97 -0
  103. package/app/components/Ui/InputGroup/Addon.vue +66 -0
  104. package/app/components/Ui/InputGroup/Button.vue +50 -0
  105. package/app/components/Ui/InputGroup/Input.vue +25 -0
  106. package/app/components/Ui/InputGroup/InputGroup.vue +44 -0
  107. package/app/components/Ui/InputGroup/Text.vue +33 -0
  108. package/app/components/Ui/InputGroup/Textarea.vue +25 -0
  109. package/app/components/Ui/Kbd/Group.vue +32 -0
  110. package/app/components/Ui/Kbd/Kbd.vue +62 -0
  111. package/app/components/Ui/Label.vue +40 -0
  112. package/app/components/Ui/Popover/Anchor.vue +13 -0
  113. package/app/components/Ui/Popover/Arrow.vue +42 -0
  114. package/app/components/Ui/Popover/Close.vue +13 -0
  115. package/app/components/Ui/Popover/Content.vue +55 -0
  116. package/app/components/Ui/Popover/Popover.vue +15 -0
  117. package/app/components/Ui/Popover/Portal.vue +13 -0
  118. package/app/components/Ui/Popover/Trigger.vue +12 -0
  119. package/app/components/Ui/Popover/X.vue +38 -0
  120. package/app/components/Ui/ScrollArea/Corner.vue +12 -0
  121. package/app/components/Ui/ScrollArea/ScrollArea.vue +38 -0
  122. package/app/components/Ui/ScrollArea/Scrollbar.vue +39 -0
  123. package/app/components/Ui/ScrollArea/Thumb.vue +39 -0
  124. package/app/components/Ui/ScrollArea/Viewport.vue +27 -0
  125. package/app/components/Ui/Scrollspy.vue +61 -0
  126. package/app/components/Ui/Separator.vue +40 -0
  127. package/app/components/Ui/Skeleton.vue +39 -0
  128. package/app/components/Ui/Sonner.vue +25 -0
  129. package/app/components/Ui/Tabs/Content.vue +27 -0
  130. package/app/components/Ui/Tabs/Indicator.vue +37 -0
  131. package/app/components/Ui/Tabs/List.vue +37 -0
  132. package/app/components/Ui/Tabs/Tabs.vue +25 -0
  133. package/app/components/Ui/Tabs/Trigger.vue +41 -0
  134. package/app/components/Ui/Textarea.vue +81 -0
  135. package/app/components/Ui/Tooltip/Arrow.vue +30 -0
  136. package/app/components/Ui/Tooltip/Content.vue +50 -0
  137. package/app/components/Ui/Tooltip/Portal.vue +12 -0
  138. package/app/components/Ui/Tooltip/Provider.vue +14 -0
  139. package/app/components/Ui/Tooltip/Tooltip.vue +25 -0
  140. package/app/components/Ui/Tooltip/Trigger.vue +12 -0
  141. package/app/components/Ui/Tree/Item.vue +15 -0
  142. package/app/components/Ui/Tree/Tree.vue +15 -0
  143. package/app/components/Ui/Tree/Virtualizer.vue +13 -0
  144. package/app/components/component-api/ComponentApiSection.vue +135 -0
  145. package/app/components/content/docs/Callout/DocsCallout.vue +99 -0
  146. package/app/components/content/prose/ProseA.global.vue +31 -0
  147. package/app/components/content/prose/ProseBlockquote.global.vue +28 -0
  148. package/app/components/content/prose/ProseCallout.global.vue +306 -0
  149. package/app/components/content/prose/ProseCard.global.vue +100 -0
  150. package/app/components/content/prose/ProseCodeCollapse.global.vue +125 -0
  151. package/app/components/content/prose/ProseCodeCopy.global.vue +67 -0
  152. package/app/components/content/prose/ProseCodeGroup.global.vue +104 -0
  153. package/app/components/content/prose/ProseCodeTree.global.vue +277 -0
  154. package/app/components/content/prose/ProseCollapsible.global.vue +64 -0
  155. package/app/components/content/prose/ProseColorModeImage.global.vue +58 -0
  156. package/app/components/content/prose/ProseComponentApi.global.vue +104 -0
  157. package/app/components/content/prose/ProseComponentEvents.global.vue +27 -0
  158. package/app/components/content/prose/ProseComponentExposed.global.vue +27 -0
  159. package/app/components/content/prose/ProseComponentProps.global.vue +27 -0
  160. package/app/components/content/prose/ProseComponentSlots.global.vue +27 -0
  161. package/app/components/content/prose/ProseEm.global.vue +5 -0
  162. package/app/components/content/prose/ProseField.global.vue +118 -0
  163. package/app/components/content/prose/ProseFieldGroup.global.vue +62 -0
  164. package/app/components/content/prose/ProseH1.global.vue +58 -0
  165. package/app/components/content/prose/ProseH2.global.vue +58 -0
  166. package/app/components/content/prose/ProseH3.global.vue +58 -0
  167. package/app/components/content/prose/ProseH4.global.vue +58 -0
  168. package/app/components/content/prose/ProseH5.global.vue +47 -0
  169. package/app/components/content/prose/ProseH6.global.vue +47 -0
  170. package/app/components/content/prose/ProseHr.global.vue +33 -0
  171. package/app/components/content/prose/ProseIconList.global.vue +113 -0
  172. package/app/components/content/prose/ProseImg.global.vue +119 -0
  173. package/app/components/content/prose/ProseLead.global.vue +28 -0
  174. package/app/components/content/prose/ProseLi.global.vue +25 -0
  175. package/app/components/content/prose/ProseMermaid.global.vue +127 -0
  176. package/app/components/content/prose/ProseOl.global.vue +25 -0
  177. package/app/components/content/prose/ProseP.global.vue +25 -0
  178. package/app/components/content/prose/ProsePmInstall.global.vue +60 -0
  179. package/app/components/content/prose/ProsePmRun.global.vue +51 -0
  180. package/app/components/content/prose/ProsePmX.global.vue +56 -0
  181. package/app/components/content/prose/ProsePre.global.vue +181 -0
  182. package/app/components/content/prose/ProseShowCase.global.vue +56 -0
  183. package/app/components/content/prose/ProseSmartIcon.global.vue +54 -0
  184. package/app/components/content/prose/ProseStep.global.vue +22 -0
  185. package/app/components/content/prose/ProseSteps.global.vue +22 -0
  186. package/app/components/content/prose/ProseStrong.global.vue +34 -0
  187. package/app/components/content/prose/ProseTable.global.vue +34 -0
  188. package/app/components/content/prose/ProseTabs.global.vue +104 -0
  189. package/app/components/content/prose/ProseTabsInner.global.vue +274 -0
  190. package/app/components/content/prose/ProseTbody.global.vue +5 -0
  191. package/app/components/content/prose/ProseTd.global.vue +25 -0
  192. package/app/components/content/prose/ProseTh.global.vue +25 -0
  193. package/app/components/content/prose/ProseThead.global.vue +9 -0
  194. package/app/components/content/prose/ProseTr.global.vue +35 -0
  195. package/app/components/content/prose/ProseUl.global.vue +25 -0
  196. package/app/components/docs/DocsExtraStuff.vue +39 -0
  197. package/app/components/docs/DocsGithubLink.vue +11 -0
  198. package/app/components/docs/DocsHeader.vue +3 -0
  199. package/app/components/docs/DocsLogo.vue +74 -0
  200. package/app/components/docs/DocsMobileBar.vue +26 -0
  201. package/app/components/docs/DocsMobileNav.vue +49 -0
  202. package/app/components/docs/DocsNav.vue +161 -0
  203. package/app/components/docs/DocsPageTransition.vue +32 -0
  204. package/app/components/docs/DocsSearch.vue +220 -0
  205. package/app/components/docs/DocsSearchButton.vue +20 -0
  206. package/app/components/docs/DocsThemeCustomizer.vue +229 -0
  207. package/app/components/docs/DocsThemeToggler.vue +98 -0
  208. package/app/components/docs/DocsTocRail.vue +162 -0
  209. package/app/components/docs/DocsToclink.vue +52 -0
  210. package/app/components/docs/DocsZigZagRail.vue +70 -0
  211. package/app/components/landing/Hero.vue +222 -0
  212. package/app/components/page/PageFooter.vue +49 -0
  213. package/app/components/page/PageHeader.vue +68 -0
  214. package/app/components/page/PageHeaderLinks.vue +138 -0
  215. package/app/components/page/PageMobileToc.vue +162 -0
  216. package/app/components/page/PageToc.vue +33 -0
  217. package/app/composables/useDefaultSlotItems.ts +90 -0
  218. package/app/composables/useDocPage.ts +66 -0
  219. package/app/composables/useDocSearch.ts +23 -0
  220. package/app/composables/useDocd.ts +107 -0
  221. package/app/composables/useLogoAssets.ts +319 -0
  222. package/app/composables/usePm.ts +73 -0
  223. package/app/composables/useProseComponentMeta.ts +30 -0
  224. package/app/composables/useScrollspy.ts +327 -0
  225. package/app/composables/useSeo.ts +240 -0
  226. package/app/composables/useUIConfig.ts +36 -0
  227. package/app/layouts/default.vue +5 -0
  228. package/app/layouts/docs.vue +64 -0
  229. package/app/middleware/colorMode.global.ts +8 -0
  230. package/app/middleware/doc-page-layout.ts +11 -0
  231. package/app/middleware/landing-page-layout.ts +11 -0
  232. package/app/pages/[...slug].vue +63 -0
  233. package/app/plugins/mermaid.client.ts +35 -0
  234. package/app/plugins/navigation-direction.client.ts +15 -0
  235. package/app/plugins/theme-restore.client.ts +7 -0
  236. package/app/templates/landing.vue +40 -0
  237. package/app/transitions/presets.ts +71 -0
  238. package/app/types/prose-component-meta.d.ts +4 -0
  239. package/app/utils/og.ts +26 -0
  240. package/app/utils/prerender.ts +9 -0
  241. package/app/utils/translucent.ts +56 -0
  242. package/app/utils/tw-helper.ts +4 -0
  243. package/content.config.ts +81 -0
  244. package/index.d.ts +25 -0
  245. package/modules/config.ts +89 -0
  246. package/modules/custom-icons.ts +17 -0
  247. package/modules/markdown-rewrite.ts +96 -0
  248. package/modules/prose-component-meta.ts +150 -0
  249. package/modules/routing.ts +22 -0
  250. package/modules/skills/index.ts +167 -0
  251. package/modules/skills/runtime/server/routes/skills-files.ts +49 -0
  252. package/modules/skills/runtime/server/routes/skills-index.ts +8 -0
  253. package/nuxt.config.ts +96 -0
  254. package/nuxt.schema.ts +288 -0
  255. package/package.json +95 -0
  256. package/server/mcp/tools/get-page.ts +66 -0
  257. package/server/mcp/tools/list-pages.ts +61 -0
  258. package/server/routes/sitemap.xml.ts +73 -0
  259. package/server/utils/content.ts +3 -0
  260. package/tailwind.config.cjs +55 -0
  261. package/utils/component-api-content.ts +467 -0
  262. package/utils/component-api-discovery.ts +165 -0
  263. package/utils/component-api-schema.ts +28 -0
  264. package/utils/component-api.ts +243 -0
  265. package/utils/generate-prose-component-meta.ts +303 -0
  266. package/utils/git.ts +123 -0
  267. package/utils/meta.ts +40 -0
  268. package/utils/pages.ts +21 -0
@@ -0,0 +1,161 @@
1
+ <template>
2
+ <nav class="flex flex-col gap-4 sm:text-sm">
3
+ <!-- Non-nested: group consecutive flat items so each group gets a shared vertical line -->
4
+ <template v-if="!nested">
5
+ <template v-for="(group, gi) in groups" :key="gi">
6
+ <!-- Flat group: vertical line + curves -->
7
+ <div
8
+ v-if="group.type === 'flat'"
9
+ class="relative flex flex-col gap-4 pt-1 pl-8 before:absolute before:left-2 before:h-[calc(100%-23px)] before:w-px before:bg-border dark:before:bg-accent"
10
+ >
11
+ <div
12
+ v-for="(item, i) in group.items"
13
+ :key="i"
14
+ class="relative flex items-center gap-4 first:mt-2"
15
+ >
16
+ <NuxtLink
17
+ class="group line-clamp-1 flex shrink-0 items-center gap-2 text-base text-ellipsis text-muted-foreground sm:text-sm"
18
+ :class="[
19
+ 'before:absolute before:-left-6 before:-mt-4 before:size-4 before:rounded-bl-md before:border-b before:border-l before:border-border dark:before:border-accent',
20
+ ]"
21
+ exact-active-class="text-primary"
22
+ :to="item.path"
23
+ :title="item.title"
24
+ :target="item.target ? (item.target as string) : '_self'"
25
+ >
26
+ <Icon
27
+ v-if="item.icon && typeof item.icon === 'string'"
28
+ :name="item.icon"
29
+ class="size-4"
30
+ />
31
+ <div class="relative">
32
+ {{ item.title }}
33
+ <div
34
+ class="absolute bottom-0 left-0 h-px w-full bg-linear-to-r from-transparent via-primary to-transparent group-hover:scale-x-100"
35
+ :class="[
36
+ 'transition-all duration-300',
37
+ route.path === item.path ? 'scale-x-100' : 'scale-x-0',
38
+ ]"
39
+ ></div>
40
+ </div>
41
+ </NuxtLink>
42
+ <UiBadge
43
+ v-if="item.label"
44
+ class="rounded-sm text-xs md:text-[10px] dark:bg-lime-600 dark:text-white"
45
+ >{{ item.label }}</UiBadge
46
+ >
47
+ </div>
48
+ </div>
49
+
50
+ <!-- Collapsible item -->
51
+ <UiCollapsible
52
+ v-else
53
+ :default-open="route.path.includes(group.item.path)"
54
+ class="flex flex-col border-b pb-4"
55
+ :class="[
56
+ isDashed ? 'border-dashed' : '',
57
+ gi > 0 && groups[gi - 1]?.type === 'flat' ? 'border-t pt-4' : '',
58
+ ]"
59
+ >
60
+ <UiCollapsibleTrigger
61
+ class="group inline-flex items-center gap-2 rounded-sm p-2 pl-2.5 text-foreground/70 transition-all duration-300 hover:bg-accent/50 hover:text-foreground/90 data-[state=open]:text-foreground/90"
62
+ >
63
+ <Icon
64
+ v-if="group.item.icon && typeof group.item.icon === 'string'"
65
+ :name="group.item.icon"
66
+ class="size-4"
67
+ />
68
+ <p class="text-sm font-semibold">{{ group.item.title }}</p>
69
+ <Icon
70
+ name="lucide:chevron-down"
71
+ class="ms-auto size-4 text-muted-foreground/50 transition-transform group-data-[state=open]:rotate-180"
72
+ />
73
+ </UiCollapsibleTrigger>
74
+
75
+ <UiCollapsibleContent class="flex flex-col gap-1 pl-2">
76
+ <div
77
+ class="relative pt-1 pl-8 before:absolute before:left-2 before:h-[calc(100%-23px)] before:w-px before:bg-border dark:before:bg-accent"
78
+ >
79
+ <DocsNav class="gap-4 first:mt-2" :items="group.item.children!" :nested="true" />
80
+ </div>
81
+ </UiCollapsibleContent>
82
+ </UiCollapsible>
83
+ </template>
84
+ </template>
85
+
86
+ <!-- Nested (inside a collapsible): parent already provides the vertical line, just render links -->
87
+ <template v-for="(item, i) in items" v-else :key="i">
88
+ <div class="relative flex items-center gap-4">
89
+ <NuxtLink
90
+ class="group line-clamp-1 flex shrink-0 items-center gap-2 text-base text-ellipsis text-muted-foreground sm:text-sm"
91
+ :class="[
92
+ 'before:absolute before:-left-6 before:-mt-4 before:size-4 before:rounded-bl-md before:border-b before:border-l before:border-border dark:before:border-accent',
93
+ ]"
94
+ exact-active-class="text-primary"
95
+ :to="item.path"
96
+ :title="item.title"
97
+ :target="item.target ? (item.target as string) : '_self'"
98
+ >
99
+ <Icon
100
+ v-if="item.icon && typeof item.icon === 'string'"
101
+ :name="item.icon"
102
+ class="size-4"
103
+ />
104
+ <div class="relative">
105
+ {{ item.title }}
106
+ <div
107
+ class="absolute bottom-0 left-0 h-px w-full bg-linear-to-r from-transparent via-primary to-transparent group-hover:scale-x-100"
108
+ :class="[
109
+ 'transition-all duration-300',
110
+ route.path === item.path ? 'scale-x-100' : 'scale-x-0',
111
+ ]"
112
+ ></div>
113
+ </div>
114
+ </NuxtLink>
115
+ <UiBadge
116
+ v-if="item.label"
117
+ class="rounded-sm text-xs md:text-[10px] dark:bg-lime-600 dark:text-white"
118
+ >{{ item.label }}</UiBadge
119
+ >
120
+ </div>
121
+ </template>
122
+ </nav>
123
+ </template>
124
+ <script lang="ts" setup>
125
+ import type { ContentNavigationItem } from "@nuxt/content";
126
+
127
+ const props = defineProps<{
128
+ items: ContentNavigationItem[];
129
+ nested?: boolean;
130
+ }>();
131
+
132
+ const route = useRoute();
133
+ const { isDashed } = useDocd();
134
+
135
+ type Group =
136
+ | { type: "flat"; items: ContentNavigationItem[] }
137
+ | { type: "collapsible"; item: ContentNavigationItem };
138
+
139
+ // Group consecutive flat items together so they share one vertical line container.
140
+ // Collapsible items (folders) stay as individual entries in their original order.
141
+ const groups = computed<Group[]>(() => {
142
+ const result: Group[] = [];
143
+ let currentFlat: ContentNavigationItem[] = [];
144
+
145
+ for (const item of props.items) {
146
+ if (item.children) {
147
+ if (currentFlat.length > 0) {
148
+ result.push({ type: "flat", items: currentFlat });
149
+ currentFlat = [];
150
+ }
151
+ result.push({ type: "collapsible", item });
152
+ } else {
153
+ currentFlat.push(item);
154
+ }
155
+ }
156
+ if (currentFlat.length > 0) {
157
+ result.push({ type: "flat", items: currentFlat });
158
+ }
159
+ return result;
160
+ });
161
+ </script>
@@ -0,0 +1,32 @@
1
+ <template>
2
+ <div
3
+ class="w-full min-w-0 overflow-x-clip"
4
+ :class="[isAnimating && 'pointer-events-none overflow-clip']"
5
+ >
6
+ <AnimatePresence mode="wait" @exit-complete="isAnimating = false">
7
+ <Motion
8
+ :key="route.path"
9
+ :initial="preset.initial"
10
+ :animate="animate"
11
+ :exit="preset.exit"
12
+ :transition="{ duration, ease: easing }"
13
+ style="width: 100%; min-width: 0"
14
+ >
15
+ <slot />
16
+ </Motion>
17
+ </AnimatePresence>
18
+ </div>
19
+ </template>
20
+
21
+ <script lang="ts" setup>
22
+ const route = useRoute();
23
+ const { preset, animate, duration, easing } = useDocd();
24
+
25
+ const isAnimating = ref(false);
26
+ watch(
27
+ () => route.path,
28
+ () => {
29
+ isAnimating.value = true;
30
+ }
31
+ );
32
+ </script>
@@ -0,0 +1,220 @@
1
+ <template>
2
+ <UiCommandDialog :key="dialogKey" v-model:open="searchModal" show-close-button highlight-on-hover>
3
+ <UiDialogTitle class="sr-only">Documentation search modal</UiDialogTitle>
4
+ <UiDialogDescription class="sr-only">
5
+ Search the documentation for components, utilities, and more.
6
+ </UiDialogDescription>
7
+
8
+ <UiCommandInput placeholder="Search..." @update:model-value="updateSearchTerm" />
9
+
10
+ <UiCommandList>
11
+ <UiCommandEmpty>No results found.</UiCommandEmpty>
12
+ <UiCommandGroup v-if="contentResults.length" heading="Content">
13
+ <UiCommandItem
14
+ v-for="section in contentResults"
15
+ :key="section.id"
16
+ class="items-start px-3 py-2"
17
+ :value="section.id"
18
+ @select="
19
+ $event.preventDefault();
20
+ navigateTo(section.id);
21
+ searchModal = false;
22
+ "
23
+ >
24
+ <Icon
25
+ :name="section.id.includes('#') ? 'lucide:heading' : 'lucide:file-text'"
26
+ class="mt-0.5 size-4 shrink-0 text-muted-foreground/70"
27
+ />
28
+ <div class="min-w-0 flex-1">
29
+ <p class="truncate font-medium text-foreground">{{ section.title }}</p>
30
+ <p v-if="getSectionContext(section)" class="truncate text-xs text-muted-foreground">
31
+ {{ getSectionContext(section) }}
32
+ </p>
33
+ <p v-if="section.content" class="line-clamp-2 text-xs text-muted-foreground">
34
+ {{ getSectionSnippet(section.content) }}
35
+ </p>
36
+ <span class="sr-only">
37
+ {{ section.title }} {{ section.titles.join(" ") }} {{ section.content }}
38
+ {{ section.id }}
39
+ </span>
40
+ </div>
41
+ </UiCommandItem>
42
+ </UiCommandGroup>
43
+
44
+ <UiCommandSeparator v-if="contentResults.length" />
45
+
46
+ <template v-for="(nav, i) in navigation" :key="i">
47
+ <!-- Nav item with children → group -->
48
+ <UiCommandGroup v-if="nav.children?.length" :heading="nav.title">
49
+ <UiCommandItem
50
+ v-for="(child, k) in nav.children"
51
+ :key="k"
52
+ class="px-3 py-2"
53
+ :value="child.title"
54
+ @select="
55
+ $event.preventDefault();
56
+ navigateTo(child.path);
57
+ searchModal = false;
58
+ "
59
+ >
60
+ <Icon
61
+ :name="(child.icon as string) || (nav.icon as string) || 'lucide:file-text'"
62
+ class="size-4 shrink-0 text-muted-foreground/70"
63
+ />
64
+ <span class="flex-1 truncate">{{ child.title }}</span>
65
+ <UiBadge
66
+ v-if="(child as any).label"
67
+ variant="outline"
68
+ class="ml-2 shrink-0 px-1.5 py-0 text-[10px] dark:bg-lime-500/20 dark:text-lime-400"
69
+ >{{ (child as any).label }}</UiBadge
70
+ >
71
+ </UiCommandItem>
72
+ </UiCommandGroup>
73
+
74
+ <!-- Nav item without children → flat link -->
75
+ <UiCommandGroup v-else class="py-0">
76
+ <UiCommandItem
77
+ class="px-3 py-2"
78
+ :value="nav.title"
79
+ @select="
80
+ $event.preventDefault();
81
+ navigateTo(nav.path);
82
+ searchModal = false;
83
+ "
84
+ >
85
+ <Icon name="lucide:link" class="size-4 shrink-0 text-muted-foreground/70" />
86
+ <span class="flex-1 truncate">{{ nav.title }}</span>
87
+ <UiBadge
88
+ v-if="(nav as any).label"
89
+ variant="outline"
90
+ class="ml-2 shrink-0 px-1.5 py-0 text-[10px] dark:bg-lime-500/20 dark:text-lime-400"
91
+ >{{ (nav as any).label }}</UiBadge
92
+ >
93
+ </UiCommandItem>
94
+ </UiCommandGroup>
95
+ </template>
96
+
97
+ <UiCommandSeparator />
98
+
99
+ <UiCommandGroup heading="Theme">
100
+ <UiCommandItem
101
+ v-for="(mode, i) in modes"
102
+ :key="i"
103
+ class="px-3 py-2"
104
+ :value="mode.value"
105
+ @select="setTheme($event, mode.value)"
106
+ >
107
+ <Icon :name="mode.icon" class="size-4 shrink-0 text-muted-foreground/70" />
108
+ <span>{{ mode.title }}</span>
109
+ </UiCommandItem>
110
+ </UiCommandGroup>
111
+ </UiCommandList>
112
+
113
+ <UiCommandFooter />
114
+ </UiCommandDialog>
115
+ </template>
116
+
117
+ <script lang="ts" setup>
118
+ import type { DocSearchSection } from "../../composables/useDocSearch";
119
+
120
+ const modes = [
121
+ { icon: "lucide:sun", title: "Light", value: "light" },
122
+ { icon: "lucide:moon", title: "Dark", value: "dark" },
123
+ { icon: "lucide:laptop", title: "System", value: "system" },
124
+ ];
125
+
126
+ const { navigation } = await useDocPage();
127
+ const { data: searchSections } = useDocSearchSections();
128
+ const searchModal = useSearchModal();
129
+ const colorMode = useColorMode();
130
+ const searchTerm = ref("");
131
+ const dialogKey = ref(0);
132
+
133
+ const setTheme = (e: Event, val: string) => {
134
+ e.preventDefault();
135
+ colorMode.preference = val;
136
+ searchModal.value = false;
137
+ };
138
+
139
+ const updateSearchTerm = (value: string | number | undefined) => {
140
+ searchTerm.value = `${value ?? ""}`;
141
+ };
142
+
143
+ const searchTokens = computed(() =>
144
+ searchTerm.value
145
+ .trim()
146
+ .toLowerCase()
147
+ .split(/\s+/)
148
+ .map((token) => token.trim())
149
+ .filter(Boolean)
150
+ );
151
+
152
+ function scoreSection(section: DocSearchSection, tokens: string[]) {
153
+ if (!tokens.length) return 0;
154
+
155
+ const title = section.title.toLowerCase();
156
+ const headings = section.titles.join(" ").toLowerCase();
157
+ const content = section.content.toLowerCase();
158
+ const id = section.id.toLowerCase();
159
+
160
+ let score = 0;
161
+ for (const token of tokens) {
162
+ let tokenScore = 0;
163
+
164
+ if (title.includes(token)) tokenScore = Math.max(tokenScore, 6);
165
+ if (headings.includes(token)) tokenScore = Math.max(tokenScore, 4);
166
+ if (id.includes(token)) tokenScore = Math.max(tokenScore, 3);
167
+ if (content.includes(token)) tokenScore = Math.max(tokenScore, 2);
168
+
169
+ if (!tokenScore) return 0;
170
+ score += tokenScore;
171
+ }
172
+
173
+ if (!section.id.includes("#")) score += 1;
174
+ return score;
175
+ }
176
+
177
+ const contentResults = computed(() => {
178
+ if (!searchTokens.value.length) return [];
179
+
180
+ return [...(searchSections.value ?? [])]
181
+ .map((section) => ({ section, score: scoreSection(section, searchTokens.value) }))
182
+ .filter((entry) => entry.score > 0)
183
+ .sort(
184
+ (a, b) =>
185
+ b.score - a.score ||
186
+ a.section.level - b.section.level ||
187
+ a.section.title.localeCompare(b.section.title)
188
+ )
189
+ .slice(0, 30)
190
+ .map((entry) => entry.section);
191
+ });
192
+
193
+ function getSectionContext(section: DocSearchSection) {
194
+ if (section.titles.length) return section.titles.join(" / ");
195
+ const path = section.id.split("#")[0] || "";
196
+ return path.replace(/^\//, "").replace(/\//g, " / ");
197
+ }
198
+
199
+ function getSectionSnippet(content: string) {
200
+ const cleaned = content.replace(/\s+/g, " ").trim();
201
+ return cleaned.length > 160 ? `${cleaned.slice(0, 157)}...` : cleaned;
202
+ }
203
+
204
+ const { meta_k } = useMagicKeys();
205
+ watch(
206
+ () => meta_k?.value,
207
+ (val) => {
208
+ if (val) {
209
+ searchModal.value = !searchModal.value;
210
+ }
211
+ }
212
+ );
213
+
214
+ watch(searchModal, (open) => {
215
+ if (!open) {
216
+ searchTerm.value = "";
217
+ dialogKey.value += 1;
218
+ }
219
+ });
220
+ </script>
@@ -0,0 +1,20 @@
1
+ <template>
2
+ <UiTooltip>
3
+ <UiTooltipTrigger as-child>
4
+ <UiButton
5
+ size="icon-sm"
6
+ variant="ghost"
7
+ class="size-7"
8
+ @click="searchModal = true"
9
+ title="Search documentation"
10
+ >
11
+ <Icon name="lucide:search" />
12
+ </UiButton>
13
+ </UiTooltipTrigger>
14
+ <UiTooltipContent> Click to search </UiTooltipContent>
15
+ </UiTooltip>
16
+ </template>
17
+
18
+ <script lang="ts" setup>
19
+ const searchModal = useSearchModal();
20
+ </script>
@@ -0,0 +1,229 @@
1
+ <template>
2
+ <UiPopover>
3
+ <UiTooltip>
4
+ <UiTooltipTrigger as-child>
5
+ <UiPopoverTrigger as-child>
6
+ <UiButton size="icon-sm" variant="ghost">
7
+ <Icon name="lucide:palette" />
8
+ </UiButton>
9
+ </UiPopoverTrigger>
10
+ </UiTooltipTrigger>
11
+ <UiPopoverContent
12
+ translucent
13
+ class="w-auto overflow-y-auto p-4"
14
+ side="right"
15
+ align="end"
16
+ :side-offset="8"
17
+ >
18
+ <DefineBgActive v-slot="{ active }">
19
+ <AnimatePresence>
20
+ <Motion
21
+ v-if="active"
22
+ class="absolute inset-0 rounded-md bg-linear-to-b from-foreground/10 via-foreground/5"
23
+ :initial="{ opacity: 0, scaleY: 0, transformOrigin: 'top left', scaleX: 0 }"
24
+ :animate="{ opacity: 1, scaleY: 1, scaleX: 1 }"
25
+ :exit="{ opacity: 0, scaleY: 0, scaleX: 0 }"
26
+ :transition="{ duration: 0.2, ease: 'easeOut' }"
27
+ />
28
+ </AnimatePresence>
29
+ </DefineBgActive>
30
+
31
+ <!-- Header -->
32
+ <div class="grid space-y-1">
33
+ <h1 class="text-base font-semibold text-foreground">Customize</h1>
34
+ <p class="text-sm text-muted-foreground">Pick a style and color for your components.</p>
35
+ </div>
36
+
37
+ <!-- Color -->
38
+ <div class="space-y-1.5 pt-6">
39
+ <UiLabel class="text-sm font-semibold">Color</UiLabel>
40
+ <RovingFocusGroup loop class="grid grid-cols-3 gap-2 py-1.5">
41
+ <RovingFocusItem v-for="t in themes" :key="t.value" as-child>
42
+ <UiButton
43
+ variant="outline"
44
+ size="sm"
45
+ class="group relative justify-start overflow-hidden bg-transparent px-3 transition-all duration-200"
46
+ :style="{
47
+ borderColor: activeTheme === t.value ? 'var(--primary)' : 'var(--border)',
48
+ }"
49
+ @click="setTheme(t.value)"
50
+ >
51
+ <BgActive :active="activeTheme === t.value" />
52
+ <span class="relative flex items-center gap-2">
53
+ <span
54
+ class="flex size-5 shrink-0 items-center justify-center rounded-full ring-1 ring-border/60 transition-transform duration-200"
55
+ :class="
56
+ activeTheme === t.value ? 'scale-110' : 'scale-100 group-hover:scale-105'
57
+ "
58
+ :style="{ backgroundColor: t.color }"
59
+ >
60
+ <AnimatePresence>
61
+ <Motion
62
+ v-if="activeTheme === t.value"
63
+ :initial="{ scale: 0, opacity: 0 }"
64
+ :animate="{ scale: 1, opacity: 1 }"
65
+ :exit="{ scale: 0, opacity: 0 }"
66
+ :transition="{ duration: 0.2, delay: 0.1, ease: 'backOut' }"
67
+ >
68
+ <Icon name="lucide:check" class="size-3 text-white" />
69
+ </Motion>
70
+ </AnimatePresence>
71
+ </span>
72
+ <span class="text-xs capitalize">{{ t.label }}</span>
73
+ </span>
74
+ </UiButton>
75
+ </RovingFocusItem>
76
+ </RovingFocusGroup>
77
+ </div>
78
+
79
+ <!-- Radius -->
80
+ <div class="space-y-1.5 pt-4">
81
+ <UiLabel class="text-sm font-semibold">Radius</UiLabel>
82
+ <RovingFocusGroup loop class="grid grid-cols-5 gap-2 py-1.5">
83
+ <RovingFocusItem v-for="r in radii" :key="r.value" as-child>
84
+ <UiButton
85
+ variant="outline"
86
+ size="sm"
87
+ class="group relative justify-start overflow-hidden bg-transparent px-3 transition-all duration-200"
88
+ :style="{
89
+ borderColor: activeRadius === r.value ? 'var(--primary)' : 'var(--border)',
90
+ }"
91
+ @click="setRadius(r.value)"
92
+ >
93
+ <BgActive :active="activeRadius === r.value" />
94
+ <Motion
95
+ class="relative text-xs"
96
+ :initial="false"
97
+ :animate="{ scale: activeRadius === r.value ? 1.05 : 1 }"
98
+ :transition="{ duration: 0.2, ease: 'easeOut' }"
99
+ >
100
+ {{ r.label }}
101
+ </Motion>
102
+ </UiButton>
103
+ </RovingFocusItem>
104
+ </RovingFocusGroup>
105
+ </div>
106
+
107
+ <!-- Theme / Mode -->
108
+ <div class="space-y-1.5 pt-4">
109
+ <UiLabel class="text-sm font-semibold">Theme</UiLabel>
110
+ <RovingFocusGroup loop class="flex space-x-2 py-1.5">
111
+ <RovingFocusItem as-child>
112
+ <UiButton
113
+ variant="outline"
114
+ size="sm"
115
+ class="group relative justify-start overflow-hidden bg-transparent px-3 transition-all duration-200"
116
+ :style="{
117
+ borderColor: colorMode.value === 'light' ? 'var(--primary)' : 'var(--border)',
118
+ }"
119
+ @click="setMode('light')"
120
+ >
121
+ <BgActive :active="colorMode.value === 'light'" />
122
+ <span class="relative flex items-center justify-center">
123
+ <Icon name="lucide:sun" class="mr-2 size-4" />
124
+ <span class="text-xs">Light</span>
125
+ </span>
126
+ </UiButton>
127
+ </RovingFocusItem>
128
+ <RovingFocusItem as-child>
129
+ <UiButton
130
+ variant="outline"
131
+ size="sm"
132
+ class="group relative justify-start overflow-hidden bg-transparent px-3 transition-all duration-200"
133
+ :style="{
134
+ borderColor: colorMode.value === 'dark' ? 'var(--primary)' : 'var(--border)',
135
+ }"
136
+ @click="setMode('dark')"
137
+ >
138
+ <BgActive :active="colorMode.value === 'dark'" />
139
+ <span class="relative flex items-center justify-center">
140
+ <Icon name="lucide:moon" class="mr-2 size-4" />
141
+ <span class="text-xs">Dark</span>
142
+ </span>
143
+ </UiButton>
144
+ </RovingFocusItem>
145
+ </RovingFocusGroup>
146
+ </div>
147
+ </UiPopoverContent>
148
+ <UiTooltipContent> Customize theme </UiTooltipContent>
149
+ </UiTooltip>
150
+ </UiPopover>
151
+ </template>
152
+
153
+ <script lang="ts" setup>
154
+ import { RovingFocusGroup, RovingFocusItem } from "reka-ui";
155
+
156
+ const [DefineBgActive, BgActive] = createReusableTemplate<{ active: boolean }>({
157
+ inheritAttrs: false,
158
+ });
159
+
160
+ const colorMode = useColorMode();
161
+
162
+ const themes = [
163
+ { label: "Stone", value: "theme-stone", color: "oklch(0.216 0.006 56.043)" },
164
+ { label: "Zinc", value: "theme-zinc", color: "oklch(0.21 0.006 285.885)" },
165
+ { label: "Neutral", value: "theme-neutral", color: "oklch(0.205 0 0)" },
166
+ { label: "Gray", value: "theme-gray", color: "oklch(0.21 0.034 264.665)" },
167
+ { label: "Slate", value: "theme-slate", color: "oklch(0.208 0.042 265.755)" },
168
+ { label: "Red", value: "theme-red", color: "oklch(0.637 0.237 25.331)" },
169
+ { label: "Rose", value: "theme-rose", color: "oklch(0.645 0.246 16.439)" },
170
+ { label: "Orange", value: "theme-orange", color: "oklch(0.705 0.213 47.604)" },
171
+ { label: "Green", value: "theme-green", color: "oklch(0.723 0.219 149.579)" },
172
+ { label: "Blue", value: "theme-blue", color: "oklch(0.623 0.214 259.815)" },
173
+ { label: "Yellow", value: "theme-yellow", color: "oklch(0.795 0.184 86.047)" },
174
+ { label: "Violet", value: "theme-violet", color: "oklch(0.606 0.25 292.717)" },
175
+ { label: "Nuxt", value: "theme-nuxt", color: "oklch(0.79 0.19 155.63)" },
176
+ { label: "Mauve", value: "theme-mauve", color: "oklch(0.212 0.019 322.12)" },
177
+ { label: "Olive", value: "theme-olive", color: "oklch(0.228 0.013 107.4)" },
178
+ { label: "Mist", value: "theme-mist", color: "oklch(0.218 0.008 223.9)" },
179
+ { label: "Taupe", value: "theme-taupe", color: "oklch(0.214 0.009 43.1)" },
180
+ ];
181
+
182
+ const radii = [
183
+ { label: "0", value: "0rem" },
184
+ { label: "0.25", value: "0.25rem" },
185
+ { label: "0.5", value: "0.5rem" },
186
+ { label: "0.625", value: "0.625rem" },
187
+ { label: "0.75", value: "0.75rem" },
188
+ { label: "1", value: "1rem" },
189
+ ];
190
+
191
+ const THEME_KEY = "docs-color-theme";
192
+ const RADIUS_KEY = "docs-radius";
193
+
194
+ const activeTheme = ref("theme-zinc");
195
+ const activeRadius = ref("0.625rem");
196
+
197
+ onMounted(() => {
198
+ const savedTheme = localStorage.getItem(THEME_KEY);
199
+ const savedRadius = localStorage.getItem(RADIUS_KEY);
200
+ if (savedTheme) applyTheme(savedTheme, false);
201
+ if (savedRadius) applyRadius(savedRadius, false);
202
+ });
203
+
204
+ function applyTheme(value: string, save = true) {
205
+ themes.forEach((t) => document.documentElement.classList.remove(t.value));
206
+ document.documentElement.classList.add(value);
207
+ activeTheme.value = value;
208
+ if (save) localStorage.setItem(THEME_KEY, value);
209
+ }
210
+
211
+ function applyRadius(value: string, save = true) {
212
+ document.documentElement.style.setProperty("--radius", value);
213
+ activeRadius.value = value;
214
+ if (save) localStorage.setItem(RADIUS_KEY, value);
215
+ }
216
+
217
+ function setTheme(value: string) {
218
+ applyTheme(value);
219
+ }
220
+
221
+ function setRadius(value: string) {
222
+ applyRadius(value);
223
+ }
224
+
225
+ function setMode(value: string) {
226
+ document.documentElement.classList.toggle("dark", value === "dark");
227
+ colorMode.preference = value;
228
+ }
229
+ </script>