@toriistudio/v0-playground 0.1.0 → 0.1.2

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/README.md CHANGED
@@ -17,6 +17,22 @@ Perfect for prototyping components, sharing usage examples, or building your own
17
17
 
18
18
  ---
19
19
 
20
+ ## 📦 Peer Dependencies
21
+
22
+ To use `@toriistudio/v0-playground`, you’ll need to install the following peer dependencies:
23
+
24
+ ```bash
25
+ yarn add @radix-ui/react-label @radix-ui/react-select @radix-ui/react-slider @radix-ui/react-slot @radix-ui/react-switch class-variance-authority clsx lucide-react tailwind-merge tailwindcss-animate
26
+ ```
27
+
28
+ Or automate it with:
29
+
30
+ ```json
31
+ "scripts": {
32
+ "install:peers": "npm install $(node -p \"Object.keys(require('./package.json').peerDependencies).join(' ')\")"
33
+ }
34
+ ```
35
+
20
36
  ## 🚀 Installation
21
37
 
22
38
  Install the package and its peer dependencies:
@@ -74,6 +90,26 @@ export default function App() {
74
90
  - Prototype interfaces quickly with real data
75
91
  - Debug and test variants visually
76
92
 
77
- ## 🤙 License
93
+ ## 📄 License
78
94
 
79
95
  MIT
96
+
97
+ ## 🤝 Contributing
98
+
99
+ We welcome contributions!
100
+
101
+ If you'd like to improve the playground, add new features, or fix bugs:
102
+
103
+ 1. **Fork** this repository
104
+ 2. **Clone** your fork: `git clone https://github.com/your-username/v0-playground`
105
+ 3. **Install** dependencies: `yarn` or `npm install`
106
+ 4. Make your changes in a branch: `git checkout -b my-new-feature`
107
+ 5. **Push** your branch and open a pull request
108
+
109
+ Before submitting a PR:
110
+
111
+ - Run `yarn build` to ensure everything compiles
112
+ - Make sure the playground runs without errors (`yalc push` or `npm link` for local testing)
113
+ - Keep the code style clean and consistent
114
+
115
+ We’re excited to see what you’ll build 🛠️✨
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 {
@@ -41,5 +44,15 @@ declare const useControls: <T extends ControlsSchema>(schema: T, options?: {
41
44
  setValue: (key: string, value: any) => void;
42
45
  jsx: () => string;
43
46
  };
47
+ declare const useUrlSyncedControls: <T extends ControlsSchema>(schema: T, options?: {
48
+ componentName?: string;
49
+ }) => { [K in keyof T]: T[K] extends {
50
+ value: infer V;
51
+ } ? V : never; } & {
52
+ controls: Record<string, any>;
53
+ schema: ControlsSchema;
54
+ setValue: (key: string, value: any) => void;
55
+ jsx: () => string;
56
+ };
44
57
 
45
- export { type ControlType, type ControlsSchema, Playground, useControls };
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 {
@@ -41,5 +44,15 @@ declare const useControls: <T extends ControlsSchema>(schema: T, options?: {
41
44
  setValue: (key: string, value: any) => void;
42
45
  jsx: () => string;
43
46
  };
47
+ declare const useUrlSyncedControls: <T extends ControlsSchema>(schema: T, options?: {
48
+ componentName?: string;
49
+ }) => { [K in keyof T]: T[K] extends {
50
+ value: infer V;
51
+ } ? V : never; } & {
52
+ controls: Record<string, any>;
53
+ schema: ControlsSchema;
54
+ setValue: (key: string, value: any) => void;
55
+ jsx: () => string;
56
+ };
44
57
 
45
- export { type ControlType, type ControlsSchema, Playground, useControls };
58
+ export { type ControlType, ControlsProvider, type ControlsSchema, Playground, useControls, useUrlSyncedControls };
package/dist/index.js CHANGED
@@ -30,11 +30,16 @@ 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
- useControls: () => useControls
35
+ useControls: () => useControls,
36
+ useUrlSyncedControls: () => useUrlSyncedControls
35
37
  });
36
38
  module.exports = __toCommonJS(src_exports);
37
39
 
40
+ // src/components/Playground/Playground.tsx
41
+ var import_react5 = require("react");
42
+
38
43
  // src/context/ResizableLayout.tsx
39
44
  var import_react = require("react");
40
45
  var import_lucide_react = require("lucide-react");
@@ -47,7 +52,10 @@ var useResizableLayout = () => {
47
52
  if (!ctx) throw new Error("ResizableLayoutContext not found");
48
53
  return ctx;
49
54
  };
50
- var ResizableLayout = ({ children }) => {
55
+ var ResizableLayout = ({
56
+ children,
57
+ hideControls
58
+ }) => {
51
59
  const [leftPanelWidth, setLeftPanelWidth] = (0, import_react.useState)(25);
52
60
  const [isDesktop, setIsDesktop] = (0, import_react.useState)(false);
53
61
  const [isHydrated, setIsHydrated] = (0, import_react.useState)(false);
@@ -111,7 +119,7 @@ var ResizableLayout = ({ children }) => {
111
119
  className: "flex flex-col md:flex-row min-h-screen w-full overflow-hidden select-none",
112
120
  children: [
113
121
  children,
114
- isHydrated && isDesktop && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
122
+ isHydrated && isDesktop && !hideControls && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
115
123
  "div",
116
124
  {
117
125
  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",
@@ -135,6 +143,19 @@ var ResizableLayout = ({ children }) => {
135
143
 
136
144
  // src/context/ControlsContext.tsx
137
145
  var import_react2 = require("react");
146
+
147
+ // src/utils/getUrlParams.ts
148
+ var getUrlParams = () => {
149
+ if (typeof window === "undefined") return {};
150
+ const params = new URLSearchParams(window.location.search);
151
+ const entries = {};
152
+ for (const [key, value] of params.entries()) {
153
+ entries[key] = value;
154
+ }
155
+ return entries;
156
+ };
157
+
158
+ // src/context/ControlsContext.tsx
138
159
  var import_jsx_runtime2 = require("react/jsx-runtime");
139
160
  var ControlsContext = (0, import_react2.createContext)(null);
140
161
  var useControlsContext = () => {
@@ -204,11 +225,36 @@ var useControls = (schema, options) => {
204
225
  jsx: jsx11
205
226
  };
206
227
  };
228
+ var useUrlSyncedControls = (schema, options) => {
229
+ const urlParams = getUrlParams();
230
+ const mergedSchema = Object.fromEntries(
231
+ Object.entries(schema).map(([key, control]) => {
232
+ const urlValue = urlParams[key];
233
+ if (!urlValue || !("value" in control)) return [key, control];
234
+ const defaultValue = control.value;
235
+ let parsed = urlValue;
236
+ if (typeof defaultValue === "number") {
237
+ parsed = parseFloat(urlValue);
238
+ if (isNaN(parsed)) parsed = defaultValue;
239
+ } else if (typeof defaultValue === "boolean") {
240
+ parsed = urlValue === "true";
241
+ }
242
+ return [
243
+ key,
244
+ {
245
+ ...control,
246
+ value: parsed
247
+ }
248
+ ];
249
+ })
250
+ );
251
+ return useControls(mergedSchema, options);
252
+ };
207
253
 
208
254
  // src/components/PreviewContainer/PreviewContainer.tsx
209
255
  var import_react3 = require("react");
210
256
  var import_jsx_runtime3 = require("react/jsx-runtime");
211
- var PreviewContainer = ({ children }) => {
257
+ var PreviewContainer = ({ children, hideControls }) => {
212
258
  const { leftPanelWidth, isDesktop, isHydrated, containerRef } = useResizableLayout();
213
259
  const previewRef = (0, import_react3.useRef)(null);
214
260
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -216,7 +262,7 @@ var PreviewContainer = ({ children }) => {
216
262
  {
217
263
  ref: previewRef,
218
264
  className: "order-1 md:order-2 flex-1 bg-black overflow-auto flex items-center justify-center relative",
219
- style: isHydrated && isDesktop ? {
265
+ style: isHydrated && isDesktop && !hideControls ? {
220
266
  width: `${100 - leftPanelWidth}%`,
221
267
  marginLeft: `${leftPanelWidth}%`
222
268
  } : {},
@@ -618,14 +664,21 @@ var ControlPanel_default = ControlPanel;
618
664
 
619
665
  // src/components/Playground/Playground.tsx
620
666
  var import_jsx_runtime10 = require("react/jsx-runtime");
667
+ var NO_CONTROLS_PARAM = "nocontrols";
621
668
  function Playground({ children }) {
622
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ResizableLayout, { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(ControlsProvider, { children: [
623
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PreviewContainer_default, { children }),
624
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ControlPanel_default, {})
669
+ const hideControls = (0, import_react5.useMemo)(() => {
670
+ if (typeof window === "undefined") return false;
671
+ return new URLSearchParams(window.location.search).get(NO_CONTROLS_PARAM) === "true";
672
+ }, []);
673
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ResizableLayout, { hideControls, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(ControlsProvider, { children: [
674
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PreviewContainer_default, { hideControls, children }),
675
+ !hideControls && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ControlPanel_default, {})
625
676
  ] }) });
626
677
  }
627
678
  // Annotate the CommonJS export names for ESM import in node:
628
679
  0 && (module.exports = {
680
+ ControlsProvider,
629
681
  Playground,
630
- useControls
682
+ useControls,
683
+ useUrlSyncedControls
631
684
  });
package/dist/index.mjs CHANGED
@@ -1,3 +1,6 @@
1
+ // src/components/Playground/Playground.tsx
2
+ import { useMemo as useMemo3 } from "react";
3
+
1
4
  // src/context/ResizableLayout.tsx
2
5
  import {
3
6
  createContext,
@@ -16,7 +19,10 @@ var useResizableLayout = () => {
16
19
  if (!ctx) throw new Error("ResizableLayoutContext not found");
17
20
  return ctx;
18
21
  };
19
- var ResizableLayout = ({ children }) => {
22
+ var ResizableLayout = ({
23
+ children,
24
+ hideControls
25
+ }) => {
20
26
  const [leftPanelWidth, setLeftPanelWidth] = useState(25);
21
27
  const [isDesktop, setIsDesktop] = useState(false);
22
28
  const [isHydrated, setIsHydrated] = useState(false);
@@ -80,7 +86,7 @@ var ResizableLayout = ({ children }) => {
80
86
  className: "flex flex-col md:flex-row min-h-screen w-full overflow-hidden select-none",
81
87
  children: [
82
88
  children,
83
- isHydrated && isDesktop && /* @__PURE__ */ jsx(
89
+ isHydrated && isDesktop && !hideControls && /* @__PURE__ */ jsx(
84
90
  "div",
85
91
  {
86
92
  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",
@@ -111,6 +117,19 @@ import {
111
117
  useEffect as useEffect2,
112
118
  useCallback
113
119
  } from "react";
120
+
121
+ // src/utils/getUrlParams.ts
122
+ var getUrlParams = () => {
123
+ if (typeof window === "undefined") return {};
124
+ const params = new URLSearchParams(window.location.search);
125
+ const entries = {};
126
+ for (const [key, value] of params.entries()) {
127
+ entries[key] = value;
128
+ }
129
+ return entries;
130
+ };
131
+
132
+ // src/context/ControlsContext.tsx
114
133
  import { jsx as jsx2 } from "react/jsx-runtime";
115
134
  var ControlsContext = createContext2(null);
116
135
  var useControlsContext = () => {
@@ -180,11 +199,36 @@ var useControls = (schema, options) => {
180
199
  jsx: jsx11
181
200
  };
182
201
  };
202
+ var useUrlSyncedControls = (schema, options) => {
203
+ const urlParams = getUrlParams();
204
+ const mergedSchema = Object.fromEntries(
205
+ Object.entries(schema).map(([key, control]) => {
206
+ const urlValue = urlParams[key];
207
+ if (!urlValue || !("value" in control)) return [key, control];
208
+ const defaultValue = control.value;
209
+ let parsed = urlValue;
210
+ if (typeof defaultValue === "number") {
211
+ parsed = parseFloat(urlValue);
212
+ if (isNaN(parsed)) parsed = defaultValue;
213
+ } else if (typeof defaultValue === "boolean") {
214
+ parsed = urlValue === "true";
215
+ }
216
+ return [
217
+ key,
218
+ {
219
+ ...control,
220
+ value: parsed
221
+ }
222
+ ];
223
+ })
224
+ );
225
+ return useControls(mergedSchema, options);
226
+ };
183
227
 
184
228
  // src/components/PreviewContainer/PreviewContainer.tsx
185
229
  import { useRef as useRef2 } from "react";
186
230
  import { jsx as jsx3 } from "react/jsx-runtime";
187
- var PreviewContainer = ({ children }) => {
231
+ var PreviewContainer = ({ children, hideControls }) => {
188
232
  const { leftPanelWidth, isDesktop, isHydrated, containerRef } = useResizableLayout();
189
233
  const previewRef = useRef2(null);
190
234
  return /* @__PURE__ */ jsx3(
@@ -192,7 +236,7 @@ var PreviewContainer = ({ children }) => {
192
236
  {
193
237
  ref: previewRef,
194
238
  className: "order-1 md:order-2 flex-1 bg-black overflow-auto flex items-center justify-center relative",
195
- style: isHydrated && isDesktop ? {
239
+ style: isHydrated && isDesktop && !hideControls ? {
196
240
  width: `${100 - leftPanelWidth}%`,
197
241
  marginLeft: `${leftPanelWidth}%`
198
242
  } : {},
@@ -594,13 +638,20 @@ var ControlPanel_default = ControlPanel;
594
638
 
595
639
  // src/components/Playground/Playground.tsx
596
640
  import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
641
+ var NO_CONTROLS_PARAM = "nocontrols";
597
642
  function Playground({ children }) {
598
- return /* @__PURE__ */ jsx10(ResizableLayout, { children: /* @__PURE__ */ jsxs5(ControlsProvider, { children: [
599
- /* @__PURE__ */ jsx10(PreviewContainer_default, { children }),
600
- /* @__PURE__ */ jsx10(ControlPanel_default, {})
643
+ const hideControls = useMemo3(() => {
644
+ if (typeof window === "undefined") return false;
645
+ return new URLSearchParams(window.location.search).get(NO_CONTROLS_PARAM) === "true";
646
+ }, []);
647
+ return /* @__PURE__ */ jsx10(ResizableLayout, { hideControls, children: /* @__PURE__ */ jsxs5(ControlsProvider, { children: [
648
+ /* @__PURE__ */ jsx10(PreviewContainer_default, { hideControls, children }),
649
+ !hideControls && /* @__PURE__ */ jsx10(ControlPanel_default, {})
601
650
  ] }) });
602
651
  }
603
652
  export {
653
+ ControlsProvider,
604
654
  Playground,
605
- useControls
655
+ useControls,
656
+ useUrlSyncedControls
606
657
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toriistudio/v0-playground",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "V0 Playground",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",
@@ -29,7 +29,7 @@
29
29
  ],
30
30
  "scripts": {
31
31
  "build": "tsup",
32
- "dev:push": "yarn build && yalc push",
32
+ "local:push": "yarn build && yalc push",
33
33
  "prepublishOnly": "npm run build",
34
34
  "test": "echo \"Error: no test specified\" && exit 1"
35
35
  },