doccupine 0.0.98 → 0.0.100

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 (117) hide show
  1. package/dist/index.js +0 -0
  2. package/dist/templates/components/MDXComponents.d.ts +1 -1
  3. package/dist/templates/components/MDXComponents.js +3 -1
  4. package/dist/templates/components/layout/Code.d.ts +1 -1
  5. package/dist/templates/components/layout/Code.js +228 -3
  6. package/dist/templates/mdx/code.mdx.d.ts +1 -1
  7. package/dist/templates/mdx/code.mdx.js +43 -1
  8. package/dist/templates/package.js +2 -2
  9. package/package.json +1 -1
  10. package/dist/index.test.d.ts +0 -1
  11. package/dist/index.test.js +0 -48
  12. package/dist/metadata.d.ts +0 -11
  13. package/dist/metadata.js +0 -52
  14. package/dist/templates/accordion.mdx.d.ts +0 -1
  15. package/dist/templates/accordion.mdx.js +0 -41
  16. package/dist/templates/api/theme/routes.d.ts +0 -1
  17. package/dist/templates/api/theme/routes.js +0 -28
  18. package/dist/templates/app/api/theme/routes.d.ts +0 -1
  19. package/dist/templates/app/api/theme/routes.js +0 -29
  20. package/dist/templates/app/error.d.ts +0 -1
  21. package/dist/templates/app/error.js +0 -45
  22. package/dist/templates/app/layout.d.ts +0 -34
  23. package/dist/templates/app/layout.js +0 -184
  24. package/dist/templates/callouts.mdx.d.ts +0 -1
  25. package/dist/templates/callouts.mdx.js +0 -57
  26. package/dist/templates/cards.mdx.d.ts +0 -1
  27. package/dist/templates/cards.mdx.js +0 -25
  28. package/dist/templates/click-outside.d.ts +0 -1
  29. package/dist/templates/click-outside.js +0 -28
  30. package/dist/templates/code.d.ts +0 -1
  31. package/dist/templates/code.js +0 -202
  32. package/dist/templates/code.mdx.d.ts +0 -1
  33. package/dist/templates/code.mdx.js +0 -46
  34. package/dist/templates/commands.mdx.d.ts +0 -1
  35. package/dist/templates/commands.mdx.js +0 -48
  36. package/dist/templates/components/ClickOutside.d.ts +0 -1
  37. package/dist/templates/components/ClickOutside.js +0 -29
  38. package/dist/templates/components/layout/ClientThemeProvider.d.ts +0 -1
  39. package/dist/templates/components/layout/ClientThemeProvider.js +0 -85
  40. package/dist/templates/components/layout/CopyButton.d.ts +0 -1
  41. package/dist/templates/components/layout/CopyButton.js +0 -85
  42. package/dist/templates/components/layout/RenderMarkdown.d.ts +0 -1
  43. package/dist/templates/components/layout/RenderMarkdown.js +0 -61
  44. package/dist/templates/components/layout/Stepts.d.ts +0 -1
  45. package/dist/templates/components/layout/Stepts.js +0 -114
  46. package/dist/templates/components/layout/ThemeToggle.d.ts +0 -1
  47. package/dist/templates/components/layout/ThemeToggle.js +0 -107
  48. package/dist/templates/constants.d.ts +0 -5
  49. package/dist/templates/constants.js +0 -5
  50. package/dist/templates/docs-components.d.ts +0 -1
  51. package/dist/templates/docs-components.js +0 -441
  52. package/dist/templates/docs.d.ts +0 -1
  53. package/dist/templates/docs.js +0 -48
  54. package/dist/templates/fields.mdx.d.ts +0 -1
  55. package/dist/templates/fields.mdx.js +0 -42
  56. package/dist/templates/footer.d.ts +0 -1
  57. package/dist/templates/footer.js +0 -9
  58. package/dist/templates/header.d.ts +0 -1
  59. package/dist/templates/header.js +0 -275
  60. package/dist/templates/home.d.ts +0 -1
  61. package/dist/templates/home.js +0 -74
  62. package/dist/templates/home.mdx.d.ts +0 -1
  63. package/dist/templates/home.mdx.js +0 -44
  64. package/dist/templates/homemdx.d.ts +0 -1
  65. package/dist/templates/homemdx.js +0 -59
  66. package/dist/templates/icon.d.ts +0 -1
  67. package/dist/templates/icon.js +0 -20
  68. package/dist/templates/icons.mdx.d.ts +0 -1
  69. package/dist/templates/icons.mdx.js +0 -44
  70. package/dist/templates/image-embeds.mdx.d.ts +0 -1
  71. package/dist/templates/image-embeds.mdx.js +0 -103
  72. package/dist/templates/layout.d.ts +0 -1
  73. package/dist/templates/layout.js +0 -69
  74. package/dist/templates/list-table.mdx.d.ts +0 -1
  75. package/dist/templates/list-table.mdx.js +0 -85
  76. package/dist/templates/mdx/config.mdx.d.ts +0 -1
  77. package/dist/templates/mdx/config.mdx.js +0 -47
  78. package/dist/templates/mdx/deployment.mdx.d.ts +0 -1
  79. package/dist/templates/mdx/deployment.mdx.js +0 -61
  80. package/dist/templates/mdx/image-embeds.mdx.d.ts +0 -1
  81. package/dist/templates/mdx/image-embeds.mdx.js +0 -95
  82. package/dist/templates/mdx/links.mdx.d.ts +0 -1
  83. package/dist/templates/mdx/links.mdx.js +0 -45
  84. package/dist/templates/mdx/list-and-tables.mdx.d.ts +0 -1
  85. package/dist/templates/mdx/list-and-tables.mdx.js +0 -78
  86. package/dist/templates/mdx/list-table.mdx.d.ts +0 -1
  87. package/dist/templates/mdx/list-table.mdx.js +0 -78
  88. package/dist/templates/mdx/platform/deployments.mdx.d.ts +0 -1
  89. package/dist/templates/mdx/platform/deployments.mdx.js +0 -36
  90. package/dist/templates/mdx/platform/sections-settings.mdx.d.ts +0 -1
  91. package/dist/templates/mdx/platform/sections-settings.mdx.js +0 -46
  92. package/dist/templates/mdx/stepts.mdx.d.ts +0 -1
  93. package/dist/templates/mdx/stepts.mdx.js +0 -60
  94. package/dist/templates/mdx/text.mdx.d.ts +0 -1
  95. package/dist/templates/mdx/text.mdx.js +0 -139
  96. package/dist/templates/middleware.d.ts +0 -1
  97. package/dist/templates/middleware.js +0 -28
  98. package/dist/templates/not-found.d.ts +0 -1
  99. package/dist/templates/not-found.js +0 -22
  100. package/dist/templates/pictograms.d.ts +0 -1
  101. package/dist/templates/pictograms.js +0 -67
  102. package/dist/templates/shared-styles.d.ts +0 -1
  103. package/dist/templates/shared-styles.js +0 -792
  104. package/dist/templates/structures.d.ts +0 -2
  105. package/dist/templates/structures.js +0 -208
  106. package/dist/templates/tabs.mdx.d.ts +0 -1
  107. package/dist/templates/tabs.mdx.js +0 -59
  108. package/dist/templates/text.mdx.d.ts +0 -1
  109. package/dist/templates/text.mdx.js +0 -142
  110. package/dist/templates/theme-toggle.d.ts +0 -1
  111. package/dist/templates/theme-toggle.js +0 -111
  112. package/dist/templates/theme.d.ts +0 -1
  113. package/dist/templates/theme.js +0 -307
  114. package/dist/templates/typography.d.ts +0 -1
  115. package/dist/templates/typography.js +0 -52
  116. package/dist/templates/update.mdx.d.ts +0 -1
  117. package/dist/templates/update.mdx.js +0 -58
