@toriistudio/v0-playground
Version:
957 lines (931 loc) • 40.1 kB
JavaScript
;
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
});