@lupinum/ginko-docs 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +112 -0
  3. package/app/app.config.ts +49 -0
  4. package/app/app.vue +71 -0
  5. package/app/assets/css/prose.css +1154 -0
  6. package/app/assets/css/tailwind.css +365 -0
  7. package/app/components/OgImage/GinkoDocs.satori.vue +117 -0
  8. package/app/components/content/DocumentPageShell.vue +55 -0
  9. package/app/components/content/Feedback.vue +97 -0
  10. package/app/components/content/PageMarkdownCopy.vue +102 -0
  11. package/app/components/mdc/MdcAccordion.vue +64 -0
  12. package/app/components/mdc/MdcAccordionItem.vue +51 -0
  13. package/app/components/mdc/MdcAlert.vue +34 -0
  14. package/app/components/mdc/MdcAside.vue +27 -0
  15. package/app/components/mdc/MdcCallout.vue +62 -0
  16. package/app/components/mdc/MdcCard.vue +186 -0
  17. package/app/components/mdc/MdcCardGroup.vue +47 -0
  18. package/app/components/mdc/MdcCenter.vue +71 -0
  19. package/app/components/mdc/MdcCodeGroup.vue +193 -0
  20. package/app/components/mdc/MdcColumn.vue +70 -0
  21. package/app/components/mdc/MdcDocImg.vue +25 -0
  22. package/app/components/mdc/MdcDropcap.vue +84 -0
  23. package/app/components/mdc/MdcError.vue +16 -0
  24. package/app/components/mdc/MdcField.vue +62 -0
  25. package/app/components/mdc/MdcFieldGroup.vue +18 -0
  26. package/app/components/mdc/MdcFigure.vue +51 -0
  27. package/app/components/mdc/MdcIdea.vue +16 -0
  28. package/app/components/mdc/MdcInfo.vue +16 -0
  29. package/app/components/mdc/MdcLayout.vue +57 -0
  30. package/app/components/mdc/MdcNote.vue +16 -0
  31. package/app/components/mdc/MdcPassage.vue +32 -0
  32. package/app/components/mdc/MdcQuiz.vue +281 -0
  33. package/app/components/mdc/MdcQuizOption.vue +91 -0
  34. package/app/components/mdc/MdcQuizQuestion.vue +226 -0
  35. package/app/components/mdc/MdcReadMore.vue +90 -0
  36. package/app/components/mdc/MdcReadMoreGroup.vue +18 -0
  37. package/app/components/mdc/MdcShortcut.vue +46 -0
  38. package/app/components/mdc/MdcStep.vue +42 -0
  39. package/app/components/mdc/MdcSteps.vue +25 -0
  40. package/app/components/mdc/MdcSuccess.vue +16 -0
  41. package/app/components/mdc/MdcTab.vue +17 -0
  42. package/app/components/mdc/MdcTabs.vue +132 -0
  43. package/app/components/mdc/MdcTimeline.vue +14 -0
  44. package/app/components/mdc/MdcTimelineItem.vue +49 -0
  45. package/app/components/mdc/MdcWarning.vue +16 -0
  46. package/app/components/mdc/icons.ts +10 -0
  47. package/app/components/prose/ProseA.vue +27 -0
  48. package/app/components/prose/ProseBlockquote.vue +14 -0
  49. package/app/components/prose/ProseCode.vue +14 -0
  50. package/app/components/prose/ProseEm.vue +14 -0
  51. package/app/components/prose/ProseH1.vue +18 -0
  52. package/app/components/prose/ProseH2.vue +24 -0
  53. package/app/components/prose/ProseH3.vue +24 -0
  54. package/app/components/prose/ProseH4.vue +14 -0
  55. package/app/components/prose/ProseHr.vue +12 -0
  56. package/app/components/prose/ProseImg.vue +120 -0
  57. package/app/components/prose/ProseLi.vue +14 -0
  58. package/app/components/prose/ProseOl.vue +14 -0
  59. package/app/components/prose/ProseP.vue +14 -0
  60. package/app/components/prose/ProsePre.vue +217 -0
  61. package/app/components/prose/ProseStrong.vue +14 -0
  62. package/app/components/prose/ProseTable.vue +16 -0
  63. package/app/components/prose/ProseTbody.vue +14 -0
  64. package/app/components/prose/ProseTd.vue +14 -0
  65. package/app/components/prose/ProseTh.vue +14 -0
  66. package/app/components/prose/ProseThead.vue +14 -0
  67. package/app/components/prose/ProseTr.vue +14 -0
  68. package/app/components/prose/ProseUl.vue +14 -0
  69. package/app/components/site/ModeToggle.vue +123 -0
  70. package/app/components/site/SiteBanner.vue +56 -0
  71. package/app/components/site/SiteFooter.vue +80 -0
  72. package/app/components/site/SiteHeader.vue +250 -0
  73. package/app/components/site/SiteInteractionLayer.vue +35 -0
  74. package/app/components/site/SiteLocaleSwitcher.vue +268 -0
  75. package/app/components/site/SiteLogoMark.vue +12 -0
  76. package/app/components/site/SiteSkipLink.vue +14 -0
  77. package/app/components/ui/accordion/Accordion.vue +15 -0
  78. package/app/components/ui/accordion/AccordionContent.vue +23 -0
  79. package/app/components/ui/accordion/AccordionItem.vue +24 -0
  80. package/app/components/ui/accordion/AccordionTrigger.vue +35 -0
  81. package/app/components/ui/accordion/index.ts +4 -0
  82. package/app/components/ui/alert/Alert.vue +21 -0
  83. package/app/components/ui/alert/AlertDescription.vue +22 -0
  84. package/app/components/ui/alert/AlertTitle.vue +22 -0
  85. package/app/components/ui/alert/index.ts +34 -0
  86. package/app/components/ui/badge/Badge.vue +28 -0
  87. package/app/components/ui/badge/index.ts +24 -0
  88. package/app/components/ui/button/Button.vue +31 -0
  89. package/app/components/ui/button/index.ts +35 -0
  90. package/app/components/ui/card/Card.vue +16 -0
  91. package/app/components/ui/card/CardAction.vue +17 -0
  92. package/app/components/ui/card/CardContent.vue +14 -0
  93. package/app/components/ui/card/CardDescription.vue +14 -0
  94. package/app/components/ui/card/CardFooter.vue +14 -0
  95. package/app/components/ui/card/CardHeader.vue +22 -0
  96. package/app/components/ui/card/CardTitle.vue +14 -0
  97. package/app/components/ui/card/card-variants.ts +15 -0
  98. package/app/components/ui/card/index.ts +8 -0
  99. package/app/components/ui/checkbox/Checkbox.vue +35 -0
  100. package/app/components/ui/checkbox/index.ts +1 -0
  101. package/app/components/ui/collapsible/Collapsible.vue +15 -0
  102. package/app/components/ui/collapsible/CollapsibleContent.vue +25 -0
  103. package/app/components/ui/collapsible/CollapsibleTrigger.vue +12 -0
  104. package/app/components/ui/collapsible/index.ts +3 -0
  105. package/app/components/ui/dialog/Dialog.vue +15 -0
  106. package/app/components/ui/dialog/DialogClose.vue +12 -0
  107. package/app/components/ui/dialog/DialogContent.vue +69 -0
  108. package/app/components/ui/dialog/DialogDescription.vue +21 -0
  109. package/app/components/ui/dialog/DialogFooter.vue +15 -0
  110. package/app/components/ui/dialog/DialogHeader.vue +12 -0
  111. package/app/components/ui/dialog/DialogOverlay.vue +26 -0
  112. package/app/components/ui/dialog/DialogPortal.vue +12 -0
  113. package/app/components/ui/dialog/DialogTitle.vue +21 -0
  114. package/app/components/ui/dialog/DialogTrigger.vue +12 -0
  115. package/app/components/ui/dialog/index.ts +10 -0
  116. package/app/components/ui/dropdown-menu/DropdownMenu.vue +15 -0
  117. package/app/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +36 -0
  118. package/app/components/ui/dropdown-menu/DropdownMenuContent.vue +40 -0
  119. package/app/components/ui/dropdown-menu/DropdownMenuGroup.vue +12 -0
  120. package/app/components/ui/dropdown-menu/DropdownMenuItem.vue +41 -0
  121. package/app/components/ui/dropdown-menu/DropdownMenuLabel.vue +25 -0
  122. package/app/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +15 -0
  123. package/app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +37 -0
  124. package/app/components/ui/dropdown-menu/DropdownMenuSeparator.vue +23 -0
  125. package/app/components/ui/dropdown-menu/DropdownMenuShortcut.vue +17 -0
  126. package/app/components/ui/dropdown-menu/DropdownMenuSub.vue +15 -0
  127. package/app/components/ui/dropdown-menu/DropdownMenuSubContent.vue +29 -0
  128. package/app/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +31 -0
  129. package/app/components/ui/dropdown-menu/DropdownMenuTrigger.vue +14 -0
  130. package/app/components/ui/dropdown-menu/index.ts +16 -0
  131. package/app/components/ui/input/Input.vue +23 -0
  132. package/app/components/ui/input/index.ts +1 -0
  133. package/app/components/ui/kbd/Kbd.vue +23 -0
  134. package/app/components/ui/kbd/KbdGroup.vue +14 -0
  135. package/app/components/ui/kbd/index.ts +22 -0
  136. package/app/components/ui/label/Label.vue +14 -0
  137. package/app/components/ui/label/index.ts +1 -0
  138. package/app/components/ui/scroll-area/ScrollArea.vue +39 -0
  139. package/app/components/ui/scroll-area/ScrollBar.vue +33 -0
  140. package/app/components/ui/scroll-area/index.ts +2 -0
  141. package/app/components/ui/separator/Separator.vue +27 -0
  142. package/app/components/ui/separator/index.ts +1 -0
  143. package/app/components/ui/sheet/Sheet.vue +15 -0
  144. package/app/components/ui/sheet/SheetClose.vue +12 -0
  145. package/app/components/ui/sheet/SheetContent.vue +59 -0
  146. package/app/components/ui/sheet/SheetDescription.vue +21 -0
  147. package/app/components/ui/sheet/SheetFooter.vue +12 -0
  148. package/app/components/ui/sheet/SheetHeader.vue +12 -0
  149. package/app/components/ui/sheet/SheetOverlay.vue +26 -0
  150. package/app/components/ui/sheet/SheetTitle.vue +21 -0
  151. package/app/components/ui/sheet/SheetTrigger.vue +12 -0
  152. package/app/components/ui/sheet/index.ts +8 -0
  153. package/app/components/ui/switch/Switch.vue +39 -0
  154. package/app/components/ui/switch/index.ts +1 -0
  155. package/app/components/ui/tabs/Tabs.vue +24 -0
  156. package/app/components/ui/tabs/TabsContent.vue +21 -0
  157. package/app/components/ui/tabs/TabsList.vue +26 -0
  158. package/app/components/ui/tabs/TabsTrigger.vue +28 -0
  159. package/app/components/ui/tabs/index.ts +4 -0
  160. package/app/components/ui/textarea/Textarea.vue +31 -0
  161. package/app/components/ui/textarea/index.ts +1 -0
  162. package/app/composables/site-navigation.utils.ts +80 -0
  163. package/app/composables/useCanonicalUrl.ts +9 -0
  164. package/app/composables/useContentRouteAlternates.ts +43 -0
  165. package/app/composables/useGinkoAnalytics.ts +37 -0
  166. package/app/composables/useGinkoDocsConfig.ts +6 -0
  167. package/app/composables/useGinkoOgImage.ts +20 -0
  168. package/app/composables/useLocalizedPath.ts +12 -0
  169. package/app/composables/useLocalizedRouteSwitch.ts +31 -0
  170. package/app/composables/usePageMarkdownActions.ts +79 -0
  171. package/app/composables/useSchemaJsonLd.ts +25 -0
  172. package/app/composables/useSiteNavigation.ts +86 -0
  173. package/app/config/site.utils.ts +36 -0
  174. package/app/error.vue +125 -0
  175. package/app/features/docs/components/DocsContributeLinks.vue +65 -0
  176. package/app/features/docs/components/DocsMobileToc.vue +104 -0
  177. package/app/features/docs/components/DocsPageContent.vue +188 -0
  178. package/app/features/docs/components/DocsPageNav.vue +57 -0
  179. package/app/features/docs/components/DocsSidebar.vue +129 -0
  180. package/app/features/docs/components/DocsSidebarDropdown.vue +70 -0
  181. package/app/features/docs/components/DocsSidebarItem.vue +169 -0
  182. package/app/features/docs/components/DocsSidebarList.vue +35 -0
  183. package/app/features/docs/components/DocsSidebarTabs.vue +35 -0
  184. package/app/features/docs/components/DocsToc.vue +60 -0
  185. package/app/features/docs/composables/useDocsEntryPath.ts +13 -0
  186. package/app/features/docs/composables/useDocsNavigation.ts +29 -0
  187. package/app/features/docs/composables/useDocsNavigationData.ts +15 -0
  188. package/app/features/docs/composables/useScrollspy.ts +67 -0
  189. package/app/features/docs/docs-navigation.ts +170 -0
  190. package/app/features/search/command-center.ts +156 -0
  191. package/app/features/search/components/SiteCommandCenter.vue +323 -0
  192. package/app/features/search/components/SiteSearchPageHighlight.vue +124 -0
  193. package/app/features/search/useCommandCenter.ts +300 -0
  194. package/app/layouts/blog.vue +15 -0
  195. package/app/layouts/default.vue +12 -0
  196. package/app/layouts/docs.vue +20 -0
  197. package/app/lib/errors.ts +84 -0
  198. package/app/lib/schema-org.ts +38 -0
  199. package/app/lib/utils.ts +7 -0
  200. package/app/pages/blog/[slug].vue +211 -0
  201. package/app/pages/blog/index.vue +106 -0
  202. package/app/pages/docs/[...slug].vue +10 -0
  203. package/app/pages/docs/index.vue +23 -0
  204. package/app/pages/index.vue +140 -0
  205. package/app/plugins/ssr-width.ts +6 -0
  206. package/app/utils/content.ts +56 -0
  207. package/app/utils/index.ts +1 -0
  208. package/components.ts +9 -0
  209. package/content.js +175 -0
  210. package/content.ts +148 -0
  211. package/i18n/i18n.config.ts +10 -0
  212. package/i18n/locales.ts +81 -0
  213. package/i18n/messages/global/banner.ts +8 -0
  214. package/i18n/messages/global/blog.ts +14 -0
  215. package/i18n/messages/global/command.ts +44 -0
  216. package/i18n/messages/global/docs.ts +32 -0
  217. package/i18n/messages/global/errors.ts +36 -0
  218. package/i18n/messages/global/feedback.ts +11 -0
  219. package/i18n/messages/global/index.ts +21 -0
  220. package/i18n/messages/global/nav.ts +18 -0
  221. package/i18n/messages/global/site.ts +4 -0
  222. package/i18n/messages/global/theme.ts +7 -0
  223. package/i18n/messages/index.ts +20 -0
  224. package/i18n/messages/localize.ts +35 -0
  225. package/i18n/messages/source.ts +5 -0
  226. package/i18n/routes.ts +33 -0
  227. package/icon-bundle.ts +141 -0
  228. package/index.d.ts +13 -0
  229. package/modules/feature-routing.ts +32 -0
  230. package/nuxt.config.ts +194 -0
  231. package/package.json +91 -0
  232. package/runtime/markdown/inline-code-highlight.ts +155 -0
  233. package/runtime/server/agent-markdown.ts +21 -0
  234. package/runtime/utils.ts +7 -0
  235. package/server/mcp/tools/get-page.ts +14 -0
  236. package/server/mcp/tools/list-pages.ts +10 -0
  237. package/server/plugins/agent-markdown.ts +6 -0
  238. package/shared/route-slugs.ts +5 -0
  239. package/shared/types/app-config.ts +100 -0
  240. package/tags.ts +204 -0
