@kushagradhawan/kookie-blocks 0.1.3 → 0.1.5
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 +98 -18
- package/dist/cjs/components/code/CodeBlock.d.ts +1 -1
- package/dist/cjs/components/code/CodeBlock.d.ts.map +1 -1
- package/dist/cjs/components/code/CodeBlock.js +1 -1
- package/dist/cjs/components/code/CodeBlock.js.map +3 -3
- package/dist/cjs/components/code/PreviewSection.d.ts.map +1 -1
- package/dist/cjs/components/code/PreviewSection.js +1 -1
- package/dist/cjs/components/code/PreviewSection.js.map +3 -3
- package/dist/esm/components/code/CodeBlock.d.ts +1 -1
- package/dist/esm/components/code/CodeBlock.d.ts.map +1 -1
- package/dist/esm/components/code/CodeBlock.js +1 -1
- package/dist/esm/components/code/CodeBlock.js.map +3 -3
- package/dist/esm/components/code/PreviewSection.d.ts.map +1 -1
- package/dist/esm/components/code/PreviewSection.js +1 -1
- package/dist/esm/components/code/PreviewSection.js.map +3 -3
- package/package.json +2 -2
- package/src/components/code/CodeBlock.tsx +335 -73
- package/src/components/code/PreviewSection.tsx +49 -31
- package/src/components/index.css +91 -18
- package/styles.css +84 -18
|
@@ -1,64 +1,346 @@
|
|
|
1
|
-
import React, { type ReactNode } from "react";
|
|
2
|
-
import { Card, Flex } from "@kushagradhawan/kookie-ui";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { PreviewSection } from "./PreviewSection";
|
|
1
|
+
import React, { useState, useRef, useEffect, useCallback, memo, type ReactNode } from "react";
|
|
2
|
+
import { Box, Card, Flex, Button, Code, Theme } from "@kushagradhawan/kookie-ui";
|
|
3
|
+
import { HugeiconsIcon } from "@hugeicons/react";
|
|
4
|
+
import { Copy01Icon, Tick01Icon } from "@hugeicons/core-free-icons";
|
|
5
|
+
import { codeToHtml } from "shiki";
|
|
7
6
|
import type { CodeBlockProps } from "./types";
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
const COLLAPSED_HEIGHT = 360;
|
|
9
|
+
const DEFAULT_LIGHT_THEME = "one-light";
|
|
10
|
+
const DEFAULT_DARK_THEME = "one-dark-pro";
|
|
11
|
+
|
|
12
|
+
// ============================================
|
|
13
|
+
// Preview Section
|
|
14
|
+
// ============================================
|
|
15
|
+
|
|
16
|
+
interface PreviewSectionProps {
|
|
17
|
+
children: ReactNode;
|
|
18
|
+
background?: "none" | "dots" | string;
|
|
19
|
+
backgroundProps?: {
|
|
20
|
+
dotSize?: number;
|
|
21
|
+
color?: string;
|
|
22
|
+
backgroundColor?: string;
|
|
23
|
+
height?: string;
|
|
24
|
+
width?: string;
|
|
25
|
+
radius?: string;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function PreviewSection({ children, background = "none", backgroundProps = {} }: PreviewSectionProps) {
|
|
30
|
+
const { dotSize = 24, color = "var(--gray-10)", backgroundColor = "var(--gray-2)", height, width = "100%", radius = "3" } = backgroundProps;
|
|
31
|
+
|
|
32
|
+
if (background === "none") {
|
|
33
|
+
return (
|
|
34
|
+
<Card size="1" variant="soft">
|
|
35
|
+
<Flex justify="center" align="center" py="4">
|
|
36
|
+
<Theme fontFamily="sans">{children}</Theme>
|
|
37
|
+
</Flex>
|
|
38
|
+
</Card>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (background === "dots") {
|
|
43
|
+
const dotsStyle: React.CSSProperties = {
|
|
44
|
+
backgroundImage: `radial-gradient(circle, ${color} 1px, transparent 1px)`,
|
|
45
|
+
borderRadius: `var(--radius-${radius})`,
|
|
46
|
+
backgroundSize: `${dotSize}px ${dotSize}px`,
|
|
47
|
+
backgroundPosition: "center",
|
|
48
|
+
backgroundColor,
|
|
49
|
+
width,
|
|
50
|
+
...(height && { height }),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<Card size="1" variant="soft">
|
|
55
|
+
<Flex justify="center" align="center" py="4" style={dotsStyle}>
|
|
56
|
+
<Theme fontFamily="sans">{children}</Theme>
|
|
57
|
+
</Flex>
|
|
58
|
+
</Card>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const imageStyle: React.CSSProperties = {
|
|
63
|
+
backgroundImage: `url(${background})`,
|
|
64
|
+
backgroundSize: "cover",
|
|
65
|
+
backgroundPosition: "center",
|
|
66
|
+
backgroundRepeat: "no-repeat",
|
|
67
|
+
borderRadius: `var(--radius-${radius})`,
|
|
68
|
+
width,
|
|
69
|
+
...(height && { height }),
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<Card size="1" variant="soft">
|
|
74
|
+
<Flex justify="center" align="center" py="4" style={imageStyle}>
|
|
75
|
+
<Theme fontFamily="sans">{children}</Theme>
|
|
76
|
+
</Flex>
|
|
77
|
+
</Card>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ============================================
|
|
82
|
+
// Code Section (for runtime highlighting)
|
|
83
|
+
// ============================================
|
|
11
84
|
|
|
12
|
-
|
|
85
|
+
interface CodeSectionProps {
|
|
86
|
+
code: string;
|
|
87
|
+
language: string;
|
|
88
|
+
showCopy?: boolean;
|
|
89
|
+
showLanguage?: boolean;
|
|
90
|
+
lightTheme?: string;
|
|
91
|
+
darkTheme?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const CodeSection = memo(function CodeSection({
|
|
95
|
+
code,
|
|
96
|
+
language,
|
|
97
|
+
showCopy = true,
|
|
98
|
+
showLanguage = true,
|
|
99
|
+
lightTheme = DEFAULT_LIGHT_THEME,
|
|
100
|
+
darkTheme = DEFAULT_DARK_THEME,
|
|
101
|
+
}: CodeSectionProps) {
|
|
102
|
+
const [highlighted, setHighlighted] = useState<string | null>(null);
|
|
103
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
104
|
+
const [contentHeight, setContentHeight] = useState(COLLAPSED_HEIGHT);
|
|
105
|
+
const [copied, setCopied] = useState(false);
|
|
106
|
+
const contentRef = useRef<HTMLDivElement>(null);
|
|
107
|
+
const resetTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
108
|
+
|
|
109
|
+
const shouldShowToggle = contentHeight > COLLAPSED_HEIGHT;
|
|
110
|
+
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
let cancelled = false;
|
|
113
|
+
codeToHtml(code, {
|
|
114
|
+
lang: language,
|
|
115
|
+
themes: { light: lightTheme, dark: darkTheme },
|
|
116
|
+
defaultColor: false,
|
|
117
|
+
})
|
|
118
|
+
.then((html) => {
|
|
119
|
+
if (!cancelled) setHighlighted(html);
|
|
120
|
+
})
|
|
121
|
+
.catch(() => {
|
|
122
|
+
if (!cancelled) setHighlighted(null);
|
|
123
|
+
});
|
|
124
|
+
return () => {
|
|
125
|
+
cancelled = true;
|
|
126
|
+
};
|
|
127
|
+
}, [code, language, lightTheme, darkTheme]);
|
|
128
|
+
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
if (contentRef.current) {
|
|
131
|
+
setContentHeight(contentRef.current.scrollHeight);
|
|
132
|
+
}
|
|
133
|
+
}, [highlighted]);
|
|
134
|
+
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
return () => {
|
|
137
|
+
if (resetTimeoutRef.current) clearTimeout(resetTimeoutRef.current);
|
|
138
|
+
};
|
|
139
|
+
}, []);
|
|
140
|
+
|
|
141
|
+
const handleCopy = useCallback(async () => {
|
|
142
|
+
if (!code.trim()) return;
|
|
143
|
+
try {
|
|
144
|
+
await navigator.clipboard.writeText(code);
|
|
145
|
+
setCopied(true);
|
|
146
|
+
if (resetTimeoutRef.current) clearTimeout(resetTimeoutRef.current);
|
|
147
|
+
resetTimeoutRef.current = setTimeout(() => setCopied(false), 2000);
|
|
148
|
+
} catch {
|
|
149
|
+
// Silently fail
|
|
150
|
+
}
|
|
151
|
+
}, [code]);
|
|
152
|
+
|
|
153
|
+
const displayLanguage = language === "text" ? "plaintext" : language;
|
|
154
|
+
|
|
155
|
+
const contentStyle: React.CSSProperties = {
|
|
156
|
+
maxHeight: isExpanded ? `${contentHeight}px` : `${COLLAPSED_HEIGHT}px`,
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<Box position="relative">
|
|
161
|
+
<Card size="1" variant="soft">
|
|
162
|
+
<Flex direction="column" gap="3">
|
|
163
|
+
<Flex gap="2" justify="between" align="start">
|
|
164
|
+
{showLanguage && (
|
|
165
|
+
<Code size="1" color="gray" highContrast>
|
|
166
|
+
{displayLanguage}
|
|
167
|
+
</Code>
|
|
168
|
+
)}
|
|
169
|
+
<Flex align="center" gap="2" className="code-action-buttons">
|
|
170
|
+
{showCopy && (
|
|
171
|
+
<Button
|
|
172
|
+
size="2"
|
|
173
|
+
variant="ghost"
|
|
174
|
+
color="gray"
|
|
175
|
+
onClick={handleCopy}
|
|
176
|
+
tooltip={copied ? "Copied!" : "Copy"}
|
|
177
|
+
aria-label={copied ? "Copied!" : "Copy code"}
|
|
178
|
+
>
|
|
179
|
+
<HugeiconsIcon icon={copied ? Tick01Icon : Copy01Icon} /> Copy
|
|
180
|
+
</Button>
|
|
181
|
+
)}
|
|
182
|
+
</Flex>
|
|
183
|
+
</Flex>
|
|
184
|
+
|
|
185
|
+
<Box ref={contentRef} style={contentStyle} className="code-content">
|
|
186
|
+
{highlighted ? (
|
|
187
|
+
<Box className="code-block-content" width="100%" style={{ minWidth: 0 }} dangerouslySetInnerHTML={{ __html: highlighted }} />
|
|
188
|
+
) : (
|
|
189
|
+
<pre className="code-block-content">
|
|
190
|
+
<Code size="3">{code}</Code>
|
|
191
|
+
</pre>
|
|
192
|
+
)}
|
|
193
|
+
</Box>
|
|
194
|
+
|
|
195
|
+
{shouldShowToggle && !isExpanded && <Box className="code-scroll-shadow visible" />}
|
|
196
|
+
</Flex>
|
|
197
|
+
</Card>
|
|
198
|
+
</Box>
|
|
199
|
+
);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// ============================================
|
|
203
|
+
// Children Code Section (for pre-highlighted MDX)
|
|
204
|
+
// ============================================
|
|
205
|
+
|
|
206
|
+
interface ChildrenCodeSectionProps {
|
|
207
|
+
children: ReactNode;
|
|
208
|
+
showCopy?: boolean;
|
|
209
|
+
showLanguage?: boolean;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function extractCodeFromChildren(children?: ReactNode): string {
|
|
213
|
+
if (!children) return "";
|
|
13
214
|
if (typeof children === "object" && children !== null && "props" in children) {
|
|
14
215
|
const childProps = (children as any).props;
|
|
216
|
+
if (childProps?.children && typeof childProps.children === "object") {
|
|
217
|
+
const codeProps = (childProps.children as any).props;
|
|
218
|
+
const codeChildren = codeProps?.children;
|
|
219
|
+
if (typeof codeChildren === "string") return codeChildren;
|
|
220
|
+
}
|
|
221
|
+
if (typeof childProps?.children === "string") return childProps.children;
|
|
222
|
+
}
|
|
223
|
+
if (typeof children === "string") return children;
|
|
224
|
+
return "";
|
|
225
|
+
}
|
|
15
226
|
|
|
16
|
-
|
|
227
|
+
function extractLanguageFromChildren(children?: ReactNode): string {
|
|
228
|
+
if (!children) return "text";
|
|
229
|
+
if (typeof children === "object" && children !== null && "props" in children) {
|
|
230
|
+
const childProps = (children as any).props;
|
|
17
231
|
if (childProps?.children && typeof childProps.children === "object") {
|
|
18
232
|
const codeProps = (childProps.children as any).props;
|
|
19
233
|
const className = codeProps?.className || "";
|
|
20
234
|
const match = className.match(/language-([\w-]+)/i);
|
|
21
235
|
if (match) return match[1];
|
|
22
236
|
}
|
|
23
|
-
|
|
24
|
-
// Direct className on children
|
|
25
237
|
const className = childProps?.className || "";
|
|
26
238
|
const match = className.match(/language-([\w-]+)/i);
|
|
27
239
|
if (match) return match[1];
|
|
28
240
|
}
|
|
29
|
-
|
|
30
241
|
return "text";
|
|
31
242
|
}
|
|
32
243
|
|
|
33
|
-
function
|
|
34
|
-
|
|
244
|
+
function formatLanguageLabel(lang: string): string {
|
|
245
|
+
const aliasMap: Record<string, string> = {
|
|
246
|
+
tsx: "TSX",
|
|
247
|
+
ts: "TS",
|
|
248
|
+
jsx: "JSX",
|
|
249
|
+
js: "JS",
|
|
250
|
+
javascript: "JS",
|
|
251
|
+
typescript: "TS",
|
|
252
|
+
css: "CSS",
|
|
253
|
+
html: "HTML",
|
|
254
|
+
json: "JSON",
|
|
255
|
+
bash: "SH",
|
|
256
|
+
sh: "SH",
|
|
257
|
+
shell: "SH",
|
|
258
|
+
text: "plaintext",
|
|
259
|
+
};
|
|
260
|
+
return aliasMap[lang.toLowerCase()] || lang.toLowerCase();
|
|
261
|
+
}
|
|
35
262
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
263
|
+
const ChildrenCodeSection = memo(function ChildrenCodeSection({ children, showCopy = true, showLanguage = true }: ChildrenCodeSectionProps) {
|
|
264
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
265
|
+
const [contentHeight, setContentHeight] = useState(COLLAPSED_HEIGHT);
|
|
266
|
+
const [copied, setCopied] = useState(false);
|
|
267
|
+
const contentRef = useRef<HTMLDivElement>(null);
|
|
268
|
+
const resetTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
39
269
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
270
|
+
const code = extractCodeFromChildren(children);
|
|
271
|
+
const language = extractLanguageFromChildren(children);
|
|
272
|
+
const displayLanguage = formatLanguageLabel(language);
|
|
273
|
+
|
|
274
|
+
const shouldShowToggle = contentHeight > COLLAPSED_HEIGHT;
|
|
44
275
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
276
|
+
useEffect(() => {
|
|
277
|
+
if (contentRef.current) {
|
|
278
|
+
setContentHeight(contentRef.current.scrollHeight);
|
|
48
279
|
}
|
|
280
|
+
}, [children]);
|
|
281
|
+
|
|
282
|
+
useEffect(() => {
|
|
283
|
+
return () => {
|
|
284
|
+
if (resetTimeoutRef.current) clearTimeout(resetTimeoutRef.current);
|
|
285
|
+
};
|
|
286
|
+
}, []);
|
|
49
287
|
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
|
|
288
|
+
const handleCopy = useCallback(async () => {
|
|
289
|
+
if (!code.trim()) return;
|
|
290
|
+
try {
|
|
291
|
+
await navigator.clipboard.writeText(code);
|
|
292
|
+
setCopied(true);
|
|
293
|
+
if (resetTimeoutRef.current) clearTimeout(resetTimeoutRef.current);
|
|
294
|
+
resetTimeoutRef.current = setTimeout(() => setCopied(false), 2000);
|
|
295
|
+
} catch {
|
|
296
|
+
// Silently fail
|
|
53
297
|
}
|
|
54
|
-
}
|
|
298
|
+
}, [code]);
|
|
55
299
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
300
|
+
const contentStyle: React.CSSProperties = {
|
|
301
|
+
maxHeight: isExpanded ? `${contentHeight}px` : `${COLLAPSED_HEIGHT}px`,
|
|
302
|
+
};
|
|
59
303
|
|
|
60
|
-
return
|
|
61
|
-
|
|
304
|
+
return (
|
|
305
|
+
<Box position="relative">
|
|
306
|
+
<Card size="1" variant="soft">
|
|
307
|
+
<Flex direction="column" gap="3">
|
|
308
|
+
<Flex gap="2" justify="between" align="start">
|
|
309
|
+
{showLanguage && (
|
|
310
|
+
<Code size="1" color="gray" highContrast>
|
|
311
|
+
{displayLanguage}
|
|
312
|
+
</Code>
|
|
313
|
+
)}
|
|
314
|
+
<Flex align="center" gap="2" className="code-action-buttons">
|
|
315
|
+
{showCopy && (
|
|
316
|
+
<Button
|
|
317
|
+
size="2"
|
|
318
|
+
variant="ghost"
|
|
319
|
+
color="gray"
|
|
320
|
+
onClick={handleCopy}
|
|
321
|
+
tooltip={copied ? "Copied!" : "Copy"}
|
|
322
|
+
aria-label={copied ? "Copied!" : "Copy code"}
|
|
323
|
+
>
|
|
324
|
+
<HugeiconsIcon icon={copied ? Tick01Icon : Copy01Icon} /> Copy
|
|
325
|
+
</Button>
|
|
326
|
+
)}
|
|
327
|
+
</Flex>
|
|
328
|
+
</Flex>
|
|
329
|
+
|
|
330
|
+
<Box ref={contentRef} style={contentStyle} className="code-content">
|
|
331
|
+
<div className="code-block-content">{children}</div>
|
|
332
|
+
</Box>
|
|
333
|
+
|
|
334
|
+
{shouldShowToggle && !isExpanded && <Box className="code-scroll-shadow visible" />}
|
|
335
|
+
</Flex>
|
|
336
|
+
</Card>
|
|
337
|
+
</Box>
|
|
338
|
+
);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// ============================================
|
|
342
|
+
// Main CodeBlock Component
|
|
343
|
+
// ============================================
|
|
62
344
|
|
|
63
345
|
export function CodeBlock({
|
|
64
346
|
children,
|
|
@@ -72,48 +354,28 @@ export function CodeBlock({
|
|
|
72
354
|
background,
|
|
73
355
|
backgroundProps,
|
|
74
356
|
}: CodeBlockProps) {
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
}
|
|
357
|
+
const hasCode = code || (children && React.Children.count(children) > 0);
|
|
358
|
+
const displayLanguage = language || extractLanguageFromChildren(children) || "text";
|
|
94
359
|
|
|
95
360
|
return (
|
|
96
|
-
<
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
{
|
|
100
|
-
|
|
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>
|
|
361
|
+
<Box className="docs-code-block" mt="6" mb="8">
|
|
362
|
+
<Flex direction="column" gap="2">
|
|
363
|
+
{preview && (
|
|
364
|
+
<PreviewSection background={background} backgroundProps={backgroundProps}>
|
|
365
|
+
{preview}
|
|
366
|
+
</PreviewSection>
|
|
367
|
+
)}
|
|
109
368
|
|
|
110
|
-
|
|
111
|
-
|
|
369
|
+
{code && (
|
|
370
|
+
<CodeSection code={code} language={displayLanguage} showCopy={showCopy} showLanguage={showLanguage} lightTheme={lightTheme} darkTheme={darkTheme} />
|
|
371
|
+
)}
|
|
112
372
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
373
|
+
{children && !code && (
|
|
374
|
+
<ChildrenCodeSection showCopy={showCopy} showLanguage={showLanguage}>
|
|
375
|
+
{children}
|
|
376
|
+
</ChildrenCodeSection>
|
|
377
|
+
)}
|
|
378
|
+
</Flex>
|
|
379
|
+
</Box>
|
|
118
380
|
);
|
|
119
381
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { type ReactNode } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { Card, Flex, Theme } from "@kushagradhawan/kookie-ui";
|
|
3
3
|
|
|
4
4
|
interface PreviewSectionProps {
|
|
5
5
|
children: ReactNode;
|
|
@@ -15,38 +15,56 @@ interface PreviewSectionProps {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export function PreviewSection({ children, background = "none", backgroundProps = {} }: PreviewSectionProps) {
|
|
18
|
-
const { dotSize =
|
|
18
|
+
const { dotSize = 24, color = "var(--gray-10)", backgroundColor = "var(--gray-2)", height, width = "100%", radius = "3" } = backgroundProps;
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
20
|
+
// Render with no background (default card styling)
|
|
21
|
+
if (background === "none") {
|
|
22
|
+
return (
|
|
23
|
+
<Card size="1" variant="soft">
|
|
24
|
+
<Flex justify="center" align="center" py="4">
|
|
25
|
+
<Theme fontFamily="sans">{children}</Theme>
|
|
26
|
+
</Flex>
|
|
27
|
+
</Card>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Render with dots pattern background
|
|
32
|
+
if (background === "dots") {
|
|
33
|
+
const dotsStyle: React.CSSProperties = {
|
|
34
|
+
backgroundImage: `radial-gradient(circle, ${color} 1px, transparent 1px)`,
|
|
35
|
+
borderRadius: `var(--radius-${radius})`,
|
|
36
|
+
backgroundSize: `${dotSize}px ${dotSize}px`,
|
|
37
|
+
backgroundPosition: "center",
|
|
38
|
+
backgroundColor,
|
|
39
|
+
width,
|
|
40
|
+
...(height && { height }),
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Card size="1" variant="soft">
|
|
45
|
+
<Flex justify="center" align="center" py="4" style={dotsStyle}>
|
|
46
|
+
<Theme fontFamily="sans">{children}</Theme>
|
|
47
|
+
</Flex>
|
|
48
|
+
</Card>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Render with custom image background
|
|
53
|
+
const imageStyle: React.CSSProperties = {
|
|
54
|
+
backgroundImage: `url(${background})`,
|
|
55
|
+
backgroundSize: "cover",
|
|
56
|
+
backgroundPosition: "center",
|
|
57
|
+
backgroundRepeat: "no-repeat",
|
|
58
|
+
borderRadius: `var(--radius-${radius})`,
|
|
59
|
+
width,
|
|
60
|
+
...(height && { height }),
|
|
61
|
+
};
|
|
33
62
|
|
|
34
63
|
return (
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
width,
|
|
41
|
-
borderRadius: radius,
|
|
42
|
-
display: "flex",
|
|
43
|
-
alignItems: "center",
|
|
44
|
-
justifyContent: "center",
|
|
45
|
-
minHeight: "200px",
|
|
46
|
-
}}
|
|
47
|
-
>
|
|
48
|
-
{children}
|
|
49
|
-
</Box>
|
|
64
|
+
<Card size="1" variant="soft">
|
|
65
|
+
<Flex justify="center" align="center" py="4" style={imageStyle}>
|
|
66
|
+
<Theme fontFamily="sans">{children}</Theme>
|
|
67
|
+
</Flex>
|
|
68
|
+
</Card>
|
|
50
69
|
);
|
|
51
70
|
}
|
|
52
|
-
|