@toriistudio/v0-playground 0.1.0 → 0.1.1
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 +37 -1
- package/dist/index.d.mts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +60 -9
- package/dist/index.mjs +58 -8
- package/package.json +2 -2
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
|
-
##
|
|
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
|
@@ -41,5 +41,15 @@ declare const useControls: <T extends ControlsSchema>(schema: T, options?: {
|
|
|
41
41
|
setValue: (key: string, value: any) => void;
|
|
42
42
|
jsx: () => string;
|
|
43
43
|
};
|
|
44
|
+
declare const useUrlSyncedControls: <T extends ControlsSchema>(schema: T, options?: {
|
|
45
|
+
componentName?: string;
|
|
46
|
+
}) => { [K in keyof T]: T[K] extends {
|
|
47
|
+
value: infer V;
|
|
48
|
+
} ? V : never; } & {
|
|
49
|
+
controls: Record<string, any>;
|
|
50
|
+
schema: ControlsSchema;
|
|
51
|
+
setValue: (key: string, value: any) => void;
|
|
52
|
+
jsx: () => string;
|
|
53
|
+
};
|
|
44
54
|
|
|
45
|
-
export { type ControlType, type ControlsSchema, Playground, useControls };
|
|
55
|
+
export { type ControlType, type ControlsSchema, Playground, useControls, useUrlSyncedControls };
|
package/dist/index.d.ts
CHANGED
|
@@ -41,5 +41,15 @@ declare const useControls: <T extends ControlsSchema>(schema: T, options?: {
|
|
|
41
41
|
setValue: (key: string, value: any) => void;
|
|
42
42
|
jsx: () => string;
|
|
43
43
|
};
|
|
44
|
+
declare const useUrlSyncedControls: <T extends ControlsSchema>(schema: T, options?: {
|
|
45
|
+
componentName?: string;
|
|
46
|
+
}) => { [K in keyof T]: T[K] extends {
|
|
47
|
+
value: infer V;
|
|
48
|
+
} ? V : never; } & {
|
|
49
|
+
controls: Record<string, any>;
|
|
50
|
+
schema: ControlsSchema;
|
|
51
|
+
setValue: (key: string, value: any) => void;
|
|
52
|
+
jsx: () => string;
|
|
53
|
+
};
|
|
44
54
|
|
|
45
|
-
export { type ControlType, type ControlsSchema, Playground, useControls };
|
|
55
|
+
export { type ControlType, type ControlsSchema, Playground, useControls, useUrlSyncedControls };
|
package/dist/index.js
CHANGED
|
@@ -31,10 +31,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var src_exports = {};
|
|
32
32
|
__export(src_exports, {
|
|
33
33
|
Playground: () => Playground,
|
|
34
|
-
useControls: () => useControls
|
|
34
|
+
useControls: () => useControls,
|
|
35
|
+
useUrlSyncedControls: () => useUrlSyncedControls
|
|
35
36
|
});
|
|
36
37
|
module.exports = __toCommonJS(src_exports);
|
|
37
38
|
|
|
39
|
+
// src/components/Playground/Playground.tsx
|
|
40
|
+
var import_react5 = require("react");
|
|
41
|
+
|
|
38
42
|
// src/context/ResizableLayout.tsx
|
|
39
43
|
var import_react = require("react");
|
|
40
44
|
var import_lucide_react = require("lucide-react");
|
|
@@ -47,7 +51,10 @@ var useResizableLayout = () => {
|
|
|
47
51
|
if (!ctx) throw new Error("ResizableLayoutContext not found");
|
|
48
52
|
return ctx;
|
|
49
53
|
};
|
|
50
|
-
var ResizableLayout = ({
|
|
54
|
+
var ResizableLayout = ({
|
|
55
|
+
children,
|
|
56
|
+
hideControls
|
|
57
|
+
}) => {
|
|
51
58
|
const [leftPanelWidth, setLeftPanelWidth] = (0, import_react.useState)(25);
|
|
52
59
|
const [isDesktop, setIsDesktop] = (0, import_react.useState)(false);
|
|
53
60
|
const [isHydrated, setIsHydrated] = (0, import_react.useState)(false);
|
|
@@ -111,7 +118,7 @@ var ResizableLayout = ({ children }) => {
|
|
|
111
118
|
className: "flex flex-col md:flex-row min-h-screen w-full overflow-hidden select-none",
|
|
112
119
|
children: [
|
|
113
120
|
children,
|
|
114
|
-
isHydrated && isDesktop && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
121
|
+
isHydrated && isDesktop && !hideControls && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
115
122
|
"div",
|
|
116
123
|
{
|
|
117
124
|
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 +142,19 @@ var ResizableLayout = ({ children }) => {
|
|
|
135
142
|
|
|
136
143
|
// src/context/ControlsContext.tsx
|
|
137
144
|
var import_react2 = require("react");
|
|
145
|
+
|
|
146
|
+
// src/utils/getUrlParams.ts
|
|
147
|
+
var getUrlParams = () => {
|
|
148
|
+
if (typeof window === "undefined") return {};
|
|
149
|
+
const params = new URLSearchParams(window.location.search);
|
|
150
|
+
const entries = {};
|
|
151
|
+
for (const [key, value] of params.entries()) {
|
|
152
|
+
entries[key] = value;
|
|
153
|
+
}
|
|
154
|
+
return entries;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// src/context/ControlsContext.tsx
|
|
138
158
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
139
159
|
var ControlsContext = (0, import_react2.createContext)(null);
|
|
140
160
|
var useControlsContext = () => {
|
|
@@ -204,11 +224,36 @@ var useControls = (schema, options) => {
|
|
|
204
224
|
jsx: jsx11
|
|
205
225
|
};
|
|
206
226
|
};
|
|
227
|
+
var useUrlSyncedControls = (schema, options) => {
|
|
228
|
+
const urlParams = getUrlParams();
|
|
229
|
+
const mergedSchema = Object.fromEntries(
|
|
230
|
+
Object.entries(schema).map(([key, control]) => {
|
|
231
|
+
const urlValue = urlParams[key];
|
|
232
|
+
if (!urlValue || !("value" in control)) return [key, control];
|
|
233
|
+
const defaultValue = control.value;
|
|
234
|
+
let parsed = urlValue;
|
|
235
|
+
if (typeof defaultValue === "number") {
|
|
236
|
+
parsed = parseFloat(urlValue);
|
|
237
|
+
if (isNaN(parsed)) parsed = defaultValue;
|
|
238
|
+
} else if (typeof defaultValue === "boolean") {
|
|
239
|
+
parsed = urlValue === "true";
|
|
240
|
+
}
|
|
241
|
+
return [
|
|
242
|
+
key,
|
|
243
|
+
{
|
|
244
|
+
...control,
|
|
245
|
+
value: parsed
|
|
246
|
+
}
|
|
247
|
+
];
|
|
248
|
+
})
|
|
249
|
+
);
|
|
250
|
+
return useControls(mergedSchema, options);
|
|
251
|
+
};
|
|
207
252
|
|
|
208
253
|
// src/components/PreviewContainer/PreviewContainer.tsx
|
|
209
254
|
var import_react3 = require("react");
|
|
210
255
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
211
|
-
var PreviewContainer = ({ children }) => {
|
|
256
|
+
var PreviewContainer = ({ children, hideControls }) => {
|
|
212
257
|
const { leftPanelWidth, isDesktop, isHydrated, containerRef } = useResizableLayout();
|
|
213
258
|
const previewRef = (0, import_react3.useRef)(null);
|
|
214
259
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
@@ -216,7 +261,7 @@ var PreviewContainer = ({ children }) => {
|
|
|
216
261
|
{
|
|
217
262
|
ref: previewRef,
|
|
218
263
|
className: "order-1 md:order-2 flex-1 bg-black overflow-auto flex items-center justify-center relative",
|
|
219
|
-
style: isHydrated && isDesktop ? {
|
|
264
|
+
style: isHydrated && isDesktop && !hideControls ? {
|
|
220
265
|
width: `${100 - leftPanelWidth}%`,
|
|
221
266
|
marginLeft: `${leftPanelWidth}%`
|
|
222
267
|
} : {},
|
|
@@ -618,14 +663,20 @@ var ControlPanel_default = ControlPanel;
|
|
|
618
663
|
|
|
619
664
|
// src/components/Playground/Playground.tsx
|
|
620
665
|
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
666
|
+
var NO_CONTROLS_PARAM = "nocontrols";
|
|
621
667
|
function Playground({ children }) {
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
668
|
+
const hideControls = (0, import_react5.useMemo)(() => {
|
|
669
|
+
if (typeof window === "undefined") return false;
|
|
670
|
+
return new URLSearchParams(window.location.search).get(NO_CONTROLS_PARAM) === "true";
|
|
671
|
+
}, []);
|
|
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, {})
|
|
625
675
|
] }) });
|
|
626
676
|
}
|
|
627
677
|
// Annotate the CommonJS export names for ESM import in node:
|
|
628
678
|
0 && (module.exports = {
|
|
629
679
|
Playground,
|
|
630
|
-
useControls
|
|
680
|
+
useControls,
|
|
681
|
+
useUrlSyncedControls
|
|
631
682
|
});
|
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 = ({
|
|
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,19 @@ 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
|
-
|
|
599
|
-
|
|
600
|
-
|
|
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 {
|
|
604
653
|
Playground,
|
|
605
|
-
useControls
|
|
654
|
+
useControls,
|
|
655
|
+
useUrlSyncedControls
|
|
606
656
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toriistudio/v0-playground",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
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
|
-
"
|
|
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
|
},
|