Files
WatchLink/src/components/ui.tsx
MrSphay 9fbd79c7ef
All checks were successful
Template Compliance / compliance (push) Successful in 5s
Build / build (push) Successful in 12m36s
Release Dry Run / release-dry-run (push) Successful in 1m33s
Redesign WatchLink application UI
2026-05-15 20:13:29 +02:00

118 lines
2.8 KiB
TypeScript

import { clsx } from "clsx";
export function PageHeader({
title,
description,
actions,
meta
}: {
title: string;
description?: string;
actions?: React.ReactNode;
meta?: React.ReactNode;
}) {
return (
<header className="page-header">
<div className="title-block">
{meta ? <div className="status-row">{meta}</div> : null}
<h1>{title}</h1>
{description ? <p>{description}</p> : null}
</div>
{actions ? <div className="toolbar">{actions}</div> : null}
</header>
);
}
export function Panel({
title,
eyebrow,
actions,
children,
className
}: {
title?: string;
eyebrow?: string;
actions?: React.ReactNode;
children: React.ReactNode;
className?: string;
}) {
return (
<section className={clsx("panel", className)}>
{title || actions || eyebrow ? (
<div className="panel-header">
<div>
{eyebrow ? <span className="eyebrow">{eyebrow}</span> : null}
{title ? <h2>{title}</h2> : null}
</div>
{actions ? <div className="toolbar compact">{actions}</div> : null}
</div>
) : null}
<div className="panel-body">{children}</div>
</section>
);
}
export function MetricTile({
label,
value,
detail,
tone = "neutral",
icon
}: {
label: string;
value: React.ReactNode;
detail?: string;
tone?: "neutral" | "good" | "warn" | "danger" | "info";
icon?: React.ReactNode;
}) {
return (
<div className={clsx("metric-tile", tone)}>
<div className="metric-top">
<span>{label}</span>
{icon ? <span className="metric-icon">{icon}</span> : null}
</div>
<strong>{value}</strong>
{detail ? <p>{detail}</p> : null}
</div>
);
}
export function Tabs({ items, active }: { items: Array<{ label: string; href: string; count?: number }>; active: string }) {
return (
<nav className="tabs" aria-label="View tabs">
{items.map((item) => (
<a className={clsx("tab", active === item.label && "active")} href={item.href} key={item.label}>
{item.label}
{typeof item.count === "number" ? <span className="tab-count">{item.count}</span> : null}
</a>
))}
</nav>
);
}
export function EmptyState({
title,
description,
action
}: {
title: string;
description?: string;
action?: React.ReactNode;
}) {
return (
<div className="empty-state">
<strong>{title}</strong>
{description ? <span>{description}</span> : null}
{action ? <div className="toolbar compact">{action}</div> : null}
</div>
);
}
export function StatusDot({ tone = "neutral", label }: { tone?: string; label: string }) {
return <span className={clsx("status-dot", tone)}>{label}</span>;
}
export function DataTable({ children }: { children: React.ReactNode }) {
return <div className="table-wrap">{children}</div>;
}