fix(ui): handle fallback UI for design systems without visual previews

This commit is contained in:
2026-05-15 14:00:01 -07:00
parent 3bed7ff3a9
commit 547d74ae44
4 changed files with 2511 additions and 2315 deletions

View File

@@ -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",
},
});

View File

@@ -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

View File

@@ -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">;