@kushagradhawan/kookie-blocks 0.1.1 → 0.1.3
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/components.css +100 -0
- package/dist/cjs/components/code/CodeBlock.d.ts +4 -0
- package/dist/cjs/components/code/CodeBlock.d.ts.map +1 -0
- package/dist/cjs/components/code/CodeBlock.js +2 -0
- package/dist/cjs/components/code/CodeBlock.js.map +7 -0
- package/dist/cjs/components/code/CopyButton.d.ts +7 -0
- package/dist/cjs/components/code/CopyButton.d.ts.map +1 -0
- package/dist/cjs/components/code/CopyButton.js +2 -0
- package/dist/cjs/components/code/CopyButton.js.map +7 -0
- package/dist/cjs/components/code/LanguageBadge.d.ts +7 -0
- package/dist/cjs/components/code/LanguageBadge.d.ts.map +1 -0
- package/dist/cjs/components/code/LanguageBadge.js +2 -0
- package/dist/cjs/components/code/LanguageBadge.js.map +7 -0
- package/dist/cjs/components/code/PreviewSection.d.ts +16 -0
- package/dist/cjs/components/code/PreviewSection.d.ts.map +1 -0
- package/dist/cjs/components/code/PreviewSection.js +2 -0
- package/dist/cjs/components/code/PreviewSection.js.map +7 -0
- package/dist/cjs/components/code/SyntaxHighlighter.d.ts +10 -0
- package/dist/cjs/components/code/SyntaxHighlighter.d.ts.map +1 -0
- package/dist/cjs/components/code/SyntaxHighlighter.js +2 -0
- package/dist/cjs/components/code/SyntaxHighlighter.js.map +7 -0
- package/dist/cjs/components/code/index.d.ts +3 -0
- package/dist/cjs/components/code/index.d.ts.map +1 -0
- package/dist/cjs/components/code/index.js +2 -0
- package/dist/cjs/components/code/index.js.map +7 -0
- package/dist/cjs/components/code/types.d.ts +24 -0
- package/dist/cjs/components/code/types.d.ts.map +1 -0
- package/dist/cjs/components/code/types.js +2 -0
- package/dist/cjs/components/code/types.js.map +7 -0
- package/dist/cjs/components/index.d.ts +1 -0
- package/dist/cjs/components/index.d.ts.map +1 -1
- package/dist/cjs/components/index.js +1 -1
- package/dist/cjs/components/index.js.map +2 -2
- package/dist/esm/components/code/CodeBlock.d.ts +4 -0
- package/dist/esm/components/code/CodeBlock.d.ts.map +1 -0
- package/dist/esm/components/code/CodeBlock.js +2 -0
- package/dist/esm/components/code/CodeBlock.js.map +7 -0
- package/dist/esm/components/code/CopyButton.d.ts +7 -0
- package/dist/esm/components/code/CopyButton.d.ts.map +1 -0
- package/dist/esm/components/code/CopyButton.js +2 -0
- package/dist/esm/components/code/CopyButton.js.map +7 -0
- package/dist/esm/components/code/LanguageBadge.d.ts +7 -0
- package/dist/esm/components/code/LanguageBadge.d.ts.map +1 -0
- package/dist/esm/components/code/LanguageBadge.js +2 -0
- package/dist/esm/components/code/LanguageBadge.js.map +7 -0
- package/dist/esm/components/code/PreviewSection.d.ts +16 -0
- package/dist/esm/components/code/PreviewSection.d.ts.map +1 -0
- package/dist/esm/components/code/PreviewSection.js +2 -0
- package/dist/esm/components/code/PreviewSection.js.map +7 -0
- package/dist/esm/components/code/SyntaxHighlighter.d.ts +10 -0
- package/dist/esm/components/code/SyntaxHighlighter.d.ts.map +1 -0
- package/dist/esm/components/code/SyntaxHighlighter.js +2 -0
- package/dist/esm/components/code/SyntaxHighlighter.js.map +7 -0
- package/dist/esm/components/code/index.d.ts +3 -0
- package/dist/esm/components/code/index.d.ts.map +1 -0
- package/dist/esm/components/code/index.js +2 -0
- package/dist/esm/components/code/index.js.map +7 -0
- package/dist/esm/components/code/types.d.ts +24 -0
- package/dist/esm/components/code/types.d.ts.map +1 -0
- package/dist/esm/components/code/types.js +1 -0
- package/dist/esm/components/code/types.js.map +7 -0
- package/dist/esm/components/index.d.ts +1 -0
- package/dist/esm/components/index.d.ts.map +1 -1
- package/dist/esm/components/index.js +1 -1
- package/dist/esm/components/index.js.map +2 -2
- package/package.json +7 -6
- package/src/components/code/CodeBlock.tsx +119 -0
- package/src/components/code/CopyButton.tsx +46 -0
- package/src/components/code/LanguageBadge.tsx +33 -0
- package/src/components/code/PreviewSection.tsx +52 -0
- package/src/components/code/SyntaxHighlighter.tsx +55 -0
- package/src/components/code/index.ts +3 -0
- package/src/components/code/types.ts +32 -0
- package/src/components/index.css +88 -0
- package/src/components/index.ts +1 -0
- package/styles.css +82 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export interface CodeBlockProps {
|
|
3
|
+
children?: ReactNode;
|
|
4
|
+
code?: string;
|
|
5
|
+
language?: string;
|
|
6
|
+
preview?: ReactNode;
|
|
7
|
+
showCopy?: boolean;
|
|
8
|
+
showLanguage?: boolean;
|
|
9
|
+
file?: string;
|
|
10
|
+
collapsible?: boolean;
|
|
11
|
+
collapsedHeight?: number;
|
|
12
|
+
background?: "none" | "dots" | string;
|
|
13
|
+
backgroundProps?: {
|
|
14
|
+
dotSize?: number;
|
|
15
|
+
color?: string;
|
|
16
|
+
backgroundColor?: string;
|
|
17
|
+
height?: string;
|
|
18
|
+
width?: string;
|
|
19
|
+
radius?: string;
|
|
20
|
+
};
|
|
21
|
+
lightTheme?: string;
|
|
22
|
+
darkTheme?: string;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/components/code/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,MAAM,WAAW,cAAc;IAE7B,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IAGzB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IACtC,eAAe,CAAC,EAAE;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IAGF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export*from"./hero";
|
|
1
|
+
export*from"./code";export*from"./hero";
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/components/index.ts"],
|
|
4
|
-
"sourcesContent": ["export * from './hero';\n"],
|
|
5
|
-
"mappings": "AAAA,WAAc",
|
|
4
|
+
"sourcesContent": ["export * from './code';\nexport * from './hero';\n"],
|
|
5
|
+
"mappings": "AAAA,WAAc,SACd,WAAc",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kushagradhawan/kookie-blocks",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
|
-
"url": "https://github.com/KushagraDhawan1997/kookie-blocks.git"
|
|
6
|
+
"url": "git+https://github.com/KushagraDhawan1997/kookie-blocks.git"
|
|
7
7
|
},
|
|
8
8
|
"description": "Pre-built composable blocks built on top of Kookie UI",
|
|
9
9
|
"homepage": "https://github.com/KushagraDhawan1997/kookie-blocks#readme",
|
|
@@ -91,13 +91,14 @@
|
|
|
91
91
|
"prepublishOnly": "pnpm lint"
|
|
92
92
|
},
|
|
93
93
|
"dependencies": {
|
|
94
|
+
"@hugeicons/core-free-icons": "^2.0.0",
|
|
95
|
+
"@hugeicons/react": "^1.1.1",
|
|
94
96
|
"@kushagradhawan/kookie-ui": "latest",
|
|
95
|
-
"
|
|
96
|
-
"react-dom": "^19.0.0"
|
|
97
|
+
"shiki": "^1.24.2"
|
|
97
98
|
},
|
|
98
99
|
"peerDependencies": {
|
|
99
|
-
"react": "
|
|
100
|
-
"react-dom": "
|
|
100
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
101
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
101
102
|
},
|
|
102
103
|
"devDependencies": {
|
|
103
104
|
"@eslint/js": "^9.18.0",
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import React, { type ReactNode } from "react";
|
|
2
|
+
import { Card, Flex } from "@kushagradhawan/kookie-ui";
|
|
3
|
+
import { SyntaxHighlighter } from "./SyntaxHighlighter";
|
|
4
|
+
import { CopyButton } from "./CopyButton";
|
|
5
|
+
import { LanguageBadge } from "./LanguageBadge";
|
|
6
|
+
import { PreviewSection } from "./PreviewSection";
|
|
7
|
+
import type { CodeBlockProps } from "./types";
|
|
8
|
+
|
|
9
|
+
function extractLanguageFromChildren(children?: ReactNode): string {
|
|
10
|
+
if (!children) return "text";
|
|
11
|
+
|
|
12
|
+
// Try to extract language from pre > code className
|
|
13
|
+
if (typeof children === "object" && children !== null && "props" in children) {
|
|
14
|
+
const childProps = (children as any).props;
|
|
15
|
+
|
|
16
|
+
// If children is a <pre><code className="language-xxx">
|
|
17
|
+
if (childProps?.children && typeof childProps.children === "object") {
|
|
18
|
+
const codeProps = (childProps.children as any).props;
|
|
19
|
+
const className = codeProps?.className || "";
|
|
20
|
+
const match = className.match(/language-([\w-]+)/i);
|
|
21
|
+
if (match) return match[1];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Direct className on children
|
|
25
|
+
const className = childProps?.className || "";
|
|
26
|
+
const match = className.match(/language-([\w-]+)/i);
|
|
27
|
+
if (match) return match[1];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return "text";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function extractCodeFromChildren(children?: ReactNode): string {
|
|
34
|
+
if (!children) return "";
|
|
35
|
+
|
|
36
|
+
// Try to extract text from pre > code structure
|
|
37
|
+
if (typeof children === "object" && children !== null && "props" in children) {
|
|
38
|
+
const childProps = (children as any).props;
|
|
39
|
+
|
|
40
|
+
// If children is <pre><code>...</code></pre>
|
|
41
|
+
if (childProps?.children && typeof childProps.children === "object") {
|
|
42
|
+
const codeProps = (childProps.children as any).props;
|
|
43
|
+
const codeChildren = codeProps?.children;
|
|
44
|
+
|
|
45
|
+
if (typeof codeChildren === "string") {
|
|
46
|
+
return codeChildren;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Direct text content
|
|
51
|
+
if (typeof childProps?.children === "string") {
|
|
52
|
+
return childProps.children;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (typeof children === "string") {
|
|
57
|
+
return children;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return "";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function CodeBlock({
|
|
64
|
+
children,
|
|
65
|
+
code,
|
|
66
|
+
language,
|
|
67
|
+
preview,
|
|
68
|
+
showCopy = true,
|
|
69
|
+
showLanguage = true,
|
|
70
|
+
lightTheme,
|
|
71
|
+
darkTheme,
|
|
72
|
+
background,
|
|
73
|
+
backgroundProps,
|
|
74
|
+
}: CodeBlockProps) {
|
|
75
|
+
// Determine the code and language to display
|
|
76
|
+
let displayCode = code;
|
|
77
|
+
let displayLanguage = language;
|
|
78
|
+
|
|
79
|
+
// If children are provided (pre-highlighted from MDX), extract code and language
|
|
80
|
+
if (children && !code) {
|
|
81
|
+
displayCode = extractCodeFromChildren(children);
|
|
82
|
+
displayLanguage = language || extractLanguageFromChildren(children);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Default language
|
|
86
|
+
if (!displayLanguage) {
|
|
87
|
+
displayLanguage = "text";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// If no code to display, render nothing
|
|
91
|
+
if (!displayCode && !children) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<Flex direction="column" className="code-block-wrapper" style={{ minWidth: 0 }} my="2">
|
|
97
|
+
{preview && (
|
|
98
|
+
<PreviewSection background={background} backgroundProps={backgroundProps}>
|
|
99
|
+
{preview}
|
|
100
|
+
</PreviewSection>
|
|
101
|
+
)}
|
|
102
|
+
|
|
103
|
+
<Card size="2" variant="soft">
|
|
104
|
+
<Flex gap="2" direction="column">
|
|
105
|
+
<Flex align="start" justify="between">
|
|
106
|
+
{showLanguage && <LanguageBadge language={displayLanguage} />}
|
|
107
|
+
{showCopy && displayCode && <CopyButton code={displayCode} />}
|
|
108
|
+
</Flex>
|
|
109
|
+
|
|
110
|
+
{/* If we have runtime code, use SyntaxHighlighter */}
|
|
111
|
+
{code && <SyntaxHighlighter code={code} language={displayLanguage} lightTheme={lightTheme} darkTheme={darkTheme} />}
|
|
112
|
+
|
|
113
|
+
{/* If we have pre-highlighted children from MDX, render them */}
|
|
114
|
+
{children && !code && <div className="code-block-content">{children}</div>}
|
|
115
|
+
</Flex>
|
|
116
|
+
</Card>
|
|
117
|
+
</Flex>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import { Button } from "@kushagradhawan/kookie-ui";
|
|
3
|
+
import { HugeiconsIcon } from "@hugeicons/react";
|
|
4
|
+
import { Copy01Icon, Tick01Icon } from "@hugeicons/core-free-icons";
|
|
5
|
+
|
|
6
|
+
interface CopyButtonProps {
|
|
7
|
+
code: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function CopyButton({ code }: CopyButtonProps) {
|
|
11
|
+
const [copied, setCopied] = useState(false);
|
|
12
|
+
const resetTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
return () => {
|
|
16
|
+
if (resetTimeoutRef.current) {
|
|
17
|
+
clearTimeout(resetTimeoutRef.current);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}, []);
|
|
21
|
+
|
|
22
|
+
const handleCopy = useCallback(async () => {
|
|
23
|
+
if (!code.trim()) return;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
await navigator.clipboard.writeText(code);
|
|
27
|
+
setCopied(true);
|
|
28
|
+
|
|
29
|
+
if (resetTimeoutRef.current) {
|
|
30
|
+
clearTimeout(resetTimeoutRef.current);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
resetTimeoutRef.current = setTimeout(() => {
|
|
34
|
+
setCopied(false);
|
|
35
|
+
}, 2000);
|
|
36
|
+
} catch {
|
|
37
|
+
// Silently fail
|
|
38
|
+
}
|
|
39
|
+
}, [code]);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<Button size="2" tooltip={copied ? "Copied" : "Copy code"} variant="ghost" onClick={handleCopy} aria-label={copied ? "Copied" : "Copy code"}>
|
|
43
|
+
<HugeiconsIcon icon={copied ? Tick01Icon : Copy01Icon} /> Copy
|
|
44
|
+
</Button>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Code } from "@kushagradhawan/kookie-ui";
|
|
3
|
+
|
|
4
|
+
interface LanguageBadgeProps {
|
|
5
|
+
language: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function formatLanguage(lang: string): string {
|
|
9
|
+
const languageMap: Record<string, string> = {
|
|
10
|
+
js: "JS",
|
|
11
|
+
jsx: "JSX",
|
|
12
|
+
ts: "TS",
|
|
13
|
+
tsx: "TSX",
|
|
14
|
+
py: "Python",
|
|
15
|
+
rb: "Ruby",
|
|
16
|
+
sh: "Shell",
|
|
17
|
+
bash: "Bash",
|
|
18
|
+
text: "plaintext",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return languageMap[lang.toLowerCase()] || lang;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function LanguageBadge({ language }: LanguageBadgeProps) {
|
|
25
|
+
const displayLanguage = formatLanguage(language);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<Code size="1" color="gray" highContrast>
|
|
29
|
+
{displayLanguage}
|
|
30
|
+
</Code>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React, { type ReactNode } from "react";
|
|
2
|
+
import { Box } from "@kushagradhawan/kookie-ui";
|
|
3
|
+
|
|
4
|
+
interface PreviewSectionProps {
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
background?: "none" | "dots" | string;
|
|
7
|
+
backgroundProps?: {
|
|
8
|
+
dotSize?: number;
|
|
9
|
+
color?: string;
|
|
10
|
+
backgroundColor?: string;
|
|
11
|
+
height?: string;
|
|
12
|
+
width?: string;
|
|
13
|
+
radius?: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function PreviewSection({ children, background = "none", backgroundProps = {} }: PreviewSectionProps) {
|
|
18
|
+
const { dotSize = 2, color = "#d4d4d8", backgroundColor = "transparent", height = "auto", width = "100%", radius = "var(--radius-3)" } = backgroundProps;
|
|
19
|
+
|
|
20
|
+
const backgroundStyle =
|
|
21
|
+
background === "dots"
|
|
22
|
+
? {
|
|
23
|
+
backgroundImage: `radial-gradient(circle, ${color} ${dotSize}px, ${backgroundColor} ${dotSize}px)`,
|
|
24
|
+
backgroundSize: "20px 20px",
|
|
25
|
+
}
|
|
26
|
+
: background !== "none"
|
|
27
|
+
? {
|
|
28
|
+
backgroundImage: `url(${background})`,
|
|
29
|
+
backgroundSize: "cover",
|
|
30
|
+
backgroundPosition: "center",
|
|
31
|
+
}
|
|
32
|
+
: {};
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<Box
|
|
36
|
+
p="4"
|
|
37
|
+
style={{
|
|
38
|
+
...backgroundStyle,
|
|
39
|
+
height,
|
|
40
|
+
width,
|
|
41
|
+
borderRadius: radius,
|
|
42
|
+
display: "flex",
|
|
43
|
+
alignItems: "center",
|
|
44
|
+
justifyContent: "center",
|
|
45
|
+
minHeight: "200px",
|
|
46
|
+
}}
|
|
47
|
+
>
|
|
48
|
+
{children}
|
|
49
|
+
</Box>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
|
+
import { codeToHtml } from "shiki";
|
|
3
|
+
import { Box, Code } from "@kushagradhawan/kookie-ui";
|
|
4
|
+
|
|
5
|
+
interface SyntaxHighlighterProps {
|
|
6
|
+
code: string;
|
|
7
|
+
language: string;
|
|
8
|
+
lightTheme?: string;
|
|
9
|
+
darkTheme?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const DEFAULT_LIGHT_THEME = "one-light";
|
|
13
|
+
const DEFAULT_DARK_THEME = "one-dark-pro";
|
|
14
|
+
|
|
15
|
+
export function SyntaxHighlighter({ code, language, lightTheme = DEFAULT_LIGHT_THEME, darkTheme = DEFAULT_DARK_THEME }: SyntaxHighlighterProps) {
|
|
16
|
+
const [highlighted, setHighlighted] = useState<string | null>(null);
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
let cancelled = false;
|
|
20
|
+
|
|
21
|
+
codeToHtml(code, {
|
|
22
|
+
lang: language,
|
|
23
|
+
themes: {
|
|
24
|
+
light: lightTheme,
|
|
25
|
+
dark: darkTheme,
|
|
26
|
+
},
|
|
27
|
+
defaultColor: false,
|
|
28
|
+
})
|
|
29
|
+
.then((html) => {
|
|
30
|
+
if (!cancelled) {
|
|
31
|
+
setHighlighted(html);
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
.catch(() => {
|
|
35
|
+
if (!cancelled) {
|
|
36
|
+
setHighlighted(null);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return () => {
|
|
41
|
+
cancelled = true;
|
|
42
|
+
};
|
|
43
|
+
}, [code, language, lightTheme, darkTheme]);
|
|
44
|
+
|
|
45
|
+
if (!highlighted) {
|
|
46
|
+
return (
|
|
47
|
+
<pre className="code-block-content">
|
|
48
|
+
<Code size="3">{code}</Code>
|
|
49
|
+
</pre>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return <Box className="code-block-content" width="100%" style={{ minWidth: 0 }} dangerouslySetInnerHTML={{ __html: highlighted }} />;
|
|
54
|
+
}
|
|
55
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
export interface CodeBlockProps {
|
|
4
|
+
// Content modes (mutually exclusive)
|
|
5
|
+
children?: ReactNode; // Pre-highlighted HTML from build-time (MDX)
|
|
6
|
+
code?: string; // Raw code to highlight at runtime
|
|
7
|
+
language?: string; // Language for runtime highlighting
|
|
8
|
+
|
|
9
|
+
// Optional features
|
|
10
|
+
preview?: ReactNode; // Preview component (for docs)
|
|
11
|
+
showCopy?: boolean; // Default: true
|
|
12
|
+
showLanguage?: boolean; // Default: true
|
|
13
|
+
file?: string; // File path label
|
|
14
|
+
collapsible?: boolean; // Enable expand/collapse
|
|
15
|
+
collapsedHeight?: number; // Default: 360px
|
|
16
|
+
|
|
17
|
+
// Preview styling (for kookie-ui docs)
|
|
18
|
+
background?: "none" | "dots" | string; // Background type
|
|
19
|
+
backgroundProps?: {
|
|
20
|
+
dotSize?: number;
|
|
21
|
+
color?: string;
|
|
22
|
+
backgroundColor?: string;
|
|
23
|
+
height?: string;
|
|
24
|
+
width?: string;
|
|
25
|
+
radius?: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Theme
|
|
29
|
+
lightTheme?: string; // Default: 'one-light'
|
|
30
|
+
darkTheme?: string; // Default: 'one-dark-pro'
|
|
31
|
+
}
|
|
32
|
+
|
package/src/components/index.css
CHANGED
|
@@ -1 +1,89 @@
|
|
|
1
1
|
/* Components CSS aggregator for Kookie Blocks (e.g., hero variants if styled). */
|
|
2
|
+
|
|
3
|
+
/* ============================================
|
|
4
|
+
Code Block Component Styles
|
|
5
|
+
============================================ */
|
|
6
|
+
|
|
7
|
+
/* Code block wrapper - constrain width in flex containers */
|
|
8
|
+
.code-block-wrapper {
|
|
9
|
+
min-width: 0;
|
|
10
|
+
max-width: 100%;
|
|
11
|
+
width: 100%;
|
|
12
|
+
overflow: hidden;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* Code block content (the pre part, below the header) */
|
|
16
|
+
.code-block-content {
|
|
17
|
+
width: 100%;
|
|
18
|
+
min-width: 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.code-block-wrapper pre,
|
|
22
|
+
.code-block-wrapper .code-block-content pre {
|
|
23
|
+
overflow-x: auto;
|
|
24
|
+
max-width: 100%;
|
|
25
|
+
min-width: 0;
|
|
26
|
+
margin: 0;
|
|
27
|
+
width: 100%;
|
|
28
|
+
box-sizing: border-box;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* Ensure Shiki-generated pre elements fill width */
|
|
32
|
+
.code-block-content > pre {
|
|
33
|
+
width: 100% !important;
|
|
34
|
+
min-width: 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* Code elements inside pre */
|
|
38
|
+
.code-block-wrapper pre code,
|
|
39
|
+
.code-block-wrapper .code-block-content pre code {
|
|
40
|
+
font-family: var(--font-mono);
|
|
41
|
+
font-size: var(--font-size-2);
|
|
42
|
+
line-height: 1.75;
|
|
43
|
+
background: none !important;
|
|
44
|
+
display: flex;
|
|
45
|
+
flex-direction: column;
|
|
46
|
+
gap: var(--space-1);
|
|
47
|
+
counter-reset: line;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Shiki line spans */
|
|
51
|
+
.code-block-wrapper pre code .line,
|
|
52
|
+
.code-block-wrapper .code-block-content pre code .line {
|
|
53
|
+
display: flex;
|
|
54
|
+
align-items: center;
|
|
55
|
+
gap: 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Line numbers */
|
|
59
|
+
.code-block-wrapper pre code .line::before,
|
|
60
|
+
.code-block-wrapper .code-block-content pre code .line::before {
|
|
61
|
+
counter-increment: line;
|
|
62
|
+
content: counter(line);
|
|
63
|
+
display: inline-block;
|
|
64
|
+
min-width: 2ch;
|
|
65
|
+
text-align: right;
|
|
66
|
+
font-size: var(--font-size-1);
|
|
67
|
+
color: var(--gray-a9);
|
|
68
|
+
user-select: none;
|
|
69
|
+
flex-shrink: 0;
|
|
70
|
+
margin-right: var(--space-4);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* Default to light theme for all tokens (codeToHtml with defaultColor: false) */
|
|
74
|
+
.code-block-wrapper pre.shiki span {
|
|
75
|
+
color: var(--shiki-light) !important;
|
|
76
|
+
font-style: var(--shiki-light-font-style);
|
|
77
|
+
font-weight: var(--shiki-light-font-weight);
|
|
78
|
+
text-decoration: var(--shiki-light-text-decoration);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* Override with dark theme colors when inside a dark context */
|
|
82
|
+
.dark .code-block-wrapper pre.shiki span,
|
|
83
|
+
.dark-theme .code-block-wrapper pre.shiki span,
|
|
84
|
+
[data-appearance="dark"] .code-block-wrapper pre.shiki span {
|
|
85
|
+
color: var(--shiki-dark) !important;
|
|
86
|
+
font-style: var(--shiki-dark-font-style);
|
|
87
|
+
font-weight: var(--shiki-dark-font-weight);
|
|
88
|
+
text-decoration: var(--shiki-dark-text-decoration);
|
|
89
|
+
}
|
package/src/components/index.ts
CHANGED
package/styles.css
CHANGED
|
@@ -1,2 +1,84 @@
|
|
|
1
1
|
/* Kookie Blocks base styles. Keep minimal; rely on Kookie UI tokens/utilities. */
|
|
2
2
|
/* Components CSS aggregator for Kookie Blocks (e.g., hero variants if styled). */
|
|
3
|
+
/* ============================================
|
|
4
|
+
Code Block Component Styles
|
|
5
|
+
============================================ */
|
|
6
|
+
/* Code block wrapper - constrain width in flex containers */
|
|
7
|
+
.code-block-wrapper {
|
|
8
|
+
min-width: 0;
|
|
9
|
+
max-width: 100%;
|
|
10
|
+
width: 100%;
|
|
11
|
+
overflow: hidden;
|
|
12
|
+
}
|
|
13
|
+
/* Code block content (the pre part, below the header) */
|
|
14
|
+
.code-block-content {
|
|
15
|
+
width: 100%;
|
|
16
|
+
min-width: 0;
|
|
17
|
+
}
|
|
18
|
+
.code-block-wrapper pre,
|
|
19
|
+
.code-block-wrapper .code-block-content pre {
|
|
20
|
+
overflow-x: auto;
|
|
21
|
+
max-width: 100%;
|
|
22
|
+
min-width: 0;
|
|
23
|
+
margin: 0;
|
|
24
|
+
width: 100%;
|
|
25
|
+
box-sizing: border-box;
|
|
26
|
+
}
|
|
27
|
+
/* Ensure Shiki-generated pre elements fill width */
|
|
28
|
+
.code-block-content > pre {
|
|
29
|
+
width: 100% !important;
|
|
30
|
+
min-width: 0;
|
|
31
|
+
}
|
|
32
|
+
/* Code elements inside pre */
|
|
33
|
+
.code-block-wrapper pre code,
|
|
34
|
+
.code-block-wrapper .code-block-content pre code {
|
|
35
|
+
font-family: var(--font-mono);
|
|
36
|
+
font-size: var(--font-size-2);
|
|
37
|
+
line-height: 1.75;
|
|
38
|
+
background: none !important;
|
|
39
|
+
display: flex;
|
|
40
|
+
flex-direction: column;
|
|
41
|
+
gap: var(--space-1);
|
|
42
|
+
counter-reset: line;
|
|
43
|
+
}
|
|
44
|
+
/* Shiki line spans */
|
|
45
|
+
.code-block-wrapper pre code .line,
|
|
46
|
+
.code-block-wrapper .code-block-content pre code .line {
|
|
47
|
+
display: flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
gap: 0;
|
|
50
|
+
}
|
|
51
|
+
/* Line numbers */
|
|
52
|
+
.code-block-wrapper pre code .line::before,
|
|
53
|
+
.code-block-wrapper .code-block-content pre code .line::before {
|
|
54
|
+
counter-increment: line;
|
|
55
|
+
content: counter(line);
|
|
56
|
+
display: inline-block;
|
|
57
|
+
min-width: 2ch;
|
|
58
|
+
text-align: right;
|
|
59
|
+
font-size: var(--font-size-1);
|
|
60
|
+
color: var(--gray-a9);
|
|
61
|
+
-webkit-user-select: none;
|
|
62
|
+
-moz-user-select: none;
|
|
63
|
+
user-select: none;
|
|
64
|
+
flex-shrink: 0;
|
|
65
|
+
margin-right: var(--space-4);
|
|
66
|
+
}
|
|
67
|
+
/* Default to light theme for all tokens (codeToHtml with defaultColor: false) */
|
|
68
|
+
.code-block-wrapper pre.shiki span {
|
|
69
|
+
color: var(--shiki-light) !important;
|
|
70
|
+
font-style: var(--shiki-light-font-style);
|
|
71
|
+
font-weight: var(--shiki-light-font-weight);
|
|
72
|
+
-webkit-text-decoration: var(--shiki-light-text-decoration);
|
|
73
|
+
text-decoration: var(--shiki-light-text-decoration);
|
|
74
|
+
}
|
|
75
|
+
/* Override with dark theme colors when inside a dark context */
|
|
76
|
+
.dark .code-block-wrapper pre.shiki span,
|
|
77
|
+
.dark-theme .code-block-wrapper pre.shiki span,
|
|
78
|
+
[data-appearance="dark"] .code-block-wrapper pre.shiki span {
|
|
79
|
+
color: var(--shiki-dark) !important;
|
|
80
|
+
font-style: var(--shiki-dark-font-style);
|
|
81
|
+
font-weight: var(--shiki-dark-font-weight);
|
|
82
|
+
-webkit-text-decoration: var(--shiki-dark-text-decoration);
|
|
83
|
+
text-decoration: var(--shiki-dark-text-decoration);
|
|
84
|
+
}
|