refactor(design): modularize scaffolds into per-surface files + unique admin

- Deleted monolithic design-scaffolds.tsx (1154 lines, 72KB)
- New folder: components/design-scaffolds/
  - types.ts       — ThemeColor interface + all theme palettes
  - web-app.tsx    — SaaS app: Dashboard / Users / Settings with AppShell
  - marketing.tsx  — Landing page: hero, features, pricing, CTA
  - admin.tsx      — NEW unique admin: System health (servers/CPU/mem/errors),
                     Moderation (user table + audit log + ban/impersonate),
                     Config (API keys, feature flags, webhooks)
  - mobile.tsx     — Phone frame previews: NativeWind / Gluestack
  - email.tsx      — React Email welcome template preview
  - docs.tsx       — Nextra + shadcn docs previews
  - index.ts       — SCAFFOLD_REGISTRY + THEME_REGISTRY (only import needed)
- Adding a new surface = create one file + add 2 lines to index.ts

Made-with: Cursor
This commit is contained in:
2026-03-05 19:54:38 -08:00
parent d30af447da
commit 57c283796f
9 changed files with 1315 additions and 1153 deletions

View File

@@ -0,0 +1,326 @@
"use client";
import { useState } from "react";
import { ThemeColor, TABLE_ROWS, MANTINE_THEMES, SHADCN_THEMES, TREMOR_THEMES } from "./types";
// Admin Panel surface — internal tool for operating the business.
// Context: the ops/support team, not end users.
// Shows: system health, user moderation, API config — NOT business metrics.
type AdminPage = "System" | "Moderation" | "Config";
const SERVERS = [
{ name: "api-prod-01", region: "us-east-1", status: "healthy", cpu: 34, mem: 58, uptime: "99.98%" },
{ name: "api-prod-02", region: "us-east-1", status: "healthy", cpu: 41, mem: 62, uptime: "99.97%" },
{ name: "worker-01", region: "eu-west-2", status: "warning", cpu: 89, mem: 71, uptime: "99.91%" },
{ name: "db-primary", region: "us-east-1", status: "healthy", cpu: 22, mem: 44, uptime: "100%" },
];
const AUDIT_LOG = [
{ user: "alice@co.com", action: "Banned user", target: "spammer@evil.com", time: "2m ago" },
{ user: "ben@co.com", action: "Promoted to Admin", target: "clara@co.com", time: "14m ago" },
{ user: "system", action: "Rate limit hit", target: "api/v1/search", time: "31m ago" },
{ user: "david@co.com", action: "Reset password", target: "eve@co.com", time: "1h ago" },
];
function StatusDot({ status }: { status: string }) {
const color = status === "healthy" ? "#22c55e" : status === "warning" ? "#f59e0b" : "#ef4444";
return <div style={{ width: 7, height: 7, borderRadius: "50%", background: color, flexShrink: 0 }} />;
}
function MiniBar({ value, color }: { value: number; color: string }) {
const bg = value > 80 ? "#ef444430" : "#f3f4f6";
const fill = value > 80 ? "#ef4444" : color;
return (
<div style={{ width: 52, height: 5, borderRadius: 3, background: bg, overflow: "hidden" }}>
<div style={{ width: `${value}%`, height: "100%", background: fill, borderRadius: 3 }} />
</div>
);
}
// ---------------------------------------------------------------------------
// Admin shell — denser than app shell, breadcrumb + secondary nav
// ---------------------------------------------------------------------------
function AdminShell({ t, page, onNav, children }: {
t: ThemeColor; page: AdminPage; onNav: (p: AdminPage) => void; children: React.ReactNode;
}) {
const NAV: { label: AdminPage; icon: string; badge?: string }[] = [
{ label: "System", icon: "◈", badge: "1 warn" },
{ label: "Moderation", icon: "◉" },
{ label: "Config", icon: "⚙" },
];
return (
<div style={{ display: "flex", height: "100%", fontFamily: "system-ui, sans-serif", fontSize: 12 }}>
{/* Left nav */}
<div style={{ width: 160, flexShrink: 0, background: "#111827", display: "flex", flexDirection: "column", padding: "12px 8px" }}>
<div style={{ display: "flex", alignItems: "center", gap: 7, padding: "0 6px", marginBottom: 20 }}>
<div style={{ width: 20, height: 20, borderRadius: 4, background: t.primary, flexShrink: 0 }} />
<span style={{ fontWeight: 700, fontSize: 10, color: "#f9fafb" }}>Acme Admin</span>
</div>
<div style={{ fontSize: 9, fontWeight: 600, color: "#6b7280", textTransform: "uppercase", letterSpacing: "0.08em", padding: "0 6px", marginBottom: 6 }}>Operations</div>
{NAV.map(({ label, icon, badge }) => (
<button key={label} onClick={() => onNav(label)} style={{
display: "flex", alignItems: "center", gap: 7, width: "100%",
padding: "7px 8px", borderRadius: 6, border: "none", cursor: "pointer",
background: page === label ? `${t.primary}22` : "none",
color: page === label ? t.primary : "#9ca3af",
fontSize: 11, fontWeight: page === label ? 600 : 400,
textAlign: "left", marginBottom: 2,
}}>
<span style={{ fontSize: 10 }}>{icon}</span>
<span style={{ flex: 1 }}>{label}</span>
{badge && <span style={{ fontSize: 9, background: "#f59e0b22", color: "#f59e0b", padding: "1px 5px", borderRadius: 4, fontWeight: 600 }}>{badge}</span>}
</button>
))}
</div>
{/* Main */}
<div style={{ flex: 1, display: "flex", flexDirection: "column", minWidth: 0 }}>
{/* Top bar */}
<div style={{ height: 40, background: "#fff", borderBottom: "1px solid #e5e7eb", display: "flex", alignItems: "center", justifyContent: "space-between", padding: "0 16px", flexShrink: 0 }}>
<div style={{ display: "flex", alignItems: "center", gap: 6, fontSize: 11, color: "#9ca3af" }}>
<span>Admin</span><span>/</span>
<span style={{ color: "#111827", fontWeight: 600 }}>{page}</span>
</div>
<div style={{ display: "flex", alignItems: "center", gap: 10 }}>
<span style={{ fontSize: 10, fontFamily: "monospace", background: "#f9fafb", border: "1px solid #e5e7eb", padding: "2px 8px", borderRadius: 4, color: "#6b7280" }}>v2.4.1-prod</span>
<div style={{ width: 24, height: 24, borderRadius: "50%", background: t.primary, display: "flex", alignItems: "center", justifyContent: "center", fontSize: 9, fontWeight: 700, color: "#fff" }}>A</div>
</div>
</div>
<div style={{ flex: 1, overflow: "auto", background: "#f9fafb" }}>
{children}
</div>
</div>
</div>
);
}
// ---------------------------------------------------------------------------
// System tab — server health, error rate, uptime
// ---------------------------------------------------------------------------
function SystemView({ t }: { t: ThemeColor }) {
return (
<div style={{ padding: 16 }}>
{/* Summary row */}
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr", gap: 8, marginBottom: 12 }}>
{[
{ l: "Uptime", v: "99.96%", good: true },
{ l: "Error rate", v: "0.12%", good: true },
{ l: "Avg latency", v: "84ms", good: true },
{ l: "Alerts", v: "1 active",good: false },
].map(s => (
<div key={s.l} style={{ background: "#fff", border: `1px solid ${s.good ? "#e5e7eb" : "#fde68a"}`, borderRadius: 8, padding: "10px 12px" }}>
<p style={{ fontSize: 9, color: "#6b7280", marginBottom: 3 }}>{s.l}</p>
<p style={{ fontSize: 15, fontWeight: 700, color: s.good ? "#111827" : "#d97706" }}>{s.v}</p>
</div>
))}
</div>
{/* Server table */}
<div style={{ background: "#fff", border: "1px solid #e5e7eb", borderRadius: 8 }}>
<div style={{ padding: "9px 14px", borderBottom: "1px solid #f3f4f6", fontSize: 11, fontWeight: 600, color: "#111827", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<span>Servers</span>
<span style={{ fontSize: 9, color: "#22c55e" }}> Live</span>
</div>
<div>
{SERVERS.map(s => (
<div key={s.name} style={{ display: "flex", alignItems: "center", padding: "9px 14px", borderBottom: "1px solid #f9fafb", gap: 10 }}>
<StatusDot status={s.status} />
<div style={{ flex: 1 }}>
<p style={{ fontSize: 10, fontWeight: 600, color: "#111827", fontFamily: "monospace" }}>{s.name}</p>
<p style={{ fontSize: 9, color: "#9ca3af" }}>{s.region}</p>
</div>
<div style={{ display: "flex", flex: "column", gap: 3, alignItems: "flex-end" }}>
<div style={{ display: "flex", alignItems: "center", gap: 5 }}>
<span style={{ fontSize: 9, color: "#6b7280", width: 22 }}>CPU</span>
<MiniBar value={s.cpu} color={t.primary} />
<span style={{ fontSize: 9, color: s.cpu > 80 ? "#ef4444" : "#374151", width: 24, textAlign: "right" }}>{s.cpu}%</span>
</div>
<div style={{ display: "flex", alignItems: "center", gap: 5 }}>
<span style={{ fontSize: 9, color: "#6b7280", width: 22 }}>MEM</span>
<MiniBar value={s.mem} color={t.primary} />
<span style={{ fontSize: 9, color: s.mem > 80 ? "#ef4444" : "#374151", width: 24, textAlign: "right" }}>{s.mem}%</span>
</div>
</div>
<span style={{ fontSize: 9, fontFamily: "monospace", color: "#22c55e" }}>{s.uptime}</span>
</div>
))}
</div>
</div>
{/* Error timeline */}
<div style={{ background: "#fff", border: "1px solid #e5e7eb", borderRadius: 8, marginTop: 10 }}>
<div style={{ padding: "9px 14px", borderBottom: "1px solid #f3f4f6", fontSize: 11, fontWeight: 600, color: "#111827" }}>Error rate last 24h</div>
<div style={{ padding: "12px 14px", display: "flex", alignItems: "flex-end", gap: 3, height: 56 }}>
{[2,1,3,2,1,1,0,0,1,2,8,3,2,1,0,1,2,1,1,0,1,0,1,2].map((h, i) => (
<div key={i} style={{ flex: 1, borderRadius: 2, background: h > 5 ? "#ef4444" : h > 2 ? "#f59e0b" : t.activeBg, height: `${Math.max(h * 10, 3)}%`, minHeight: 2 }} />
))}
</div>
</div>
</div>
);
}
// ---------------------------------------------------------------------------
// Moderation tab — user management with admin actions
// ---------------------------------------------------------------------------
function ModerationView({ t }: { t: ThemeColor }) {
return (
<div style={{ padding: 16 }}>
{/* User table with actions */}
<div style={{ background: "#fff", border: "1px solid #e5e7eb", borderRadius: 8, marginBottom: 10 }}>
<div style={{ padding: "9px 14px", borderBottom: "1px solid #f3f4f6", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<span style={{ fontSize: 11, fontWeight: 600, color: "#111827" }}>User management</span>
<div style={{ display: "flex", gap: 6 }}>
<input style={{ height: 26, padding: "0 9px", border: "1px solid #e5e7eb", borderRadius: 5, fontSize: 10, outline: "none", width: 110 }} placeholder="Search users..." />
<select style={{ height: 26, padding: "0 8px", border: "1px solid #e5e7eb", borderRadius: 5, fontSize: 10, background: "#fff", color: "#374151" }}>
<option>All statuses</option>
</select>
</div>
</div>
{TABLE_ROWS.map(r => (
<div key={r.name} style={{ display: "flex", alignItems: "center", padding: "9px 14px", borderBottom: "1px solid #f9fafb", gap: 10 }}>
<div style={{ width: 26, height: 26, borderRadius: "50%", background: t.activeBg, display: "flex", alignItems: "center", justifyContent: "center", fontSize: 10, fontWeight: 700, color: t.activeFg, flexShrink: 0 }}>
{r.name[0]}
</div>
<div style={{ flex: 1 }}>
<p style={{ fontSize: 11, fontWeight: 600, color: "#111827" }}>{r.name}</p>
<p style={{ fontSize: 9, color: "#9ca3af", fontFamily: "monospace" }}>{r.email}</p>
</div>
<span style={{ fontSize: 9, padding: "2px 7px", borderRadius: 4, background: r.role === "Admin" ? "#fef3c7" : "#f3f4f6", color: r.role === "Admin" ? "#92400e" : "#6b7280", fontWeight: 600 }}>{r.role}</span>
<span style={{ fontSize: 9, padding: "2px 7px", borderRadius: 4, fontWeight: 600, background: r.status === "Active" ? "#d1fae5" : r.status === "Pending" ? "#fef3c7" : "#fee2e2", color: r.status === "Active" ? "#065f46" : r.status === "Pending" ? "#92400e" : "#991b1b" }}>{r.status}</span>
{/* Admin action buttons */}
<div style={{ display: "flex", gap: 4 }}>
<button style={{ height: 22, padding: "0 8px", borderRadius: 4, border: "1px solid #e5e7eb", fontSize: 9, background: "#fff", color: "#374151", cursor: "pointer" }}>Impersonate</button>
<button style={{ height: 22, padding: "0 8px", borderRadius: 4, border: "1px solid #fca5a5", fontSize: 9, background: "#fff", color: "#dc2626", cursor: "pointer" }}>Ban</button>
</div>
</div>
))}
</div>
{/* Audit log */}
<div style={{ background: "#fff", border: "1px solid #e5e7eb", borderRadius: 8 }}>
<div style={{ padding: "9px 14px", borderBottom: "1px solid #f3f4f6", fontSize: 11, fontWeight: 600, color: "#111827" }}>Audit log</div>
{AUDIT_LOG.map((e, i) => (
<div key={i} style={{ display: "flex", alignItems: "center", padding: "8px 14px", borderBottom: "1px solid #f9fafb", gap: 8 }}>
<div style={{ width: 6, height: 6, borderRadius: "50%", background: t.primary, flexShrink: 0 }} />
<span style={{ fontSize: 9, fontFamily: "monospace", color: "#6b7280", minWidth: 100 }}>{e.user}</span>
<span style={{ fontSize: 10, fontWeight: 500, color: "#111827", flex: 1 }}>{e.action}</span>
<span style={{ fontSize: 9, fontFamily: "monospace", color: "#9ca3af" }}>{e.target}</span>
<span style={{ fontSize: 9, color: "#d1d5db" }}>{e.time}</span>
</div>
))}
</div>
</div>
);
}
// ---------------------------------------------------------------------------
// Config tab — API keys, feature flags, webhooks
// ---------------------------------------------------------------------------
function ConfigView({ t }: { t: ThemeColor }) {
return (
<div style={{ padding: 16, display: "flex", flexDirection: "column", gap: 10 }}>
{/* API keys */}
<div style={{ background: "#fff", border: "1px solid #e5e7eb", borderRadius: 8 }}>
<div style={{ padding: "9px 14px", borderBottom: "1px solid #f3f4f6", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<span style={{ fontSize: 11, fontWeight: 600, color: "#111827" }}>API Keys</span>
<button style={{ height: 24, padding: "0 10px", borderRadius: 5, background: t.primary, color: t.primaryFg, border: "none", fontSize: 9, fontWeight: 600, cursor: "pointer" }}>+ Generate</button>
</div>
{[
{ name: "Production", key: "sk_prod_••••••••••••••3d8f", created: "Jan 2, 2026", last: "2m ago" },
{ name: "Staging", key: "sk_test_••••••••••••••9a2c", created: "Dec 10, 2025", last: "1d ago" },
].map(k => (
<div key={k.name} style={{ display: "flex", alignItems: "center", padding: "9px 14px", borderBottom: "1px solid #f9fafb", gap: 10 }}>
<div style={{ flex: 1 }}>
<p style={{ fontSize: 10, fontWeight: 600, color: "#111827", marginBottom: 2 }}>{k.name}</p>
<p style={{ fontSize: 9, fontFamily: "monospace", color: "#9ca3af" }}>{k.key}</p>
</div>
<span style={{ fontSize: 9, color: "#9ca3af" }}>Created {k.created}</span>
<span style={{ fontSize: 9, color: "#22c55e" }}>Used {k.last}</span>
<button style={{ fontSize: 9, padding: "2px 8px", borderRadius: 4, border: "1px solid #fca5a5", color: "#dc2626", background: "none", cursor: "pointer" }}>Revoke</button>
</div>
))}
</div>
{/* Feature flags */}
<div style={{ background: "#fff", border: "1px solid #e5e7eb", borderRadius: 8 }}>
<div style={{ padding: "9px 14px", borderBottom: "1px solid #f3f4f6", fontSize: 11, fontWeight: 600, color: "#111827" }}>Feature flags</div>
{[
{ flag: "new-onboarding", on: true, env: "production" },
{ flag: "ai-suggestions", on: true, env: "production" },
{ flag: "bulk-export-v2", on: false, env: "staging" },
{ flag: "realtime-collab", on: false, env: "dev" },
].map(f => (
<div key={f.flag} style={{ display: "flex", alignItems: "center", padding: "8px 14px", borderBottom: "1px solid #f9fafb", gap: 10 }}>
<p style={{ flex: 1, fontSize: 10, fontFamily: "monospace", color: "#374151" }}>{f.flag}</p>
<span style={{ fontSize: 9, padding: "1px 6px", borderRadius: 3, background: f.env === "production" ? "#d1fae5" : f.env === "staging" ? "#fef3c7" : "#e0e7ff", color: f.env === "production" ? "#065f46" : f.env === "staging" ? "#92400e" : "#3730a3" }}>{f.env}</span>
<div style={{ width: 32, height: 16, borderRadius: 8, background: f.on ? t.primary : "#e5e7eb", position: "relative", cursor: "pointer", flexShrink: 0 }}>
<div style={{ position: "absolute", top: 2, width: 12, height: 12, borderRadius: "50%", background: "#fff", boxShadow: "0 1px 2px rgba(0,0,0,0.2)", transform: f.on ? "translateX(18px)" : "translateX(2px)" }} />
</div>
</div>
))}
</div>
{/* Webhooks */}
<div style={{ background: "#fff", border: "1px solid #e5e7eb", borderRadius: 8 }}>
<div style={{ padding: "9px 14px", borderBottom: "1px solid #f3f4f6", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<span style={{ fontSize: 11, fontWeight: 600, color: "#111827" }}>Webhooks</span>
<button style={{ height: 24, padding: "0 10px", borderRadius: 5, border: "1px solid #e5e7eb", fontSize: 9, background: "none", color: "#374151", cursor: "pointer" }}>+ Add endpoint</button>
</div>
{[
{ url: "https://hooks.slack.com/T0123/B456/••••", events: "user.created", status: "active" },
{ url: "https://api.pagerduty.com/webhooks/••••", events: "alert.fired", status: "active" },
].map((w, i) => (
<div key={i} style={{ display: "flex", alignItems: "center", padding: "8px 14px", borderBottom: "1px solid #f9fafb", gap: 8 }}>
<div style={{ width: 6, height: 6, borderRadius: "50%", background: "#22c55e", flexShrink: 0 }} />
<span style={{ flex: 1, fontSize: 9, fontFamily: "monospace", color: "#374151", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{w.url}</span>
<span style={{ fontSize: 9, padding: "1px 6px", borderRadius: 3, background: "#f3f4f6", color: "#6b7280", flexShrink: 0 }}>{w.events}</span>
</div>
))}
</div>
</div>
);
}
// ---------------------------------------------------------------------------
// Exported scaffold components
// ---------------------------------------------------------------------------
export function AdminMantine({ themeColor }: { themeColor?: ThemeColor }) {
const [page, setPage] = useState<AdminPage>("System");
const t = themeColor ?? MANTINE_THEMES[0];
return (
<AdminShell t={t} page={page} onNav={setPage}>
{page === "System" && <SystemView t={t} />}
{page === "Moderation" && <ModerationView t={t} />}
{page === "Config" && <ConfigView t={t} />}
</AdminShell>
);
}
export function AdminShadcn({ themeColor }: { themeColor?: ThemeColor }) {
const [page, setPage] = useState<AdminPage>("System");
const t = themeColor ?? SHADCN_THEMES[0];
return (
<AdminShell t={t} page={page} onNav={setPage}>
{page === "System" && <SystemView t={t} />}
{page === "Moderation" && <ModerationView t={t} />}
{page === "Config" && <ConfigView t={t} />}
</AdminShell>
);
}
export function AdminTremor({ themeColor }: { themeColor?: ThemeColor }) {
const [page, setPage] = useState<AdminPage>("System");
const t = themeColor ?? TREMOR_THEMES[0];
return (
<AdminShell t={t} page={page} onNav={setPage}>
{page === "System" && <SystemView t={t} />}
{page === "Moderation" && <ModerationView t={t} />}
{page === "Config" && <ConfigView t={t} />}
</AdminShell>
);
}
export { MANTINE_THEMES, SHADCN_THEMES, TREMOR_THEMES };

View File

@@ -0,0 +1,81 @@
"use client";
import { ThemeColor } from "./types";
export function DocsNextra({ themeColor }: { themeColor?: ThemeColor }) {
return (
<div style={{ display: "flex", height: "100%", fontFamily: "system-ui, sans-serif", fontSize: 12, background: "#fff" }}>
<div style={{ width: 168, flexShrink: 0, borderRight: "1px solid #f3f4f6", padding: "14px 10px", background: "#fafafa", display: "flex", flexDirection: "column", gap: 2 }}>
{[
{ section: "Getting Started", items: ["Introduction", "Installation", "Quick Start"] },
{ section: "Components", items: ["Button", "Card", "Input", "Modal", "Table"] },
{ section: "API Reference", items: ["REST API", "Webhooks"] },
].map(g => (
<div key={g.section} style={{ marginBottom: 14 }}>
<p style={{ fontSize: 9, fontWeight: 600, color: "#9ca3af", textTransform: "uppercase", letterSpacing: "0.08em", marginBottom: 6, padding: "0 6px" }}>{g.section}</p>
{g.items.map((item, i) => (
<div key={item} style={{ padding: "5px 8px", borderRadius: 5, fontSize: 10, color: i === 0 && g.section === "Getting Started" ? "#fff" : "#71717a", background: i === 0 && g.section === "Getting Started" ? "#18181b" : "none", marginBottom: 1, cursor: "pointer" }}>{item}</div>
))}
</div>
))}
</div>
<div style={{ flex: 1, overflow: "auto", padding: "18px 22px" }}>
<div style={{ display: "flex", alignItems: "center", gap: 5, fontSize: 9, color: "#9ca3af", marginBottom: 16 }}>
<span>Docs</span><span>/</span><span>Getting Started</span><span>/</span><span style={{ color: "#374151" }}>Introduction</span>
</div>
<h1 style={{ fontSize: 16, fontWeight: 700, color: "#09090b", marginBottom: 8 }}>Introduction</h1>
<p style={{ fontSize: 11, color: "#71717a", marginBottom: 14, lineHeight: 1.65 }}>Acme UI is a collection of re-usable components that you can copy and paste into your web apps. Built with Radix UI and Tailwind CSS.</p>
<div style={{ borderRadius: 8, padding: "10px 14px", marginBottom: 14, background: "#18181b" }}>
<p style={{ fontSize: 10, fontFamily: "monospace", color: "#10b981" }}>$ npm install acme-ui</p>
</div>
<h2 style={{ fontSize: 13, fontWeight: 600, color: "#09090b", marginBottom: 8 }}>Key features</h2>
{["Accessible by default", "Customisable", "Open source", "TypeScript ready"].map(f => (
<div key={f} style={{ display: "flex", alignItems: "center", gap: 6, fontSize: 10, color: "#71717a", marginBottom: 5 }}>
<div style={{ width: 5, height: 5, borderRadius: "50%", background: "#9ca3af", flexShrink: 0 }} />{f}
</div>
))}
</div>
</div>
);
}
export function DocsShadcnCustom({ themeColor }: { themeColor?: ThemeColor }) {
return (
<div style={{ display: "flex", height: "100%", fontFamily: "system-ui, sans-serif", fontSize: 12, background: "#fff" }}>
<div style={{ width: 168, flexShrink: 0, borderRight: "1px solid #e5e7eb", padding: "14px 10px", display: "flex", flexDirection: "column" }}>
<div style={{ display: "flex", alignItems: "center", gap: 7, marginBottom: 16, paddingBottom: 12, borderBottom: "1px solid #f3f4f6" }}>
<div style={{ width: 18, height: 18, borderRadius: 4, background: "#09090b" }} />
<span style={{ fontWeight: 700, fontSize: 11, color: "#09090b" }}>Acme Docs</span>
</div>
{[{ section: "Guides", items: ["Introduction", "Installation", "Theming"] }, { section: "Components", items: ["Button", "Input", "Select", "Dialog"] }].map(g => (
<div key={g.section} style={{ marginBottom: 12 }}>
<p style={{ fontSize: 9, fontWeight: 600, color: "#9ca3af", textTransform: "uppercase", letterSpacing: "0.08em", marginBottom: 5, padding: "0 6px" }}>{g.section}</p>
{g.items.map((item, i) => (
<div key={item} style={{ padding: "5px 8px", borderRadius: 5, fontSize: 10, marginBottom: 1, cursor: "pointer", background: i === 0 && g.section === "Guides" ? "#f4f4f5" : "none", color: i === 0 && g.section === "Guides" ? "#09090b" : "#71717a", fontWeight: i === 0 && g.section === "Guides" ? 500 : 400 }}>{item}</div>
))}
</div>
))}
</div>
<div style={{ flex: 1, padding: "18px 22px", overflow: "auto" }}>
<h1 style={{ fontSize: 16, fontWeight: 700, color: "#09090b", marginBottom: 6 }}>Introduction</h1>
<p style={{ fontSize: 11, color: "#71717a", marginBottom: 16, lineHeight: 1.65 }}>A set of beautifully designed components built with Radix UI and Tailwind CSS.</p>
<div style={{ border: "1px solid #e5e7eb", borderRadius: 8, overflow: "hidden", marginBottom: 16 }}>
<div style={{ display: "flex", alignItems: "center", gap: 6, padding: "7px 12px", borderBottom: "1px solid #e5e7eb", background: "#fafafa" }}>
<span style={{ fontSize: 9, color: "#9ca3af" }}>bash</span>
</div>
<div style={{ padding: "10px 14px", background: "#09090b" }}>
<p style={{ fontSize: 10, fontFamily: "monospace", color: "#4ade80" }}>npx shadcn@latest init</p>
</div>
</div>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }}>
{["Button", "Card", "Input", "Badge"].map(c => (
<div key={c} style={{ borderRadius: 8, border: "1px solid #e5e7eb", padding: "10px 14px", cursor: "pointer" }}>
<p style={{ fontSize: 10, fontWeight: 600, color: "#374151", marginBottom: 2 }}>{c}</p>
<p style={{ fontSize: 9, color: "#9ca3af" }}>View component </p>
</div>
))}
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,33 @@
"use client";
import { ThemeColor } from "./types";
export function EmailReactEmail({ themeColor }: { themeColor?: ThemeColor }) {
return (
<div style={{ display: "flex", alignItems: "flex-start", justifyContent: "center", height: "100%", padding: "18px 16px", fontFamily: "system-ui, sans-serif", background: "#f6f9fc", overflow: "auto" }}>
<div style={{ width: "100%", maxWidth: 380, background: "#fff", borderRadius: 8, boxShadow: "0 2px 8px rgba(0,0,0,0.08)" }}>
<div style={{ padding: "14px 24px", borderBottom: "1px solid #e6e6e6", display: "flex", alignItems: "center", gap: 7 }}>
<div style={{ width: 18, height: 18, borderRadius: 4, background: "#000" }} />
<span style={{ fontWeight: 700, fontSize: 11, color: "#09090b" }}>Acme</span>
</div>
<div style={{ padding: "20px 24px" }}>
<p style={{ fontSize: 14, fontWeight: 700, color: "#09090b", marginBottom: 8 }}>Welcome to Acme! 🎉</p>
<p style={{ fontSize: 11, color: "#71717a", marginBottom: 16, lineHeight: 1.6 }}>Hi Alice, thanks for signing up. Your account is ready and you can start building right away.</p>
<div style={{ textAlign: "center", marginBottom: 16 }}>
<button style={{ height: 34, padding: "0 22px", borderRadius: 6, fontSize: 11, fontWeight: 600, color: "#fff", background: "#000", border: "none", cursor: "pointer" }}>Get started </button>
</div>
<div style={{ borderRadius: 7, padding: "12px 14px", marginBottom: 16, background: "#f6f9fc", border: "1px solid #e6e6e6" }}>
<p style={{ fontSize: 10, fontWeight: 600, color: "#374151", marginBottom: 6 }}>Your account details</p>
{["Plan: Starter", "Workspace: alice-workspace", "Status: Active"].map(d => (
<p key={d} style={{ fontSize: 10, color: "#71717a", marginBottom: 2 }}>{d}</p>
))}
</div>
<p style={{ fontSize: 10, color: "#9ca3af", lineHeight: 1.6 }}>If you have any questions, reply to this email or visit our help center. We&apos;re here to help.</p>
</div>
<div style={{ padding: "12px 24px", textAlign: "center", borderTop: "1px solid #e6e6e6", background: "#fafafa" }}>
<p style={{ fontSize: 9, color: "#9ca3af" }}>Acme Inc · 123 Main St · San Francisco CA · <span style={{ textDecoration: "underline" }}>Unsubscribe</span></p>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,85 @@
/**
* Design Scaffolds — modular UI previews for each design surface.
*
* Adding a new surface:
* 1. Create components/design-scaffolds/<surface>.tsx
* 2. Export your scaffold component(s) from it
* 3. Add the surface ID to SCAFFOLD_REGISTRY and THEME_REGISTRY below
* 4. Add it to ALL_SURFACES in the design page
*/
export type { ThemeColor } from "./types";
export {
SHADCN_THEMES, MANTINE_THEMES, HEROUI_THEMES, TREMOR_THEMES,
DAISY_THEMES, HEROUI_MARKETING_THEMES,
} from "./types";
import { WebAppShadcn, WebAppMantine, WebAppHeroUI, WebAppTremor } from "./web-app";
import { MarketingDaisy, MarketingHeroUI, MarketingAceternity, MarketingTailwind } from "./marketing";
import { AdminMantine, AdminShadcn, AdminTremor } from "./admin";
import { MobileNativewind, MobileGluestack } from "./mobile";
import { EmailReactEmail } from "./email";
import { DocsNextra, DocsShadcnCustom } from "./docs";
import type { ThemeColor } from "./types";
import {
SHADCN_THEMES, MANTINE_THEMES, HEROUI_THEMES, TREMOR_THEMES,
DAISY_THEMES, HEROUI_MARKETING_THEMES,
} from "./types";
// ---------------------------------------------------------------------------
// SCAFFOLD_REGISTRY — surface → library → preview component
// ---------------------------------------------------------------------------
export const SCAFFOLD_REGISTRY: Record<string, Record<string, React.ComponentType<{ themeColor?: ThemeColor }>>> = {
"web-app": {
shadcn: WebAppShadcn,
mantine: WebAppMantine,
"hero-ui": WebAppHeroUI,
tremor: WebAppTremor,
},
"marketing": {
"daisy-ui": MarketingDaisy,
"hero-ui": MarketingHeroUI,
aceternity: MarketingAceternity,
"tailwind-only": MarketingTailwind,
},
"admin": {
mantine: AdminMantine,
shadcn: AdminShadcn,
tremor: AdminTremor,
},
"mobile": {
nativewind: MobileNativewind,
gluestack: MobileGluestack,
},
"email": {
"react-email": EmailReactEmail,
},
"docs": {
nextra: DocsNextra,
shadcn: DocsShadcnCustom,
},
};
// ---------------------------------------------------------------------------
// THEME_REGISTRY — surface → library → available color themes
// ---------------------------------------------------------------------------
export const THEME_REGISTRY: Record<string, Record<string, ThemeColor[]>> = {
"web-app": {
shadcn: SHADCN_THEMES,
mantine: MANTINE_THEMES,
"hero-ui": HEROUI_THEMES,
tremor: TREMOR_THEMES,
},
"marketing": {
"daisy-ui": DAISY_THEMES,
"hero-ui": HEROUI_MARKETING_THEMES,
},
"admin": {
mantine: MANTINE_THEMES,
shadcn: SHADCN_THEMES,
tremor: TREMOR_THEMES,
},
};

View File

@@ -0,0 +1,211 @@
"use client";
import { ThemeColor, DAISY_THEMES, HEROUI_MARKETING_THEMES } from "./types";
// Marketing Site surface — public-facing pages: hero, features, pricing, CTA
// Context: prospective users discovering the product, not signed-in users.
export function MarketingDaisy({ themeColor }: { themeColor?: ThemeColor }) {
const theme = themeColor ?? DAISY_THEMES[0];
const text = theme.textColor ?? "#f8f8f2";
const muted = theme.mutedText ?? "rgba(255,255,255,0.5)";
const card = theme.cardBg ?? "rgba(255,255,255,0.05)";
const border = theme.borderColor ?? "rgba(255,255,255,0.1)";
const bg = theme.bg ?? "#1d232a";
return (
<div style={{ height: "100%", fontFamily: "system-ui, sans-serif", background: bg, color: text, overflow: "auto" }}>
{/* Nav */}
<nav style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "10px 22px", borderBottom: `1px solid ${border}` }}>
<div style={{ display: "flex", alignItems: "center", gap: 7 }}>
<div style={{ width: 20, height: 20, borderRadius: 4, background: theme.primary }} />
<span style={{ fontWeight: 800, fontSize: 12, color: text }}>Acme</span>
</div>
<div style={{ display: "flex", gap: 16, fontSize: 10, color: muted }}>
{["Features", "Pricing", "Docs", "Blog"].map(i => <span key={i}>{i}</span>)}
</div>
<div style={{ display: "flex", gap: 7 }}>
<button style={{ height: 26, padding: "0 12px", borderRadius: 20, fontSize: 10, fontWeight: 700, background: `${border}`, color: text, border: "none", cursor: "pointer" }}>Login</button>
<button style={{ height: 26, padding: "0 12px", borderRadius: 20, fontSize: 10, fontWeight: 700, background: theme.primary, color: theme.primaryFg, border: "none", cursor: "pointer" }}>Get started</button>
</div>
</nav>
{/* Hero */}
<div style={{ padding: "28px 22px 18px", textAlign: "center" }}>
<div style={{ display: "inline-flex", alignItems: "center", gap: 5, padding: "3px 10px", borderRadius: 20, fontSize: 9, fontWeight: 700, marginBottom: 14, background: `${theme.primary}22`, color: theme.primary, border: `1px solid ${theme.primary}44` }}>
{theme.label} theme v2.0
</div>
<h1 style={{ fontSize: 22, fontWeight: 900, marginBottom: 8, color: text, lineHeight: 1.15 }}>Build faster,<br />ship smarter</h1>
<p style={{ fontSize: 10, color: muted, maxWidth: 260, margin: "0 auto 18px", lineHeight: 1.6 }}>The all-in-one platform that helps teams build, launch, and scale their products.</p>
<div style={{ display: "flex", gap: 8, justifyContent: "center", marginBottom: 22 }}>
<button style={{ height: 32, padding: "0 18px", borderRadius: 20, fontSize: 10, fontWeight: 700, background: theme.primary, color: theme.primaryFg, border: "none", cursor: "pointer" }}>Start for free</button>
<button style={{ height: 32, padding: "0 18px", borderRadius: 20, fontSize: 10, fontWeight: 700, border: `1px solid ${border}`, color: text, background: "none", cursor: "pointer" }}>See demo </button>
</div>
</div>
{/* Feature cards */}
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 8, padding: "0 22px 18px" }}>
{[{ icon: "⚡", title: "Lightning fast", desc: "Deploy in seconds" }, { icon: "🔒", title: "Secure by default", desc: "Enterprise-grade security" }, { icon: "📈", title: "Scales with you", desc: "From zero to millions" }].map(f => (
<div key={f.title} style={{ padding: "12px 14px", borderRadius: 12, textAlign: "center", background: card, border: `1px solid ${border}` }}>
<div style={{ fontSize: 18, marginBottom: 5 }}>{f.icon}</div>
<p style={{ fontSize: 10, fontWeight: 700, color: text, marginBottom: 3 }}>{f.title}</p>
<p style={{ fontSize: 9, color: muted }}>{f.desc}</p>
</div>
))}
</div>
{/* Pricing strip */}
<div style={{ padding: "0 22px" }}>
<div style={{ borderRadius: 12, padding: "14px 16px", background: card, border: `1px solid ${border}` }}>
<p style={{ fontSize: 10, fontWeight: 700, color: text, marginBottom: 8 }}>Simple pricing</p>
<div style={{ display: "flex", gap: 8 }}>
{[{ plan: "Starter", price: "Free", highlight: false }, { plan: "Pro", price: "$29/mo", highlight: true }, { plan: "Enterprise", price: "Custom", highlight: false }].map(p => (
<div key={p.plan} style={{ flex: 1, padding: "8px 10px", borderRadius: 8, background: p.highlight ? theme.primary : "transparent", border: `1px solid ${p.highlight ? "transparent" : border}`, textAlign: "center" }}>
<p style={{ fontSize: 9, fontWeight: 600, color: p.highlight ? theme.primaryFg : muted, marginBottom: 2 }}>{p.plan}</p>
<p style={{ fontSize: 13, fontWeight: 800, color: p.highlight ? theme.primaryFg : text }}>{p.price}</p>
</div>
))}
</div>
</div>
</div>
</div>
);
}
export function MarketingHeroUI({ themeColor }: { themeColor?: ThemeColor }) {
const theme = themeColor ?? HEROUI_MARKETING_THEMES[0];
const bg = theme.bg ?? "#fff";
const text = theme.textColor ?? "#18181b";
const muted = theme.mutedText ?? "#71717a";
const card = theme.cardBg ?? "#fff";
const border = theme.borderColor ?? "#f4f4f5";
return (
<div style={{ height: "100%", fontFamily: "system-ui, sans-serif", background: bg, overflow: "auto" }}>
<nav style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "10px 22px", borderBottom: `1px solid ${border}` }}>
<div style={{ display: "flex", alignItems: "center", gap: 7 }}>
<div style={{ width: 20, height: 20, borderRadius: "50%", background: theme.primary }} />
<span style={{ fontWeight: 800, fontSize: 11, color: text }}>Acme</span>
</div>
<div style={{ display: "flex", gap: 14, fontSize: 10, color: muted }}>
{["Features", "Pricing", "Docs", "Blog"].map(i => <span key={i}>{i}</span>)}
</div>
<div style={{ display: "flex", gap: 7 }}>
<button style={{ height: 26, padding: "0 12px", borderRadius: 20, border: `1px solid ${border}`, fontSize: 10, background: "none", color: muted, cursor: "pointer" }}>Login</button>
<button style={{ height: 26, padding: "0 12px", borderRadius: 20, fontSize: 10, fontWeight: 600, background: theme.primary, color: theme.primaryFg, border: "none", cursor: "pointer" }}>Get started</button>
</div>
</nav>
<div style={{ padding: "26px 22px 16px", textAlign: "center" }}>
<div style={{ display: "inline-flex", alignItems: "center", gap: 5, padding: "3px 10px", borderRadius: 20, fontSize: 9, fontWeight: 600, marginBottom: 14, background: theme.activeBg, color: theme.activeFg, border: `1px solid ${theme.ring}` }}>
🚀 {theme.label} theme v2.0
</div>
<h1 style={{ fontSize: 22, fontWeight: 900, color: theme.primary, marginBottom: 8, lineHeight: 1.15 }}>Build faster,<br />ship smarter</h1>
<p style={{ fontSize: 10, color: muted, maxWidth: 260, margin: "0 auto 16px", lineHeight: 1.6 }}>The all-in-one platform for teams that move fast.</p>
<div style={{ display: "flex", gap: 8, justifyContent: "center", marginBottom: 18 }}>
<button style={{ height: 32, padding: "0 18px", borderRadius: 20, fontSize: 10, fontWeight: 600, background: theme.primary, color: theme.primaryFg, border: "none", cursor: "pointer", boxShadow: `0 4px 14px ${theme.ring}` }}>Start for free</button>
<button style={{ height: 32, padding: "0 18px", borderRadius: 20, fontSize: 10, background: "none", border: `1px solid ${border}`, color: muted, cursor: "pointer" }}>Live demo </button>
</div>
{/* Dashboard preview card */}
<div style={{ borderRadius: 16, overflow: "hidden", border: `1px solid ${border}`, maxWidth: 340, margin: "0 auto", background: card, boxShadow: `0 8px 30px rgba(0,0,0,0.08)` }}>
<div style={{ height: 22, display: "flex", alignItems: "center", gap: 5, padding: "0 10px", background: bg === "#09090b" ? "#27272a" : "#fafafa", borderBottom: `1px solid ${border}` }}>
{["#ff5f56", "#ffbd2e", "#27c93f"].map(c => <div key={c} style={{ width: 7, height: 7, borderRadius: "50%", background: c }} />)}
</div>
<div style={{ padding: 12 }}>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }}>
{["Revenue $12k", "Users 2.8k", "Growth +24%", "Churn 2.1%"].map((m, i) => (
<div key={m} style={{ padding: "8px 10px", borderRadius: 10, background: theme.activeBg, border: `1px solid ${theme.ring}` }}>
<p style={{ fontSize: 9, color: muted, marginBottom: 2 }}>{m.split(" ")[0]}</p>
<p style={{ fontSize: 13, fontWeight: 700, color: text }}>{m.split(" ")[1]}</p>
</div>
))}
</div>
</div>
</div>
</div>
</div>
);
}
export function MarketingAceternity() {
return (
<div style={{ height: "100%", fontFamily: "system-ui, sans-serif", background: "#030303", overflow: "auto" }}>
<nav style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "10px 22px", borderBottom: "1px solid rgba(255,255,255,0.06)" }}>
<div style={{ display: "flex", alignItems: "center", gap: 7 }}>
<div style={{ width: 20, height: 20, borderRadius: 4, background: "linear-gradient(135deg,#a855f7,#3b82f6)" }} />
<span style={{ fontWeight: 800, fontSize: 11, color: "#fff" }}>Acme</span>
</div>
<div style={{ display: "flex", gap: 14, fontSize: 10, color: "rgba(255,255,255,0.4)" }}>
{["Features", "Pricing", "Docs"].map(i => <span key={i}>{i}</span>)}
</div>
<button style={{ height: 26, padding: "0 12px", borderRadius: 5, fontSize: 10, fontWeight: 600, color: "#fff", background: "rgba(168,85,247,0.2)", border: "1px solid rgba(168,85,247,0.3)", cursor: "pointer" }}>Get started</button>
</nav>
<div style={{ position: "relative", padding: "28px 22px 18px", textAlign: "center", overflow: "hidden" }}>
<div style={{ position: "absolute", inset: 0, background: "radial-gradient(ellipse 60% 40% at 50% 0%,rgba(168,85,247,0.15),transparent)", pointerEvents: "none" }} />
<div style={{ display: "inline-flex", alignItems: "center", gap: 5, padding: "3px 10px", borderRadius: 20, fontSize: 9, fontWeight: 500, marginBottom: 14, border: "1px solid rgba(168,85,247,0.3)", color: "rgba(168,85,247,0.9)" }}>
Open source · 12k GitHub stars
</div>
<h1 style={{ fontSize: 22, fontWeight: 900, marginBottom: 8, lineHeight: 1.15, background: "linear-gradient(180deg,#fff 0%,rgba(255,255,255,0.5) 100%)", WebkitBackgroundClip: "text", WebkitTextFillColor: "transparent" }}>
Build the future<br />of the web
</h1>
<p style={{ fontSize: 10, color: "rgba(255,255,255,0.35)", maxWidth: 240, margin: "0 auto 18px", lineHeight: 1.6 }}>Beautifully crafted components built with Tailwind CSS and Framer Motion.</p>
<div style={{ display: "flex", gap: 8, justifyContent: "center", marginBottom: 22 }}>
<button style={{ height: 32, padding: "0 18px", borderRadius: 6, fontSize: 10, fontWeight: 600, color: "#fff", background: "linear-gradient(135deg,#a855f7,#3b82f6)", border: "none", cursor: "pointer" }}>Get started </button>
<button style={{ height: 32, padding: "0 18px", borderRadius: 6, fontSize: 10, color: "rgba(255,255,255,0.6)", border: "1px solid rgba(255,255,255,0.1)", background: "none", cursor: "pointer" }}>View components</button>
</div>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 8 }}>
{["Animated", "Accessible", "Open source"].map((f, i) => (
<div key={f} style={{ padding: "10px 12px", borderRadius: 10, textAlign: "left", background: "rgba(255,255,255,0.03)", border: "1px solid rgba(255,255,255,0.06)" }}>
<div style={{ width: 20, height: 20, borderRadius: 5, marginBottom: 6, background: ["rgba(168,85,247,0.15)", "rgba(59,130,246,0.15)", "rgba(34,197,94,0.15)"][i], display: "flex", alignItems: "center", justifyContent: "center" }}>
<div style={{ width: 8, height: 8, borderRadius: "50%", background: ["#a855f7", "#3b82f6", "#22c55e"][i] }} />
</div>
<p style={{ fontSize: 9, fontWeight: 600, color: "#fff", marginBottom: 2 }}>{f}</p>
<p style={{ fontSize: 9, color: "rgba(255,255,255,0.3)" }}>Built for production</p>
</div>
))}
</div>
</div>
</div>
);
}
export function MarketingTailwind() {
return (
<div style={{ height: "100%", fontFamily: "system-ui, sans-serif", background: "#fff", overflow: "auto" }}>
<nav style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "10px 22px", borderBottom: "1px solid #e5e7eb" }}>
<span style={{ fontWeight: 900, fontSize: 12, color: "#09090b" }}>acme</span>
<div style={{ display: "flex", gap: 14, fontSize: 10, color: "#71717a" }}>
{["Features", "Pricing", "Blog"].map(i => <span key={i}>{i}</span>)}
</div>
<div style={{ display: "flex", gap: 6 }}>
<button style={{ height: 26, padding: "0 10px", fontSize: 10, color: "#71717a", background: "none", border: "none", cursor: "pointer" }}>Log in</button>
<button style={{ height: 26, padding: "0 12px", borderRadius: 5, fontSize: 10, fontWeight: 500, background: "#09090b", color: "#fff", border: "none", cursor: "pointer" }}>Sign up</button>
</div>
</nav>
<div style={{ padding: "28px 22px" }}>
<div style={{ maxWidth: 320 }}>
<span style={{ fontSize: 9, fontWeight: 600, color: "#7c3aed", letterSpacing: "0.12em", textTransform: "uppercase" }}>Now in public beta</span>
<h1 style={{ fontSize: 22, fontWeight: 900, color: "#09090b", marginTop: 4, marginBottom: 8, lineHeight: 1.15 }}>The platform<br />built for scale</h1>
<p style={{ fontSize: 10, color: "#71717a", marginBottom: 18, lineHeight: 1.6 }}>Everything your team needs to build, deploy, and monitor production applications.</p>
<div style={{ display: "flex", gap: 8, marginBottom: 24 }}>
<button style={{ height: 30, padding: "0 14px", borderRadius: 6, fontSize: 10, fontWeight: 500, background: "#09090b", color: "#fff", border: "none", cursor: "pointer" }}>Get started free</button>
<button style={{ height: 30, padding: "0 14px", borderRadius: 6, fontSize: 10, fontWeight: 500, border: "1px solid #e5e7eb", color: "#71717a", background: "none", cursor: "pointer" }}>Documentation </button>
</div>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }}>
{["99.9% uptime SLA", "10ms avg latency", "SOC2 compliant", "GDPR ready"].map(f => (
<div key={f} style={{ display: "flex", alignItems: "center", gap: 6, fontSize: 9, color: "#71717a" }}>
<div style={{ width: 14, height: 14, borderRadius: "50%", background: "#09090b", flexShrink: 0, display: "flex", alignItems: "center", justifyContent: "center" }}>
<span style={{ color: "#fff", fontSize: 8, lineHeight: 1 }}></span>
</div>
{f}
</div>
))}
</div>
</div>
</div>
</div>
);
}
export { DAISY_THEMES, HEROUI_MARKETING_THEMES };