package/dist/index.js CHANGED
File without changes
@@ -1 +1 @@
1
- export declare const mdxComponentsTemplate = "import React from \"react\";\nimport Link from \"next/link\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype MDXComponents = Record<string, React.ComponentType<any>>;\nimport { Space } from \"cherry-styled-components\";\nimport { Code as CodeBlock } from \"@/components/layout/Code\";\nimport { Card } from \"@/components/layout/Card\";\nimport { Accordion } from \"@/components/layout/Accordion\";\nimport { Tabs, TabContent } from \"@/components/layout/Tabs\";\nimport { Callout } from \"@/components/layout/Callout\";\nimport { Icon } from \"@/components/layout/Icon\";\nimport { Columns } from \"@/components/layout/Columns\";\nimport { Field } from \"@/components/layout/Field\";\nimport { Update } from \"@/components/layout/Update\";\nimport { Steps, Step } from \"@/components/layout/Steps\";\nimport { Button } from \"@/components/layout/Button\";\nimport { ColorSwatch, ColorSwatchGroup } from \"@/components/layout/ColorSwatch\";\nimport { DemoTheme } from \"@/components/layout/DemoTheme\";\n\ninterface HeadingProps extends React.HTMLAttributes<HTMLHeadingElement> {\n children?: React.ReactNode;\n}\n\ninterface PreProps extends React.HTMLAttributes<HTMLPreElement> {\n children?: React.ReactNode;\n}\n\nfunction extractAllTextFromChildren(children: React.ReactNode): string {\n if (children == null) return \"\";\n if (typeof children === \"string\") return children;\n if (typeof children === \"number\") return String(children);\n if (typeof children === \"boolean\") return \"\";\n if (Array.isArray(children))\n return children.map(extractAllTextFromChildren).join(\"\");\n if (React.isValidElement(children)) {\n const element = children as React.ReactElement<{\n children?: React.ReactNode;\n }>;\n return extractAllTextFromChildren(element.props.children);\n }\n return \"\";\n}\n\nfunction generateId(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, \"\")\n .replace(/\\s+/g, \"-\")\n .trim();\n}\n\n// Map <pre><code class=\"language-xyz\"> to our <Code /> component\nfunction Pre(props: PreProps) {\n const child = React.Children.only(props.children) as React.ReactElement<{\n className?: string;\n children?: React.ReactNode;\n }> | null;\n if (child && child.type === \"code\") {\n const className = child.props.className || \"\";\n const match = /language-(\\w+)/.exec(className);\n const language = match ? match[1] : undefined;\n const code = extractAllTextFromChildren(child.props.children).replace(\n /\\n$/,\n \"\",\n );\n if (language) {\n return (\n <CodeBlock className={className} code={code} language={language} />\n );\n }\n }\n return <pre {...props} />;\n}\n\nexport function useMDXComponents(components: MDXComponents): MDXComponents {\n return {\n // Headings with auto-generated ids for TOC and deep links\n h1: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h1 id={id} {...props}>\n {children}\n </h1>\n );\n },\n h2: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h2 id={id} {...props}>\n {children}\n </h2>\n );\n },\n h3: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h3 id={id} {...props}>\n {children}\n </h3>\n );\n },\n h4: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h4 id={id} {...props}>\n {children}\n </h4>\n );\n },\n h5: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h5 id={id} {...props}>\n {children}\n </h5>\n );\n },\n h6: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h6 id={id} {...props}>\n {children}\n </h6>\n );\n },\n\n // Links - use Next.js Link for internal paths\n a: ({\n href,\n children,\n ...props\n }: React.AnchorHTMLAttributes<HTMLAnchorElement>) => {\n if (href && href.startsWith(\"/\")) {\n return (\n <Link href={href} {...props}>\n {children}\n </Link>\n );\n }\n return (\n <a href={href} target=\"_blank\" rel=\"noopener noreferrer\" {...props}>\n {children}\n </a>\n );\n },\n\n // Tables - wrap in a div for responsive overflow\n table: (props: React.TableHTMLAttributes<HTMLTableElement>) => (\n <div className=\"table-wrapper\">\n <table {...props} />\n </div>\n ),\n\n // Code blocks\n pre: Pre,\n\n // Expose your custom components for MDX usage\n Card,\n Accordion,\n Tabs,\n TabContent,\n Callout,\n Icon,\n Columns,\n Field,\n Update,\n Steps,\n Step,\n Button,\n ColorSwatch,\n ColorSwatchGroup,\n DemoTheme,\n Space,\n ...components,\n };\n}\n";
1
+ export declare const mdxComponentsTemplate = "import React from \"react\";\nimport Link from \"next/link\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype MDXComponents = Record<string, React.ComponentType<any>>;\nimport { Space } from \"cherry-styled-components\";\nimport { Code as CodeBlock, CodeTabs } from \"@/components/layout/Code\";\nimport { Card } from \"@/components/layout/Card\";\nimport { Accordion } from \"@/components/layout/Accordion\";\nimport { Tabs, TabContent } from \"@/components/layout/Tabs\";\nimport { Callout } from \"@/components/layout/Callout\";\nimport { Icon } from \"@/components/layout/Icon\";\nimport { Columns } from \"@/components/layout/Columns\";\nimport { Field } from \"@/components/layout/Field\";\nimport { Update } from \"@/components/layout/Update\";\nimport { Steps, Step } from \"@/components/layout/Steps\";\nimport { Button } from \"@/components/layout/Button\";\nimport { ColorSwatch, ColorSwatchGroup } from \"@/components/layout/ColorSwatch\";\nimport { DemoTheme } from \"@/components/layout/DemoTheme\";\n\ninterface HeadingProps extends React.HTMLAttributes<HTMLHeadingElement> {\n children?: React.ReactNode;\n}\n\ninterface PreProps extends React.HTMLAttributes<HTMLPreElement> {\n children?: React.ReactNode;\n}\n\nfunction extractAllTextFromChildren(children: React.ReactNode): string {\n if (children == null) return \"\";\n if (typeof children === \"string\") return children;\n if (typeof children === \"number\") return String(children);\n if (typeof children === \"boolean\") return \"\";\n if (Array.isArray(children))\n return children.map(extractAllTextFromChildren).join(\"\");\n if (React.isValidElement(children)) {\n const element = children as React.ReactElement<{\n children?: React.ReactNode;\n }>;\n return extractAllTextFromChildren(element.props.children);\n }\n return \"\";\n}\n\nfunction generateId(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, \"\")\n .replace(/\\s+/g, \"-\")\n .trim();\n}\n\n// Map <pre><code class=\"language-xyz\"> to our <Code /> component\nfunction Pre(props: PreProps) {\n const child = React.Children.only(props.children) as React.ReactElement<{\n className?: string;\n children?: React.ReactNode;\n }> | null;\n if (child && child.type === \"code\") {\n const className = child.props.className || \"\";\n const match = /language-(\\w+)/.exec(className);\n const language = match ? match[1] : undefined;\n const code = extractAllTextFromChildren(child.props.children).replace(\n /\\n$/,\n \"\",\n );\n if (language) {\n return (\n <CodeBlock className={className} code={code} language={language} />\n );\n }\n }\n return <pre {...props} />;\n}\n\nexport function useMDXComponents(components: MDXComponents): MDXComponents {\n return {\n // Headings with auto-generated ids for TOC and deep links\n h1: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h1 id={id} {...props}>\n {children}\n </h1>\n );\n },\n h2: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h2 id={id} {...props}>\n {children}\n </h2>\n );\n },\n h3: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h3 id={id} {...props}>\n {children}\n </h3>\n );\n },\n h4: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h4 id={id} {...props}>\n {children}\n </h4>\n );\n },\n h5: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h5 id={id} {...props}>\n {children}\n </h5>\n );\n },\n h6: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h6 id={id} {...props}>\n {children}\n </h6>\n );\n },\n\n // Links - use Next.js Link for internal paths\n a: ({\n href,\n children,\n ...props\n }: React.AnchorHTMLAttributes<HTMLAnchorElement>) => {\n if (href && href.startsWith(\"/\")) {\n return (\n <Link href={href} {...props}>\n {children}\n </Link>\n );\n }\n return (\n <a href={href} target=\"_blank\" rel=\"noopener noreferrer\" {...props}>\n {children}\n </a>\n );\n },\n\n // Tables - wrap in a div for responsive overflow\n table: (props: React.TableHTMLAttributes<HTMLTableElement>) => (\n <div className=\"table-wrapper\">\n <table {...props} />\n </div>\n ),\n\n // Code blocks\n pre: Pre,\n\n // Expose your custom components for MDX usage\n Code: CodeBlock,\n CodeTabs,\n Card,\n Accordion,\n Tabs,\n TabContent,\n Callout,\n Icon,\n Columns,\n Field,\n Update,\n Steps,\n Step,\n Button,\n ColorSwatch,\n ColorSwatchGroup,\n DemoTheme,\n Space,\n ...components,\n };\n}\n";
@@ -4,7 +4,7 @@ import Link from "next/link";
4
4
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5
5
  type MDXComponents = Record<string, React.ComponentType<any>>;
