doccupine 0.0.102 → 0.0.103
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.
- package/dist/index.js +7 -0
- package/dist/lib/layout.js +1 -0
- package/dist/lib/structures.js +2 -0
- package/dist/lib/types.d.ts +2 -0
- package/dist/templates/components/Docs.d.ts +1 -1
- package/dist/templates/components/Docs.js +10 -14
- package/dist/templates/components/MDXComponents.d.ts +1 -1
- package/dist/templates/components/MDXComponents.js +16 -15
- package/dist/templates/components/SectionNavProvider.d.ts +1 -1
- package/dist/templates/components/SectionNavProvider.js +8 -1
- package/dist/templates/components/SideBar.d.ts +1 -1
- package/dist/templates/components/SideBar.js +136 -19
- package/dist/templates/components/layout/DocsComponents.d.ts +1 -1
- package/dist/templates/components/layout/DocsComponents.js +102 -6
- package/dist/templates/components/layout/DocsNavigation.d.ts +1 -1
- package/dist/templates/components/layout/DocsNavigation.js +27 -12
- package/dist/templates/components/layout/Slug.d.ts +1 -0
- package/dist/templates/components/layout/Slug.js +32 -0
- package/dist/templates/components/layout/Tabs.d.ts +1 -1
- package/dist/templates/components/layout/Tabs.js +11 -0
- package/dist/templates/components/layout/Update.d.ts +1 -1
- package/dist/templates/components/layout/Update.js +19 -14
- package/dist/templates/mdx/components.mdx.d.ts +1 -1
- package/dist/templates/mdx/components.mdx.js +1 -0
- package/dist/templates/mdx/globals.mdx.d.ts +1 -1
- package/dist/templates/mdx/globals.mdx.js +1 -0
- package/dist/templates/mdx/index.mdx.d.ts +1 -1
- package/dist/templates/mdx/index.mdx.js +1 -0
- package/dist/templates/mdx/navigation.mdx.d.ts +1 -1
- package/dist/templates/mdx/navigation.mdx.js +69 -1
- package/dist/templates/mdx/platform/build-and-deploy.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/build-and-deploy.mdx.js +1 -0
- package/dist/templates/mdx/platform/file-editor.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/file-editor.mdx.js +1 -0
- package/dist/templates/mdx/platform/index.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/index.mdx.js +1 -0
- package/dist/templates/mdx/platform/project-settings.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/project-settings.mdx.js +1 -0
- package/dist/templates/mdx/platform/site-settings.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/site-settings.mdx.js +1 -0
- package/dist/templates/mdx/tabs.mdx.d.ts +1 -1
- package/dist/templates/mdx/tabs.mdx.js +13 -6
- package/dist/templates/utils/orderNavItems.d.ts +1 -1
- package/dist/templates/utils/orderNavItems.js +11 -0
- package/package.json +1 -1
|
@@ -114,6 +114,7 @@ export const StyledMarkdownContainer = styled.div\`
|
|
|
114
114
|
interface Props {
|
|
115
115
|
theme?: Theme;
|
|
116
116
|
$isActive?: boolean;
|
|
117
|
+
$isOpen?: boolean;
|
|
117
118
|
$hasSectionBar?: boolean;
|
|
118
119
|
}
|
|
119
120
|
|
|
@@ -271,6 +272,13 @@ export const StyledStrong = styled.strong<{ theme: Theme }>\`
|
|
|
271
272
|
font-weight: 600;
|
|
272
273
|
\${({ theme }) => styledStrong(theme)};
|
|
273
274
|
color: \${({ theme }) => theme.colors.accentStrong};
|
|
275
|
+
display: inline-flex;
|
|
276
|
+
align-items: center;
|
|
277
|
+
gap: 8px;
|
|
278
|
+
|
|
279
|
+
& svg {
|
|
280
|
+
flex-shrink: 0;
|
|
281
|
+
}
|
|
274
282
|
\`;
|
|
275
283
|
|
|
276
284
|
export const StyledSidebarListItem = styled.li\`
|
|
@@ -279,16 +287,26 @@ export const StyledSidebarListItem = styled.li\`
|
|
|
279
287
|
clear: both;
|
|
280
288
|
\`;
|
|
281
289
|
|
|
282
|
-
|
|
290
|
+
// Shared appearance for every sidebar row - leaf links AND nested group
|
|
291
|
+
// headers - so hover, active state, and the left rail read identically.
|
|
292
|
+
const sidebarRowStyles = css<Props>\`
|
|
283
293
|
text-decoration: none;
|
|
284
294
|
font-size: \${({ theme }) => theme.fontSizes.small.lg};
|
|
285
295
|
line-height: 1.6;
|
|
286
296
|
color: \${({ theme }) => theme.colors.accentMuted};
|
|
287
297
|
padding: 5px 0 5px 20px;
|
|
288
298
|
display: flex;
|
|
299
|
+
align-items: center;
|
|
300
|
+
gap: 8px;
|
|
301
|
+
flex: 1;
|
|
302
|
+
min-width: 0;
|
|
289
303
|
transition: all 0.3s ease;
|
|
290
304
|
border-left: solid 1px \${({ theme }) => theme.colors.grayLight};
|
|
291
305
|
|
|
306
|
+
& svg {
|
|
307
|
+
flex-shrink: 0;
|
|
308
|
+
}
|
|
309
|
+
|
|
292
310
|
&:hover {
|
|
293
311
|
color: \${({ theme }) => theme.colors.accent};
|
|
294
312
|
border-color: \${({ theme }) => theme.colors.primary};
|
|
@@ -296,11 +314,89 @@ export const StyledSidebarListItemLink = styled(Link)<Props>\`
|
|
|
296
314
|
|
|
297
315
|
\${({ $isActive, theme }) =>
|
|
298
316
|
$isActive &&
|
|
299
|
-
\`
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
317
|
+
css\`
|
|
318
|
+
color: \${theme.colors.accentStrong};
|
|
319
|
+
border-color: \${theme.colors.primary};
|
|
320
|
+
font-weight: 600;
|
|
321
|
+
\`};
|
|
322
|
+
\`;
|
|
323
|
+
|
|
324
|
+
// The collapse chevron points right when closed and rotates to point down
|
|
325
|
+
// when the group is open.
|
|
326
|
+
const sidebarChevron = css<Props>\`
|
|
327
|
+
& .lucide-chevron-right {
|
|
328
|
+
margin-left: auto;
|
|
329
|
+
transition: transform 0.3s ease;
|
|
330
|
+
|
|
331
|
+
\${({ $isOpen }) =>
|
|
332
|
+
$isOpen &&
|
|
333
|
+
css\`
|
|
334
|
+
transform: rotate(90deg);
|
|
335
|
+
\`}
|
|
336
|
+
}
|
|
337
|
+
\`;
|
|
338
|
+
|
|
339
|
+
export const StyledSidebarListItemLink = styled(Link)<Props>\`
|
|
340
|
+
\${sidebarRowStyles};
|
|
341
|
+
\`;
|
|
342
|
+
|
|
343
|
+
// Nested navigation group. The header shares the link appearance/hover; a
|
|
344
|
+
// non-navigable group is a single toggle button, while a group that is also a
|
|
345
|
+
// page pairs a link with a chevron toggle. Children live in
|
|
346
|
+
// StyledSidebarGroupContent, which animates open/closed via height 0 <-> auto
|
|
347
|
+
// (enabled by interpolate-size in GlobalStyles, same as the Accordion).
|
|
348
|
+
export const StyledSidebarGroupButton = styled.button<Props>\`
|
|
349
|
+
\${resetButton};
|
|
350
|
+
\${sidebarRowStyles};
|
|
351
|
+
\${sidebarChevron};
|
|
352
|
+
width: 100%;
|
|
353
|
+
text-align: left;
|
|
354
|
+
\`;
|
|
355
|
+
|
|
356
|
+
export const StyledSidebarGroupRow = styled.div<Props>\`
|
|
357
|
+
\${sidebarRowStyles};
|
|
358
|
+
\${sidebarChevron};
|
|
359
|
+
\`;
|
|
360
|
+
|
|
361
|
+
export const StyledSidebarGroupLink = styled(Link)\`
|
|
362
|
+
display: flex;
|
|
363
|
+
align-items: center;
|
|
364
|
+
gap: 8px;
|
|
365
|
+
flex: 1;
|
|
366
|
+
min-width: 0;
|
|
367
|
+
color: inherit;
|
|
368
|
+
text-decoration: none;
|
|
369
|
+
|
|
370
|
+
& svg {
|
|
371
|
+
flex-shrink: 0;
|
|
372
|
+
}
|
|
373
|
+
\`;
|
|
374
|
+
|
|
375
|
+
export const StyledSidebarGroupChevron = styled.button\`
|
|
376
|
+
display: inline-flex;
|
|
377
|
+
align-items: center;
|
|
378
|
+
justify-content: center;
|
|
379
|
+
flex-shrink: 0;
|
|
380
|
+
padding: 0;
|
|
381
|
+
border: 0;
|
|
382
|
+
background: none;
|
|
383
|
+
color: inherit;
|
|
384
|
+
cursor: pointer;
|
|
385
|
+
\`;
|
|
386
|
+
|
|
387
|
+
export const StyledSidebarGroupContent = styled.ul<Props>\`
|
|
388
|
+
list-style: none;
|
|
389
|
+
margin: 0;
|
|
390
|
+
padding: 0;
|
|
391
|
+
height: 0;
|
|
392
|
+
overflow: clip;
|
|
393
|
+
transition: all 0.3s ease;
|
|
394
|
+
|
|
395
|
+
\${({ $isOpen }) =>
|
|
396
|
+
$isOpen &&
|
|
397
|
+
css\`
|
|
398
|
+
height: auto;
|
|
399
|
+
\`}
|
|
304
400
|
\`;
|
|
305
401
|
|
|
306
402
|
export const StyleMobileBar = styled.button<Props>\`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const docsNavigationTemplate = "\"use client\";\nimport { useContext } from \"react\";\nimport { usePathname } from \"next/navigation\";\nimport Link from \"next/link\";\nimport styled, { css } from \"styled-components\";\nimport { Icon } from \"@/components/layout/Icon\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { interactiveStyles } from \"@/components/layout/SharedStyled\";\nimport { ChatContext } from \"@/components/Chat\";\n\nconst StyledNavigationWrapper = styled.div<{\n $isChatOpen?: boolean;\n}>`\n transition: all 0.3s ease;\n padding: 0 20px 100px 20px;\n ${mq(\"lg\")} {\n padding: 0 300px 80px 300px;\n ${({ $isChatOpen }) =>\n $isChatOpen &&\n css`\n padding: 0 440px 80px 300px;\n `}\n }\n`;\n\nconst StyledNavigationInner = styled.div`\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 20px;\n max-width: 640px;\n margin: auto;\n`;\n\nconst StyledNavButton = styled(Link)<{ theme: Theme }>`\n ${interactiveStyles};\n display: flex;\n flex-direction: column;\n text-decoration: none;\n padding: 20px;\n flex: 50%;\n max-width: 50%;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n color: ${({ theme }) => theme.colors.dark};\n\n &:hover {\n border-color: ${({ theme }) => theme.colors.primary};\n }\n\n &[data-direction=\"prev\"] {\n align-items: flex-start;\n }\n\n &[data-direction=\"next\"] {\n align-items: flex-end;\n margin-left: auto;\n text-align: right;\n }\n`;\n\nconst StyledNavLabel = styled.span<{ theme: Theme }>`\n color: ${({ theme }) => theme.colors.grayDark};\n display: flex;\n flex-direction: row;\n gap: 4px;\n\n & svg {\n margin: auto 0;\n }\n`;\n\nconst StyledNavTitle = styled.span<{ theme: Theme }>`\n color: ${({ theme }) => theme.colors.dark};\n font-weight: 600;\n margin: 0 0 4px 0;\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n max-width: 100%;\n`;\n\nconst StyledSpacer = styled.div`\n flex: 1;\n`;\n\ninterface Page {\n
|
|
1
|
+
export declare const docsNavigationTemplate = "\"use client\";\nimport { useContext } from \"react\";\nimport { usePathname } from \"next/navigation\";\nimport Link from \"next/link\";\nimport styled, { css } from \"styled-components\";\nimport { Icon } from \"@/components/layout/Icon\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { interactiveStyles } from \"@/components/layout/SharedStyled\";\nimport { ChatContext } from \"@/components/Chat\";\n\nconst StyledNavigationWrapper = styled.div<{\n $isChatOpen?: boolean;\n}>`\n transition: all 0.3s ease;\n padding: 0 20px 100px 20px;\n ${mq(\"lg\")} {\n padding: 0 300px 80px 300px;\n ${({ $isChatOpen }) =>\n $isChatOpen &&\n css`\n padding: 0 440px 80px 300px;\n `}\n }\n`;\n\nconst StyledNavigationInner = styled.div`\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 20px;\n max-width: 640px;\n margin: auto;\n`;\n\nconst StyledNavButton = styled(Link)<{ theme: Theme }>`\n ${interactiveStyles};\n display: flex;\n flex-direction: column;\n text-decoration: none;\n padding: 20px;\n flex: 50%;\n max-width: 50%;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n color: ${({ theme }) => theme.colors.dark};\n\n &:hover {\n border-color: ${({ theme }) => theme.colors.primary};\n }\n\n &[data-direction=\"prev\"] {\n align-items: flex-start;\n }\n\n &[data-direction=\"next\"] {\n align-items: flex-end;\n margin-left: auto;\n text-align: right;\n }\n`;\n\nconst StyledNavLabel = styled.span<{ theme: Theme }>`\n color: ${({ theme }) => theme.colors.grayDark};\n display: flex;\n flex-direction: row;\n gap: 4px;\n\n & svg {\n margin: auto 0;\n }\n`;\n\nconst StyledNavTitle = styled.span<{ theme: Theme }>`\n color: ${({ theme }) => theme.colors.dark};\n font-weight: 600;\n margin: 0 0 4px 0;\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n max-width: 100%;\n`;\n\nconst StyledSpacer = styled.div`\n flex: 1;\n`;\n\ninterface Page {\n // Optional: nested group nodes may act as headers without their own page.\n // collectPages() only pushes nodes that actually have a slug.\n slug?: string;\n title: string;\n category?: string;\n links?: Page[];\n items?: Page[];\n [key: string]: unknown;\n}\n\ninterface NavigationItem {\n category?: string;\n slug?: string;\n title?: string;\n links?: Page[];\n items?: Page[];\n [key: string]: unknown;\n}\n\ninterface DocsNavigationProps {\n result: NavigationItem[];\n}\n\nfunction DocsNavigation({ result }: DocsNavigationProps) {\n const { isOpen } = useContext(ChatContext);\n const pathname = usePathname();\n // Walk categories and any nested link groups depth-first so prev/next spans\n // every real page (a node with a slug), in reading order.\n const collectPages = (nodes: unknown): Page[] => {\n if (!Array.isArray(nodes)) return [];\n const pages: Page[] = [];\n for (const node of nodes as Page[]) {\n if (node && node.slug !== undefined) {\n pages.push(node);\n }\n const children = node && ((node.links ?? node.items) as unknown);\n if (Array.isArray(children)) {\n pages.push(...collectPages(children));\n }\n }\n return pages;\n };\n const allPages: Page[] = result.flatMap((item) =>\n collectPages(\n item.links ??\n item.items ??\n (item.slug !== undefined ? [item as Page] : []),\n ),\n );\n const currentSlug = pathname.replace(/^\\//, \"\").replace(/\\/$/, \"\");\n const currentIndex = allPages.findIndex((page) => page.slug === currentSlug);\n const prevPage = currentIndex > 0 ? allPages[currentIndex - 1] : null;\n const nextPage =\n currentIndex < allPages.length - 1 ? allPages[currentIndex + 1] : null;\n if (currentIndex === -1 || allPages.length === 0) {\n return null;\n }\n if (!prevPage && !nextPage) {\n return null;\n }\n return (\n <StyledNavigationWrapper $isChatOpen={isOpen}>\n <StyledNavigationInner>\n {prevPage ? (\n <StyledNavButton href={`/${prevPage.slug}`} data-direction=\"prev\">\n <StyledNavTitle>{prevPage.title}</StyledNavTitle>\n <StyledNavLabel>\n <Icon name=\"arrow-left\" size={16} /> Previous\n </StyledNavLabel>\n </StyledNavButton>\n ) : (\n <StyledSpacer />\n )}\n {nextPage && (\n <StyledNavButton href={`/${nextPage.slug}`} data-direction=\"next\">\n <StyledNavTitle>{nextPage.title}</StyledNavTitle>\n <StyledNavLabel>\n Next <Icon name=\"arrow-right\" size={16} />\n </StyledNavLabel>\n </StyledNavButton>\n )}\n </StyledNavigationInner>\n </StyledNavigationWrapper>\n );\n}\n\nexport { DocsNavigation };\n";
|
|
@@ -86,9 +86,13 @@ const StyledSpacer = styled.div\`
|
|
|
86
86
|
\`;
|
|
87
87
|
|
|
88
88
|
interface Page {
|
|
89
|
-
|
|
89
|
+
// Optional: nested group nodes may act as headers without their own page.
|
|
90
|
+
// collectPages() only pushes nodes that actually have a slug.
|
|
91
|
+
slug?: string;
|
|
90
92
|
title: string;
|
|
91
93
|
category?: string;
|
|
94
|
+
links?: Page[];
|
|
95
|
+
items?: Page[];
|
|
92
96
|
[key: string]: unknown;
|
|
93
97
|
}
|
|
94
98
|
|
|
@@ -108,18 +112,29 @@ interface DocsNavigationProps {
|
|
|
108
112
|
function DocsNavigation({ result }: DocsNavigationProps) {
|
|
109
113
|
const { isOpen } = useContext(ChatContext);
|
|
110
114
|
const pathname = usePathname();
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
115
|
+
// Walk categories and any nested link groups depth-first so prev/next spans
|
|
116
|
+
// every real page (a node with a slug), in reading order.
|
|
117
|
+
const collectPages = (nodes: unknown): Page[] => {
|
|
118
|
+
if (!Array.isArray(nodes)) return [];
|
|
119
|
+
const pages: Page[] = [];
|
|
120
|
+
for (const node of nodes as Page[]) {
|
|
121
|
+
if (node && node.slug !== undefined) {
|
|
122
|
+
pages.push(node);
|
|
123
|
+
}
|
|
124
|
+
const children = node && ((node.links ?? node.items) as unknown);
|
|
125
|
+
if (Array.isArray(children)) {
|
|
126
|
+
pages.push(...collectPages(children));
|
|
127
|
+
}
|
|
114
128
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
129
|
+
return pages;
|
|
130
|
+
};
|
|
131
|
+
const allPages: Page[] = result.flatMap((item) =>
|
|
132
|
+
collectPages(
|
|
133
|
+
item.links ??
|
|
134
|
+
item.items ??
|
|
135
|
+
(item.slug !== undefined ? [item as Page] : []),
|
|
136
|
+
),
|
|
137
|
+
);
|
|
123
138
|
const currentSlug = pathname.replace(/^\\//, "").replace(/\\/$/, "");
|
|
124
139
|
const currentIndex = allPages.findIndex((page) => page.slug === currentSlug);
|
|
125
140
|
const prevPage = currentIndex > 0 ? allPages[currentIndex - 1] : null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const slugTemplate = "// Shared heading/anchor id helpers.\n//\n// `slugify` turns heading text into a URL-safe base slug. `createSlugger`\n// wraps it with occurrence-based de-duplication so repeated heading text\n// yields stable, unique ids (\"setup\", \"setup-1\", \"setup-2\", ...) matching\n// the scheme used by GitHub and rehype-slug.\n//\n// The index sidebar (components/Docs.tsx) and the rendered headings\n// (components/MDXComponents.tsx, components/layout/Update.tsx) must walk the\n// document in the same order with a shared slugger so every sidebar link\n// resolves to exactly one heading.\n\nexport function slugify(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, \"\")\n .replace(/\\s+/g, \"-\")\n .trim();\n}\n\nexport type Slugger = (text: string) => string;\n\nexport function createSlugger(): Slugger {\n const seen = new Map<string, number>();\n return (text: string) => {\n const base = slugify(text);\n const count = seen.get(base) ?? 0;\n seen.set(base, count + 1);\n return count === 0 ? base : `${base}-${count}`;\n };\n}\n";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export const slugTemplate = `// Shared heading/anchor id helpers.
|
|
2
|
+
//
|
|
3
|
+
// \`slugify\` turns heading text into a URL-safe base slug. \`createSlugger\`
|
|
4
|
+
// wraps it with occurrence-based de-duplication so repeated heading text
|
|
5
|
+
// yields stable, unique ids ("setup", "setup-1", "setup-2", ...) matching
|
|
6
|
+
// the scheme used by GitHub and rehype-slug.
|
|
7
|
+
//
|
|
8
|
+
// The index sidebar (components/Docs.tsx) and the rendered headings
|
|
9
|
+
// (components/MDXComponents.tsx, components/layout/Update.tsx) must walk the
|
|
10
|
+
// document in the same order with a shared slugger so every sidebar link
|
|
11
|
+
// resolves to exactly one heading.
|
|
12
|
+
|
|
13
|
+
export function slugify(text: string): string {
|
|
14
|
+
return text
|
|
15
|
+
.toLowerCase()
|
|
16
|
+
.replace(/[^\\w\\s-]/g, "")
|
|
17
|
+
.replace(/\\s+/g, "-")
|
|
18
|
+
.trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type Slugger = (text: string) => string;
|
|
22
|
+
|
|
23
|
+
export function createSlugger(): Slugger {
|
|
24
|
+
const seen = new Map<string, number>();
|
|
25
|
+
return (text: string) => {
|
|
26
|
+
const base = slugify(text);
|
|
27
|
+
const count = seen.get(base) ?? 0;
|
|
28
|
+
seen.set(base, count + 1);
|
|
29
|
+
return count === 0 ? base : \`\${base}-\${count}\`;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const tabsTemplate = "\"use client\";\nimport { Theme } from \"@/app/theme\";\nimport { styledText } from \"cherry-styled-components\";\nimport React, { useState, ReactNode } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { thinScrollbar } from \"@/components/layout/SharedStyled\";\ninterface TabContentProps {\n title: string;\n children: ReactNode;\n}\ninterface TabsProps {\n children: React.ReactElement<TabContentProps>[];\n}\nconst TabsContainer = styled.div`\n width: 100%;\n margin: 0 auto;\n`;\nconst TabsList = styled.div<{ theme: Theme }>`\n display: flex;\n overflow: hidden;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n background-color: ${({ theme }) => theme.colors.light};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n overflow-x: auto;\n ${thinScrollbar};\n`;\nconst TabButton = styled.button<{ theme: Theme; $isActive?: boolean }>`\n flex: 1;\n padding: 12px 20px;\n border: none;\n background: ${({ theme }) => theme.colors.light};\n cursor: pointer;\n transition: all 0.2s ease;\n border-bottom: 3px solid transparent;\n min-width: fit-content;\n ${({ theme }) => styledText(theme)};\n color: ${({ theme }) => theme.colors.dark};\n font-weight: 600;\n ${({ theme, $isActive }) =>\n $isActive &&\n css`\n color: ${theme.colors.primary};\n border-bottom: 3px solid ${theme.colors.primary};\n `}\n position: relative;\n &:hover {\n ${({ theme, $isActive }) =>\n !$isActive &&\n css`\n color: ${theme.colors.primary};\n background-color: color-mix(\n in srgb,\n ${theme.colors.primaryLight} 10%,\n transparent\n );\n `}\n }\n &:focus {\n outline: none;\n }\n &:not(:last-child) {\n border-right: 1px solid ${({ theme }) => theme.colors.grayLight};\n }\n`;\nconst TabPanel = styled.div<{ theme: Theme }>`\n background-color: ${({ theme }) => theme.colors.light};\n padding: 20px;\n border-radius: 0 0 ${({ theme }) => theme.spacing.radius.lg}\n ${({ theme }) => theme.spacing.radius.lg};\n color: ${({ theme }) => theme.colors.grayDark};\n ${({ theme }) => styledText(theme)}\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n border-top: none;\n display: flex;\n flex-direction: column;\n gap: 20px;\n flex-wrap: wrap;\n flex: 1;\n`;\nconst TabContent: React.FC<TabContentProps> = ({ children }) => {\n return <>{children}</>;\n};\nconst Tabs: React.FC<TabsProps> = ({ children }) => {\n const [activeTab, setActiveTab] = useState(0);\n const tabs = React.Children.toArray(children).filter(\n (child): child is React.ReactElement<TabContentProps> =>\n Boolean(\n React.isValidElement(child) &&\n child.props &&\n typeof child.props === \"object\" &&\n \"title\" in child.props &&\n typeof child.props.title === \"string\" &&\n child.props.title.trim() !== \"\",\n ),\n );\n return (\n <TabsContainer>\n <TabsList>\n {tabs.map((tab, index) => (\n <TabButton\n key={index}\n $isActive={activeTab === index}\n onClick={() => setActiveTab(index)}\n type=\"button\"\n >\n {tab.props.title}\n </TabButton>\n ))}\n </TabsList>\n <TabPanel>{tabs[activeTab]?.props.children}</TabPanel>\n </TabsContainer>\n );\n};\nexport { Tabs, TabContent };\n";
|
|
1
|
+
export declare const tabsTemplate = "\"use client\";\nimport { Theme } from \"@/app/theme\";\nimport { styledText } from \"cherry-styled-components\";\nimport React, { useState, ReactNode } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { thinScrollbar } from \"@/components/layout/SharedStyled\";\nimport { Icon } from \"@/components/layout/Icon\";\ninterface TabContentProps {\n title: string;\n icon?: string;\n children: ReactNode;\n}\ninterface TabsProps {\n children: React.ReactElement<TabContentProps>[];\n}\nconst TabsContainer = styled.div`\n width: 100%;\n margin: 0 auto;\n`;\nconst TabsList = styled.div<{ theme: Theme }>`\n display: flex;\n overflow: hidden;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n background-color: ${({ theme }) => theme.colors.light};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n overflow-x: auto;\n ${thinScrollbar};\n`;\nconst TabButton = styled.button<{ theme: Theme; $isActive?: boolean }>`\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 12px 20px;\n border: none;\n background: ${({ theme }) => theme.colors.light};\n cursor: pointer;\n transition: all 0.2s ease;\n border-bottom: 3px solid transparent;\n min-width: fit-content;\n ${({ theme }) => styledText(theme)};\n color: ${({ theme }) => theme.colors.dark};\n font-weight: 600;\n\n & svg {\n flex-shrink: 0;\n }\n ${({ theme, $isActive }) =>\n $isActive &&\n css`\n color: ${theme.colors.primary};\n border-bottom: 3px solid ${theme.colors.primary};\n `}\n position: relative;\n &:hover {\n ${({ theme, $isActive }) =>\n !$isActive &&\n css`\n color: ${theme.colors.primary};\n background-color: color-mix(\n in srgb,\n ${theme.colors.primaryLight} 10%,\n transparent\n );\n `}\n }\n &:focus {\n outline: none;\n }\n &:not(:last-child) {\n border-right: 1px solid ${({ theme }) => theme.colors.grayLight};\n }\n`;\nconst TabPanel = styled.div<{ theme: Theme }>`\n background-color: ${({ theme }) => theme.colors.light};\n padding: 20px;\n border-radius: 0 0 ${({ theme }) => theme.spacing.radius.lg}\n ${({ theme }) => theme.spacing.radius.lg};\n color: ${({ theme }) => theme.colors.grayDark};\n ${({ theme }) => styledText(theme)}\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n border-top: none;\n display: flex;\n flex-direction: column;\n gap: 20px;\n flex-wrap: wrap;\n flex: 1;\n`;\nconst TabContent: React.FC<TabContentProps> = ({ children }) => {\n return <>{children}</>;\n};\nconst Tabs: React.FC<TabsProps> = ({ children }) => {\n const [activeTab, setActiveTab] = useState(0);\n const tabs = React.Children.toArray(children).filter(\n (child): child is React.ReactElement<TabContentProps> =>\n Boolean(\n React.isValidElement(child) &&\n child.props &&\n typeof child.props === \"object\" &&\n \"title\" in child.props &&\n typeof child.props.title === \"string\" &&\n child.props.title.trim() !== \"\",\n ),\n );\n return (\n <TabsContainer>\n <TabsList>\n {tabs.map((tab, index) => (\n <TabButton\n key={index}\n $isActive={activeTab === index}\n onClick={() => setActiveTab(index)}\n type=\"button\"\n >\n {tab.props.icon && <Icon name={tab.props.icon} size={18} />}\n {tab.props.title}\n </TabButton>\n ))}\n </TabsList>\n <TabPanel>{tabs[activeTab]?.props.children}</TabPanel>\n </TabsContainer>\n );\n};\nexport { Tabs, TabContent };\n";
|
|
@@ -4,8 +4,10 @@ import { styledText } from "cherry-styled-components";
|
|
|
4
4
|
import React, { useState, ReactNode } from "react";
|
|
5
5
|
import styled, { css } from "styled-components";
|
|
6
6
|
import { thinScrollbar } from "@/components/layout/SharedStyled";
|
|
7
|
+
import { Icon } from "@/components/layout/Icon";
|
|
7
8
|
interface TabContentProps {
|
|
8
9
|
title: string;
|
|
10
|
+
icon?: string;
|
|
9
11
|
children: ReactNode;
|
|
10
12
|
}
|
|
11
13
|
interface TabsProps {
|
|
@@ -28,6 +30,10 @@ const TabsList = styled.div<{ theme: Theme }>\`
|
|
|
28
30
|
\`;
|
|
29
31
|
const TabButton = styled.button<{ theme: Theme; $isActive?: boolean }>\`
|
|
30
32
|
flex: 1;
|
|
33
|
+
display: flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
justify-content: center;
|
|
36
|
+
gap: 8px;
|
|
31
37
|
padding: 12px 20px;
|
|
32
38
|
border: none;
|
|
33
39
|
background: \${({ theme }) => theme.colors.light};
|
|
@@ -38,6 +44,10 @@ const TabButton = styled.button<{ theme: Theme; $isActive?: boolean }>\`
|
|
|
38
44
|
\${({ theme }) => styledText(theme)};
|
|
39
45
|
color: \${({ theme }) => theme.colors.dark};
|
|
40
46
|
font-weight: 600;
|
|
47
|
+
|
|
48
|
+
& svg {
|
|
49
|
+
flex-shrink: 0;
|
|
50
|
+
}
|
|
41
51
|
\${({ theme, $isActive }) =>
|
|
42
52
|
$isActive &&
|
|
43
53
|
css\`
|
|
@@ -105,6 +115,7 @@ const Tabs: React.FC<TabsProps> = ({ children }) => {
|
|
|
105
115
|
onClick={() => setActiveTab(index)}
|
|
106
116
|
type="button"
|
|
107
117
|
>
|
|
118
|
+
{tab.props.icon && <Icon name={tab.props.icon} size={18} />}
|
|
108
119
|
{tab.props.title}
|
|
109
120
|
</TabButton>
|
|
110
121
|
))}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const updateTemplate = "\"use client\";\nimport styled from \"styled-components\";\nimport { styledSmall } from \"cherry-styled-components\";\nimport { mq, Theme } from \"@/app/theme\";\n\nconst StyledUpdate = styled.div<{ theme: Theme; $columns?: number }>`\n position: relative;\n display: flex;\n gap: 20px;\n flex-direction: column;\n\n ${mq(\"lg\")} {\n margin: 20px 0;\n flex-direction: row;\n }\n`;\n\nconst StyledUpdateLabel = styled.
|
|
1
|
+
export declare const updateTemplate = "\"use client\";\nimport styled from \"styled-components\";\nimport { styledSmall } from \"cherry-styled-components\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { slugify } from \"@/components/layout/Slug\";\n\nconst StyledUpdate = styled.div<{ theme: Theme; $columns?: number }>`\n position: relative;\n display: flex;\n gap: 20px;\n flex-direction: column;\n\n ${mq(\"lg\")} {\n margin: 20px 0;\n flex-direction: row;\n }\n`;\n\nconst StyledUpdateLabel = styled.a<{ theme: Theme }>`\n display: inline-block;\n width: fit-content;\n background: ${({ theme }) =>\n `color-mix(in srgb, ${theme.colors.primaryLight} 20%, transparent)`};\n color: ${({ theme }) => theme.colors.primary};\n padding: 2px 4px;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n font-weight: 600;\n text-decoration: none;\n ${({ theme }) => styledSmall(theme)};\n\n &:hover {\n background: ${({ theme }) =>\n `color-mix(in srgb, ${theme.colors.primaryLight} 35%, transparent)`};\n }\n`;\n\nconst StyledUpdateDescription = styled.div<{ theme: Theme }>`\n ${({ theme }) => styledSmall(theme)};\n color: ${({ theme }) => theme.colors.gray};\n`;\n\nconst StyledUpdateSidebar = styled.div`\n display: flex;\n flex-direction: column;\n gap: 10px;\n\n ${mq(\"lg\")} {\n min-width: 160px;\n }\n`;\n\nconst StyledUpdateChildren = styled.div`\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 20px;\n`;\n\ninterface UpdateProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n label: string;\n description: string;\n}\n\nfunction Update({ children, label, description, id }: UpdateProps) {\n // MDXComponents injects a de-duplicated id from the shared slugger so the\n // index sidebar anchor resolves here; fall back to a plain slug when used\n // outside the MDX pipeline. The label doubles as an anchor so readers can\n // right-click to copy the deep link (native #anchor jump is offset by the\n // global scroll-padding-top).\n const anchor = id ?? slugify(label);\n return (\n <StyledUpdate id={anchor}>\n <StyledUpdateSidebar>\n <div>\n <StyledUpdateLabel href={`#${anchor}`}>{label}</StyledUpdateLabel>\n </div>\n <StyledUpdateDescription>{description}</StyledUpdateDescription>\n </StyledUpdateSidebar>\n <StyledUpdateChildren>{children}</StyledUpdateChildren>\n </StyledUpdate>\n );\n}\n\nexport { Update };\n";
|
|
@@ -2,6 +2,7 @@ export const updateTemplate = `"use client";
|
|
|
2
2
|
import styled from "styled-components";
|
|
3
3
|
import { styledSmall } from "cherry-styled-components";
|
|
4
4
|
import { mq, Theme } from "@/app/theme";
|
|
5
|
+
import { slugify } from "@/components/layout/Slug";
|
|
5
6
|
|
|
6
7
|
const StyledUpdate = styled.div<{ theme: Theme; $columns?: number }>\`
|
|
7
8
|
position: relative;
|
|
@@ -15,14 +16,22 @@ const StyledUpdate = styled.div<{ theme: Theme; $columns?: number }>\`
|
|
|
15
16
|
}
|
|
16
17
|
\`;
|
|
17
18
|
|
|
18
|
-
const StyledUpdateLabel = styled.
|
|
19
|
+
const StyledUpdateLabel = styled.a<{ theme: Theme }>\`
|
|
20
|
+
display: inline-block;
|
|
21
|
+
width: fit-content;
|
|
19
22
|
background: \${({ theme }) =>
|
|
20
23
|
\`color-mix(in srgb, \${theme.colors.primaryLight} 20%, transparent)\`};
|
|
21
24
|
color: \${({ theme }) => theme.colors.primary};
|
|
22
25
|
padding: 2px 4px;
|
|
23
26
|
border-radius: \${({ theme }) => theme.spacing.radius.xs};
|
|
24
27
|
font-weight: 600;
|
|
28
|
+
text-decoration: none;
|
|
25
29
|
\${({ theme }) => styledSmall(theme)};
|
|
30
|
+
|
|
31
|
+
&:hover {
|
|
32
|
+
background: \${({ theme }) =>
|
|
33
|
+
\`color-mix(in srgb, \${theme.colors.primaryLight} 35%, transparent)\`};
|
|
34
|
+
}
|
|
26
35
|
\`;
|
|
27
36
|
|
|
28
37
|
const StyledUpdateDescription = styled.div<{ theme: Theme }>\`
|
|
@@ -53,22 +62,18 @@ interface UpdateProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
53
62
|
description: string;
|
|
54
63
|
}
|
|
55
64
|
|
|
56
|
-
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
.trim();
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function Update({ children, label, description }: UpdateProps) {
|
|
65
|
+
function Update({ children, label, description, id }: UpdateProps) {
|
|
66
|
+
// MDXComponents injects a de-duplicated id from the shared slugger so the
|
|
67
|
+
// index sidebar anchor resolves here; fall back to a plain slug when used
|
|
68
|
+
// outside the MDX pipeline. The label doubles as an anchor so readers can
|
|
69
|
+
// right-click to copy the deep link (native #anchor jump is offset by the
|
|
70
|
+
// global scroll-padding-top).
|
|
71
|
+
const anchor = id ?? slugify(label);
|
|
67
72
|
return (
|
|
68
|
-
<StyledUpdate id={
|
|
73
|
+
<StyledUpdate id={anchor}>
|
|
69
74
|
<StyledUpdateSidebar>
|
|
70
75
|
<div>
|
|
71
|
-
<StyledUpdateLabel>{label}</StyledUpdateLabel>
|
|
76
|
+
<StyledUpdateLabel href={\`#\${anchor}\`}>{label}</StyledUpdateLabel>
|
|
72
77
|
</div>
|
|
73
78
|
<StyledUpdateDescription>{description}</StyledUpdateDescription>
|
|
74
79
|
</StyledUpdateSidebar>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const componentsMdxTemplate = "---\ntitle: \"Components\"\ndescription: \"Explore the full library of built-in components available in your documentation pages.\"\ndate: \"2026-02-19\"\ncategory: \"Components\"\ncategoryOrder: 1\norder: 0\n---\n\n# Components\n\nDoccupine includes a rich set of built-in components you can use directly in your MDX files - no imports needed. Browse the full library below.\n\n<Columns cols={3}>\n <Card title=\"Headers and Text\" icon=\"heading\" href=\"/headers-and-text\">\n Headings, paragraphs, bold, italic, links, and other text formatting.\n </Card>\n <Card title=\"Lists and Tables\" icon=\"list\" href=\"/lists-and-tables\">\n Ordered lists, unordered lists, and data tables.\n </Card>\n <Card title=\"Code\" icon=\"code\" href=\"/code\">\n Inline code, fenced code blocks, and syntax highlighting.\n </Card>\n <Card title=\"Accordion\" icon=\"chevrons-down\" href=\"/accordion\">\n Collapsible sections for organizing lengthy content.\n </Card>\n <Card title=\"Tabs\" icon=\"panel-top\" href=\"/tabs\">\n Tabbed interfaces for grouping related content.\n </Card>\n <Card title=\"Cards\" icon=\"square\" href=\"/cards\">\n Visual containers for content, icons, and links.\n </Card>\n <Card title=\"Buttons\" icon=\"mouse-pointer-click\" href=\"/buttons\">\n Action components with variants, sizes, and icons.\n </Card>\n <Card title=\"Callouts\" icon=\"megaphone\" href=\"/callouts\">\n Highlighted blocks for tips, warnings, and important notes.\n </Card>\n <Card title=\"Images and Embeds\" icon=\"image\" href=\"/images-and-embeds\">\n Images, videos, and embedded content.\n </Card>\n <Card title=\"Icons\" icon=\"smile\" href=\"/icons\">\n Lucide icons you can use anywhere in your docs.\n </Card>\n <Card title=\"Fields\" icon=\"text-cursor-input\" href=\"/fields\">\n Property and parameter documentation blocks.\n </Card>\n <Card title=\"Update\" icon=\"bell\" href=\"/update\">\n Changelog and update announcement blocks.\n </Card>\n <Card title=\"Columns\" icon=\"columns-3\" href=\"/columns\">\n Grid layouts for arranging cards and content side by side.\n </Card>\n <Card title=\"Steps\" icon=\"footprints\" href=\"/steps\">\n Numbered step-by-step guides and walkthroughs.\n </Card>\n <Card title=\"Color Swatches\" icon=\"palette\" href=\"/color-swatches\">\n Visual color palette swatches to document your theme colors.\n </Card>\n</Columns>";
|
|
1
|
+
export declare const componentsMdxTemplate = "---\ntitle: \"Components\"\ndescription: \"Explore the full library of built-in components available in your documentation pages.\"\ndate: \"2026-02-19\"\ncategory: \"Components\"\ncategoryOrder: 1\ncategoryIcon: \"blocks\"\norder: 0\n---\n\n# Components\n\nDoccupine includes a rich set of built-in components you can use directly in your MDX files - no imports needed. Browse the full library below.\n\n<Columns cols={3}>\n <Card title=\"Headers and Text\" icon=\"heading\" href=\"/headers-and-text\">\n Headings, paragraphs, bold, italic, links, and other text formatting.\n </Card>\n <Card title=\"Lists and Tables\" icon=\"list\" href=\"/lists-and-tables\">\n Ordered lists, unordered lists, and data tables.\n </Card>\n <Card title=\"Code\" icon=\"code\" href=\"/code\">\n Inline code, fenced code blocks, and syntax highlighting.\n </Card>\n <Card title=\"Accordion\" icon=\"chevrons-down\" href=\"/accordion\">\n Collapsible sections for organizing lengthy content.\n </Card>\n <Card title=\"Tabs\" icon=\"panel-top\" href=\"/tabs\">\n Tabbed interfaces for grouping related content.\n </Card>\n <Card title=\"Cards\" icon=\"square\" href=\"/cards\">\n Visual containers for content, icons, and links.\n </Card>\n <Card title=\"Buttons\" icon=\"mouse-pointer-click\" href=\"/buttons\">\n Action components with variants, sizes, and icons.\n </Card>\n <Card title=\"Callouts\" icon=\"megaphone\" href=\"/callouts\">\n Highlighted blocks for tips, warnings, and important notes.\n </Card>\n <Card title=\"Images and Embeds\" icon=\"image\" href=\"/images-and-embeds\">\n Images, videos, and embedded content.\n </Card>\n <Card title=\"Icons\" icon=\"smile\" href=\"/icons\">\n Lucide icons you can use anywhere in your docs.\n </Card>\n <Card title=\"Fields\" icon=\"text-cursor-input\" href=\"/fields\">\n Property and parameter documentation blocks.\n </Card>\n <Card title=\"Update\" icon=\"bell\" href=\"/update\">\n Changelog and update announcement blocks.\n </Card>\n <Card title=\"Columns\" icon=\"columns-3\" href=\"/columns\">\n Grid layouts for arranging cards and content side by side.\n </Card>\n <Card title=\"Steps\" icon=\"footprints\" href=\"/steps\">\n Numbered step-by-step guides and walkthroughs.\n </Card>\n <Card title=\"Color Swatches\" icon=\"palette\" href=\"/color-swatches\">\n Visual color palette swatches to document your theme colors.\n </Card>\n</Columns>";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const globalsMdxTemplate = "---\ntitle: \"Globals\"\ndescription: \"Configure global settings for your documentation.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 1\n---\n\n# Global Configuration\n\nUse a `config.json` file to define project\u2011wide metadata for your documentation site. These values are applied to every generated page unless a page overrides them in its own frontmatter.\n\n## config.json\n\nPlace a `config.json` at your project root (the same folder where you execute `npx doccupine`) to define global metadata for your documentation site.\n\n```json\n{\n \"name\": \"Doccupine\",\n \"description\": \"Doccupine is a free and open-source documentation platform. Write MDX, get a production-ready site with AI chat, built-in components, and an MCP server - in one command.\",\n \"icon\": \"https://docs.doccupine.com/favicon.ico\",\n \"image\": \"https://docs.doccupine.com/preview.png\",\n \"url\": \"https://docs.example.com\"\n}\n```\n\n## Fields\n\nAll fields are optional. Doccupine uses sensible defaults when a field is not set.\n\n- **name**: The primary name of your documentation website. Displayed in the site title and used in various UI elements.\n- **description**: A concise summary of your project, used in site metadata (e.g., HTML meta description) and social previews when not overridden.\n- **icon**: The favicon for your site. You can provide a full URL or a relative path to an asset in your project.\n- **image**: The Open Graph image used when links to your docs are shared on social platforms. Accepts a full URL or a relative path.\n- **url**: The public URL of your deployed site. Used as the base URL for `sitemap.xml` and `robots.txt`. When omitted, no sitemap is generated. Can be overridden at deploy time with the `NEXT_PUBLIC_SITE_URL` environment variable.\n\n## Per-page overrides\n\nAny page can override global values by defining the matching key in its frontmatter. When present, the page's value takes precedence over `config.json` for that page only.\n\n| Frontmatter field | Overrides | Effect |\n| ----------------- | ------------- | ----------------------------------------------------------- |\n| **title** | - | Page title in metadata and Open Graph |\n| **description** | `description` | Meta description and Open Graph description |\n| **name** | `name` | Site name shown in the title suffix (e.g. \"Page - My Docs\") |\n| **icon** | `icon` | Favicon for this page |\n| **image** | `image` | Open Graph preview image |\n| **section** | - | Assigns the page to a [section](/sections) |\n| **sectionOrder** | - | Controls section position in the tab bar |\n| **sectionLabel** | - | Renames the default \"Docs\" tab (use on `index.mdx`) |\n\n<Callout type=\"note\">\n If a key is not specified in a page's frontmatter, Doccupine falls back to the corresponding value in `config.json`.\n</Callout>\n\nExample frontmatter in an `.mdx` file:\n\n```text\n---\ntitle: \"My Feature\"\ndescription: \"A focused description just for this page.\"\nname: \"My Product Docs\"\nicon: \"/custom-favicon.ico\"\nimage: \"/custom-preview.png\"\ndate: \"2026-02-19\"\ncategory: \"Guides\"\n---\n```";
|
|
1
|
+
export declare const globalsMdxTemplate = "---\ntitle: \"Globals\"\ndescription: \"Configure global settings for your documentation.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\ncategoryIcon: \"settings\"\norder: 1\n---\n\n# Global Configuration\n\nUse a `config.json` file to define project\u2011wide metadata for your documentation site. These values are applied to every generated page unless a page overrides them in its own frontmatter.\n\n## config.json\n\nPlace a `config.json` at your project root (the same folder where you execute `npx doccupine`) to define global metadata for your documentation site.\n\n```json\n{\n \"name\": \"Doccupine\",\n \"description\": \"Doccupine is a free and open-source documentation platform. Write MDX, get a production-ready site with AI chat, built-in components, and an MCP server - in one command.\",\n \"icon\": \"https://docs.doccupine.com/favicon.ico\",\n \"image\": \"https://docs.doccupine.com/preview.png\",\n \"url\": \"https://docs.example.com\"\n}\n```\n\n## Fields\n\nAll fields are optional. Doccupine uses sensible defaults when a field is not set.\n\n- **name**: The primary name of your documentation website. Displayed in the site title and used in various UI elements.\n- **description**: A concise summary of your project, used in site metadata (e.g., HTML meta description) and social previews when not overridden.\n- **icon**: The favicon for your site. You can provide a full URL or a relative path to an asset in your project.\n- **image**: The Open Graph image used when links to your docs are shared on social platforms. Accepts a full URL or a relative path.\n- **url**: The public URL of your deployed site. Used as the base URL for `sitemap.xml` and `robots.txt`. When omitted, no sitemap is generated. Can be overridden at deploy time with the `NEXT_PUBLIC_SITE_URL` environment variable.\n\n## Per-page overrides\n\nAny page can override global values by defining the matching key in its frontmatter. When present, the page's value takes precedence over `config.json` for that page only.\n\n| Frontmatter field | Overrides | Effect |\n| ----------------- | ------------- | ----------------------------------------------------------- |\n| **title** | - | Page title in metadata and Open Graph |\n| **description** | `description` | Meta description and Open Graph description |\n| **name** | `name` | Site name shown in the title suffix (e.g. \"Page - My Docs\") |\n| **icon** | `icon` | Favicon for this page |\n| **image** | `image` | Open Graph preview image |\n| **section** | - | Assigns the page to a [section](/sections) |\n| **sectionOrder** | - | Controls section position in the tab bar |\n| **sectionLabel** | - | Renames the default \"Docs\" tab (use on `index.mdx`) |\n\n<Callout type=\"note\">\n If a key is not specified in a page's frontmatter, Doccupine falls back to the corresponding value in `config.json`.\n</Callout>\n\nExample frontmatter in an `.mdx` file:\n\n```text\n---\ntitle: \"My Feature\"\ndescription: \"A focused description just for this page.\"\nname: \"My Product Docs\"\nicon: \"/custom-favicon.ico\"\nimage: \"/custom-preview.png\"\ndate: \"2026-02-19\"\ncategory: \"Guides\"\n---\n```";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const indexMdxTemplate = "---\ntitle: \"Introduction\"\ndescription: \"Doccupine is a free and open-source documentation platform. Write MDX, get a production-ready site with AI chat, built-in components, and an MCP server - in one command.\"\ndate: \"2026-02-19\"\ncategory: \"Getting Started\"\ncategoryOrder: 0\norder: 0\n---\n\n# Welcome to Doccupine\n\nDoccupine turns a folder of MDX files into a beautiful, production-ready documentation website. Write in standard Markdown, use any of the built-in components, and get a fully themed site with AI-powered search and automatic navigation - all from one command.\n\n```bash\nnpx doccupine\n```\n\nThat's it. Doccupine prompts you for a source directory, generates your site, and starts a dev server at `http://localhost:3000`. Every edit rebuilds instantly.\n\n<Callout type=\"info\">\n Want a managed experience instead? The [Doccupine Platform](/platform) gives you a browser-based editor, one-click publishing, custom domains, and team collaboration - no local setup needed.\n</Callout>\n\n## How it works\n\nYou write `.mdx` files using Markdown and built-in components like this one. Doccupine watches your files, generates pages, builds the sidebar from your frontmatter, and handles theming, AI chat, and deployment config for you.\n\nEverything on this page - the callout above, the cards below, the code block - is a live example of what your docs can look like out of the box.\n\n<Columns cols={2}>\n <Card title=\"Start writing\" icon=\"pencil\" href=\"/components\">\n Rich docs from day one. 14+ components work out of the box - no imports needed.\n </Card>\n <Card title=\"Ship it\" icon=\"rocket\" href=\"/deployment-and-hosting\">\n Deploy to the Doccupine Platform or self-host on any platform that supports Next.js.\n </Card>\n</Columns>\n\n## Configure your site\n\nDoccupine works with no configuration, but when you're ready to customize, everything is controlled through simple JSON files in your project root.\n\n- [Global settings](/globals) - Site name, description, favicon, and preview image via `config.json`\n- [Navigation](/navigation) - Override the auto-generated sidebar with `navigation.json`\n- [Theme](/theme) - Colors, logos, and dark mode palette via `theme.json`\n- [Fonts](/fonts) - Google Fonts or local font files via `fonts.json`\n- [AI Assistant](/ai-assistant) - Enable the built-in chat with your own API key\n- [MCP Server](/model-context-protocol) - Let AI tools search your docs through `/api/mcp`";
|
|
1
|
+
export declare const indexMdxTemplate = "---\ntitle: \"Introduction\"\ndescription: \"Doccupine is a free and open-source documentation platform. Write MDX, get a production-ready site with AI chat, built-in components, and an MCP server - in one command.\"\ndate: \"2026-02-19\"\ncategory: \"Getting Started\"\ncategoryOrder: 0\ncategoryIcon: \"rocket\"\norder: 0\n---\n\n# Welcome to Doccupine\n\nDoccupine turns a folder of MDX files into a beautiful, production-ready documentation website. Write in standard Markdown, use any of the built-in components, and get a fully themed site with AI-powered search and automatic navigation - all from one command.\n\n```bash\nnpx doccupine\n```\n\nThat's it. Doccupine prompts you for a source directory, generates your site, and starts a dev server at `http://localhost:3000`. Every edit rebuilds instantly.\n\n<Callout type=\"info\">\n Want a managed experience instead? The [Doccupine Platform](/platform) gives you a browser-based editor, one-click publishing, custom domains, and team collaboration - no local setup needed.\n</Callout>\n\n## How it works\n\nYou write `.mdx` files using Markdown and built-in components like this one. Doccupine watches your files, generates pages, builds the sidebar from your frontmatter, and handles theming, AI chat, and deployment config for you.\n\nEverything on this page - the callout above, the cards below, the code block - is a live example of what your docs can look like out of the box.\n\n<Columns cols={2}>\n <Card title=\"Start writing\" icon=\"pencil\" href=\"/components\">\n Rich docs from day one. 14+ components work out of the box - no imports needed.\n </Card>\n <Card title=\"Ship it\" icon=\"rocket\" href=\"/deployment-and-hosting\">\n Deploy to the Doccupine Platform or self-host on any platform that supports Next.js.\n </Card>\n</Columns>\n\n## Configure your site\n\nDoccupine works with no configuration, but when you're ready to customize, everything is controlled through simple JSON files in your project root.\n\n- [Global settings](/globals) - Site name, description, favicon, and preview image via `config.json`\n- [Navigation](/navigation) - Override the auto-generated sidebar with `navigation.json`\n- [Theme](/theme) - Colors, logos, and dark mode palette via `theme.json`\n- [Fonts](/fonts) - Google Fonts or local font files via `fonts.json`\n- [AI Assistant](/ai-assistant) - Enable the built-in chat with your own API key\n- [MCP Server](/model-context-protocol) - Let AI tools search your docs through `/api/mcp`";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const navigationMdxTemplate = "---\ntitle: \"Navigation\"\ndescription: \"Organize and structure your navigation.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 2\n---\n\n# Navigation\n\nDoccupine builds your sidebar automatically from your MDX pages. By default, it reads the page frontmatter and groups pages into categories in the order you define. For larger docs, you can take full control with a `navigation.json` file.\n\n## Automatic navigation (default)\n\nWhen no custom navigation is provided, Doccupine generates a structure based on each page's frontmatter.\n\n### Frontmatter fields\n\n- **category**: The category name that groups the page in the sidebar.\n- **categoryOrder**: The position of the category within the sidebar. Lower numbers appear first.\n- **order**: The position of the page within its category. Lower numbers appear first.\n\n### Example frontmatter\n\n```text\n---\ntitle: \"Navigation\"\ndescription: \"Organize and structure your navigation.\"\ndate: \"2025-01-15\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 2\n---\n```\n\nThis approach is great for small sets of documents. For larger projects, setting these fields on every page can become repetitive.\n\n## Custom navigation with navigation.json\n\nTo centrally define the entire sidebar, create a `navigation.json` at your project root (the same folder where you execute `npx doccupine`). When present, it takes priority over page frontmatter and fully controls the navigation structure.\n\n### Array format\n\nThe simplest format is an array of categories. When using [sections](/sections), this applies to the root section only.\n\n```json\n[\n {\n \"label\": \"Getting Started\",\n \"links\": [\n { \"slug\": \"\", \"title\": \"Introduction\" },\n { \"slug\": \"commands\", \"title\": \"Commands\" }\n ]\n },\n {\n \"label\": \"Components\",\n \"links\": [\n { \"slug\": \"components\", \"title\": \"Components\" },\n { \"slug\": \"headers-and-text\", \"title\": \"Headers and Text\" },\n { \"slug\": \"lists-and-tables\", \"title\": \"Lists and tables\" },\n { \"slug\": \"code\", \"title\": \"Code\" },\n { \"slug\": \"accordion\", \"title\": \"Accordion\" },\n { \"slug\": \"tabs\", \"title\": \"Tabs\" },\n { \"slug\": \"cards\", \"title\": \"Cards\" },\n { \"slug\": \"buttons\", \"title\": \"Buttons\" },\n { \"slug\": \"callouts\", \"title\": \"Callouts\" },\n { \"slug\": \"image-and-embeds\", \"title\": \"Images and embeds\" },\n { \"slug\": \"icons\", \"title\": \"Icons\" },\n { \"slug\": \"fields\", \"title\": \"Fields\" },\n { \"slug\": \"update\", \"title\": \"Update\" },\n { \"slug\": \"columns\", \"title\": \"Columns\" },\n { \"slug\": \"steps\", \"title\": \"Steps\" },\n { \"slug\": \"color-swatches\", \"title\": \"Color Swatches\" }\n ]\n },\n {\n \"label\": \"Configuration\",\n \"links\": [\n { \"slug\": \"globals\", \"title\": \"Globals\" },\n { \"slug\": \"navigation\", \"title\": \"Navigation\" },\n { \"slug\": \"sections\", \"title\": \"Sections\" },\n { \"slug\": \"footer-links\", \"title\": \"Footer Links\" },\n { \"slug\": \"theme\", \"title\": \"Theme\" },\n { \"slug\": \"media-and-assets\", \"title\": \"Media and assets\" },\n { \"slug\": \"fonts\", \"title\": \"Fonts\" },\n { \"slug\": \"ai-assistant\", \"title\": \"AI Assistant\" },\n { \"slug\": \"model-context-protocol\", \"title\": \"Model Context Protocol\" },\n { \"slug\": \"analytics\", \"title\": \"Analytics\" },\n { \"slug\": \"deployment-and-hosting\", \"title\": \"Deployment & Hosting\" }\n ]\n }\n]\n```\n\n### Object format (per-section)\n\nWhen using [sections](/sections), you can define navigation for each section by using an object keyed by section slug. Sections without a key fall back to auto-generated navigation from frontmatter.\n\n```json\n{\n \"\": [\n {\n \"label\": \"General\",\n \"links\": [\n { \"slug\": \"\", \"title\": \"Getting Started\" },\n { \"slug\": \"commands\", \"title\": \"Commands\" }\n ]\n }\n ],\n \"platform\": [\n {\n \"label\": \"Overview\",\n \"links\": [\n { \"slug\": \"platform/auth\", \"title\": \"Authentication\" },\n { \"slug\": \"platform/users\", \"title\": \"Users\" }\n ]\n }\n ]\n}\n```\n\nThe key `\"\"` controls the root section. Other keys match section slugs defined in `sections.json` or derived from frontmatter. See [Sections](/sections) for details on configuring sections.\n\n### Fields\n\n- **label**: The section header shown in the sidebar.\n- **links**: An array of page entries for that section.\n - **slug**: The MDX file slug (filename without extension). Use an empty string `\"\"` for `index.mdx`.\n - **title**: The display title in the navigation. This can differ from the page's `title` frontmatter.\n\n## Precedence and behavior\n\n<Callout type=\"note\">\n `navigation.json` takes priority over frontmatter. If present, it fully controls the sidebar structure for the sections it covers.\n</Callout>\n\n- Without `navigation.json`, the sidebar is built from page frontmatter: `category` -> grouped; `categoryOrder` -> category position; `order` -> page position.\n- When using the object format, sections not listed in `navigation.json` fall back to frontmatter-based navigation.\n- Pages without a `category` appear at the top level.\n\n## Tips\n\n- **Start simple**: Use frontmatter for small docs. Switch to `navigation.json` as the structure grows.\n- **Keep slugs consistent**: `slug` must match the MDX filename (e.g., `text.mdx` -> `text`).\n- **Control titles**: Use `title` in `navigation.json` to customize sidebar labels without changing page frontmatter.\n- **Per-section navigation**: Use the object format to define different sidebars for each section. Mix and match - define some sections explicitly and let others auto-generate.";
|
|
1
|
+
export declare const navigationMdxTemplate = "---\ntitle: \"Navigation\"\ndescription: \"Organize and structure your navigation.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 2\n---\n\n# Navigation\n\nDoccupine builds your sidebar automatically from your MDX pages. By default, it reads the page frontmatter and groups pages into categories in the order you define. For larger docs, you can take full control with a `navigation.json` file.\n\n## Automatic navigation (default)\n\nWhen no custom navigation is provided, Doccupine generates a structure based on each page's frontmatter.\n\n### Frontmatter fields\n\n- **category**: The category name that groups the page in the sidebar.\n- **categoryOrder**: The position of the category within the sidebar. Lower numbers appear first.\n- **order**: The position of the page within its category. Lower numbers appear first.\n- **navIcon**: Optional [Lucide](https://lucide.dev/icons) icon name shown next to the page's sidebar link.\n- **categoryIcon**: Optional Lucide icon name for the page's category header. The first page in a category that sets it wins.\n\n### Example frontmatter\n\n```text\n---\ntitle: \"Navigation\"\ndescription: \"Organize and structure your navigation.\"\ndate: \"2025-01-15\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 2\n---\n```\n\nThis approach is great for small sets of documents. For larger projects, setting these fields on every page can become repetitive.\n\n## Custom navigation with navigation.json\n\nTo centrally define the entire sidebar, create a `navigation.json` at your project root (the same folder where you execute `npx doccupine`). When present, it takes priority over page frontmatter and fully controls the navigation structure.\n\n### Array format\n\nThe simplest format is an array of categories. When using [sections](/sections), this applies to the root section only.\n\n```json\n[\n {\n \"label\": \"Getting Started\",\n \"links\": [\n { \"slug\": \"\", \"title\": \"Introduction\" },\n { \"slug\": \"commands\", \"title\": \"Commands\" }\n ]\n },\n {\n \"label\": \"Components\",\n \"links\": [\n { \"slug\": \"components\", \"title\": \"Components\" },\n { \"slug\": \"headers-and-text\", \"title\": \"Headers and Text\" },\n { \"slug\": \"lists-and-tables\", \"title\": \"Lists and tables\" },\n { \"slug\": \"code\", \"title\": \"Code\" },\n { \"slug\": \"accordion\", \"title\": \"Accordion\" },\n { \"slug\": \"tabs\", \"title\": \"Tabs\" },\n { \"slug\": \"cards\", \"title\": \"Cards\" },\n { \"slug\": \"buttons\", \"title\": \"Buttons\" },\n { \"slug\": \"callouts\", \"title\": \"Callouts\" },\n { \"slug\": \"image-and-embeds\", \"title\": \"Images and embeds\" },\n { \"slug\": \"icons\", \"title\": \"Icons\" },\n { \"slug\": \"fields\", \"title\": \"Fields\" },\n { \"slug\": \"update\", \"title\": \"Update\" },\n { \"slug\": \"columns\", \"title\": \"Columns\" },\n { \"slug\": \"steps\", \"title\": \"Steps\" },\n { \"slug\": \"color-swatches\", \"title\": \"Color Swatches\" }\n ]\n },\n {\n \"label\": \"Configuration\",\n \"links\": [\n { \"slug\": \"globals\", \"title\": \"Globals\" },\n { \"slug\": \"navigation\", \"title\": \"Navigation\" },\n { \"slug\": \"sections\", \"title\": \"Sections\" },\n { \"slug\": \"footer-links\", \"title\": \"Footer Links\" },\n { \"slug\": \"theme\", \"title\": \"Theme\" },\n { \"slug\": \"media-and-assets\", \"title\": \"Media and assets\" },\n { \"slug\": \"fonts\", \"title\": \"Fonts\" },\n { \"slug\": \"ai-assistant\", \"title\": \"AI Assistant\" },\n { \"slug\": \"model-context-protocol\", \"title\": \"Model Context Protocol\" },\n { \"slug\": \"analytics\", \"title\": \"Analytics\" },\n { \"slug\": \"deployment-and-hosting\", \"title\": \"Deployment & Hosting\" },\n { \"slug\": \"authentication\", \"title\": \"Authentication\" }\n ]\n }\n]\n```\n\n### Object format (per-section)\n\nWhen using [sections](/sections), you can define navigation for each section by using an object keyed by section slug. Sections without a key fall back to auto-generated navigation from frontmatter.\n\n```json\n{\n \"\": [\n {\n \"label\": \"General\",\n \"links\": [\n { \"slug\": \"\", \"title\": \"Getting Started\" },\n { \"slug\": \"commands\", \"title\": \"Commands\" }\n ]\n }\n ],\n \"platform\": [\n {\n \"label\": \"Overview\",\n \"links\": [\n { \"slug\": \"platform/auth\", \"title\": \"Authentication\" },\n { \"slug\": \"platform/users\", \"title\": \"Users\" }\n ]\n }\n ]\n}\n```\n\nThe key `\"\"` controls the root section. Other keys match section slugs defined in `sections.json` or derived from frontmatter. See [Sections](/sections) for details on configuring sections.\n\n### Fields\n\n- **label**: The section header shown in the sidebar.\n- **icon**: Optional [Lucide](https://lucide.dev/icons) icon name shown next to the category header.\n- **links**: An array of page entries for that section.\n - **slug**: The MDX file slug (filename without extension). Use an empty string `\"\"` for `index.mdx`.\n - **title**: The display title in the navigation. This can differ from the page's `title` frontmatter.\n - **icon**: Optional Lucide icon name shown next to the link.\n - **links**: Optional array of nested child links. See [Nested navigation](#nested-navigation).\n\n## Icons\n\nAdd icons to categories and links to make the sidebar easier to scan. Icons use [Lucide](https://lucide.dev/icons) names in kebab-case (e.g. `rocket`, `book-open`, `settings`). Unknown names render nothing, so a typo never breaks the build.\n\nWith frontmatter, set `navIcon` on a page for its sidebar link and `categoryIcon` for its category:\n\n```text\n---\ntitle: \"Introduction\"\ncategory: \"Getting Started\"\ncategoryOrder: 1\norder: 1\nnavIcon: \"rocket\"\ncategoryIcon: \"book-open\"\n---\n```\n\nWith `navigation.json`, add an `icon` to any category or link:\n\n```json\n[\n {\n \"label\": \"Getting Started\",\n \"icon\": \"book-open\",\n \"links\": [\n { \"slug\": \"\", \"title\": \"Introduction\", \"icon\": \"rocket\" },\n { \"slug\": \"commands\", \"title\": \"Commands\", \"icon\": \"terminal\" }\n ]\n }\n]\n```\n\n## Nested navigation\n\nA link in `navigation.json` can hold its own `links` array to create a collapsible group. Groups expand and collapse on click and open automatically when one of their pages is active. Nesting can go as deep as you need.\n\n```json\n[\n {\n \"label\": \"Guides\",\n \"icon\": \"book-open\",\n \"links\": [\n { \"slug\": \"guides\", \"title\": \"Overview\", \"icon\": \"compass\" },\n {\n \"title\": \"Advanced\",\n \"icon\": \"settings\",\n \"links\": [\n { \"slug\": \"guides/caching\", \"title\": \"Caching\" },\n { \"slug\": \"guides/streaming\", \"title\": \"Streaming\" }\n ]\n }\n ]\n }\n]\n```\n\nA group can be a plain label - omit `slug` and it acts only as a collapsible header - or a real page, by adding a `slug` so the group title is also a link.\n\n<Callout type=\"note\">\n Nested groups are only available through `navigation.json`. Frontmatter navigation is always two levels: category and pages.\n</Callout>\n\n## Precedence and behavior\n\n<Callout type=\"note\">\n `navigation.json` takes priority over frontmatter. If present, it fully controls the sidebar structure for the sections it covers.\n</Callout>\n\n- Without `navigation.json`, the sidebar is built from page frontmatter: `category` -> grouped; `categoryOrder` -> category position; `order` -> page position.\n- When using the object format, sections not listed in `navigation.json` fall back to frontmatter-based navigation.\n- Pages without a `category` appear at the top level.\n\n## Tips\n\n- **Start simple**: Use frontmatter for small docs. Switch to `navigation.json` as the structure grows.\n- **Keep slugs consistent**: `slug` must match the MDX filename (e.g., `text.mdx` -> `text`).\n- **Control titles**: Use `title` in `navigation.json` to customize sidebar labels without changing page frontmatter.\n- **Per-section navigation**: Use the object format to define different sidebars for each section. Mix and match - define some sections explicitly and let others auto-generate.";
|
|
@@ -20,6 +20,8 @@ When no custom navigation is provided, Doccupine generates a structure based on
|
|
|
20
20
|
- **category**: The category name that groups the page in the sidebar.
|
|
21
21
|
- **categoryOrder**: The position of the category within the sidebar. Lower numbers appear first.
|
|
22
22
|
- **order**: The position of the page within its category. Lower numbers appear first.
|
|
23
|
+
- **navIcon**: Optional [Lucide](https://lucide.dev/icons) icon name shown next to the page's sidebar link.
|
|
24
|
+
- **categoryIcon**: Optional Lucide icon name for the page's category header. The first page in a category that sets it wins.
|
|
23
25
|
|
|
24
26
|
### Example frontmatter
|
|
25
27
|
|
|
@@ -87,7 +89,8 @@ The simplest format is an array of categories. When using [sections](/sections),
|
|
|
87
89
|
{ "slug": "ai-assistant", "title": "AI Assistant" },
|
|
88
90
|
{ "slug": "model-context-protocol", "title": "Model Context Protocol" },
|
|
89
91
|
{ "slug": "analytics", "title": "Analytics" },
|
|
90
|
-
{ "slug": "deployment-and-hosting", "title": "Deployment & Hosting" }
|
|
92
|
+
{ "slug": "deployment-and-hosting", "title": "Deployment & Hosting" },
|
|
93
|
+
{ "slug": "authentication", "title": "Authentication" }
|
|
91
94
|
]
|
|
92
95
|
}
|
|
93
96
|
]
|
|
@@ -125,9 +128,74 @@ The key \`""\` controls the root section. Other keys match section slugs defined
|
|
|
125
128
|
### Fields
|
|
126
129
|
|
|
127
130
|
- **label**: The section header shown in the sidebar.
|
|
131
|
+
- **icon**: Optional [Lucide](https://lucide.dev/icons) icon name shown next to the category header.
|
|
128
132
|
- **links**: An array of page entries for that section.
|
|
129
133
|
- **slug**: The MDX file slug (filename without extension). Use an empty string \`""\` for \`index.mdx\`.
|
|
130
134
|
- **title**: The display title in the navigation. This can differ from the page's \`title\` frontmatter.
|
|
135
|
+
- **icon**: Optional Lucide icon name shown next to the link.
|
|
136
|
+
- **links**: Optional array of nested child links. See [Nested navigation](#nested-navigation).
|
|
137
|
+
|
|
138
|
+
## Icons
|
|
139
|
+
|
|
140
|
+
Add icons to categories and links to make the sidebar easier to scan. Icons use [Lucide](https://lucide.dev/icons) names in kebab-case (e.g. \`rocket\`, \`book-open\`, \`settings\`). Unknown names render nothing, so a typo never breaks the build.
|
|
141
|
+
|
|
142
|
+
With frontmatter, set \`navIcon\` on a page for its sidebar link and \`categoryIcon\` for its category:
|
|
143
|
+
|
|
144
|
+
\`\`\`text
|
|
145
|
+
---
|
|
146
|
+
title: "Introduction"
|
|
147
|
+
category: "Getting Started"
|
|
148
|
+
categoryOrder: 1
|
|
149
|
+
order: 1
|
|
150
|
+
navIcon: "rocket"
|
|
151
|
+
categoryIcon: "book-open"
|
|
152
|
+
---
|
|
153
|
+
\`\`\`
|
|
154
|
+
|
|
155
|
+
With \`navigation.json\`, add an \`icon\` to any category or link:
|
|
156
|
+
|
|
157
|
+
\`\`\`json
|
|
158
|
+
[
|
|
159
|
+
{
|
|
160
|
+
"label": "Getting Started",
|
|
161
|
+
"icon": "book-open",
|
|
162
|
+
"links": [
|
|
163
|
+
{ "slug": "", "title": "Introduction", "icon": "rocket" },
|
|
164
|
+
{ "slug": "commands", "title": "Commands", "icon": "terminal" }
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
]
|
|
168
|
+
\`\`\`
|
|
169
|
+
|
|
170
|
+
## Nested navigation
|
|
171
|
+
|
|
172
|
+
A link in \`navigation.json\` can hold its own \`links\` array to create a collapsible group. Groups expand and collapse on click and open automatically when one of their pages is active. Nesting can go as deep as you need.
|
|
173
|
+
|
|
174
|
+
\`\`\`json
|
|
175
|
+
[
|
|
176
|
+
{
|
|
177
|
+
"label": "Guides",
|
|
178
|
+
"icon": "book-open",
|
|
179
|
+
"links": [
|
|
180
|
+
{ "slug": "guides", "title": "Overview", "icon": "compass" },
|
|
181
|
+
{
|
|
182
|
+
"title": "Advanced",
|
|
183
|
+
"icon": "settings",
|
|
184
|
+
"links": [
|
|
185
|
+
{ "slug": "guides/caching", "title": "Caching" },
|
|
186
|
+
{ "slug": "guides/streaming", "title": "Streaming" }
|
|
187
|
+
]
|
|
188
|
+
}
|
|
189
|
+
]
|
|
190
|
+
}
|
|
191
|
+
]
|
|
192
|
+
\`\`\`
|
|
193
|
+
|
|
194
|
+
A group can be a plain label - omit \`slug\` and it acts only as a collapsible header - or a real page, by adding a \`slug\` so the group title is also a link.
|
|
195
|
+
|
|
196
|
+
<Callout type="note">
|
|
197
|
+
Nested groups are only available through \`navigation.json\`. Frontmatter navigation is always two levels: category and pages.
|
|
198
|
+
</Callout>
|
|
131
199
|
|
|
132
200
|
## Precedence and behavior
|
|
133
201
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const platformBuildAndDeployMdxTemplate = "---\ntitle: \"Build & Deploy\"\ndescription: \"Monitor your documentation site's build status and deployment history.\"\ndate: \"2026-02-19\"\ncategory: \"Infrastructure\"\ncategoryOrder: 3\norder: 1\nsection: \"Platform\"\n---\n\n# Build & Deploy\n\nThe Build & Deploy page shows your documentation site's build and deployment history. Every time you publish changes or push to GitHub, a new deployment is created.\n\n## Deployment status\n\nThe project header always shows your current deployment status:\n\n- **Ready** - your site is live and up to date\n- **Building** - a new deployment is in progress\n- **Queued** - a deployment is waiting to start\n- **Error** - the build failed\n\n<Callout type=\"warning\">\n If a deployment fails, check the build logs for error details. Common issues include MDX syntax errors or missing frontmatter fields.\n</Callout>\n\n## Build logs\n\nClick on any deployment to view its build logs. These show the full output of the build process, making it easy to diagnose issues.\n\n## Deployment triggers\n\nDeployments are triggered in three ways:\n\n1. **Publishing from Doccupine** - clicking Publish in the project header\n2. **Pushing to GitHub** - pushing to the `main` branch of your repository triggers an automatic deploy via webhook\n3. **Configuration changes** - saving AI assistant settings triggers a redeploy since those are stored as environment variables\n\n## Visiting your site\n\nClick the **Visit** button in the project header to open your live documentation site in a new tab.";
|
|
1
|
+
export declare const platformBuildAndDeployMdxTemplate = "---\ntitle: \"Build & Deploy\"\ndescription: \"Monitor your documentation site's build status and deployment history.\"\ndate: \"2026-02-19\"\ncategory: \"Infrastructure\"\ncategoryOrder: 3\ncategoryIcon: \"server\"\norder: 1\nsection: \"Platform\"\n---\n\n# Build & Deploy\n\nThe Build & Deploy page shows your documentation site's build and deployment history. Every time you publish changes or push to GitHub, a new deployment is created.\n\n## Deployment status\n\nThe project header always shows your current deployment status:\n\n- **Ready** - your site is live and up to date\n- **Building** - a new deployment is in progress\n- **Queued** - a deployment is waiting to start\n- **Error** - the build failed\n\n<Callout type=\"warning\">\n If a deployment fails, check the build logs for error details. Common issues include MDX syntax errors or missing frontmatter fields.\n</Callout>\n\n## Build logs\n\nClick on any deployment to view its build logs. These show the full output of the build process, making it easy to diagnose issues.\n\n## Deployment triggers\n\nDeployments are triggered in three ways:\n\n1. **Publishing from Doccupine** - clicking Publish in the project header\n2. **Pushing to GitHub** - pushing to the `main` branch of your repository triggers an automatic deploy via webhook\n3. **Configuration changes** - saving AI assistant settings triggers a redeploy since those are stored as environment variables\n\n## Visiting your site\n\nClick the **Visit** button in the project header to open your live documentation site in a new tab.";
|