doccupine 0.0.102 → 0.0.104
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 +5 -1
- package/dist/lib/types.d.ts +2 -0
- package/dist/templates/app/not-found.d.ts +1 -1
- package/dist/templates/app/not-found.js +2 -6
- 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 +124 -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/Footer.d.ts +1 -1
- package/dist/templates/components/layout/Footer.js +7 -0
- package/dist/templates/components/layout/Header.d.ts +1 -1
- package/dist/templates/components/layout/Header.js +3 -0
- package/dist/templates/components/layout/NotFound.d.ts +1 -0
- package/dist/templates/components/layout/NotFound.js +76 -0
- package/dist/templates/components/layout/SectionBar.d.ts +1 -1
- package/dist/templates/components/layout/SectionBar.js +9 -0
- package/dist/templates/components/layout/SharedStyles.d.ts +1 -1
- package/dist/templates/components/layout/SharedStyles.js +24 -0
- package/dist/templates/components/layout/Slug.d.ts +1 -0
- package/dist/templates/components/layout/Slug.js +32 -0
- package/dist/templates/components/layout/Steps.d.ts +1 -1
- package/dist/templates/components/layout/Steps.js +2 -1
- 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
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import Link from "next/link";
|
|
12
12
|
import { mq, Theme } from "@/app/theme";
|
|
13
13
|
import {
|
|
14
|
+
focusRing,
|
|
14
15
|
styledAnchor,
|
|
15
16
|
styledTable,
|
|
16
17
|
stylesLists,
|
|
@@ -114,6 +115,7 @@ export const StyledMarkdownContainer = styled.div\`
|
|
|
114
115
|
interface Props {
|
|
115
116
|
theme?: Theme;
|
|
116
117
|
$isActive?: boolean;
|
|
118
|
+
$isOpen?: boolean;
|
|
117
119
|
$hasSectionBar?: boolean;
|
|
118
120
|
}
|
|
119
121
|
|
|
@@ -251,6 +253,7 @@ export const StyledIndexSidebarLink = styled.a<{
|
|
|
251
253
|
font-weight: \${({ $isActive }) => ($isActive ? "600" : "400")};
|
|
252
254
|
text-decoration: none;
|
|
253
255
|
transition: all 0.3s ease;
|
|
256
|
+
\${focusRing};
|
|
254
257
|
|
|
255
258
|
&:hover {
|
|
256
259
|
color: \${({ theme }) => theme.colors.primary};
|
|
@@ -271,6 +274,13 @@ export const StyledStrong = styled.strong<{ theme: Theme }>\`
|
|
|
271
274
|
font-weight: 600;
|
|
272
275
|
\${({ theme }) => styledStrong(theme)};
|
|
273
276
|
color: \${({ theme }) => theme.colors.accentStrong};
|
|
277
|
+
display: inline-flex;
|
|
278
|
+
align-items: center;
|
|
279
|
+
gap: 8px;
|
|
280
|
+
|
|
281
|
+
& svg {
|
|
282
|
+
flex-shrink: 0;
|
|
283
|
+
}
|
|
274
284
|
\`;
|
|
275
285
|
|
|
276
286
|
export const StyledSidebarListItem = styled.li\`
|
|
@@ -279,28 +289,136 @@ export const StyledSidebarListItem = styled.li\`
|
|
|
279
289
|
clear: both;
|
|
280
290
|
\`;
|
|
281
291
|
|
|
282
|
-
|
|
292
|
+
// Shared appearance for every sidebar row - leaf links AND nested group
|
|
293
|
+
// headers - so hover, active state, and the left rail read identically.
|
|
294
|
+
const sidebarRowStyles = css<Props>\`
|
|
283
295
|
text-decoration: none;
|
|
284
296
|
font-size: \${({ theme }) => theme.fontSizes.small.lg};
|
|
285
297
|
line-height: 1.6;
|
|
286
298
|
color: \${({ theme }) => theme.colors.accentMuted};
|
|
287
299
|
padding: 5px 0 5px 20px;
|
|
288
300
|
display: flex;
|
|
301
|
+
align-items: center;
|
|
302
|
+
gap: 8px;
|
|
303
|
+
flex: 1;
|
|
304
|
+
min-width: 0;
|
|
289
305
|
transition: all 0.3s ease;
|
|
290
306
|
border-left: solid 1px \${({ theme }) => theme.colors.grayLight};
|
|
291
307
|
|
|
308
|
+
& svg {
|
|
309
|
+
flex-shrink: 0;
|
|
310
|
+
}
|
|
311
|
+
|
|
292
312
|
&:hover {
|
|
293
313
|
color: \${({ theme }) => theme.colors.accent};
|
|
294
314
|
border-color: \${({ theme }) => theme.colors.primary};
|
|
295
315
|
}
|
|
296
316
|
|
|
317
|
+
\${focusRing};
|
|
318
|
+
|
|
297
319
|
\${({ $isActive, theme }) =>
|
|
298
320
|
$isActive &&
|
|
299
|
-
\`
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
321
|
+
css\`
|
|
322
|
+
color: \${theme.colors.accentStrong};
|
|
323
|
+
border-color: \${theme.colors.primary};
|
|
324
|
+
font-weight: 600;
|
|
325
|
+
\`};
|
|
326
|
+
\`;
|
|
327
|
+
|
|
328
|
+
// The collapse chevron points right when closed and rotates to point down
|
|
329
|
+
// when the group is open.
|
|
330
|
+
const sidebarChevron = css<Props>\`
|
|
331
|
+
& .lucide-chevron-right {
|
|
332
|
+
margin-left: auto;
|
|
333
|
+
transition: transform 0.3s ease;
|
|
334
|
+
|
|
335
|
+
\${({ $isOpen }) =>
|
|
336
|
+
$isOpen &&
|
|
337
|
+
css\`
|
|
338
|
+
transform: rotate(90deg);
|
|
339
|
+
\`}
|
|
340
|
+
}
|
|
341
|
+
\`;
|
|
342
|
+
|
|
343
|
+
export const StyledSidebarListItemLink = styled(Link)<Props>\`
|
|
344
|
+
\${sidebarRowStyles};
|
|
345
|
+
\`;
|
|
346
|
+
|
|
347
|
+
// Nested navigation group. The header shares the link appearance/hover; a
|
|
348
|
+
// non-navigable group is a single toggle button, while a group that is also a
|
|
349
|
+
// page pairs a link with a chevron toggle. Children live in
|
|
350
|
+
// StyledSidebarGroupContent, which animates open/closed via height 0 <-> auto
|
|
351
|
+
// (enabled by interpolate-size in GlobalStyles, same as the Accordion).
|
|
352
|
+
export const StyledSidebarGroupButton = styled.button<Props>\`
|
|
353
|
+
\${resetButton};
|
|
354
|
+
\${sidebarRowStyles};
|
|
355
|
+
\${sidebarChevron};
|
|
356
|
+
width: 100%;
|
|
357
|
+
text-align: left;
|
|
358
|
+
\`;
|
|
359
|
+
|
|
360
|
+
export const StyledSidebarGroupRow = styled.div<Props>\`
|
|
361
|
+
\${sidebarRowStyles};
|
|
362
|
+
\${sidebarChevron};
|
|
363
|
+
|
|
364
|
+
/* The row is a plain div, so it never matches :focus-visible itself. Ring the
|
|
365
|
+
whole row when its inner link or chevron takes keyboard focus instead, to
|
|
366
|
+
match the leaf-row focus treatment. */
|
|
367
|
+
&:has(:focus-visible) {
|
|
368
|
+
border-radius: \${({ theme }) => theme.spacing.radius.xs};
|
|
369
|
+
box-shadow: 0 0 0 4px \${({ theme }) => theme.colors.primaryLight};
|
|
370
|
+
}
|
|
371
|
+
\`;
|
|
372
|
+
|
|
373
|
+
export const StyledSidebarGroupLink = styled(Link)\`
|
|
374
|
+
display: flex;
|
|
375
|
+
align-items: center;
|
|
376
|
+
gap: 8px;
|
|
377
|
+
flex: 1;
|
|
378
|
+
min-width: 0;
|
|
379
|
+
color: inherit;
|
|
380
|
+
text-decoration: none;
|
|
381
|
+
|
|
382
|
+
& svg {
|
|
383
|
+
flex-shrink: 0;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/* Ring is drawn on the parent row via :has(:focus-visible). */
|
|
387
|
+
&:focus-visible {
|
|
388
|
+
outline: none;
|
|
389
|
+
}
|
|
390
|
+
\`;
|
|
391
|
+
|
|
392
|
+
export const StyledSidebarGroupChevron = styled.button\`
|
|
393
|
+
display: inline-flex;
|
|
394
|
+
align-items: center;
|
|
395
|
+
justify-content: center;
|
|
396
|
+
flex-shrink: 0;
|
|
397
|
+
padding: 0;
|
|
398
|
+
border: 0;
|
|
399
|
+
background: none;
|
|
400
|
+
color: inherit;
|
|
401
|
+
cursor: pointer;
|
|
402
|
+
|
|
403
|
+
/* Ring is drawn on the parent row via :has(:focus-visible). */
|
|
404
|
+
&:focus-visible {
|
|
405
|
+
outline: none;
|
|
406
|
+
}
|
|
407
|
+
\`;
|
|
408
|
+
|
|
409
|
+
export const StyledSidebarGroupContent = styled.ul<Props>\`
|
|
410
|
+
list-style: none;
|
|
411
|
+
margin: 0;
|
|
412
|
+
padding: 0;
|
|
413
|
+
height: 0;
|
|
414
|
+
overflow: clip;
|
|
415
|
+
transition: all 0.3s ease;
|
|
416
|
+
|
|
417
|
+
\${({ $isOpen }) =>
|
|
418
|
+
$isOpen &&
|
|
419
|
+
css\`
|
|
420
|
+
height: auto;
|
|
421
|
+
\`}
|
|
304
422
|
\`;
|
|
305
423
|
|
|
306
424
|
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;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const footerTemplate = "\"use client\";\nimport { useContext } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { Space, styledSmall } from \"cherry-styled-components\";\nimport { ChatContext } from \"@/components/Chat\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { GitHubLogo } from \"@/components/layout/Pictograms\";\nimport linksData from \"@/links.json\";\n\ninterface LinkProps {\n title: string;\n url: string;\n icon?: string;\n}\n\nconst links = linksData as LinkProps[];\n\nconst StyledFooter = styled.footer<{\n theme: Theme;\n $isChatOpen?: boolean;\n $hasLinks?: boolean;\n}>`\n padding: 0 20px;\n transition: all 0.3s ease;\n\n ${({ $hasLinks }) =>\n $hasLinks &&\n css`\n margin-top: 20px;\n `}\n\n ${mq(\"lg\")} {\n margin: 0;\n padding: 0 300px 0 300px;\n\n ${({ $isChatOpen }) =>\n $isChatOpen &&\n css`\n padding: 0 440px 0 300px;\n `}\n }\n`;\n\nconst StyledFooterInner = styled.div<{ theme: Theme }>`\n border-top: solid 1px ${({ theme }) => theme.colors.grayLight};\n max-width: 640px;\n margin: 0 auto;\n padding: 28px 0;\n color: ${({ theme }) => theme.colors.grayDark};\n ${({ theme }) => styledSmall(theme)};\n\n ${mq(\"lg\")} {\n padding: 20px 0;\n }\n\n & a {\n font-weight: 700;\n color: ${({ theme }) => theme.colors.accent};\n text-decoration: none;\n transition: all 0.3s ease;\n display: inline-flex;\n\n &:hover {\n color: ${({ theme }) => theme.colors.primary};\n }\n\n & svg {\n width: 18px;\n height: 18px;\n }\n }\n`;\n\nconst StyledFooterFlex = styled.div`\n display: flex;\n justify-content: flex-start;\n align-items: center;\n gap: 20px;\n\n ${mq(\"lg\")} {\n justify-content: space-between;\n }\n`;\n\nfunction Footer({ hideBranding }: { hideBranding?: boolean }) {\n const { isOpen } = useContext(ChatContext);\n\n if (hideBranding) return <Space $xs={80} $lg=\"none\" />;\n\n return (\n <StyledFooter $isChatOpen={isOpen} $hasLinks={links.length > 0}>\n <StyledFooterInner>\n <StyledFooterFlex>\n <span>\n Powered by{\" \"}\n <a\n href=\"https://doccupine.com\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Doccupine\n </a>\n </span>\n <a\n href=\"https://github.com/doccupine/cli\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n aria-label=\"Doccupine on GitHub\"\n >\n <GitHubLogo />\n </a>\n </StyledFooterFlex>\n </StyledFooterInner>\n </StyledFooter>\n );\n}\n\nexport { Footer };\n";
|
|
1
|
+
export declare const footerTemplate = "\"use client\";\nimport { useContext } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { Space, styledSmall } from \"cherry-styled-components\";\nimport { ChatContext } from \"@/components/Chat\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { GitHubLogo } from \"@/components/layout/Pictograms\";\nimport { focusRing } from \"@/components/layout/SharedStyled\";\nimport linksData from \"@/links.json\";\n\ninterface LinkProps {\n title: string;\n url: string;\n icon?: string;\n}\n\nconst links = linksData as LinkProps[];\n\nconst StyledFooter = styled.footer<{\n theme: Theme;\n $isChatOpen?: boolean;\n $hasLinks?: boolean;\n}>`\n padding: 0 20px;\n transition: all 0.3s ease;\n\n ${({ $hasLinks }) =>\n $hasLinks &&\n css`\n margin-top: 20px;\n `}\n\n ${mq(\"lg\")} {\n margin: 0;\n padding: 0 300px 0 300px;\n\n ${({ $isChatOpen }) =>\n $isChatOpen &&\n css`\n padding: 0 440px 0 300px;\n `}\n }\n`;\n\nconst StyledFooterInner = styled.div<{ theme: Theme }>`\n border-top: solid 1px ${({ theme }) => theme.colors.grayLight};\n max-width: 640px;\n margin: 0 auto;\n padding: 28px 0;\n color: ${({ theme }) => theme.colors.grayDark};\n ${({ theme }) => styledSmall(theme)};\n\n ${mq(\"lg\")} {\n padding: 20px 0;\n }\n\n & a {\n font-weight: 700;\n color: ${({ theme }) => theme.colors.accent};\n text-decoration: none;\n transition: all 0.3s ease;\n display: inline-flex;\n ${focusRing};\n\n &:hover {\n color: ${({ theme }) => theme.colors.primary};\n }\n\n /* The GitHub logo is an icon-only link - round its focus ring to match. */\n &:has(svg):focus-visible {\n border-radius: 100%;\n }\n\n & svg {\n width: 18px;\n height: 18px;\n }\n }\n`;\n\nconst StyledFooterFlex = styled.div`\n display: flex;\n justify-content: flex-start;\n align-items: center;\n gap: 20px;\n\n ${mq(\"lg\")} {\n justify-content: space-between;\n }\n`;\n\nfunction Footer({ hideBranding }: { hideBranding?: boolean }) {\n const { isOpen } = useContext(ChatContext);\n\n if (hideBranding) return <Space $xs={80} $lg=\"none\" />;\n\n return (\n <StyledFooter $isChatOpen={isOpen} $hasLinks={links.length > 0}>\n <StyledFooterInner>\n <StyledFooterFlex>\n <span>\n Powered by{\" \"}\n <a\n href=\"https://doccupine.com\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Doccupine\n </a>\n </span>\n <a\n href=\"https://github.com/doccupine/cli\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n aria-label=\"Doccupine on GitHub\"\n >\n <GitHubLogo />\n </a>\n </StyledFooterFlex>\n </StyledFooterInner>\n </StyledFooter>\n );\n}\n\nexport { Footer };\n";
|
|
@@ -6,6 +6,7 @@ import { Space, styledSmall } from "cherry-styled-components";
|
|
|
6
6
|
import { ChatContext } from "@/components/Chat";
|
|
7
7
|
import { mq, Theme } from "@/app/theme";
|
|
8
8
|
import { GitHubLogo } from "@/components/layout/Pictograms";
|
|
9
|
+
import { focusRing } from "@/components/layout/SharedStyled";
|
|
9
10
|
import linksData from "@/links.json";
|
|
10
11
|
|
|
11
12
|
interface LinkProps {
|
|
@@ -60,11 +61,17 @@ const StyledFooterInner = styled.div<{ theme: Theme }>\`
|
|
|
60
61
|
text-decoration: none;
|
|
61
62
|
transition: all 0.3s ease;
|
|
62
63
|
display: inline-flex;
|
|
64
|
+
\${focusRing};
|
|
63
65
|
|
|
64
66
|
&:hover {
|
|
65
67
|
color: \${({ theme }) => theme.colors.primary};
|
|
66
68
|
}
|
|
67
69
|
|
|
70
|
+
/* The GitHub logo is an icon-only link - round its focus ring to match. */
|
|
71
|
+
&:has(svg):focus-visible {
|
|
72
|
+
border-radius: 100%;
|
|
73
|
+
}
|
|
74
|
+
|
|
68
75
|
& svg {
|
|
69
76
|
width: 18px;
|
|
70
77
|
height: 18px;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const headerTemplate = "\"use client\";\nimport React from \"react\";\nimport { useCallback, useContext, useRef, useState } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport Link from \"next/link\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { useOnClickOutside } from \"cherry-styled-components\";\nimport { Search } from \"lucide-react\";\nimport { Logo } from \"@/components/layout/Pictograms\";\nimport { ChatContext, ChatButtonCTA } from \"@/components/Chat\";\nimport {\n SearchContext,\n SearchKbd,\n StyledSearchButton,\n} from \"@/components/SearchDocs\";\nimport themeJson from \"@/theme.json\";\n\nconst customThemeJson = themeJson as typeof themeJson & {\n logo?: { dark: string; light: string };\n};\n\nconst StyledHeader = styled.header<{ theme: Theme; $hasChildren: boolean }>`\n position: sticky;\n top: 0;\n margin: 0;\n z-index: 1000;\n width: 100%;\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n\n ${({ $hasChildren }) =>\n !$hasChildren &&\n css`\n ${mq(\"lg\")} {\n padding-bottom: 16px;\n padding-top: 16px;\n }\n `}\n\n &::before,\n &::after {\n display: block;\n content: \"\";\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n background: ${({ theme }) => theme.colors.light};\n z-index: -2;\n }\n\n &::after {\n background: ${({ theme }) =>\n `color-mix(in srgb, ${theme.colors.primaryLight} 5%, transparent)`};\n z-index: -1;\n }\n\n & .logo {\n display: flex;\n\n & svg,\n & img {\n margin: auto;\n height: auto;\n width: fit-content;\n min-width: fit-content;\n max-width: 182px;\n max-height: 30px;\n\n & path[fill] {\n fill: ${({ theme }) => theme.colors.primary};\n }\n }\n }\n`;\n\nconst StyledHeaderInner = styled.div<{ $hasChildren: boolean }>`\n display: flex;\n align-items: center;\n justify-content: space-between;\n flex-wrap: wrap;\n padding: 16px 0 0 20px;\n\n ${({ $hasChildren }) =>\n !$hasChildren &&\n css`\n padding-bottom: 16px;\n `}\n\n ${mq(\"lg\")} {\n flex-wrap: nowrap;\n padding: 0 20px;\n }\n`;\n\nconst StyledLeftWrapper = styled.div`\n display: flex;\n align-items: center;\n gap: 10px;\n min-width: fit-content;\n padding-right: 20px;\n\n ${mq(\"lg\")} {\n padding-right: 0;\n }\n`;\n\ninterface HeaderProps {\n children?: React.ReactNode;\n}\n\nfunction Header({ children }: HeaderProps) {\n const [isOptionActive, setIsOptionActive] = useState(false);\n const [isLangActive, setIsLangActive] = useState(false);\n\n const wrapperRef = useRef<HTMLSpanElement>(null);\n const elmRef = useRef<HTMLDivElement>(null);\n const langRef = useRef<HTMLSpanElement>(null);\n const closeMenu = useCallback(() => {\n setIsOptionActive(false);\n setIsLangActive(false);\n }, []);\n\n useOnClickOutside(\n [elmRef, wrapperRef],\n isOptionActive ? closeMenu : () => {},\n );\n useOnClickOutside([langRef, wrapperRef], isLangActive ? closeMenu : () => {});\n const { isChatActive } = useContext(ChatContext);\n const { openSearch } = useContext(SearchContext);\n\n return (\n <StyledHeader $hasChildren={children ? true : false} id=\"header\">\n <StyledHeaderInner $hasChildren={children ? true : false}>\n <Link href=\"/\" className=\"logo\" aria-label=\"Logo\">\n {customThemeJson.logo ? (\n <>\n {/* Both logos render; .light-only and .dark-only classes in\n GlobalStyles hide the inactive one based on the \"dark\" class\n on <html>. Avoids a JS-driven swap so no flash on first load. */}\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n className=\"light-only\"\n src={customThemeJson.logo.light}\n alt=\"Logo\"\n width=\"100\"\n height=\"100\"\n />\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n className=\"dark-only\"\n src={customThemeJson.logo.dark}\n alt=\"Logo\"\n width=\"100\"\n height=\"100\"\n />\n </>\n ) : (\n <Logo />\n )}\n </Link>\n {children}\n <StyledLeftWrapper>\n <StyledSearchButton onClick={openSearch} aria-label=\"Search docs\">\n <Search size={14} />\n <SearchKbd>⌘K</SearchKbd>\n </StyledSearchButton>\n {isChatActive && <ChatButtonCTA />}\n </StyledLeftWrapper>\n </StyledHeaderInner>\n </StyledHeader>\n );\n}\n\nexport { Header };\n";
|
|
1
|
+
export declare const headerTemplate = "\"use client\";\nimport React from \"react\";\nimport { useCallback, useContext, useRef, useState } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport Link from \"next/link\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { focusRing } from \"@/components/layout/SharedStyled\";\nimport { useOnClickOutside } from \"cherry-styled-components\";\nimport { Search } from \"lucide-react\";\nimport { Logo } from \"@/components/layout/Pictograms\";\nimport { ChatContext, ChatButtonCTA } from \"@/components/Chat\";\nimport {\n SearchContext,\n SearchKbd,\n StyledSearchButton,\n} from \"@/components/SearchDocs\";\nimport themeJson from \"@/theme.json\";\n\nconst customThemeJson = themeJson as typeof themeJson & {\n logo?: { dark: string; light: string };\n};\n\nconst StyledHeader = styled.header<{ theme: Theme; $hasChildren: boolean }>`\n position: sticky;\n top: 0;\n margin: 0;\n z-index: 1000;\n width: 100%;\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n\n ${({ $hasChildren }) =>\n !$hasChildren &&\n css`\n ${mq(\"lg\")} {\n padding-bottom: 16px;\n padding-top: 16px;\n }\n `}\n\n &::before,\n &::after {\n display: block;\n content: \"\";\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n background: ${({ theme }) => theme.colors.light};\n z-index: -2;\n }\n\n &::after {\n background: ${({ theme }) =>\n `color-mix(in srgb, ${theme.colors.primaryLight} 5%, transparent)`};\n z-index: -1;\n }\n\n & .logo {\n display: flex;\n min-width: max-content;\n ${focusRing};\n\n & svg,\n & img {\n margin: auto;\n height: auto;\n width: fit-content;\n min-width: fit-content;\n max-width: 182px;\n max-height: 30px;\n\n & path[fill] {\n fill: ${({ theme }) => theme.colors.primary};\n }\n }\n }\n`;\n\nconst StyledHeaderInner = styled.div<{ $hasChildren: boolean }>`\n display: flex;\n align-items: center;\n justify-content: space-between;\n flex-wrap: wrap;\n padding: 16px 0 0 20px;\n\n ${({ $hasChildren }) =>\n !$hasChildren &&\n css`\n padding-bottom: 16px;\n `}\n\n ${mq(\"lg\")} {\n flex-wrap: nowrap;\n padding: 0 20px;\n }\n`;\n\nconst StyledLeftWrapper = styled.div`\n display: flex;\n align-items: center;\n gap: 10px;\n min-width: fit-content;\n padding-right: 20px;\n\n ${mq(\"lg\")} {\n padding-right: 0;\n }\n`;\n\ninterface HeaderProps {\n children?: React.ReactNode;\n}\n\nfunction Header({ children }: HeaderProps) {\n const [isOptionActive, setIsOptionActive] = useState(false);\n const [isLangActive, setIsLangActive] = useState(false);\n\n const wrapperRef = useRef<HTMLSpanElement>(null);\n const elmRef = useRef<HTMLDivElement>(null);\n const langRef = useRef<HTMLSpanElement>(null);\n const closeMenu = useCallback(() => {\n setIsOptionActive(false);\n setIsLangActive(false);\n }, []);\n\n useOnClickOutside(\n [elmRef, wrapperRef],\n isOptionActive ? closeMenu : () => {},\n );\n useOnClickOutside([langRef, wrapperRef], isLangActive ? closeMenu : () => {});\n const { isChatActive } = useContext(ChatContext);\n const { openSearch } = useContext(SearchContext);\n\n return (\n <StyledHeader $hasChildren={children ? true : false} id=\"header\">\n <StyledHeaderInner $hasChildren={children ? true : false}>\n <Link href=\"/\" className=\"logo\" aria-label=\"Logo\">\n {customThemeJson.logo ? (\n <>\n {/* Both logos render; .light-only and .dark-only classes in\n GlobalStyles hide the inactive one based on the \"dark\" class\n on <html>. Avoids a JS-driven swap so no flash on first load. */}\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n className=\"light-only\"\n src={customThemeJson.logo.light}\n alt=\"Logo\"\n width=\"100\"\n height=\"100\"\n />\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n className=\"dark-only\"\n src={customThemeJson.logo.dark}\n alt=\"Logo\"\n width=\"100\"\n height=\"100\"\n />\n </>\n ) : (\n <Logo />\n )}\n </Link>\n {children}\n <StyledLeftWrapper>\n <StyledSearchButton onClick={openSearch} aria-label=\"Search docs\">\n <Search size={14} />\n <SearchKbd>⌘K</SearchKbd>\n </StyledSearchButton>\n {isChatActive && <ChatButtonCTA />}\n </StyledLeftWrapper>\n </StyledHeaderInner>\n </StyledHeader>\n );\n}\n\nexport { Header };\n";
|
|
@@ -4,6 +4,7 @@ import { useCallback, useContext, useRef, useState } from "react";
|
|
|
4
4
|
import styled, { css } from "styled-components";
|
|
5
5
|
import Link from "next/link";
|
|
6
6
|
import { mq, Theme } from "@/app/theme";
|
|
7
|
+
import { focusRing } from "@/components/layout/SharedStyled";
|
|
7
8
|
import { useOnClickOutside } from "cherry-styled-components";
|
|
8
9
|
import { Search } from "lucide-react";
|
|
9
10
|
import { Logo } from "@/components/layout/Pictograms";
|
|
@@ -58,6 +59,8 @@ const StyledHeader = styled.header<{ theme: Theme; $hasChildren: boolean }>\`
|
|
|
58
59
|
|
|
59
60
|
& .logo {
|
|
60
61
|
display: flex;
|
|
62
|
+
min-width: max-content;
|
|
63
|
+
\${focusRing};
|
|
61
64
|
|
|
62
65
|
& svg,
|
|
63
66
|
& img {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const notFoundComponentTemplate = "\"use client\";\nimport styled from \"styled-components\";\nimport { Compass } from \"lucide-react\";\nimport { Theme } from \"@/app/theme\";\nimport { Button } from \"@/components/layout/Button\";\n\nconst StyledWrapper = styled.div<{ theme: Theme }>`\n min-height: 100dvh;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 20px;\n background: ${({ theme }) => theme.colors.light};\n`;\n\nconst StyledCard = styled.div<{ theme: Theme }>`\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 20px;\n width: 100%;\n max-width: 420px;\n padding: 40px 32px;\n background: ${({ theme }) => theme.colors.light};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n box-shadow: ${({ theme }) => theme.shadows.sm};\n text-align: center;\n`;\n\nconst StyledIcon = styled.div<{ theme: Theme }>`\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n border-radius: 50%;\n color: ${({ theme }) => theme.colors.accent};\n background: ${({ theme }) => theme.colors.grayLight};\n\n & svg {\n width: 22px;\n height: 22px;\n }\n`;\n\nconst StyledTitle = styled.h1<{ theme: Theme }>`\n margin: 0;\n font-size: 24px;\n font-weight: 700;\n color: ${({ theme }) => theme.colors.accent};\n`;\n\nconst StyledText = styled.p<{ theme: Theme }>`\n margin: 0;\n font-size: 15px;\n color: ${({ theme }) => theme.colors.grayDark};\n`;\n\nexport function NotFound() {\n return (\n <StyledWrapper>\n <StyledCard>\n <StyledIcon>\n <Compass />\n </StyledIcon>\n <StyledTitle>Error 404</StyledTitle>\n <StyledText>This page could not be found.</StyledText>\n <Button href=\"/\" icon=\"house\">\n Home\n </Button>\n </StyledCard>\n </StyledWrapper>\n );\n}\n";
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export const notFoundComponentTemplate = `"use client";
|
|
2
|
+
import styled from "styled-components";
|
|
3
|
+
import { Compass } from "lucide-react";
|
|
4
|
+
import { Theme } from "@/app/theme";
|
|
5
|
+
import { Button } from "@/components/layout/Button";
|
|
6
|
+
|
|
7
|
+
const StyledWrapper = styled.div<{ theme: Theme }>\`
|
|
8
|
+
min-height: 100dvh;
|
|
9
|
+
display: flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
justify-content: center;
|
|
12
|
+
padding: 20px;
|
|
13
|
+
background: \${({ theme }) => theme.colors.light};
|
|
14
|
+
\`;
|
|
15
|
+
|
|
16
|
+
const StyledCard = styled.div<{ theme: Theme }>\`
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
align-items: center;
|
|
20
|
+
gap: 20px;
|
|
21
|
+
width: 100%;
|
|
22
|
+
max-width: 420px;
|
|
23
|
+
padding: 40px 32px;
|
|
24
|
+
background: \${({ theme }) => theme.colors.light};
|
|
25
|
+
border: solid 1px \${({ theme }) => theme.colors.grayLight};
|
|
26
|
+
border-radius: \${({ theme }) => theme.spacing.radius.lg};
|
|
27
|
+
box-shadow: \${({ theme }) => theme.shadows.sm};
|
|
28
|
+
text-align: center;
|
|
29
|
+
\`;
|
|
30
|
+
|
|
31
|
+
const StyledIcon = styled.div<{ theme: Theme }>\`
|
|
32
|
+
display: inline-flex;
|
|
33
|
+
align-items: center;
|
|
34
|
+
justify-content: center;
|
|
35
|
+
width: 48px;
|
|
36
|
+
height: 48px;
|
|
37
|
+
border-radius: 50%;
|
|
38
|
+
color: \${({ theme }) => theme.colors.accent};
|
|
39
|
+
background: \${({ theme }) => theme.colors.grayLight};
|
|
40
|
+
|
|
41
|
+
& svg {
|
|
42
|
+
width: 22px;
|
|
43
|
+
height: 22px;
|
|
44
|
+
}
|
|
45
|
+
\`;
|
|
46
|
+
|
|
47
|
+
const StyledTitle = styled.h1<{ theme: Theme }>\`
|
|
48
|
+
margin: 0;
|
|
49
|
+
font-size: 24px;
|
|
50
|
+
font-weight: 700;
|
|
51
|
+
color: \${({ theme }) => theme.colors.accent};
|
|
52
|
+
\`;
|
|
53
|
+
|
|
54
|
+
const StyledText = styled.p<{ theme: Theme }>\`
|
|
55
|
+
margin: 0;
|
|
56
|
+
font-size: 15px;
|
|
57
|
+
color: \${({ theme }) => theme.colors.grayDark};
|
|
58
|
+
\`;
|
|
59
|
+
|
|
60
|
+
export function NotFound() {
|
|
61
|
+
return (
|
|
62
|
+
<StyledWrapper>
|
|
63
|
+
<StyledCard>
|
|
64
|
+
<StyledIcon>
|
|
65
|
+
<Compass />
|
|
66
|
+
</StyledIcon>
|
|
67
|
+
<StyledTitle>Error 404</StyledTitle>
|
|
68
|
+
<StyledText>This page could not be found.</StyledText>
|
|
69
|
+
<Button href="/" icon="house">
|
|
70
|
+
Home
|
|
71
|
+
</Button>
|
|
72
|
+
</StyledCard>
|
|
73
|
+
</StyledWrapper>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const sectionBarTemplate = "\"use client\";\nimport { usePathname } from \"next/navigation\";\nimport Link from \"next/link\";\nimport styled from \"styled-components\";\nimport { styledText } from \"cherry-styled-components\";\nimport { mq, Theme } from \"@/app/theme\";\n\ninterface SectionConfig {\n label: string;\n slug: string;\n directory?: string;\n}\n\ninterface SectionBarProps {\n sections: SectionConfig[];\n}\n\nconst StyledSectionBar = styled.nav<{ theme: Theme }>`\n display: flex;\n order: 3;\n width: calc(100% + 20px);\n margin: 0 0 0 -10px;\n padding: 0;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n position: relative;\n\n &::-webkit-scrollbar {\n display: none;\n }\n\n ${mq(\"lg\")} {\n padding: 0 10px;\n order: unset;\n width: 100%;\n margin: 0;\n justify-content: flex-end;\n }\n`;\n\nconst StyledSectionLink = styled(Link)<{\n theme: Theme;\n $isActive: boolean;\n}>`\n ${({ theme }) => styledText(theme)};\n text-decoration: none;\n padding: 16px 10px;\n white-space: nowrap;\n font-weight: ${({ $isActive }) => ($isActive ? \"600\" : \"500\")};\n color: ${({ theme, $isActive }) =>\n $isActive ? theme.colors.primary : theme.colors.gray};\n border-bottom: solid 2px\n ${({ theme, $isActive }) =>\n $isActive ? theme.colors.primary : \"transparent\"};\n transition: all 0.3s ease;\n min-width: fit-content;\n\n &:hover {\n color: ${({ theme }) => theme.colors.primary};\n }\n`;\n\nfunction SectionBar({ sections }: SectionBarProps) {\n const pathname = usePathname();\n const currentPath = pathname.replace(/^\\//, \"\").replace(/\\/$/, \"\");\n\n const activeSection = sections.find((section) => {\n if (section.slug === \"\") return false;\n return (\n currentPath === section.slug || currentPath.startsWith(section.slug + \"/\")\n );\n });\n\n const activeSectionSlug = activeSection ? activeSection.slug : \"\";\n\n return (\n <StyledSectionBar>\n {sections.map((section) => (\n <StyledSectionLink\n key={section.slug}\n href={section.slug === \"\" ? \"/\" : `/${section.slug}`}\n $isActive={activeSectionSlug === section.slug}\n >\n {section.label}\n </StyledSectionLink>\n ))}\n </StyledSectionBar>\n );\n}\n\nexport { SectionBar };\n";
|
|
1
|
+
export declare const sectionBarTemplate = "\"use client\";\nimport { usePathname } from \"next/navigation\";\nimport Link from \"next/link\";\nimport styled from \"styled-components\";\nimport { styledText } from \"cherry-styled-components\";\nimport { mq, Theme } from \"@/app/theme\";\n\ninterface SectionConfig {\n label: string;\n slug: string;\n directory?: string;\n}\n\ninterface SectionBarProps {\n sections: SectionConfig[];\n}\n\nconst StyledSectionBar = styled.nav<{ theme: Theme }>`\n display: flex;\n order: 3;\n width: calc(100% + 20px);\n margin: 0 0 0 -10px;\n padding: 0;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n position: relative;\n\n &::-webkit-scrollbar {\n display: none;\n }\n\n ${mq(\"lg\")} {\n padding: 0 10px;\n order: unset;\n width: 100%;\n margin: 0;\n justify-content: flex-end;\n }\n`;\n\nconst StyledSectionLink = styled(Link)<{\n theme: Theme;\n $isActive: boolean;\n}>`\n ${({ theme }) => styledText(theme)};\n text-decoration: none;\n padding: 16px 10px;\n white-space: nowrap;\n font-weight: ${({ $isActive }) => ($isActive ? \"600\" : \"500\")};\n color: ${({ theme, $isActive }) =>\n $isActive ? theme.colors.primary : theme.colors.gray};\n border-bottom: solid 2px\n ${({ theme, $isActive }) =>\n $isActive ? theme.colors.primary : \"transparent\"};\n transition: all 0.3s ease;\n min-width: fit-content;\n\n /* Inset ring (not the shared outset focusRing): the scrolling section bar\n clips overflow, so an outset glow gets cropped top/bottom. Drawing it inside\n the link's padding keeps the primaryLight focus look fully visible. */\n &:focus-visible {\n outline: none;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n box-shadow: inset 0 0 0 4px ${({ theme }) => theme.colors.primaryLight};\n }\n\n &:hover {\n color: ${({ theme }) => theme.colors.primary};\n }\n`;\n\nfunction SectionBar({ sections }: SectionBarProps) {\n const pathname = usePathname();\n const currentPath = pathname.replace(/^\\//, \"\").replace(/\\/$/, \"\");\n\n const activeSection = sections.find((section) => {\n if (section.slug === \"\") return false;\n return (\n currentPath === section.slug || currentPath.startsWith(section.slug + \"/\")\n );\n });\n\n const activeSectionSlug = activeSection ? activeSection.slug : \"\";\n\n return (\n <StyledSectionBar>\n {sections.map((section) => (\n <StyledSectionLink\n key={section.slug}\n href={section.slug === \"\" ? \"/\" : `/${section.slug}`}\n $isActive={activeSectionSlug === section.slug}\n >\n {section.label}\n </StyledSectionLink>\n ))}\n </StyledSectionBar>\n );\n}\n\nexport { SectionBar };\n";
|
|
@@ -55,6 +55,15 @@ const StyledSectionLink = styled(Link)<{
|
|
|
55
55
|
transition: all 0.3s ease;
|
|
56
56
|
min-width: fit-content;
|
|
57
57
|
|
|
58
|
+
/* Inset ring (not the shared outset focusRing): the scrolling section bar
|
|
59
|
+
clips overflow, so an outset glow gets cropped top/bottom. Drawing it inside
|
|
60
|
+
the link's padding keeps the primaryLight focus look fully visible. */
|
|
61
|
+
&:focus-visible {
|
|
62
|
+
outline: none;
|
|
63
|
+
border-radius: \${({ theme }) => theme.spacing.radius.xs};
|
|
64
|
+
box-shadow: inset 0 0 0 4px \${({ theme }) => theme.colors.primaryLight};
|
|
65
|
+
}
|
|
66
|
+
|
|
58
67
|
&:hover {
|
|
59
68
|
color: \${({ theme }) => theme.colors.primary};
|
|
60
69
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const sharedStyledTemplate = "\"use client\";\nimport { styledSmall, styledText } from \"cherry-styled-components\";\nimport { mq, Theme } from \"@/app/theme\";\nimport styled, { css } from \"styled-components\";\n\n/** Slim, theme-aware scrollbar for internal scroll areas (code blocks,\n modals, tables) so the chunky native bar doesn't stand out, especially\n in dark mode. */\nexport const thinScrollbar = css<{ theme: Theme }>`\n scrollbar-width: thin;\n scrollbar-color: ${({ theme }) => theme.colors.grayLight} transparent;\n`;\n\nexport const interactiveStyles = css<{ theme: Theme }>`\n transition: all 0.3s ease;\n border: solid 1px transparent;\n box-shadow: 0 0 0 0px ${({ theme }) => theme.colors.primary};\n\n &:hover {\n border-color: ${({ theme }) => theme.colors.primary};\n }\n\n &:focus {\n border-color: ${({ theme }) => theme.colors.primary};\n box-shadow: 0 0 0 4px ${({ theme }) => theme.colors.primaryLight};\n }\n\n &:active {\n box-shadow: 0 0 0 2px ${({ theme }) => theme.colors.primaryLight};\n }\n`;\n\nexport const styledAnchor = css<{ theme: Theme }>`\n & a:not([class]):not(:has(img)) {\n color: inherit;\n transition: all 0.3s ease;\n text-decoration: none;\n box-shadow: 0 2px 0 0 ${({ theme }) => theme.colors.primary};\n\n &:hover {\n color: ${({ theme }) => theme.colors.accent};\n box-shadow: 0 1px 0 0 ${({ theme }) => theme.colors.primary};\n }\n }\n`;\n\nexport const stylesLists = css<{ theme: Theme }>`\n & ul,\n & ol {\n & li {\n & > .code-wrapper {\n margin: 10px 0;\n }\n }\n }\n\n & ul {\n list-style: none;\n padding: 0;\n margin: 0;\n\n & li {\n text-indent: 0;\n display: block;\n position: relative;\n padding: 0 0 0 15px;\n margin: 0;\n ${({ theme }) => styledText(theme)};\n min-height: 23px;\n\n ${mq(\"lg\")} {\n min-height: 27px;\n }\n\n &::before {\n content: \"\";\n display: block;\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: ${({ theme }) => theme.colors.primary};\n position: absolute;\n top: 8px;\n left: 2px;\n\n ${mq(\"lg\")} {\n top: 10px;\n }\n }\n }\n }\n\n & ol {\n padding: 0;\n margin: 0;\n\n & ul {\n padding-left: 15px;\n }\n\n & > li {\n position: relative;\n padding: 0;\n counter-increment: item;\n margin: 0;\n ${({ theme }) => styledText(theme)};\n\n &::before {\n content: counter(item) \".\";\n display: inline-block;\n margin: 0 4px 0 0;\n font-weight: 700;\n color: ${({ theme }) => theme.colors.primary};\n min-width: max-content;\n }\n }\n }\n`;\n\nexport const styledTable = css<{ theme: Theme }>`\n & .table-wrapper {\n overflow-x: auto;\n width: 100%;\n ${thinScrollbar};\n }\n\n & table {\n margin: 0;\n padding: 0;\n border-collapse: collapse;\n width: 100%;\n text-align: left;\n\n & tr {\n margin: 0;\n padding: 0;\n }\n\n & th {\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n padding: 10px 10px 10px 0;\n ${({ theme }) => styledSmall(theme)};\n font-weight: 600;\n color: ${({ theme }) => theme.colors.dark};\n }\n\n & td {\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n padding: 10px 10px 10px 0;\n color: ${({ theme }) => theme.colors.grayDark};\n ${({ theme }) => styledSmall(theme)};\n }\n }\n`;\n\nexport const StyledSmallButton = styled.button<{ theme: Theme }>`\n ${interactiveStyles};\n background: ${({ theme }) => theme.colors.light};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n color: ${({ theme }) => theme.colors.accent};\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n padding: 6px 8px;\n font-size: 12px;\n font-family: inherit;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s ease;\n display: flex;\n align-items: center;\n gap: 6px;\n margin-right: -6px;\n\n & svg.lucide {\n color: inherit;\n }\n`;\n";
|
|
1
|
+
export declare const sharedStyledTemplate = "\"use client\";\nimport { styledSmall, styledText } from \"cherry-styled-components\";\nimport { mq, Theme } from \"@/app/theme\";\nimport styled, { css } from \"styled-components\";\n\n/** Slim, theme-aware scrollbar for internal scroll areas (code blocks,\n modals, tables) so the chunky native bar doesn't stand out, especially\n in dark mode. */\nexport const thinScrollbar = css<{ theme: Theme }>`\n scrollbar-width: thin;\n scrollbar-color: ${({ theme }) => theme.colors.grayLight} transparent;\n`;\n\nexport const interactiveStyles = css<{ theme: Theme }>`\n transition: all 0.3s ease;\n border: solid 1px transparent;\n box-shadow: 0 0 0 0px ${({ theme }) => theme.colors.primary};\n\n &:hover {\n border-color: ${({ theme }) => theme.colors.primary};\n }\n\n &:focus {\n border-color: ${({ theme }) => theme.colors.primary};\n box-shadow: 0 0 0 4px ${({ theme }) => theme.colors.primaryLight};\n }\n\n &:active {\n box-shadow: 0 0 0 2px ${({ theme }) => theme.colors.primaryLight};\n }\n`;\n\n/** Keyboard-focus ring for bare navigation links (header links, sidebar rows,\n footer links) that don't carry the full interactiveStyles treatment. Mirrors\n the primaryLight glow from interactiveStyles, but uses :focus-visible so the\n ring only shows for keyboard users - not on mouse click. Pair with an element\n that already has a transition for a smooth fade. The small border-radius\n rounds the ring, so don't apply this to elements that set their own radius. */\nexport const focusRing = css<{ theme: Theme }>`\n &:focus-visible {\n outline: none;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n box-shadow: 0 0 0 4px ${({ theme }) => theme.colors.primaryLight};\n }\n`;\n\nexport const styledAnchor = css<{ theme: Theme }>`\n & a:not([class]):not(:has(img)) {\n color: inherit;\n transition: all 0.3s ease;\n text-decoration: none;\n box-shadow: 0 2px 0 0 ${({ theme }) => theme.colors.primary};\n\n &:hover {\n color: ${({ theme }) => theme.colors.accent};\n box-shadow: 0 1px 0 0 ${({ theme }) => theme.colors.primary};\n }\n\n /* Same primaryLight focus glow as focusRing, but layered over the link's\n own box-shadow underline (same property) so the underline is preserved. */\n &:focus-visible {\n outline: none;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n box-shadow:\n 0 2px 0 0 ${({ theme }) => theme.colors.primary},\n 0 0 0 4px ${({ theme }) => theme.colors.primaryLight};\n }\n }\n`;\n\nexport const stylesLists = css<{ theme: Theme }>`\n & ul,\n & ol {\n & li {\n & > .code-wrapper {\n margin: 10px 0;\n }\n }\n }\n\n & ul {\n list-style: none;\n padding: 0;\n margin: 0;\n\n & li {\n text-indent: 0;\n display: block;\n position: relative;\n padding: 0 0 0 15px;\n margin: 0;\n ${({ theme }) => styledText(theme)};\n min-height: 23px;\n\n ${mq(\"lg\")} {\n min-height: 27px;\n }\n\n &::before {\n content: \"\";\n display: block;\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: ${({ theme }) => theme.colors.primary};\n position: absolute;\n top: 8px;\n left: 2px;\n\n ${mq(\"lg\")} {\n top: 10px;\n }\n }\n }\n }\n\n & ol {\n padding: 0;\n margin: 0;\n\n & ul {\n padding-left: 15px;\n }\n\n & > li {\n position: relative;\n padding: 0;\n counter-increment: item;\n margin: 0;\n ${({ theme }) => styledText(theme)};\n\n &::before {\n content: counter(item) \".\";\n display: inline-block;\n margin: 0 4px 0 0;\n font-weight: 700;\n color: ${({ theme }) => theme.colors.primary};\n min-width: max-content;\n }\n }\n }\n`;\n\nexport const styledTable = css<{ theme: Theme }>`\n & .table-wrapper {\n overflow-x: auto;\n width: 100%;\n ${thinScrollbar};\n }\n\n & table {\n margin: 0;\n padding: 0;\n border-collapse: collapse;\n width: 100%;\n text-align: left;\n\n & tr {\n margin: 0;\n padding: 0;\n }\n\n & th {\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n padding: 10px 10px 10px 0;\n ${({ theme }) => styledSmall(theme)};\n font-weight: 600;\n color: ${({ theme }) => theme.colors.dark};\n }\n\n & td {\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n padding: 10px 10px 10px 0;\n color: ${({ theme }) => theme.colors.grayDark};\n ${({ theme }) => styledSmall(theme)};\n }\n }\n`;\n\nexport const StyledSmallButton = styled.button<{ theme: Theme }>`\n ${interactiveStyles};\n background: ${({ theme }) => theme.colors.light};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n color: ${({ theme }) => theme.colors.accent};\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n padding: 6px 8px;\n font-size: 12px;\n font-family: inherit;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s ease;\n display: flex;\n align-items: center;\n gap: 6px;\n margin-right: -6px;\n\n & svg.lucide {\n color: inherit;\n }\n`;\n";
|
|
@@ -30,6 +30,20 @@ export const interactiveStyles = css<{ theme: Theme }>\`
|
|
|
30
30
|
}
|
|
31
31
|
\`;
|
|
32
32
|
|
|
33
|
+
/** Keyboard-focus ring for bare navigation links (header links, sidebar rows,
|
|
34
|
+
footer links) that don't carry the full interactiveStyles treatment. Mirrors
|
|
35
|
+
the primaryLight glow from interactiveStyles, but uses :focus-visible so the
|
|
36
|
+
ring only shows for keyboard users - not on mouse click. Pair with an element
|
|
37
|
+
that already has a transition for a smooth fade. The small border-radius
|
|
38
|
+
rounds the ring, so don't apply this to elements that set their own radius. */
|
|
39
|
+
export const focusRing = css<{ theme: Theme }>\`
|
|
40
|
+
&:focus-visible {
|
|
41
|
+
outline: none;
|
|
42
|
+
border-radius: \${({ theme }) => theme.spacing.radius.xs};
|
|
43
|
+
box-shadow: 0 0 0 4px \${({ theme }) => theme.colors.primaryLight};
|
|
44
|
+
}
|
|
45
|
+
\`;
|
|
46
|
+
|
|
33
47
|
export const styledAnchor = css<{ theme: Theme }>\`
|
|
34
48
|
& a:not([class]):not(:has(img)) {
|
|
35
49
|
color: inherit;
|
|
@@ -41,6 +55,16 @@ export const styledAnchor = css<{ theme: Theme }>\`
|
|
|
41
55
|
color: \${({ theme }) => theme.colors.accent};
|
|
42
56
|
box-shadow: 0 1px 0 0 \${({ theme }) => theme.colors.primary};
|
|
43
57
|
}
|
|
58
|
+
|
|
59
|
+
/* Same primaryLight focus glow as focusRing, but layered over the link's
|
|
60
|
+
own box-shadow underline (same property) so the underline is preserved. */
|
|
61
|
+
&:focus-visible {
|
|
62
|
+
outline: none;
|
|
63
|
+
border-radius: \${({ theme }) => theme.spacing.radius.xs};
|
|
64
|
+
box-shadow:
|
|
65
|
+
0 2px 0 0 \${({ theme }) => theme.colors.primary},
|
|
66
|
+
0 0 0 4px \${({ theme }) => theme.colors.primaryLight};
|
|
67
|
+
}
|
|
44
68
|
}
|
|
45
69
|
\`;
|
|
46
70
|
|
|
@@ -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 stepsTemplate = "\"use client\";\nimport React from \"react\";\nimport styled, { useTheme } from \"styled-components\";\nimport { styledText } from \"cherry-styled-components\";\nimport { Theme } from \"@/app/theme\";\nimport { Icon, IconProps } from \"@/components/layout/Icon\";\n\nconst StyledStepsContainer = styled.div<{ theme: Theme }>`\n position: relative;\n width: 100%;\n`;\n\nconst StyledStep = styled.div<{ theme: Theme }>`\n background: ${({ theme }) => theme.colors.light};\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n padding: 0 0 20px 52px;\n margin: 0;\n position: relative;\n ${({ theme }) => styledText(theme)}\n color: ${({ theme }) => theme.colors.grayDark};\n\n &::after {\n content: \"\";\n position: absolute;\n left: 16px;\n top: 0;\n width: 1px;\n height: 100%;\n background: ${({ theme }) => theme.colors.primary};\n background: linear-gradient(\n 180deg,\n ${({ theme }) => theme.colors.primary},\n transparent\n );\n border-radius: 4px;\n }\n`;\n\nconst StepNumber = styled.div<{ theme: Theme }>`\n width: 32px;\n height: 32px;\n border-radius: 50%;\n background: ${({ theme }) => theme.colors.primary};\n color: ${({ theme }) => theme.colors.light};\n display: flex;\n align-items: center;\n justify-content: center;\n font-weight: 700;\n margin-bottom: 12px;\n ${({ theme }) => styledText(theme)};\n position: absolute;\n left: 0;\n top: 0;\n z-index: 1;\n`;\n\nconst StyledStepTitle = styled.
|
|
1
|
+
export declare const stepsTemplate = "\"use client\";\nimport React from \"react\";\nimport styled, { useTheme } from \"styled-components\";\nimport { styledText } from \"cherry-styled-components\";\nimport { Theme } from \"@/app/theme\";\nimport { Icon, IconProps } from \"@/components/layout/Icon\";\n\nconst StyledStepsContainer = styled.div<{ theme: Theme }>`\n position: relative;\n width: 100%;\n`;\n\nconst StyledStep = styled.div<{ theme: Theme }>`\n background: ${({ theme }) => theme.colors.light};\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n padding: 0 0 20px 52px;\n margin: 0;\n position: relative;\n ${({ theme }) => styledText(theme)}\n color: ${({ theme }) => theme.colors.grayDark};\n\n &::after {\n content: \"\";\n position: absolute;\n left: 16px;\n top: 0;\n width: 1px;\n height: 100%;\n background: ${({ theme }) => theme.colors.primary};\n background: linear-gradient(\n 180deg,\n ${({ theme }) => theme.colors.primary},\n transparent\n );\n border-radius: 4px;\n }\n`;\n\nconst StepNumber = styled.div<{ theme: Theme }>`\n width: 32px;\n height: 32px;\n border-radius: 50%;\n background: ${({ theme }) => theme.colors.primary};\n color: ${({ theme }) => theme.colors.light};\n display: flex;\n align-items: center;\n justify-content: center;\n font-weight: 700;\n margin-bottom: 12px;\n ${({ theme }) => styledText(theme)};\n position: absolute;\n left: 0;\n top: 0;\n z-index: 1;\n`;\n\nconst StyledStepTitle = styled.div<{ theme: Theme }>`\n margin: 0 0 10px 0;\n padding: 2px 0 0 0;\n color: ${({ theme }) => theme.colors.dark};\n ${({ theme }) => styledText(theme)};\n font-weight: 700;\n display: flex;\n align-items: center;\n gap: 10px;\n`;\n\nconst StepContent = styled.div<{ theme: Theme }>`\n color: ${({ theme }) => theme.colors.grayDark};\n ${({ theme }) => styledText(theme)};\n\n & > .code-wrapper {\n margin: 10px 0;\n }\n`;\n\ninterface StepProps extends React.HTMLAttributes<HTMLDivElement> {\n title: string;\n children: React.ReactNode;\n icon?: IconProps;\n}\n\nfunction Step(_props: StepProps) {\n return null;\n}\n\ninterface StepsProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n}\n\nfunction Steps({ children }: StepsProps) {\n const theme = useTheme() as Theme;\n\n const steps = React.Children.toArray(children).filter(\n (child): child is React.ReactElement<StepProps> =>\n React.isValidElement(child),\n );\n\n return (\n <StyledStepsContainer theme={theme}>\n {steps.map((step, index) => {\n const { title, children: stepContent, icon } = step.props;\n\n return (\n <StyledStep key={index} theme={theme}>\n <StepNumber theme={theme}>{index + 1}</StepNumber>\n <StyledStepTitle theme={theme}>\n {icon && <Icon name={icon} color={theme.colors.primary} />}\n {title}\n </StyledStepTitle>\n <StepContent theme={theme}>{stepContent}</StepContent>\n </StyledStep>\n );\n })}\n </StyledStepsContainer>\n );\n}\n\nexport { Steps, Step };\n";
|
|
@@ -54,11 +54,12 @@ const StepNumber = styled.div<{ theme: Theme }>\`
|
|
|
54
54
|
z-index: 1;
|
|
55
55
|
\`;
|
|
56
56
|
|
|
57
|
-
const StyledStepTitle = styled.
|
|
57
|
+
const StyledStepTitle = styled.div<{ theme: Theme }>\`
|
|
58
58
|
margin: 0 0 10px 0;
|
|
59
59
|
padding: 2px 0 0 0;
|
|
60
60
|
color: \${({ theme }) => theme.colors.dark};
|
|
61
61
|
\${({ theme }) => styledText(theme)};
|
|
62
|
+
font-weight: 700;
|
|
62
63
|
display: flex;
|
|
63
64
|
align-items: center;
|
|
64
65
|
gap: 10px;
|
|
@@ -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";
|