@toriistudio/v0-playground 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -31,6 +31,9 @@ type ControlType = {
31
31
  render?: () => React.ReactNode;
32
32
  };
33
33
  type ControlsSchema = Record<string, ControlType>;
34
+ declare const ControlsProvider: ({ children }: {
35
+ children: ReactNode;
36
+ }) => react_jsx_runtime.JSX.Element;
34
37
  declare const useControls: <T extends ControlsSchema>(schema: T, options?: {
35
38
  componentName?: string;
36
39
  }) => { [K in keyof T]: T[K] extends {
@@ -52,4 +55,4 @@ declare const useUrlSyncedControls: <T extends ControlsSchema>(schema: T, option
52
55
  jsx: () => string;
53
56
  };
54
57
 
55
- export { type ControlType, type ControlsSchema, Playground, useControls, useUrlSyncedControls };
58
+ export { type ControlType, ControlsProvider, type ControlsSchema, Playground, useControls, useUrlSyncedControls };
package/dist/index.d.ts CHANGED
@@ -31,6 +31,9 @@ type ControlType = {
31
31
  render?: () => React.ReactNode;
32
32
  };
33
33
  type ControlsSchema = Record<string, ControlType>;
34
+ declare const ControlsProvider: ({ children }: {
35
+ children: ReactNode;
36
+ }) => react_jsx_runtime.JSX.Element;
34
37
  declare const useControls: <T extends ControlsSchema>(schema: T, options?: {
35
38
  componentName?: string;
36
39
  }) => { [K in keyof T]: T[K] extends {
@@ -52,4 +55,4 @@ declare const useUrlSyncedControls: <T extends ControlsSchema>(schema: T, option
52
55
  jsx: () => string;
53
56
  };
54
57
 
55
- export { type ControlType, type ControlsSchema, Playground, useControls, useUrlSyncedControls };
58
+ export { type ControlType, ControlsProvider, type ControlsSchema, Playground, useControls, useUrlSyncedControls };
package/dist/index.js CHANGED
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var src_exports = {};
32
32
  __export(src_exports, {
33
+ ControlsProvider: () => ControlsProvider,
33
34
  Playground: () => Playground,
34
35
  useControls: () => useControls,
35
36
  useUrlSyncedControls: () => useUrlSyncedControls
@@ -207,7 +208,7 @@ var useControls = (schema, options) => {
207
208
  }
208
209
  }, [JSON.stringify(schema), JSON.stringify(ctx.values)]);
209
210
  const typedValues = ctx.values;
210
- const jsx11 = (0, import_react2.useCallback)(() => {
211
+ const jsx12 = (0, import_react2.useCallback)(() => {
211
212
  if (!options?.componentName) return "";
212
213
  const props = Object.entries(typedValues).map(([key, val]) => {
213
214
  if (typeof val === "string") return `${key}="${val}"`;
@@ -221,7 +222,7 @@ var useControls = (schema, options) => {
221
222
  controls: ctx.values,
222
223
  schema: ctx.schema,
223
224
  setValue: ctx.setValue,
224
- jsx: jsx11
225
+ jsx: jsx12
225
226
  };
226
227
  };
227
228
  var useUrlSyncedControls = (schema, options) => {
@@ -481,8 +482,53 @@ var SelectSeparator = React8.forwardRef(({ className, ...props }, ref) => /* @__
481
482
  ));
482
483
  SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
483
484
 
484
- // src/components/ControlPanel/ControlPanel.tsx
485
+ // src/components/ui/button.tsx
486
+ var React9 = __toESM(require("react"));
487
+ var import_react_slot = require("@radix-ui/react-slot");
488
+ var import_class_variance_authority2 = require("class-variance-authority");
485
489
  var import_jsx_runtime9 = require("react/jsx-runtime");
490
+ var buttonVariants = (0, import_class_variance_authority2.cva)(
491
+ "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",
492
+ {
493
+ variants: {
494
+ variant: {
495
+ default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
496
+ destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
497
+ outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
498
+ secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
499
+ ghost: "bg-gray-800 hover:bg-gray-900",
500
+ link: "text-primary underline-offset-4 hover:underline"
501
+ },
502
+ size: {
503
+ default: "h-9 px-4 py-2",
504
+ sm: "h-8 rounded-md px-3 text-xs",
505
+ lg: "h-10 rounded-md px-8",
506
+ icon: "h-9 w-9"
507
+ }
508
+ },
509
+ defaultVariants: {
510
+ variant: "default",
511
+ size: "default"
512
+ }
513
+ }
514
+ );
515
+ var Button = React9.forwardRef(
516
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
517
+ const Comp = asChild ? import_react_slot.Slot : "button";
518
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
519
+ Comp,
520
+ {
521
+ className: cn(buttonVariants({ variant, size, className })),
522
+ ref,
523
+ ...props
524
+ }
525
+ );
526
+ }
527
+ );
528
+ Button.displayName = "Button";
529
+
530
+ // src/components/ControlPanel/ControlPanel.tsx
531
+ var import_jsx_runtime10 = require("react/jsx-runtime");
486
532
  var ControlPanel = () => {
487
533
  const [copied, setCopied] = (0, import_react4.useState)(false);
488
534
  const { leftPanelWidth, isDesktop, isHydrated, sidebarNarrow } = useResizableLayout();
@@ -493,7 +539,17 @@ var ControlPanel = () => {
493
539
  const buttonControls = Object.entries(schema).filter(
494
540
  ([, control]) => control.type === "button"
495
541
  );
496
- const jsx11 = (0, import_react4.useMemo)(() => {
542
+ const previewUrl = (0, import_react4.useMemo)(() => {
543
+ const params = new URLSearchParams();
544
+ params.set("nocontrols", "true");
545
+ for (const [key, value] of Object.entries(values)) {
546
+ if (value !== void 0 && value !== null) {
547
+ params.set(key, value.toString());
548
+ }
549
+ }
550
+ return `${window.location.pathname}?${params.toString()}`;
551
+ }, [values]);
552
+ const jsx12 = (0, import_react4.useMemo)(() => {
497
553
  if (!componentName) return "";
498
554
  const props = Object.entries(values).map(([key, val]) => {
499
555
  if (typeof val === "string") return `${key}="${val}"`;
@@ -502,7 +558,7 @@ var ControlPanel = () => {
502
558
  }).join(" ");
503
559
  return `<${componentName} ${props} />`;
504
560
  }, [componentName, values]);
505
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
561
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
506
562
  "div",
507
563
  {
508
564
  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"}`,
@@ -519,19 +575,19 @@ var ControlPanel = () => {
519
575
  overflowY: "auto"
520
576
  } : {}
521
577
  },
522
- children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "space-y-4 p-2 md:p-4 border border-stone-700 rounded-md", children: [
523
- /* @__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: "Controls" }) }),
524
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "space-y-4 pt-2", children: [
578
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "space-y-4 p-2 md:p-4 border border-stone-700 rounded-md", children: [
579
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "space-y-1", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h1", { className: "text-lg text-stone-100 font-bold", children: "Controls" }) }),
580
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "space-y-4 pt-2", children: [
525
581
  normalControls.map(([key, control]) => {
526
582
  const value = values[key];
527
583
  switch (control.type) {
528
584
  case "boolean":
529
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
585
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
530
586
  "div",
531
587
  {
532
588
  className: "flex items-center space-x-4 border-t border-stone-700 pt-4",
533
589
  children: [
534
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
590
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
535
591
  Switch,
536
592
  {
537
593
  id: key,
@@ -540,19 +596,19 @@ var ControlPanel = () => {
540
596
  className: "data-[state=checked]:bg-stone-700 data-[state=unchecked]:bg-stone-700/40"
541
597
  }
542
598
  ),
543
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Label, { htmlFor: key, className: "cursor-pointer", children: key })
599
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Label, { htmlFor: key, className: "cursor-pointer", children: key })
544
600
  ]
545
601
  },
546
602
  key
547
603
  );
548
604
  case "number":
549
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "space-y-2 w-full", children: [
550
- /* @__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: [
605
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "space-y-2 w-full", children: [
606
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex items-center justify-between pb-1", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Label, { className: "text-stone-300", htmlFor: key, children: [
551
607
  key,
552
608
  ": ",
553
609
  value
554
610
  ] }) }),
555
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
611
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
556
612
  Slider,
557
613
  {
558
614
  id: key,
@@ -566,7 +622,7 @@ var ControlPanel = () => {
566
622
  )
567
623
  ] }, key);
568
624
  case "string":
569
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
625
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
570
626
  Input,
571
627
  {
572
628
  id: key,
@@ -578,9 +634,9 @@ var ControlPanel = () => {
578
634
  key
579
635
  );
580
636
  case "color":
581
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "space-y-2 w-full", children: [
582
- /* @__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 }) }),
583
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
637
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "space-y-2 w-full", children: [
638
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex items-center justify-between pb-1", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Label, { className: "text-stone-300", htmlFor: key, children: key }) }),
639
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
584
640
  "input",
585
641
  {
586
642
  type: "color",
@@ -592,20 +648,20 @@ var ControlPanel = () => {
592
648
  )
593
649
  ] }, key);
594
650
  case "select":
595
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
651
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
596
652
  "div",
597
653
  {
598
654
  className: "space-y-2 border-t border-stone-700 pt-4",
599
655
  children: [
600
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Label, { className: "text-stone-300", htmlFor: key, children: key }),
601
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
656
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Label, { className: "text-stone-300", htmlFor: key, children: key }),
657
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
602
658
  Select,
603
659
  {
604
660
  value,
605
661
  onValueChange: (val) => setValue(key, val),
606
662
  children: [
607
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(SelectTrigger, { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(SelectValue, { placeholder: "Select option" }) }),
608
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(SelectContent, { children: control.options.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(SelectItem, { value: opt, children: opt }, opt)) })
663
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SelectTrigger, { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SelectValue, { placeholder: "Select option" }) }),
664
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SelectContent, { children: control.options.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SelectItem, { value: opt, children: opt }, opt)) })
609
665
  ]
610
666
  }
611
667
  )
@@ -617,31 +673,31 @@ var ControlPanel = () => {
617
673
  return null;
618
674
  }
619
675
  }),
620
- (buttonControls.length > 0 || jsx11) && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "border-t border-stone-700", children: [
621
- jsx11 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "flex-1 pt-4", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
676
+ (buttonControls.length > 0 || jsx12) && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "border-t border-stone-700", children: [
677
+ jsx12 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex-1 pt-4", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
622
678
  "button",
623
679
  {
624
680
  onClick: () => {
625
- navigator.clipboard.writeText(jsx11);
681
+ navigator.clipboard.writeText(jsx12);
626
682
  setCopied(true);
627
683
  setTimeout(() => setCopied(false), 5e3);
628
684
  },
629
685
  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",
630
- children: copied ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
631
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react3.Check, { className: "w-4 h-4" }),
686
+ children: copied ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
687
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react3.Check, { className: "w-4 h-4" }),
632
688
  "Copied"
633
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
634
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react3.Copy, { className: "w-4 h-4" }),
689
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
690
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react3.Copy, { className: "w-4 h-4" }),
635
691
  "Copy to Clipboard"
636
692
  ] })
637
693
  }
638
694
  ) }, "control-panel-jsx"),
639
- buttonControls.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "flex flex-wrap gap-2 pt-4", children: buttonControls.map(
640
- ([key, control]) => control.type === "button" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
695
+ buttonControls.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex flex-wrap gap-2 pt-4", children: buttonControls.map(
696
+ ([key, control]) => control.type === "button" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
641
697
  "div",
642
698
  {
643
699
  className: "flex-1",
644
- children: control.render ? control.render() : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
700
+ children: control.render ? control.render() : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
645
701
  "button",
646
702
  {
647
703
  onClick: control.onClick,
@@ -654,7 +710,20 @@ var ControlPanel = () => {
654
710
  ) : null
655
711
  ) })
656
712
  ] })
657
- ] })
713
+ ] }),
714
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Button, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
715
+ "a",
716
+ {
717
+ href: previewUrl,
718
+ target: "_blank",
719
+ rel: "noopener noreferrer",
720
+ className: "w-full px-4 py-2 text-sm text-center bg-stone-800 hover:bg-stone-700 text-white rounded",
721
+ children: [
722
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react3.SquareArrowOutUpRight, {}),
723
+ " Open in a New Tab"
724
+ ]
725
+ }
726
+ ) })
658
727
  ] })
659
728
  }
660
729
  );
@@ -662,20 +731,21 @@ var ControlPanel = () => {
662
731
  var ControlPanel_default = ControlPanel;
663
732
 
664
733
  // src/components/Playground/Playground.tsx
665
- var import_jsx_runtime10 = require("react/jsx-runtime");
734
+ var import_jsx_runtime11 = require("react/jsx-runtime");
666
735
  var NO_CONTROLS_PARAM = "nocontrols";
667
736
  function Playground({ children }) {
668
737
  const hideControls = (0, import_react5.useMemo)(() => {
669
738
  if (typeof window === "undefined") return false;
670
739
  return new URLSearchParams(window.location.search).get(NO_CONTROLS_PARAM) === "true";
671
740
  }, []);
672
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ResizableLayout, { hideControls, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(ControlsProvider, { children: [
673
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PreviewContainer_default, { hideControls, children }),
674
- !hideControls && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ControlPanel_default, {})
741
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ResizableLayout, { hideControls, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(ControlsProvider, { children: [
742
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(PreviewContainer_default, { hideControls, children }),
743
+ !hideControls && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ControlPanel_default, {})
675
744
  ] }) });
676
745
  }
677
746
  // Annotate the CommonJS export names for ESM import in node:
678
747
  0 && (module.exports = {
748
+ ControlsProvider,
679
749
  Playground,
680
750
  useControls,
681
751
  useUrlSyncedControls
package/dist/index.mjs CHANGED
@@ -182,7 +182,7 @@ var useControls = (schema, options) => {
182
182
  }
183
183
  }, [JSON.stringify(schema), JSON.stringify(ctx.values)]);
184
184
  const typedValues = ctx.values;
185
- const jsx11 = useCallback(() => {
185
+ const jsx12 = useCallback(() => {
186
186
  if (!options?.componentName) return "";
187
187
  const props = Object.entries(typedValues).map(([key, val]) => {
188
188
  if (typeof val === "string") return `${key}="${val}"`;
@@ -196,7 +196,7 @@ var useControls = (schema, options) => {
196
196
  controls: ctx.values,
197
197
  schema: ctx.schema,
198
198
  setValue: ctx.setValue,
199
- jsx: jsx11
199
+ jsx: jsx12
200
200
  };
201
201
  };
202
202
  var useUrlSyncedControls = (schema, options) => {
@@ -248,7 +248,7 @@ var PreviewContainer_default = PreviewContainer;
248
248
 
249
249
  // src/components/ControlPanel/ControlPanel.tsx
250
250
  import { useState as useState3, useMemo as useMemo2 } from "react";
251
- import { Check as Check2, Copy } from "lucide-react";
251
+ import { Check as Check2, Copy, SquareArrowOutUpRight } from "lucide-react";
252
252
 
253
253
  // src/components/ui/switch.tsx
254
254
  import * as React4 from "react";
@@ -456,8 +456,53 @@ var SelectSeparator = React8.forwardRef(({ className, ...props }, ref) => /* @__
456
456
  ));
457
457
  SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
458
458
 
459
+ // src/components/ui/button.tsx
460
+ import * as React9 from "react";
461
+ import { Slot } from "@radix-ui/react-slot";
462
+ import { cva as cva2 } from "class-variance-authority";
463
+ import { jsx as jsx9 } from "react/jsx-runtime";
464
+ var buttonVariants = cva2(
465
+ "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",
466
+ {
467
+ variants: {
468
+ variant: {
469
+ default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
470
+ destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
471
+ outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
472
+ secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
473
+ ghost: "bg-gray-800 hover:bg-gray-900",
474
+ link: "text-primary underline-offset-4 hover:underline"
475
+ },
476
+ size: {
477
+ default: "h-9 px-4 py-2",
478
+ sm: "h-8 rounded-md px-3 text-xs",
479
+ lg: "h-10 rounded-md px-8",
480
+ icon: "h-9 w-9"
481
+ }
482
+ },
483
+ defaultVariants: {
484
+ variant: "default",
485
+ size: "default"
486
+ }
487
+ }
488
+ );
489
+ var Button = React9.forwardRef(
490
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
491
+ const Comp = asChild ? Slot : "button";
492
+ return /* @__PURE__ */ jsx9(
493
+ Comp,
494
+ {
495
+ className: cn(buttonVariants({ variant, size, className })),
496
+ ref,
497
+ ...props
498
+ }
499
+ );
500
+ }
501
+ );
502
+ Button.displayName = "Button";
503
+
459
504
  // src/components/ControlPanel/ControlPanel.tsx
460
- import { Fragment, jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
505
+ import { Fragment, jsx as jsx10, jsxs as jsxs4 } from "react/jsx-runtime";
461
506
  var ControlPanel = () => {
462
507
  const [copied, setCopied] = useState3(false);
463
508
  const { leftPanelWidth, isDesktop, isHydrated, sidebarNarrow } = useResizableLayout();
@@ -468,7 +513,17 @@ var ControlPanel = () => {
468
513
  const buttonControls = Object.entries(schema).filter(
469
514
  ([, control]) => control.type === "button"
470
515
  );
471
- const jsx11 = useMemo2(() => {
516
+ const previewUrl = useMemo2(() => {
517
+ const params = new URLSearchParams();
518
+ params.set("nocontrols", "true");
519
+ for (const [key, value] of Object.entries(values)) {
520
+ if (value !== void 0 && value !== null) {
521
+ params.set(key, value.toString());
522
+ }
523
+ }
524
+ return `${window.location.pathname}?${params.toString()}`;
525
+ }, [values]);
526
+ const jsx12 = useMemo2(() => {
472
527
  if (!componentName) return "";
473
528
  const props = Object.entries(values).map(([key, val]) => {
474
529
  if (typeof val === "string") return `${key}="${val}"`;
@@ -477,7 +532,7 @@ var ControlPanel = () => {
477
532
  }).join(" ");
478
533
  return `<${componentName} ${props} />`;
479
534
  }, [componentName, values]);
480
- return /* @__PURE__ */ jsx9(
535
+ return /* @__PURE__ */ jsx10(
481
536
  "div",
482
537
  {
483
538
  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"}`,
@@ -495,7 +550,7 @@ var ControlPanel = () => {
495
550
  } : {}
496
551
  },
497
552
  children: /* @__PURE__ */ jsxs4("div", { className: "space-y-4 p-2 md:p-4 border border-stone-700 rounded-md", children: [
498
- /* @__PURE__ */ jsx9("div", { className: "space-y-1", children: /* @__PURE__ */ jsx9("h1", { className: "text-lg text-stone-100 font-bold", children: "Controls" }) }),
553
+ /* @__PURE__ */ jsx10("div", { className: "space-y-1", children: /* @__PURE__ */ jsx10("h1", { className: "text-lg text-stone-100 font-bold", children: "Controls" }) }),
499
554
  /* @__PURE__ */ jsxs4("div", { className: "space-y-4 pt-2", children: [
500
555
  normalControls.map(([key, control]) => {
501
556
  const value = values[key];
@@ -506,7 +561,7 @@ var ControlPanel = () => {
506
561
  {
507
562
  className: "flex items-center space-x-4 border-t border-stone-700 pt-4",
508
563
  children: [
509
- /* @__PURE__ */ jsx9(
564
+ /* @__PURE__ */ jsx10(
510
565
  Switch,
511
566
  {
512
567
  id: key,
@@ -515,19 +570,19 @@ var ControlPanel = () => {
515
570
  className: "data-[state=checked]:bg-stone-700 data-[state=unchecked]:bg-stone-700/40"
516
571
  }
517
572
  ),
518
- /* @__PURE__ */ jsx9(Label, { htmlFor: key, className: "cursor-pointer", children: key })
573
+ /* @__PURE__ */ jsx10(Label, { htmlFor: key, className: "cursor-pointer", children: key })
519
574
  ]
520
575
  },
521
576
  key
522
577
  );
523
578
  case "number":
524
579
  return /* @__PURE__ */ jsxs4("div", { className: "space-y-2 w-full", children: [
525
- /* @__PURE__ */ jsx9("div", { className: "flex items-center justify-between pb-1", children: /* @__PURE__ */ jsxs4(Label, { className: "text-stone-300", htmlFor: key, children: [
580
+ /* @__PURE__ */ jsx10("div", { className: "flex items-center justify-between pb-1", children: /* @__PURE__ */ jsxs4(Label, { className: "text-stone-300", htmlFor: key, children: [
526
581
  key,
527
582
  ": ",
528
583
  value
529
584
  ] }) }),
530
- /* @__PURE__ */ jsx9(
585
+ /* @__PURE__ */ jsx10(
531
586
  Slider,
532
587
  {
533
588
  id: key,
@@ -541,7 +596,7 @@ var ControlPanel = () => {
541
596
  )
542
597
  ] }, key);
543
598
  case "string":
544
- return /* @__PURE__ */ jsx9(
599
+ return /* @__PURE__ */ jsx10(
545
600
  Input,
546
601
  {
547
602
  id: key,
@@ -554,8 +609,8 @@ var ControlPanel = () => {
554
609
  );
555
610
  case "color":
556
611
  return /* @__PURE__ */ jsxs4("div", { className: "space-y-2 w-full", children: [
557
- /* @__PURE__ */ jsx9("div", { className: "flex items-center justify-between pb-1", children: /* @__PURE__ */ jsx9(Label, { className: "text-stone-300", htmlFor: key, children: key }) }),
558
- /* @__PURE__ */ jsx9(
612
+ /* @__PURE__ */ jsx10("div", { className: "flex items-center justify-between pb-1", children: /* @__PURE__ */ jsx10(Label, { className: "text-stone-300", htmlFor: key, children: key }) }),
613
+ /* @__PURE__ */ jsx10(
559
614
  "input",
560
615
  {
561
616
  type: "color",
@@ -572,15 +627,15 @@ var ControlPanel = () => {
572
627
  {
573
628
  className: "space-y-2 border-t border-stone-700 pt-4",
574
629
  children: [
575
- /* @__PURE__ */ jsx9(Label, { className: "text-stone-300", htmlFor: key, children: key }),
630
+ /* @__PURE__ */ jsx10(Label, { className: "text-stone-300", htmlFor: key, children: key }),
576
631
  /* @__PURE__ */ jsxs4(
577
632
  Select,
578
633
  {
579
634
  value,
580
635
  onValueChange: (val) => setValue(key, val),
581
636
  children: [
582
- /* @__PURE__ */ jsx9(SelectTrigger, { children: /* @__PURE__ */ jsx9(SelectValue, { placeholder: "Select option" }) }),
583
- /* @__PURE__ */ jsx9(SelectContent, { children: control.options.map((opt) => /* @__PURE__ */ jsx9(SelectItem, { value: opt, children: opt }, opt)) })
637
+ /* @__PURE__ */ jsx10(SelectTrigger, { children: /* @__PURE__ */ jsx10(SelectValue, { placeholder: "Select option" }) }),
638
+ /* @__PURE__ */ jsx10(SelectContent, { children: control.options.map((opt) => /* @__PURE__ */ jsx10(SelectItem, { value: opt, children: opt }, opt)) })
584
639
  ]
585
640
  }
586
641
  )
@@ -592,31 +647,31 @@ var ControlPanel = () => {
592
647
  return null;
593
648
  }
594
649
  }),
595
- (buttonControls.length > 0 || jsx11) && /* @__PURE__ */ jsxs4("div", { className: "border-t border-stone-700", children: [
596
- jsx11 && /* @__PURE__ */ jsx9("div", { className: "flex-1 pt-4", children: /* @__PURE__ */ jsx9(
650
+ (buttonControls.length > 0 || jsx12) && /* @__PURE__ */ jsxs4("div", { className: "border-t border-stone-700", children: [
651
+ jsx12 && /* @__PURE__ */ jsx10("div", { className: "flex-1 pt-4", children: /* @__PURE__ */ jsx10(
597
652
  "button",
598
653
  {
599
654
  onClick: () => {
600
- navigator.clipboard.writeText(jsx11);
655
+ navigator.clipboard.writeText(jsx12);
601
656
  setCopied(true);
602
657
  setTimeout(() => setCopied(false), 5e3);
603
658
  },
604
659
  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",
605
660
  children: copied ? /* @__PURE__ */ jsxs4(Fragment, { children: [
606
- /* @__PURE__ */ jsx9(Check2, { className: "w-4 h-4" }),
661
+ /* @__PURE__ */ jsx10(Check2, { className: "w-4 h-4" }),
607
662
  "Copied"
608
663
  ] }) : /* @__PURE__ */ jsxs4(Fragment, { children: [
609
- /* @__PURE__ */ jsx9(Copy, { className: "w-4 h-4" }),
664
+ /* @__PURE__ */ jsx10(Copy, { className: "w-4 h-4" }),
610
665
  "Copy to Clipboard"
611
666
  ] })
612
667
  }
613
668
  ) }, "control-panel-jsx"),
614
- buttonControls.length > 0 && /* @__PURE__ */ jsx9("div", { className: "flex flex-wrap gap-2 pt-4", children: buttonControls.map(
615
- ([key, control]) => control.type === "button" ? /* @__PURE__ */ jsx9(
669
+ buttonControls.length > 0 && /* @__PURE__ */ jsx10("div", { className: "flex flex-wrap gap-2 pt-4", children: buttonControls.map(
670
+ ([key, control]) => control.type === "button" ? /* @__PURE__ */ jsx10(
616
671
  "div",
617
672
  {
618
673
  className: "flex-1",
619
- children: control.render ? control.render() : /* @__PURE__ */ jsx9(
674
+ children: control.render ? control.render() : /* @__PURE__ */ jsx10(
620
675
  "button",
621
676
  {
622
677
  onClick: control.onClick,
@@ -629,7 +684,20 @@ var ControlPanel = () => {
629
684
  ) : null
630
685
  ) })
631
686
  ] })
632
- ] })
687
+ ] }),
688
+ /* @__PURE__ */ jsx10(Button, { asChild: true, children: /* @__PURE__ */ jsxs4(
689
+ "a",
690
+ {
691
+ href: previewUrl,
692
+ target: "_blank",
693
+ rel: "noopener noreferrer",
694
+ className: "w-full px-4 py-2 text-sm text-center bg-stone-800 hover:bg-stone-700 text-white rounded",
695
+ children: [
696
+ /* @__PURE__ */ jsx10(SquareArrowOutUpRight, {}),
697
+ " Open in a New Tab"
698
+ ]
699
+ }
700
+ ) })
633
701
  ] })
634
702
  }
635
703
  );
@@ -637,19 +705,20 @@ var ControlPanel = () => {
637
705
  var ControlPanel_default = ControlPanel;
638
706
 
639
707
  // src/components/Playground/Playground.tsx
640
- import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
708
+ import { jsx as jsx11, jsxs as jsxs5 } from "react/jsx-runtime";
641
709
  var NO_CONTROLS_PARAM = "nocontrols";
642
710
  function Playground({ children }) {
643
711
  const hideControls = useMemo3(() => {
644
712
  if (typeof window === "undefined") return false;
645
713
  return new URLSearchParams(window.location.search).get(NO_CONTROLS_PARAM) === "true";
646
714
  }, []);
647
- return /* @__PURE__ */ jsx10(ResizableLayout, { hideControls, children: /* @__PURE__ */ jsxs5(ControlsProvider, { children: [
648
- /* @__PURE__ */ jsx10(PreviewContainer_default, { hideControls, children }),
649
- !hideControls && /* @__PURE__ */ jsx10(ControlPanel_default, {})
715
+ return /* @__PURE__ */ jsx11(ResizableLayout, { hideControls, children: /* @__PURE__ */ jsxs5(ControlsProvider, { children: [
716
+ /* @__PURE__ */ jsx11(PreviewContainer_default, { hideControls, children }),
717
+ !hideControls && /* @__PURE__ */ jsx11(ControlPanel_default, {})
650
718
  ] }) });
651
719
  }
652
720
  export {
721
+ ControlsProvider,
653
722
  Playground,
654
723
  useControls,
655
724
  useUrlSyncedControls
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toriistudio/v0-playground",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "V0 Playground",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",