feat(slice-3): design-system foundation — Wiggleverse tokens, fonts, brand marks, UI kit (per ui/designs export)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
// The ecomm UI kit — React faces of the app.css primitives, matching the Claude Design
|
||||
// export (ui/designs/ecomm-login-and-create-storefront-designs, hf-kit). Presentation
|
||||
// only: no business rules live here (§6.2 — the SPA renders what the BFF returns).
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
export function Screen({
|
||||
children,
|
||||
horizon = false,
|
||||
plain = false,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
horizon?: boolean;
|
||||
plain?: boolean;
|
||||
}) {
|
||||
const cls = plain ? "screen screen--plain" : horizon ? "screen screen--horizon" : "screen";
|
||||
return <div className={cls}>{children}</div>;
|
||||
}
|
||||
|
||||
export function TopBar({ left, right }: { left: ReactNode; right?: ReactNode }) {
|
||||
return (
|
||||
<header className="topbar">
|
||||
<div className="topbar__side">{left}</div>
|
||||
<div className="topbar__side">{right}</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
export function Wordmark({ size = 26 }: { size?: number }) {
|
||||
return (
|
||||
<span className="wordmark">
|
||||
<img src="/brand/mark-tile.svg" width={size} height={size} alt="" />
|
||||
<span className="wordmark__name" style={{ fontSize: size * 0.86 }}>
|
||||
ecomm
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer className="footer">
|
||||
<span className="footer__id">
|
||||
<img src="/brand/mark-mono-gold.svg" width={16} height={16} alt="" />
|
||||
ecomm · a Wiggleverse line
|
||||
</span>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
||||
export function AuthCard({ children }: { children: ReactNode }) {
|
||||
return <div className="card">{children}</div>;
|
||||
}
|
||||
|
||||
export function Eyebrow({ children }: { children: ReactNode }) {
|
||||
return <p className="eyebrow">{children}</p>;
|
||||
}
|
||||
|
||||
export function Banner({
|
||||
tone = "attn",
|
||||
title,
|
||||
children,
|
||||
}: {
|
||||
tone?: "attn" | "info";
|
||||
title?: string;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className={`banner banner--${tone}`} role={tone === "attn" ? "alert" : "status"}>
|
||||
<span className="banner__icon" aria-hidden="true">
|
||||
{tone === "attn" ? "◆" : "◇"}
|
||||
</span>
|
||||
<div>
|
||||
{title && <div className="banner__title">{title}</div>}
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function PrimaryButton({
|
||||
children,
|
||||
busy = false,
|
||||
disabled = false,
|
||||
type = "submit",
|
||||
onClick,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
busy?: boolean;
|
||||
disabled?: boolean;
|
||||
type?: "submit" | "button";
|
||||
onClick?: () => void;
|
||||
}) {
|
||||
return (
|
||||
<button className="btn-primary" type={type} disabled={disabled || busy} onClick={onClick}>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export function Field({
|
||||
label,
|
||||
optional = false,
|
||||
error,
|
||||
helper,
|
||||
inputProps,
|
||||
}: {
|
||||
label: string;
|
||||
optional?: boolean;
|
||||
error?: string | null;
|
||||
helper?: string;
|
||||
inputProps: React.InputHTMLAttributes<HTMLInputElement>;
|
||||
}) {
|
||||
return (
|
||||
<div className="field">
|
||||
<label className="field__label">
|
||||
<span>{label}</span>
|
||||
{optional && <span className="field__optional">optional</span>}
|
||||
</label>
|
||||
<input
|
||||
className={`field__input${error ? " field__input--error" : ""}`}
|
||||
aria-invalid={!!error}
|
||||
{...inputProps}
|
||||
/>
|
||||
{error && (
|
||||
<p className="note note--attn" role="alert">
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
{helper && !error && <p className="note">{helper}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function AccountChip({ email, onSignOut }: { email: string; onSignOut: () => void }) {
|
||||
return (
|
||||
<div className="chip">
|
||||
<span className="chip__avatar" aria-hidden="true">
|
||||
{email[0]?.toUpperCase()}
|
||||
</span>
|
||||
<span className="chip__email">{email}</span>
|
||||
<span className="chip__divider" aria-hidden="true" />
|
||||
<button type="button" className="signout" onClick={onSignOut}>
|
||||
Sign out
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user