View File

@@ -0,0 +1,93 @@
"use client";
import { ThemeColor } from "./types";
function MobileFrame({ children }: { children: React.ReactNode }) {
return (
<div style={{ display: "flex", alignItems: "center", justifyContent: "center", height: "100%", background: "#f4f4f5" }}>
<div style={{ position: "relative", width: 200, height: 380 }}>
<div style={{ position: "absolute", inset: 0, borderRadius: 36, background: "#1a1a1a", boxShadow: "0 20px 60px rgba(0,0,0,0.3)" }} />
<div style={{ position: "absolute", top: 10, left: "50%", transform: "translateX(-50%)", width: 60, height: 14, borderRadius: 20, background: "#000", zIndex: 10 }} />
<div style={{ position: "absolute", inset: 8, borderRadius: 30, overflow: "hidden", background: "#fff" }}>
{children}
</div>
</div>
</div>
);
}
export function MobileNativewind({ themeColor }: { themeColor?: ThemeColor }) {
return (
<MobileFrame>
<div style={{ height: "100%", display: "flex", flexDirection: "column", background: "#fff", fontFamily: "system-ui, sans-serif", fontSize: 11 }}>
<div style={{ paddingTop: 22, paddingBottom: 10, paddingLeft: 12, paddingRight: 12, background: "#18181b" }}>
<p style={{ fontSize: 8, color: "#71717a", marginBottom: 2 }}>Good morning</p>
<p style={{ fontSize: 13, fontWeight: 700, color: "#fff" }}>Dashboard</p>
</div>
<div style={{ flex: 1, overflow: "auto", padding: 10, background: "#fafafa", display: "flex", flexDirection: "column", gap: 8 }}>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 6 }}>
{[["Revenue", "$4.2k"], ["Users", "184"]].map(([l, v]) => (
<div key={l} style={{ background: "#fff", borderRadius: 10, padding: "8px 10px", border: "1px solid #e4e4e7" }}>
<p style={{ fontSize: 7, color: "#a1a1aa", marginBottom: 2 }}>{l}</p>
<p style={{ fontSize: 12, fontWeight: 700, color: "#09090b" }}>{v}</p>
</div>
))}
</div>
{["Fix login bug", "Update pricing", "Review PR #42"].map((t, i) => (
<div key={t} style={{ background: "#fff", borderRadius: 10, padding: "8px 10px", display: "flex", alignItems: "center", gap: 8, border: "1px solid #e4e4e7" }}>
<div style={{ width: 14, height: 14, borderRadius: 3, border: "1px solid #d4d4d8", flexShrink: 0, background: i === 0 ? "#18181b" : "none", borderColor: i === 0 ? "#18181b" : "#d4d4d8", display: "flex", alignItems: "center", justifyContent: "center" }}>
{i === 0 && <span style={{ color: "#fff", fontSize: 8 }}></span>}
</div>
<p style={{ fontSize: 9, color: i === 0 ? "#a1a1aa" : "#09090b", textDecoration: i === 0 ? "line-through" : "none" }}>{t}</p>
</div>
))}
</div>
<div style={{ display: "flex", borderTop: "1px solid #f4f4f5", padding: "6px 0 10px", background: "#fff" }}>
{["Home", "Projects", "Chat", "Profile"].map((l, i) => (
<div key={l} style={{ flex: 1, display: "flex", flexDirection: "column", alignItems: "center", gap: 2 }}>
<div style={{ width: 14, height: 14, borderRadius: 3, background: i === 0 ? "#18181b" : "#e4e4e7" }} />
<span style={{ fontSize: 7, color: i === 0 ? "#18181b" : "#a1a1aa" }}>{l}</span>
</div>
))}
</div>
</div>
</MobileFrame>
);
}
export function MobileGluestack({ themeColor }: { themeColor?: ThemeColor }) {
return (
<MobileFrame>
<div style={{ height: "100%", display: "flex", flexDirection: "column", fontFamily: "system-ui, sans-serif", fontSize: 11 }}>
<div style={{ paddingTop: 22, paddingBottom: 10, paddingLeft: 12, paddingRight: 12, background: "#1976d2", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<p style={{ fontSize: 13, fontWeight: 700, color: "#fff" }}>Dashboard</p>
<div style={{ width: 20, height: 20, borderRadius: "50%", background: "rgba(255,255,255,0.2)" }} />
</div>
<div style={{ flex: 1, overflow: "auto", padding: 10, background: "#f5f5f5", display: "flex", flexDirection: "column", gap: 7 }}>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 6 }}>
{[["Revenue", "$4.2k", "#1976d2"], ["Users", "184", "#7b1fa2"]].map(([l, v, c]) => (
<div key={l} style={{ borderRadius: 8, padding: "8px 10px", background: c }}>
<p style={{ fontSize: 7, color: "rgba(255,255,255,0.7)", marginBottom: 2 }}>{l}</p>
<p style={{ fontSize: 12, fontWeight: 700, color: "#fff" }}>{v}</p>
</div>
))}
</div>
{["Fix login bug", "Update pricing", "Review PR #42"].map((t, i) => (
<div key={t} style={{ background: "#fff", borderRadius: 8, padding: "8px 10px", display: "flex", alignItems: "center", gap: 8, boxShadow: "0 1px 3px rgba(0,0,0,0.08)" }}>
<div style={{ width: 14, height: 14, borderRadius: "50%", border: `2px solid ${i === 0 ? "#1976d2" : "#bdbdbd"}`, flexShrink: 0, background: i === 0 ? "#1976d2" : "none" }} />
<p style={{ fontSize: 9, color: i === 0 ? "#9e9e9e" : "#212121", textDecoration: i === 0 ? "line-through" : "none" }}>{t}</p>
</div>
))}
</div>
<div style={{ display: "flex", borderTop: "1px solid #e0e0e0", padding: "6px 0 10px", background: "#fff" }}>
{["Home", "Tasks", "Chat", "Profile"].map((l, i) => (
<div key={l} style={{ flex: 1, display: "flex", flexDirection: "column", alignItems: "center", gap: 2 }}>
<div style={{ width: 14, height: 14, borderRadius: 3, background: i === 0 ? "#1976d2" : "#e0e0e0" }} />
<span style={{ fontSize: 7, color: i === 0 ? "#1976d2" : "#9e9e9e" }}>{l}</span>
</div>
))}
</div>
</div>
</MobileFrame>
);
}

