@toriistudio/v0-playground 0.1.0

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/dist/index.mjs ADDED
@@ -0,0 +1,606 @@
1
+ // src/context/ResizableLayout.tsx
2
+ import {
3
+ createContext,
4
+ useContext,
5
+ useRef,
6
+ useState,
7
+ useEffect
8
+ } from "react";
9
+ import { GripVertical } from "lucide-react";
10
+ import { jsx, jsxs } from "react/jsx-runtime";
11
+ var ResizableLayoutContext = createContext(
12
+ null
13
+ );
14
+ var useResizableLayout = () => {
15
+ const ctx = useContext(ResizableLayoutContext);
16
+ if (!ctx) throw new Error("ResizableLayoutContext not found");
17
+ return ctx;
18
+ };
19
+ var ResizableLayout = ({ children }) => {
20
+ const [leftPanelWidth, setLeftPanelWidth] = useState(25);
21
+ const [isDesktop, setIsDesktop] = useState(false);
22
+ const [isHydrated, setIsHydrated] = useState(false);
23
+ const [isDragging, setIsDragging] = useState(false);
24
+ const [sidebarNarrow, setSidebarNarrow] = useState(false);
25
+ const containerRef = useRef(null);
26
+ useEffect(() => {
27
+ setIsHydrated(true);
28
+ const handleResize = () => setIsDesktop(window.innerWidth >= 768);
29
+ handleResize();
30
+ window.addEventListener("resize", handleResize);
31
+ return () => window.removeEventListener("resize", handleResize);
32
+ }, []);
33
+ useEffect(() => {
34
+ if (!isHydrated || !isDesktop) return;
35
+ const checkSidebarWidth = () => {
36
+ if (containerRef.current) {
37
+ const containerWidth = containerRef.current.clientWidth;
38
+ const sidebarWidth = leftPanelWidth / 100 * containerWidth;
39
+ setSidebarNarrow(sidebarWidth < 350);
40
+ }
41
+ };
42
+ checkSidebarWidth();
43
+ window.addEventListener("resize", checkSidebarWidth);
44
+ return () => window.removeEventListener("resize", checkSidebarWidth);
45
+ }, [leftPanelWidth, isHydrated, isDesktop]);
46
+ useEffect(() => {
47
+ const handleMouseMove = (e) => {
48
+ if (isDragging && containerRef.current) {
49
+ const containerRect = containerRef.current.getBoundingClientRect();
50
+ const newLeftWidth = (e.clientX - containerRect.left) / containerRect.width * 100;
51
+ if (newLeftWidth >= 20 && newLeftWidth <= 80) {
52
+ setLeftPanelWidth(newLeftWidth);
53
+ }
54
+ }
55
+ };
56
+ const handleMouseUp = () => setIsDragging(false);
57
+ if (isDragging) {
58
+ document.addEventListener("mousemove", handleMouseMove);
59
+ document.addEventListener("mouseup", handleMouseUp);
60
+ }
61
+ return () => {
62
+ document.removeEventListener("mousemove", handleMouseMove);
63
+ document.removeEventListener("mouseup", handleMouseUp);
64
+ };
65
+ }, [isDragging]);
66
+ return /* @__PURE__ */ jsx(
67
+ ResizableLayoutContext.Provider,
68
+ {
69
+ value: {
70
+ leftPanelWidth,
71
+ isHydrated,
72
+ isDesktop,
73
+ sidebarNarrow,
74
+ containerRef
75
+ },
76
+ children: /* @__PURE__ */ jsx("div", { className: "min-h-screen w-full bg-black text-white", children: /* @__PURE__ */ jsxs(
77
+ "div",
78
+ {
79
+ ref: containerRef,
80
+ className: "flex flex-col md:flex-row min-h-screen w-full overflow-hidden select-none",
81
+ children: [
82
+ children,
83
+ isHydrated && isDesktop && /* @__PURE__ */ jsx(
84
+ "div",
85
+ {
86
+ className: "order-3 w-2 bg-stone-800 hover:bg-stone-700 cursor-col-resize items-center justify-center z-10 transition-opacity duration-300",
87
+ onMouseDown: () => setIsDragging(true),
88
+ style: {
89
+ position: "absolute",
90
+ left: `${leftPanelWidth}%`,
91
+ top: 0,
92
+ bottom: 0,
93
+ display: "flex"
94
+ },
95
+ children: /* @__PURE__ */ jsx(GripVertical, { className: "h-6 w-6 text-stone-500" })
96
+ }
97
+ )
98
+ ]
99
+ }
100
+ ) })
101
+ }
102
+ );
103
+ };
104
+
105
+ // src/context/ControlsContext.tsx
106
+ import {
107
+ createContext as createContext2,
108
+ useContext as useContext2,
109
+ useState as useState2,
110
+ useMemo,
111
+ useEffect as useEffect2,
112
+ useCallback
113
+ } from "react";
114
+ import { jsx as jsx2 } from "react/jsx-runtime";
115
+ var ControlsContext = createContext2(null);
116
+ var useControlsContext = () => {
117
+ const ctx = useContext2(ControlsContext);
118
+ if (!ctx) throw new Error("useControls must be used within ControlsProvider");
119
+ return ctx;
120
+ };
121
+ var ControlsProvider = ({ children }) => {
122
+ const [schema, setSchema] = useState2({});
123
+ const [values, setValues] = useState2({});
124
+ const [componentName, setComponentName] = useState2();
125
+ const setValue = (key, value) => {
126
+ setValues((prev) => ({ ...prev, [key]: value }));
127
+ };
128
+ const registerSchema = (newSchema, opts) => {
129
+ if (opts?.componentName) {
130
+ setComponentName(opts.componentName);
131
+ }
132
+ setSchema((prevSchema) => ({ ...prevSchema, ...newSchema }));
133
+ setValues((prevValues) => {
134
+ const updated = { ...prevValues };
135
+ for (const key in newSchema) {
136
+ if (!(key in updated)) {
137
+ const control = newSchema[key];
138
+ if ("value" in control) {
139
+ updated[key] = control.value;
140
+ }
141
+ }
142
+ }
143
+ return updated;
144
+ });
145
+ };
146
+ const contextValue = useMemo(
147
+ () => ({ schema, values, setValue, registerSchema, componentName }),
148
+ [schema, values, componentName]
149
+ );
150
+ return /* @__PURE__ */ jsx2(ControlsContext.Provider, { value: contextValue, children });
151
+ };
152
+ var useControls = (schema, options) => {
153
+ const ctx = useContext2(ControlsContext);
154
+ if (!ctx) throw new Error("useControls must be used within ControlsProvider");
155
+ useEffect2(() => {
156
+ ctx.registerSchema(schema, options);
157
+ }, [JSON.stringify(schema), JSON.stringify(options)]);
158
+ useEffect2(() => {
159
+ for (const key in schema) {
160
+ if (!(key in ctx.values) && "value" in schema[key]) {
161
+ ctx.setValue(key, schema[key].value);
162
+ }
163
+ }
164
+ }, [JSON.stringify(schema), JSON.stringify(ctx.values)]);
165
+ const typedValues = ctx.values;
166
+ const jsx11 = useCallback(() => {
167
+ if (!options?.componentName) return "";
168
+ const props = Object.entries(typedValues).map(([key, val]) => {
169
+ if (typeof val === "string") return `${key}="${val}"`;
170
+ if (typeof val === "boolean") return `${key}={${val}}`;
171
+ return `${key}={${JSON.stringify(val)}}`;
172
+ }).join(" ");
173
+ return `<${options.componentName} ${props} />`;
174
+ }, [options?.componentName, JSON.stringify(typedValues)]);
175
+ return {
176
+ ...typedValues,
177
+ controls: ctx.values,
178
+ schema: ctx.schema,
179
+ setValue: ctx.setValue,
180
+ jsx: jsx11
181
+ };
182
+ };
183
+
184
+ // src/components/PreviewContainer/PreviewContainer.tsx
185
+ import { useRef as useRef2 } from "react";
186
+ import { jsx as jsx3 } from "react/jsx-runtime";
187
+ var PreviewContainer = ({ children }) => {
188
+ const { leftPanelWidth, isDesktop, isHydrated, containerRef } = useResizableLayout();
189
+ const previewRef = useRef2(null);
190
+ return /* @__PURE__ */ jsx3(
191
+ "div",
192
+ {
193
+ ref: previewRef,
194
+ className: "order-1 md:order-2 flex-1 bg-black overflow-auto flex items-center justify-center relative",
195
+ style: isHydrated && isDesktop ? {
196
+ width: `${100 - leftPanelWidth}%`,
197
+ marginLeft: `${leftPanelWidth}%`
198
+ } : {},
199
+ children
200
+ }
201
+ );
202
+ };
203
+ var PreviewContainer_default = PreviewContainer;
204
+
205
+ // src/components/ControlPanel/ControlPanel.tsx
206
+ import { useState as useState3, useMemo as useMemo2 } from "react";
207
+ import { Check as Check2, Copy } from "lucide-react";
208
+
209
+ // src/components/ui/switch.tsx
210
+ import * as React4 from "react";
211
+ import * as SwitchPrimitives from "@radix-ui/react-switch";
212
+
213
+ // src/lib/utils.ts
214
+ import { clsx } from "clsx";
215
+ import { twMerge } from "tailwind-merge";
216
+ function cn(...inputs) {
217
+ return twMerge(clsx(inputs));
218
+ }
219
+
220
+ // src/components/ui/switch.tsx
221
+ import { jsx as jsx4 } from "react/jsx-runtime";
222
+ var Switch = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx4(
223
+ SwitchPrimitives.Root,
224
+ {
225
+ className: cn(
226
+ "peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
227
+ className
228
+ ),
229
+ ...props,
230
+ ref,
231
+ children: /* @__PURE__ */ jsx4(
232
+ SwitchPrimitives.Thumb,
233
+ {
234
+ className: cn(
235
+ "pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0"
236
+ )
237
+ }
238
+ )
239
+ }
240
+ ));
241
+ Switch.displayName = SwitchPrimitives.Root.displayName;
242
+
243
+ // src/components/ui/label.tsx
244
+ import * as React5 from "react";
245
+ import * as LabelPrimitive from "@radix-ui/react-label";
246
+ import { cva } from "class-variance-authority";
247
+ import { jsx as jsx5 } from "react/jsx-runtime";
248
+ var labelVariants = cva(
249
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
250
+ );
251
+ var Label = React5.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx5(
252
+ LabelPrimitive.Root,
253
+ {
254
+ ref,
255
+ className: cn(labelVariants(), className),
256
+ ...props
257
+ }
258
+ ));
259
+ Label.displayName = LabelPrimitive.Root.displayName;
260
+
261
+ // src/components/ui/slider.tsx
262
+ import * as React6 from "react";
263
+ import * as SliderPrimitive from "@radix-ui/react-slider";
264
+ import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
265
+ var Slider = React6.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxs2(
266
+ SliderPrimitive.Root,
267
+ {
268
+ ref,
269
+ className: cn(
270
+ "relative flex w-full touch-none select-none items-center",
271
+ className
272
+ ),
273
+ ...props,
274
+ children: [
275
+ /* @__PURE__ */ jsx6(SliderPrimitive.Track, { className: "relative h-2 w-full grow overflow-hidden rounded-full bg-secondary", children: /* @__PURE__ */ jsx6(SliderPrimitive.Range, { className: "absolute h-full bg-primary" }) }),
276
+ /* @__PURE__ */ jsx6(SliderPrimitive.Thumb, { className: "block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" })
277
+ ]
278
+ }
279
+ ));
280
+ Slider.displayName = SliderPrimitive.Root.displayName;
281
+
282
+ // src/components/ui/input.tsx
283
+ import * as React7 from "react";
284
+ import { jsx as jsx7 } from "react/jsx-runtime";
285
+ var Input = React7.forwardRef(
286
+ ({ className, type, ...props }, ref) => {
287
+ return /* @__PURE__ */ jsx7(
288
+ "input",
289
+ {
290
+ type,
291
+ className: cn(
292
+ "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
293
+ className
294
+ ),
295
+ ref,
296
+ ...props
297
+ }
298
+ );
299
+ }
300
+ );
301
+ Input.displayName = "Input";
302
+
303
+ // src/components/ui/select.tsx
304
+ import * as React8 from "react";
305
+ import * as SelectPrimitive from "@radix-ui/react-select";
306
+ import { Check, ChevronDown, ChevronUp } from "lucide-react";
307
+ import { jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
308
+ var Select = SelectPrimitive.Root;
309
+ var SelectValue = SelectPrimitive.Value;
310
+ var SelectTrigger = React8.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs3(
311
+ SelectPrimitive.Trigger,
312
+ {
313
+ ref,
314
+ className: cn(
315
+ "flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
316
+ className
317
+ ),
318
+ ...props,
319
+ children: [
320
+ children,
321
+ /* @__PURE__ */ jsx8(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx8(ChevronDown, { className: "h-4 w-4 opacity-50" }) })
322
+ ]
323
+ }
324
+ ));
325
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
326
+ var SelectScrollUpButton = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx8(
327
+ SelectPrimitive.ScrollUpButton,
328
+ {
329
+ ref,
330
+ className: cn(
331
+ "flex cursor-default items-center justify-center py-1",
332
+ className
333
+ ),
334
+ ...props,
335
+ children: /* @__PURE__ */ jsx8(ChevronUp, { className: "h-4 w-4" })
336
+ }
337
+ ));
338
+ SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
339
+ var SelectScrollDownButton = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx8(
340
+ SelectPrimitive.ScrollDownButton,
341
+ {
342
+ ref,
343
+ className: cn(
344
+ "flex cursor-default items-center justify-center py-1",
345
+ className
346
+ ),
347
+ ...props,
348
+ children: /* @__PURE__ */ jsx8(ChevronDown, { className: "h-4 w-4" })
349
+ }
350
+ ));
351
+ SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
352
+ var SelectContent = React8.forwardRef(({ className, children, position = "popper", ...props }, ref) => /* @__PURE__ */ jsx8(SelectPrimitive.Portal, { children: /* @__PURE__ */ jsxs3(
353
+ SelectPrimitive.Content,
354
+ {
355
+ ref,
356
+ className: cn(
357
+ "relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-select-content-transform-origin]",
358
+ position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
359
+ className
360
+ ),
361
+ position,
362
+ ...props,
363
+ children: [
364
+ /* @__PURE__ */ jsx8(SelectScrollUpButton, {}),
365
+ /* @__PURE__ */ jsx8(
366
+ SelectPrimitive.Viewport,
367
+ {
368
+ className: cn(
369
+ "p-1",
370
+ position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
371
+ ),
372
+ children
373
+ }
374
+ ),
375
+ /* @__PURE__ */ jsx8(SelectScrollDownButton, {})
376
+ ]
377
+ }
378
+ ) }));
379
+ SelectContent.displayName = SelectPrimitive.Content.displayName;
380
+ var SelectLabel = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx8(
381
+ SelectPrimitive.Label,
382
+ {
383
+ ref,
384
+ className: cn("px-2 py-1.5 text-sm font-semibold", className),
385
+ ...props
386
+ }
387
+ ));
388
+ SelectLabel.displayName = SelectPrimitive.Label.displayName;
389
+ var SelectItem = React8.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs3(
390
+ SelectPrimitive.Item,
391
+ {
392
+ ref,
393
+ className: cn(
394
+ "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
395
+ className
396
+ ),
397
+ ...props,
398
+ children: [
399
+ /* @__PURE__ */ jsx8("span", { className: "absolute right-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ jsx8(SelectPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx8(Check, { className: "h-4 w-4" }) }) }),
400
+ /* @__PURE__ */ jsx8(SelectPrimitive.ItemText, { children })
401
+ ]
402
+ }
403
+ ));
404
+ SelectItem.displayName = SelectPrimitive.Item.displayName;
405
+ var SelectSeparator = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx8(
406
+ SelectPrimitive.Separator,
407
+ {
408
+ ref,
409
+ className: cn("-mx-1 my-1 h-px bg-muted", className),
410
+ ...props
411
+ }
412
+ ));
413
+ SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
414
+
415
+ // src/components/ControlPanel/ControlPanel.tsx
416
+ import { Fragment, jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
417
+ var ControlPanel = () => {
418
+ const [copied, setCopied] = useState3(false);
419
+ const { leftPanelWidth, isDesktop, isHydrated, sidebarNarrow } = useResizableLayout();
420
+ const { schema, setValue, values, componentName } = useControlsContext();
421
+ const normalControls = Object.entries(schema).filter(
422
+ ([, control]) => control.type !== "button"
423
+ );
424
+ const buttonControls = Object.entries(schema).filter(
425
+ ([, control]) => control.type === "button"
426
+ );
427
+ const jsx11 = useMemo2(() => {
428
+ if (!componentName) return "";
429
+ const props = Object.entries(values).map(([key, val]) => {
430
+ if (typeof val === "string") return `${key}="${val}"`;
431
+ if (typeof val === "boolean") return `${key}={${val}}`;
432
+ return `${key}={${JSON.stringify(val)}}`;
433
+ }).join(" ");
434
+ return `<${componentName} ${props} />`;
435
+ }, [componentName, values]);
436
+ return /* @__PURE__ */ jsx9(
437
+ "div",
438
+ {
439
+ className: `order-2 md:order-1 w-full md:h-auto p-2 md:p-4 bg-stone-900 font-mono text-stone-300 transition-opacity duration-300 ${!isHydrated ? "opacity-0" : "opacity-100"}`,
440
+ style: {
441
+ width: "100%",
442
+ height: "auto",
443
+ flex: "0 0 auto",
444
+ ...isHydrated && isDesktop ? {
445
+ position: "absolute",
446
+ left: 0,
447
+ top: 0,
448
+ bottom: 0,
449
+ width: `${leftPanelWidth}%`,
450
+ overflowY: "auto"
451
+ } : {}
452
+ },
453
+ children: /* @__PURE__ */ jsxs4("div", { className: "space-y-4 p-2 md:p-4 border border-stone-700 rounded-md", children: [
454
+ /* @__PURE__ */ jsx9("div", { className: "space-y-1", children: /* @__PURE__ */ jsx9("h1", { className: "text-lg text-stone-100 font-bold", children: "Controls" }) }),
455
+ /* @__PURE__ */ jsxs4("div", { className: "space-y-4 pt-2", children: [
456
+ normalControls.map(([key, control]) => {
457
+ const value = values[key];
458
+ switch (control.type) {
459
+ case "boolean":
460
+ return /* @__PURE__ */ jsxs4(
461
+ "div",
462
+ {
463
+ className: "flex items-center space-x-4 border-t border-stone-700 pt-4",
464
+ children: [
465
+ /* @__PURE__ */ jsx9(
466
+ Switch,
467
+ {
468
+ id: key,
469
+ checked: value,
470
+ onCheckedChange: (v) => setValue(key, v),
471
+ className: "data-[state=checked]:bg-stone-700 data-[state=unchecked]:bg-stone-700/40"
472
+ }
473
+ ),
474
+ /* @__PURE__ */ jsx9(Label, { htmlFor: key, className: "cursor-pointer", children: key })
475
+ ]
476
+ },
477
+ key
478
+ );
479
+ case "number":
480
+ return /* @__PURE__ */ jsxs4("div", { className: "space-y-2 w-full", children: [
481
+ /* @__PURE__ */ jsx9("div", { className: "flex items-center justify-between pb-1", children: /* @__PURE__ */ jsxs4(Label, { className: "text-stone-300", htmlFor: key, children: [
482
+ key,
483
+ ": ",
484
+ value
485
+ ] }) }),
486
+ /* @__PURE__ */ jsx9(
487
+ Slider,
488
+ {
489
+ id: key,
490
+ min: control.min ?? 0,
491
+ max: control.max ?? 100,
492
+ step: control.step ?? 1,
493
+ value: [value],
494
+ onValueChange: ([v]) => setValue(key, v),
495
+ className: "[&>span]:border-none [&_.bg-primary]:bg-stone-800 [&>.bg-background]:bg-stone-500/30"
496
+ }
497
+ )
498
+ ] }, key);
499
+ case "string":
500
+ return /* @__PURE__ */ jsx9(
501
+ Input,
502
+ {
503
+ id: key,
504
+ value,
505
+ className: "bg-stone-900",
506
+ placeholder: key,
507
+ onChange: (e) => setValue(key, e.target.value)
508
+ },
509
+ key
510
+ );
511
+ case "color":
512
+ return /* @__PURE__ */ jsxs4("div", { className: "space-y-2 w-full", children: [
513
+ /* @__PURE__ */ jsx9("div", { className: "flex items-center justify-between pb-1", children: /* @__PURE__ */ jsx9(Label, { className: "text-stone-300", htmlFor: key, children: key }) }),
514
+ /* @__PURE__ */ jsx9(
515
+ "input",
516
+ {
517
+ type: "color",
518
+ id: key,
519
+ value,
520
+ onChange: (e) => setValue(key, e.target.value),
521
+ className: "w-full h-10 rounded border border-stone-600 bg-transparent"
522
+ }
523
+ )
524
+ ] }, key);
525
+ case "select":
526
+ return /* @__PURE__ */ jsxs4(
527
+ "div",
528
+ {
529
+ className: "space-y-2 border-t border-stone-700 pt-4",
530
+ children: [
531
+ /* @__PURE__ */ jsx9(Label, { className: "text-stone-300", htmlFor: key, children: key }),
532
+ /* @__PURE__ */ jsxs4(
533
+ Select,
534
+ {
535
+ value,
536
+ onValueChange: (val) => setValue(key, val),
537
+ children: [
538
+ /* @__PURE__ */ jsx9(SelectTrigger, { children: /* @__PURE__ */ jsx9(SelectValue, { placeholder: "Select option" }) }),
539
+ /* @__PURE__ */ jsx9(SelectContent, { children: control.options.map((opt) => /* @__PURE__ */ jsx9(SelectItem, { value: opt, children: opt }, opt)) })
540
+ ]
541
+ }
542
+ )
543
+ ]
544
+ },
545
+ key
546
+ );
547
+ default:
548
+ return null;
549
+ }
550
+ }),
551
+ (buttonControls.length > 0 || jsx11) && /* @__PURE__ */ jsxs4("div", { className: "border-t border-stone-700", children: [
552
+ jsx11 && /* @__PURE__ */ jsx9("div", { className: "flex-1 pt-4", children: /* @__PURE__ */ jsx9(
553
+ "button",
554
+ {
555
+ onClick: () => {
556
+ navigator.clipboard.writeText(jsx11);
557
+ setCopied(true);
558
+ setTimeout(() => setCopied(false), 5e3);
559
+ },
560
+ className: "w-full px-4 py-2 text-sm bg-stone-700 hover:bg-stone-600 text-white rounded flex items-center justify-center gap-2",
561
+ children: copied ? /* @__PURE__ */ jsxs4(Fragment, { children: [
562
+ /* @__PURE__ */ jsx9(Check2, { className: "w-4 h-4" }),
563
+ "Copied"
564
+ ] }) : /* @__PURE__ */ jsxs4(Fragment, { children: [
565
+ /* @__PURE__ */ jsx9(Copy, { className: "w-4 h-4" }),
566
+ "Copy to Clipboard"
567
+ ] })
568
+ }
569
+ ) }, "control-panel-jsx"),
570
+ buttonControls.length > 0 && /* @__PURE__ */ jsx9("div", { className: "flex flex-wrap gap-2 pt-4", children: buttonControls.map(
571
+ ([key, control]) => control.type === "button" ? /* @__PURE__ */ jsx9(
572
+ "div",
573
+ {
574
+ className: "flex-1",
575
+ children: control.render ? control.render() : /* @__PURE__ */ jsx9(
576
+ "button",
577
+ {
578
+ onClick: control.onClick,
579
+ className: "w-full px-4 py-2 text-sm bg-stone-700 hover:bg-stone-600 text-white rounded",
580
+ children: control.label ?? key
581
+ }
582
+ )
583
+ },
584
+ `control-panel-custom-${key}`
585
+ ) : null
586
+ ) })
587
+ ] })
588
+ ] })
589
+ ] })
590
+ }
591
+ );
592
+ };
593
+ var ControlPanel_default = ControlPanel;
594
+
595
+ // src/components/Playground/Playground.tsx
596
+ import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
597
+ function Playground({ children }) {
598
+ return /* @__PURE__ */ jsx10(ResizableLayout, { children: /* @__PURE__ */ jsxs5(ControlsProvider, { children: [
599
+ /* @__PURE__ */ jsx10(PreviewContainer_default, { children }),
600
+ /* @__PURE__ */ jsx10(ControlPanel_default, {})
601
+ ] }) });
602
+ }
603
+ export {
604
+ Playground,
605
+ useControls
606
+ };
@@ -0,0 +1,5 @@
1
+ import { Config } from 'tailwindcss';
2
+
3
+ declare const preset: Partial<Config>;
4
+
5
+ export { preset as default };
@@ -0,0 +1,5 @@
1
+ import { Config } from 'tailwindcss';
2
+
3
+ declare const preset: Partial<Config>;
4
+
5
+ export { preset as default };