@toriistudio/v0-playground 0.2.7 → 0.2.10

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
@@ -5,37 +5,45 @@ declare function Playground({ children }: {
5
5
  children: ReactNode;
6
6
  }): react_jsx_runtime.JSX.Element | null;
7
7
 
8
- type ControlType = {
8
+ type BaseControl = {
9
+ hidden?: boolean;
10
+ };
11
+ type ControlType = ({
9
12
  type: "boolean";
10
13
  value: boolean;
11
- } | {
14
+ } & BaseControl) | ({
12
15
  type: "number";
13
16
  value: number;
14
17
  min?: number;
15
18
  max?: number;
16
19
  step?: number;
17
- } | {
20
+ } & BaseControl) | ({
18
21
  type: "string";
19
22
  value: string;
20
- } | {
23
+ } & BaseControl) | ({
21
24
  type: "color";
22
25
  value: string;
23
- } | {
26
+ } & BaseControl) | ({
24
27
  type: "select";
25
28
  value: string;
26
29
  options: string[];
27
- } | {
30
+ } & BaseControl) | ({
28
31
  type: "button";
29
32
  onClick?: () => void;
30
33
  label?: string;
31
34
  render?: () => React.ReactNode;
32
- };
35
+ } & BaseControl);
33
36
  type ControlsSchema = Record<string, ControlType>;
37
+ type ControlsConfig = {
38
+ showCopyButton?: boolean;
39
+ mainLabel?: string;
40
+ };
34
41
  declare const ControlsProvider: ({ children }: {
35
42
  children: ReactNode;
36
43
  }) => react_jsx_runtime.JSX.Element;
