doccupine 0.0.88 → 0.0.89

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 (64) hide show
  1. package/README.md +15 -2
  2. package/dist/index.js +79 -4
  3. package/dist/lib/layout.js +38 -24
  4. package/dist/lib/metadata.d.ts +30 -0
  5. package/dist/lib/metadata.js +98 -1
  6. package/dist/templates/app/theme.d.ts +1 -1
  7. package/dist/templates/app/theme.js +84 -19
  8. package/dist/templates/components/Chat.d.ts +1 -1
  9. package/dist/templates/components/Chat.js +26 -27
  10. package/dist/templates/components/SearchModalContent.d.ts +1 -1
  11. package/dist/templates/components/SearchModalContent.js +12 -6
  12. package/dist/templates/components/SideBar.d.ts +1 -1
  13. package/dist/templates/components/SideBar.js +3 -1
  14. package/dist/templates/components/layout/Accordion.d.ts +1 -1
  15. package/dist/templates/components/layout/Accordion.js +2 -1
  16. package/dist/templates/components/layout/ActionBar.d.ts +1 -1
  17. package/dist/templates/components/layout/ActionBar.js +4 -6
  18. package/dist/templates/components/layout/Button.d.ts +1 -1
  19. package/dist/templates/components/layout/Button.js +10 -0
  20. package/dist/templates/components/layout/Callout.d.ts +1 -1
  21. package/dist/templates/components/layout/Callout.js +75 -20
  22. package/dist/templates/components/layout/Card.d.ts +1 -1
  23. package/dist/templates/components/layout/Card.js +2 -1
  24. package/dist/templates/components/layout/CherryThemeProvider.d.ts +1 -1
  25. package/dist/templates/components/layout/CherryThemeProvider.js +6 -12
  26. package/dist/templates/components/layout/ClientThemeProvider.d.ts +1 -1
  27. package/dist/templates/components/layout/ClientThemeProvider.js +45 -40
  28. package/dist/templates/components/layout/Code.d.ts +1 -1
  29. package/dist/templates/components/layout/Code.js +223 -255
  30. package/dist/templates/components/layout/ColorSwatch.d.ts +1 -1
  31. package/dist/templates/components/layout/ColorSwatch.js +2 -2
  32. package/dist/templates/components/layout/Columns.d.ts +1 -1
  33. package/dist/templates/components/layout/Columns.js +1 -1
  34. package/dist/templates/components/layout/DemoTheme.d.ts +1 -1
  35. package/dist/templates/components/layout/DemoTheme.js +65 -167
  36. package/dist/templates/components/layout/DocsComponents.d.ts +1 -1
  37. package/dist/templates/components/layout/DocsComponents.js +13 -19
  38. package/dist/templates/components/layout/Field.d.ts +1 -1
  39. package/dist/templates/components/layout/Field.js +6 -4
  40. package/dist/templates/components/layout/Footer.d.ts +1 -1
  41. package/dist/templates/components/layout/Footer.js +1 -2
  42. package/dist/templates/components/layout/GlobalStyles.d.ts +1 -1
  43. package/dist/templates/components/layout/GlobalStyles.js +63 -10
  44. package/dist/templates/components/layout/Header.d.ts +1 -1
  45. package/dist/templates/components/layout/Header.js +14 -11
  46. package/dist/templates/components/layout/SharedStyles.d.ts +1 -1
  47. package/dist/templates/components/layout/SharedStyles.js +4 -5
  48. package/dist/templates/components/layout/StaticLinks.d.ts +1 -1
  49. package/dist/templates/components/layout/StaticLinks.js +4 -6
  50. package/dist/templates/components/layout/Steps.d.ts +1 -1
  51. package/dist/templates/components/layout/Steps.js +3 -3
  52. package/dist/templates/components/layout/Tabs.d.ts +1 -1
  53. package/dist/templates/components/layout/Tabs.js +5 -2
  54. package/dist/templates/components/layout/ThemeToggle.d.ts +1 -1
  55. package/dist/templates/components/layout/ThemeToggle.js +17 -19
  56. package/dist/templates/components/layout/Typography.d.ts +1 -1
  57. package/dist/templates/components/layout/Typography.js +1 -1
  58. package/dist/templates/components/layout/Update.d.ts +1 -1
  59. package/dist/templates/components/layout/Update.js +4 -3
  60. package/dist/templates/mdx/platform/site-settings.mdx.d.ts +1 -1
  61. package/dist/templates/mdx/platform/site-settings.mdx.js +5 -1
  62. package/dist/templates/package.js +0 -1
  63. package/dist/templates/proxy.js +14 -20
  64. package/package.json +1 -1
@@ -3,7 +3,6 @@ import { useContext, useState } from "react";
3
3
  import styled, { css } from "styled-components";
4
4
  import { Icon } from "@/components/layout/Icon";
