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.
Files changed (94) hide show
  1. package/README.md +4 -3
  2. package/dist/lib/structures.js +4 -0
  3. package/dist/templates/app/api/rag/route.d.ts +1 -1
  4. package/dist/templates/app/api/rag/route.js +11 -4
  5. package/dist/templates/app/theme.d.ts +3 -1
  6. package/dist/templates/app/theme.js +17 -15
  7. package/dist/templates/components/Chat.d.ts +1 -1
  8. package/dist/templates/components/Chat.js +358 -161
  9. package/dist/templates/components/DocsSideBar.d.ts +1 -1
  10. package/dist/templates/components/DocsSideBar.js +45 -11
  11. package/dist/templates/components/LockBodyScroll.d.ts +1 -0
  12. package/dist/templates/components/LockBodyScroll.js +17 -0
  13. package/dist/templates/components/SideBar.d.ts +1 -1
  14. package/dist/templates/components/SideBar.js +15 -2
  15. package/dist/templates/components/layout/Accordion.d.ts +1 -1
  16. package/dist/templates/components/layout/Accordion.js +1 -1
  17. package/dist/templates/components/layout/ActionBar.d.ts +1 -1
  18. package/dist/templates/components/layout/ActionBar.js +17 -87
  19. package/dist/templates/components/layout/Callout.d.ts +1 -1
  20. package/dist/templates/components/layout/Callout.js +1 -1
  21. package/dist/templates/components/layout/Card.d.ts +1 -1
  22. package/dist/templates/components/layout/Card.js +26 -7
  23. package/dist/templates/components/layout/Code.d.ts +1 -1
  24. package/dist/templates/components/layout/Code.js +1 -1
  25. package/dist/templates/components/layout/Columns.d.ts +1 -1
  26. package/dist/templates/components/layout/Columns.js +1 -1
  27. package/dist/templates/components/layout/DocsComponents.d.ts +1 -1
  28. package/dist/templates/components/layout/DocsComponents.js +40 -14
  29. package/dist/templates/components/layout/DocsNavigation.d.ts +1 -1
  30. package/dist/templates/components/layout/DocsNavigation.js +3 -2
  31. package/dist/templates/components/layout/Field.d.ts +1 -1
  32. package/dist/templates/components/layout/Field.js +1 -1
  33. package/dist/templates/components/layout/Footer.d.ts +1 -1
  34. package/dist/templates/components/layout/Footer.js +28 -6
  35. package/dist/templates/components/layout/Header.d.ts +1 -1
  36. package/dist/templates/components/layout/Header.js +10 -12
  37. package/dist/templates/components/layout/SharedStyles.d.ts +1 -1
  38. package/dist/templates/components/layout/SharedStyles.js +26 -2
  39. package/dist/templates/components/layout/StaticLinks.d.ts +1 -1
  40. package/dist/templates/components/layout/StaticLinks.js +7 -3
  41. package/dist/templates/components/layout/Steps.d.ts +1 -1
  42. package/dist/templates/components/layout/Steps.js +7 -2
  43. package/dist/templates/components/layout/Tabs.d.ts +1 -1
  44. package/dist/templates/components/layout/Tabs.js +2 -2
  45. package/dist/templates/components/layout/Update.d.ts +1 -1
  46. package/dist/templates/components/layout/Update.js +1 -1
  47. package/dist/templates/mdx/ai-assistant.mdx.d.ts +1 -1
  48. package/dist/templates/mdx/ai-assistant.mdx.js +8 -0
  49. package/dist/templates/mdx/callouts.mdx.d.ts +1 -1
  50. package/dist/templates/mdx/callouts.mdx.js +6 -2
  51. package/dist/templates/mdx/cards.mdx.d.ts +1 -1
  52. package/dist/templates/mdx/cards.mdx.js +19 -3
  53. package/dist/templates/mdx/columns.mdx.d.ts +1 -1
  54. package/dist/templates/mdx/columns.mdx.js +2 -2
  55. package/dist/templates/mdx/commands.mdx.d.ts +1 -1
  56. package/dist/templates/mdx/commands.mdx.js +10 -2
  57. package/dist/templates/mdx/components.mdx.d.ts +1 -0
  58. package/dist/templates/mdx/components.mdx.js +56 -0
  59. package/dist/templates/mdx/deployment.mdx.d.ts +1 -1
  60. package/dist/templates/mdx/deployment.mdx.js +1 -1
  61. package/dist/templates/mdx/globals.mdx.d.ts +1 -1
  62. package/dist/templates/mdx/globals.mdx.js +5 -0
  63. package/dist/templates/mdx/index.mdx.d.ts +1 -1
  64. package/dist/templates/mdx/index.mdx.js +5 -5
  65. package/dist/templates/mdx/model-context-protocol.mdx.d.ts +1 -1
  66. package/dist/templates/mdx/model-context-protocol.mdx.js +2 -2
  67. package/dist/templates/mdx/navigation.mdx.d.ts +1 -1
  68. package/dist/templates/mdx/navigation.mdx.js +1 -1
  69. package/dist/templates/mdx/platform/ai-assistant.mdx.d.ts +1 -1
  70. package/dist/templates/mdx/platform/ai-assistant.mdx.js +20 -0
  71. package/dist/templates/mdx/platform/external-links.mdx.d.ts +1 -1
  72. package/dist/templates/mdx/platform/external-links.mdx.js +2 -0
  73. package/dist/templates/mdx/platform/fonts-settings.mdx.d.ts +1 -1
  74. package/dist/templates/mdx/platform/fonts-settings.mdx.js +8 -5
  75. package/dist/templates/mdx/platform/index.mdx.d.ts +1 -1
  76. package/dist/templates/mdx/platform/index.mdx.js +10 -1
  77. package/dist/templates/mdx/platform/site-settings.mdx.d.ts +1 -1
  78. package/dist/templates/mdx/platform/site-settings.mdx.js +4 -4
  79. package/dist/templates/mdx/sections.mdx.d.ts +1 -1
  80. package/dist/templates/mdx/sections.mdx.js +2 -2
  81. package/dist/templates/mdx/steps.mdx.d.ts +1 -1
  82. package/dist/templates/mdx/steps.mdx.js +4 -0
  83. package/dist/templates/mdx/tabs.mdx.d.ts +1 -1
  84. package/dist/templates/mdx/tabs.mdx.js +1 -1
  85. package/dist/templates/package.js +5 -5
  86. package/dist/templates/services/llm/types.d.ts +1 -1
  87. package/dist/templates/services/llm/types.js +1 -1
  88. package/dist/templates/services/mcp/server.d.ts +1 -1
  89. package/dist/templates/services/mcp/server.js +1 -1
  90. package/dist/templates/services/mcp/tools.d.ts +1 -1
  91. package/dist/templates/services/mcp/tools.js +1 -5
  92. package/dist/templates/utils/config.d.ts +1 -1
  93. package/dist/templates/utils/config.js +1 -1
  94. 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\nexport interface Heading {\n id: string;\n text: string;\n level: number;\n}\n\nconst OFFSET = 80;\n\nexport function DocsSideBar({ headings }: { headings: Heading[] }) {\n const [activeId, setActiveId] = useState<string>(\"\");\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 - OFFSET,\n );\n for (const heading of visibleHeadings) {\n const distance = Math.abs(heading.getBoundingClientRect().top - OFFSET);\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 <= OFFSET) {\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 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({ top: elementPosition - OFFSET, behavior: \"smooth\" });\n }\n };\n\n return (\n <StyledIndexSidebar>\n {headings?.length > 0 && (\n <>\n <StyledIndexSidebarLabel>On this page</StyledIndexSidebarLabel>\n <Space $size={20} />\n </>\n )}\n {headings.map((heading, index) => (\n <li\n key={index}\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 </li>\n ))}\n </StyledIndexSidebar>\n );\n}\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
- export interface Heading {
11
+ interface Heading {
11
12
  id: string;
12
13
  text: string;
13
14
  level: number;
14
15
  }
15
16
 
16
- const OFFSET = 80;
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 - OFFSET,
49
+ closestHeading.getBoundingClientRect().top - getOffset(),
43
50
  );
44
51
  for (const heading of visibleHeadings) {
45
- const distance = Math.abs(heading.getBoundingClientRect().top - OFFSET);
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 <= OFFSET) {
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({ top: elementPosition - OFFSET, behavior: "smooth" });
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={20} />
142
+ <Space $size={15} />
111
143
  </>
112
144
  )}
113
145
  {headings.map((heading, index) => (
114
- <li
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
- </li>
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\nexport interface 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";
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
- export interface AccordionProps extends React.HTMLAttributes<HTMLDivElement> {
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 { ChatContext } from \"@/components/Chat\";\nimport { SectionBarContext } from \"@/components/layout/DocsComponents\";\n\ninterface ActionBarProps {\n children: React.ReactNode;\n content: string;\n}\n\nconst StyledActionBar = styled.div<{\n theme: Theme;\n $isChatOpen?: boolean;\n}>`\n position: absolute;\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n left: 0;\n padding: 0 20px 20px;\n display: flex;\n justify-content: space-between;\n width: 100%;\n transition: all 0.3s ease;\n\n ${mq(\"lg\")} {\n left: 50%;\n transform: translateX(-50%);\n max-width: calc(100vw - 640px);\n width: 100%;\n padding: 0 20px 20px;\n margin: 0;\n\n ${({ $isChatOpen }) =>\n $isChatOpen &&\n css`\n padding: 0 120px 20px 20px;\n `}\n }\n`;\n\nconst StyledActionBarContent = styled.div`\n margin: auto 0;\n`;\n\nconst StyledCopyButton = styled.button<{ theme: Theme; $copied: boolean }>`\n background: transparent;\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 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: ${({ theme, $copied }) =>\n $copied ? theme.colors.success : theme.colors.primary};\n }\n\n &:hover {\n border-color: ${({ theme, $copied }) =>\n $copied ? theme.colors.success : theme.colors.primary};\n }\n\n &:active {\n transform: scale(0.95);\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 $isChatActive?: boolean;\n $isChatOpen?: boolean;\n $hasSectionBar?: boolean;\n}>`\n padding-top: 74px;\n transition: all 0.3s ease;\n\n ${mq(\"lg\")} {\n ${({ $isChatActive }) =>\n $isChatActive &&\n css`\n padding-top: 140px;\n `}\n\n ${({ $isChatOpen, $isChatActive }) =>\n $isChatOpen &&\n $isChatActive &&\n css`\n padding-top: 70px;\n `}\n }\n\n & textarea {\n max-width: 640px;\n margin: auto;\n width: 100%;\n height: 100%;\n min-height: calc(\n 100vh - ${({ $hasSectionBar }) => ($hasSectionBar ? 219 : 178)}px\n );\n\n ${({ $isChatOpen, $isChatActive, $hasSectionBar }) =>\n !$isChatOpen &&\n $isChatActive &&\n css`\n min-height: calc(100vh - ${$hasSectionBar ? 288 : 246}px);\n `}\n\n ${mq(\"lg\")} {\n min-height: calc(100vh - 176px);\n\n ${({ $isChatOpen, $isChatActive }) =>\n !$isChatOpen &&\n $isChatActive &&\n css`\n min-height: calc(100vh - 242px);\n `}\n }\n }\n`;\n\nfunction ActionBar({ children, content }: ActionBarProps) {\n const [isView, setIsView] = useState(true);\n const [copied, setCopied] = useState(false);\n const { isOpen, isChatActive } = useContext(ChatContext);\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 $isChatOpen={isOpen}>\n <StyledCopyButton onClick={handleCopyContent} $copied={copied}>\n {copied ? (\n <>\n <Icon name=\"check\" size={16} />\n <span>Copied!</span>\n </>\n ) : (\n <>\n <Icon name=\"copy\" size={16} />\n <span>Copy Content</span>\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\n $isChatActive={isChatActive}\n $isChatOpen={isOpen}\n $hasSectionBar={hasSectionBar}\n >\n {children}\n </StyledContent>\n )}\n {!isView && (\n <StyledContent\n $isChatActive={isChatActive}\n $isChatOpen={isOpen}\n $hasSectionBar={hasSectionBar}\n >\n <Textarea defaultValue={content} $fullWidth />\n </StyledContent>\n )}\n </>\n );\n}\n\nexport { ActionBar };\n";
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 20px 20px;
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
- left: 50%;
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.button<{ theme: Theme; $copied: boolean }>\`
50
- background: transparent;
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: 74px;
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 ? 219 : 178)}px
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 - 176px);
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 $isChatOpen={isOpen}>
162
+ <StyledActionBar>
223
163
  <StyledCopyButton onClick={handleCopyContent} $copied={copied}>
224
164
  {copied ? (
225
165
  <>
226
166
  <Icon name="check" size={16} />
227
- <span>Copied!</span>
167
+ Copied!
228
168
  </>
229
169
  ) : (
230
170
  <>
231
171
  <Icon name="copy" size={16} />
232
- <span>Copy Content</span>
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\nexport interface 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";
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
- export interface CalloutProps extends React.HTMLAttributes<HTMLDivElement> {
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 StyledCard = 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 color: ${({ theme }) => theme.colors.grayDark};\n`;\n\nconst StyledCardTitle = styled.h3<{ theme: Theme }>`\n margin: 5px 0;\n color: ${({ theme }) => theme.colors.dark};\n ${({ theme }) => styledText(theme)};\n`;\n\nexport interface CardProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n title: string;\n icon?: IconProps;\n}\n\nfunction Card({ children, title, icon }: CardProps) {\n const theme = useTheme() as Theme;\n\n return (\n <StyledCard>\n {icon && <Icon name={icon} color={theme.colors.primary} />}\n <StyledCardTitle>{title}</StyledCardTitle>\n {children}\n </StyledCard>\n );\n}\n\nexport { Card };\n";
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 styled, { useTheme } from "styled-components";
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 StyledCard = styled.div<{ theme: Theme }>\`
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
- export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
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
- return (
32
- <StyledCard>
44
+ const content = (
45
+ <>
33
46
  {icon && <Icon name={icon} color={theme.colors.primary} />}
34
47
  <StyledCardTitle>{title}</StyledCardTitle>
35
48
  {children}
36
- </StyledCard>
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(100svh - 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, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#039;\");\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";
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, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#039;\");\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";