@@ -0,0 +1,193 @@
1
+ <script setup lang="ts">
2
+ import type { VNode } from "vue";
3
+ import { Comment, cloneVNode, computed, h, ref, resolveComponent, useSlots } from "vue";
4
+ import { useClipboard } from "@vueuse/core";
5
+ import { useI18n } from "#imports";
6
+ import { cn } from "../../utils";
7
+
8
+ type CodeBlockProps = {
9
+ code?: string;
10
+ filename?: string | null;
11
+ language?: string | null;
12
+ meta?: string | null;
13
+ };
14
+
15
+ type CodeGroupItem = {
16
+ code: string;
17
+ icon?: string;
18
+ label: string;
19
+ node: VNode;
20
+ value: string;
21
+ };
22
+
23
+ const slots = useSlots();
24
+ const activeValue = ref("");
25
+ const { copy, copied } = useClipboard();
26
+ const { t } = useI18n();
27
+
28
+ const PACKAGE_ICONS: Record<string, string> = {
29
+ bun: "logos:bun",
30
+ npm: "logos:npm-icon",
31
+ pnpm: "logos:pnpm",
32
+ yarn: "logos:yarn",
33
+ };
34
+
35
+ function isRenderableNode(node: VNode) {
36
+ return node.type !== Comment;
37
+ }
38
+
39
+ function textFromNode(input: unknown): string {
40
+ if (typeof input === "string") return input;
41
+ if (Array.isArray(input)) return input.map(textFromNode).join("");
42
+ if (input && typeof input === "object" && "children" in input) {
43
+ return textFromNode((input as VNode).children);
44
+ }
45
+
46
+ return "";
47
+ }
48
+
49
+ function cleanLabel(input: string | null | undefined): string {
50
+ return (
51
+ input
52
+ ?.trim()
53
+ .replace(/^\[(.*)]$/, "$1")
54
+ .trim() || ""
55
+ );
56
+ }
57
+
58
+ function labelFromMeta(meta: string | null | undefined): string {
59
+ const match = meta?.match(/\[([^\]]+)]/);
60
+ return cleanLabel(match?.[1]);
61
+ }
62
+
63
+ function nodeProps(node: VNode): CodeBlockProps {
64
+ return (node.props || {}) as CodeBlockProps;
65
+ }
66
+
67
+ function nodeLabel(node: VNode, index: number): string {
68
+ const props = nodeProps(node);
69
+
70
+ return (
71
+ cleanLabel(props.filename) ||
72
+ labelFromMeta(props.meta) ||
73
+ cleanLabel(props.language) ||
74
+ `Code ${index + 1}`
75
+ );
76
+ }
77
+
78
+ const items = computed<CodeGroupItem[]>(() =>
79
+ (slots.default?.() || []).filter(isRenderableNode).map((node, index) => {
80
+ const label = nodeLabel(node, index);
81
+ const value = `${label}-${index}`;
82
+ const normalizedLabel = label.toLowerCase();
83
+ const props = nodeProps(node);
84
+
85
+ return {
86
+ code: props.code || textFromNode(node.children),
87
+ icon: PACKAGE_ICONS[normalizedLabel],
88
+ label,
89
+ node,
90
+ value,
91
+ };
92
+ }),
93
+ );
94
+
95
+ const activeItem = computed(() => {
96
+ const current = items.value.find((item) => item.value === activeValue.value);
97
+ return current || items.value[0];
98
+ });
99
+
100
+ function selectItem(value: string) {
101
+ activeValue.value = value;
102
+ }
103
+
104
+ async function copyActiveCode() {
105
+ await copy(activeItem.value?.code || "");
106
+ }
107
+
108
+ function renderCodeGroup() {
109
+ if (!items.value.length) return null;
110
+ const Icon = resolveComponent("Icon");
111
+
112
+ return h(
113
+ "div",
114
+ {
115
+ class: "content-codegroup not-prose group",
116
+ },
117
+ [
118
+ h("div", { class: "content-codegroup-header" }, [
119
+ h(
120
+ "div",
121
+ {
122
+ class: "content-codegroup-tabs",
123
+ role: "tablist",
124
+ },
125
+ items.value.map((item) =>
126
+ h(
127
+ "button",
128
+ {
129
+ key: item.value,
130
+ type: "button",
131
+ role: "tab",
132
+ "aria-selected": activeItem.value?.value === item.value ? "true" : "false",
133
+ class: cn(
134
+ "content-codegroup-tab",
135
+ activeItem.value?.value === item.value && "content-codegroup-tab-active",
136
+ ),
137
+ onClick: () => selectItem(item.value),
138
+ },
139
+ [
140
+ item.icon
141
+ ? h(Icon, {
142
+ name: item.icon,
143
+ class: "content-codegroup-tab-icon",
144
+ "aria-hidden": "true",
145
+ })
146
+ : null,
147
+ item.label,
148
+ ],
149
+ ),
150
+ ),
151
+ ),
152
+ h(
153
+ "button",
154
+ {
155
+ type: "button",
156
+ class: "content-codeblock-copy-button content-codegroup-copy-button",
157
+ "aria-label": copied.value ? t("docs.copiedText") : t("docs.copyText"),
158
+ onClick: copyActiveCode,
159
+ },
160
+ [
161
+ h(Icon, {
162
+ name: copied.value ? "lucide:check" : "lucide:clipboard",
163
+ class: "size-3.5",
164
+ "aria-hidden": "true",
165
+ }),
166
+ ],
167
+ ),
168
+ ]),
169
+ h(
170
+ "div",
171
+ {
172
+ class: "content-codegroup-panels",
173
+ },
174
+ items.value.map((item) =>
175
+ h(
176
+ "div",
177
+ {
178
+ key: item.value,
179
+ role: "tabpanel",
180
+ style: activeItem.value?.value === item.value ? undefined : { display: "none" },
181
+ },
182
+ [cloneVNode(item.node, { inGroup: true })],
183
+ ),
184
+ ),
185
+ ),
186
+ ],
187
+ );
188
+ }
189
+ </script>
190
+
191
+ <template>
192
+ <component :is="renderCodeGroup" />
193
+ </template>
@@ -0,0 +1,70 @@
1
+ <script setup lang="ts">
2
+ import { computed, inject, nextTick, onMounted, ref, type HTMLAttributes } from "vue";
3
+ import { cn } from "../../utils";
4
+
5
+ type ValueProp<T extends string> = T | { value?: T | string };
6
+ type ColumnSize = "sm" | "md" | "lg";
7
+
8
+ const props = withDefaults(
9
+ defineProps<{
10
+ size?: ValueProp<ColumnSize>;
11
+ class?: HTMLAttributes["class"];
12
+ }>(),
13
+ {
14
+ size: "md",
15
+ },
16
+ );
17
+
18
+ const parentLayoutType = inject(
19
+ "mdc-layout-type",
20
+ computed(() => "default"),
21
+ );
22
+
23
+ const sizeValue = computed<ColumnSize>(() => {
24
+ if (typeof props.size === "string") return props.size;
25
+ return (props.size?.value as ColumnSize | undefined) ?? "md";
26
+ });
27
+
28
+ const columnSizeClass = {
29
+ sm: "content-layout-column-sm",
30
+ md: "content-layout-column-md",
31
+ lg: "content-layout-column-lg",
32
+ } as const;
33
+
34
+ const isLastColumn = ref(false);
35
+ const columnRef = ref<HTMLElement | null>(null);
36
+
37
+ onMounted(() => {
38
+ nextTick(() => {
39
+ if (columnRef.value?.parentElement) {
40
+ const siblings = Array.from(columnRef.value.parentElement.children);
41
+ isLastColumn.value = siblings.indexOf(columnRef.value) === siblings.length - 1;
42
+ }
43
+ });
44
+ });
45
+
46
+ const showDivider = computed(
47
+ () => !isLastColumn.value && ["border", "border-dashed"].includes(parentLayoutType.value),
48
+ );
49
+ </script>
50
+
51
+ <template>
52
+ <div
53
+ ref="columnRef"
54
+ :class="
55
+ cn(
56
+ 'content-layout-column',
57
+ columnSizeClass[sizeValue],
58
+ showDivider && 'content-layout-column-divider',
59
+ showDivider &&
60
+ parentLayoutType === 'border-dashed' &&
61
+ 'content-layout-column-divider-dashed',
62
+ props.class,
63
+ )
64
+ "
65
+ >
66
+ <div class="content-layout-column-inner">
67
+ <slot />
68
+ </div>
69
+ </div>
70
+ </template>
@@ -0,0 +1,25 @@
1
+ <script setup lang="ts">
2
+ import { computed } from "vue";
3
+
4
+ /**
5
+ * Wrap standalone `![alt](url)` lines in markdown with this block so Comark can
6
+ * keep the image outside paragraph wrappers.
7
+ * Avoids invalid `<p><figure>...</figure></p>` and hydration mismatches.
8
+ *
9
+ * @example
10
+ * ::doc-img
11
+ * ![Alt text](https://shadcn-starter.ginko-content.dev/images/example.jpg)
12
+ * ::
13
+ */
14
+ const props = withDefaults(defineProps<{ bleed?: boolean | string }>(), { bleed: undefined });
15
+
16
+ const shouldBleed = computed(
17
+ () => props.bleed === true || props.bleed === "true" || props.bleed === "outside",
18
+ );
19
+ </script>
20
+
21
+ <template>
22
+ <div class="content-doc-img" :data-bleed="shouldBleed ? 'true' : undefined">
23
+ <slot unwrap="p" />
24
+ </div>
25
+ </template>
@@ -0,0 +1,84 @@
1
+ <script setup lang="ts">
2
+ import { computed, type CSSProperties } from "vue";
3
+ import { cn } from "../../utils";
4
+ import type { HTMLAttributes } from "vue";
5
+
6
+ const props = withDefaults(
7
+ defineProps<{
8
+ /**
9
+ * Drop cap spans this many line boxes (`initial-letter` size / sink).
10
+ * Markdown props may arrive as strings.
11
+ */
12
+ lines?: number | string;
13
+ class?: HTMLAttributes["class"];
14
+ }>(),
15
+ { lines: 2 },
16
+ );
17
+
18
+ const lineCount = computed(() => {
19
+ const n = Math.round(Number(props.lines));
20
+ if (Number.isFinite(n) && n >= 2 && n <= 4) return n;
21
+ return 2;
22
+ });
23
+
24
+ const dropcapStyle = computed(
25
+ (): CSSProperties => ({
26
+ "--mdc-dropcap-lines": String(lineCount.value),
27
+ }),
28
+ );
29
+ </script>
30
+
31
+ <template>
32
+ <div
33
+ :class="
34
+ cn(
35
+ 'mdc-dropcap content-prose content-prose-trim not-prose my-6 text-pretty [&>p]:text-muted-foreground',
36
+ '[&>p:not(:first-of-type)]:mt-4',
37
+ '[&>p:first-of-type]:flow-root',
38
+ props.class,
39
+ )
40
+ "
41
+ :style="dropcapStyle"
42
+ >
43
+ <slot />
44
+ </div>
45
+ </template>
46
+
47
+ <style scoped>
48
+ .mdc-dropcap {
49
+ --mdc-dropcap-lines: 2;
50
+ }
51
+
52
+ .mdc-dropcap :deep(p:first-of-type)::first-letter {
53
+ font-weight: 600;
54
+ color: var(--foreground);
55
+ margin-inline-end: 0.15em;
56
+ }
57
+
58
+ /* CSS Inline Layout 3: typographic alignment vs float hacks (Chrome 110+, Safari + webkit prefix). */
59
+ @supports (-webkit-initial-letter: 2) or (initial-letter: 1 1) {
60
+ .mdc-dropcap :deep(p:first-of-type)::first-letter {
61
+ -webkit-initial-letter: var(--mdc-dropcap-lines);
62
+ initial-letter: var(--mdc-dropcap-lines);
63
+ }
64
+ }
65
+
66
+ /* Firefox and legacy: float plus glyph height approximates N line boxes (`lh` ties to paragraph leading). */
67
+ @supports (not ((-webkit-initial-letter: 2) or (initial-letter: 1 1))) {
68
+ .mdc-dropcap :deep(p:first-of-type)::first-letter {
69
+ float: left;
70
+ line-height: 1;
71
+ margin-top: 0.08em;
72
+ margin-bottom: -0.06em;
73
+ font-size: calc(var(--mdc-dropcap-lines) * 1.325em);
74
+ }
75
+
76
+ @supports (font-size: 1lh) {
77
+ .mdc-dropcap :deep(p:first-of-type)::first-letter {
78
+ font-size: calc(var(--mdc-dropcap-lines) * 1lh);
79
+ margin-top: 0.05em;
80
+ margin-bottom: -0.06em;
81
+ }
82
+ }
83
+ }
84
+ </style>
@@ -0,0 +1,16 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from "vue";
3
+ import MdcCallout from "./MdcCallout.vue";
4
+
5
+ const props = defineProps<{
6
+ title?: string;
7
+ icon?: string;
8
+ class?: HTMLAttributes["class"];
9
+ }>();
10
+ </script>
11
+
12
+ <template>
13
+ <MdcCallout :title="title" :icon="icon" type="error" :class="props.class">
14
+ <slot />
15
+ </MdcCallout>
16
+ </template>
@@ -0,0 +1,62 @@
1
+ <script setup lang="ts">
2
+ import { inject } from "vue";
3
+
4
+ const MDC_FIELD_GROUP = Symbol.for("mdc.fieldGroup");
5
+ const inFieldGroup = inject(MDC_FIELD_GROUP, false);
6
+
7
+ defineProps<{
8
+ name: string;
9
+ type?: string;
10
+ required?: boolean;
11
+ description?: string;
12
+ defaultValue?: string;
13
+ }>();
14
+ </script>
15
+
16
+ <template>
17
+ <div
18
+ data-slot="card"
19
+ :class="
20
+ inFieldGroup
21
+ ? 'not-prose flex flex-col gap-0 rounded-none border-0 bg-transparent py-0 text-card-foreground shadow-none ring-0 outline-none'
22
+ : 'not-prose my-4 flex flex-col gap-0 rounded-xl border bg-card py-0 text-card-foreground shadow-xs'
23
+ "
24
+ >
25
+ <div data-slot="card-content" class="space-y-4 px-6 py-6">
26
+ <div class="flex flex-col gap-3 sm:flex-row sm:items-center">
27
+ <div class="flex flex-wrap items-center gap-2">
28
+ <code class="relative rounded-md bg-muted px-1 font-sans font-normal text-foreground">
29
+ {{ name }}
30
+ </code>
31
+
32
+ <span
33
+ v-if="required"
34
+ class="inline-flex items-center rounded-full border border-transparent bg-secondary px-2.5 py-0.5 text-xs font-semibold text-secondary-foreground"
35
+ >
36
+ Required
37
+ </span>
38
+ </div>
39
+
40
+ <span v-if="type" class="font-mono text-sm text-muted-foreground sm:ml-auto">
41
+ {{ type }}
42
+ </span>
43
+ </div>
44
+
45
+ <div class="content-prose content-prose-trim text-muted-foreground">
46
+ <slot v-if="$slots.default" unwrap="p" />
47
+ <span v-else-if="description">{{ description }}</span>
48
+ </div>
49
+ </div>
50
+
51
+ <div
52
+ v-if="defaultValue"
53
+ data-slot="card-footer"
54
+ class="flex flex-wrap items-center gap-2 border-t border-border pb-6 text-sm"
55
+ >
56
+ <span class="font-medium text-foreground">Default:</span>
57
+ <code class="relative rounded-md bg-muted px-1 font-sans font-normal text-muted-foreground">
58
+ {{ defaultValue }}
59
+ </code>
60
+ </div>
61
+ </div>
62
+ </template>
@@ -0,0 +1,18 @@
1
+ <script setup lang="ts">
2
+ import { provide } from "vue";
3
+
4
+ const MDC_FIELD_GROUP = Symbol.for("mdc.fieldGroup");
5
+
6
+ provide(MDC_FIELD_GROUP, true);
7
+ </script>
8
+
9
+ <template>
10
+ <div
11
+ data-slot="card"
12
+ class="not-prose my-4 flex flex-col gap-0 overflow-hidden rounded-xl border bg-card py-0 text-card-foreground shadow-xs"
13
+ >
14
+ <div class="flex flex-col divide-y divide-border">
15
+ <slot />
16
+ </div>
17
+ </div>
18
+ </template>
@@ -0,0 +1,51 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from "vue";
3
+ import { computed } from "vue";
4
+ import { cn } from "../../utils";
5
+
6
+ const props = defineProps<{
7
+ src?: string;
8
+ alt?: string;
9
+ caption?: string;
10
+ width?: string | number;
11
+ height?: string | number;
12
+ bleed?: boolean | string;
13
+ aspect?: "auto" | "video" | "wide" | "square" | "portrait";
14
+ fit?: "cover" | "contain";
15
+ class?: HTMLAttributes["class"];
16
+ }>();
17
+
18
+ const shouldBleed = computed(
19
+ () => props.bleed === true || props.bleed === "true" || props.bleed === "outside",
20
+ );
21
+
22
+ const aspectClass = computed(() => {
23
+ return {
24
+ auto: "",
25
+ video: "aspect-video",
26
+ wide: "aspect-[21/9]",
27
+ square: "aspect-square",
28
+ portrait: "aspect-[4/5]",
29
+ }[props.aspect ?? "auto"];
30
+ });
31
+ </script>
32
+
33
+ <template>
34
+ <figure
35
+ :class="cn('content-media not-prose', props.class)"
36
+ :data-bleed="shouldBleed ? 'true' : undefined"
37
+ >
38
+ <img
39
+ v-if="src"
40
+ :src="src"
41
+ :alt="alt"
42
+ :width="width"
43
+ :height="height"
44
+ :class="cn('w-full', aspectClass, fit === 'contain' ? 'object-contain' : 'object-cover')"
45
+ />
46
+ <slot />
47
+ <figcaption v-if="caption || alt" class="text-center">
48
+ {{ caption || alt }}
49
+ </figcaption>
50
+ </figure>
51
+ </template>
@@ -0,0 +1,16 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from "vue";
3
+ import MdcCallout from "./MdcCallout.vue";
4
+
5
+ const props = defineProps<{
6
+ title?: string;
7
+ icon?: string;
8
+ class?: HTMLAttributes["class"];
9
+ }>();
10
+ </script>
11
+
12
+ <template>
13
+ <MdcCallout :title="title" :icon="icon" type="idea" :class="props.class">
14
+ <slot />
15
+ </MdcCallout>
16
+ </template>
@@ -0,0 +1,16 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from "vue";
3
+ import MdcCallout from "./MdcCallout.vue";
4
+
5
+ const props = defineProps<{
6
+ title?: string;
7
+ icon?: string;
8
+ class?: HTMLAttributes["class"];
9
+ }>();
10
+ </script>
11
+
12
+ <template>
13
+ <MdcCallout :title="title" :icon="icon" type="info" :class="props.class">
14
+ <slot />
15
+ </MdcCallout>
16
+ </template>
@@ -0,0 +1,57 @@
1
+ <script setup lang="ts">
2
+ import { computed, provide, type HTMLAttributes } from "vue";
3
+ import { cn } from "../../utils";
4
+
5
+ type ValueProp<T extends string> = T | { value?: T | string };
6
+ type LayoutType = "default" | "card" | "border" | "border-dashed" | "outline" | "outline-dashed";
7
+
8
+ const props = withDefaults(
9
+ defineProps<{
10
+ type?: ValueProp<LayoutType>;
11
+ class?: HTMLAttributes["class"];
12
+ }>(),
13
+ {
14
+ type: "default",
15
+ },
16
+ );
17
+
18
+ const layoutTypeValue = computed<LayoutType>(() => {
19
+ if (typeof props.type === "string") return props.type;
20
+ return (props.type?.value as LayoutType | undefined) ?? "default";
21
+ });
22
+
23
+ const layoutRowClass = "content-layout-row";
24
+
25
+ provide(
26
+ "mdc-layout-type",
27
+ computed(() => layoutTypeValue.value),
28
+ );
29
+ provide("mdc-inside-layout", true);
30
+ </script>
31
+
32
+ <template>
33
+ <div :class="cn('content-layout h-full', props.class)">
34
+ <div v-if="layoutTypeValue === 'card'" class="w-full">
35
+ <div class="h-full">
36
+ <div :class="layoutRowClass">
37
+ <slot />
38
+ </div>
39
+ </div>
40
+ </div>
41
+ <div
42
+ v-else
43
+ :class="
44
+ cn(
45
+ layoutRowClass,
46
+ 'h-full',
47
+ layoutTypeValue === 'border' && 'content-layout-row-border',
48
+ layoutTypeValue === 'border-dashed' && 'content-layout-row-border-dashed',
49
+ layoutTypeValue === 'outline' && 'content-layout-row-outline',
50
+ layoutTypeValue === 'outline-dashed' && 'content-layout-row-outline-dashed',
51
+ )
52
+ "
53
+ >
54
+ <slot />
55
+ </div>
56
+ </div>
57
+ </template>
@@ -0,0 +1,16 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from "vue";
3
+ import MdcCallout from "./MdcCallout.vue";
4
+
5
+ const props = defineProps<{
6
+ title?: string;
7
+ icon?: string;
8
+ class?: HTMLAttributes["class"];
9
+ }>();
10
+ </script>
11
+
12
+ <template>
13
+ <MdcCallout :title="title" :icon="icon" type="note" :class="props.class">
14
+ <slot />
15
+ </MdcCallout>
16
+ </template>
@@ -0,0 +1,32 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from "vue";
3
+ import { cn } from "../../utils";
4
+
5
+ const props = defineProps<{
6
+ /** Short label above the block (e.g. excerpt, chapter). */
7
+ label?: string;
8
+ class?: HTMLAttributes["class"];
9
+ }>();
10
+ </script>
11
+
12
+ <template>
13
+ <aside class="not-prose my-4">
14
+ <div
15
+ data-slot="card"
16
+ :class="
17
+ cn(
18
+ 'content-prose content-prose-trim gap-0 rounded-xl border border-border bg-muted/40 px-4 py-3 text-muted-foreground shadow-xs',
19
+ props.class,
20
+ )
21
+ "
22
+ >
23
+ <p
24
+ v-if="label"
25
+ class="mb-3 text-xs font-medium tracking-wider text-muted-foreground uppercase"
26
+ >
27
+ {{ label }}
28
+ </p>
29
+ <slot />
30
+ </div>
31
+ </aside>
32
+ </template>