69 lines
2.1 KiB
TypeScript
69 lines
2.1 KiB
TypeScript
import type { ReactNode } from 'react';
|
|
|
|
export function Card({ title, children, actions }: { title?: ReactNode; children: ReactNode; actions?: ReactNode }) {
|
|
return (
|
|
<section className="card">
|
|
{(title || actions) && (
|
|
<header className="mb-4 flex items-center justify-between gap-4">
|
|
{title ? <h2 className="text-lg font-semibold">{title}</h2> : <span />}
|
|
{actions}
|
|
</header>
|
|
)}
|
|
{children}
|
|
</section>
|
|
);
|
|
}
|
|
|
|
const badgeTones: Record<string, string> = {
|
|
neutral: 'bg-slate-200 text-slate-800 dark:bg-slate-700 dark:text-slate-100',
|
|
success: 'bg-emerald-600 text-white',
|
|
warning: 'bg-amber-300 text-black',
|
|
danger: 'bg-red-600 text-white',
|
|
brand: 'bg-brand-600 text-white',
|
|
};
|
|
|
|
export function Badge({ children, tone = 'neutral' }: { children: ReactNode; tone?: keyof typeof badgeTones }) {
|
|
return <span className={`badge ${badgeTones[tone] ?? badgeTones.neutral}`}>{children}</span>;
|
|
}
|
|
|
|
const alertTones: Record<string, string> = {
|
|
info: 'border-brand-400 bg-brand-50 text-brand-700 dark:bg-brand-700/20 dark:text-brand-100',
|
|
success: 'border-emerald-500 bg-emerald-50 text-emerald-800 dark:bg-emerald-900/30 dark:text-emerald-100',
|
|
warning: 'border-amber-400 bg-amber-50 text-black',
|
|
error: 'border-red-500 bg-red-50 text-red-800 dark:bg-red-900/30 dark:text-red-100',
|
|
};
|
|
|
|
export function Alert({ children, tone = 'info' }: { children: ReactNode; tone?: keyof typeof alertTones }) {
|
|
return (
|
|
<div role="status" className={`rounded-lg border px-4 py-3 text-sm ${alertTones[tone] ?? alertTones.info}`}>
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function Field({
|
|
label,
|
|
htmlFor,
|
|
hint,
|
|
children,
|
|
}: {
|
|
label: string;
|
|
htmlFor: string;
|
|
hint?: string;
|
|
children: ReactNode;
|
|
}) {
|
|
return (
|
|
<div>
|
|
<label className="label" htmlFor={htmlFor}>
|
|
{label}
|
|
</label>
|
|
{children}
|
|
{hint && <p className="muted mt-1 text-xs">{hint}</p>}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function EmptyState({ children }: { children?: ReactNode }) {
|
|
return <p className="muted py-6 text-center text-sm">{children ?? 'Nothing here yet.'}</p>;
|
|
}
|