- 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
404 lines
26 KiB
TypeScript
404 lines
26 KiB
TypeScript
"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 };
|