@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 +37 -1
- package/dist/index.d.mts +14 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.js +62 -9
- package/dist/index.mjs +59 -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
|
@@ -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 = ({
|
|
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
|
-
|
|
623
|
-
|
|
624
|
-
|
|
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 = ({
|
|
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
|
-
|
|
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 {
|
|
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.
|
|
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
|
-
"
|
|
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
|
},
|