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.
- package/README.md +15 -2
- package/dist/index.js +79 -4
- package/dist/lib/layout.js +38 -24
- package/dist/lib/metadata.d.ts +30 -0
- package/dist/lib/metadata.js +98 -1
- package/dist/templates/app/theme.d.ts +1 -1
- package/dist/templates/app/theme.js +84 -19
- package/dist/templates/components/Chat.d.ts +1 -1
- package/dist/templates/components/Chat.js +26 -27
- package/dist/templates/components/SearchModalContent.d.ts +1 -1
- package/dist/templates/components/SearchModalContent.js +12 -6
- package/dist/templates/components/SideBar.d.ts +1 -1
- package/dist/templates/components/SideBar.js +3 -1
- package/dist/templates/components/layout/Accordion.d.ts +1 -1
- package/dist/templates/components/layout/Accordion.js +2 -1
- package/dist/templates/components/layout/ActionBar.d.ts +1 -1
- package/dist/templates/components/layout/ActionBar.js +4 -6
- package/dist/templates/components/layout/Button.d.ts +1 -1
- package/dist/templates/components/layout/Button.js +10 -0
- package/dist/templates/components/layout/Callout.d.ts +1 -1
- package/dist/templates/components/layout/Callout.js +75 -20
- package/dist/templates/components/layout/Card.d.ts +1 -1
- package/dist/templates/components/layout/Card.js +2 -1
- package/dist/templates/components/layout/CherryThemeProvider.d.ts +1 -1
- package/dist/templates/components/layout/CherryThemeProvider.js +6 -12
- package/dist/templates/components/layout/ClientThemeProvider.d.ts +1 -1
- package/dist/templates/components/layout/ClientThemeProvider.js +45 -40
- package/dist/templates/components/layout/Code.d.ts +1 -1
- package/dist/templates/components/layout/Code.js +223 -255
- package/dist/templates/components/layout/ColorSwatch.d.ts +1 -1
- package/dist/templates/components/layout/ColorSwatch.js +2 -2
- package/dist/templates/components/layout/Columns.d.ts +1 -1
- package/dist/templates/components/layout/Columns.js +1 -1
- package/dist/templates/components/layout/DemoTheme.d.ts +1 -1
- package/dist/templates/components/layout/DemoTheme.js +65 -167
- package/dist/templates/components/layout/DocsComponents.d.ts +1 -1
- package/dist/templates/components/layout/DocsComponents.js +13 -19
- package/dist/templates/components/layout/Field.d.ts +1 -1
- package/dist/templates/components/layout/Field.js +6 -4
- package/dist/templates/components/layout/Footer.d.ts +1 -1
- package/dist/templates/components/layout/Footer.js +1 -2
- package/dist/templates/components/layout/GlobalStyles.d.ts +1 -1
- package/dist/templates/components/layout/GlobalStyles.js +63 -10
- package/dist/templates/components/layout/Header.d.ts +1 -1
- package/dist/templates/components/layout/Header.js +14 -11
- package/dist/templates/components/layout/SharedStyles.d.ts +1 -1
- package/dist/templates/components/layout/SharedStyles.js +4 -5
- package/dist/templates/components/layout/StaticLinks.d.ts +1 -1
- package/dist/templates/components/layout/StaticLinks.js +4 -6
- package/dist/templates/components/layout/Steps.d.ts +1 -1
- package/dist/templates/components/layout/Steps.js +3 -3
- package/dist/templates/components/layout/Tabs.d.ts +1 -1
- package/dist/templates/components/layout/Tabs.js +5 -2
- package/dist/templates/components/layout/ThemeToggle.d.ts +1 -1
- package/dist/templates/components/layout/ThemeToggle.js +17 -19
- package/dist/templates/components/layout/Typography.d.ts +1 -1
- package/dist/templates/components/layout/Typography.js +1 -1
- package/dist/templates/components/layout/Update.d.ts +1 -1
- package/dist/templates/components/layout/Update.js +4 -3
- package/dist/templates/mdx/platform/site-settings.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/site-settings.mdx.js +5 -1
- package/dist/templates/package.js +0 -1
- package/dist/templates/proxy.js +14 -20
- 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 }) =>
|
|
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 ${({
|
|
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
|
-
|
|
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:
|
|
29
|
-
background:
|
|
32
|
+
border-color: #0ea5e933;
|
|
33
|
+
background: #f0f9ff80;
|
|
30
34
|
|
|
31
35
|
& svg.lucide,
|
|
32
36
|
& p {
|
|
33
|
-
color:
|
|
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
|
-
\${({
|
|
51
|
+
\${({ $type }) =>
|
|
38
52
|
$type === "info" &&
|
|
39
53
|
css\`
|
|
40
|
-
border-color:
|
|
41
|
-
background:
|
|
54
|
+
border-color: #71717a33;
|
|
55
|
+
background: #fafafa80;
|
|
42
56
|
|
|
43
57
|
& svg.lucide,
|
|
44
58
|
& .lucide,
|
|
45
59
|
& p {
|
|
46
|
-
color:
|
|
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
|
-
\${({
|
|
75
|
+
\${({ $type }) =>
|
|
51
76
|
$type === "warning" &&
|
|
52
77
|
css\`
|
|
53
|
-
border-color:
|
|
54
|
-
background:
|
|
78
|
+
border-color: #f59e0b33;
|
|
79
|
+
background: #fffbeb80;
|
|
55
80
|
|
|
56
81
|
& svg.lucide,
|
|
57
82
|
& p {
|
|
58
|
-
color:
|
|
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
|
-
\${({
|
|
97
|
+
\${({ $type }) =>
|
|
63
98
|
$type === "danger" &&
|
|
64
99
|
css\`
|
|
65
|
-
border-color:
|
|
66
|
-
background:
|
|
100
|
+
border-color: #ef444433;
|
|
101
|
+
background: #fef2f280;
|
|
67
102
|
|
|
68
103
|
& svg.lucide,
|
|
69
104
|
& p {
|
|
70
|
-
color:
|
|
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
|
-
\${({
|
|
119
|
+
\${({ $type }) =>
|
|
75
120
|
$type === "success" &&
|
|
76
121
|
css\`
|
|
77
|
-
border-color:
|
|
78
|
-
background:
|
|
122
|
+
border-color: #10b98133;
|
|
123
|
+
background: #ecfdf580;
|
|
79
124
|
|
|
80
125
|
& svg.lucide,
|
|
81
126
|
& p {
|
|
82
|
-
color:
|
|
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
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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, {
|
|
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, {
|
|
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
|
|
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
|
-
|
|
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
|
|
14
|
-
React.createContext<ThemeOverrideContextValue | null>(null);
|
|
19
|
+
const ThemeContext = React.createContext<ThemeContextValue | null>(null);
|
|
15
20
|
|
|
16
21
|
function useThemeOverride() {
|
|
17
|
-
const ctx = useContext(
|
|
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
|
-
|
|
32
|
-
|
|
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
|
|
51
|
+
const has = document.cookie
|
|
36
52
|
.split(";")
|
|
37
|
-
.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
}, [
|
|
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
|
-
<
|
|
67
|
-
|
|
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
|
-
</
|
|
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 {
|
|
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, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n};\n\nconst sanitizeLanguage = (lang: string): string =>\n lang.replace(/[^a-zA-Z0-9_-]/g, \"\");\n\nconst highlightCode = (code: string, language: string): string => {\n const escapedCode = escapeHtml(code);\n const safeLang = sanitizeLanguage(language);\n const result = unified()\n .use(rehypeParse, { fragment: true })\n .use(rehypeHighlight, {\n detect: true,\n ignoreMissing: true,\n })\n .use(rehypeStringify)\n .processSync(\n `<pre><code class=\"language-${safeLang}\">${escapedCode}</code></pre>`,\n );\n\n return String(result);\n};\n\nfunction Code({ code, language = \"javascript\", theme, className }: CodeProps) {\n const [copied, setCopied] = useState(false);\n const highlightedCode = useMemo(\n () => highlightCode(code, language),\n [code, language],\n );\n\n const handleCopy = useCallback(async () => {\n try {\n await navigator.clipboard.writeText(code);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n } catch (err) {\n console.error(\"Failed to copy code:\", err);\n }\n }, [code]);\n\n return (\n <CodeWrapper\n className={`${className ?? \"\"} code-wrapper`.trim()}\n theme={theme}\n >\n <TopBar theme={theme}>\n <DotsContainer>\n <Dot theme={theme} />\n <Dot theme={theme} />\n <Dot theme={theme} />\n </DotsContainer>\n <CopyButton onClick={handleCopy} $copied={copied} theme={theme}>\n {copied ? (\n <>\n <Icon name=\"check\" size={12} />\n <span>Copied!</span>\n </>\n ) : (\n <>\n <Icon name=\"copy\" size={12} />\n <span>Copy</span>\n </>\n )}\n </CopyButton>\n </TopBar>\n <Body\n dangerouslySetInnerHTML={{ __html: highlightedCode }}\n theme={theme}\n className=\"code-wrapper-body\"\n />\n </CodeWrapper>\n );\n}\n\nexport { Code };\n";
|