- Farb-Tokens laut Brandbook §11.4 (Lotsenblau, Signalorange, Graphit, Hafengrau, Stahlgrau, Zink, Funktionsfarben) in globals.css + src/lib/brand.ts; helles Theme - Inline-SVG-Logo src/components/brand/craftvia-logo.tsx (horizontal/signet, color/mono/reversed, App-Icon-Kachel, optionale Tagline); Certvia-/GEFIM-Logos entfernt - Favicon/PWA-Icons aus dem Signet erzeugt (scripts/generate-brand-icons.ts), site.webmanifest (Craftvia, theme_color #082E5B) - Inter selbst gehostet (next/font/local, lokale OFL-Datei), Open Sans/Poppins entfernt - Metadaten, WebAuthn-RP-Name, Mail-/Dokument-CD auf Craftvia umgestellt - docs/craftvia/BRANDING.md ersetzt docs/BRANDING-CERTVIA.md Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
122 lines
3.4 KiB
TypeScript
122 lines
3.4 KiB
TypeScript
import { cn } from "@/lib/utils";
|
|
|
|
/**
|
|
* UI-Basisbausteine (Craftvia-CD): PageHead (Crumb/Titel/Sub + Aktionen), KPI-Kapsel,
|
|
* Tag-Chip, Owner-Chip, Status-Pille. Farbe nie als einziges Statussignal — Pillen
|
|
* tragen immer Text.
|
|
*/
|
|
|
|
export function PageHead({
|
|
crumb,
|
|
title,
|
|
sub,
|
|
actions,
|
|
}: {
|
|
crumb: string;
|
|
title: string;
|
|
sub?: string;
|
|
actions?: React.ReactNode;
|
|
}) {
|
|
return (
|
|
<div className="mb-4.5 flex flex-wrap items-end justify-between gap-4">
|
|
<div>
|
|
<div className="text-xs text-muted-foreground">{crumb}</div>
|
|
<h1 className="text-[22px]">{title}</h1>
|
|
{sub && <div className="mt-1 text-[13px] text-muted-foreground">{sub}</div>}
|
|
</div>
|
|
{actions && <div className="flex gap-2">{actions}</div>}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function KpiCard({
|
|
label,
|
|
value,
|
|
trend,
|
|
trendColor = "muted",
|
|
}: {
|
|
label: string;
|
|
value: React.ReactNode;
|
|
trend?: string;
|
|
trendColor?: "muted" | "risk" | "warn" | "ok";
|
|
}) {
|
|
const trendColors = {
|
|
muted: "text-muted-foreground",
|
|
risk: "text-[var(--risk)]",
|
|
warn: "text-[var(--warn)]",
|
|
ok: "text-[var(--ok)]",
|
|
};
|
|
return (
|
|
<div className="shadow-card rounded-xl border bg-card p-4">
|
|
<div className="text-[12.5px] font-semibold text-muted-foreground">{label}</div>
|
|
<div className="mt-1.5 mb-0.5 font-heading text-3xl font-bold leading-none">{value}</div>
|
|
{trend && <div className={cn("mt-1 text-xs font-semibold", trendColors[trendColor])}>{trend}</div>}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function Tag({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<span className="inline-block rounded-md bg-[var(--muted)] px-2 py-0.5 text-[11px] font-bold text-muted-foreground">
|
|
{children}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
export function OwnerChip({ name, noOwnerLabel }: { name?: string | null; noOwnerLabel: string }) {
|
|
if (!name) return <Pill tone="risk">{noOwnerLabel}</Pill>;
|
|
const initials = name
|
|
.split(/\s+/)
|
|
.map((p) => p[0])
|
|
.slice(0, 2)
|
|
.join("")
|
|
.toUpperCase();
|
|
return (
|
|
<span className="inline-flex items-center gap-2">
|
|
<span className="grid size-6.5 place-items-center rounded-full bg-[var(--sidebar-accent)] font-heading text-[11px] font-bold text-[var(--primary)]">
|
|
{initials}
|
|
</span>
|
|
{name}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
// Status-Chips: ~12 % Alpha der Funktionsfarbe auf hellem Grund (Brandbook §11.4).
|
|
const PILL_TONES = {
|
|
ok: "bg-[rgba(35,132,93,0.12)] text-[var(--ok)]",
|
|
warn: "bg-[rgba(200,121,18,0.12)] text-[var(--warn)]",
|
|
orange: "bg-[rgba(255,106,0,0.12)] text-[#b34a00]",
|
|
risk: "bg-[rgba(198,66,66,0.12)] text-[var(--risk)]",
|
|
info: "bg-[rgba(47,111,163,0.12)] text-[var(--info)]",
|
|
mut: "bg-[rgba(102,114,125,0.12)] text-muted-foreground",
|
|
violet: "bg-[rgba(8,46,91,0.1)] text-[var(--primary)]",
|
|
} as const;
|
|
|
|
export function Pill({
|
|
tone,
|
|
children,
|
|
}: {
|
|
tone: keyof typeof PILL_TONES;
|
|
children: React.ReactNode;
|
|
}) {
|
|
return (
|
|
<span
|
|
className={cn(
|
|
"inline-flex items-center gap-1.5 rounded-full px-2.5 py-[3px] text-[11.5px] font-bold whitespace-nowrap",
|
|
PILL_TONES[tone]
|
|
)}
|
|
>
|
|
{children}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
export function SectTitle({ title, sub }: { title: string; sub?: string }) {
|
|
return (
|
|
<div>
|
|
<div className="font-heading text-[15px] font-semibold">{title}</div>
|
|
{sub && <div className="mt-0.5 text-[12.5px] text-muted-foreground">{sub}</div>}
|
|
</div>
|
|
);
|
|
}
|