View File

@@ -0,0 +1,83 @@
"use client";
// ---------------------------------------------------------------------------
// Shared types + theme palettes used across all scaffold surfaces
// ---------------------------------------------------------------------------
export interface ThemeColor {
id: string;
label: string;
primary: string;
primaryFg: string;
activeBg: string;
activeFg: string;
ring: string;
bg?: string;
cardBg?: string;
textColor?: string;
borderColor?: string;
mutedText?: string;
}
// Shared mock data
export const TABLE_ROWS = [
{ name: "Alice Martin", email: "alice@co.com", role: "Admin", status: "Active", date: "Jan 12" },
{ name: "Ben Walsh", email: "ben@co.com", role: "Member", status: "Pending", date: "Jan 14" },
{ name: "Clara Kim", email: "clara@co.com", role: "Member", status: "Active", date: "Jan 15" },
{ name: "David Osei", email: "david@co.com", role: "Viewer", status: "Inactive", date: "Jan 16" },
];
// ---------------------------------------------------------------------------
// Theme palettes
// ---------------------------------------------------------------------------
export const SHADCN_THEMES: ThemeColor[] = [
{ id: "neutral", label: "Neutral", primary: "#18181b", primaryFg: "#fff", activeBg: "#f4f4f5", activeFg: "#18181b", ring: "#e4e4e7" },
{ id: "blue", label: "Blue", primary: "#3b82f6", primaryFg: "#fff", activeBg: "#eff6ff", activeFg: "#1d4ed8", ring: "#bfdbfe" },
{ id: "green", label: "Green", primary: "#22c55e", primaryFg: "#fff", activeBg: "#f0fdf4", activeFg: "#15803d", ring: "#bbf7d0" },
{ id: "orange", label: "Orange", primary: "#f97316", primaryFg: "#fff", activeBg: "#fff7ed", activeFg: "#c2410c", ring: "#fed7aa" },
{ id: "violet", label: "Violet", primary: "#8b5cf6", primaryFg: "#fff", activeBg: "#f5f3ff", activeFg: "#6d28d9", ring: "#ddd6fe" },
{ id: "rose", label: "Rose", primary: "#f43f5e", primaryFg: "#fff", activeBg: "#fff1f2", activeFg: "#be123c", ring: "#fecdd3" },
];
export const MANTINE_THEMES: ThemeColor[] = [
{ id: "blue", label: "Blue", primary: "#228be6", primaryFg: "#fff", activeBg: "#e7f5ff", activeFg: "#1971c2", ring: "#a5d8ff" },
{ id: "teal", label: "Teal", primary: "#12b886", primaryFg: "#fff", activeBg: "#e6fcf5", activeFg: "#087f5b", ring: "#96f2d7" },
{ id: "violet", label: "Violet", primary: "#7950f2", primaryFg: "#fff", activeBg: "#f3f0ff", activeFg: "#5f3dc4", ring: "#d0bfff" },
{ id: "red", label: "Red", primary: "#fa5252", primaryFg: "#fff", activeBg: "#fff5f5", activeFg: "#c92a2a", ring: "#ffc9c9" },
{ id: "orange", label: "Orange", primary: "#fd7e14", primaryFg: "#fff", activeBg: "#fff4e6", activeFg: "#d9480f", ring: "#ffd8a8" },
{ id: "green", label: "Green", primary: "#40c057", primaryFg: "#fff", activeBg: "#ebfbee", activeFg: "#2f9e44", ring: "#b2f2bb" },
];
export const HEROUI_THEMES: ThemeColor[] = [
{ id: "violet", label: "Violet", primary: "#7c3aed", primaryFg: "#fff", activeBg: "rgba(124,58,237,0.3)", activeFg: "#c084fc", ring: "rgba(168,85,247,0.3)" },
{ id: "blue", label: "Blue", primary: "#2563eb", primaryFg: "#fff", activeBg: "rgba(37,99,235,0.3)", activeFg: "#93c5fd", ring: "rgba(59,130,246,0.3)" },
{ id: "rose", label: "Rose", primary: "#e11d48", primaryFg: "#fff", activeBg: "rgba(225,29,72,0.3)", activeFg: "#fda4af", ring: "rgba(244,63,94,0.3)" },
{ id: "green", label: "Green", primary: "#16a34a", primaryFg: "#fff", activeBg: "rgba(22,163,74,0.3)", activeFg: "#86efac", ring: "rgba(34,197,94,0.3)" },
];
export const TREMOR_THEMES: ThemeColor[] = [
{ id: "blue", label: "Blue", primary: "#2563eb", primaryFg: "#fff", activeBg: "#eff6ff", activeFg: "#1d4ed8", ring: "#bfdbfe" },
{ id: "indigo", label: "Indigo", primary: "#4f46e5", primaryFg: "#fff", activeBg: "#eef2ff", activeFg: "#3730a3", ring: "#c7d2fe" },
{ id: "teal", label: "Teal", primary: "#0d9488", primaryFg: "#fff", activeBg: "#f0fdfa", activeFg: "#0f766e", ring: "#99f6e4" },
{ id: "rose", label: "Rose", primary: "#e11d48", primaryFg: "#fff", activeBg: "#fff1f2", activeFg: "#be123c", ring: "#fecdd3" },
];
export const DAISY_THEMES: ThemeColor[] = [
{ id: "dark", label: "Dark", primary: "#793ef9", primaryFg: "#fff", activeBg: "rgba(121,62,249,0.2)", activeFg: "#a78bfa", ring: "#4c1d95", bg: "#1d232a", cardBg: "#191e24", textColor: "#a6adba", borderColor: "#2a323c", mutedText: "#6b7280" },
{ id: "light", label: "Light", primary: "#570df8", primaryFg: "#fff", activeBg: "#f3f0ff", activeFg: "#4c1d95", ring: "#ddd6fe", bg: "#fff", cardBg: "#fff", textColor: "#1f2937", borderColor: "#e5e7eb", mutedText: "#6b7280" },
{ id: "cupcake", label: "Cupcake", primary: "#65c3c8", primaryFg: "#291334", activeBg: "#d9f5f6", activeFg: "#0e6b70", ring: "#a7eaec", bg: "#faf7f5", cardBg: "#fff", textColor: "#291334", borderColor: "#e9e3df", mutedText: "#9ca3af" },
{ id: "synthwave", label: "Synth", primary: "#e779c1", primaryFg: "#2d1b69", activeBg: "rgba(231,121,193,0.2)", activeFg: "#f0abdc", ring: "#701a75", bg: "#1a103c", cardBg: "#221551", textColor: "#e2e8f0", borderColor: "#4c3585", mutedText: "#a78bfa" },
{ id: "dracula", label: "Dracula", primary: "#ff79c6", primaryFg: "#282a36", activeBg: "rgba(255,121,198,0.15)",activeFg: "#ff79c6", ring: "#bd93f9", bg: "#282a36", cardBg: "#343746", textColor: "#f8f8f2", borderColor: "#44475a", mutedText: "#6272a4" },
{ id: "forest", label: "Forest", primary: "#1eb854", primaryFg: "#fff", activeBg: "rgba(30,184,84,0.15)", activeFg: "#1eb854", ring: "#15803d", bg: "#171212", cardBg: "#1d1d1d", textColor: "#d1d5db", borderColor: "#292929", mutedText: "#4b5563" },
{ id: "retro", label: "Retro", primary: "#ef9995", primaryFg: "#282425", activeBg: "#fde8e7", activeFg: "#7f1d1d", ring: "#fca5a5", bg: "#e4d8b4", cardBg: "#f7f0d8", textColor: "#282425", borderColor: "#d4b483", mutedText: "#6b5745" },
{ id: "winter", label: "Winter", primary: "#047aed", primaryFg: "#fff", activeBg: "#e0f0ff", activeFg: "#0369a1", ring: "#bae6fd", bg: "#fff", cardBg: "#f0f9ff", textColor: "#1e3a5f", borderColor: "#bae6fd", mutedText: "#64748b" },
];
export const HEROUI_MARKETING_THEMES: ThemeColor[] = [
{ id: "purple", label: "Purple", primary: "#7c3aed", primaryFg: "#fff", activeBg: "rgba(124,58,237,0.08)", activeFg: "#7c3aed", ring: "rgba(124,58,237,0.15)", bg: "#fff" },
{ id: "blue", label: "Blue", primary: "#2563eb", primaryFg: "#fff", activeBg: "rgba(37,99,235,0.08)", activeFg: "#2563eb", ring: "rgba(37,99,235,0.15)", bg: "#fff" },
{ id: "teal", label: "Teal", primary: "#0d9488", primaryFg: "#fff", activeBg: "rgba(13,148,136,0.08)", activeFg: "#0d9488", ring: "rgba(13,148,136,0.15)", bg: "#fff" },
{ id: "dark", label: "Dark", primary: "#7c3aed", primaryFg: "#fff", activeBg: "rgba(124,58,237,0.2)", activeFg: "#c084fc", ring: "rgba(124,58,237,0.3)", bg: "#09090b", cardBg: "#18181b", textColor: "#f4f4f5", borderColor: "#27272a", mutedText: "#71717a" },
{ id: "modern", label: "Modern", primary: "#06b6d4", primaryFg: "#fff", activeBg: "rgba(6,182,212,0.08)", activeFg: "#06b6d4", ring: "rgba(6,182,212,0.15)", bg: "#fff" },
];

