mirror of
https://github.com/vercel-labs/json-render.git
synced 2026-10-03 04:18:15 +08:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb72020a6c | ||
|
|
c0113b6d32 |
@@ -16,17 +16,20 @@ import { CopyButton } from "./copy-button";
|
||||
import { Toaster } from "./ui/sonner";
|
||||
import { Header } from "./header";
|
||||
import { Sheet, SheetContent, SheetTitle } from "./ui/sheet";
|
||||
import { JsonEditor } from "@visual-json/react";
|
||||
import type { JsonValue } from "@visual-json/react";
|
||||
import { PlaygroundRenderer } from "@/lib/render/renderer";
|
||||
import { playgroundCatalog } from "@/lib/render/catalog";
|
||||
import { buildCatalogDisplayData } from "@/lib/render/catalog-display";
|
||||
|
||||
type Tab = "json" | "nested" | "stream" | "catalog";
|
||||
type Tab = "json" | "nested" | "stream" | "catalog" | "visual";
|
||||
type RenderView = "preview" | "code";
|
||||
type MobileView =
|
||||
| "json"
|
||||
| "nested"
|
||||
| "stream"
|
||||
| "catalog"
|
||||
| "visual"
|
||||
| "preview"
|
||||
| "generated-code";
|
||||
|
||||
@@ -233,6 +236,20 @@ export function Playground() {
|
||||
[handleSubmit],
|
||||
);
|
||||
|
||||
const handleVisualChange = useCallback(
|
||||
(value: JsonValue) => {
|
||||
if (!selectedVersionId || isStreaming) return;
|
||||
setVersions((prev) =>
|
||||
prev.map((v) =>
|
||||
v.id === selectedVersionId
|
||||
? { ...v, tree: value as unknown as Spec }
|
||||
: v,
|
||||
),
|
||||
);
|
||||
},
|
||||
[selectedVersionId, isStreaming],
|
||||
);
|
||||
|
||||
const jsonCode = currentTree
|
||||
? JSON.stringify(currentTree, null, 2)
|
||||
: "// waiting...";
|
||||
@@ -479,31 +496,69 @@ ${jsx}
|
||||
? jsonCode
|
||||
: activeTab === "nested"
|
||||
? nestedCode
|
||||
: "";
|
||||
: activeTab === "visual"
|
||||
? jsonCode
|
||||
: "";
|
||||
|
||||
const codePane = (
|
||||
<div className="h-full flex flex-col border-t border-border">
|
||||
<div className="border-b border-border px-3 h-9 flex items-center gap-3">
|
||||
{(["json", "nested", "stream", "catalog"] as const).map((tab) => (
|
||||
<button
|
||||
key={tab}
|
||||
onClick={() => setActiveTab(tab)}
|
||||
className={`text-xs font-mono transition-colors ${
|
||||
activeTab === tab
|
||||
? "text-foreground"
|
||||
: "text-muted-foreground hover:text-foreground"
|
||||
}`}
|
||||
>
|
||||
{tab}
|
||||
</button>
|
||||
))}
|
||||
{(["json", "visual", "nested", "stream", "catalog"] as const).map(
|
||||
(tab) => (
|
||||
<button
|
||||
key={tab}
|
||||
onClick={() => setActiveTab(tab)}
|
||||
className={`text-xs font-mono transition-colors ${
|
||||
activeTab === tab
|
||||
? "text-foreground"
|
||||
: "text-muted-foreground hover:text-foreground"
|
||||
}`}
|
||||
>
|
||||
{tab}
|
||||
</button>
|
||||
),
|
||||
)}
|
||||
<div className="flex-1" />
|
||||
{activeTab !== "catalog" && (
|
||||
{activeTab !== "catalog" && activeTab !== "visual" && (
|
||||
<CopyButton text={copyText} className="text-muted-foreground" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 overflow-auto">
|
||||
{activeTab === "catalog" ? (
|
||||
{activeTab === "visual" ? (
|
||||
currentTree ? (
|
||||
<JsonEditor
|
||||
value={currentTree as unknown as JsonValue}
|
||||
onChange={handleVisualChange}
|
||||
readOnly={isStreaming}
|
||||
sidebarOpen={false}
|
||||
height="100%"
|
||||
className="h-full"
|
||||
style={
|
||||
{
|
||||
"--vj-bg": "var(--background)",
|
||||
"--vj-bg-panel": "var(--background)",
|
||||
"--vj-bg-hover": "var(--muted)",
|
||||
"--vj-bg-selected": "var(--primary)",
|
||||
"--vj-bg-selected-muted": "var(--muted)",
|
||||
"--vj-text": "var(--foreground)",
|
||||
"--vj-text-selected": "var(--primary-foreground)",
|
||||
"--vj-text-muted": "var(--muted-foreground)",
|
||||
"--vj-text-dim": "var(--muted-foreground)",
|
||||
"--vj-border": "var(--border)",
|
||||
"--vj-border-subtle": "var(--border)",
|
||||
"--vj-accent": "var(--primary)",
|
||||
"--vj-accent-muted": "var(--muted)",
|
||||
"--vj-input-bg": "var(--secondary)",
|
||||
"--vj-input-border": "var(--border)",
|
||||
} as React.CSSProperties
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<div className="text-muted-foreground/50 p-3 text-sm font-mono">
|
||||
{"// generate a spec to edit visually"}
|
||||
</div>
|
||||
)
|
||||
) : activeTab === "catalog" ? (
|
||||
<div className="h-full flex flex-col text-sm">
|
||||
<div className="flex items-center gap-3 px-3 h-9 border-b border-border">
|
||||
{(
|
||||
@@ -735,19 +790,21 @@ ${jsx}
|
||||
: 0}
|
||||
</button>
|
||||
{/* Code tabs */}
|
||||
{(["json", "nested", "stream", "catalog"] as const).map((tab) => (
|
||||
<button
|
||||
key={tab}
|
||||
onClick={() => setMobileView(tab)}
|
||||
className={`text-xs font-mono transition-colors shrink-0 ${
|
||||
mobileView === tab
|
||||
? "text-foreground"
|
||||
: "text-muted-foreground hover:text-foreground"
|
||||
}`}
|
||||
>
|
||||
{tab}
|
||||
</button>
|
||||
))}
|
||||
{(["json", "visual", "nested", "stream", "catalog"] as const).map(
|
||||
(tab) => (
|
||||
<button
|
||||
key={tab}
|
||||
onClick={() => setMobileView(tab)}
|
||||
className={`text-xs font-mono transition-colors shrink-0 ${
|
||||
mobileView === tab
|
||||
? "text-foreground"
|
||||
: "text-muted-foreground hover:text-foreground"
|
||||
}`}
|
||||
>
|
||||
{tab}
|
||||
</button>
|
||||
),
|
||||
)}
|
||||
<div className="flex-1" />
|
||||
{/* Preview / code toggle */}
|
||||
{[
|
||||
@@ -770,7 +827,41 @@ ${jsx}
|
||||
|
||||
{/* Main content area */}
|
||||
<div className="flex-1 min-h-0 overflow-auto">
|
||||
{mobileView === "catalog" ? (
|
||||
{mobileView === "visual" ? (
|
||||
currentTree ? (
|
||||
<JsonEditor
|
||||
value={currentTree as unknown as JsonValue}
|
||||
onChange={handleVisualChange}
|
||||
readOnly={isStreaming}
|
||||
sidebarOpen={false}
|
||||
height="100%"
|
||||
className="h-full"
|
||||
style={
|
||||
{
|
||||
"--vj-bg": "var(--background)",
|
||||
"--vj-bg-panel": "var(--background)",
|
||||
"--vj-bg-hover": "var(--muted)",
|
||||
"--vj-bg-selected": "var(--primary)",
|
||||
"--vj-bg-selected-muted": "var(--muted)",
|
||||
"--vj-text": "var(--foreground)",
|
||||
"--vj-text-selected": "var(--primary-foreground)",
|
||||
"--vj-text-muted": "var(--muted-foreground)",
|
||||
"--vj-text-dim": "var(--muted-foreground)",
|
||||
"--vj-border": "var(--border)",
|
||||
"--vj-border-subtle": "var(--border)",
|
||||
"--vj-accent": "var(--primary)",
|
||||
"--vj-accent-muted": "var(--muted)",
|
||||
"--vj-input-bg": "var(--secondary)",
|
||||
"--vj-input-border": "var(--border)",
|
||||
} as React.CSSProperties
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<div className="text-muted-foreground/50 p-3 text-sm font-mono">
|
||||
{"// generate a spec to edit visually"}
|
||||
</div>
|
||||
)
|
||||
) : mobileView === "catalog" ? (
|
||||
<div className="h-full flex flex-col text-sm">
|
||||
<div className="flex items-center gap-3 px-3 h-9 border-b border-border">
|
||||
{(
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"@upstash/redis": "^1.36.1",
|
||||
"@vercel/analytics": "^1.6.1",
|
||||
"@vercel/speed-insights": "^1.3.1",
|
||||
"@visual-json/react": "0.1.1",
|
||||
"ai": "^6.0.33",
|
||||
"bash-tool": "1.3.14",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
|
||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
Generated
+18
@@ -98,6 +98,9 @@ importers:
|
||||
'@vercel/speed-insights':
|
||||
specifier: ^1.3.1
|
||||
version: 1.3.1(next@16.1.1(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)
|
||||
'@visual-json/react':
|
||||
specifier: 0.1.1
|
||||
version: 0.1.1(react@19.2.3)
|
||||
ai:
|
||||
specifier: ^6.0.33
|
||||
version: 6.0.33(zod@4.3.5)
|
||||
@@ -5009,6 +5012,14 @@ packages:
|
||||
vue-router:
|
||||
optional: true
|
||||
|
||||
'@visual-json/core@0.1.1':
|
||||
resolution: {integrity: sha512-VLARpQnjJU1SnnYk1NiXScGfp2FyAwyZS2kA8hIJV5OzkaH2oS1dTHsaB8vOiyi14/L7jsbpA4g/EwzRWbxQmg==}
|
||||
|
||||
'@visual-json/react@0.1.1':
|
||||
resolution: {integrity: sha512-KCpFYmBhH5F+xctfvxxVX7mVZK/iwvDwh/ITNkGPzEKXTIJQ16fau+hrG7m7QoTAOxvsdhHtHoCUiEjNHXboUQ==}
|
||||
peerDependencies:
|
||||
react: ^18.0.0 || ^19.0.0
|
||||
|
||||
'@vitest/expect@4.0.17':
|
||||
resolution: {integrity: sha512-mEoqP3RqhKlbmUmntNDDCJeTDavDR+fVYkSOw8qRwJFaW/0/5zA9zFeTrHqNtcmwh6j26yMmwx2PqUDPzt5ZAQ==}
|
||||
|
||||
@@ -16551,6 +16562,13 @@ snapshots:
|
||||
next: 16.1.1(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
react: 19.2.3
|
||||
|
||||
'@visual-json/core@0.1.1': {}
|
||||
|
||||
'@visual-json/react@0.1.1(react@19.2.3)':
|
||||
dependencies:
|
||||
'@visual-json/core': 0.1.1
|
||||
react: 19.2.3
|
||||
|
||||
'@vitest/expect@4.0.17':
|
||||
dependencies:
|
||||
'@standard-schema/spec': 1.1.0
|
||||
|
||||
Reference in New Issue
Block a user