doccupine 0.0.63 → 0.0.65
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/README.md +4 -3
- package/dist/lib/structures.js +4 -0
- package/dist/templates/app/api/rag/route.d.ts +1 -1
- package/dist/templates/app/api/rag/route.js +11 -4
- package/dist/templates/app/theme.d.ts +3 -1
- package/dist/templates/app/theme.js +17 -15
- package/dist/templates/components/Chat.d.ts +1 -1
- package/dist/templates/components/Chat.js +358 -161
- package/dist/templates/components/DocsSideBar.d.ts +1 -1
- package/dist/templates/components/DocsSideBar.js +45 -11
- package/dist/templates/components/LockBodyScroll.d.ts +1 -0
- package/dist/templates/components/LockBodyScroll.js +17 -0
- package/dist/templates/components/SideBar.d.ts +1 -1
- package/dist/templates/components/SideBar.js +15 -2
- package/dist/templates/components/layout/Accordion.d.ts +1 -1
- package/dist/templates/components/layout/Accordion.js +1 -1
- package/dist/templates/components/layout/ActionBar.d.ts +1 -1
- package/dist/templates/components/layout/ActionBar.js +17 -87
- package/dist/templates/components/layout/Callout.d.ts +1 -1
- package/dist/templates/components/layout/Callout.js +1 -1
- package/dist/templates/components/layout/Card.d.ts +1 -1
- package/dist/templates/components/layout/Card.js +26 -7
- package/dist/templates/components/layout/Code.d.ts +1 -1
- package/dist/templates/components/layout/Code.js +1 -1
- package/dist/templates/components/layout/Columns.d.ts +1 -1
- package/dist/templates/components/layout/Columns.js +1 -1
- package/dist/templates/components/layout/DocsComponents.d.ts +1 -1
- package/dist/templates/components/layout/DocsComponents.js +40 -14
- package/dist/templates/components/layout/DocsNavigation.d.ts +1 -1
- package/dist/templates/components/layout/DocsNavigation.js +3 -2
- package/dist/templates/components/layout/Field.d.ts +1 -1
- package/dist/templates/components/layout/Field.js +1 -1
- package/dist/templates/components/layout/Footer.d.ts +1 -1
- package/dist/templates/components/layout/Footer.js +28 -6
- package/dist/templates/components/layout/Header.d.ts +1 -1
- package/dist/templates/components/layout/Header.js +10 -12
- package/dist/templates/components/layout/SharedStyles.d.ts +1 -1
- package/dist/templates/components/layout/SharedStyles.js +26 -2
- package/dist/templates/components/layout/StaticLinks.d.ts +1 -1
- package/dist/templates/components/layout/StaticLinks.js +7 -3
- package/dist/templates/components/layout/Steps.d.ts +1 -1
- package/dist/templates/components/layout/Steps.js +7 -2
- package/dist/templates/components/layout/Tabs.d.ts +1 -1
- package/dist/templates/components/layout/Tabs.js +2 -2
- package/dist/templates/components/layout/Update.d.ts +1 -1
- package/dist/templates/components/layout/Update.js +1 -1
- package/dist/templates/mdx/ai-assistant.mdx.d.ts +1 -1
- package/dist/templates/mdx/ai-assistant.mdx.js +8 -0
- package/dist/templates/mdx/callouts.mdx.d.ts +1 -1
- package/dist/templates/mdx/callouts.mdx.js +6 -2
- package/dist/templates/mdx/cards.mdx.d.ts +1 -1
- package/dist/templates/mdx/cards.mdx.js +19 -3
- package/dist/templates/mdx/columns.mdx.d.ts +1 -1
- package/dist/templates/mdx/columns.mdx.js +2 -2
- package/dist/templates/mdx/commands.mdx.d.ts +1 -1
- package/dist/templates/mdx/commands.mdx.js +10 -2
- package/dist/templates/mdx/components.mdx.d.ts +1 -0
- package/dist/templates/mdx/components.mdx.js +56 -0
- package/dist/templates/mdx/deployment.mdx.d.ts +1 -1
- package/dist/templates/mdx/deployment.mdx.js +1 -1
- package/dist/templates/mdx/globals.mdx.d.ts +1 -1
- package/dist/templates/mdx/globals.mdx.js +5 -0
- package/dist/templates/mdx/index.mdx.d.ts +1 -1
- package/dist/templates/mdx/index.mdx.js +5 -5
- package/dist/templates/mdx/model-context-protocol.mdx.d.ts +1 -1
- package/dist/templates/mdx/model-context-protocol.mdx.js +2 -2
- package/dist/templates/mdx/navigation.mdx.d.ts +1 -1
- package/dist/templates/mdx/navigation.mdx.js +1 -1
- package/dist/templates/mdx/platform/ai-assistant.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/ai-assistant.mdx.js +20 -0
- package/dist/templates/mdx/platform/external-links.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/external-links.mdx.js +2 -0
- package/dist/templates/mdx/platform/fonts-settings.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/fonts-settings.mdx.js +8 -5
- package/dist/templates/mdx/platform/index.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/index.mdx.js +10 -1
- package/dist/templates/mdx/platform/site-settings.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/site-settings.mdx.js +4 -4
- package/dist/templates/mdx/sections.mdx.d.ts +1 -1
- package/dist/templates/mdx/sections.mdx.js +2 -2
- package/dist/templates/mdx/steps.mdx.d.ts +1 -1
- package/dist/templates/mdx/steps.mdx.js +4 -0
- package/dist/templates/mdx/tabs.mdx.d.ts +1 -1
- package/dist/templates/mdx/tabs.mdx.js +1 -1
- package/dist/templates/package.js +5 -5
- package/dist/templates/services/llm/types.d.ts +1 -1
- package/dist/templates/services/llm/types.js +1 -1
- package/dist/templates/services/mcp/server.d.ts +1 -1
- package/dist/templates/services/mcp/server.js +1 -1
- package/dist/templates/services/mcp/tools.d.ts +1 -1
- package/dist/templates/services/mcp/tools.js +1 -5
- package/dist/templates/utils/config.d.ts +1 -1
- package/dist/templates/utils/config.js +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const docsSideBarTemplate = "\"use client\";\nimport { useCallback, useEffect, useState } from \"react\";\nimport { Space } from \"cherry-styled-components\";\nimport {\n StyledIndexSidebar,\n StyledIndexSidebarLink,\n StyledIndexSidebarLabel,\n} from \"@/components/layout/DocsComponents\";\n\
|
|
1
|
+
export declare const docsSideBarTemplate = "\"use client\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { Space } from \"cherry-styled-components\";\nimport {\n StyledIndexSidebar,\n StyledIndexSidebarLink,\n StyledIndexSidebarLabel,\n StyledIndexSidebarLi,\n} from \"@/components/layout/DocsComponents\";\n\ninterface Heading {\n id: string;\n text: string;\n level: number;\n}\n\nconst FALLBACK_OFFSET = 60;\n\nfunction getOffset() {\n const header = document.getElementById(\"header\");\n return (header ? header.offsetHeight : FALLBACK_OFFSET) + 20;\n}\n\nexport function DocsSideBar({ headings }: { headings: Heading[] }) {\n const [activeId, setActiveId] = useState<string>(\"\");\n const activeRef = useRef<HTMLLIElement>(null);\n\n const handleScroll = useCallback(() => {\n if (headings.length === 0) return;\n\n const headingElements = headings\n .map((heading) => document.getElementById(heading.id))\n .filter((el): el is HTMLElement => el !== null);\n\n if (headingElements.length === 0) return;\n\n const windowHeight = window.innerHeight;\n\n const visibleHeadings = headingElements.filter((element) => {\n const rect = element.getBoundingClientRect();\n const elementTop = rect.top;\n const elementBottom = rect.bottom;\n return elementTop < windowHeight && elementBottom > -50;\n });\n\n if (visibleHeadings.length > 0) {\n let closestHeading = visibleHeadings[0];\n let closestDistance = Math.abs(\n closestHeading.getBoundingClientRect().top - getOffset(),\n );\n for (const heading of visibleHeadings) {\n const distance = Math.abs(\n heading.getBoundingClientRect().top - getOffset(),\n );\n if (\n distance < closestDistance &&\n heading.getBoundingClientRect().top <= windowHeight * 0.3\n ) {\n closestDistance = distance;\n closestHeading = heading;\n }\n }\n setActiveId(closestHeading.id);\n return;\n }\n\n let currentActiveId = headings[0].id;\n for (const element of headingElements) {\n const rect = element.getBoundingClientRect();\n if (rect.top <= getOffset()) {\n currentActiveId = element.id;\n } else {\n break;\n }\n }\n setActiveId(currentActiveId);\n }, [headings]);\n\n useEffect(() => {\n if (headings.length === 0) return;\n // Set active heading from URL hash immediately on mount\n if (window.location.hash) {\n setActiveId(window.location.hash.slice(1));\n }\n // Run initial scroll check on next frame to avoid synchronous setState in effect\n const rafId = requestAnimationFrame(handleScroll);\n // Re-check after browser finishes scrolling to hash target on new tab/page load\n const delayedId = setTimeout(handleScroll, 300);\n let timeoutId: NodeJS.Timeout;\n const throttledHandleScroll = () => {\n clearTimeout(timeoutId);\n timeoutId = setTimeout(handleScroll, 50);\n };\n window.addEventListener(\"scroll\", throttledHandleScroll);\n window.addEventListener(\"resize\", handleScroll);\n return () => {\n window.removeEventListener(\"scroll\", throttledHandleScroll);\n window.removeEventListener(\"resize\", handleScroll);\n cancelAnimationFrame(rafId);\n clearTimeout(delayedId);\n clearTimeout(timeoutId);\n };\n }, [handleScroll, headings]);\n\n useEffect(() => {\n const el = activeRef.current;\n const container = el?.closest(\"[data-sidebar]\") as HTMLElement | null;\n if (!el || !container) return;\n const elRect = el.getBoundingClientRect();\n const cRect = container.getBoundingClientRect();\n const pad = 140;\n if (elRect.bottom + pad > cRect.bottom) {\n container.scrollBy({\n top: elRect.bottom - cRect.bottom + pad,\n behavior: \"smooth\",\n });\n } else if (elRect.top - pad < cRect.top) {\n container.scrollBy({\n top: elRect.top - cRect.top - pad,\n behavior: \"smooth\",\n });\n }\n }, [activeId]);\n\n const handleHeadingClick = (headingId: string) => {\n const element = document.getElementById(headingId);\n if (element) {\n const elementPosition =\n element.getBoundingClientRect().top + window.scrollY;\n window.scrollTo({\n top: elementPosition - getOffset(),\n behavior: \"smooth\",\n });\n }\n };\n\n return (\n <StyledIndexSidebar data-sidebar>\n {headings?.length > 0 && (\n <>\n <StyledIndexSidebarLabel>On this page</StyledIndexSidebarLabel>\n <Space $size={15} />\n </>\n )}\n {headings.map((heading, index) => (\n <StyledIndexSidebarLi\n key={index}\n ref={activeId === heading.id ? activeRef : null}\n $isActive={activeId === heading.id}\n style={{ paddingLeft: `${(heading.level - 1) * 16}px` }}\n >\n <StyledIndexSidebarLink\n href={`#${heading.id}`}\n onClick={(e) => {\n e.preventDefault();\n handleHeadingClick(heading.id);\n }}\n $isActive={activeId === heading.id}\n >\n {heading.text}\n </StyledIndexSidebarLink>\n </StyledIndexSidebarLi>\n ))}\n </StyledIndexSidebar>\n );\n}\n";
|
|
@@ -1,22 +1,29 @@
|
|
|
1
1
|
export const docsSideBarTemplate = `"use client";
|
|
2
|
-
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
3
|
import { Space } from "cherry-styled-components";
|
|
4
4
|
import {
|
|
5
5
|
StyledIndexSidebar,
|
|
6
6
|
StyledIndexSidebarLink,
|
|
7
7
|
StyledIndexSidebarLabel,
|
|
8
|
+
StyledIndexSidebarLi,
|
|
8
9
|
} from "@/components/layout/DocsComponents";
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
interface Heading {
|
|
11
12
|
id: string;
|
|
12
13
|
text: string;
|
|
13
14
|
level: number;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
const
|
|
17
|
+
const FALLBACK_OFFSET = 60;
|
|
18
|
+
|
|
19
|
+
function getOffset() {
|
|
20
|
+
const header = document.getElementById("header");
|
|
21
|
+
return (header ? header.offsetHeight : FALLBACK_OFFSET) + 20;
|
|
22
|
+
}
|
|
17
23
|
|
|
18
24
|
export function DocsSideBar({ headings }: { headings: Heading[] }) {
|
|
19
25
|
const [activeId, setActiveId] = useState<string>("");
|
|
26
|
+
const activeRef = useRef<HTMLLIElement>(null);
|
|
20
27
|
|
|
21
28
|
const handleScroll = useCallback(() => {
|
|
22
29
|
if (headings.length === 0) return;
|
|
@@ -39,10 +46,12 @@ export function DocsSideBar({ headings }: { headings: Heading[] }) {
|
|
|
39
46
|
if (visibleHeadings.length > 0) {
|
|
40
47
|
let closestHeading = visibleHeadings[0];
|
|
41
48
|
let closestDistance = Math.abs(
|
|
42
|
-
closestHeading.getBoundingClientRect().top -
|
|
49
|
+
closestHeading.getBoundingClientRect().top - getOffset(),
|
|
43
50
|
);
|
|
44
51
|
for (const heading of visibleHeadings) {
|
|
45
|
-
const distance = Math.abs(
|
|
52
|
+
const distance = Math.abs(
|
|
53
|
+
heading.getBoundingClientRect().top - getOffset(),
|
|
54
|
+
);
|
|
46
55
|
if (
|
|
47
56
|
distance < closestDistance &&
|
|
48
57
|
heading.getBoundingClientRect().top <= windowHeight * 0.3
|
|
@@ -58,7 +67,7 @@ export function DocsSideBar({ headings }: { headings: Heading[] }) {
|
|
|
58
67
|
let currentActiveId = headings[0].id;
|
|
59
68
|
for (const element of headingElements) {
|
|
60
69
|
const rect = element.getBoundingClientRect();
|
|
61
|
-
if (rect.top <=
|
|
70
|
+
if (rect.top <= getOffset()) {
|
|
62
71
|
currentActiveId = element.id;
|
|
63
72
|
} else {
|
|
64
73
|
break;
|
|
@@ -93,26 +102,51 @@ export function DocsSideBar({ headings }: { headings: Heading[] }) {
|
|
|
93
102
|
};
|
|
94
103
|
}, [handleScroll, headings]);
|
|
95
104
|
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
const el = activeRef.current;
|
|
107
|
+
const container = el?.closest("[data-sidebar]") as HTMLElement | null;
|
|
108
|
+
if (!el || !container) return;
|
|
109
|
+
const elRect = el.getBoundingClientRect();
|
|
110
|
+
const cRect = container.getBoundingClientRect();
|
|
111
|
+
const pad = 140;
|
|
112
|
+
if (elRect.bottom + pad > cRect.bottom) {
|
|
113
|
+
container.scrollBy({
|
|
114
|
+
top: elRect.bottom - cRect.bottom + pad,
|
|
115
|
+
behavior: "smooth",
|
|
116
|
+
});
|
|
117
|
+
} else if (elRect.top - pad < cRect.top) {
|
|
118
|
+
container.scrollBy({
|
|
119
|
+
top: elRect.top - cRect.top - pad,
|
|
120
|
+
behavior: "smooth",
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}, [activeId]);
|
|
124
|
+
|
|
96
125
|
const handleHeadingClick = (headingId: string) => {
|
|
97
126
|
const element = document.getElementById(headingId);
|
|
98
127
|
if (element) {
|
|
99
128
|
const elementPosition =
|
|
100
129
|
element.getBoundingClientRect().top + window.scrollY;
|
|
101
|
-
window.scrollTo({
|
|
130
|
+
window.scrollTo({
|
|
131
|
+
top: elementPosition - getOffset(),
|
|
132
|
+
behavior: "smooth",
|
|
133
|
+
});
|
|
102
134
|
}
|
|
103
135
|
};
|
|
104
136
|
|
|
105
137
|
return (
|
|
106
|
-
<StyledIndexSidebar>
|
|
138
|
+
<StyledIndexSidebar data-sidebar>
|
|
107
139
|
{headings?.length > 0 && (
|
|
108
140
|
<>
|
|
109
141
|
<StyledIndexSidebarLabel>On this page</StyledIndexSidebarLabel>
|
|
110
|
-
<Space $size={
|
|
142
|
+
<Space $size={15} />
|
|
111
143
|
</>
|
|
112
144
|
)}
|
|
113
145
|
{headings.map((heading, index) => (
|
|
114
|
-
<
|
|
146
|
+
<StyledIndexSidebarLi
|
|
115
147
|
key={index}
|
|
148
|
+
ref={activeId === heading.id ? activeRef : null}
|
|
149
|
+
$isActive={activeId === heading.id}
|
|
116
150
|
style={{ paddingLeft: \`\${(heading.level - 1) * 16}px\` }}
|
|
117
151
|
>
|
|
118
152
|
<StyledIndexSidebarLink
|
|
@@ -125,7 +159,7 @@ export function DocsSideBar({ headings }: { headings: Heading[] }) {
|
|
|
125
159
|
>
|
|
126
160
|
{heading.text}
|
|
127
161
|
</StyledIndexSidebarLink>
|
|
128
|
-
</
|
|
162
|
+
</StyledIndexSidebarLi>
|
|
129
163
|
))}
|
|
130
164
|
</StyledIndexSidebar>
|
|
131
165
|
);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const lockBodyScrollTemplate = "import { useEffect } from \"react\";\n\nexport function useLockBodyScroll(isActive: boolean) {\n useEffect(() => {\n const mql = window.matchMedia(\"(max-width: 992px)\");\n function update() {\n document.body.style.overflow = isActive && mql.matches ? \"hidden\" : \"\";\n }\n update();\n mql.addEventListener(\"change\", update);\n return () => {\n mql.removeEventListener(\"change\", update);\n document.body.style.overflow = \"\";\n };\n }, [isActive]);\n}\n";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const lockBodyScrollTemplate = `import { useEffect } from "react";
|
|
2
|
+
|
|
3
|
+
export function useLockBodyScroll(isActive: boolean) {
|
|
4
|
+
useEffect(() => {
|
|
5
|
+
const mql = window.matchMedia("(max-width: 992px)");
|
|
6
|
+
function update() {
|
|
7
|
+
document.body.style.overflow = isActive && mql.matches ? "hidden" : "";
|
|
8
|
+
}
|
|
9
|
+
update();
|
|
10
|
+
mql.addEventListener("change", update);
|
|
11
|
+
return () => {
|
|
12
|
+
mql.removeEventListener("change", update);
|
|
13
|
+
document.body.style.overflow = "";
|
|
14
|
+
};
|
|
15
|
+
}, [isActive]);
|
|
16
|
+
}
|
|
17
|
+
`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const sideBarTemplate = "\"use client\";\nimport { useContext, useState } from \"react\";\nimport { usePathname } from \"next/navigation\";\nimport { Space } from \"cherry-styled-components\";\nimport {\n DocsSidebar,\n SectionBarContext,\n StyledSidebar,\n StyledSidebarList,\n StyledSidebarListItem,\n StyledStrong,\n StyledSidebarListItemLink,\n StyleMobileBar,\n StyledMobileBurger,\n} from \"@/components/layout/DocsComponents\";\n\ntype NavItem = {\n label: string;\n links: NavItemLink[];\n};\n\ntype NavItemLink = {\n slug: string;\n title: string;\n};\n\ninterface SideBarProps {\n result: NavItem[];\n}\n\nfunction SideBar({ result }: SideBarProps) {\n const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);\n const hasSectionBar = useContext(SectionBarContext);\n const pathname = usePathname();\n\n return (\n <DocsSidebar>\n <StyleMobileBar\n onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}\n $isActive={isMobileMenuOpen}\n >\n <StyledMobileBurger $isActive={isMobileMenuOpen} />\n </StyleMobileBar>\n\n <StyledSidebar\n $isActive={isMobileMenuOpen}\n $hasSectionBar={hasSectionBar}\n >\n {result &&\n result.map((item: NavItem, index: number) => {\n return (\n <StyledSidebarList key={index}>\n <StyledSidebarListItem>\n <StyledStrong>{item.label}</StyledStrong>{\" \"}\n </StyledSidebarListItem>\n <li>\n <Space $size={20} />\n </li>\n {item.links &&\n item.links.map((link: NavItemLink, indexChild: number) => {\n return (\n <StyledSidebarListItem key={indexChild}>\n <StyledSidebarListItemLink\n href={`/${link.slug}`}\n $isActive={pathname === `/${link.slug}`}\n onClick={() => setIsMobileMenuOpen(false)}\n >\n {link.title}\n </StyledSidebarListItemLink>\n </StyledSidebarListItem>\n );\n })}\n <Space $size={20} />\n </StyledSidebarList>\n );\n })}\n </StyledSidebar>\n </DocsSidebar>\n );\n}\n\nexport { SideBar };\n";
|
|
1
|
+
export declare const sideBarTemplate = "\"use client\";\nimport { useContext, useState, Suspense } from \"react\";\nimport { usePathname } from \"next/navigation\";\nimport { Flex, Space } from \"cherry-styled-components\";\nimport {\n DocsSidebar,\n SectionBarContext,\n StyledSidebar,\n StyledSidebarList,\n StyledSidebarListItem,\n StyledStrong,\n StyledSidebarListItemLink,\n StyleMobileBar,\n StyledMobileBurger,\n} from \"@/components/layout/DocsComponents\";\nimport {\n ToggleTheme,\n ToggleThemeLoading,\n} from \"@/components/layout/ThemeToggle\";\nimport { useLockBodyScroll } from \"@/components/LockBodyScroll\";\n\ntype NavItem = {\n label: string;\n links: NavItemLink[];\n};\n\ntype NavItemLink = {\n slug: string;\n title: string;\n};\n\ninterface SideBarProps {\n result: NavItem[];\n}\n\nfunction SideBar({ result }: SideBarProps) {\n const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);\n const hasSectionBar = useContext(SectionBarContext);\n const pathname = usePathname();\n\n useLockBodyScroll(isMobileMenuOpen);\n\n return (\n <DocsSidebar>\n <StyleMobileBar\n onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}\n $isActive={isMobileMenuOpen}\n >\n <StyledMobileBurger $isActive={isMobileMenuOpen} />\n </StyleMobileBar>\n\n <StyledSidebar\n $isActive={isMobileMenuOpen}\n $hasSectionBar={hasSectionBar}\n >\n {result &&\n result.map((item: NavItem, index: number) => {\n return (\n <StyledSidebarList key={index}>\n <StyledSidebarListItem>\n <StyledStrong>{item.label}</StyledStrong>{\" \"}\n </StyledSidebarListItem>\n <li>\n <Space $size={20} />\n </li>\n {item.links &&\n item.links.map((link: NavItemLink, indexChild: number) => {\n return (\n <StyledSidebarListItem key={indexChild}>\n <StyledSidebarListItemLink\n href={`/${link.slug}`}\n $isActive={pathname === `/${link.slug}`}\n onClick={() => setIsMobileMenuOpen(false)}\n >\n {link.title}\n </StyledSidebarListItemLink>\n </StyledSidebarListItem>\n );\n })}\n <Space $size={20} />\n </StyledSidebarList>\n );\n })}\n <Space $xs={40} $lg={20} />\n <Flex $xsJustifyContent=\"flex-start\" $lgJustifyContent=\"flex-end\">\n <Suspense fallback={<ToggleThemeLoading />}>\n <ToggleTheme />\n </Suspense>\n </Flex>\n </StyledSidebar>\n </DocsSidebar>\n );\n}\n\nexport { SideBar };\n";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const sideBarTemplate = `"use client";
|
|
2
|
-
import { useContext, useState } from "react";
|
|
2
|
+
import { useContext, useState, Suspense } from "react";
|
|
3
3
|
import { usePathname } from "next/navigation";
|
|
4
|
-
import { Space } from "cherry-styled-components";
|
|
4
|
+
import { Flex, Space } from "cherry-styled-components";
|
|
5
5
|
import {
|
|
6
6
|
DocsSidebar,
|
|
7
7
|
SectionBarContext,
|
|
@@ -13,6 +13,11 @@ import {
|
|
|
13
13
|
StyleMobileBar,
|
|
14
14
|
StyledMobileBurger,
|
|
15
15
|
} from "@/components/layout/DocsComponents";
|
|
16
|
+
import {
|
|
17
|
+
ToggleTheme,
|
|
18
|
+
ToggleThemeLoading,
|
|
19
|
+
} from "@/components/layout/ThemeToggle";
|
|
20
|
+
import { useLockBodyScroll } from "@/components/LockBodyScroll";
|
|
16
21
|
|
|
17
22
|
type NavItem = {
|
|
18
23
|
label: string;
|
|
@@ -33,6 +38,8 @@ function SideBar({ result }: SideBarProps) {
|
|
|
33
38
|
const hasSectionBar = useContext(SectionBarContext);
|
|
34
39
|
const pathname = usePathname();
|
|
35
40
|
|
|
41
|
+
useLockBodyScroll(isMobileMenuOpen);
|
|
42
|
+
|
|
36
43
|
return (
|
|
37
44
|
<DocsSidebar>
|
|
38
45
|
<StyleMobileBar
|
|
@@ -74,6 +81,12 @@ function SideBar({ result }: SideBarProps) {
|
|
|
74
81
|
</StyledSidebarList>
|
|
75
82
|
);
|
|
76
83
|
})}
|
|
84
|
+
<Space $xs={40} $lg={20} />
|
|
85
|
+
<Flex $xsJustifyContent="flex-start" $lgJustifyContent="flex-end">
|
|
86
|
+
<Suspense fallback={<ToggleThemeLoading />}>
|
|
87
|
+
<ToggleTheme />
|
|
88
|
+
</Suspense>
|
|
89
|
+
</Flex>
|
|
77
90
|
</StyledSidebar>
|
|
78
91
|
</DocsSidebar>
|
|
79
92
|
);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const accordionTemplate = "\"use client\";\nimport { useState } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { styledText, Theme } from \"cherry-styled-components\";\nimport { Icon } from \"@/components/layout/Icon\";\n\nconst StyledAccordion = styled.div<{ theme: Theme }>`\n background: ${({ theme }) => theme.colors.light};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n padding: 20px;\n margin: 0;\n ${({ theme }) => styledText(theme)};\n width: 100%;\n`;\n\nconst StyledAccordionTitle = styled.h3<{ theme: Theme; $isOpen: boolean }>`\n cursor: pointer;\n margin: 0;\n padding: 0 40px 0 0;\n ${({ theme }) => styledText(theme)};\n color: ${({ theme }) => theme.colors.primary};\n transition: color 0.3s ease;\n position: relative;\n\n &:hover {\n color: ${({ theme }) => theme.colors.primaryDark};\n }\n\n & .lucide-chevron-down {\n position: absolute;\n top: 50%;\n transform: translateY(-50%);\n right: 0;\n transition: transform 0.3s ease;\n\n ${({ $isOpen }) =>\n $isOpen &&\n css`\n transform: translateY(-50%) rotate(180deg);\n `}\n }\n`;\n\nconst StyledAccordionContent = styled.div<{ theme: Theme; $isOpen: boolean }>`\n ${({ theme }) => styledText(theme)};\n color: ${({ theme }) => theme.colors.grayDark};\n height: 0;\n overflow: clip;\n transition: all 0.3s ease;\n display: flex;\n flex-direction: column;\n gap: 20px;\n flex-wrap: wrap;\n flex: 1;\n\n ${({ $isOpen }) =>\n $isOpen &&\n css`\n margin: 20px 0 0;\n height: auto;\n `}\n`;\n\
|
|
1
|
+
export declare const accordionTemplate = "\"use client\";\nimport { useState } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { styledText, Theme } from \"cherry-styled-components\";\nimport { Icon } from \"@/components/layout/Icon\";\n\nconst StyledAccordion = styled.div<{ theme: Theme }>`\n background: ${({ theme }) => theme.colors.light};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n padding: 20px;\n margin: 0;\n ${({ theme }) => styledText(theme)};\n width: 100%;\n`;\n\nconst StyledAccordionTitle = styled.h3<{ theme: Theme; $isOpen: boolean }>`\n cursor: pointer;\n margin: 0;\n padding: 0 40px 0 0;\n ${({ theme }) => styledText(theme)};\n color: ${({ theme }) => theme.colors.primary};\n transition: color 0.3s ease;\n position: relative;\n\n &:hover {\n color: ${({ theme }) => theme.colors.primaryDark};\n }\n\n & .lucide-chevron-down {\n position: absolute;\n top: 50%;\n transform: translateY(-50%);\n right: 0;\n transition: transform 0.3s ease;\n\n ${({ $isOpen }) =>\n $isOpen &&\n css`\n transform: translateY(-50%) rotate(180deg);\n `}\n }\n`;\n\nconst StyledAccordionContent = styled.div<{ theme: Theme; $isOpen: boolean }>`\n ${({ theme }) => styledText(theme)};\n color: ${({ theme }) => theme.colors.grayDark};\n height: 0;\n overflow: clip;\n transition: all 0.3s ease;\n display: flex;\n flex-direction: column;\n gap: 20px;\n flex-wrap: wrap;\n flex: 1;\n\n ${({ $isOpen }) =>\n $isOpen &&\n css`\n margin: 20px 0 0;\n height: auto;\n `}\n`;\n\ninterface AccordionProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n title: string;\n}\n\nfunction Accordion({ children, title }: AccordionProps) {\n const [isOpen, setIsOpen] = useState(false);\n\n return (\n <StyledAccordion>\n <StyledAccordionTitle\n onClick={() => setIsOpen(!isOpen)}\n $isOpen={isOpen}\n role=\"button\"\n aria-expanded={isOpen}\n >\n {title} <Icon name=\"ChevronDown\" />\n </StyledAccordionTitle>\n <StyledAccordionContent $isOpen={isOpen}>\n {children}\n </StyledAccordionContent>\n </StyledAccordion>\n );\n}\n\nexport { Accordion };\n";
|
|
@@ -62,7 +62,7 @@ const StyledAccordionContent = styled.div<{ theme: Theme; $isOpen: boolean }>\`
|
|
|
62
62
|
\`}
|
|
63
63
|
\`;
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
interface AccordionProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
66
66
|
children: React.ReactNode;
|
|
67
67
|
title: string;
|
|
68
68
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const actionBarTemplate = "\"use client\";\nimport { useContext, useState } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { Icon } from \"@/components/layout/Icon\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { rgba } from \"polished\";\nimport { resetButton, Textarea } from \"cherry-styled-components\";\nimport {
|
|
1
|
+
export declare const actionBarTemplate = "\"use client\";\nimport { useContext, useState } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { Icon } from \"@/components/layout/Icon\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { rgba } from \"polished\";\nimport { resetButton, Textarea } from \"cherry-styled-components\";\nimport { SectionBarContext } from \"@/components/layout/DocsComponents\";\nimport { StyledSmallButton } from \"@/components/layout/SharedStyled\";\n\ninterface ActionBarProps {\n children: React.ReactNode;\n content: string;\n}\n\nconst StyledActionBar = styled.div<{\n theme: Theme;\n $isChatOpen?: boolean;\n}>`\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n left: 0;\n padding: 12px 0;\n display: flex;\n justify-content: space-between;\n width: 100%;\n max-width: 640px;\n margin: auto;\n transition: all 0.3s ease;\n\n ${mq(\"lg\")} {\n padding: 12px 0;\n }\n`;\n\nconst StyledActionBarContent = styled.div`\n margin: auto 0;\n`;\n\nconst StyledCopyButton = styled(StyledSmallButton)<{\n theme: Theme;\n $copied: boolean;\n}>`\n border: solid 1px\n ${({ theme, $copied }) =>\n $copied ? theme.colors.success : theme.colors.grayLight};\n color: ${({ theme, $copied }) =>\n $copied ? theme.colors.success : theme.colors.primary};\n\n & svg.lucide {\n color: ${({ theme, $copied }) =>\n $copied ? theme.colors.success : theme.colors.primary};\n }\n`;\n\nconst StyledToggle = styled.button<{ theme: Theme; $isActive?: boolean }>`\n ${resetButton}\n width: 56px;\n height: 32px;\n border-radius: 30px;\n display: flex;\n position: relative;\n margin: auto 0;\n transform: scale(1);\n background: ${({ theme }) => theme.colors.light};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n\n &::after {\n content: \"\";\n position: absolute;\n top: 3px;\n left: 3px;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: ${({ theme }) => rgba(theme.colors.primaryLight, 0.2)};\n transition: all 0.3s ease;\n z-index: 1;\n ${({ $isActive }) =>\n !$isActive &&\n css`\n transform: translateX(24px);\n `}\n }\n\n & svg {\n width: 16px;\n height: 16px;\n object-fit: contain;\n margin: auto;\n transition: all 0.3s ease;\n position: relative;\n z-index: 2;\n }\n\n & .lucide-eye {\n transform: translateX(1px);\n }\n\n & .lucide-code-xml {\n transform: translateX(-1px);\n }\n\n & svg[stroke] {\n stroke: ${({ theme }) => theme.colors.primary};\n }\n\n &:hover {\n transform: scale(1.05);\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};\n\n & svg[stroke] {\n stroke: ${({ theme }) =>\n theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};\n }\n }\n\n &:active {\n transform: scale(0.97);\n }\n`;\n\nconst StyledContent = styled.div<{\n theme: Theme;\n $hasSectionBar?: boolean;\n}>`\n padding-top: 20px;\n transition: all 0.3s ease;\n\n & textarea {\n max-width: 640px;\n margin: auto;\n width: 100%;\n height: 100%;\n min-height: calc(\n 100vh - ${({ $hasSectionBar }) => ($hasSectionBar ? 202 : 160)}px\n );\n\n ${mq(\"lg\")} {\n min-height: calc(100vh - 159px);\n }\n }\n`;\n\nfunction ActionBar({ children, content }: ActionBarProps) {\n const [isView, setIsView] = useState(true);\n const [copied, setCopied] = useState(false);\n const hasSectionBar = useContext(SectionBarContext);\n\n const handleCopyContent = async () => {\n try {\n await navigator.clipboard.writeText(content);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n } catch (err) {\n console.error(\"Failed to copy:\", err);\n }\n };\n\n return (\n <>\n <StyledActionBar>\n <StyledCopyButton onClick={handleCopyContent} $copied={copied}>\n {copied ? (\n <>\n <Icon name=\"check\" size={16} />\n Copied!\n </>\n ) : (\n <>\n <Icon name=\"copy\" size={16} />\n Copy content\n </>\n )}\n </StyledCopyButton>\n <StyledActionBarContent>\n <StyledToggle\n onClick={() => setIsView(!isView)}\n aria-label=\"Toggle Theme\"\n $isActive={isView}\n >\n <Icon name=\"Eye\" />\n <Icon name=\"CodeXml\" />\n </StyledToggle>\n </StyledActionBarContent>\n </StyledActionBar>\n {isView && (\n <StyledContent $hasSectionBar={hasSectionBar}>{children}</StyledContent>\n )}\n {!isView && (\n <StyledContent $hasSectionBar={hasSectionBar}>\n <Textarea defaultValue={content} $fullWidth />\n </StyledContent>\n )}\n </>\n );\n}\n\nexport { ActionBar };\n";
|
|
@@ -5,8 +5,8 @@ import { Icon } from "@/components/layout/Icon";
|
|
|
5
5
|
import { mq, Theme } from "@/app/theme";
|
|
6
6
|
import { rgba } from "polished";
|
|
7
7
|
import { resetButton, Textarea } from "cherry-styled-components";
|
|
8
|
-
import { ChatContext } from "@/components/Chat";
|
|
9
8
|
import { SectionBarContext } from "@/components/layout/DocsComponents";
|
|
9
|
+
import { StyledSmallButton } from "@/components/layout/SharedStyled";
|
|
10
10
|
|
|
11
11
|
interface ActionBarProps {
|
|
12
12
|
children: React.ReactNode;
|
|
@@ -17,28 +17,18 @@ const StyledActionBar = styled.div<{
|
|
|
17
17
|
theme: Theme;
|
|
18
18
|
$isChatOpen?: boolean;
|
|
19
19
|
}>\`
|
|
20
|
-
position: absolute;
|
|
21
20
|
border-bottom: solid 1px \${({ theme }) => theme.colors.grayLight};
|
|
22
21
|
left: 0;
|
|
23
|
-
padding: 0
|
|
22
|
+
padding: 12px 0;
|
|
24
23
|
display: flex;
|
|
25
24
|
justify-content: space-between;
|
|
26
25
|
width: 100%;
|
|
26
|
+
max-width: 640px;
|
|
27
|
+
margin: auto;
|
|
27
28
|
transition: all 0.3s ease;
|
|
28
29
|
|
|
29
30
|
\${mq("lg")} {
|
|
30
|
-
|
|
31
|
-
transform: translateX(-50%);
|
|
32
|
-
max-width: calc(100vw - 640px);
|
|
33
|
-
width: 100%;
|
|
34
|
-
padding: 0 20px 20px;
|
|
35
|
-
margin: 0;
|
|
36
|
-
|
|
37
|
-
\${({ $isChatOpen }) =>
|
|
38
|
-
$isChatOpen &&
|
|
39
|
-
css\`
|
|
40
|
-
padding: 0 120px 20px 20px;
|
|
41
|
-
\`}
|
|
31
|
+
padding: 12px 0;
|
|
42
32
|
}
|
|
43
33
|
\`;
|
|
44
34
|
|
|
@@ -46,38 +36,20 @@ const StyledActionBarContent = styled.div\`
|
|
|
46
36
|
margin: auto 0;
|
|
47
37
|
\`;
|
|
48
38
|
|
|
49
|
-
const StyledCopyButton = styled
|
|
50
|
-
|
|
39
|
+
const StyledCopyButton = styled(StyledSmallButton)<{
|
|
40
|
+
theme: Theme;
|
|
41
|
+
$copied: boolean;
|
|
42
|
+
}>\`
|
|
51
43
|
border: solid 1px
|
|
52
44
|
\${({ theme, $copied }) =>
|
|
53
45
|
$copied ? theme.colors.success : theme.colors.grayLight};
|
|
54
46
|
color: \${({ theme, $copied }) =>
|
|
55
47
|
$copied ? theme.colors.success : theme.colors.primary};
|
|
56
|
-
border-radius: \${({ theme }) => theme.spacing.radius.xs};
|
|
57
|
-
padding: 6px 8px;
|
|
58
|
-
font-size: 12px;
|
|
59
|
-
font-family: inherit;
|
|
60
|
-
font-weight: 600;
|
|
61
|
-
cursor: pointer;
|
|
62
|
-
transition: all 0.2s ease;
|
|
63
|
-
display: flex;
|
|
64
|
-
align-items: center;
|
|
65
|
-
gap: 6px;
|
|
66
|
-
margin-right: -6px;
|
|
67
48
|
|
|
68
49
|
& svg.lucide {
|
|
69
50
|
color: \${({ theme, $copied }) =>
|
|
70
51
|
$copied ? theme.colors.success : theme.colors.primary};
|
|
71
52
|
}
|
|
72
|
-
|
|
73
|
-
&:hover {
|
|
74
|
-
border-color: \${({ theme, $copied }) =>
|
|
75
|
-
$copied ? theme.colors.success : theme.colors.primary};
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
&:active {
|
|
79
|
-
transform: scale(0.95);
|
|
80
|
-
}
|
|
81
53
|
\`;
|
|
82
54
|
|
|
83
55
|
const StyledToggle = styled.button<{ theme: Theme; $isActive?: boolean }>\`
|
|
@@ -150,53 +122,22 @@ const StyledToggle = styled.button<{ theme: Theme; $isActive?: boolean }>\`
|
|
|
150
122
|
|
|
151
123
|
const StyledContent = styled.div<{
|
|
152
124
|
theme: Theme;
|
|
153
|
-
$isChatActive?: boolean;
|
|
154
|
-
$isChatOpen?: boolean;
|
|
155
125
|
$hasSectionBar?: boolean;
|
|
156
126
|
}>\`
|
|
157
|
-
padding-top:
|
|
127
|
+
padding-top: 20px;
|
|
158
128
|
transition: all 0.3s ease;
|
|
159
129
|
|
|
160
|
-
\${mq("lg")} {
|
|
161
|
-
\${({ $isChatActive }) =>
|
|
162
|
-
$isChatActive &&
|
|
163
|
-
css\`
|
|
164
|
-
padding-top: 140px;
|
|
165
|
-
\`}
|
|
166
|
-
|
|
167
|
-
\${({ $isChatOpen, $isChatActive }) =>
|
|
168
|
-
$isChatOpen &&
|
|
169
|
-
$isChatActive &&
|
|
170
|
-
css\`
|
|
171
|
-
padding-top: 70px;
|
|
172
|
-
\`}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
130
|
& textarea {
|
|
176
131
|
max-width: 640px;
|
|
177
132
|
margin: auto;
|
|
178
133
|
width: 100%;
|
|
179
134
|
height: 100%;
|
|
180
135
|
min-height: calc(
|
|
181
|
-
100vh - \${({ $hasSectionBar }) => ($hasSectionBar ?
|
|
136
|
+
100vh - \${({ $hasSectionBar }) => ($hasSectionBar ? 202 : 160)}px
|
|
182
137
|
);
|
|
183
138
|
|
|
184
|
-
\${({ $isChatOpen, $isChatActive, $hasSectionBar }) =>
|
|
185
|
-
!$isChatOpen &&
|
|
186
|
-
$isChatActive &&
|
|
187
|
-
css\`
|
|
188
|
-
min-height: calc(100vh - \${$hasSectionBar ? 288 : 246}px);
|
|
189
|
-
\`}
|
|
190
|
-
|
|
191
139
|
\${mq("lg")} {
|
|
192
|
-
min-height: calc(100vh -
|
|
193
|
-
|
|
194
|
-
\${({ $isChatOpen, $isChatActive }) =>
|
|
195
|
-
!$isChatOpen &&
|
|
196
|
-
$isChatActive &&
|
|
197
|
-
css\`
|
|
198
|
-
min-height: calc(100vh - 242px);
|
|
199
|
-
\`}
|
|
140
|
+
min-height: calc(100vh - 159px);
|
|
200
141
|
}
|
|
201
142
|
}
|
|
202
143
|
\`;
|
|
@@ -204,7 +145,6 @@ const StyledContent = styled.div<{
|
|
|
204
145
|
function ActionBar({ children, content }: ActionBarProps) {
|
|
205
146
|
const [isView, setIsView] = useState(true);
|
|
206
147
|
const [copied, setCopied] = useState(false);
|
|
207
|
-
const { isOpen, isChatActive } = useContext(ChatContext);
|
|
208
148
|
const hasSectionBar = useContext(SectionBarContext);
|
|
209
149
|
|
|
210
150
|
const handleCopyContent = async () => {
|
|
@@ -219,17 +159,17 @@ function ActionBar({ children, content }: ActionBarProps) {
|
|
|
219
159
|
|
|
220
160
|
return (
|
|
221
161
|
<>
|
|
222
|
-
<StyledActionBar
|
|
162
|
+
<StyledActionBar>
|
|
223
163
|
<StyledCopyButton onClick={handleCopyContent} $copied={copied}>
|
|
224
164
|
{copied ? (
|
|
225
165
|
<>
|
|
226
166
|
<Icon name="check" size={16} />
|
|
227
|
-
|
|
167
|
+
Copied!
|
|
228
168
|
</>
|
|
229
169
|
) : (
|
|
230
170
|
<>
|
|
231
171
|
<Icon name="copy" size={16} />
|
|
232
|
-
|
|
172
|
+
Copy content
|
|
233
173
|
</>
|
|
234
174
|
)}
|
|
235
175
|
</StyledCopyButton>
|
|
@@ -245,20 +185,10 @@ function ActionBar({ children, content }: ActionBarProps) {
|
|
|
245
185
|
</StyledActionBarContent>
|
|
246
186
|
</StyledActionBar>
|
|
247
187
|
{isView && (
|
|
248
|
-
<StyledContent
|
|
249
|
-
$isChatActive={isChatActive}
|
|
250
|
-
$isChatOpen={isOpen}
|
|
251
|
-
$hasSectionBar={hasSectionBar}
|
|
252
|
-
>
|
|
253
|
-
{children}
|
|
254
|
-
</StyledContent>
|
|
188
|
+
<StyledContent $hasSectionBar={hasSectionBar}>{children}</StyledContent>
|
|
255
189
|
)}
|
|
256
190
|
{!isView && (
|
|
257
|
-
<StyledContent
|
|
258
|
-
$isChatActive={isChatActive}
|
|
259
|
-
$isChatOpen={isOpen}
|
|
260
|
-
$hasSectionBar={hasSectionBar}
|
|
261
|
-
>
|
|
191
|
+
<StyledContent $hasSectionBar={hasSectionBar}>
|
|
262
192
|
<Textarea defaultValue={content} $fullWidth />
|
|
263
193
|
</StyledContent>
|
|
264
194
|
)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const calloutTemplate = "\"use client\";\nimport { Theme } from \"@/app/theme\";\nimport { styledSmall } from \"cherry-styled-components\";\nimport styled, { css } from \"styled-components\";\nimport { Icon, IconProps } from \"@/components/layout/Icon\";\n\ntype CalloutType = \"note\" | \"info\" | \"warning\" | \"danger\" | \"success\";\n\nconst StyledCallout = styled.div<{ theme: Theme; $type?: CalloutType }>`\n background: ${({ theme }) => theme.colors.light};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n padding: 20px;\n margin: 0;\n ${({ theme }) => styledSmall(theme)}\n color: ${({ theme }) => theme.colors.grayDark};\n display: flex;\n\n & svg {\n vertical-align: middle;\n min-width: min-content;\n margin: 3px 10px 0 0;\n }\n\n ${({ theme, $type }) =>\n $type === \"note\" &&\n css`\n border-color: ${theme.isDark ? \"#0ea5e94d\" : \"#0ea5e933\"};\n background: ${theme.isDark ? \"#0ea5e91a\" : \"#f0f9ff80\"};\n\n & svg.lucide,\n & p {\n color: ${theme.isDark ? \"#bae6fd\" : \"#0c4a6e\"};\n }\n `}\n\n ${({ theme, $type }) =>\n $type === \"info\" &&\n css`\n border-color: ${theme.isDark ? \"#71717a4d\" : \"#71717a33\"};\n background: ${theme.isDark ? \"#71717a1a\" : \"#fafafa80\"};\n\n & svg.lucide,\n & .lucide,\n & p {\n color: ${theme.isDark ? \"#e4e4e7\" : \"#18181b\"};\n }\n `}\n\n ${({ theme, $type }) =>\n $type === \"warning\" &&\n css`\n border-color: ${theme.isDark ? \"#f59e0b4d\" : \"#f59e0b33\"};\n background: ${theme.isDark ? \"#f59e0b1a\" : \"#fffbeb80\"};\n\n & svg.lucide,\n & p {\n color: ${theme.isDark ? \"#fde68a\" : \"#78350f\"};\n }\n `}\n\n ${({ theme, $type }) =>\n $type === \"danger\" &&\n css`\n border-color: ${theme.isDark ? \"#ef44444d\" : \"#ef444433\"};\n background: ${theme.isDark ? \"#ef44441a\" : \"#fef2f280\"};\n\n & svg.lucide,\n & p {\n color: ${theme.isDark ? \"#fecaca\" : \"#7f1d1d\"};\n }\n `}\n\n ${({ theme, $type }) =>\n $type === \"success\" &&\n css`\n border-color: ${theme.isDark ? \"#10b9814d\" : \"#10b98133\"};\n background: ${theme.isDark ? \"#10b9811a\" : \"#ecfdf580\"};\n\n & svg.lucide,\n & p {\n color: ${theme.isDark ? \"#a7f3d0\" : \"#064e3b\"};\n }\n `}\n`;\n\
|
|
1
|
+
export declare const calloutTemplate = "\"use client\";\nimport { Theme } from \"@/app/theme\";\nimport { styledSmall } from \"cherry-styled-components\";\nimport styled, { css } from \"styled-components\";\nimport { Icon, IconProps } from \"@/components/layout/Icon\";\n\ntype CalloutType = \"note\" | \"info\" | \"warning\" | \"danger\" | \"success\";\n\nconst StyledCallout = styled.div<{ theme: Theme; $type?: CalloutType }>`\n background: ${({ theme }) => theme.colors.light};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n padding: 20px;\n margin: 0;\n ${({ theme }) => styledSmall(theme)}\n color: ${({ theme }) => theme.colors.grayDark};\n display: flex;\n\n & svg {\n vertical-align: middle;\n min-width: min-content;\n margin: 3px 10px 0 0;\n }\n\n ${({ theme, $type }) =>\n $type === \"note\" &&\n css`\n border-color: ${theme.isDark ? \"#0ea5e94d\" : \"#0ea5e933\"};\n background: ${theme.isDark ? \"#0ea5e91a\" : \"#f0f9ff80\"};\n\n & svg.lucide,\n & p {\n color: ${theme.isDark ? \"#bae6fd\" : \"#0c4a6e\"};\n }\n `}\n\n ${({ theme, $type }) =>\n $type === \"info\" &&\n css`\n border-color: ${theme.isDark ? \"#71717a4d\" : \"#71717a33\"};\n background: ${theme.isDark ? \"#71717a1a\" : \"#fafafa80\"};\n\n & svg.lucide,\n & .lucide,\n & p {\n color: ${theme.isDark ? \"#e4e4e7\" : \"#18181b\"};\n }\n `}\n\n ${({ theme, $type }) =>\n $type === \"warning\" &&\n css`\n border-color: ${theme.isDark ? \"#f59e0b4d\" : \"#f59e0b33\"};\n background: ${theme.isDark ? \"#f59e0b1a\" : \"#fffbeb80\"};\n\n & svg.lucide,\n & p {\n color: ${theme.isDark ? \"#fde68a\" : \"#78350f\"};\n }\n `}\n\n ${({ theme, $type }) =>\n $type === \"danger\" &&\n css`\n border-color: ${theme.isDark ? \"#ef44444d\" : \"#ef444433\"};\n background: ${theme.isDark ? \"#ef44441a\" : \"#fef2f280\"};\n\n & svg.lucide,\n & p {\n color: ${theme.isDark ? \"#fecaca\" : \"#7f1d1d\"};\n }\n `}\n\n ${({ theme, $type }) =>\n $type === \"success\" &&\n css`\n border-color: ${theme.isDark ? \"#10b9814d\" : \"#10b98133\"};\n background: ${theme.isDark ? \"#10b9811a\" : \"#ecfdf580\"};\n\n & svg.lucide,\n & p {\n color: ${theme.isDark ? \"#a7f3d0\" : \"#064e3b\"};\n }\n `}\n`;\n\ninterface CalloutProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n icon?: IconProps;\n type?: CalloutType;\n}\n\nfunction Callout({ children, type, icon }: CalloutProps) {\n const iconType =\n type === \"note\"\n ? \"CircleAlert\"\n : type === \"info\"\n ? \"Info\"\n : type === \"warning\"\n ? \"TriangleAlert\"\n : type === \"danger\"\n ? \"OctagonAlert\"\n : type === \"success\"\n ? \"Check\"\n : (icon as IconProps);\n return (\n <StyledCallout $type={type}>\n <Icon name={iconType} size={16} />\n {children}\n </StyledCallout>\n );\n}\n\nexport { Callout };\n";
|
|
@@ -84,7 +84,7 @@ const StyledCallout = styled.div<{ theme: Theme; $type?: CalloutType }>\`
|
|
|
84
84
|
\`}
|
|
85
85
|
\`;
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
interface CalloutProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
88
88
|
children: React.ReactNode;
|
|
89
89
|
icon?: IconProps;
|
|
90
90
|
type?: CalloutType;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const cardTemplate = "\"use client\";\nimport styled, { useTheme } from \"styled-components\";\nimport { styledText, Theme } from \"cherry-styled-components\";\nimport { Icon, IconProps } from \"@/components/layout/Icon\";\n\nconst
|
|
1
|
+
export declare const cardTemplate = "\"use client\";\nimport Link from \"next/link\";\nimport styled, { css, useTheme } from \"styled-components\";\nimport { styledText, Theme } from \"cherry-styled-components\";\nimport { Icon, IconProps } from \"@/components/layout/Icon\";\nimport { interactiveStyles } from \"@/components/layout/SharedStyled\";\n\nconst cardStyles = css<{ theme: Theme }>`\n background: ${({ theme }) => theme.colors.light};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n padding: 20px;\n margin: 0;\n ${({ theme }) => styledText(theme)}\n color: ${({ theme }) => theme.colors.grayDark};\n`;\n\nconst StyledCard = styled.div<{ theme: Theme }>`\n ${cardStyles}\n`;\n\nconst StyledCardLink = styled(Link)<{ theme: Theme }>`\n ${interactiveStyles};\n ${cardStyles}\n text-decoration: none;\n`;\n\nconst StyledCardTitle = styled.h3<{ theme: Theme }>`\n margin: 5px 0;\n color: ${({ theme }) => theme.colors.dark};\n ${({ theme }) => styledText(theme)};\n`;\n\ninterface CardProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n title: string;\n icon?: IconProps;\n href?: string;\n}\n\nfunction Card({ children, title, icon, href }: CardProps) {\n const theme = useTheme() as Theme;\n\n const content = (\n <>\n {icon && <Icon name={icon} color={theme.colors.primary} />}\n <StyledCardTitle>{title}</StyledCardTitle>\n {children}\n </>\n );\n\n if (href) {\n return <StyledCardLink href={href}>{content}</StyledCardLink>;\n }\n\n return <StyledCard>{content}</StyledCard>;\n}\n\nexport { Card };\n";
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export const cardTemplate = `"use client";
|
|
2
|
-
import
|
|
2
|
+
import Link from "next/link";
|
|
3
|
+
import styled, { css, useTheme } from "styled-components";
|
|
3
4
|
import { styledText, Theme } from "cherry-styled-components";
|
|
4
5
|
import { Icon, IconProps } from "@/components/layout/Icon";
|
|
6
|
+
import { interactiveStyles } from "@/components/layout/SharedStyled";
|
|
5
7
|
|
|
6
|
-
const
|
|
8
|
+
const cardStyles = css<{ theme: Theme }>\`
|
|
7
9
|
background: \${({ theme }) => theme.colors.light};
|
|
8
10
|
border: solid 1px \${({ theme }) => theme.colors.grayLight};
|
|
9
11
|
border-radius: \${({ theme }) => theme.spacing.radius.lg};
|
|
@@ -13,28 +15,45 @@ const StyledCard = styled.div<{ theme: Theme }>\`
|
|
|
13
15
|
color: \${({ theme }) => theme.colors.grayDark};
|
|
14
16
|
\`;
|
|
15
17
|
|
|
18
|
+
const StyledCard = styled.div<{ theme: Theme }>\`
|
|
19
|
+
\${cardStyles}
|
|
20
|
+
\`;
|
|
21
|
+
|
|
22
|
+
const StyledCardLink = styled(Link)<{ theme: Theme }>\`
|
|
23
|
+
\${interactiveStyles};
|
|
24
|
+
\${cardStyles}
|
|
25
|
+
text-decoration: none;
|
|
26
|
+
\`;
|
|
27
|
+
|
|
16
28
|
const StyledCardTitle = styled.h3<{ theme: Theme }>\`
|
|
17
29
|
margin: 5px 0;
|
|
18
30
|
color: \${({ theme }) => theme.colors.dark};
|
|
19
31
|
\${({ theme }) => styledText(theme)};
|
|
20
32
|
\`;
|
|
21
33
|
|
|
22
|
-
|
|
34
|
+
interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
23
35
|
children: React.ReactNode;
|
|
24
36
|
title: string;
|
|
25
37
|
icon?: IconProps;
|
|
38
|
+
href?: string;
|
|
26
39
|
}
|
|
27
40
|
|
|
28
|
-
function Card({ children, title, icon }: CardProps) {
|
|
41
|
+
function Card({ children, title, icon, href }: CardProps) {
|
|
29
42
|
const theme = useTheme() as Theme;
|
|
30
43
|
|
|
31
|
-
|
|
32
|
-
|
|
44
|
+
const content = (
|
|
45
|
+
<>
|
|
33
46
|
{icon && <Icon name={icon} color={theme.colors.primary} />}
|
|
34
47
|
<StyledCardTitle>{title}</StyledCardTitle>
|
|
35
48
|
{children}
|
|
36
|
-
|
|
49
|
+
</>
|
|
37
50
|
);
|
|
51
|
+
|
|
52
|
+
if (href) {
|
|
53
|
+
return <StyledCardLink href={href}>{content}</StyledCardLink>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return <StyledCard>{content}</StyledCard>;
|
|
38
57
|
}
|
|
39
58
|
|
|
40
59
|
export { Card };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const codeTemplate = "\"use client\";\nimport { useState, useCallback, useMemo } from \"react\";\nimport styled from \"styled-components\";\nimport { Theme, styledCode } from \"cherry-styled-components\";\nimport { rgba } from \"polished\";\nimport { unified } from \"unified\";\nimport rehypeParse from \"rehype-parse\";\nimport rehypeHighlight from \"rehype-highlight\";\nimport rehypeStringify from \"rehype-stringify\";\nimport { Icon } from \"@/components/layout/Icon\";\n\ninterface CodeProps extends Omit<\n React.HTMLAttributes<HTMLDivElement>,\n \"theme\"\n> {\n code: string;\n language?: string;\n theme?: Theme;\n}\n\nconst CodeWrapper = styled.span<{ theme: Theme }>`\n position: relative;\n z-index: 2;\n display: block;\n width: 100%;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n border: solid 1px\n ${({ theme }) =>\n theme.isDark\n ? rgba(theme.colors.dark, 0.2)\n : rgba(theme.colors.dark, 0.1)};\n`;\n\nconst TopBar = styled.div<{ theme: Theme }>`\n background: ${({ theme }) => (theme.isDark ? \"#0d1117\" : \"#f6f8fa\")};\n border-top-left-radius: ${({ theme }) => theme.spacing.radius.lg};\n border-top-right-radius: ${({ theme }) => theme.spacing.radius.lg};\n border-bottom: solid 1px\n ${({ theme }) =>\n theme.isDark ? rgba(\"#ffffff\", 0.1) : rgba(\"#000000\", 0.1)};\n height: 33px;\n width: 100%;\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 5px;\n padding: 0 10px;\n`;\n\nconst DotsContainer = styled.div`\n display: flex;\n gap: 5px;\n`;\n\nconst Dot = styled.span<{ theme: Theme }>`\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background: ${({ theme }) =>\n theme.isDark ? rgba(\"#ffffff\", 0.1) : rgba(\"#000000\", 0.1)};\n`;\n\nconst CopyButton = styled.button<{ theme: Theme; $copied: boolean }>`\n background: ${({ theme, $copied }) =>\n $copied\n ? theme.isDark\n ? rgba(\"#7ee787\", 0.2)\n : rgba(\"#2da44e\", 0.1)\n : \"transparent\"};\n border: solid 1px\n ${({ theme, $copied }) =>\n $copied\n ? theme.isDark\n ? \"#7ee787\"\n : \"#2da44e\"\n : theme.isDark\n ? rgba(\"#ffffff\", 0.1)\n : rgba(\"#000000\", 0.1)};\n color: ${({ theme, $copied }) =>\n $copied\n ? theme.isDark\n ? \"#7ee787\"\n : \"#2da44e\"\n : theme.isDark\n ? \"#c9d1d9\"\n : \"#57606a\"};\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n padding: 4px 8px;\n font-size: 12px;\n font-family: ${({ theme }) => theme.fonts.mono};\n cursor: pointer;\n transition: all 0.2s ease;\n display: flex;\n align-items: center;\n gap: 4px;\n margin-right: -6px;\n\n & svg.lucide {\n color: ${({ theme, $copied }) =>\n $copied\n ? theme.isDark\n ? \"#7ee787\"\n : \"#2da44e\"\n : theme.isDark\n ? \"#c9d1d9\"\n : \"#57606a\"};\n }\n\n &:hover {\n background: ${({ theme, $copied }) =>\n $copied\n ? theme.isDark\n ? rgba(\"#7ee787\", 0.3)\n : rgba(\"#2da44e\", 0.2)\n : theme.isDark\n ? rgba(\"#ffffff\", 0.1)\n : rgba(\"#000000\", 0.05)};\n border-color: ${({ theme, $copied }) =>\n $copied\n ? theme.isDark\n ? \"#7ee787\"\n : \"#2da44e\"\n : theme.isDark\n ? rgba(\"#ffffff\", 0.2)\n : rgba(\"#000000\", 0.2)};\n }\n\n &:active {\n transform: scale(0.95);\n }\n`;\n\nconst Body = styled.div<{ theme: Theme }>`\n background: ${({ theme }) => (theme.isDark ? \"#0d1117\" : \"#ffffff\")};\n border-bottom-left-radius: ${({ theme }) => theme.spacing.radius.lg};\n border-bottom-right-radius: ${({ theme }) => theme.spacing.radius.lg};\n color: ${({ theme }) => (theme.isDark ? \"#ffffff\" : \"#24292f\")};\n padding: 20px;\n font-family: ${({ theme }) => theme.fonts.mono};\n text-align: left;\n overflow-x: auto;\n overflow-y: auto;\n max-height: calc(
|
|
1
|
+
export declare const codeTemplate = "\"use client\";\nimport { useState, useCallback, useMemo } from \"react\";\nimport styled from \"styled-components\";\nimport { Theme, styledCode } from \"cherry-styled-components\";\nimport { rgba } from \"polished\";\nimport { unified } from \"unified\";\nimport rehypeParse from \"rehype-parse\";\nimport rehypeHighlight from \"rehype-highlight\";\nimport rehypeStringify from \"rehype-stringify\";\nimport { Icon } from \"@/components/layout/Icon\";\n\ninterface CodeProps extends Omit<\n React.HTMLAttributes<HTMLDivElement>,\n \"theme\"\n> {\n code: string;\n language?: string;\n theme?: Theme;\n}\n\nconst CodeWrapper = styled.span<{ theme: Theme }>`\n position: relative;\n z-index: 2;\n display: block;\n width: 100%;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n border: solid 1px\n ${({ theme }) =>\n theme.isDark\n ? rgba(theme.colors.dark, 0.2)\n : rgba(theme.colors.dark, 0.1)};\n`;\n\nconst TopBar = styled.div<{ theme: Theme }>`\n background: ${({ theme }) => (theme.isDark ? \"#0d1117\" : \"#f6f8fa\")};\n border-top-left-radius: ${({ theme }) => theme.spacing.radius.lg};\n border-top-right-radius: ${({ theme }) => theme.spacing.radius.lg};\n border-bottom: solid 1px\n ${({ theme }) =>\n theme.isDark ? rgba(\"#ffffff\", 0.1) : rgba(\"#000000\", 0.1)};\n height: 33px;\n width: 100%;\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 5px;\n padding: 0 10px;\n`;\n\nconst DotsContainer = styled.div`\n display: flex;\n gap: 5px;\n`;\n\nconst Dot = styled.span<{ theme: Theme }>`\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background: ${({ theme }) =>\n theme.isDark ? rgba(\"#ffffff\", 0.1) : rgba(\"#000000\", 0.1)};\n`;\n\nconst CopyButton = styled.button<{ theme: Theme; $copied: boolean }>`\n background: ${({ theme, $copied }) =>\n $copied\n ? theme.isDark\n ? rgba(\"#7ee787\", 0.2)\n : rgba(\"#2da44e\", 0.1)\n : \"transparent\"};\n border: solid 1px\n ${({ theme, $copied }) =>\n $copied\n ? theme.isDark\n ? \"#7ee787\"\n : \"#2da44e\"\n : theme.isDark\n ? rgba(\"#ffffff\", 0.1)\n : rgba(\"#000000\", 0.1)};\n color: ${({ theme, $copied }) =>\n $copied\n ? theme.isDark\n ? \"#7ee787\"\n : \"#2da44e\"\n : theme.isDark\n ? \"#c9d1d9\"\n : \"#57606a\"};\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n padding: 4px 8px;\n font-size: 12px;\n font-family: ${({ theme }) => theme.fonts.mono};\n cursor: pointer;\n transition: all 0.2s ease;\n display: flex;\n align-items: center;\n gap: 4px;\n margin-right: -6px;\n\n & svg.lucide {\n color: ${({ theme, $copied }) =>\n $copied\n ? theme.isDark\n ? \"#7ee787\"\n : \"#2da44e\"\n : theme.isDark\n ? \"#c9d1d9\"\n : \"#57606a\"};\n }\n\n &:hover {\n background: ${({ theme, $copied }) =>\n $copied\n ? theme.isDark\n ? rgba(\"#7ee787\", 0.3)\n : rgba(\"#2da44e\", 0.2)\n : theme.isDark\n ? rgba(\"#ffffff\", 0.1)\n : rgba(\"#000000\", 0.05)};\n border-color: ${({ theme, $copied }) =>\n $copied\n ? theme.isDark\n ? \"#7ee787\"\n : \"#2da44e\"\n : theme.isDark\n ? rgba(\"#ffffff\", 0.2)\n : rgba(\"#000000\", 0.2)};\n }\n\n &:active {\n transform: scale(0.95);\n }\n`;\n\nconst Body = styled.div<{ theme: Theme }>`\n background: ${({ theme }) => (theme.isDark ? \"#0d1117\" : \"#ffffff\")};\n border-bottom-left-radius: ${({ theme }) => theme.spacing.radius.lg};\n border-bottom-right-radius: ${({ theme }) => theme.spacing.radius.lg};\n color: ${({ theme }) => (theme.isDark ? \"#ffffff\" : \"#24292f\")};\n padding: 20px;\n font-family: ${({ theme }) => theme.fonts.mono};\n text-align: left;\n overflow-x: auto;\n overflow-y: auto;\n max-height: calc(100dvh - 400px);\n ${({ theme }) => styledCode(theme)};\n\n /* Dark mode syntax highlighting (GitHub Dark) */\n ${({ theme }) =>\n theme.isDark &&\n `\n & .hljs {\n color: #c9d1d9;\n background: #0d1117;\n }\n\n & .hljs-doctag,\n & .hljs-keyword,\n & .hljs-meta .hljs-keyword,\n & .hljs-template-tag,\n & .hljs-template-variable,\n & .hljs-type,\n & .hljs-variable.language_ {\n color: #ff7b72;\n }\n\n & .hljs-title,\n & .hljs-title.class_,\n & .hljs-title.class_.inherited__,\n & .hljs-title.function_ {\n color: #d2a8ff;\n }\n\n & .hljs-attr,\n & .hljs-attribute,\n & .hljs-literal,\n & .hljs-meta,\n & .hljs-number,\n & .hljs-operator,\n & .hljs-selector-attr,\n & .hljs-selector-class,\n & .hljs-selector-id,\n & .hljs-variable {\n color: #79c0ff;\n }\n\n & .hljs-meta .hljs-string,\n & .hljs-regexp,\n & .hljs-string {\n color: #a5d6ff;\n }\n\n & .hljs-built_in,\n & .hljs-symbol {\n color: #ffa657;\n }\n\n & .hljs-code,\n & .hljs-comment,\n & .hljs-formula {\n color: #8b949e;\n }\n\n & .hljs-name,\n & .hljs-quote,\n & .hljs-selector-pseudo,\n & .hljs-selector-tag {\n color: #7ee787;\n }\n\n & .hljs-subst {\n color: #c9d1d9;\n }\n\n & .hljs-section {\n color: #1f6feb;\n font-weight: 700;\n }\n\n & .hljs-bullet {\n color: #f2cc60;\n }\n\n & .hljs-emphasis {\n color: #c9d1d9;\n font-style: italic;\n }\n\n & .hljs-strong {\n color: #c9d1d9;\n font-weight: 700;\n }\n\n & .hljs-addition {\n color: #aff5b4;\n background-color: #033a16;\n }\n\n & .hljs-deletion {\n color: #ffdcd7;\n background-color: #67060c;\n }\n `}\n\n /* Light mode syntax highlighting (GitHub Light) */\n ${({ theme }) =>\n !theme.isDark &&\n `\n & .hljs {\n color: #24292f;\n background: #ffffff;\n }\n\n & .hljs-doctag,\n & .hljs-keyword,\n & .hljs-meta .hljs-keyword,\n & .hljs-template-tag,\n & .hljs-template-variable,\n & .hljs-type,\n & .hljs-variable.language_ {\n color: #cf222e;\n }\n\n & .hljs-title,\n & .hljs-title.class_,\n & .hljs-title.class_.inherited__,\n & .hljs-title.function_ {\n color: #8250df;\n }\n\n & .hljs-attr,\n & .hljs-attribute,\n & .hljs-literal,\n & .hljs-meta,\n & .hljs-number,\n & .hljs-operator,\n & .hljs-selector-attr,\n & .hljs-selector-class,\n & .hljs-selector-id,\n & .hljs-variable {\n color: #0550ae;\n }\n\n & .hljs-meta .hljs-string,\n & .hljs-regexp,\n & .hljs-string {\n color: #0a3069;\n }\n\n & .hljs-built_in,\n & .hljs-symbol {\n color: #953800;\n }\n\n & .hljs-code,\n & .hljs-comment,\n & .hljs-formula {\n color: #6e7781;\n }\n\n & .hljs-name,\n & .hljs-quote,\n & .hljs-selector-pseudo,\n & .hljs-selector-tag {\n color: #116329;\n }\n\n & .hljs-subst {\n color: #24292f;\n }\n\n & .hljs-section {\n color: #0550ae;\n font-weight: 700;\n }\n\n & .hljs-bullet {\n color: #953800;\n }\n\n & .hljs-emphasis {\n color: #24292f;\n font-style: italic;\n }\n\n & .hljs-strong {\n color: #24292f;\n font-weight: 700;\n }\n\n & .hljs-addition {\n color: #116329;\n background-color: #dafbe1;\n }\n\n & .hljs-deletion {\n color: #82071e;\n background-color: #ffebe9;\n }\n `}\n`;\n\nconst escapeHtml = (unsafe: string): string => {\n return unsafe\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n};\n\nconst sanitizeLanguage = (lang: string): string =>\n lang.replace(/[^a-zA-Z0-9_-]/g, \"\");\n\nconst highlightCode = (code: string, language: string): string => {\n const escapedCode = escapeHtml(code);\n const safeLang = sanitizeLanguage(language);\n const result = unified()\n .use(rehypeParse, { fragment: true })\n .use(rehypeHighlight, {\n detect: true,\n ignoreMissing: true,\n })\n .use(rehypeStringify)\n .processSync(\n `<pre><code class=\"language-${safeLang}\">${escapedCode}</code></pre>`,\n );\n\n return String(result);\n};\n\nfunction Code({ code, language = \"javascript\", theme, className }: CodeProps) {\n const [copied, setCopied] = useState(false);\n const highlightedCode = useMemo(\n () => highlightCode(code, language),\n [code, language],\n );\n\n const handleCopy = useCallback(async () => {\n try {\n await navigator.clipboard.writeText(code);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n } catch (err) {\n console.error(\"Failed to copy code:\", err);\n }\n }, [code]);\n\n return (\n <CodeWrapper\n className={`${className ?? \"\"} code-wrapper`.trim()}\n theme={theme}\n >\n <TopBar theme={theme}>\n <DotsContainer>\n <Dot theme={theme} />\n <Dot theme={theme} />\n <Dot theme={theme} />\n </DotsContainer>\n <CopyButton onClick={handleCopy} $copied={copied} theme={theme}>\n {copied ? (\n <>\n <Icon name=\"check\" size={12} />\n <span>Copied!</span>\n </>\n ) : (\n <>\n <Icon name=\"copy\" size={12} />\n <span>Copy</span>\n </>\n )}\n </CopyButton>\n </TopBar>\n <Body\n dangerouslySetInnerHTML={{ __html: highlightedCode }}\n theme={theme}\n className=\"code-wrapper-body\"\n />\n </CodeWrapper>\n );\n}\n\nexport { Code };\n";
|