View File

@@ -0,0 +1,403 @@
"use client";
import { useState } from "react";
import { ThemeColor, TABLE_ROWS, SHADCN_THEMES, MANTINE_THEMES, HEROUI_THEMES, TREMOR_THEMES } from "./types";
// Web App surface — shows a logged-in SaaS product: Dashboard, Users, Settings
// This is what the user's customers see after signing in.
type Page = "Dashboard" | "Users" | "Settings";
// ---------------------------------------------------------------------------
// Shared layout shell — nav + header are reused across libraries
// ---------------------------------------------------------------------------
function AppShell({
brand, navBg, navText, navItems, activePage, onNav, headerBg, header, children,
}: {
brand: string; navBg: string; navText: string;
navItems: { label: Page; icon: string }[];
activePage: Page; onNav: (p: Page) => void;
headerBg: string; header: React.ReactNode; children: React.ReactNode;
}) {
return (
<div style={{ display: "flex", height: "100%", fontFamily: "system-ui, sans-serif", fontSize: 14 }}>
<div style={{ width: 168, flexShrink: 0, background: navBg, borderRight: "1px solid rgba(0,0,0,0.07)", display: "flex", flexDirection: "column", padding: "14px 10px" }}>
<div style={{ display: "flex", alignItems: "center", gap: 8, padding: "0 6px", marginBottom: 18 }}>
<div style={{ width: 22, height: 22, borderRadius: 5, background: brand, flexShrink: 0 }} />
<span style={{ fontWeight: 700, fontSize: 11, color: navText }}>Acme Inc</span>
</div>
{navItems.map(({ label, icon }) => (
<button key={label} onClick={() => onNav(label)} style={{
display: "flex", alignItems: "center", gap: 7,
width: "100%", padding: "7px 8px", borderRadius: 6, border: "none",
background: "none", cursor: "pointer", textAlign: "left",
fontSize: 11, fontWeight: activePage === label ? 600 : 400,
color: activePage === label ? navText : `${navText}80`,
marginBottom: 2,
}}>
<span style={{ fontSize: 10, opacity: 0.6 }}>{icon}</span>{label}
</button>
))}
</div>
<div style={{ flex: 1, display: "flex", flexDirection: "column", minWidth: 0 }}>
<div style={{ height: 44, background: headerBg, borderBottom: "1px solid rgba(0,0,0,0.07)", display: "flex", alignItems: "center", justifyContent: "space-between", padding: "0 18px", flexShrink: 0 }}>
{header}
</div>
<div style={{ flex: 1, overflow: "auto" }}>
{children}
</div>
</div>
</div>
);
}
// ---------------------------------------------------------------------------
// shadcn/ui
// ---------------------------------------------------------------------------
export function WebAppShadcn({ themeColor }: { themeColor?: ThemeColor }) {
const [page, setPage] = useState<Page>("Dashboard");
const t = themeColor ?? SHADCN_THEMES[0];
const NAV = [{ label: "Dashboard" as Page, icon: "▦" }, { label: "Users" as Page, icon: "◎" }, { label: "Settings" as Page, icon: "⚙" }];
return (
<AppShell brand={t.primary} navBg="#fff" navText="#18181b"
navItems={NAV} activePage={page} onNav={setPage}
headerBg="#fff" header={<>
<span style={{ fontWeight: 600, fontSize: 13, color: "#18181b" }}>{page}</span>
<div style={{ display: "flex", gap: 6 }}>
<div style={{ height: 28, padding: "0 12px", borderRadius: 6, border: "1px solid #e4e4e7", display: "flex", alignItems: "center", fontSize: 11, color: "#71717a" }}>Export</div>
<div style={{ height: 28, padding: "0 12px", borderRadius: 6, background: t.primary, color: t.primaryFg, display: "flex", alignItems: "center", fontSize: 11, fontWeight: 600 }}>+ New</div>
</div>
</>}
>
{page === "Dashboard" && (
<div style={{ padding: 18, background: "#fafafa" }}>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 10, marginBottom: 14 }}>
{[["Total Revenue", "$12,400", "+12%"], ["Active Users", "2,841", "+8%"], ["Conversions", "18.2%", "+3%"]].map(([l, v, d]) => (
<div key={l} style={{ background: "#fff", border: "1px solid #e4e4e7", borderRadius: 8, padding: "12px 14px" }}>
<p style={{ fontSize: 10, color: "#71717a", marginBottom: 4 }}>{l}</p>
<p style={{ fontSize: 18, fontWeight: 700, color: "#09090b", marginBottom: 2 }}>{v}</p>
<p style={{ fontSize: 10, color: t.activeFg }}>{d} from last month</p>
</div>
))}
</div>
<div style={{ background: "#fff", border: "1px solid #e4e4e7", borderRadius: 8, marginBottom: 10 }}>
<div style={{ padding: "10px 14px", borderBottom: "1px solid #f4f4f5", fontSize: 11, fontWeight: 600, color: "#09090b" }}>Revenue</div>
<div style={{ padding: "14px 14px", display: "flex", alignItems: "flex-end", gap: 4, height: 80 }}>
{[40,60,45,75,65,85,70,90,55,80,75,95].map((h, i) => (
<div key={i} style={{ flex: 1, borderRadius: 3, background: i === 11 ? t.primary : t.ring, height: `${h}%` }} />
))}
</div>
</div>
<div style={{ background: "#fff", border: "1px solid #e4e4e7", borderRadius: 8 }}>
{TABLE_ROWS.slice(0, 3).map(r => (
<div key={r.name} style={{ display: "flex", alignItems: "center", gap: 10, padding: "10px 14px", borderBottom: "1px solid #f9f9f9" }}>
<div style={{ width: 24, height: 24, borderRadius: "50%", background: t.ring, flexShrink: 0 }} />
<div style={{ flex: 1 }}>
<p style={{ fontSize: 11, fontWeight: 500, color: "#09090b" }}>{r.name}</p>
<p style={{ fontSize: 10, color: "#a1a1aa" }}>{r.email}</p>
</div>
<span style={{ fontSize: 10, fontWeight: 600, padding: "2px 7px", borderRadius: 4, background: r.status === "Active" ? t.activeBg : "#f4f4f5", color: r.status === "Active" ? t.activeFg : "#a1a1aa" }}>{r.status}</span>
</div>
))}
</div>
</div>
)}
{page === "Users" && (
<div style={{ padding: 18, background: "#fafafa" }}>
<div style={{ background: "#fff", border: "1px solid #e4e4e7", borderRadius: 8 }}>
<div style={{ padding: "10px 14px", borderBottom: "1px solid #f4f4f5", display: "flex", alignItems: "center", justifyContent: "space-between" }}>
<span style={{ fontSize: 11, fontWeight: 600, color: "#09090b" }}>Team members</span>
<div style={{ display: "flex", gap: 6 }}>
<input style={{ height: 28, padding: "0 10px", border: "1px solid #e4e4e7", borderRadius: 6, fontSize: 11, width: 120, outline: "none" }} placeholder="Search..." />
<div style={{ height: 28, padding: "0 12px", borderRadius: 6, background: t.primary, color: t.primaryFg, display: "flex", alignItems: "center", fontSize: 11, fontWeight: 600 }}>Invite</div>
</div>
</div>
{TABLE_ROWS.map(r => (
<div key={r.name} style={{ display: "flex", alignItems: "center", padding: "10px 14px", borderBottom: "1px solid #f9f9f9", gap: 10 }}>
<div style={{ width: 24, height: 24, borderRadius: "50%", background: t.ring, flexShrink: 0 }} />
<div style={{ flex: 1 }}>
<p style={{ fontSize: 11, fontWeight: 500, color: "#09090b" }}>{r.name}</p>
<p style={{ fontSize: 10, color: "#a1a1aa" }}>{r.email}</p>
</div>
<span style={{ fontSize: 10, padding: "2px 7px", borderRadius: 4, border: "1px solid #e4e4e7", color: "#71717a" }}>{r.role}</span>
<span style={{ fontSize: 10, fontWeight: 600, padding: "2px 7px", borderRadius: 4, background: r.status === "Active" ? t.activeBg : "#f4f4f5", color: r.status === "Active" ? t.activeFg : "#a1a1aa" }}>{r.status}</span>
<span style={{ fontSize: 10, color: "#d4d4d8" }}>{r.date}</span>
</div>
))}
</div>
</div>
)}
{page === "Settings" && (
<div style={{ padding: 18, background: "#fafafa" }}>
<div style={{ background: "#fff", border: "1px solid #e4e4e7", borderRadius: 8, marginBottom: 10 }}>
<div style={{ padding: "10px 14px", borderBottom: "1px solid #f4f4f5", fontSize: 11, fontWeight: 600, color: "#09090b" }}>General</div>
<div style={{ padding: 14, display: "flex", flexDirection: "column", gap: 10 }}>
{[["Workspace name", "Acme Inc"], ["Slug", "acme-inc"], ["Email", "admin@acme.com"]].map(([l, v]) => (
<div key={l}>
<label style={{ fontSize: 10, fontWeight: 500, color: "#71717a", display: "block", marginBottom: 4 }}>{l}</label>
<input defaultValue={v} style={{ width: "100%", height: 32, padding: "0 10px", border: "1px solid #e4e4e7", borderRadius: 6, fontSize: 11, outline: "none" }} />
</div>
))}
<div style={{ height: 30, padding: "0 14px", borderRadius: 6, background: t.primary, color: t.primaryFg, display: "inline-flex", alignItems: "center", fontSize: 11, fontWeight: 600, alignSelf: "flex-start" }}>Save changes</div>
</div>
</div>
</div>
)}
</AppShell>
);
}
// ---------------------------------------------------------------------------
// Mantine
// ---------------------------------------------------------------------------
export function WebAppMantine({ themeColor }: { themeColor?: ThemeColor }) {
const [page, setPage] = useState<Page>("Dashboard");
const t = themeColor ?? MANTINE_THEMES[0];
const NAV = [{ label: "Dashboard" as Page, icon: "▦" }, { label: "Users" as Page, icon: "◎" }, { label: "Settings" as Page, icon: "⚙" }];
return (
<AppShell brand={t.primary} navBg="#fff" navText="#212529"
navItems={NAV} activePage={page} onNav={setPage}
headerBg="#fff" header={<>
<span style={{ fontWeight: 700, fontSize: 13, color: "#212529" }}>{page}</span>
<div style={{ display: "flex", gap: 6 }}>
<button style={{ height: 28, padding: "0 12px", border: "1px solid #dee2e6", borderRadius: 5, fontSize: 11, background: "none", color: "#495057" }}>Export</button>
<button style={{ height: 28, padding: "0 12px", borderRadius: 5, background: t.primary, color: t.primaryFg, border: "none", fontSize: 11, fontWeight: 600 }}>+ New</button>
</div>
</>}
>
{page === "Dashboard" && (
<div style={{ padding: 18, background: "#f8f9fa" }}>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 10, marginBottom: 14 }}>
{[["Total Revenue", "$12,400", "#2f9e44"], ["Active Users", "2,841", "#228be6"], ["Conversions", "18.2%", "#e67700"]].map(([l, v, c]) => (
<div key={l} style={{ background: "#fff", border: "1px solid #e9ecef", borderRadius: 8, padding: "12px 14px" }}>
<p style={{ fontSize: 10, color: "#868e96", marginBottom: 4 }}>{l}</p>
<p style={{ fontSize: 18, fontWeight: 700, color: "#212529", marginBottom: 2 }}>{v}</p>
<p style={{ fontSize: 10, color: c }}> trending up</p>
</div>
))}
</div>
<div style={{ background: "#fff", border: "1px solid #e9ecef", borderRadius: 8, marginBottom: 10 }}>
<div style={{ padding: "10px 14px", borderBottom: "1px solid #e9ecef", fontSize: 11, fontWeight: 600, color: "#212529" }}>Monthly revenue</div>
<div style={{ padding: "14px", display: "flex", alignItems: "flex-end", gap: 4, height: 80 }}>
{[40,60,45,75,65,85,70,90,55,80,75,95].map((h, i) => (
<div key={i} style={{ flex: 1, borderRadius: 2, background: i === 11 ? t.primary : t.ring, height: `${h}%` }} />
))}
</div>
</div>
<div style={{ background: "#fff", border: "1px solid #e9ecef", borderRadius: 8 }}>
{TABLE_ROWS.slice(0, 3).map(r => (
<div key={r.name} style={{ display: "flex", alignItems: "center", gap: 10, padding: "10px 14px", borderBottom: "1px solid #f1f3f5" }}>
<div style={{ width: 26, height: 26, borderRadius: "50%", background: t.ring, flexShrink: 0 }} />
<div style={{ flex: 1 }}>
<p style={{ fontSize: 11, fontWeight: 600, color: "#212529" }}>{r.name}</p>
<p style={{ fontSize: 10, color: "#868e96" }}>{r.email}</p>
</div>
<span style={{ fontSize: 10, fontWeight: 600, padding: "2px 7px", borderRadius: 4, background: r.status === "Active" ? "#d3f9d8" : "#f1f3f5", color: r.status === "Active" ? "#2f9e44" : "#868e96" }}>{r.status}</span>
</div>
))}
</div>
</div>
)}
{page === "Users" && (
<div style={{ padding: 18, background: "#f8f9fa" }}>
<div style={{ background: "#fff", border: "1px solid #e9ecef", borderRadius: 8 }}>
<div style={{ padding: "10px 14px", borderBottom: "1px solid #e9ecef", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<span style={{ fontSize: 11, fontWeight: 600, color: "#212529" }}>Team members</span>
<button style={{ height: 28, padding: "0 12px", borderRadius: 5, background: t.primary, color: t.primaryFg, border: "none", fontSize: 11, fontWeight: 600 }}>+ Invite</button>
</div>
{TABLE_ROWS.map(r => (
<div key={r.name} style={{ display: "flex", alignItems: "center", padding: "10px 14px", borderBottom: "1px solid #f1f3f5", gap: 10 }}>
<div style={{ width: 26, height: 26, borderRadius: "50%", background: t.ring, flexShrink: 0 }} />
<div style={{ flex: 1 }}>
<p style={{ fontSize: 11, fontWeight: 600, color: "#212529" }}>{r.name}</p>
<p style={{ fontSize: 10, color: "#868e96" }}>{r.email}</p>
</div>
<span style={{ fontSize: 10, padding: "2px 7px", borderRadius: 4, background: t.activeBg, color: t.activeFg }}>{r.role}</span>
<span style={{ fontSize: 10, fontWeight: 600, padding: "2px 7px", borderRadius: 4, background: r.status === "Active" ? "#d3f9d8" : r.status === "Pending" ? "#fff3bf" : "#f1f3f5", color: r.status === "Active" ? "#2f9e44" : r.status === "Pending" ? "#e67700" : "#868e96" }}>{r.status}</span>
</div>
))}
</div>
</div>
)}
{page === "Settings" && (
<div style={{ padding: 18, background: "#f8f9fa" }}>
<div style={{ background: "#fff", border: "1px solid #e9ecef", borderRadius: 8 }}>
<div style={{ padding: "10px 14px", borderBottom: "1px solid #e9ecef", fontSize: 11, fontWeight: 600, color: "#212529" }}>Workspace</div>
<div style={{ padding: 14, display: "flex", flexDirection: "column", gap: 10 }}>
{[["Name", "Acme Inc"], ["Slug", "acme-inc"], ["Email", "admin@acme.com"]].map(([l, v]) => (
<div key={l}>
<label style={{ fontSize: 10, fontWeight: 600, color: "#495057", display: "block", marginBottom: 4 }}>{l}</label>
<input defaultValue={v} style={{ width: "100%", height: 32, padding: "0 10px", border: "1px solid #dee2e6", borderRadius: 5, fontSize: 11, outline: "none" }} />
</div>
))}
<button style={{ height: 30, padding: "0 14px", borderRadius: 5, background: t.primary, color: t.primaryFg, border: "none", fontSize: 11, fontWeight: 600, alignSelf: "flex-start" }}>Save</button>
</div>
</div>
</div>
)}
</AppShell>
);
}
// ---------------------------------------------------------------------------
// HeroUI
// ---------------------------------------------------------------------------
export function WebAppHeroUI({ themeColor }: { themeColor?: ThemeColor }) {
const [page, setPage] = useState<Page>("Dashboard");
const t = themeColor ?? HEROUI_THEMES[0];
const NAV = [{ label: "Dashboard" as Page, icon: "▦" }, { label: "Users" as Page, icon: "◎" }, { label: "Settings" as Page, icon: "⚙" }];
return (
<AppShell brand={t.primary} navBg="linear-gradient(180deg,#1a1a2e,#16213e)" navText="#fff"
navItems={NAV} activePage={page} onNav={setPage}
headerBg="#fff" header={<>
<span style={{ fontWeight: 700, fontSize: 13, color: t.primary }}>{page}</span>
<div style={{ display: "flex", gap: 6 }}>
<button style={{ height: 28, padding: "0 12px", borderRadius: 20, border: "1px solid #e4e4e7", fontSize: 11, background: "none", color: "#71717a" }}>Export</button>
<button style={{ height: 28, padding: "0 12px", borderRadius: 20, background: t.primary, color: t.primaryFg, border: "none", fontSize: 11, fontWeight: 600 }}>+ New</button>
</div>
</>}
>
{page === "Dashboard" && (
<div style={{ padding: 18, background: "#fafafa" }}>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 10, marginBottom: 14 }}>
{[["Revenue", "$12,400"], ["Users", "2,841"], ["Conversion", "18.2%"]].map(([l, v]) => (
<div key={l} style={{ background: "#fff", border: "1px solid #f4f4f5", borderRadius: 14, padding: "12px 14px", boxShadow: "0 2px 8px rgba(0,0,0,0.05)" }}>
<p style={{ fontSize: 10, color: "#a1a1aa", marginBottom: 4 }}>{l}</p>
<p style={{ fontSize: 18, fontWeight: 700, color: "#18181b" }}>{v}</p>
<p style={{ fontSize: 10, color: t.primary }}> this month</p>
</div>
))}
</div>
<div style={{ background: "#fff", border: "1px solid #f4f4f5", borderRadius: 14, padding: 14, boxShadow: "0 2px 8px rgba(0,0,0,0.05)" }}>
<div style={{ display: "flex", alignItems: "flex-end", gap: 4, height: 72 }}>
{[40,60,45,75,65,85,70,90,55,80,75,95].map((h, i) => (
<div key={i} style={{ flex: 1, borderRadius: 5, background: i === 11 ? `linear-gradient(180deg,${t.primary},${t.ring})` : t.activeBg, height: `${h}%` }} />
))}
</div>
</div>
</div>
)}
{page === "Users" && (
<div style={{ padding: 18, background: "#fafafa" }}>
<div style={{ background: "#fff", border: "1px solid #f4f4f5", borderRadius: 14, boxShadow: "0 2px 8px rgba(0,0,0,0.05)" }}>
<div style={{ padding: "10px 14px", borderBottom: "1px solid #f4f4f5", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<span style={{ fontSize: 11, fontWeight: 700, color: "#18181b" }}>Team</span>
<button style={{ height: 28, padding: "0 14px", borderRadius: 20, background: `linear-gradient(135deg,${t.primary},${t.ring})`, color: "#fff", border: "none", fontSize: 11, fontWeight: 600 }}>+ Invite</button>
</div>
{TABLE_ROWS.map(r => (
<div key={r.name} style={{ display: "flex", alignItems: "center", padding: "10px 14px", borderBottom: "1px solid #fafafa", gap: 10 }}>
<div style={{ width: 28, height: 28, borderRadius: "50%", background: t.activeBg, flexShrink: 0 }} />
<div style={{ flex: 1 }}>
<p style={{ fontSize: 11, fontWeight: 600, color: "#18181b" }}>{r.name}</p>
<p style={{ fontSize: 10, color: "#a1a1aa" }}>{r.email}</p>
</div>
<span style={{ fontSize: 10, padding: "2px 8px", borderRadius: 20, background: t.activeBg, color: t.activeFg }}>{r.role}</span>
<span style={{ fontSize: 10, fontWeight: 600, padding: "2px 8px", borderRadius: 20, background: r.status === "Active" ? "rgba(34,197,94,0.1)" : "#f4f4f5", color: r.status === "Active" ? "#16a34a" : "#a1a1aa" }}>{r.status}</span>
</div>
))}
</div>
</div>
)}
{page === "Settings" && (
<div style={{ padding: 18, background: "#fafafa" }}>
<div style={{ background: "#fff", border: "1px solid #f4f4f5", borderRadius: 14, boxShadow: "0 2px 8px rgba(0,0,0,0.05)" }}>
<div style={{ padding: "10px 14px", borderBottom: "1px solid #f4f4f5", fontSize: 11, fontWeight: 700, color: "#18181b" }}>Profile</div>
<div style={{ padding: 14, display: "flex", flexDirection: "column", gap: 10 }}>
{[["Workspace", "Acme Inc"], ["Slug", "acme-inc"], ["Email", "admin@acme.com"]].map(([l, v]) => (
<div key={l}>
<label style={{ fontSize: 10, fontWeight: 600, color: "#71717a", display: "block", marginBottom: 4 }}>{l}</label>
<input defaultValue={v} style={{ width: "100%", height: 32, padding: "0 12px", border: "1px solid #e4e4e7", borderRadius: 10, fontSize: 11, outline: "none" }} />
</div>
))}
<button style={{ height: 30, padding: "0 16px", borderRadius: 20, background: `linear-gradient(135deg,${t.primary},${t.ring})`, color: "#fff", border: "none", fontSize: 11, fontWeight: 700, alignSelf: "flex-start" }}>Save changes</button>
</div>
</div>
</div>
)}
</AppShell>
);
}
// ---------------------------------------------------------------------------
// Tremor (analytics/data-heavy)
// ---------------------------------------------------------------------------
export function WebAppTremor({ themeColor }: { themeColor?: ThemeColor }) {
const [page, setPage] = useState<Page>("Dashboard");
const t = themeColor ?? TREMOR_THEMES[0];
const NAV = [{ label: "Dashboard" as Page, icon: "▦" }, { label: "Users" as Page, icon: "◎" }, { label: "Settings" as Page, icon: "⚙" }];
return (
<AppShell brand={t.primary} navBg="#fff" navText="#374151"
navItems={NAV} activePage={page} onNav={setPage}
headerBg="#fff" header={<>
<span style={{ fontWeight: 700, fontSize: 13, color: "#111827" }}>{page}</span>
<button style={{ height: 28, padding: "0 12px", borderRadius: 7, background: t.primary, color: t.primaryFg, border: "none", fontSize: 11, fontWeight: 600 }}>+ New</button>
</>}
>
{page === "Dashboard" && (
<div style={{ padding: 18, background: "#f9fafb" }}>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 10, marginBottom: 14 }}>
{[{ l: "Revenue", v: "$12,400", c: t.primary, pct: 65 }, { l: "Users", v: "2,841", c: "#7c3aed", pct: 48 }, { l: "Conversion", v: "18.2%", c: "#059669", pct: 72 }].map(item => (
<div key={item.l} style={{ background: "#fff", border: "1px solid #e5e7eb", borderRadius: 10, padding: "12px 14px" }}>
<p style={{ fontSize: 10, color: "#6b7280", marginBottom: 6 }}>{item.l}</p>
<p style={{ fontSize: 18, fontWeight: 700, color: "#111827", marginBottom: 6 }}>{item.v}</p>
<div style={{ height: 5, borderRadius: 9, background: "#f3f4f6" }}>
<div style={{ height: "100%", borderRadius: 9, width: `${item.pct}%`, background: item.c }} />
</div>
</div>
))}
</div>
<div style={{ background: "#fff", border: "1px solid #e5e7eb", borderRadius: 10, padding: 14 }}>
<p style={{ fontSize: 11, fontWeight: 600, color: "#111827", marginBottom: 10 }}>Revenue over time</p>
<div style={{ display: "flex", alignItems: "flex-end", gap: 4, height: 70 }}>
{[40,65,55,80,70,90,75,85,60,95,80,100].map((h, i) => (
<div key={i} style={{ flex: 1, borderRadius: 3, background: i === 11 ? t.primary : t.activeBg, height: `${h}%` }} />
))}
</div>
</div>
</div>
)}
{page === "Users" && (
<div style={{ padding: 18, background: "#f9fafb" }}>
<div style={{ background: "#fff", border: "1px solid #e5e7eb", borderRadius: 10 }}>
<div style={{ padding: "10px 14px", borderBottom: "1px solid #f3f4f6", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<span style={{ fontSize: 11, fontWeight: 600, color: "#111827" }}>All users</span>
<input style={{ height: 28, padding: "0 10px", border: "1px solid #e5e7eb", borderRadius: 7, fontSize: 11, width: 120, outline: "none" }} placeholder="Filter..." />
</div>
{TABLE_ROWS.map(r => (
<div key={r.name} style={{ display: "flex", alignItems: "center", padding: "10px 14px", borderBottom: "1px solid #f9fafb", gap: 10 }}>
<span style={{ flex: 1, fontSize: 11, fontWeight: 500, color: "#111827" }}>{r.name}</span>
<span style={{ fontSize: 10, color: "#6b7280" }}>{r.role}</span>
<div style={{ display: "flex", alignItems: "center", gap: 4 }}>
<div style={{ width: 6, height: 6, borderRadius: "50%", background: r.status === "Active" ? "#059669" : r.status === "Pending" ? "#d97706" : "#d1d5db" }} />
<span style={{ fontSize: 10, color: "#374151" }}>{r.status}</span>
</div>
</div>
))}
</div>
</div>
)}
{page === "Settings" && (
<div style={{ padding: 18, background: "#f9fafb" }}>
<div style={{ background: "#fff", border: "1px solid #e5e7eb", borderRadius: 10, padding: 14 }}>
<p style={{ fontSize: 11, fontWeight: 600, color: "#111827", marginBottom: 12 }}>Workspace</p>
<div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
{[["Name", "Acme Inc"], ["Domain", "acme.com"], ["Timezone", "UTC-8"]].map(([l, v]) => (
<div key={l}>
<label style={{ fontSize: 10, fontWeight: 500, color: "#6b7280", display: "block", marginBottom: 4 }}>{l}</label>
<input defaultValue={v} style={{ width: "100%", height: 32, padding: "0 10px", border: "1px solid #e5e7eb", borderRadius: 7, fontSize: 11, outline: "none" }} />
</div>
))}
<button style={{ height: 30, padding: "0 14px", borderRadius: 7, background: t.primary, color: t.primaryFg, border: "none", fontSize: 11, fontWeight: 600, alignSelf: "flex-start" }}>Save</button>
</div>
</div>
</div>
)}
</AppShell>
);
}
export { SHADCN_THEMES, MANTINE_THEMES, HEROUI_THEMES, TREMOR_THEMES };