From 7ba3b9563ee8e86391d0872eb49f7a1a206d7cc0 Mon Sep 17 00:00:00 2001 From: Mark Henderson Date: Mon, 2 Mar 2026 14:06:53 -0800 Subject: [PATCH] refactor: move all design controls below scaffold render MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Theme swatches removed from inside scaffold components. Theme state lifted to SurfaceSection which passes themeColor down as a prop. Controls bar below the scaffold now has three rows: 1. Library tabs (shadcn / Mantine / HeroUI / Tremor etc.) 2. Color theme swatches — only shown when the active library has theme variants (shadcn: 8, Mantine: 6, HeroUI: 5, Tremor: 5, DaisyUI: 12, HeroUI marketing: 6) 3. Description + tags + Docs link + Lock in button Scaffold renders cleanly with no UI chrome inside it. Made-with: Cursor --- .../project/[projectId]/design/page.tsx | 81 ++++++++++++------- components/design-scaffolds.tsx | 59 +++++++------- 2 files changed, 82 insertions(+), 58 deletions(-) diff --git a/app/[workspace]/project/[projectId]/design/page.tsx b/app/[workspace]/project/[projectId]/design/page.tsx index daf9b83..e452f44 100644 --- a/app/[workspace]/project/[projectId]/design/page.tsx +++ b/app/[workspace]/project/[projectId]/design/page.tsx @@ -9,7 +9,7 @@ import { Monitor, Globe, Settings, Smartphone, Mail, BookOpen, Lock, CheckCircle2, Loader2, ChevronRight, Pencil, } from "lucide-react"; -import { SCAFFOLD_REGISTRY } from "@/components/design-scaffolds"; +import { SCAFFOLD_REGISTRY, THEME_REGISTRY, type ThemeColor } from "@/components/design-scaffolds"; // --------------------------------------------------------------------------- // Surface definitions @@ -314,30 +314,33 @@ function SurfaceSection({ // Active preview tab — if locked show that, otherwise the selected/first const previewId = lockedThemeId ?? selectedThemeId ?? surface.themes[0]?.id ?? null; const activeTheme = surface.themes.find(t => t.id === previewId); - const ScaffoldComponent = previewId - ? SCAFFOLD_REGISTRY[surface.id]?.[previewId] - : null; + const ScaffoldComponent = previewId ? SCAFFOLD_REGISTRY[surface.id]?.[previewId] : null; + + // Theme color variants for the active library (e.g. shadcn has 8 color themes) + const availableColorThemes: ThemeColor[] = previewId + ? (THEME_REGISTRY[surface.id]?.[previewId] ?? []) + : []; + const [selectedColorTheme, setSelectedColorTheme] = useState(null); + const activeColorTheme = selectedColorTheme ?? availableColorThemes[0] ?? null; return (
{/* Scaffold preview — browser chrome frame */}
- {/* Browser chrome */}
- {activeTheme ? `/${surface.id} — ${activeTheme.name}` : ""} + {activeTheme ? `/${surface.id} — ${activeTheme.name}${activeColorTheme ? ` / ${activeColorTheme.label}` : ""}` : ""}
- {/* Scaffold */}
{ScaffoldComponent - ? + ? : (
Select a library below to preview @@ -347,9 +350,10 @@ function SurfaceSection({
- {/* Controls bar — library tabs + description + lock in */} -
- {/* Library tab row */} + {/* Controls bar — all below the render */} +
+ + {/* Row 1: library tabs */}
{surface.themes.map(theme => { const isActive = theme.id === previewId; @@ -357,13 +361,11 @@ function SurfaceSection({ return (
- {/* Description + tags + actions */} + {/* Row 2: color theme swatches (only if this library has color variants) */} + {availableColorThemes.length > 0 && ( +
+ Theme +
+ {availableColorThemes.map(ct => ( +
+
+ )} + + {/* Row 3: description + tags + docs + lock in */}
{activeTheme && ( <> @@ -387,28 +417,19 @@ function SurfaceSection({ {t} ))}
- + Docs ↗ )} {lockedThemeId ? ( ) : ( - diff --git a/components/design-scaffolds.tsx b/components/design-scaffolds.tsx index 735c8fb..8d6473c 100644 --- a/components/design-scaffolds.tsx +++ b/components/design-scaffolds.tsx @@ -221,9 +221,9 @@ function ShadcnSettings({ t }: { t: ThemeColor }) { ); } -export function WebAppShadcn() { +export function WebAppShadcn({ themeColor }: { themeColor?: ThemeColor }) { const [page, setPage] = useState("Dashboard"); - const [theme, setTheme] = useState(SHADCN_THEMES[0]); + const theme = themeColor ?? SHADCN_THEMES[0]; const NAV_ITEMS: { label: Page; icon: string }[] = [ { label: "Dashboard", icon: "▦" }, { label: "Users", icon: "◎" }, @@ -231,7 +231,6 @@ export function WebAppShadcn() { ]; return (
- {/* Sidebar */}
@@ -245,12 +244,10 @@ export function WebAppShadcn() { ))}
- {/* Main */}
{page} -
- +
Export
+ New
@@ -357,9 +354,9 @@ function MantineSettings({ t }: { t: ThemeColor }) { ); } -export function WebAppMantine() { +export function WebAppMantine({ themeColor }: { themeColor?: ThemeColor }) { const [page, setPage] = useState("Dashboard"); - const [theme, setTheme] = useState(MANTINE_THEMES[0]); + const theme = themeColor ?? MANTINE_THEMES[0]; const NAV: { label: Page; icon: string }[] = [ { label: "Dashboard", icon: "▦" }, { label: "Users", icon: "◎" }, @@ -383,8 +380,7 @@ export function WebAppMantine() {
{page} -
- +
@@ -491,9 +487,9 @@ function HeroUISettings() { ); } -export function WebAppHeroUI() { +export function WebAppHeroUI({ themeColor }: { themeColor?: ThemeColor }) { const [page, setPage] = useState("Dashboard"); - const [theme, setTheme] = useState(HEROUI_THEMES[0]); + const theme = themeColor ?? HEROUI_THEMES[0]; const NAV: { label: Page; icon: string }[] = [ { label: "Dashboard", icon: "▦" }, { label: "Users", icon: "◎" }, @@ -517,8 +513,7 @@ export function WebAppHeroUI() {
{page} -
- +
@@ -632,9 +627,9 @@ function TremorSettings() { ); } -export function WebAppTremor() { +export function WebAppTremor({ themeColor }: { themeColor?: ThemeColor }) { const [page, setPage] = useState("Dashboard"); - const [theme, setTheme] = useState(TREMOR_THEMES[0]); + const theme = themeColor ?? TREMOR_THEMES[0]; const NAV: { label: Page; icon: string }[] = [ { label: "Dashboard", icon: "▦" }, { label: "Users", icon: "◎" }, @@ -658,8 +653,7 @@ export function WebAppTremor() {
{page} -
- +
@@ -675,11 +669,8 @@ export function WebAppTremor() { // MARKETING scaffolds // --------------------------------------------------------------------------- -export function MarketingDaisy() { - const [theme, setTheme] = useState(DAISY_THEMES[0]); - const isDark = !!theme.bg && ["#1d232a","#282a36","#1a103c","#171212","#20150e","#ffee00"].includes(theme.bg) === false - ? theme.bg.startsWith("#1") || theme.bg.startsWith("#2") || theme.bg === "#ffee00" - : false; +export function MarketingDaisy({ themeColor }: { themeColor?: ThemeColor }) { + const theme = themeColor ?? DAISY_THEMES[0]; const textColor = theme.textColor ?? "#f8f8f2"; const mutedText = theme.mutedText ?? "rgba(255,255,255,0.5)"; const cardBg = theme.cardBg ?? "rgba(255,255,255,0.05)"; @@ -697,7 +688,6 @@ export function MarketingDaisy() { {["Features","Pricing","Docs","Blog"].map(i=>{i})}
-
@@ -749,8 +739,8 @@ const HEROUI_MARKETING_THEMES: ThemeColor[] = [ { 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" }, ]; -export function MarketingHeroUI() { - const [theme, setTheme] = useState(HEROUI_MARKETING_THEMES[0]); +export function MarketingHeroUI({ themeColor }: { themeColor?: ThemeColor }) { + const theme = themeColor ?? HEROUI_MARKETING_THEMES[0]; const isDark = theme.id === "dark"; const bg = theme.bg ?? "#fff"; const textColor = theme.textColor ?? "#18181b"; @@ -769,7 +759,6 @@ export function MarketingHeroUI() { {["Features","Pricing","Docs","Blog"].map(i=>{i})}
-
@@ -1118,7 +1107,7 @@ export function DocsShadcnCustom() { // Registry — maps surface+theme to scaffold component // --------------------------------------------------------------------------- -export const SCAFFOLD_REGISTRY: Record> = { +export const SCAFFOLD_REGISTRY: Record>> = { "web-app": { "shadcn": WebAppShadcn, "mantine": WebAppMantine, @@ -1148,3 +1137,17 @@ export const SCAFFOLD_REGISTRY: Record> = { + "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, + }, +};