fix(ui): handle fallback UI for design systems without visual previews
This commit is contained in:
@@ -12,19 +12,57 @@ export async function GET(
|
||||
return new NextResponse("Invalid design system ID", { status: 400 });
|
||||
}
|
||||
|
||||
const htmlPath = path.join(
|
||||
const systemPath = path.join(
|
||||
process.cwd(),
|
||||
"lib",
|
||||
"scaffold",
|
||||
"open-design",
|
||||
"design-systems",
|
||||
id,
|
||||
"components.html"
|
||||
id
|
||||
);
|
||||
|
||||
const htmlPath = path.join(systemPath, "components.html");
|
||||
|
||||
try {
|
||||
if (!fs.existsSync(systemPath)) {
|
||||
return new NextResponse(`Design system not found for ${id}`, { status: 404 });
|
||||
}
|
||||
|
||||
if (!fs.existsSync(htmlPath)) {
|
||||
return new NextResponse(`Preview not found for ${id}`, { status: 404 });
|
||||
// If there's no components.html, serve a beautiful fallback UI
|
||||
// that explains this system only has Markdown guidelines for the AI
|
||||
const fallbackHtml = `
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>${id} — No preview available</title>
|
||||
<style>
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; background: #fbfaf9; color: #52525b; text-align: center; }
|
||||
.card { background: white; padding: 40px; border-radius: 12px; border: 1px solid #e4e4e7; box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.05); max-width: 400px; }
|
||||
h2 { color: #18181b; margin-top: 0; }
|
||||
p { line-height: 1.5; font-size: 14px; }
|
||||
.badge { display: inline-block; background: #e0e7ff; color: #4338ca; padding: 4px 8px; border-radius: 6px; font-size: 12px; font-weight: 500; font-family: monospace; margin-top: 16px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h2>Preview Not Available</h2>
|
||||
<p>The <strong>${id}</strong> design system is a "Rules-Only" system.</p>
|
||||
<p>It provides strict typographic, spacing, and layout instructions to the AI via its <code>DESIGN.md</code>, but it doesn't currently ship with a visual HTML preview gallery.</p>
|
||||
<div class="badge">AI Guidelines Active</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
return new NextResponse(fallbackHtml, {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "text/html; charset=utf-8",
|
||||
"X-Frame-Options": "SAMEORIGIN",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const html = await fs.promises.readFile(htmlPath, "utf-8");
|
||||
@@ -33,7 +71,6 @@ export async function GET(
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "text/html; charset=utf-8",
|
||||
// Allow framing only from same origin for security
|
||||
"X-Frame-Options": "SAMEORIGIN",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { Loader2, Check, Search, Eye, Sparkles } from "lucide-react";
|
||||
import { Loader2, Check, Search, Eye, Sparkles, LayoutTemplate } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { STARTER_KITS } from "@/lib/design-kits/registry";
|
||||
import { DEFAULT_DESIGN_KIT_ID } from "@/lib/design-kits/types";
|
||||
@@ -88,7 +88,7 @@ export function DesignSystemExplorer() {
|
||||
<div className="flex-1 bg-[#fbfaf9] h-full flex flex-col md:flex-row overflow-hidden">
|
||||
|
||||
{/* LEFT SIDEBAR: Library & Search */}
|
||||
<div className="w-full md:w-80 flex-shrink-0 border-r border-zinc-200 bg-white flex flex-col h-full overflow-hidden">
|
||||
<div className="w-full md:w-80 flex-shrink-0 border-r border-zinc-200 bg-white flex flex-col h-full overflow-hidden z-20">
|
||||
<div className="p-4 border-b border-zinc-200">
|
||||
<h2 className="text-sm font-semibold text-zinc-900 mb-3 flex items-center gap-2">
|
||||
<Sparkles className="w-4 h-4 text-indigo-600" />
|
||||
@@ -114,7 +114,7 @@ export function DesignSystemExplorer() {
|
||||
<button
|
||||
key={kit.id}
|
||||
onClick={() => setPreviewKitId(kit.id)}
|
||||
className={`w-full text-left p-3 rounded-lg flex items-start gap-3 transition-colors ${
|
||||
className={`w-full text-left p-3 rounded-lg flex items-start gap-3 transition-colors group relative ${
|
||||
isSelected ? 'bg-indigo-50/80 ring-1 ring-indigo-500/30' : 'hover:bg-zinc-50'
|
||||
}`}
|
||||
>
|
||||
@@ -127,7 +127,7 @@ export function DesignSystemExplorer() {
|
||||
}`}>
|
||||
{isCurrentlyActive && <Check className="w-2.5 h-2.5 text-white" />}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex-1 min-w-0 pr-6">
|
||||
<div className={`text-sm font-medium truncate ${isSelected ? 'text-indigo-950' : 'text-zinc-900'}`}>
|
||||
{kit.name}
|
||||
</div>
|
||||
@@ -135,6 +135,12 @@ export function DesignSystemExplorer() {
|
||||
{kit.tagline || kit.id}
|
||||
</div>
|
||||
</div>
|
||||
{/* Visual Indicator if it has an HTML preview */}
|
||||
{kit.hasPreview && (
|
||||
<div className="absolute right-3 top-3.5 text-zinc-300 group-hover:text-indigo-400 transition-colors" title="Visual Preview Available">
|
||||
<LayoutTemplate className="w-3.5 h-3.5" />
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
@@ -152,8 +158,11 @@ export function DesignSystemExplorer() {
|
||||
<div className="h-14 flex-shrink-0 bg-white border-b border-zinc-200 px-6 flex items-center justify-between shadow-sm z-10">
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
<Eye className="w-4 h-4 text-zinc-400" />
|
||||
<span className="text-sm font-medium text-zinc-900 truncate">
|
||||
<span className="text-sm font-medium text-zinc-900 truncate flex items-center gap-2">
|
||||
Previewing: {previewKit.name}
|
||||
{!previewKit.hasPreview && (
|
||||
<span className="px-2 py-0.5 rounded-full bg-zinc-100 text-zinc-500 text-[10px] font-mono tracking-wide uppercase border border-zinc-200">Rules Only</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -162,8 +171,8 @@ export function DesignSystemExplorer() {
|
||||
disabled={saving || isActive}
|
||||
className={`px-4 py-1.5 rounded-md text-sm font-medium transition-all flex items-center gap-2 ${
|
||||
isActive
|
||||
? 'bg-zinc-100 text-zinc-400 cursor-default'
|
||||
: 'bg-indigo-600 hover:bg-indigo-700 text-white shadow-sm'
|
||||
? 'bg-zinc-100 text-zinc-400 cursor-default shadow-inner'
|
||||
: 'bg-indigo-600 hover:bg-indigo-700 text-white shadow-sm hover:shadow-md'
|
||||
}`}
|
||||
>
|
||||
{saving && <Loader2 className="w-3.5 h-3.5 animate-spin" />}
|
||||
@@ -172,22 +181,22 @@ export function DesignSystemExplorer() {
|
||||
</div>
|
||||
|
||||
{/* Live Iframe */}
|
||||
<div className="flex-1 relative bg-white m-4 rounded-xl border border-zinc-200 shadow-sm overflow-hidden flex flex-col">
|
||||
<div className="flex-1 relative bg-white m-4 rounded-xl border border-zinc-200 shadow-sm overflow-hidden flex flex-col transition-all">
|
||||
<div className="h-8 bg-zinc-50 border-b border-zinc-200 flex items-center px-4 gap-2 flex-shrink-0">
|
||||
<div className="flex gap-1.5">
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-red-400/80"></div>
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-amber-400/80"></div>
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-green-400/80"></div>
|
||||
</div>
|
||||
<div className="mx-auto bg-white px-3 py-0.5 rounded-md border border-zinc-200 text-[10px] text-zinc-400 font-mono">
|
||||
<div className="mx-auto bg-white px-3 py-0.5 rounded-md border border-zinc-200 text-[10px] text-zinc-400 font-mono shadow-sm">
|
||||
{previewKit.id}.design.preview
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<iframe
|
||||
key={previewKit.id} // Forces iframe to remount and show loading state if needed
|
||||
key={previewKit.id} // Forces iframe to remount
|
||||
src={`/api/design-systems/${previewKit.id}/preview`}
|
||||
className="flex-1 w-full h-full bg-white"
|
||||
className="flex-1 w-full h-full bg-zinc-50"
|
||||
sandbox="allow-scripts allow-same-origin"
|
||||
/>
|
||||
</div>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -40,6 +40,7 @@ export interface DesignSystemDefinition {
|
||||
id: string;
|
||||
name: string;
|
||||
tagline: string;
|
||||
hasPreview?: boolean;
|
||||
defaults: DesignKitOverrides;
|
||||
/** Customize panel fields shown for this starter */
|
||||
customizeFields: Array<"accent" | "radius" | "font" | "density">;
|
||||
|
||||
Reference in New Issue
Block a user