UNPKG

@toriistudio/v0-playground

Version:
957 lines (931 loc) 40.1 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { Button: () => Button, CameraLogger: () => CameraLogger, Canvas: () => Canvas_default, ControlsProvider: () => ControlsProvider, Playground: () => Playground, PlaygroundCanvas: () => PlaygroundCanvas_default, useControls: () => useControls, useUrlSyncedControls: () => useUrlSyncedControls }); module.exports = __toCommonJS(src_exports); // src/components/Playground/Playground.tsx var import_react6 = require("react"); var import_lucide_react4 = require("lucide-react"); // src/context/ResizableLayout.tsx var import_react = require("react"); var import_lucide_react = require("lucide-react"); var import_jsx_runtime = require("react/jsx-runtime"); var ResizableLayoutContext = (0, import_react.createContext)( null ); var useResizableLayout = () => { const ctx = (0, import_react.useContext)(ResizableLayoutContext); if (!ctx) throw new Error("ResizableLayoutContext not found"); return ctx; }; var ResizableLayout = ({ children, hideControls }) => { const [leftPanelWidth, setLeftPanelWidth] = (0, import_react.useState)(25); const [isDesktop, setIsDesktop] = (0, import_react.useState)(false); const [isHydrated, setIsHydrated] = (0, import_react.useState)(false); const [isDragging, setIsDragging] = (0, import_react.useState)(false); const [sidebarNarrow, setSidebarNarrow] = (0, import_react.useState)(false); const containerRef = (0, import_react.useRef)(null); (0, import_react.useEffect)(() => { setIsHydrated(true); const handleResize = () => setIsDesktop(window.innerWidth >= 768); handleResize(); window.addEventListener("resize", handleResize); return () => window.removeEventListener("resize", handleResize); }, []); (0, import_react.useEffect)(() => { if (!isHydrated || !isDesktop) return; const checkSidebarWidth = () => { if (containerRef.current) { const containerWidth = containerRef.current.clientWidth; const sidebarWidth = leftPanelWidth / 100 * containerWidth; setSidebarNarrow(sidebarWidth < 350); } }; checkSidebarWidth(); window.addEventListener("resize", checkSidebarWidth); return () => window.removeEventListener("resize", checkSidebarWidth); }, [leftPanelWidth, isHydrated, isDesktop]); (0, import_react.useEffect)(() => { const handleMouseMove = (e) => { if (isDragging && containerRef.current) { const containerRect = containerRef.current.getBoundingClientRect(); const newLeftWidth = (e.clientX - containerRect.left) / containerRect.width * 100; if (newLeftWidth >= 20 && newLeftWidth <= 80) { setLeftPanelWidth(newLeftWidth); } } }; const handleMouseUp = () => setIsDragging(false); if (isDragging) { document.addEventListener("mousemove", handleMouseMove); document.addEventListener("mouseup", handleMouseUp); } return () => { document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseup", handleMouseUp); }; }, [isDragging]); return /* @__PURE__ */ (0, import_jsx_runtime.jsx)( ResizableLayoutContext.Provider, { value: { leftPanelWidth, isHydrated, isDesktop, sidebarNarrow, containerRef }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "min-h-screen w-full bg-black text-white", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)( "div", { ref: containerRef, className: "flex flex-col md:flex-row min-h-screen w-full overflow-hidden select-none", children: [ children, isHydrated && isDesktop && !hideControls && /* @__PURE__ */ (0, import_jsx_runtime.jsx)( "div", { 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", onMouseDown: () => setIsDragging(true), style: { position: "absolute", left: `${leftPanelWidth}%`, top: 0, bottom: 0, display: "flex" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.GripVertical, { className: "h-6 w-6 text-stone-500" }) } ) ] } ) }) } ); }; // src/context/ControlsContext.tsx var import_react2 = require("react"); // src/utils/getUrlParams.ts var getUrlParams = () => { if (typeof window === "undefined") return {}; const params = new URLSearchParams(window.location.search); const entries = {}; for (const [key, value] of params.entries()) { entries[key] = value; } return entries; }; // src/context/ControlsContext.tsx var import_jsx_runtime2 = require("react/jsx-runtime"); var ControlsContext = (0, import_react2.createContext)(null); var useControlsContext = () => { const ctx = (0, import_react2.useContext)(ControlsContext); if (!ctx) throw new Error("useControls must be used within ControlsProvider"); return ctx; }; var ControlsProvider = ({ children }) => { const [schema, setSchema] = (0, import_react2.useState)({}); const [values, setValues] = (0, import_react2.useState)({}); const [config, setConfig] = (0, import_react2.useState)({ showCopyButton: true }); const [componentName, setComponentName] = (0, import_react2.useState)(); const setValue = (key, value) => { setValues((prev) => ({ ...prev, [key]: value })); }; const registerSchema = (newSchema, opts) => { if (opts?.componentName) { setComponentName(opts.componentName); } if (opts?.config) { setConfig((prev) => ({ ...prev, ...opts.config })); } setSchema((prevSchema) => ({ ...prevSchema, ...newSchema })); setValues((prevValues) => { const updated = { ...prevValues }; for (const key in newSchema) { const control = newSchema[key]; if (!(key in updated)) { if ("value" in control) { updated[key] = control.value; } } } return updated; }); }; const contextValue = (0, import_react2.useMemo)( () => ({ schema, values, setValue, registerSchema, componentName, config }), [schema, values, componentName, config] ); return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ControlsContext.Provider, { value: contextValue, children }); }; var useControls = (schema, options) => { const ctx = (0, import_react2.useContext)(ControlsContext); if (!ctx) throw new Error("useControls must be used within ControlsProvider"); const urlParams = getUrlParams(); const mergedSchema = Object.fromEntries( Object.entries(schema).map(([key, control]) => { const urlValue = urlParams[key]; if (!urlValue || !("value" in control)) return [key, control]; const defaultValue = control.value; let parsed = urlValue; if (typeof defaultValue === "number") { parsed = parseFloat(urlValue); if (isNaN(parsed)) parsed = defaultValue; } else if (typeof defaultValue === "boolean") { parsed = urlValue === "true"; } return [ key, { ...control, value: parsed } ]; }) ); (0, import_react2.useEffect)(() => { ctx.registerSchema(mergedSchema, options); }, [JSON.stringify(mergedSchema), JSON.stringify(options)]); (0, import_react2.useEffect)(() => { for (const key in mergedSchema) { if (!(key in ctx.values) && "value" in mergedSchema[key]) { ctx.setValue(key, mergedSchema[key].value); } } }, [JSON.stringify(mergedSchema), JSON.stringify(ctx.values)]); const typedValues = ctx.values; const jsx16 = (0, import_react2.useCallback)(() => { if (!options?.componentName) return ""; const props = Object.entries(typedValues).map(([key, val]) => { if (typeof val === "string") return `${key}="${val}"`; if (typeof val === "boolean") return `${key}={${val}}`; return `${key}={${JSON.stringify(val)}}`; }).join(" "); return `<${options.componentName} ${props} />`; }, [options?.componentName, JSON.stringify(typedValues)]); return { ...typedValues, controls: ctx.values, schema: ctx.schema, setValue: ctx.setValue, jsx: jsx16 }; }; var useUrlSyncedControls = useControls; // src/components/ControlPanel/ControlPanel.tsx var import_react4 = require("react"); var import_lucide_react3 = require("lucide-react"); // src/hooks/usePreviewUrl.ts var import_react3 = require("react"); var usePreviewUrl = (values, basePath = "") => { const [url, setUrl] = (0, import_react3.useState)(""); (0, import_react3.useEffect)(() => { if (typeof window === "undefined") return; const params = new URLSearchParams(); params.set("nocontrols", "true"); for (const [key, value] of Object.entries(values)) { if (value !== void 0 && value !== null) { params.set(key, value.toString()); } } const fullUrl = `${basePath || window.location.pathname}?${params.toString()}`; setUrl(fullUrl); }, [values, basePath]); return url; }; // src/components/ui/switch.tsx var React3 = __toESM(require("react")); var SwitchPrimitives = __toESM(require("@radix-ui/react-switch")); // src/lib/utils.ts var import_clsx = require("clsx"); var import_tailwind_merge = require("tailwind-merge"); function cn(...inputs) { return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs)); } // src/components/ui/switch.tsx var import_jsx_runtime3 = require("react/jsx-runtime"); var Switch = React3.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( SwitchPrimitives.Root, { className: cn( "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", className ), ...props, ref, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( SwitchPrimitives.Thumb, { className: cn( "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" ) } ) } )); Switch.displayName = SwitchPrimitives.Root.displayName; // src/components/ui/label.tsx var React4 = __toESM(require("react")); var LabelPrimitive = __toESM(require("@radix-ui/react-label")); var import_class_variance_authority = require("class-variance-authority"); var import_jsx_runtime4 = require("react/jsx-runtime"); var labelVariants = (0, import_class_variance_authority.cva)( "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" ); var Label = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( LabelPrimitive.Root, { ref, className: cn(labelVariants(), className), ...props } )); Label.displayName = LabelPrimitive.Root.displayName; // src/components/ui/slider.tsx var React5 = __toESM(require("react")); var SliderPrimitive = __toESM(require("@radix-ui/react-slider")); var import_jsx_runtime5 = require("react/jsx-runtime"); var Slider = React5.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)( SliderPrimitive.Root, { ref, className: cn( "relative flex w-full touch-none select-none items-center", className ), ...props, children: [ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SliderPrimitive.Track, { className: "relative h-2 w-full grow overflow-hidden rounded-full bg-secondary", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SliderPrimitive.Range, { className: "absolute h-full bg-primary" }) }), /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(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" }) ] } )); Slider.displayName = SliderPrimitive.Root.displayName; // src/components/ui/input.tsx var React6 = __toESM(require("react")); var import_jsx_runtime6 = require("react/jsx-runtime"); var Input = React6.forwardRef( ({ className, type, ...props }, ref) => { return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)( "input", { type, className: cn( "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", className ), ref, ...props } ); } ); Input.displayName = "Input"; // src/components/ui/select.tsx var React7 = __toESM(require("react")); var SelectPrimitive = __toESM(require("@radix-ui/react-select")); var import_lucide_react2 = require("lucide-react"); var import_jsx_runtime7 = require("react/jsx-runtime"); var Select = SelectPrimitive.Root; var SelectValue = SelectPrimitive.Value; var SelectTrigger = React7.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)( SelectPrimitive.Trigger, { ref, className: cn( "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", className ), ...props, children: [ children, /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react2.ChevronDown, { className: "h-4 w-4 opacity-50" }) }) ] } )); SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; var SelectScrollUpButton = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)( SelectPrimitive.ScrollUpButton, { ref, className: cn( "flex cursor-default items-center justify-center py-1", className ), ...props, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react2.ChevronUp, { className: "h-4 w-4" }) } )); SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName; var SelectScrollDownButton = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)( SelectPrimitive.ScrollDownButton, { ref, className: cn( "flex cursor-default items-center justify-center py-1", className ), ...props, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react2.ChevronDown, { className: "h-4 w-4" }) } )); SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName; var SelectContent = React7.forwardRef(({ className, children, position = "popper", ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SelectPrimitive.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)( SelectPrimitive.Content, { ref, className: cn( "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]", 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", className ), position, ...props, children: [ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SelectScrollUpButton, {}), /* @__PURE__ */ (0, import_jsx_runtime7.jsx)( SelectPrimitive.Viewport, { className: cn( "p-1", position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]" ), children } ), /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SelectScrollDownButton, {}) ] } ) })); SelectContent.displayName = SelectPrimitive.Content.displayName; var SelectLabel = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)( SelectPrimitive.Label, { ref, className: cn("px-2 py-1.5 text-sm font-semibold", className), ...props } )); SelectLabel.displayName = SelectPrimitive.Label.displayName; var SelectItem = React7.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)( SelectPrimitive.Item, { ref, className: cn( "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", className ), ...props, children: [ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "absolute right-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SelectPrimitive.ItemIndicator, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react2.Check, { className: "h-4 w-4" }) }) }), /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SelectPrimitive.ItemText, { children }) ] } )); SelectItem.displayName = SelectPrimitive.Item.displayName; var SelectSeparator = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)( SelectPrimitive.Separator, { ref, className: cn("-mx-1 my-1 h-px bg-muted", className), ...props } )); SelectSeparator.displayName = SelectPrimitive.Separator.displayName; // src/components/ui/button.tsx var React8 = __toESM(require("react")); var import_react_slot = require("@radix-ui/react-slot"); var import_class_variance_authority2 = require("class-variance-authority"); var import_jsx_runtime8 = require("react/jsx-runtime"); var buttonVariants = (0, import_class_variance_authority2.cva)( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", { variants: { variant: { default: "bg-primary text-primary-foreground shadow hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", ghost: "bg-gray-800 hover:bg-gray-900", link: "text-primary underline-offset-4 hover:underline" }, size: { default: "h-9 px-4 py-2", sm: "h-8 rounded-md px-3 text-xs", lg: "h-10 rounded-md px-8", icon: "h-9 w-9" } }, defaultVariants: { variant: "default", size: "default" } } ); var Button = React8.forwardRef( ({ className, variant, size, asChild = false, ...props }, ref) => { const Comp = asChild ? import_react_slot.Slot : "button"; return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)( Comp, { className: cn(buttonVariants({ variant, size, className })), ref, ...props } ); } ); Button.displayName = "Button"; // src/components/ControlPanel/ControlPanel.tsx var import_jsx_runtime9 = require("react/jsx-runtime"); var ControlPanel = () => { const [copied, setCopied] = (0, import_react4.useState)(false); const { leftPanelWidth, isDesktop, isHydrated } = useResizableLayout(); const { schema, setValue, values, componentName, config } = useControlsContext(); const previewUrl = usePreviewUrl(values); const normalControls = Object.entries(schema).filter( ([, control]) => control.type !== "button" && !control.hidden ); const buttonControls = Object.entries(schema).filter( ([, control]) => control.type === "button" && !control.hidden ); const jsx16 = (0, import_react4.useMemo)(() => { if (!componentName) return ""; const props = Object.entries(values).map(([key, val]) => { if (typeof val === "string") return `${key}="${val}"`; if (typeof val === "boolean") return `${key}={${val}}`; return `${key}={${JSON.stringify(val)}}`; }).join(" "); return `<${componentName} ${props} />`; }, [componentName, values]); return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)( "div", { 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"}`, onPointerDown: (e) => e.stopPropagation(), onTouchStart: (e) => e.stopPropagation(), style: { width: "100%", height: "auto", flex: "0 0 auto", ...isHydrated && isDesktop ? { position: "absolute", left: 0, top: 0, bottom: 0, width: `${leftPanelWidth}%`, overflowY: "auto" } : {} }, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "mb-10 space-y-4 p-2 md:p-4 border border-stone-700 rounded-md", children: [ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "space-y-1", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h1", { className: "text-lg text-stone-100 font-bold", children: config?.mainLabel ?? "Controls" }) }), /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "space-y-4 pt-2", children: [ normalControls.map(([key, control]) => { const value = values[key]; switch (control.type) { case "boolean": return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)( "div", { className: "flex items-center space-x-4 border-t border-stone-700 pt-4", children: [ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)( Switch, { id: key, checked: value, onCheckedChange: (v) => setValue(key, v), className: "data-[state=checked]:bg-stone-700 data-[state=unchecked]:bg-stone-700/40" } ), /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Label, { htmlFor: key, className: "cursor-pointer", children: key }) ] }, key ); case "number": return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "space-y-2 w-full", children: [ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "flex items-center justify-between pb-1", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Label, { className: "text-stone-300", htmlFor: key, children: [ key, ": ", value ] }) }), /* @__PURE__ */ (0, import_jsx_runtime9.jsx)( Slider, { id: key, min: control.min ?? 0, max: control.max ?? 100, step: control.step ?? 1, value: [value], onValueChange: ([v]) => setValue(key, v), className: "[&>span]:border-none [&_.bg-primary]:bg-stone-800 [&>.bg-background]:bg-stone-500/30" } ) ] }, key); case "string": return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)( Input, { id: key, value, className: "bg-stone-900", placeholder: key, onChange: (e) => setValue(key, e.target.value) }, key ); case "color": return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "space-y-2 w-full", children: [ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "flex items-center justify-between pb-1", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Label, { className: "text-stone-300", htmlFor: key, children: key }) }), /* @__PURE__ */ (0, import_jsx_runtime9.jsx)( "input", { type: "color", id: key, value, onChange: (e) => setValue(key, e.target.value), className: "w-full h-10 rounded border border-stone-600 bg-transparent" } ) ] }, key); case "select": return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)( "div", { className: "space-y-2 border-t border-stone-700 pt-4", children: [ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Label, { className: "text-stone-300", htmlFor: key, children: key }), /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)( Select, { value, onValueChange: (val) => setValue(key, val), children: [ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(SelectTrigger, { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(SelectValue, { placeholder: "Select option" }) }), /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(SelectContent, { children: Object.entries(control.options).map( ([label, _val]) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(SelectItem, { value: label, children: label }, label) ) }) ] } ) ] }, key ); default: return null; } }), (buttonControls.length > 0 || jsx16) && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)( "div", { className: `${normalControls.length > 0 ? "border-t" : ""} border-stone-700`, children: [ jsx16 && config?.showCopyButton !== false && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "flex-1 pt-4", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)( "button", { onClick: () => { navigator.clipboard.writeText(jsx16); setCopied(true); setTimeout(() => setCopied(false), 5e3); }, 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", children: copied ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react3.Check, { className: "w-4 h-4" }), "Copied" ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react3.Copy, { className: "w-4 h-4" }), "Copy to Clipboard" ] }) } ) }, "control-panel-jsx"), buttonControls.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "flex flex-wrap gap-2 pt-4", children: buttonControls.map( ([key, control]) => control.type === "button" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)( "div", { className: "flex-1", children: control.render ? control.render() : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)( "button", { onClick: control.onClick, className: "w-full px-4 py-2 text-sm bg-stone-700 hover:bg-stone-600 text-white rounded", children: control.label ?? key } ) }, `control-panel-custom-${key}` ) : null ) }) ] } ) ] }), previewUrl && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Button, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)( "a", { href: previewUrl, target: "_blank", rel: "noopener noreferrer", className: "w-full px-4 py-2 text-sm text-center bg-stone-800 hover:bg-stone-700 text-white rounded", children: [ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react3.SquareArrowOutUpRight, {}), " Open in a New Tab" ] } ) }) ] }) } ); }; var ControlPanel_default = ControlPanel; // src/components/PreviewContainer/PreviewContainer.tsx var import_react5 = require("react"); // src/components/Grid/Grid.tsx var import_jsx_runtime10 = require("react/jsx-runtime"); function Grid() { return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)( "div", { className: "absolute inset-0 w-screen h-screen z-[0] blur-[1px]", style: { backgroundImage: ` linear-gradient(to right,rgb(13, 13, 13) 1px, transparent 1px), linear-gradient(to bottom,rgb(13, 13, 13) 1px, transparent 1px) `, backgroundSize: "1rem 1rem", backgroundPosition: "center" } } ); } var Grid_default = Grid; // src/components/PreviewContainer/PreviewContainer.tsx var import_jsx_runtime11 = require("react/jsx-runtime"); var PreviewContainer = ({ children, hideControls }) => { const { config } = useControlsContext(); const { leftPanelWidth, isDesktop, isHydrated, containerRef } = useResizableLayout(); const previewRef = (0, import_react5.useRef)(null); return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)( "div", { ref: previewRef, className: "order-1 md:order-2 flex-1 bg-black overflow-auto flex items-center justify-center relative", style: isHydrated && isDesktop && !hideControls ? { width: `${100 - leftPanelWidth}%`, marginLeft: `${leftPanelWidth}%` } : {}, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "w-screen h-screen", children: [ config?.showGrid && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Grid_default, {}), /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "w-screen h-screen flex items-center justify-center relative", children }) ] }) } ); }; var PreviewContainer_default = PreviewContainer; // src/components/Playground/Playground.tsx var import_jsx_runtime12 = require("react/jsx-runtime"); var NO_CONTROLS_PARAM = "nocontrols"; function Playground({ children }) { const [isHydrated, setIsHydrated] = (0, import_react6.useState)(false); const [copied, setCopied] = (0, import_react6.useState)(false); (0, import_react6.useEffect)(() => { setIsHydrated(true); }, []); const hideControls = (0, import_react6.useMemo)(() => { if (typeof window === "undefined") return false; return new URLSearchParams(window.location.search).get(NO_CONTROLS_PARAM) === "true"; }, []); const handleCopy = () => { navigator.clipboard.writeText(window.location.href); setCopied(true); setTimeout(() => setCopied(false), 2e3); }; if (!isHydrated) return null; return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ResizableLayout, { hideControls, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(ControlsProvider, { children: [ hideControls && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)( "button", { onClick: handleCopy, className: "absolute top-4 right-4 z-50 flex items-center gap-1 rounded bg-black/70 px-3 py-1 text-white hover:bg-black", children: [ copied ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react4.Check, { size: 16 }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react4.Copy, { size: 16 }), copied ? "Copied!" : "Share" ] } ), /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PreviewContainer_default, { hideControls, children }), !hideControls && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ControlPanel_default, {}) ] }) }); } // src/components/Canvas/Canvas.tsx var import_react8 = __toESM(require("react")); var import_fiber2 = require("@react-three/fiber"); var import_fiber3 = require("@react-three/fiber"); // src/components/CameraLogger/CameraLogger.tsx var import_react7 = require("react"); var import_drei = require("@react-three/drei"); var import_fiber = require("@react-three/fiber"); var import_lodash = require("lodash"); var import_jsx_runtime13 = require("react/jsx-runtime"); function CameraLogger() { const { camera } = (0, import_fiber.useThree)(); const controlsRef = (0, import_react7.useRef)(null); const logRef = (0, import_react7.useRef)(null); (0, import_react7.useEffect)(() => { logRef.current = (0, import_lodash.debounce)(() => { console.info("Camera position:", camera.position.toArray()); }, 200); }, [camera]); (0, import_react7.useEffect)(() => { const controls = controlsRef.current; const handler = logRef.current; if (!controls || !handler) return; controls.addEventListener("change", handler); return () => controls.removeEventListener("change", handler); }, []); return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_drei.OrbitControls, { ref: controlsRef }); } // src/components/Canvas/Canvas.tsx var import_jsx_runtime14 = require("react/jsx-runtime"); var ResponsiveCamera = ({ height, width }) => { const { camera } = (0, import_fiber2.useThree)(); (0, import_react8.useEffect)(() => { const isMobile = width < 768; const zoomFactor = isMobile ? 70 : 100; camera.position.z = height / zoomFactor; camera.updateProjectionMatrix(); }, [height, camera, width]); return null; }; var Canvas = ({ mediaProps, children }) => { const canvasRef = (0, import_react8.useRef)(null); const [parentSize, setParentSize] = (0, import_react8.useState)(null); (0, import_react8.useEffect)(() => { let observer = null; const tryObserve = () => { const node = canvasRef.current; if (!node || !node.parentElement) { setTimeout(tryObserve, 50); return; } const parent = node.parentElement; observer = new ResizeObserver(([entry]) => { const { width, height } = entry.contentRect; setParentSize({ width, height }); }); observer.observe(parent); }; tryObserve(); return () => { if (observer) observer.disconnect(); }; }, []); const mergedMediaProps = { ...mediaProps || {}, size: mediaProps?.size || { width: 400, height: 400 } }; return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)( "div", { ref: canvasRef, className: "w-full h-full pointer-events-none relative touch-none", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)( import_fiber2.Canvas, { resize: { polyfill: ResizeObserver }, style: { width: parentSize?.width, height: parentSize?.height }, gl: { preserveDrawingBuffer: true }, children: [ parentSize?.height && parentSize?.width && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)( ResponsiveCamera, { height: parentSize.height, width: parentSize.width } ), mediaProps?.debugOrbit && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(CameraLogger, {}), /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("ambientLight", { intensity: 1 }), /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("pointLight", { position: [10, 10, 10] }), import_react8.default.cloneElement(children, mergedMediaProps) ] } ) } ); }; var Canvas_default = Canvas; // src/components/PlaygroundCanvas/PlaygroundCanvas.tsx var import_jsx_runtime15 = require("react/jsx-runtime"); var PlaygroundCanvas = ({ children, mediaProps }) => { return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Playground, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Canvas_default, { mediaProps, children }) }); }; var PlaygroundCanvas_default = PlaygroundCanvas; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Button, CameraLogger, Canvas, ControlsProvider, Playground, PlaygroundCanvas, useControls, useUrlSyncedControls });