6
6
  import { Space } from "cherry-styled-components";
7
- import { Code as CodeBlock } from "@/components/layout/Code";
7
+ import { Code as CodeBlock, CodeTabs } from "@/components/layout/Code";
8
8
  import { Card } from "@/components/layout/Card";
9
9
  import { Accordion } from "@/components/layout/Accordion";
10
10
  import { Tabs, TabContent } from "@/components/layout/Tabs";
@@ -156,6 +156,8 @@ export function useMDXComponents(components: MDXComponents): MDXComponents {
156
156
  pre: Pre,
157
157
 
158
158
  // Expose your custom components for MDX usage
159
+ Code: CodeBlock,
160
+ CodeTabs,
159
161
  Card,
160
162
  Accordion,
161
163
  Tabs,
@@ -1 +1 @@
1
- export declare const codeTemplate = "\"use client\";\nimport { useState, useCallback, useMemo } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport {\n interactiveStyles,\n resetButton,\n styledCode,\n} from \"cherry-styled-components\";\nimport { Theme } from \"@/app/theme\";\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\";\nimport { thinScrollbar } from \"@/components/layout/SharedStyled\";\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 rgba(0, 0, 0, 0.1);\n\n :root.dark & {\n border-color: rgba(255, 255, 255, 0.2);\n }\n`;\n\n/* Code block uses a fixed GitHub-style palette in both modes. Independent of\n theme.json so syntax highlighting stays legible regardless of brand colors.\n Dark variants live in :root.dark & blocks so the swap happens via the\n active <html> class with no re-render. */\nconst TopBar = styled.div<{ theme: Theme }>`\n background: #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 rgba(0, 0, 0, 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 :root.dark & {\n background: #0d1117;\n border-bottom-color: rgba(255, 255, 255, 0.1);\n }\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: rgba(0, 0, 0, 0.1);\n\n :root.dark & {\n background: rgba(255, 255, 255, 0.1);\n }\n`;\n\n/* Icon-only copy button. interactiveStyles supplies the border highlight on\n hover plus the focus/active rings (no scale effect); the GitHub-style\n copied/base colors stay fixed like the rest of the code block. The dark\n block re-declares the hover border because its higher-specificity\n :root.dark & border-color would otherwise override the mixin's hover. */\nconst CopyButton = styled.button<{ theme: Theme; $copied: boolean }>`\n ${resetButton}\n ${interactiveStyles}\n background: ${({ $copied }) =>\n $copied ? \"rgba(45, 164, 78, 0.1)\" : \"transparent\"};\n border-color: ${({ $copied }) => ($copied ? \"#2da44e\" : \"rgba(0, 0, 0, 0.1)\")};\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n padding: 4px;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-right: -6px;\n\n & svg.lucide {\n color: ${({ $copied }) => ($copied ? \"#2da44e\" : \"#57606a\")};\n }\n\n :root.dark & {\n background: ${({ $copied }) =>\n $copied ? \"rgba(126, 231, 135, 0.2)\" : \"transparent\"};\n border-color: ${({ $copied }) =>\n $copied ? \"#7ee787\" : \"rgba(255, 255, 255, 0.1)\"};\n\n & svg.lucide {\n color: ${({ $copied }) => ($copied ? \"#7ee787\" : \"#c9d1d9\")};\n }\n\n &:hover {\n border-color: ${({ theme }) => theme.colors.primary};\n }\n }\n`;\n\n/* GitHub Light syntax highlighting by default; GitHub Dark in :root.dark.\n Browser resolves which rule wins based on the active <html> class with no\n JS or React re-render involved. */\nconst lightSyntaxHighlight = css`\n & .hljs {\n color: #24292f;\n background: #ffffff;\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 & .hljs-title,\n & .hljs-title.class_,\n & .hljs-title.class_.inherited__,\n & .hljs-title.function_ {\n color: #8250df;\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 & .hljs-meta .hljs-string,\n & .hljs-regexp,\n & .hljs-string {\n color: #0a3069;\n }\n & .hljs-built_in,\n & .hljs-symbol {\n color: #953800;\n }\n & .hljs-code,\n & .hljs-comment,\n & .hljs-formula {\n color: #6e7781;\n }\n & .hljs-name,\n & .hljs-quote,\n & .hljs-selector-pseudo,\n & .hljs-selector-tag {\n color: #116329;\n }\n & .hljs-subst {\n color: #24292f;\n }\n & .hljs-section {\n color: #0550ae;\n font-weight: 700;\n }\n & .hljs-bullet {\n color: #953800;\n }\n & .hljs-emphasis {\n color: #24292f;\n font-style: italic;\n }\n & .hljs-strong {\n color: #24292f;\n font-weight: 700;\n }\n & .hljs-addition {\n color: #116329;\n background-color: #dafbe1;\n }\n & .hljs-deletion {\n color: #82071e;\n background-color: #ffebe9;\n }\n`;\n\nconst darkSyntaxHighlight = css`\n & .hljs {\n color: #c9d1d9;\n background: #0d1117;\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 & .hljs-title,\n & .hljs-title.class_,\n & .hljs-title.class_.inherited__,\n & .hljs-title.function_ {\n color: #d2a8ff;\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 & .hljs-meta .hljs-string,\n & .hljs-regexp,\n & .hljs-string {\n color: #a5d6ff;\n }\n & .hljs-built_in,\n & .hljs-symbol {\n color: #ffa657;\n }\n & .hljs-code,\n & .hljs-comment,\n & .hljs-formula {\n color: #8b949e;\n }\n & .hljs-name,\n & .hljs-quote,\n & .hljs-selector-pseudo,\n & .hljs-selector-tag {\n color: #7ee787;\n }\n & .hljs-subst {\n color: #c9d1d9;\n }\n & .hljs-section {\n color: #1f6feb;\n font-weight: 700;\n }\n & .hljs-bullet {\n color: #f2cc60;\n }\n & .hljs-emphasis {\n color: #c9d1d9;\n font-style: italic;\n }\n & .hljs-strong {\n color: #c9d1d9;\n font-weight: 700;\n }\n & .hljs-addition {\n color: #aff5b4;\n background-color: #033a16;\n }\n & .hljs-deletion {\n color: #ffdcd7;\n background-color: #67060c;\n }\n`;\n\nconst Body = styled.div<{ theme: Theme }>`\n background: #ffffff;\n border-bottom-left-radius: ${({ theme }) => theme.spacing.radius.lg};\n border-bottom-right-radius: ${({ theme }) => theme.spacing.radius.lg};\n color: #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 ${thinScrollbar};\n ${({ theme }) => styledCode(theme)};\n ${lightSyntaxHighlight};\n\n :root.dark & {\n background: #0d1117;\n color: #ffffff;\n ${darkSyntaxHighlight};\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\n onClick={handleCopy}\n $copied={copied}\n theme={theme}\n aria-label={copied ? \"Copied\" : \"Copy code\"}\n >\n <Icon name={copied ? \"check\" : \"copy\"} size={12} />\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, useId } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport {\n interactiveStyles,\n resetButton,\n styledCode,\n} from \"cherry-styled-components\";\nimport { Theme } from \"@/app/theme\";\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\";\nimport { thinScrollbar } from \"@/components/layout/SharedStyled\";\n\ninterface CodeProps extends Omit<\n React.HTMLAttributes<HTMLDivElement>,\n \"theme\"\n> {\n code: string;\n language?: string;\n title?: string;\n theme?: Theme;\n}\n\ninterface CodeTab {\n label: string;\n code: string;\n language?: string;\n}\n\ninterface CodeTabsProps extends Omit<\n React.HTMLAttributes<HTMLDivElement>,\n \"theme\"\n> {\n tabs: CodeTab[];\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 rgba(0, 0, 0, 0.1);\n\n :root.dark & {\n border-color: rgba(255, 255, 255, 0.2);\n }\n`;\n\n/* Code block uses a fixed GitHub-style palette in both modes. Independent of\n theme.json so syntax highlighting stays legible regardless of brand colors.\n Dark variants live in :root.dark & blocks so the swap happens via the\n active <html> class with no re-render. */\nconst TopBar = styled.div<{ theme: Theme }>`\n position: relative;\n background: #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 rgba(0, 0, 0, 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 :root.dark & {\n background: #0d1117;\n border-bottom-color: rgba(255, 255, 255, 0.1);\n }\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: rgba(0, 0, 0, 0.1);\n\n :root.dark & {\n background: rgba(255, 255, 255, 0.1);\n }\n`;\n\n/* Icon-only copy button. interactiveStyles supplies the border highlight on\n hover plus the focus/active rings (no scale effect); the GitHub-style\n copied/base colors stay fixed like the rest of the code block. The dark\n block re-declares the hover border because its higher-specificity\n :root.dark & border-color would otherwise override the mixin's hover. */\nconst CopyButton = styled.button<{ theme: Theme; $copied: boolean }>`\n ${resetButton}\n ${interactiveStyles}\n background: ${({ $copied }) =>\n $copied ? \"rgba(45, 164, 78, 0.1)\" : \"transparent\"};\n border-color: ${({ $copied }) => ($copied ? \"#2da44e\" : \"rgba(0, 0, 0, 0.1)\")};\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n padding: 4px;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-right: -6px;\n\n & svg.lucide {\n color: ${({ $copied }) => ($copied ? \"#2da44e\" : \"#57606a\")};\n }\n\n :root.dark & {\n background: ${({ $copied }) =>\n $copied ? \"rgba(126, 231, 135, 0.2)\" : \"transparent\"};\n border-color: ${({ $copied }) =>\n $copied ? \"#7ee787\" : \"rgba(255, 255, 255, 0.1)\"};\n\n & svg.lucide {\n color: ${({ $copied }) => ($copied ? \"#7ee787\" : \"#c9d1d9\")};\n }\n\n &:hover {\n border-color: ${({ theme }) => theme.colors.primary};\n }\n }\n`;\n\n/* Centered file name in the TopBar. Sits absolutely over the flex row so the\n dots and copy button keep their edge alignment. Monospace + muted GitHub\n grays, swapped via :root.dark like the rest of the block. pointer-events off\n so clicks fall through to nothing and never steal focus from the buttons. */\nconst TopBarTitle = styled.span<{ theme: Theme }>`\n position: absolute;\n left: 50%;\n transform: translateX(-50%);\n max-width: 60%;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n pointer-events: none;\n font-family: ${({ theme }) => theme.fonts.mono};\n font-size: 12px;\n line-height: 2;\n color: #57606a;\n\n :root.dark & {\n color: #8b949e;\n }\n`;\n\n/* Segmented control living in the TopBar for CodeTabs. Scrolls horizontally\n with the thin scrollbar when the labels overflow the 33px bar. */\nconst TabList = styled.div<{ theme: Theme }>`\n display: flex;\n align-items: center;\n gap: 2px;\n min-width: 0;\n overflow-x: auto;\n ${thinScrollbar};\n margin-left: -6px;\n`;\n\n/* Individual tab button. Active tab reads as part of the window: a light body\n fill (#ffffff) that echoes the code area, with a soft border. Inactive tabs\n are muted and transparent. resetButton strips native styling; focus-visible\n uses the brand primary ring for keyboard users. All colors stay fixed and\n swap purely via :root.dark, matching the no-re-render palette approach. */\nconst CodeTab = styled.button<{ theme: Theme; $active: boolean }>`\n ${resetButton}\n flex: 0 0 auto;\n font-family: ${({ theme }) => theme.fonts.mono};\n font-size: 12px;\n line-height: 1;\n padding: 5px;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n cursor: pointer;\n white-space: nowrap;\n transition:\n color 0.15s ease,\n background 0.15s ease,\n border-color 0.15s ease;\n border: solid 1px\n ${({ $active }) => ($active ? \"rgba(0, 0, 0, 0.1)\" : \"transparent\")};\n background: ${({ $active }) => ($active ? \"#ffffff\" : \"transparent\")};\n color: ${({ $active }) => ($active ? \"#24292f\" : \"#57606a\")};\n\n &:hover {\n color: #24292f;\n background: ${({ $active }) =>\n $active ? \"#ffffff\" : \"rgba(0, 0, 0, 0.04)\"};\n }\n\n &:focus-visible {\n outline: solid 2px ${({ theme }) => theme.colors.primary};\n outline-offset: 1px;\n }\n\n :root.dark & {\n border-color: ${({ $active }) =>\n $active ? \"rgba(255, 255, 255, 0.15)\" : \"transparent\"};\n background: ${({ $active }) => ($active ? \"#161b22\" : \"transparent\")};\n color: ${({ $active }) => ($active ? \"#e6edf3\" : \"#8b949e\")};\n\n &:hover {\n color: #e6edf3;\n background: ${({ $active }) =>\n $active ? \"#161b22\" : \"rgba(255, 255, 255, 0.06)\"};\n }\n }\n`;\n\n/* GitHub Light syntax highlighting by default; GitHub Dark in :root.dark.\n Browser resolves which rule wins based on the active <html> class with no\n JS or React re-render involved. */\nconst lightSyntaxHighlight = css`\n & .hljs {\n color: #24292f;\n background: #ffffff;\n min-width: min-content;\n width: 100%;\n display: block;\n padding-right: 20px;\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 & .hljs-title,\n & .hljs-title.class_,\n & .hljs-title.class_.inherited__,\n & .hljs-title.function_ {\n color: #8250df;\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 & .hljs-meta .hljs-string,\n & .hljs-regexp,\n & .hljs-string {\n color: #0a3069;\n }\n & .hljs-built_in,\n & .hljs-symbol {\n color: #953800;\n }\n & .hljs-code,\n & .hljs-comment,\n & .hljs-formula {\n color: #6e7781;\n }\n & .hljs-name,\n & .hljs-quote,\n & .hljs-selector-pseudo,\n & .hljs-selector-tag {\n color: #116329;\n }\n & .hljs-subst {\n color: #24292f;\n }\n & .hljs-section {\n color: #0550ae;\n font-weight: 700;\n }\n & .hljs-bullet {\n color: #953800;\n }\n & .hljs-emphasis {\n color: #24292f;\n font-style: italic;\n }\n & .hljs-strong {\n color: #24292f;\n font-weight: 700;\n }\n & .hljs-addition {\n color: #116329;\n background-color: #dafbe1;\n }\n & .hljs-deletion {\n color: #82071e;\n background-color: #ffebe9;\n }\n`;\n\nconst darkSyntaxHighlight = css`\n & .hljs {\n color: #c9d1d9;\n background: #0d1117;\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 & .hljs-title,\n & .hljs-title.class_,\n & .hljs-title.class_.inherited__,\n & .hljs-title.function_ {\n color: #d2a8ff;\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 & .hljs-meta .hljs-string,\n & .hljs-regexp,\n & .hljs-string {\n color: #a5d6ff;\n }\n & .hljs-built_in,\n & .hljs-symbol {\n color: #ffa657;\n }\n & .hljs-code,\n & .hljs-comment,\n & .hljs-formula {\n color: #8b949e;\n }\n & .hljs-name,\n & .hljs-quote,\n & .hljs-selector-pseudo,\n & .hljs-selector-tag {\n color: #7ee787;\n }\n & .hljs-subst {\n color: #c9d1d9;\n }\n & .hljs-section {\n color: #1f6feb;\n font-weight: 700;\n }\n & .hljs-bullet {\n color: #f2cc60;\n }\n & .hljs-emphasis {\n color: #c9d1d9;\n font-style: italic;\n }\n & .hljs-strong {\n color: #c9d1d9;\n font-weight: 700;\n }\n & .hljs-addition {\n color: #aff5b4;\n background-color: #033a16;\n }\n & .hljs-deletion {\n color: #ffdcd7;\n background-color: #67060c;\n }\n`;\n\nconst Body = styled.div<{ theme: Theme }>`\n background: #ffffff;\n border-bottom-left-radius: ${({ theme }) => theme.spacing.radius.lg};\n border-bottom-right-radius: ${({ theme }) => theme.spacing.radius.lg};\n color: #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 ${thinScrollbar};\n ${({ theme }) => styledCode(theme)};\n ${lightSyntaxHighlight};\n\n /* Diff lines: highlight.js wraps each +/- line in a single span, so\n stretching it to the full row reads like a GitHub diff. inline-block keeps\n the trailing newline as the line break instead of adding an empty row. */\n & .hljs-addition,\n & .hljs-deletion {\n display: inline-table;\n width: 100%;\n }\n\n :root.dark & {\n background: #0d1117;\n color: #ffffff;\n ${darkSyntaxHighlight};\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({\n code,\n language = \"javascript\",\n title,\n theme,\n className,\n}: 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 {title ? <TopBarTitle theme={theme}>{title}</TopBarTitle> : null}\n <CopyButton\n onClick={handleCopy}\n $copied={copied}\n theme={theme}\n aria-label={copied ? \"Copied\" : \"Copy code\"}\n >\n <Icon name={copied ? \"check\" : \"copy\"} size={12} />\n </CopyButton>\n </TopBar>\n <Body\n dangerouslySetInnerHTML={{ __html: highlightedCode }}\n theme={theme}\n className=\"code-wrapper-body\"\n />\n </CodeWrapper>\n );\n}\n\n/* Multi-variant code block (npm / pnpm / yarn, etc). Tabs replace the macOS\n dots in the TopBar as a keyboard-accessible tablist; the copy button copies\n whichever tab is active and resets its copied state when the tab changes. */\nfunction CodeTabs({ tabs, theme, className }: CodeTabsProps) {\n const baseId = useId();\n const [active, setActive] = useState(0);\n const [copied, setCopied] = useState(false);\n\n const safeActive = active < tabs.length ? active : 0;\n const activeTab = tabs[safeActive];\n\n const highlightedCode = useMemo(\n () =>\n activeTab\n ? highlightCode(activeTab.code, activeTab.language ?? \"bash\")\n : \"\",\n [activeTab],\n );\n\n const handleSelect = useCallback((index: number) => {\n setActive(index);\n setCopied(false);\n }, []);\n\n const handleCopy = useCallback(async () => {\n if (!activeTab) return;\n try {\n await navigator.clipboard.writeText(activeTab.code);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n } catch (err) {\n console.error(\"Failed to copy code:\", err);\n }\n }, [activeTab]);\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLButtonElement>) => {\n let next = safeActive;\n if (event.key === \"ArrowRight\" || event.key === \"ArrowDown\") {\n next = (safeActive + 1) % tabs.length;\n } else if (event.key === \"ArrowLeft\" || event.key === \"ArrowUp\") {\n next = (safeActive - 1 + tabs.length) % tabs.length;\n } else if (event.key === \"Home\") {\n next = 0;\n } else if (event.key === \"End\") {\n next = tabs.length - 1;\n } else {\n return;\n }\n event.preventDefault();\n handleSelect(next);\n const target = document.getElementById(`${baseId}-tab-${next}`);\n if (target) target.focus();\n },\n [baseId, handleSelect, safeActive, tabs.length],\n );\n\n if (!tabs || tabs.length === 0) return null;\n\n return (\n <CodeWrapper\n className={`${className ?? \"\"} code-wrapper`.trim()}\n theme={theme}\n >\n <TopBar theme={theme}>\n <TabList role=\"tablist\" aria-label=\"Code variants\">\n {tabs.map((tab, index) => (\n <CodeTab\n key={`${tab.label}-${index}`}\n type=\"button\"\n role=\"tab\"\n id={`${baseId}-tab-${index}`}\n aria-selected={index === safeActive}\n aria-controls={`${baseId}-panel`}\n tabIndex={index === safeActive ? 0 : -1}\n onClick={() => handleSelect(index)}\n onKeyDown={handleKeyDown}\n $active={index === safeActive}\n theme={theme}\n >\n {tab.label}\n </CodeTab>\n ))}\n </TabList>\n <CopyButton\n onClick={handleCopy}\n $copied={copied}\n theme={theme}\n aria-label={copied ? \"Copied\" : \"Copy code\"}\n >\n <Icon name={copied ? \"check\" : \"copy\"} size={12} />\n </CopyButton>\n </TopBar>\n <Body\n id={`${baseId}-panel`}\n role=\"tabpanel\"\n aria-labelledby={`${baseId}-tab-${safeActive}`}\n dangerouslySetInnerHTML={{ __html: highlightedCode }}\n theme={theme}\n className=\"code-wrapper-body\"\n />\n </CodeWrapper>\n );\n}\n\nexport { Code, CodeTabs };\n";
@@ -1,5 +1,5 @@
1
1
  export const codeTemplate = `"use client";
2
- import { useState, useCallback, useMemo } from "react";
2
+ import { useState, useCallback, useMemo, useId } from "react";
3
3
  import styled, { css } from "styled-components";
4
4
  import {
5
5
  interactiveStyles,
@@ -20,6 +20,21 @@ interface CodeProps extends Omit<
20
20
  > {
21
21
  code: string;
22
22
  language?: string;
23
+ title?: string;
24
+ theme?: Theme;
25
+ }
26
+
27
+ interface CodeTab {
28
+ label: string;
29
+ code: string;
30
+ language?: string;
31
+ }
32
+
33
+ interface CodeTabsProps extends Omit<
34
+ React.HTMLAttributes<HTMLDivElement>,
35
+ "theme"
36
+ > {
37
+ tabs: CodeTab[];
23
38
  theme?: Theme;
24
39
  }
25
40
 
@@ -41,6 +56,7 @@ const CodeWrapper = styled.span<{ theme: Theme }>\`
41
56
  Dark variants live in :root.dark & blocks so the swap happens via the
42
57
  active <html> class with no re-render. */
43
58
  const TopBar = styled.div<{ theme: Theme }>\`
59
+ position: relative;
44
60
  background: #f6f8fa;
45
61
  border-top-left-radius: \${({ theme }) => theme.spacing.radius.lg};
46
62
  border-top-right-radius: \${({ theme }) => theme.spacing.radius.lg};
@@ -113,6 +129,90 @@ const CopyButton = styled.button<{ theme: Theme; $copied: boolean }>\`
113
129
  }
114
130
  \`;
115
131
 
132
+ /* Centered file name in the TopBar. Sits absolutely over the flex row so the
133
+ dots and copy button keep their edge alignment. Monospace + muted GitHub
134
+ grays, swapped via :root.dark like the rest of the block. pointer-events off
135
+ so clicks fall through to nothing and never steal focus from the buttons. */
136
+ const TopBarTitle = styled.span<{ theme: Theme }>\`
137
+ position: absolute;
138
+ left: 50%;
139
+ transform: translateX(-50%);
140
+ max-width: 60%;
141
+ overflow: hidden;
142
+ text-overflow: ellipsis;
143
+ white-space: nowrap;
144
+ pointer-events: none;
145
+ font-family: \${({ theme }) => theme.fonts.mono};
146
+ font-size: 12px;
147
+ line-height: 2;
148
+ color: #57606a;
149
+
150
+ :root.dark & {
151
+ color: #8b949e;
152
+ }
153
+ \`;
154
+
155
+ /* Segmented control living in the TopBar for CodeTabs. Scrolls horizontally
156
+ with the thin scrollbar when the labels overflow the 33px bar. */
157
+ const TabList = styled.div<{ theme: Theme }>\`
158
+ display: flex;
159
+ align-items: center;
160
+ gap: 2px;
161
+ min-width: 0;
162
+ overflow-x: auto;
163
+ \${thinScrollbar};
164
+ margin-left: -6px;
165
+ \`;
166
+
167
+ /* Individual tab button. Active tab reads as part of the window: a light body
168
+ fill (#ffffff) that echoes the code area, with a soft border. Inactive tabs
169
+ are muted and transparent. resetButton strips native styling; focus-visible
170
+ uses the brand primary ring for keyboard users. All colors stay fixed and
171
+ swap purely via :root.dark, matching the no-re-render palette approach. */
172
+ const CodeTab = styled.button<{ theme: Theme; $active: boolean }>\`
173
+ \${resetButton}
174
+ flex: 0 0 auto;
175
+ font-family: \${({ theme }) => theme.fonts.mono};
176
+ font-size: 12px;
177
+ line-height: 1;
178
+ padding: 5px;
179
+ border-radius: \${({ theme }) => theme.spacing.radius.xs};
180
+ cursor: pointer;
181
+ white-space: nowrap;
182
+ transition:
183
+ color 0.15s ease,
184
+ background 0.15s ease,
185
+ border-color 0.15s ease;
186
+ border: solid 1px
187
+ \${({ $active }) => ($active ? "rgba(0, 0, 0, 0.1)" : "transparent")};
188
+ background: \${({ $active }) => ($active ? "#ffffff" : "transparent")};
189
+ color: \${({ $active }) => ($active ? "#24292f" : "#57606a")};
190
+
191
+ &:hover {
192
+ color: #24292f;
193
+ background: \${({ $active }) =>
194
+ $active ? "#ffffff" : "rgba(0, 0, 0, 0.04)"};
195
+ }
196
+
197
+ &:focus-visible {
198
+ outline: solid 2px \${({ theme }) => theme.colors.primary};
199
+ outline-offset: 1px;
200
+ }
201
+
202
+ :root.dark & {
203
+ border-color: \${({ $active }) =>
204
+ $active ? "rgba(255, 255, 255, 0.15)" : "transparent"};
205
+ background: \${({ $active }) => ($active ? "#161b22" : "transparent")};
206
+ color: \${({ $active }) => ($active ? "#e6edf3" : "#8b949e")};
207
+
208
+ &:hover {
209
+ color: #e6edf3;
210
+ background: \${({ $active }) =>
211
+ $active ? "#161b22" : "rgba(255, 255, 255, 0.06)"};
212
+ }
213
+ }
214
+ \`;
215
+
116
216
  /* GitHub Light syntax highlighting by default; GitHub Dark in :root.dark.
117
217
  Browser resolves which rule wins based on the active <html> class with no
118
218
  JS or React re-render involved. */
@@ -120,6 +220,10 @@ const lightSyntaxHighlight = css\`
120
220
  & .hljs {
121
221
  color: #24292f;
122
222
  background: #ffffff;
223
+ min-width: min-content;
224
+ width: 100%;
225
+ display: block;
226
+ padding-right: 20px;
123
227
  }
124
228
  & .hljs-doctag,
125
229
  & .hljs-keyword,
@@ -291,6 +395,15 @@ const Body = styled.div<{ theme: Theme }>\`
291
395
  \${({ theme }) => styledCode(theme)};
292
396
  \${lightSyntaxHighlight};
293
397
 
398
+ /* Diff lines: highlight.js wraps each +/- line in a single span, so
399
+ stretching it to the full row reads like a GitHub diff. inline-block keeps
400
+ the trailing newline as the line break instead of adding an empty row. */
401
+ & .hljs-addition,
402
+ & .hljs-deletion {
403
+ display: inline-table;
404
+ width: 100%;
405
+ }
406
+
294
407
  :root.dark & {
295
408
  background: #0d1117;
296
409
  color: #ffffff;
@@ -327,7 +440,13 @@ const highlightCode = (code: string, language: string): string => {
327
440
  return String(result);
328
441
  };
329
442
 
330
- function Code({ code, language = "javascript", theme, className }: CodeProps) {
443
+ function Code({
444
+ code,
445
+ language = "javascript",
446
+ title,
447
+ theme,
448
+ className,
449
+ }: CodeProps) {
331
450
  const [copied, setCopied] = useState(false);
332
451
  const highlightedCode = useMemo(
333
452
  () => highlightCode(code, language),
@@ -355,6 +474,109 @@ function Code({ code, language = "javascript", theme, className }: CodeProps) {
355
474
  <Dot theme={theme} />
356
475
  <Dot theme={theme} />
357
476
  </DotsContainer>
477
+ {title ? <TopBarTitle theme={theme}>{title}</TopBarTitle> : null}
478
+ <CopyButton
479
+ onClick={handleCopy}
480
+ $copied={copied}
481
+ theme={theme}
482
+ aria-label={copied ? "Copied" : "Copy code"}
483
+ >
484
+ <Icon name={copied ? "check" : "copy"} size={12} />
485
+ </CopyButton>
486
+ </TopBar>
487
+ <Body
488
+ dangerouslySetInnerHTML={{ __html: highlightedCode }}
489
+ theme={theme}
490
+ className="code-wrapper-body"
491
+ />
492
+ </CodeWrapper>
493
+ );
494
+ }
495
+
496
+ /* Multi-variant code block (npm / pnpm / yarn, etc). Tabs replace the macOS
497
+ dots in the TopBar as a keyboard-accessible tablist; the copy button copies
498
+ whichever tab is active and resets its copied state when the tab changes. */
499
+ function CodeTabs({ tabs, theme, className }: CodeTabsProps) {
500
+ const baseId = useId();
501
+ const [active, setActive] = useState(0);
502
+ const [copied, setCopied] = useState(false);
503
+
504
+ const safeActive = active < tabs.length ? active : 0;
505
+ const activeTab = tabs[safeActive];
506
+
507
+ const highlightedCode = useMemo(
508
+ () =>
509
+ activeTab
510
+ ? highlightCode(activeTab.code, activeTab.language ?? "bash")
511
+ : "",
512
+ [activeTab],
513
+ );
514
+
515
+ const handleSelect = useCallback((index: number) => {
516
+ setActive(index);
517
+ setCopied(false);
518
+ }, []);
519
+
520
+ const handleCopy = useCallback(async () => {
521
+ if (!activeTab) return;
522
+ try {
523
+ await navigator.clipboard.writeText(activeTab.code);
524
+ setCopied(true);
525
+ setTimeout(() => setCopied(false), 2000);
526
+ } catch (err) {
527
+ console.error("Failed to copy code:", err);
528
+ }
529
+ }, [activeTab]);
530
+
531
+ const handleKeyDown = useCallback(
532
+ (event: React.KeyboardEvent<HTMLButtonElement>) => {
533
+ let next = safeActive;
534
+ if (event.key === "ArrowRight" || event.key === "ArrowDown") {
535
+ next = (safeActive + 1) % tabs.length;
536
+ } else if (event.key === "ArrowLeft" || event.key === "ArrowUp") {
537
+ next = (safeActive - 1 + tabs.length) % tabs.length;
538
+ } else if (event.key === "Home") {
539
+ next = 0;
540
+ } else if (event.key === "End") {
541
+ next = tabs.length - 1;
542
+ } else {
543
+ return;
544
+ }
545
+ event.preventDefault();
546
+ handleSelect(next);
547
+ const target = document.getElementById(\`\${baseId}-tab-\${next}\`);
548
+ if (target) target.focus();
549
+ },
550
+ [baseId, handleSelect, safeActive, tabs.length],
551
+ );
552
+
553
+ if (!tabs || tabs.length === 0) return null;
554
+
555
+ return (
556
+ <CodeWrapper
557
+ className={\`\${className ?? ""} code-wrapper\`.trim()}
558
+ theme={theme}
559
+ >
560
+ <TopBar theme={theme}>
561
+ <TabList role="tablist" aria-label="Code variants">
562
+ {tabs.map((tab, index) => (
563
+ <CodeTab
564
+ key={\`\${tab.label}-\${index}\`}
565
+ type="button"
566
+ role="tab"
567
+ id={\`\${baseId}-tab-\${index}\`}
568
+ aria-selected={index === safeActive}
569
+ aria-controls={\`\${baseId}-panel\`}
570
+ tabIndex={index === safeActive ? 0 : -1}
571
+ onClick={() => handleSelect(index)}
572
+ onKeyDown={handleKeyDown}
573
+ $active={index === safeActive}
574
+ theme={theme}
575
+ >
576
+ {tab.label}
577
+ </CodeTab>
578
+ ))}
579
+ </TabList>
358
580
  <CopyButton
359
581
  onClick={handleCopy}
360
582
  $copied={copied}
@@ -365,6 +587,9 @@ function Code({ code, language = "javascript", theme, className }: CodeProps) {
365
587
  </CopyButton>
366
588
  </TopBar>
367
589
  <Body
590
+ id={\`\${baseId}-panel\`}
591
+ role="tabpanel"
592
+ aria-labelledby={\`\${baseId}-tab-\${safeActive}\`}
368
593
  dangerouslySetInnerHTML={{ __html: highlightedCode }}
369
594
  theme={theme}
370
595
  className="code-wrapper-body"
@@ -373,5 +598,5 @@ function Code({ code, language = "javascript", theme, className }: CodeProps) {
373
598
  );
374
599
  }
375
600
 
376
- export { Code };
601
+ export { Code, CodeTabs };
377
602
  `;
@@ -1 +1 @@
1
- export declare const codeMdxTemplate = "---\ntitle: \"Code\"\ndescription: \"Learn how to display inline code and code blocks in documentation.\"\ndate: \"2026-02-19\"\ncategory: \"Components\"\ncategoryOrder: 1\norder: 3\n---\n\n# Code\n\nLearn how to display inline code and code blocks in documentation.\n\n## Adding Code Samples\n\nBoth inline code snippets and full code blocks are supported. Code blocks offer customization for syntax highlighting and more to improve readability and user experience.\n\n### Inline Code\n\nHighlight code within text by wrapping it with backticks:\n\n```text\nEnclose any `word` or `phrase` in backticks to format it as code.\n```\n\nEnclose any `word` or `phrase` in backticks to format it as code.\n\n## Code Blocks\n\nTo present larger code samples, use triple backticks for fenced code blocks. Each block can be copied, and\u2014if assistant features are enabled\u2014users can request explanations.\n\nYou may specify the language for highlighting:\n\n````text\n```java\nclass HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello, World!\");\n }\n}\n```\n````\n\n```java\nclass HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello, World!\");\n }\n}\n```";
1
+ export declare const codeMdxTemplate = "---\ntitle: \"Code\"\ndescription: \"Learn how to display inline code and code blocks in documentation.\"\ndate: \"2026-02-19\"\ncategory: \"Components\"\ncategoryOrder: 1\norder: 3\n---\n\n# Code\n\nLearn how to display inline code and code blocks in documentation.\n\n## Adding Code Samples\n\nBoth inline code snippets and full code blocks are supported. Code blocks offer customization for syntax highlighting and more to improve readability and user experience.\n\n### Inline Code\n\nHighlight code within text by wrapping it with backticks:\n\n```text\nEnclose any `word` or `phrase` in backticks to format it as code.\n```\n\nEnclose any `word` or `phrase` in backticks to format it as code.\n\n## Code Blocks\n\nTo present larger code samples, use triple backticks for fenced code blocks. Each block can be copied, and\u2014if assistant features are enabled\u2014users can request explanations.\n\nYou may specify the language for highlighting:\n\n````text\n```java\nclass HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello, World!\");\n }\n}\n```\n````\n\n```java\nclass HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello, World!\");\n }\n}\n```\n\n## Highlighting Diffs\n\nShow a visual diff of added or removed lines in your code blocks. Added lines are highlighted in green and removed lines are highlighted in red. Set the language to `diff` and prefix each changed line with `+` or `-`:\n\n````text\n```diff\nfunction calculateTotal(items) {\n- return items.reduce((sum, item) => sum + item.price, 0);\n+ const total = items.reduce((sum, item) => sum + item.price, 0);\n+ return Math.round(total * 100) / 100;\n}\n```\n````\n\n```diff\nfunction calculateTotal(items) {\n- return items.reduce((sum, item) => sum + item.price, 0);\n+ const total = items.reduce((sum, item) => sum + item.price, 0);\n+ return Math.round(total * 100) / 100;\n}\n```\n\n## File Names\n\nAdd a `title` to a code block to display a file name in the window bar, styled to match the GitHub-style header. Pass it through the `<Code />` component:\n\n```text\n<Code title=\"package.json\" language=\"json\" code={`{\\n \"name\": \"my-app\",\\n \"version\": \"1.0.0\"\\n}`} />\n```\n\n<Code title=\"package.json\" language=\"json\" code={`{\\n \"name\": \"my-app\",\\n \"version\": \"1.0.0\"\\n}`} />\n\n## Tabbed Code Blocks\n\nUse `<CodeTabs />` to show several variants of the same snippet - for example the same install command across package managers. Each tab is a keyboard-accessible button, and the copy button copies whichever tab is active. Each tab may set a `language` for highlighting (defaults to `bash`).\n\n```text\n<CodeTabs tabs={[{ label: \"npm\", code: \"npm install doccupine\" }, { label: \"pnpm\", code: \"pnpm add doccupine\" }, { label: \"yarn\", code: \"yarn add doccupine\" }]} />\n```\n\n<CodeTabs tabs={[{ label: \"npm\", code: \"npm install doccupine\" }, { label: \"pnpm\", code: \"pnpm add doccupine\" }, { label: \"yarn\", code: \"yarn add doccupine\" }]} />";
@@ -47,4 +47,46 @@ class HelloWorld {
47
47
  System.out.println("Hello, World!");
48
48
  }
49
49
  }