37
44
  declare const useControls: <T extends ControlsSchema>(schema: T, options?: {
38
45
  componentName?: string;
46
+ config?: ControlsConfig;
39
47
  }) => { [K in keyof T]: T[K] extends {
40
48
  value: infer V;
41
49
  } ? V : never; } & {
@@ -46,6 +54,7 @@ declare const useControls: <T extends ControlsSchema>(schema: T, options?: {
46
54
  };
47
55
  declare const useUrlSyncedControls: <T extends ControlsSchema>(schema: T, options?: {
48
56
  componentName?: string;
57
+ config?: ControlsConfig;
49
58
  }) => { [K in keyof T]: T[K] extends {
50
59
  value: infer V;
51
60
  } ? V : never; } & {
package/dist/index.d.ts CHANGED
@@ -5,37 +5,45 @@ declare function Playground({ children }: {
5
5
  children: ReactNode;
6
6
  }): react_jsx_runtime.JSX.Element | null;
7
7
 
8
- type ControlType = {
8
+ type BaseControl = {
9
+ hidden?: boolean;
10
+ };
11
+ type ControlType = ({
9
12
  type: "boolean";
10
13
  value: boolean;
11
- } | {
14
+ } & BaseControl) | ({
12
15
  type: "number";
13
16
  value: number;
14
17
  min?: number;
15
18
  max?: number;
16
19
  step?: number;
17
- } | {
20
+ } & BaseControl) | ({
18
21
  type: "string";
19
22
  value: string;
20
- } | {
23
+ } & BaseControl) | ({
21
24
  type: "color";
22
25
  value: string;
23
- } | {
26
+ } & BaseControl) | ({
24
27
  type: "select";
25
28
  value: string;
26
29
  options: string[];
27
- } | {
30
+ } & BaseControl) | ({
28
31
  type: "button";
29
32
  onClick?: () => void;
30
33
  label?: string;
31
34
  render?: () => React.ReactNode;
32
- };
35
+ } & BaseControl);
33
36
  type ControlsSchema = Record<string, ControlType>;
37
+ type ControlsConfig = {
38
+ showCopyButton?: boolean;
39
+ mainLabel?: string;
40
+ };
34
41
  declare const ControlsProvider: ({ children }: {
35
42
  children: ReactNode;
36
43
  }) => react_jsx_runtime.JSX.Element;
37
44
  declare const useControls: <T extends ControlsSchema>(schema: T, options?: {
38
45
  componentName?: string;
46
+ config?: ControlsConfig;
39
47
  }) => { [K in keyof T]: T[K] extends {
40
48
  value: infer V;
41
49
  } ? V : never; } & {
@@ -46,6 +54,7 @@ declare const useControls: <T extends ControlsSchema>(schema: T, options?: {
46
54
  };
47
55
  declare const useUrlSyncedControls: <T extends ControlsSchema>(schema: T, options?: {
48
56
  componentName?: string;
57
+ config?: ControlsConfig;
49
58
  }) => { [K in keyof T]: T[K] extends {
50
59
  value: infer V;
51
60
  } ? V : never; } & {
package/dist/index.js CHANGED
@@ -39,6 +39,7 @@ module.exports = __toCommonJS(src_exports);
39
39
 
40
40
  // src/components/Playground/Playground.tsx
41
41
  var import_react6 = require("react");
42
+ var import_lucide_react4 = require("lucide-react");
42
43
 
43
44
  // src/context/ResizableLayout.tsx
44
45
  var import_react = require("react");
@@ -166,6 +167,9 @@ var useControlsContext = () => {
166
167
  var ControlsProvider = ({ children }) => {
167
168
  const [schema, setSchema] = (0, import_react2.useState)({});
168
169
  const [values, setValues] = (0, import_react2.useState)({});
170
+ const [config, setConfig] = (0, import_react2.useState)({
171
+ showCopyButton: true
172
+ });
169
173
  const [componentName, setComponentName] = (0, import_react2.useState)();
170
174
  const setValue = (key, value) => {
171
175
  setValues((prev) => ({ ...prev, [key]: value }));
@@ -174,12 +178,18 @@ var ControlsProvider = ({ children }) => {
174
178
  if (opts?.componentName) {
175
179
  setComponentName(opts.componentName);
176
180
  }
181
+ if (opts?.config) {
182
+ setConfig((prev) => ({
183
+ ...prev,
184
+ ...opts.config
185
+ }));
186
+ }
177
187
  setSchema((prevSchema) => ({ ...prevSchema, ...newSchema }));
178
188
  setValues((prevValues) => {
179
189
  const updated = { ...prevValues };
180
190
  for (const key in newSchema) {
191
+ const control = newSchema[key];
181
192
  if (!(key in updated)) {
182
- const control = newSchema[key];
183
193
  if ("value" in control) {
184
194
  updated[key] = control.value;
185
195
  }
@@ -189,8 +199,15 @@ var ControlsProvider = ({ children }) => {
189
199
  });
190
200
  };
191
201
  const contextValue = (0, import_react2.useMemo)(
192
- () => ({ schema, values, setValue, registerSchema, componentName }),
193
- [schema, values, componentName]
202
+ () => ({
203
+ schema,
204
+ values,
205
+ setValue,
206
+ registerSchema,
207
+ componentName,
208
+ config
209
+ }),
210
+ [schema, values, componentName, config]
194
211
  );
195
212
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ControlsContext.Provider, { value: contextValue, children });
196
213
  };
@@ -551,13 +568,13 @@ var import_jsx_runtime10 = require("react/jsx-runtime");
551
568
  var ControlPanel = () => {
552
569
  const [copied, setCopied] = (0, import_react5.useState)(false);
553
570
  const { leftPanelWidth, isDesktop, isHydrated, sidebarNarrow } = useResizableLayout();
554
- const { schema, setValue, values, componentName } = useControlsContext();
571
+ const { schema, setValue, values, componentName, config } = useControlsContext();
555
572
  const previewUrl = usePreviewUrl(values);
556
573
  const normalControls = Object.entries(schema).filter(
557
- ([, control]) => control.type !== "button"
574
+ ([, control]) => control.type !== "button" && !control.hidden
558
575
  );
559
576
  const buttonControls = Object.entries(schema).filter(
560
- ([, control]) => control.type === "button"
577
+ ([, control]) => control.type === "button" && !control.hidden
561
578
  );
562
579
  const jsx12 = (0, import_react5.useMemo)(() => {
563
580
  if (!componentName) return "";
@@ -588,7 +605,7 @@ var ControlPanel = () => {
588
605
  } : {}
589
606
  },
590
607
  children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "mb-10 space-y-4 p-2 md:p-4 border border-stone-700 rounded-md", children: [
591
- /* @__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" }) }),
608
+ /* @__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: config?.mainLabel ?? "Controls" }) }),
592
609
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "space-y-4 pt-2", children: [
593
610
  normalControls.map(([key, control]) => {
594
611
  const value = values[key];
@@ -685,43 +702,49 @@ var ControlPanel = () => {
685
702
  return null;
686
703
  }
687
704
  }),
688
- (buttonControls.length > 0 || jsx12) && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "border-t border-stone-700", children: [
689
- jsx12 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex-1 pt-4", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
690
- "button",
691
- {
692
- onClick: () => {
693
- navigator.clipboard.writeText(jsx12);
694
- setCopied(true);
695
- setTimeout(() => setCopied(false), 5e3);
696
- },
697
- 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",
698
- children: copied ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
699
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react3.Check, { className: "w-4 h-4" }),
700
- "Copied"
701
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
702
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react3.Copy, { className: "w-4 h-4" }),
703
- "Copy to Clipboard"
704
- ] })
705
- }
706
- ) }, "control-panel-jsx"),
707
- buttonControls.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex flex-wrap gap-2 pt-4", children: buttonControls.map(
708
- ([key, control]) => control.type === "button" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
709
- "div",
710
- {
711
- className: "flex-1",
712
- children: control.render ? control.render() : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
713
- "button",
705
+ (buttonControls.length > 0 || jsx12) && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
706
+ "div",
707
+ {
708
+ className: `${normalControls.length > 0 ? "border-t" : ""} border-stone-700`,
709
+ children: [
710
+ jsx12 && config?.showCopyButton !== false && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex-1 pt-4", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
711
+ "button",
712
+ {
713
+ onClick: () => {
714
+ navigator.clipboard.writeText(jsx12);
715
+ setCopied(true);
716
+ setTimeout(() => setCopied(false), 5e3);
717
+ },
718
+ 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",
719
+ children: copied ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
720
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react3.Check, { className: "w-4 h-4" }),
721
+ "Copied"
722
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
723
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react3.Copy, { className: "w-4 h-4" }),
724
+ "Copy to Clipboard"
725
+ ] })
726
+ }
727
+ ) }, "control-panel-jsx"),
728
+ buttonControls.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex flex-wrap gap-2 pt-4", children: buttonControls.map(
729
+ ([key, control]) => control.type === "button" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
730
+ "div",
714
731
  {
715
- onClick: control.onClick,
716
- className: "w-full px-4 py-2 text-sm bg-stone-700 hover:bg-stone-600 text-white rounded",
717
- children: control.label ?? key
718
- }
719
- )
720
- },
721
- `control-panel-custom-${key}`
722
- ) : null
723
- ) })
724
- ] })
732
+ className: "flex-1",
733
+ children: control.render ? control.render() : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
734
+ "button",
735
+ {
736
+ onClick: control.onClick,
737
+ className: "w-full px-4 py-2 text-sm bg-stone-700 hover:bg-stone-600 text-white rounded",
738
+ children: control.label ?? key
739
+ }
740
+ )
741
+ },
742
+ `control-panel-custom-${key}`
743
+ ) : null
744
+ ) })
745
+ ]
746
+ }
747
+ )
725
748
  ] }),
726
749
  previewUrl && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Button, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
727
750
  "a",
@@ -747,6 +770,7 @@ var import_jsx_runtime11 = require("react/jsx-runtime");
747
770
  var NO_CONTROLS_PARAM = "nocontrols";
748
771
  function Playground({ children }) {
749
772
  const [isHydrated, setIsHydrated] = (0, import_react6.useState)(false);
773
+ const [copied, setCopied] = (0, import_react6.useState)(false);
750
774
  (0, import_react6.useEffect)(() => {
751
775
  setIsHydrated(true);
752
776
  }, []);
@@ -754,8 +778,24 @@ function Playground({ children }) {
754
778
  if (typeof window === "undefined") return false;
755
779
  return new URLSearchParams(window.location.search).get(NO_CONTROLS_PARAM) === "true";
756
780
  }, []);
781
+ const handleCopy = () => {
782
+ navigator.clipboard.writeText(window.location.href);
783
+ setCopied(true);
784
+ setTimeout(() => setCopied(false), 2e3);
785
+ };
757
786
  if (!isHydrated) return null;
758
787
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ResizableLayout, { hideControls, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(ControlsProvider, { children: [
788
+ hideControls && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
789
+ "button",
790
+ {
791
+ onClick: handleCopy,
792
+ 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",
793
+ children: [
794
+ copied ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react4.Check, { size: 16 }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react4.Copy, { size: 16 }),
795
+ copied ? "Copied!" : "Share"
796
+ ]
797
+ }
798
+ ),
759
799
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(PreviewContainer_default, { hideControls, children }),
760
800
  !hideControls && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ControlPanel_default, {})
761
801
  ] }) });
package/dist/index.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  // src/components/Playground/Playground.tsx
2
2
  import { useEffect as useEffect4, useMemo as useMemo3, useState as useState5 } from "react";
3
+ import { Check as Check3, Copy as Copy2 } from "lucide-react";
3
4
 
4
5
  // src/context/ResizableLayout.tsx
5
6
  import {
@@ -140,6 +141,9 @@ var useControlsContext = () => {
140
141
  var ControlsProvider = ({ children }) => {
141
142
  const [schema, setSchema] = useState2({});
142
143
  const [values, setValues] = useState2({});
144
+ const [config, setConfig] = useState2({
145
+ showCopyButton: true
146
+ });
143
147
  const [componentName, setComponentName] = useState2();
144
148
  const setValue = (key, value) => {
145
149
  setValues((prev) => ({ ...prev, [key]: value }));
@@ -148,12 +152,18 @@ var ControlsProvider = ({ children }) => {
148
152
  if (opts?.componentName) {
149
153
  setComponentName(opts.componentName);
150
154
  }
155
+ if (opts?.config) {
156
+ setConfig((prev) => ({
157
+ ...prev,
158
+ ...opts.config
159
+ }));
160
+ }
151
161
  setSchema((prevSchema) => ({ ...prevSchema, ...newSchema }));
152
162
  setValues((prevValues) => {
153
163
  const updated = { ...prevValues };
154
164
  for (const key in newSchema) {
165
+ const control = newSchema[key];
155
166
  if (!(key in updated)) {
156
- const control = newSchema[key];
157
167
  if ("value" in control) {
158
168
  updated[key] = control.value;
159
169
  }
@@ -163,8 +173,15 @@ var ControlsProvider = ({ children }) => {
163
173
  });
164
174
  };
165
175
  const contextValue = useMemo(
166
- () => ({ schema, values, setValue, registerSchema, componentName }),
167
- [schema, values, componentName]
176
+ () => ({
177
+ schema,
178
+ values,
179
+ setValue,
180
+ registerSchema,
181
+ componentName,
182
+ config
183
+ }),
184
+ [schema, values, componentName, config]
168
185
  );
169
186
  return /* @__PURE__ */ jsx2(ControlsContext.Provider, { value: contextValue, children });
170
187
  };
@@ -525,13 +542,13 @@ import { Fragment, jsx as jsx10, jsxs as jsxs4 } from "react/jsx-runtime";
525
542
  var ControlPanel = () => {
526
543
  const [copied, setCopied] = useState4(false);
527
544
  const { leftPanelWidth, isDesktop, isHydrated, sidebarNarrow } = useResizableLayout();
528
- const { schema, setValue, values, componentName } = useControlsContext();
545
+ const { schema, setValue, values, componentName, config } = useControlsContext();
529
546
  const previewUrl = usePreviewUrl(values);
530
547
  const normalControls = Object.entries(schema).filter(
531
- ([, control]) => control.type !== "button"
548
+ ([, control]) => control.type !== "button" && !control.hidden
532
549
  );
533
550
  const buttonControls = Object.entries(schema).filter(
534
- ([, control]) => control.type === "button"
551
+ ([, control]) => control.type === "button" && !control.hidden
535
552
  );
536
553
  const jsx12 = useMemo2(() => {
537
554
  if (!componentName) return "";
@@ -562,7 +579,7 @@ var ControlPanel = () => {
562
579
  } : {}
563
580
  },
564
581
  children: /* @__PURE__ */ jsxs4("div", { className: "mb-10 space-y-4 p-2 md:p-4 border border-stone-700 rounded-md", children: [
565
- /* @__PURE__ */ jsx10("div", { className: "space-y-1", children: /* @__PURE__ */ jsx10("h1", { className: "text-lg text-stone-100 font-bold", children: "Controls" }) }),
582
+ /* @__PURE__ */ jsx10("div", { className: "space-y-1", children: /* @__PURE__ */ jsx10("h1", { className: "text-lg text-stone-100 font-bold", children: config?.mainLabel ?? "Controls" }) }),
566
583
  /* @__PURE__ */ jsxs4("div", { className: "space-y-4 pt-2", children: [
567
584
  normalControls.map(([key, control]) => {
568
585
  const value = values[key];
@@ -659,43 +676,49 @@ var ControlPanel = () => {
659
676
  return null;
660
677
  }
661
678
  }),
662
- (buttonControls.length > 0 || jsx12) && /* @__PURE__ */ jsxs4("div", { className: "border-t border-stone-700", children: [
663
- jsx12 && /* @__PURE__ */ jsx10("div", { className: "flex-1 pt-4", children: /* @__PURE__ */ jsx10(
664
- "button",
665
- {
666
- onClick: () => {
667
- navigator.clipboard.writeText(jsx12);
668
- setCopied(true);
669
- setTimeout(() => setCopied(false), 5e3);
670
- },
671
- 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",
672
- children: copied ? /* @__PURE__ */ jsxs4(Fragment, { children: [
673
- /* @__PURE__ */ jsx10(Check2, { className: "w-4 h-4" }),
674
- "Copied"
675
- ] }) : /* @__PURE__ */ jsxs4(Fragment, { children: [
676
- /* @__PURE__ */ jsx10(Copy, { className: "w-4 h-4" }),
677
- "Copy to Clipboard"
678
- ] })
679
- }
680
- ) }, "control-panel-jsx"),
681
- buttonControls.length > 0 && /* @__PURE__ */ jsx10("div", { className: "flex flex-wrap gap-2 pt-4", children: buttonControls.map(
682
- ([key, control]) => control.type === "button" ? /* @__PURE__ */ jsx10(
683
- "div",
684
- {
685
- className: "flex-1",
686
- children: control.render ? control.render() : /* @__PURE__ */ jsx10(
687
- "button",
679
+ (buttonControls.length > 0 || jsx12) && /* @__PURE__ */ jsxs4(
680
+ "div",
681
+ {
682
+ className: `${normalControls.length > 0 ? "border-t" : ""} border-stone-700`,
683
+ children: [
684
+ jsx12 && config?.showCopyButton !== false && /* @__PURE__ */ jsx10("div", { className: "flex-1 pt-4", children: /* @__PURE__ */ jsx10(
685
+ "button",
686
+ {
687
+ onClick: () => {
688
+ navigator.clipboard.writeText(jsx12);
689
+ setCopied(true);
690
+ setTimeout(() => setCopied(false), 5e3);
691
+ },
692
+ 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",
693
+ children: copied ? /* @__PURE__ */ jsxs4(Fragment, { children: [
694
+ /* @__PURE__ */ jsx10(Check2, { className: "w-4 h-4" }),
695
+ "Copied"
696
+ ] }) : /* @__PURE__ */ jsxs4(Fragment, { children: [
697
+ /* @__PURE__ */ jsx10(Copy, { className: "w-4 h-4" }),
698
+ "Copy to Clipboard"
699
+ ] })
700
+ }
701
+ ) }, "control-panel-jsx"),
702
+ buttonControls.length > 0 && /* @__PURE__ */ jsx10("div", { className: "flex flex-wrap gap-2 pt-4", children: buttonControls.map(
703
+ ([key, control]) => control.type === "button" ? /* @__PURE__ */ jsx10(
704
+ "div",
688
705
  {
689
- onClick: control.onClick,
690
- className: "w-full px-4 py-2 text-sm bg-stone-700 hover:bg-stone-600 text-white rounded",
691
- children: control.label ?? key
692
- }
693
- )
694
- },
695
- `control-panel-custom-${key}`
696
- ) : null
697
- ) })
698
- ] })
706
+ className: "flex-1",
707
+ children: control.render ? control.render() : /* @__PURE__ */ jsx10(
708
+ "button",
709
+ {
710
+ onClick: control.onClick,
711
+ className: "w-full px-4 py-2 text-sm bg-stone-700 hover:bg-stone-600 text-white rounded",
712
+ children: control.label ?? key
713
+ }
714
+ )
715
+ },
716
+ `control-panel-custom-${key}`
717
+ ) : null
718
+ ) })
719
+ ]
720
+ }
721
+ )
699
722
  ] }),
700
723
  previewUrl && /* @__PURE__ */ jsx10(Button, { asChild: true, children: /* @__PURE__ */ jsxs4(
701
724
  "a",
@@ -721,6 +744,7 @@ import { jsx as jsx11, jsxs as jsxs5 } from "react/jsx-runtime";
721
744
  var NO_CONTROLS_PARAM = "nocontrols";
722
745
  function Playground({ children }) {
723
746
  const [isHydrated, setIsHydrated] = useState5(false);
747
+ const [copied, setCopied] = useState5(false);
724
748
  useEffect4(() => {
725
749
  setIsHydrated(true);
726
750
  }, []);
@@ -728,8 +752,24 @@ function Playground({ children }) {
728
752
  if (typeof window === "undefined") return false;
729
753
  return new URLSearchParams(window.location.search).get(NO_CONTROLS_PARAM) === "true";
730
754
  }, []);
755
+ const handleCopy = () => {
756
+ navigator.clipboard.writeText(window.location.href);
757
+ setCopied(true);
758
+ setTimeout(() => setCopied(false), 2e3);
759
+ };
731
760
  if (!isHydrated) return null;
732
761
  return /* @__PURE__ */ jsx11(ResizableLayout, { hideControls, children: /* @__PURE__ */ jsxs5(ControlsProvider, { children: [
762
+ hideControls && /* @__PURE__ */ jsxs5(
763
+ "button",
764
+ {
765
+ onClick: handleCopy,
766
+ 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",
767
+ children: [
768
+ copied ? /* @__PURE__ */ jsx11(Check3, { size: 16 }) : /* @__PURE__ */ jsx11(Copy2, { size: 16 }),
769
+ copied ? "Copied!" : "Share"
770
+ ]
771
+ }
772
+ ),
733
773
  /* @__PURE__ */ jsx11(PreviewContainer_default, { hideControls, children }),
734
774
  !hideControls && /* @__PURE__ */ jsx11(ControlPanel_default, {})
735
775
  ] }) });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toriistudio/v0-playground",
3
- "version": "0.2.7",
3
+ "version": "0.2.10",
4
4
  "description": "V0 Playground",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",
@@ -30,7 +30,9 @@
30
30
  "scripts": {
31
31
  "build": "tsup",
32
32
  "local:push": "yarn build && yalc push",
33
- "prepublishOnly": "npm run build",
33
+ "release:patch": "npm version patch && git push --follow-tags && npm publish",
34
+ "release:minor": "npm version minor && git push --follow-tags && npm publish",
35
+ "release:major": "npm version major && git push --follow-tags && npm publish",
34
36
  "test": "echo \"Error: no test specified\" && exit 1"
35
37
  },
36
38
  "repository": {