5
5
  import { mq, Theme } from "@/app/theme";
6
- import { rgba } from "polished";
7
6
  import { resetButton, Textarea } from "cherry-styled-components";
8
7
  import { SectionBarContext } from "@/components/layout/DocsComponents";
9
8
  import { StyledSmallButton } from "@/components/layout/SharedStyled";
@@ -72,7 +71,8 @@ const StyledToggle = styled.button<{ theme: Theme; $isActive?: boolean }>\`
72
71
  width: 24px;
73
72
  height: 24px;
74
73
  border-radius: 50%;
75
- background: \${({ theme }) => rgba(theme.colors.primaryLight, 0.2)};
74
+ background: \${({ theme }) =>
75
+ \`color-mix(in srgb, \${theme.colors.primaryLight} 20%, transparent)\`};
76
76
  transition: all 0.3s ease;
77
77
  z-index: 1;
78
78
  \${({ $isActive }) =>
@@ -106,12 +106,10 @@ const StyledToggle = styled.button<{ theme: Theme; $isActive?: boolean }>\`
106
106
 
107
107
  &:hover {
108
108
  transform: scale(1.05);
109
- color: \${({ theme }) =>
110
- theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};
109
+ color: \${({ theme }) => theme.colors.accent};
111
110
 
112
111
  & svg[stroke] {
113
- stroke: \${({ theme }) =>
114
- theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};
112
+ stroke: \${({ theme }) => theme.colors.accent};
115
113
  }
116
114
  }
117
115
 
@@ -1 +1 @@
1
- export declare const buttonTemplate = "\"use client\";\nimport Link from \"next/link\";\nimport styled from \"styled-components\";\nimport {\n theme as localTheme,\n ButtonProps,\n buttonStyles,\n} from \"cherry-styled-components\";\nimport { Icon } from \"@/components/layout/Icon\";\n\ninterface LinkButtonProps extends ButtonProps {\n href?: string;\n target?: \"_blank\" | \"_self\" | \"_parent\" | \"_top\";\n variant?: \"primary\" | \"secondary\" | \"tertiary\";\n size?: \"default\" | \"big\";\n outline?: boolean;\n fullWidth?: boolean;\n icon?: string;\n iconPosition?: \"left\" | \"right\";\n theme?: typeof localTheme;\n}\n\nconst StyledLinkButton = styled(Link)<LinkButtonProps>`\n ${({ theme, $variant, $size, $outline, $fullWidth, disabled }) =>\n buttonStyles(theme, $variant, $size, $outline, $fullWidth, disabled)}\n\n & p {\n color: inherit !important;\n }\n\n & svg.lucide {\n margin: auto 0;\n min-width: min-content;\n color: inherit;\n }\n`;\n\nconst ButtonBase = styled.button<ButtonProps>`\n ${({ theme, $variant, $size, $outline, $fullWidth, disabled }) =>\n buttonStyles(theme, $variant, $size, $outline, $fullWidth, disabled)}\n\n & p {\n color: inherit !important;\n }\n\n & svg.lucide {\n margin: auto 0;\n min-width: min-content;\n color: inherit;\n }\n`;\n\nfunction Button({\n variant = \"primary\",\n size,\n outline,\n fullWidth,\n icon,\n iconPosition = \"left\",\n theme: _theme = localTheme,\n href,\n ...props\n}: LinkButtonProps) {\n return href ? (\n <div>\n <StyledLinkButton\n {...props}\n href={href}\n $variant={variant}\n $size={size}\n $outline={outline}\n $fullWidth={fullWidth}\n >\n {iconPosition === \"left\" && icon && <Icon name={icon} size={16} />}\n {props.children}\n {iconPosition === \"right\" && icon && <Icon name={icon} size={16} />}\n </StyledLinkButton>\n </div>\n ) : (\n <div>\n <ButtonBase\n {...props}\n $variant={variant}\n $size={size}\n $outline={outline}\n $fullWidth={fullWidth}\n >\n {iconPosition === \"left\" && icon && <Icon name={icon} size={16} />}\n {props.children}\n {iconPosition === \"right\" && icon && <Icon name={icon} size={16} />}\n </ButtonBase>\n </div>\n );\n}\n\nexport { Button };\n";
1
+ export declare const buttonTemplate = "\"use client\";\nimport Link from \"next/link\";\nimport styled from \"styled-components\";\nimport {\n theme as localTheme,\n ButtonProps,\n buttonStyles,\n} from \"cherry-styled-components\";\nimport { Icon } from \"@/components/layout/Icon\";\n\ninterface LinkButtonProps extends ButtonProps {\n href?: string;\n target?: \"_blank\" | \"_self\" | \"_parent\" | \"_top\";\n variant?: \"primary\" | \"secondary\" | \"tertiary\";\n size?: \"default\" | \"big\";\n outline?: boolean;\n fullWidth?: boolean;\n icon?: string;\n iconPosition?: \"left\" | \"right\";\n theme?: typeof localTheme;\n}\n\n// Cherry's buttonStyles picks filled-button text via `isDark ? colors.dark : colors.light`.\n// Our theme's isDark is a stub (false) because mode switching lives in CSS vars, so\n// the fallback resolves to --color-light (black in dark mode). Re-pin to `surface`,\n// which resolves to white in both modes, for the filled, non-disabled case.\nconst StyledLinkButton = styled(Link)<LinkButtonProps>`\n ${({ theme, $variant, $size, $outline, $fullWidth, disabled }) =>\n buttonStyles(theme, $variant, $size, $outline, $fullWidth, disabled)}\n\n ${({ theme, $outline, disabled }) =>\n !disabled && !$outline && `color: ${theme.colors.surface};`}\n\n & p {\n color: inherit !important;\n }\n\n & svg.lucide {\n margin: auto 0;\n min-width: min-content;\n color: inherit;\n }\n`;\n\nconst ButtonBase = styled.button<ButtonProps>`\n ${({ theme, $variant, $size, $outline, $fullWidth, disabled }) =>\n buttonStyles(theme, $variant, $size, $outline, $fullWidth, disabled)}\n\n ${({ theme, $outline, disabled }) =>\n !disabled && !$outline && `color: ${theme.colors.surface};`}\n\n & p {\n color: inherit !important;\n }\n\n & svg.lucide {\n margin: auto 0;\n min-width: min-content;\n color: inherit;\n }\n`;\n\nfunction Button({\n variant = \"primary\",\n size,\n outline,\n fullWidth,\n icon,\n iconPosition = \"left\",\n theme: _theme = localTheme,\n href,\n ...props\n}: LinkButtonProps) {\n return href ? (\n <div>\n <StyledLinkButton\n {...props}\n href={href}\n $variant={variant}\n $size={size}\n $outline={outline}\n $fullWidth={fullWidth}\n >\n {iconPosition === \"left\" && icon && <Icon name={icon} size={16} />}\n {props.children}\n {iconPosition === \"right\" && icon && <Icon name={icon} size={16} />}\n </StyledLinkButton>\n </div>\n ) : (\n <div>\n <ButtonBase\n {...props}\n $variant={variant}\n $size={size}\n $outline={outline}\n $fullWidth={fullWidth}\n >\n {iconPosition === \"left\" && icon && <Icon name={icon} size={16} />}\n {props.children}\n {iconPosition === \"right\" && icon && <Icon name={icon} size={16} />}\n </ButtonBase>\n </div>\n );\n}\n\nexport { Button };\n";
@@ -20,10 +20,17 @@ interface LinkButtonProps extends ButtonProps {
20
20
  theme?: typeof localTheme;
21
21
  }
22
22
 
23
+ // Cherry's buttonStyles picks filled-button text via \`isDark ? colors.dark : colors.light\`.
24
+ // Our theme's isDark is a stub (false) because mode switching lives in CSS vars, so
25
+ // the fallback resolves to --color-light (black in dark mode). Re-pin to \`surface\`,
26
+ // which resolves to white in both modes, for the filled, non-disabled case.
23
27
  const StyledLinkButton = styled(Link)<LinkButtonProps>\`
24
28
  \${({ theme, $variant, $size, $outline, $fullWidth, disabled }) =>
25
29
  buttonStyles(theme, $variant, $size, $outline, $fullWidth, disabled)}
26
30
 
31
+ \${({ theme, $outline, disabled }) =>
32
+ !disabled && !$outline && \`color: \${theme.colors.surface};\`}
33
+
27
34
  & p {
28
35
  color: inherit !important;
29
36
  }
@@ -39,6 +46,9 @@ const ButtonBase = styled.button<ButtonProps>\`
39
46
  \${({ theme, $variant, $size, $outline, $fullWidth, disabled }) =>
40
47
  buttonStyles(theme, $variant, $size, $outline, $fullWidth, disabled)}
41
48
 
49
+ \${({ theme, $outline, disabled }) =>
50
+ !disabled && !$outline && \`color: \${theme.colors.surface};\`}
51
+
42
52
  & p {
43
53
  color: inherit !important;
44
54
  }
@@ -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\nconst StyledChildren = styled.span`\n display: flex;\n flex-direction: column;\n gap: 10px;\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 <StyledChildren>{children}</StyledChildren>\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 /* Callout tints are alert-semantic (info-blue, warning-amber, danger-red,\n etc.) and intentionally independent of theme.json. Light-mode values are\n defined by default; dark-mode overrides live in :root.dark & blocks so\n the swap happens via the active <html> class with no re-render. */\n ${({ $type }) =>\n $type === \"note\" &&\n css`\n border-color: #0ea5e933;\n background: #f0f9ff80;\n\n & svg.lucide,\n & p {\n color: #0c4a6e;\n }\n\n :root.dark & {\n border-color: #0ea5e94d;\n background: #0ea5e91a;\n\n & svg.lucide,\n & p {\n color: #bae6fd;\n }\n }\n `}\n\n ${({ $type }) =>\n $type === \"info\" &&\n css`\n border-color: #71717a33;\n background: #fafafa80;\n\n & svg.lucide,\n & .lucide,\n & p {\n color: #18181b;\n }\n\n :root.dark & {\n border-color: #71717a4d;\n background: #71717a1a;\n\n & svg.lucide,\n & .lucide,\n & p {\n color: #e4e4e7;\n }\n }\n `}\n\n ${({ $type }) =>\n $type === \"warning\" &&\n css`\n border-color: #f59e0b33;\n background: #fffbeb80;\n\n & svg.lucide,\n & p {\n color: #78350f;\n }\n\n :root.dark & {\n border-color: #f59e0b4d;\n background: #f59e0b1a;\n\n & svg.lucide,\n & p {\n color: #fde68a;\n }\n }\n `}\n\n ${({ $type }) =>\n $type === \"danger\" &&\n css`\n border-color: #ef444433;\n background: #fef2f280;\n\n & svg.lucide,\n & p {\n color: #7f1d1d;\n }\n\n :root.dark & {\n border-color: #ef44444d;\n background: #ef44441a;\n\n & svg.lucide,\n & p {\n color: #fecaca;\n }\n }\n `}\n\n ${({ $type }) =>\n $type === \"success\" &&\n css`\n border-color: #10b98133;\n background: #ecfdf580;\n\n & svg.lucide,\n & p {\n color: #064e3b;\n }\n\n :root.dark & {\n border-color: #10b9814d;\n background: #10b9811a;\n\n & svg.lucide,\n & p {\n color: #a7f3d0;\n }\n }\n `}\n`;\n\nconst StyledChildren = styled.span`\n display: flex;\n flex-direction: column;\n gap: 10px;\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 <StyledChildren>{children}</StyledChildren>\n </StyledCallout>\n );\n}\n\nexport { Callout };\n";
@@ -22,64 +22,119 @@ const StyledCallout = styled.div<{ theme: Theme; $type?: CalloutType }>\`
22
22
  margin: 3px 10px 0 0;
23
23
  }
24
24
 
25
- \${({ theme, $type }) =>
25
+ /* Callout tints are alert-semantic (info-blue, warning-amber, danger-red,
26
+ etc.) and intentionally independent of theme.json. Light-mode values are
27
+ defined by default; dark-mode overrides live in :root.dark & blocks so
28
+ the swap happens via the active <html> class with no re-render. */
29
+ \${({ $type }) =>
26
30
  $type === "note" &&
27
31
  css\`
28
- border-color: \${theme.isDark ? "#0ea5e94d" : "#0ea5e933"};
29
- background: \${theme.isDark ? "#0ea5e91a" : "#f0f9ff80"};
32
+ border-color: #0ea5e933;
33
+ background: #f0f9ff80;
30
34
 
31
35
  & svg.lucide,
32
36
  & p {
33
- color: \${theme.isDark ? "#bae6fd" : "#0c4a6e"};
37
+ color: #0c4a6e;
38
+ }
39
+
40
+ :root.dark & {
41
+ border-color: #0ea5e94d;
42
+ background: #0ea5e91a;
43
+
44
+ & svg.lucide,
45
+ & p {
46
+ color: #bae6fd;
47
+ }
34
48
  }
35
49
  \`}
36
50
 
37
- \${({ theme, $type }) =>
51
+ \${({ $type }) =>
38
52
  $type === "info" &&
39
53
  css\`
40
- border-color: \${theme.isDark ? "#71717a4d" : "#71717a33"};
41
- background: \${theme.isDark ? "#71717a1a" : "#fafafa80"};
54
+ border-color: #71717a33;
55
+ background: #fafafa80;
42
56
 
43
57
  & svg.lucide,
44
58
  & .lucide,
45
59
  & p {
46
- color: \${theme.isDark ? "#e4e4e7" : "#18181b"};
60
+ color: #18181b;
61
+ }
62
+
63
+ :root.dark & {
64
+ border-color: #71717a4d;
65
+ background: #71717a1a;
66
+
67
+ & svg.lucide,
68
+ & .lucide,
69
+ & p {
70
+ color: #e4e4e7;
71
+ }
47
72
  }
48
73
  \`}
49
74
 
50
- \${({ theme, $type }) =>
75
+ \${({ $type }) =>
51
76
  $type === "warning" &&
52
77
  css\`
53
- border-color: \${theme.isDark ? "#f59e0b4d" : "#f59e0b33"};
54
- background: \${theme.isDark ? "#f59e0b1a" : "#fffbeb80"};
78
+ border-color: #f59e0b33;
79
+ background: #fffbeb80;
55
80
 
56
81
  & svg.lucide,
57
82
  & p {
58
- color: \${theme.isDark ? "#fde68a" : "#78350f"};
83
+ color: #78350f;
84
+ }
85
+
86
+ :root.dark & {
87
+ border-color: #f59e0b4d;
88
+ background: #f59e0b1a;
89
+
90
+ & svg.lucide,
91
+ & p {
92
+ color: #fde68a;
93
+ }
59
94
  }
60
95
  \`}
61
96
 
62
- \${({ theme, $type }) =>
97
+ \${({ $type }) =>
63
98
  $type === "danger" &&
64
99
  css\`
65
- border-color: \${theme.isDark ? "#ef44444d" : "#ef444433"};
66
- background: \${theme.isDark ? "#ef44441a" : "#fef2f280"};
100
+ border-color: #ef444433;
101
+ background: #fef2f280;
67
102
 
68
103
  & svg.lucide,
69
104
  & p {
70
- color: \${theme.isDark ? "#fecaca" : "#7f1d1d"};
105
+ color: #7f1d1d;
106
+ }
107
+
108
+ :root.dark & {
109
+ border-color: #ef44444d;
110
+ background: #ef44441a;
111
+
112
+ & svg.lucide,
113
+ & p {
114
+ color: #fecaca;
115
+ }
71
116
  }
72
117
  \`}
73
118
 
74
- \${({ theme, $type }) =>
119
+ \${({ $type }) =>
75
120
  $type === "success" &&
76
121
  css\`
77
- border-color: \${theme.isDark ? "#10b9814d" : "#10b98133"};
78
- background: \${theme.isDark ? "#10b9811a" : "#ecfdf580"};
122
+ border-color: #10b98133;
123
+ background: #ecfdf580;
79
124
 
80
125
  & svg.lucide,
81
126
  & p {
82
- color: \${theme.isDark ? "#a7f3d0" : "#064e3b"};
127
+ color: #064e3b;
128
+ }
129
+
130
+ :root.dark & {
131
+ border-color: #10b9814d;
132
+ background: #10b9811a;
133
+
134
+ & svg.lucide,
135
+ & p {
136
+ color: #a7f3d0;
137
+ }
83
138
  }
84
139
  \`}
85
140
  \`;
@@ -1 +1 @@
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.p<{ theme: Theme }>`\n font-weight: bold;\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
+ export declare const cardTemplate = "\"use client\";\nimport Link from \"next/link\";\nimport styled, { css, useTheme } from \"styled-components\";\nimport { styledText } from \"cherry-styled-components\";\nimport { Theme } from \"@/app/theme\";\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.p<{ theme: Theme }>`\n font-weight: bold;\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,7 +1,8 @@
1
1
  export const cardTemplate = `"use client";
2
2
  import Link from "next/link";
3
3
  import styled, { css, useTheme } from "styled-components";
4
- import { styledText, Theme } from "cherry-styled-components";
4
+ import { styledText } from "cherry-styled-components";
5
+ import { Theme } from "@/app/theme";
5
6
  import { Icon, IconProps } from "@/components/layout/Icon";
6
7
  import { interactiveStyles } from "@/components/layout/SharedStyled";
7
8
 
@@ -1 +1 @@
1
- export declare const cherryThemeProviderTemplate = "import React from \"react\";\nimport { cookies } from \"next/headers\";\nimport { Theme } from \"@/app/theme\";\nimport { ClientThemeProvider } from \"@/components/layout/ClientThemeProvider\";\n\nasync function CherryThemeProvider({\n children,\n theme,\n themeDark,\n}: {\n children: React.ReactNode;\n theme: Theme;\n themeDark?: Theme;\n}) {\n const cookieStore = await cookies();\n const cookieTheme = cookieStore.get(\"theme\")?.value;\n const useDark = cookieTheme === \"dark\";\n const currentTheme = useDark && themeDark ? themeDark : theme;\n\n return (\n <ClientThemeProvider theme={currentTheme}>{children}</ClientThemeProvider>\n );\n}\n\nexport { CherryThemeProvider };\n";
1
+ export declare const cherryThemeProviderTemplate = "import React from \"react\";\nimport { Theme } from \"@/app/theme\";\nimport { ClientThemeProvider } from \"@/components/layout/ClientThemeProvider\";\n\n// Light vs dark is decided by the \"dark\" class on <html>, set before paint\n// by the theme-init blocking script in the root layout. The theme object\n// itself references CSS variables that flip per :root vs :root.dark, so\n// no per-request server data is needed and pages stay statically renderable.\nfunction CherryThemeProvider({\n children,\n theme,\n}: {\n children: React.ReactNode;\n theme: Theme;\n}) {\n return <ClientThemeProvider theme={theme}>{children}</ClientThemeProvider>;\n}\n\nexport { CherryThemeProvider };\n";
@@ -1,25 +1,19 @@
1
1
  export const cherryThemeProviderTemplate = `import React from "react";
2
- import { cookies } from "next/headers";
3
2
  import { Theme } from "@/app/theme";
4
3
  import { ClientThemeProvider } from "@/components/layout/ClientThemeProvider";
5
4
 
6
- async function CherryThemeProvider({
5
+ // Light vs dark is decided by the "dark" class on <html>, set before paint
6
+ // by the theme-init blocking script in the root layout. The theme object
7
+ // itself references CSS variables that flip per :root vs :root.dark, so
8
+ // no per-request server data is needed and pages stay statically renderable.
9
+ function CherryThemeProvider({
7
10
  children,
8
11
  theme,
9
- themeDark,
10
12
  }: {
11
13
  children: React.ReactNode;
12
14
  theme: Theme;
13
- themeDark?: Theme;
14
15
  }) {
15
- const cookieStore = await cookies();
16
- const cookieTheme = cookieStore.get("theme")?.value;
17
- const useDark = cookieTheme === "dark";
18
- const currentTheme = useDark && themeDark ? themeDark : theme;
19
-
20
- return (
21
- <ClientThemeProvider theme={currentTheme}>{children}</ClientThemeProvider>
22
- );
16
+ return <ClientThemeProvider theme={theme}>{children}</ClientThemeProvider>;
23
17
  }
24
18
 
25
19
  export { CherryThemeProvider };
@@ -1 +1 @@
1
- export declare const clientThemeProviderTemplate = "\"use client\";\nimport React, { useEffect, useMemo, useState, useContext } from \"react\";\nimport { ThemeProvider as StyledThemeProvider } from \"styled-components\";\nimport { Theme } from \"@/app/theme\";\nimport { useRouter } from \"next/navigation\";\nimport { GlobalStyles } from \"@/components/layout/GlobalStyles\";\n\ntype ThemeOverrideContextValue = {\n theme: Theme;\n setTheme: (t: Theme | null) => void;\n};\n\nconst ThemeOverrideContext =\n React.createContext<ThemeOverrideContextValue | null>(null);\n\nfunction useThemeOverride() {\n const ctx = useContext(ThemeOverrideContext);\n if (!ctx) {\n throw new Error(\"useThemeOverride must be used within ClientThemeProvider\");\n }\n return ctx;\n}\n\nfunction ClientThemeProvider({\n children,\n theme,\n}: {\n children: React.ReactNode;\n theme: Theme;\n}) {\n const router = useRouter();\n const [overrideTheme, setOverrideTheme] = useState<Theme | null>(null);\n useEffect(() => {\n try {\n const cookie = document.cookie\n .split(\";\")\n .map((c) => c.trim())\n .find((c) => c.startsWith(\"theme=\"));\n const cookieValue = cookie ? cookie.split(\"=\")[1] : undefined;\n if (!cookieValue) {\n // Fallback: blocking script should have set this, but handle edge cases\n const prefersDark =\n window.matchMedia &&\n window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n const shouldBe = prefersDark ? \"dark\" : \"light\";\n document.cookie = `theme=${shouldBe};path=/;max-age=31536000;SameSite=Lax`;\n router.refresh();\n } else if (theme.isDark !== (cookieValue === \"dark\")) {\n // Server-rendered theme doesn't match cookie (e.g., cookie was set\n // by the blocking script after SSR already committed to light theme)\n router.refresh();\n } else {\n // Theme matches cookie - remove the flash-prevention style injected\n // by the blocking script so styled-components takes over\n const el = document.getElementById(\"__theme-init\");\n if (el) el.remove();\n }\n } catch {}\n }, [router, theme.isDark]);\n\n const effectiveTheme = useMemo(\n () => overrideTheme ?? theme,\n [overrideTheme, theme],\n );\n return (\n <ThemeOverrideContext.Provider\n value={{ theme: effectiveTheme, setTheme: setOverrideTheme }}\n >\n <StyledThemeProvider theme={effectiveTheme}>\n <GlobalStyles />\n {children}\n </StyledThemeProvider>\n </ThemeOverrideContext.Provider>\n );\n}\n\nexport { ClientThemeProvider, useThemeOverride };\n";
1
+ export declare const clientThemeProviderTemplate = "\"use client\";\nimport React, { useCallback, useContext, useEffect, useState } from \"react\";\nimport { ThemeProvider as StyledThemeProvider } from \"styled-components\";\nimport { Theme } from \"@/app/theme\";\nimport { GlobalStyles } from \"@/components/layout/GlobalStyles\";\n\ntype ThemeMode = \"light\" | \"dark\";\n\ntype ThemeContextValue = {\n /** The styled-components theme object \u2014 same reference in both modes;\n the values it points to are CSS variables that flip per active class. */\n theme: Theme;\n /** Active mode, derived from the \"dark\" class on <html>. */\n mode: ThemeMode;\n /** Update the active mode: flips the class and persists the cookie. */\n setMode: (m: ThemeMode) => void;\n};\n\nconst ThemeContext = React.createContext<ThemeContextValue | null>(null);\n\nfunction useThemeOverride() {\n const ctx = useContext(ThemeContext);\n if (!ctx) {\n throw new Error(\"useThemeOverride must be used within ClientThemeProvider\");\n }\n return ctx;\n}\n\nfunction readMode(): ThemeMode {\n if (typeof document === \"undefined\") return \"light\";\n return document.documentElement.classList.contains(\"dark\") ? \"dark\" : \"light\";\n}\n\nfunction ClientThemeProvider({\n children,\n theme,\n}: {\n children: React.ReactNode;\n theme: Theme;\n}) {\n // Mode lives in React state so consumers (e.g. ThemeToggle) re-render when\n // the user toggles. The visual swap itself is driven entirely by the\n // \"dark\" class on <html> + CSS variables \u2014 React doesn't drive paint.\n const [mode, setModeState] = useState<ThemeMode>(\"light\");\n\n useEffect(() => {\n // Sync with the class set by the theme-init blocking script. Also\n // backfill the cookie if the script didn't get to write one (rare).\n setModeState(readMode());\n try {\n const has = document.cookie\n .split(\";\")\n .some((c) => c.trim().startsWith(\"theme=\"));\n if (!has) {\n const m = readMode();\n document.cookie = `theme=${m};path=/;max-age=31536000;SameSite=Lax`;\n }\n } catch {}\n }, []);\n\n const setMode = useCallback((next: ThemeMode) => {\n if (typeof document !== \"undefined\") {\n document.documentElement.classList.toggle(\"dark\", next === \"dark\");\n document.documentElement.style.colorScheme = next;\n try {\n document.cookie = `theme=${next};path=/;max-age=31536000;SameSite=Lax`;\n } catch {}\n }\n setModeState(next);\n }, []);\n\n return (\n <ThemeContext.Provider value={{ theme, mode, setMode }}>\n <StyledThemeProvider theme={theme}>\n <GlobalStyles />\n {children}\n </StyledThemeProvider>\n </ThemeContext.Provider>\n );\n}\n\nexport { ClientThemeProvider, useThemeOverride };\n";
@@ -1,26 +1,36 @@
1
1
  export const clientThemeProviderTemplate = `"use client";
2
- import React, { useEffect, useMemo, useState, useContext } from "react";
2
+ import React, { useCallback, useContext, useEffect, useState } from "react";
3
3
  import { ThemeProvider as StyledThemeProvider } from "styled-components";
4
4
  import { Theme } from "@/app/theme";
5
- import { useRouter } from "next/navigation";
6
5
  import { GlobalStyles } from "@/components/layout/GlobalStyles";
7
6
 
8
- type ThemeOverrideContextValue = {
7
+ type ThemeMode = "light" | "dark";
8
+
9
+ type ThemeContextValue = {
10
+ /** The styled-components theme object — same reference in both modes;
11
+ the values it points to are CSS variables that flip per active class. */
9
12
  theme: Theme;
10
- setTheme: (t: Theme | null) => void;
13
+ /** Active mode, derived from the "dark" class on <html>. */
14
+ mode: ThemeMode;
15
+ /** Update the active mode: flips the class and persists the cookie. */
16
+ setMode: (m: ThemeMode) => void;
11
17
  };
12
18
 
13
- const ThemeOverrideContext =
14
- React.createContext<ThemeOverrideContextValue | null>(null);
19
+ const ThemeContext = React.createContext<ThemeContextValue | null>(null);
15
20
 
16
21
  function useThemeOverride() {
17
- const ctx = useContext(ThemeOverrideContext);
22
+ const ctx = useContext(ThemeContext);
18
23
  if (!ctx) {
19
24
  throw new Error("useThemeOverride must be used within ClientThemeProvider");
20
25
  }
21
26
  return ctx;
22
27
  }
23
28
 
29
+ function readMode(): ThemeMode {
30
+ if (typeof document === "undefined") return "light";
31
+ return document.documentElement.classList.contains("dark") ? "dark" : "light";
32
+ }
33
+
24
34
  function ClientThemeProvider({
25
35
  children,
26
36
  theme,
@@ -28,49 +38,44 @@ function ClientThemeProvider({
28
38
  children: React.ReactNode;
29
39
  theme: Theme;
30
40
  }) {
31
- const router = useRouter();
32
- const [overrideTheme, setOverrideTheme] = useState<Theme | null>(null);
41
+ // Mode lives in React state so consumers (e.g. ThemeToggle) re-render when
42
+ // the user toggles. The visual swap itself is driven entirely by the
43
+ // "dark" class on <html> + CSS variables — React doesn't drive paint.
44
+ const [mode, setModeState] = useState<ThemeMode>("light");
45
+
33
46
  useEffect(() => {
47
+ // Sync with the class set by the theme-init blocking script. Also
48
+ // backfill the cookie if the script didn't get to write one (rare).
49
+ setModeState(readMode());
34
50
  try {
35
- const cookie = document.cookie
51
+ const has = document.cookie
36
52
  .split(";")
37
- .map((c) => c.trim())
38
- .find((c) => c.startsWith("theme="));
39
- const cookieValue = cookie ? cookie.split("=")[1] : undefined;
40
- if (!cookieValue) {
41
- // Fallback: blocking script should have set this, but handle edge cases
42
- const prefersDark =
43
- window.matchMedia &&
44
- window.matchMedia("(prefers-color-scheme: dark)").matches;
45
- const shouldBe = prefersDark ? "dark" : "light";
46
- document.cookie = \`theme=\${shouldBe};path=/;max-age=31536000;SameSite=Lax\`;
47
- router.refresh();
48
- } else if (theme.isDark !== (cookieValue === "dark")) {
49
- // Server-rendered theme doesn't match cookie (e.g., cookie was set
50
- // by the blocking script after SSR already committed to light theme)
51
- router.refresh();
52
- } else {
53
- // Theme matches cookie - remove the flash-prevention style injected
54
- // by the blocking script so styled-components takes over
55
- const el = document.getElementById("__theme-init");
56
- if (el) el.remove();
53
+ .some((c) => c.trim().startsWith("theme="));
54
+ if (!has) {
55
+ const m = readMode();
56
+ document.cookie = \`theme=\${m};path=/;max-age=31536000;SameSite=Lax\`;
57
57
  }
58
58
  } catch {}
59
- }, [router, theme.isDark]);
59
+ }, []);
60
+
61
+ const setMode = useCallback((next: ThemeMode) => {
62
+ if (typeof document !== "undefined") {
63
+ document.documentElement.classList.toggle("dark", next === "dark");
64
+ document.documentElement.style.colorScheme = next;
65
+ try {
66
+ document.cookie = \`theme=\${next};path=/;max-age=31536000;SameSite=Lax\`;
67
+ } catch {}
68
+ }
69
+ setModeState(next);
70
+ }, []);
60
71
 
61
- const effectiveTheme = useMemo(
62
- () => overrideTheme ?? theme,
63
- [overrideTheme, theme],
64
- );
65
72
  return (
66
- <ThemeOverrideContext.Provider
67
- value={{ theme: effectiveTheme, setTheme: setOverrideTheme }}
68
- >
69
- <StyledThemeProvider theme={effectiveTheme}>
73
+ <ThemeContext.Provider value={{ theme, mode, setMode }}>
74
+ <StyledThemeProvider theme={theme}>
70
75
  <GlobalStyles />
71
76
  {children}
72
77
  </StyledThemeProvider>
73
- </ThemeOverrideContext.Provider>
78
+ </ThemeContext.Provider>
74
79
  );
75
80
  }
76
81
 
@@ -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(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";
1
+ export declare const codeTemplate = "\"use client\";\nimport { useState, useCallback, useMemo } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { styledCode } 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\";\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\nconst CopyButton = styled.button<{ theme: Theme; $copied: boolean }>`\n background: ${({ $copied }) =>\n $copied ? \"rgba(45, 164, 78, 0.1)\" : \"transparent\"};\n border: solid 1px\n ${({ $copied }) => ($copied ? \"#2da44e\" : \"rgba(0, 0, 0, 0.1)\")};\n color: ${({ $copied }) => ($copied ? \"#2da44e\" : \"#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: ${({ $copied }) => ($copied ? \"#2da44e\" : \"#57606a\")};\n }\n\n &:hover {\n background: ${({ $copied }) =>\n $copied ? \"rgba(45, 164, 78, 0.2)\" : \"rgba(0, 0, 0, 0.05)\"};\n border-color: ${({ $copied }) =>\n $copied ? \"#2da44e\" : \"rgba(0, 0, 0, 0.2)\"};\n }\n\n &:active {\n transform: scale(0.95);\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 color: ${({ $copied }) => ($copied ? \"#7ee787\" : \"#c9d1d9\")};\n\n & svg.lucide {\n color: ${({ $copied }) => ($copied ? \"#7ee787\" : \"#c9d1d9\")};\n }\n\n &:hover {\n background: ${({ $copied }) =>\n $copied ? \"rgba(126, 231, 135, 0.3)\" : \"rgba(255, 255, 255, 0.1)\"};\n border-color: ${({ $copied }) =>\n $copied ? \"#7ee787\" : \"rgba(255, 255, 255, 0.2)\"};\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 ${({ 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 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";