50
- \`\`\``;
50
+ \`\`\`
51
+
52
+ ## Highlighting Diffs
53
+
54
+ Show a visual diff of added or removed lines in your code blocks. Added lines are highlighted in green and removed lines are highlighted in red. Set the language to \`diff\` and prefix each changed line with \`+\` or \`-\`:
55
+
56
+ \`\`\`\`text
57
+ \`\`\`diff
58
+ function calculateTotal(items) {
59
+ - return items.reduce((sum, item) => sum + item.price, 0);
60
+ + const total = items.reduce((sum, item) => sum + item.price, 0);
61
+ + return Math.round(total * 100) / 100;
62
+ }
63
+ \`\`\`
64
+ \`\`\`\`
65
+
66
+ \`\`\`diff
67
+ function calculateTotal(items) {
68
+ - return items.reduce((sum, item) => sum + item.price, 0);
69
+ + const total = items.reduce((sum, item) => sum + item.price, 0);
70
+ + return Math.round(total * 100) / 100;
71
+ }
72
+ \`\`\`
73
+
74
+ ## File Names
75
+
76
+ Add a \`title\` to a code block to display a file name in the window bar, styled to match the GitHub-style header. Pass it through the \`<Code />\` component:
77
+
78
+ \`\`\`text
79
+ <Code title="package.json" language="json" code={\`{\\n "name": "my-app",\\n "version": "1.0.0"\\n}\`} />
80
+ \`\`\`
81
+
82
+ <Code title="package.json" language="json" code={\`{\\n "name": "my-app",\\n "version": "1.0.0"\\n}\`} />
83
+
84
+ ## Tabbed Code Blocks
85
+
86
+ Use \`<CodeTabs />\` to show several variants of the same snippet - for example the same install command across package managers. Each tab is a keyboard-accessible button, and the copy button copies whichever tab is active. Each tab may set a \`language\` for highlighting (defaults to \`bash\`).
87
+
88
+ \`\`\`text
89
+ <CodeTabs tabs={[{ label: "npm", code: "npm install doccupine" }, { label: "pnpm", code: "pnpm add doccupine" }, { label: "yarn", code: "yarn add doccupine" }]} />
90
+ \`\`\`
91
+
92
+ <CodeTabs tabs={[{ label: "npm", code: "npm install doccupine" }, { label: "pnpm", code: "pnpm add doccupine" }, { label: "yarn", code: "yarn add doccupine" }]} />`;
@@ -17,7 +17,7 @@ export const packageJsonTemplate = JSON.stringify({
17
17
  "@mdx-js/react": "^3.1.1",
18
18
  "@modelcontextprotocol/sdk": "^1.29.0",
19
19
  "@posthog/react": "^1.10.3",
20
- "cherry-styled-components": "^0.2.7",
20
+ "cherry-styled-components": "^0.2.8",
21
21
  langchain: "^1.5.2",
22
22
  "lucide-react": "^1.23.0",
23
23
  minisearch: "^7.2.0",
@@ -39,7 +39,7 @@ export const packageJsonTemplate = JSON.stringify({
39
39
  "@types/node": "^26",
40
40
  "@types/react": "^19",
41
41
  "@types/react-dom": "^19",
42
- "baseline-browser-mapping": "^2.10.41",
42
+ "baseline-browser-mapping": "^2.10.42",
43
43
  eslint: "^9",
44
44
  "eslint-config-next": "16.2.10",
45
45
  prettier: "^3.9.4",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doccupine",
3
- "version": "0.0.98",
3
+ "version": "0.0.100",
4
4
  "description": "Free and open-source documentation platform. Write MDX, get a production-ready site with AI chat, built-in components, and an MCP server - in one command.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -1 +0,0 @@
1
- export {};
@@ -1,48 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { generateSlug, escapeTemplateContent } from "./index.js";
3
- describe("generateSlug", () => {
4
- it("returns empty string for index.mdx", () => {
5
- expect(generateSlug("index.mdx")).toBe("");
6
- expect(generateSlug("./index.mdx")).toBe("");
7
- });
8
- it("strips .mdx extension", () => {
9
- expect(generateSlug("getting-started.mdx")).toBe("getting-started");
10
- });
11
- it("lowercases the slug", () => {
12
- expect(generateSlug("MyPage.mdx")).toBe("mypage");
13
- });
14
- it("replaces special characters with hyphens", () => {
15
- expect(generateSlug("hello world.mdx")).toBe("hello-world");
16
- });
17
- it("preserves forward slashes for nested paths", () => {
18
- expect(generateSlug("guides/setup.mdx")).toBe("guides/setup");
19
- });
20
- it("converts backslashes to forward slashes", () => {
21
- expect(generateSlug("guides\\setup.mdx")).toBe("guides/setup");
22
- });
23
- it("preserves underscores and hyphens", () => {
24
- expect(generateSlug("my_page-name.mdx")).toBe("my_page-name");
25
- });
26
- });
27
- describe("escapeTemplateContent", () => {
28
- it("escapes backticks", () => {
29
- expect(escapeTemplateContent("hello `world`")).toBe("hello \\`world\\`");
30
- });
31
- it("escapes template literal expressions", () => {
32
- expect(escapeTemplateContent("${variable}")).toBe("\\${variable}");
33
- });
34
- it("escapes backslashes", () => {
35
- expect(escapeTemplateContent("path\\to\\file")).toBe("path\\\\to\\\\file");
36
- });
37
- it("escapes all dangerous characters together", () => {
38
- const input = "Use `${process.env.SECRET}` here";
39
- const result = escapeTemplateContent(input);
40
- expect(result).toBe("Use \\`\\${process.env.SECRET}\\` here");
41
- });
42
- it("handles content with no special characters", () => {
43
- expect(escapeTemplateContent("plain text")).toBe("plain text");
44
- });
45
- it("handles empty string", () => {
46
- expect(escapeTemplateContent("")).toBe("");
47
- });
48
- });
@@ -1,11 +0,0 @@
1
- export interface MetadataOptions {
2
- title?: string;
3
- titleFallback: string;
4
- name?: string;
5
- titleOrder: "page-first" | "name-first";
6
- description?: string;
7
- icon?: string;
8
- image?: string;
9
- }
10
- export declare function generateMetadataBlock(opts: MetadataOptions): string;
11
- export declare function generateRuntimeOnlyMetadataBlock(): string;
package/dist/metadata.js DELETED
@@ -1,52 +0,0 @@
1
- import { DEFAULT_FAVICON, DEFAULT_META_DESCRIPTION, DEFAULT_OG_IMAGE, DEFAULT_SITE_NAME, } from "./templates/constants.js";
2
- function buildFieldExpression(staticValue, configKey, defaultValue) {
3
- if (staticValue)
4
- return staticValue;
5
- return `\${config.${configKey} || "${defaultValue}"}`;
6
- }
7
- function buildTitleExpression(opts) {
8
- const title = opts.title || opts.titleFallback;
9
- if (opts.titleOrder === "page-first") {
10
- const suffix = opts.name
11
- ? `- ${opts.name}`
12
- : `\${config.name ? "- " + config.name : "- ${DEFAULT_SITE_NAME}"}`;
13
- return `${title} ${suffix}`;
14
- }
15
- // name-first
16
- const prefix = opts.name
17
- ? `${opts.name} -`
18
- : `\${config.name ? config.name + " -" : "${DEFAULT_SITE_NAME} -"}`;
19
- return `${prefix} ${title}`;
20
- }
21
- export function generateMetadataBlock(opts) {
22
- const title = buildTitleExpression(opts);
23
- const desc = buildFieldExpression(opts.description, "description", DEFAULT_META_DESCRIPTION);
24
- const icon = buildFieldExpression(opts.icon, "icon", DEFAULT_FAVICON);
25
- const image = buildFieldExpression(opts.image, "image", DEFAULT_OG_IMAGE);
26
- return `export const metadata: Metadata = {
27
- title: \`${title}\`,
28
- description: \`${desc}\`,
29
- icons: \`${icon}\`,
30
- openGraph: {
31
- title: \`${title}\`,
32
- description: \`${desc}\`,
33
- images: \`${image}\`,
34
- },
35
- };`;
36
- }
37
- export function generateRuntimeOnlyMetadataBlock() {
38
- const title = `\${config.name || "${DEFAULT_SITE_NAME}"}`;
39
- const desc = `\${config.description || "${DEFAULT_META_DESCRIPTION}"}`;
40
- const icon = `\${config.icon || "${DEFAULT_FAVICON}"}`;
41
- const image = `\${config.image || "${DEFAULT_OG_IMAGE}"}`;
42
- return `export const metadata: Metadata = {
43
- title: \`${title}\`,
44
- description: \`${desc}\`,
45
- icons: \`${icon}\`,
46
- openGraph: {
47
- title: \`${title}\`,
48
- description: \`${desc}\`,
49
- images: \`${image}\`,
50
- },
51
- };`;
52
- }
@@ -1 +0,0 @@
1
- export declare const accordionMdxTemplate = "---\ntitle: \"Accordion\"\ndescription: \"Doccupine CLI commands\"\ndate: \"2025-01-15\"\ncategory: \"Components\"\ncategoryOrder: 1\norder: 4\n---\n# Accordion\nInteractive panels for toggling visibility of content.\n\nAccordion elements help organize information by letting users show or hide sections as needed. They\u2019re an effective way to manage progressive disclosure and simplify navigation through dense or optional content.\n\n## Accordion Usage\nYou can use the Accordion component directly within your MDX files without any import. The following example shows a basic usage:\n\n~~~mdx\n<Accordion title=\"What is MDX?\">\n You can put any content in here, including other components, like code:\n\n ```java HelloWorld.java\n class HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello, World!\");\n }\n }\n ```\n</Accordion>\n~~~\n\n<Accordion title=\"What is MDX?\">\n You can put any content in here, including other components, like code:\n\n ```java HelloWorld.java\n class HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello, World!\");\n }\n }\n ```\n</Accordion>";