@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/LICENSE.md +21 -0
- package/README.md +79 -0
- package/dist/index.d.mts +45 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +631 -0
- package/dist/index.mjs +606 -0
- package/dist/preset.d.mts +5 -0
- package/dist/preset.d.ts +5 -0
- package/dist/preset.js +219 -0
- package/dist/preset.mjs +186 -0
- package/package.json +89 -0
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
|
+
};
|