671 lines
24 KiB
TypeScript
671 lines
24 KiB
TypeScript
"use client";
|
||
|
||
/**
|
||
* Visual panels for the design kit explorer — all driven by ResolvedKitTokens
|
||
* so previews stay in sync with the user’s starter kit + overrides.
|
||
*/
|
||
|
||
import type { CSSProperties, FC, ReactNode } from "react";
|
||
import type { ResolvedKitTokens } from "@/lib/design-kits/resolve";
|
||
|
||
export interface KitSectionItemMeta {
|
||
id: string;
|
||
title: string;
|
||
subtitle?: string;
|
||
panelKey: string;
|
||
}
|
||
|
||
export interface KitSectionMeta {
|
||
title: string;
|
||
items: KitSectionItemMeta[];
|
||
}
|
||
|
||
export const DESIGN_KIT_SECTIONS: KitSectionMeta[] = [
|
||
{
|
||
title: "Type",
|
||
items: [
|
||
{
|
||
id: "t-fw",
|
||
title: "Font Weights & Colors",
|
||
subtitle: "Weights + semantic text roles (starter font)",
|
||
panelKey: "fontWeights",
|
||
},
|
||
{
|
||
id: "t-scale",
|
||
title: "Type Scale",
|
||
subtitle: "Pixel/rem steps matched to kit density",
|
||
panelKey: "typeScale",
|
||
},
|
||
],
|
||
},
|
||
{
|
||
title: "Colors",
|
||
items: [
|
||
{
|
||
id: "c-accent",
|
||
title: "Accent scale",
|
||
subtitle: "12-step ramp from your accent · CTAs, focus, links",
|
||
panelKey: "accentScale",
|
||
},
|
||
{
|
||
id: "c-gray",
|
||
title: "Gray Scale",
|
||
subtitle: "12-step neutral — surfaces, borders, text",
|
||
panelKey: "grayScale",
|
||
},
|
||
{
|
||
id: "c-semantic",
|
||
title: "Semantic Colors",
|
||
subtitle: "Status & tag hues (reference palette)",
|
||
panelKey: "semanticColors",
|
||
},
|
||
],
|
||
},
|
||
{
|
||
title: "Spacing",
|
||
items: [
|
||
{ id: "s-radius", title: "Border Radius", subtitle: "Derived from radius token", panelKey: "radius" },
|
||
{ id: "s-shadow", title: "Box Shadows", subtitle: "Elevation system + focus line", panelKey: "shadows" },
|
||
{ id: "s-space", title: "Spacing Tokens", subtitle: "4px base unit spacing scale", panelKey: "spacing" },
|
||
],
|
||
},
|
||
{
|
||
title: "Components",
|
||
items: [
|
||
{ id: "x-btn", title: "Buttons", subtitle: "Primary · accent · danger · ghost", panelKey: "buttons" },
|
||
{ id: "x-input", title: "Input Fields", subtitle: "Default · focus · error · disabled", panelKey: "inputs" },
|
||
{ id: "x-nav", title: "Navigation Items", subtitle: "Sidebar density preview", panelKey: "nav" },
|
||
{ id: "x-tag", title: "Tag Colors", subtitle: "Status chips", panelKey: "tags" },
|
||
],
|
||
},
|
||
{
|
||
title: "Brand",
|
||
items: [
|
||
{ id: "b-icon", title: "Illustration Icons", subtitle: "Placeholder grid · swap with assets", panelKey: "illustrations" },
|
||
{ id: "b-logo", title: "Logo & Brand", subtitle: "Mark preview uses accent-on-neutral", panelKey: "logo" },
|
||
],
|
||
},
|
||
];
|
||
|
||
const codeSm: CSSProperties = {
|
||
fontFamily: "var(--font-ibm-plex-mono), ui-monospace, monospace",
|
||
fontSize: "0.68rem",
|
||
color: "#52525b",
|
||
};
|
||
|
||
const panelMiniTitle: CSSProperties = {
|
||
fontSize: "0.65rem",
|
||
fontWeight: 700,
|
||
letterSpacing: "0.06em",
|
||
color: "#a1a1aa",
|
||
marginBottom: 10,
|
||
};
|
||
|
||
const inputBase: CSSProperties = {
|
||
width: "100%",
|
||
boxSizing: "border-box",
|
||
padding: "10px 12px",
|
||
borderRadius: 8,
|
||
border: "1px solid #e4e4e7",
|
||
fontSize: "0.82rem",
|
||
fontFamily: "inherit",
|
||
background: "#fff",
|
||
};
|
||
|
||
function densityPad(tokens: ResolvedKitTokens, base: number): number {
|
||
return tokens.density === "compact" ? Math.round(base * 0.88) : base;
|
||
}
|
||
|
||
function SwatchRow({ colors, labels }: { colors: string[]; labels?: Record<number, string> }) {
|
||
return (
|
||
<div style={{ display: "flex", flexWrap: "wrap", gap: 6, alignItems: "flex-end" }}>
|
||
{colors.map((c, i) => (
|
||
<div key={i} style={{ textAlign: "center", width: 52 }}>
|
||
<div style={{ height: 40, borderRadius: 6, background: c, border: "1px solid #e4e4e7" }} />
|
||
<div style={{ fontSize: "0.62rem", color: "#71717a", marginTop: 4 }}>{labels?.[i] ?? i + 1}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export function FontWeightsPanel({ tokens }: { tokens: ResolvedKitTokens }) {
|
||
return (
|
||
<div style={{ fontFamily: tokens.fontFamily, display: "grid", gridTemplateColumns: "1fr 1fr", gap: 20 }}>
|
||
<div>
|
||
<div style={panelMiniTitle}>Font weights</div>
|
||
{[
|
||
["400", "Regular — body, field values"],
|
||
["500", "Medium — buttons, nav, labels"],
|
||
["600", "Semibold — headings, CTAs"],
|
||
].map(([w, desc]) => (
|
||
<div key={w} style={{ marginBottom: densityPad(tokens, 12) }}>
|
||
<div style={{ fontWeight: Number(w), fontSize: "0.95rem", color: "#18181b" }}>Aa {w}</div>
|
||
<div style={{ fontSize: "0.72rem", color: "#71717a" }}>{desc}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
<div>
|
||
<div style={panelMiniTitle}>Semantic text colors</div>
|
||
{[
|
||
["primary", "#18181b", "Headings, record names"],
|
||
["secondary", "#52525b", "Body, descriptions"],
|
||
["tertiary", "#a1a1aa", "Labels, hints"],
|
||
["accent", tokens.accentHex, "Links & emphasis"],
|
||
["danger", "#dc2626", "Errors"],
|
||
].map(([role, hex, desc]) => (
|
||
<div key={role} style={{ marginBottom: 10, display: "flex", alignItems: "baseline", gap: 10 }}>
|
||
<span style={{ fontSize: "0.72rem", width: 72, color: "#71717a" }}>{role}</span>
|
||
<span style={{ fontSize: "0.88rem", color: hex, fontWeight: 500 }}>Sample</span>
|
||
<span style={{ fontSize: "0.7rem", color: "#a1a1aa", flex: 1 }}>{desc}</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export function TypeScalePanel({ tokens }: { tokens: ResolvedKitTokens }) {
|
||
const scale =
|
||
tokens.density === "compact"
|
||
? [
|
||
{ token: "--font-size-xxl", rem: "1.65rem", sample: "Hero title", note: "Hero" },
|
||
{ token: "--font-size-xl", rem: "1.38rem", sample: "Page title", note: "Page" },
|
||
{ token: "--font-size-lg", rem: "1.12rem", sample: "Section heading", note: "Section" },
|
||
{ token: "--font-size-md", rem: "0.94rem", sample: "Body — primary interface size", note: "Body" },
|
||
{ token: "--font-size-sm", rem: "0.85rem", sample: "Secondary label", note: "Meta" },
|
||
{ token: "--font-size-xs", rem: "0.78rem", sample: "Caption · nav", note: "Dense" },
|
||
]
|
||
: [
|
||
{ token: "--font-size-xxl", rem: "1.85rem", sample: "The Open-Source CRM", note: "Hero titles" },
|
||
{ token: "--font-size-xl", rem: "1.54rem", sample: "People & Companies", note: "Section titles" },
|
||
{ token: "--font-size-lg", rem: "1.23rem", sample: "Section heading", note: "Card headers" },
|
||
{ token: "--font-size-md", rem: "1rem", sample: "Body text — primary interface size", note: "Default UI copy" },
|
||
{ token: "--font-size-sm", rem: "0.92rem", sample: "Secondary label · timestamp", note: "Meta" },
|
||
{ token: "--font-size-xs", rem: "0.85rem", sample: "Caption · nav item", note: "Dense UI" },
|
||
];
|
||
|
||
const rowPad = densityPad(tokens, 12);
|
||
|
||
return (
|
||
<div style={{ fontFamily: tokens.fontFamily, display: "flex", flexDirection: "column", gap: 0 }}>
|
||
{scale.map((r) => (
|
||
<div
|
||
key={r.token}
|
||
style={{
|
||
display: "grid",
|
||
gridTemplateColumns: "160px 1fr",
|
||
gap: 16,
|
||
padding: `${rowPad}px 0`,
|
||
borderBottom: "1px solid #f4f4f5",
|
||
}}
|
||
>
|
||
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
|
||
<code style={codeSm}>{r.token}</code>
|
||
<span style={{ color: "#a1a1aa", fontSize: "0.7rem" }}>{r.rem}</span>
|
||
</div>
|
||
<div style={{ display: "flex", flexDirection: "column", justifyContent: "center" }}>
|
||
<span style={{ fontSize: r.rem, fontWeight: 500, color: "#18181b" }}>{r.sample}</span>
|
||
<span style={{ fontSize: "0.7rem", color: "#a1a1aa", marginTop: 4 }}>{r.note}</span>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export function AccentScalePanel({ tokens }: { tokens: ResolvedKitTokens }) {
|
||
const a = tokens.accentScale;
|
||
const md = tokens.radiusMdPx;
|
||
return (
|
||
<div style={{ fontFamily: tokens.fontFamily }}>
|
||
<SwatchRow colors={a} labels={{ 4: "primary", 8: "brand", 10: "text" }} />
|
||
<div style={{ display: "flex", flexWrap: "wrap", gap: 10, marginTop: 16 }}>
|
||
<button
|
||
type="button"
|
||
style={{
|
||
padding: "8px 14px",
|
||
borderRadius: md,
|
||
background: a[8] ?? tokens.accentHex,
|
||
color: "#fff",
|
||
border: "none",
|
||
fontSize: "0.8rem",
|
||
fontWeight: 500,
|
||
}}
|
||
>
|
||
Primary CTA
|
||
</button>
|
||
<button
|
||
type="button"
|
||
style={{
|
||
padding: "8px 14px",
|
||
borderRadius: md,
|
||
background: a[2],
|
||
color: a[10],
|
||
border: `1px solid ${a[6]}`,
|
||
fontSize: "0.8rem",
|
||
fontWeight: 500,
|
||
}}
|
||
>
|
||
Secondary
|
||
</button>
|
||
<span style={{ fontSize: "0.8rem", color: a[8], textDecoration: "underline", alignSelf: "center" }}>
|
||
Link · accent {tokens.accentHex}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export function GrayScalePanel({ tokens }: { tokens: ResolvedKitTokens }) {
|
||
return (
|
||
<div style={{ fontFamily: tokens.fontFamily }}>
|
||
<SwatchRow colors={tokens.grayScale} />
|
||
<p style={{ fontSize: "0.72rem", color: "#71717a", marginTop: 12, lineHeight: 1.55 }}>
|
||
Neutrals for surfaces & typography — pair with your accent ramp above.
|
||
</p>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export function SemanticColorsPanel({ tokens }: { tokens: ResolvedKitTokens }) {
|
||
const named = [
|
||
"red",
|
||
"orange",
|
||
"yellow",
|
||
"grass",
|
||
"green",
|
||
"cyan",
|
||
"blue",
|
||
"purple",
|
||
"crimson",
|
||
"amber",
|
||
"lime",
|
||
"jade",
|
||
"mint",
|
||
"iris",
|
||
"plum",
|
||
"pink",
|
||
];
|
||
const hues = [
|
||
"#ef4444",
|
||
"#f97316",
|
||
"#eab308",
|
||
"#84cc16",
|
||
"#22c55e",
|
||
"#06b6d4",
|
||
"#3b82f6",
|
||
"#a855f7",
|
||
"#e11d48",
|
||
"#f59e0b",
|
||
"#65a30d",
|
||
"#059669",
|
||
"#14b8a6",
|
||
"#6366f1",
|
||
"#9333ea",
|
||
"#ec4899",
|
||
];
|
||
return (
|
||
<div style={{ fontFamily: tokens.fontFamily }}>
|
||
<div style={{ display: "grid", gridTemplateColumns: "repeat(8, 1fr)", gap: 8 }}>
|
||
{named.map((n, i) => (
|
||
<div key={n} style={{ textAlign: "center" }}>
|
||
<div style={{ height: 36, borderRadius: tokens.radiusSm, background: hues[i] }} />
|
||
<div style={{ fontSize: "0.62rem", color: "#71717a", marginTop: 4 }}>{n}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
<div style={{ display: "flex", gap: 8, marginTop: 14, flexWrap: "wrap" }}>
|
||
{[
|
||
["Danger", "#fef2f2", "#dc2626"],
|
||
["Success", "#f0fdf4", "#16a34a"],
|
||
["Warning", "#fffbeb", "#ca8a04"],
|
||
["Info", "#eff6ff", "#2563eb"],
|
||
].map(([label, bg, fg]) => (
|
||
<span
|
||
key={label}
|
||
style={{
|
||
padding: "4px 10px",
|
||
borderRadius: 999,
|
||
background: bg as string,
|
||
color: fg as string,
|
||
fontSize: "0.72rem",
|
||
fontWeight: 600,
|
||
}}
|
||
>
|
||
{label}
|
||
</span>
|
||
))}
|
||
</div>
|
||
<p style={{ fontSize: "0.7rem", color: "#a1a1aa", marginTop: 12 }}>Reference hues for tags & status (not overridden by accent).</p>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export function RadiusPanel({ tokens }: { tokens: ResolvedKitTokens }) {
|
||
const rows = [
|
||
{ id: "radius-xs", px: tokens.radiusXs, use: "micro chips" },
|
||
{ id: "radius-sm", px: tokens.radiusSm, use: "inputs, buttons" },
|
||
{ id: "radius-md", px: tokens.radiusMdPx, use: "cards, dropdowns" },
|
||
{ id: "radius-xl", px: tokens.radiusXl, use: "large panels" },
|
||
{ id: "radius-xxl", px: tokens.radiusXxl, use: "hero surfaces" },
|
||
{ id: "radius-pill", px: 999, use: "tags" },
|
||
{ id: "radius-full", px: 100, use: "avatars" },
|
||
];
|
||
const fill = tokens.accentScale[3] ?? "#eceeff";
|
||
return (
|
||
<div style={{ display: "flex", flexWrap: "wrap", gap: 14, alignItems: "flex-end", fontFamily: tokens.fontFamily }}>
|
||
{rows.map((x) => (
|
||
<div key={x.id} style={{ textAlign: "center", width: 72 }}>
|
||
<div
|
||
style={{
|
||
width: 56,
|
||
height: 40,
|
||
margin: "0 auto",
|
||
background: fill,
|
||
borderRadius: x.px >= 100 ? "50%" : x.px,
|
||
}}
|
||
/>
|
||
<div style={{ fontSize: "0.62rem", fontWeight: 600, marginTop: 6, color: "#3f3f46" }}>{x.id}</div>
|
||
<div style={{ fontSize: "0.62rem", color: "#a1a1aa" }}>{x.use}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export function ShadowsPanel({ tokens }: { tokens: ResolvedKitTokens }) {
|
||
const focus = tokens.accentHex;
|
||
const cards = [
|
||
{ id: "shadow-light", desc: "Buttons, rows", shadow: "0 1px 2px rgba(0,0,0,0.05)" },
|
||
{ id: "shadow-strong", desc: "Dropdowns", shadow: "0 4px 16px rgba(0,0,0,0.08)" },
|
||
{ id: "shadow-super-heavy", desc: "Modals", shadow: "0 12px 48px rgba(0,0,0,0.12)" },
|
||
{ id: "shadow-underline", desc: "Focus line", shadow: `inset 0 -2px 0 ${focus}` },
|
||
];
|
||
return (
|
||
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(140px, 1fr))", gap: 12, fontFamily: tokens.fontFamily }}>
|
||
{cards.map((c) => (
|
||
<div key={c.id} style={{ padding: 14, borderRadius: tokens.radiusMdPx, background: "#fff", boxShadow: c.shadow, border: "1px solid #f4f4f5" }}>
|
||
<div style={{ fontSize: "0.72rem", fontWeight: 600, color: "#18181b" }}>{c.id}</div>
|
||
<div style={{ fontSize: "0.65rem", color: "#71717a", marginTop: 6 }}>{c.desc}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export function SpacingPanel({ tokens }: { tokens: ResolvedKitTokens }) {
|
||
const bar = tokens.accentScale[5] ?? "#9aa6ff";
|
||
const steps = [
|
||
[1, 4, "sibling gap min"],
|
||
[2, 8, "button padding"],
|
||
[3, 12, "sidebar gap"],
|
||
[4, 16, "section padding"],
|
||
[6, 24, "card padding"],
|
||
[8, 32, "large gap"],
|
||
[12, 48, "table row"],
|
||
[16, 64, "section spacer"],
|
||
];
|
||
return (
|
||
<div style={{ fontFamily: tokens.fontFamily }}>
|
||
<div style={{ fontSize: "0.65rem", fontWeight: 700, letterSpacing: "0.06em", color: "#a1a1aa", marginBottom: 12 }}>
|
||
SPACING — 4PX BASE · space(n) = n × 4px
|
||
</div>
|
||
{steps.map(([n, px, note]) => (
|
||
<div key={n} style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 8 }}>
|
||
<code style={{ ...codeSm, width: 72 }}>space({n})</code>
|
||
<span style={{ fontSize: "0.72rem", color: "#71717a", width: 36 }}>{px}px</span>
|
||
<div style={{ flex: 1, height: 8, background: "#f4f4f5", borderRadius: 4, overflow: "hidden" }}>
|
||
<div style={{ width: Math.min((px as number) * 2, 200), height: "100%", background: bar }} />
|
||
</div>
|
||
<span style={{ fontSize: "0.72rem", color: "#71717a", flex: 1 }}>{note}</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export function ButtonsPanel({ tokens }: { tokens: ResolvedKitTokens }) {
|
||
const a = tokens.accentScale;
|
||
const md = tokens.radiusMdPx;
|
||
const accent = a[8] ?? tokens.accentHex;
|
||
return (
|
||
<div style={{ display: "flex", flexDirection: "column", gap: 14, fontFamily: tokens.fontFamily }}>
|
||
<div style={panelMiniTitle}>Primary</div>
|
||
<div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
|
||
<button type="button" style={{ padding: "8px 16px", borderRadius: md, background: "#18181b", color: "#fff", border: "none", fontSize: "0.8rem" }}>
|
||
Continue
|
||
</button>
|
||
<button type="button" style={{ padding: "8px 16px", borderRadius: md, background: accent, color: "#fff", border: "none", fontSize: "0.8rem" }}>
|
||
Accent
|
||
</button>
|
||
<button type="button" style={{ padding: "8px 16px", borderRadius: md, background: "#dc2626", color: "#fff", border: "none", fontSize: "0.8rem" }}>
|
||
Danger
|
||
</button>
|
||
<button type="button" disabled style={{ padding: "8px 16px", borderRadius: md, background: "#e4e4e7", color: "#a1a1aa", border: "none", fontSize: "0.8rem" }}>
|
||
Disabled
|
||
</button>
|
||
</div>
|
||
<div style={panelMiniTitle}>Secondary · outlined</div>
|
||
<div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
|
||
<button type="button" style={{ padding: "8px 16px", borderRadius: md, background: "#fff", border: "1px solid #e4e4e7", fontSize: "0.8rem" }}>
|
||
Default
|
||
</button>
|
||
<button type="button" style={{ padding: "8px 16px", borderRadius: md, background: "#fff", border: `1px solid ${accent}`, color: accent, fontSize: "0.8rem" }}>
|
||
Accent outline
|
||
</button>
|
||
</div>
|
||
<div style={panelMiniTitle}>Tertiary · text</div>
|
||
<div style={{ display: "flex", gap: 16 }}>
|
||
<button type="button" style={{ background: "none", border: "none", fontSize: "0.8rem", color: "#18181b", cursor: "pointer" }}>
|
||
Cancel
|
||
</button>
|
||
<button type="button" style={{ background: "none", border: "none", fontSize: "0.8rem", color: accent, cursor: "pointer" }}>
|
||
Save changes
|
||
</button>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function Field({ label, children }: { label: string; children: ReactNode }) {
|
||
return (
|
||
<div>
|
||
<div style={{ fontSize: "0.68rem", fontWeight: 500, color: "#71717a", marginBottom: 6 }}>{label}</div>
|
||
{children}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export function InputsPanel({ tokens }: { tokens: ResolvedKitTokens }) {
|
||
const accent = tokens.accentHex;
|
||
const md = tokens.radiusMdPx;
|
||
return (
|
||
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(168px, 1fr))", gap: 14, fontFamily: tokens.fontFamily }}>
|
||
<Field label="Default">
|
||
<input placeholder="Type something…" style={{ ...inputBase, borderRadius: md }} />
|
||
</Field>
|
||
<Field label="Focused">
|
||
<input defaultValue="Acme Corporation" style={{ ...inputBase, borderRadius: md, outline: `2px solid ${accent}`, borderColor: accent }} />
|
||
</Field>
|
||
<Field label="Error">
|
||
<input defaultValue="invalid@" style={{ ...inputBase, borderRadius: md, borderColor: "#fca5a5", boxShadow: "0 0 0 3px rgba(252,165,165,0.35)" }} />
|
||
</Field>
|
||
<Field label="Disabled">
|
||
<input defaultValue="Read only" disabled style={{ ...inputBase, borderRadius: md, opacity: 0.55, background: "#fafafa" }} />
|
||
</Field>
|
||
<Field label="Search">
|
||
<input placeholder="Search…" style={{ ...inputBase, borderRadius: md }} />
|
||
</Field>
|
||
<Field label="Multiline">
|
||
<textarea placeholder="Add a note…" rows={3} style={{ ...inputBase, borderRadius: md, resize: "vertical" }} />
|
||
</Field>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export function NavPanel({ tokens }: { tokens: ResolvedKitTokens }) {
|
||
const rs = tokens.radiusSm;
|
||
const pad = tokens.density === "compact" ? 6 : 8;
|
||
const Item = ({ active, children }: { active?: boolean; children: ReactNode }) => (
|
||
<div
|
||
style={{
|
||
padding: `${pad}px 10px`,
|
||
borderRadius: rs,
|
||
background: active ? "#ebebeb" : "transparent",
|
||
fontSize: tokens.density === "compact" ? "0.76rem" : "0.8rem",
|
||
color: active ? "#18181b" : "#52525b",
|
||
fontWeight: active ? 600 : 400,
|
||
fontFamily: tokens.fontFamily,
|
||
}}
|
||
>
|
||
{children}
|
||
</div>
|
||
);
|
||
return (
|
||
<div style={{ display: "grid", gridTemplateColumns: "220px 1fr", gap: 24 }}>
|
||
<div
|
||
style={{
|
||
background: tokens.grayScale[2],
|
||
borderRadius: tokens.radiusMdPx,
|
||
border: `1px solid ${tokens.grayScale[4]}`,
|
||
padding: 10,
|
||
fontFamily: tokens.fontFamily,
|
||
}}
|
||
>
|
||
<div style={{ fontSize: "0.62rem", fontWeight: 700, color: "#a1a1aa", letterSpacing: "0.06em", margin: "4px 8px 8px" }}>MAIN</div>
|
||
<Item active>People</Item>
|
||
<Item>Companies</Item>
|
||
<Item>
|
||
Opportunities{" "}
|
||
<span style={{ float: "right", fontSize: "0.65rem", background: "#e4e4e7", padding: "1px 6px", borderRadius: 999 }}>12</span>
|
||
</Item>
|
||
<Item>Inbox</Item>
|
||
<div style={{ fontSize: "0.62rem", fontWeight: 700, color: "#a1a1aa", letterSpacing: "0.06em", margin: "14px 8px 8px" }}>WORKSPACE</div>
|
||
<Item>Workflows</Item>
|
||
<Item>Tasks</Item>
|
||
</div>
|
||
<div style={{ fontSize: "0.72rem", color: "#71717a", lineHeight: 1.65, fontFamily: tokens.fontFamily }}>
|
||
<div>Drawer preview uses kit neutrals + radius tokens.</div>
|
||
<div>Accent {tokens.accentHex} applies to links & focus off-nav.</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export function TagsPanel({ tokens }: { tokens: ResolvedKitTokens }) {
|
||
const pills = [
|
||
["Active", "#dcfce7", "#166534"],
|
||
["Closed", "#fee2e2", "#991b1b"],
|
||
["In Progress", "#fef9c3", "#854d0e"],
|
||
["Draft", tokens.accentScale[2] ?? "#e0e7ff", tokens.accentScale[10] ?? "#3730a3"],
|
||
];
|
||
return (
|
||
<div style={{ fontFamily: tokens.fontFamily }}>
|
||
<div style={{ fontSize: "0.72rem", color: "#71717a", marginBottom: 10 }}>Pill tags · radius-full</div>
|
||
<div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
|
||
{pills.map(([label, bg, fg]) => (
|
||
<span
|
||
key={label}
|
||
style={{
|
||
padding: "4px 12px",
|
||
borderRadius: 999,
|
||
background: bg as string,
|
||
color: fg as string,
|
||
fontSize: "0.75rem",
|
||
fontWeight: 600,
|
||
}}
|
||
>
|
||
{label}
|
||
</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export function IllustrationsPanel({ tokens }: { tokens: ResolvedKitTokens }) {
|
||
const icons = ["user", "mail", "calendar", "file", "link", "tag", "settings", "numbers"];
|
||
const r = tokens.radiusMdPx;
|
||
return (
|
||
<div style={{ fontFamily: tokens.fontFamily }}>
|
||
<div style={{ display: "flex", flexWrap: "wrap", gap: 10 }}>
|
||
{icons.map((name) => (
|
||
<div
|
||
key={name}
|
||
style={{
|
||
width: 52,
|
||
height: 52,
|
||
borderRadius: r,
|
||
border: "1px solid #e4e4e7",
|
||
display: "flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
fontSize: "0.62rem",
|
||
color: "#52525b",
|
||
background: "#fafafa",
|
||
}}
|
||
>
|
||
{name}
|
||
</div>
|
||
))}
|
||
</div>
|
||
<p style={{ fontSize: "0.7rem", color: "#a1a1aa", marginTop: 12 }}>
|
||
Replace with project SVGs · <code style={codeSm}>assets/illustrations/</code>
|
||
</p>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export function LogoPanel({ tokens }: { tokens: ResolvedKitTokens }) {
|
||
const r = tokens.radiusMdPx;
|
||
return (
|
||
<div style={{ display: "flex", flexWrap: "wrap", gap: 14, fontFamily: tokens.fontFamily }}>
|
||
{["Logo light", "Logo dark", "Icon mark", "Workspace"].map((label) => (
|
||
<div key={label} style={{ padding: 16, borderRadius: r, border: "1px solid #e4e4e7", background: "#fafafa", minWidth: 120 }}>
|
||
<div style={{ fontSize: "0.62rem", color: "#71717a", marginBottom: 10 }}>{label}</div>
|
||
<div
|
||
style={{
|
||
width: 40,
|
||
height: 40,
|
||
borderRadius: tokens.radiusSm,
|
||
background: `linear-gradient(135deg, ${tokens.accentHex}, ${tokens.accentScale[10] ?? "#1a1a1a"})`,
|
||
color: "#fff",
|
||
display: "flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
fontWeight: 700,
|
||
fontSize: "0.75rem",
|
||
}}
|
||
>
|
||
V
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
const PANEL_COMPONENTS: Record<string, FC<{ tokens: ResolvedKitTokens }>> = {
|
||
fontWeights: FontWeightsPanel,
|
||
typeScale: TypeScalePanel,
|
||
accentScale: AccentScalePanel,
|
||
grayScale: GrayScalePanel,
|
||
semanticColors: SemanticColorsPanel,
|
||
radius: RadiusPanel,
|
||
shadows: ShadowsPanel,
|
||
spacing: SpacingPanel,
|
||
buttons: ButtonsPanel,
|
||
inputs: InputsPanel,
|
||
nav: NavPanel,
|
||
tags: TagsPanel,
|
||
illustrations: IllustrationsPanel,
|
||
logo: LogoPanel,
|
||
};
|
||
|
||
export function renderKitPanel(panelKey: string, tokens: ResolvedKitTokens): ReactNode {
|
||
const C = PANEL_COMPONENTS[panelKey];
|
||
if (!C) return <p style={{ color: "#71717a", fontSize: "0.82rem" }}>Unknown panel: {panelKey}</p>;
|
||
return <C tokens={tokens} />;
|
||
}
|