Files
CASAN/packages/casan-control-panel/frontend/src/components/ui/Card.tsx
T

33 lines
2.8 KiB
TypeScript

import type { ReactNode } from 'react';
export function Card({ title, children, right, className = '' }: { title?: string; children: ReactNode; right?: ReactNode; className?: string }) {
return (
<section className={`rounded-2xl border border-slate-200/80 bg-white/95 p-5 shadow-[0_12px_30px_rgba(15,23,42,0.045)] backdrop-blur-sm sm:p-6 ${className}`}>
{title && (
<div className="mb-5 flex items-center justify-between gap-4">
<h2 className="text-sm font-semibold uppercase tracking-[0.13em] text-slate-500">{title}</h2>
{right}
</div>
)}
{children}
</section>
);
}
export function StatTile({ label, value, sub }: { label: string; value: ReactNode; sub?: string }) {
return (
<div className="group rounded-2xl border border-slate-200/80 bg-white/90 p-4 shadow-[0_10px_24px_rgba(15,23,42,0.035)] transition duration-200 hover:-translate-y-0.5 hover:shadow-[0_16px_30px_rgba(15,23,42,0.07)]">
<div className="text-[11px] font-semibold uppercase tracking-[0.12em] text-slate-500">{label}</div>
<div className="mt-2 text-2xl font-semibold tracking-tight text-slate-900">{value}</div>
{sub && <div className="mt-1.5 text-xs leading-5 text-slate-400">{sub}</div>}
</div>
);
}
const TONE: Record<string, string> = {
ok: 'border-emerald-200 bg-emerald-50 text-emerald-700', pass: 'border-emerald-200 bg-emerald-50 text-emerald-700', success: 'border-emerald-200 bg-emerald-50 text-emerald-700', allow: 'border-emerald-200 bg-emerald-50 text-emerald-700', approved: 'border-emerald-200 bg-emerald-50 text-emerald-700', answered: 'border-emerald-200 bg-emerald-50 text-emerald-700', certified: 'border-emerald-200 bg-emerald-50 text-emerald-700', verified: 'border-emerald-200 bg-emerald-50 text-emerald-700',
warn: 'border-amber-200 bg-amber-50 text-amber-700', stale: 'border-amber-200 bg-amber-50 text-amber-700', draft: 'border-amber-200 bg-amber-50 text-amber-700', pending: 'border-amber-200 bg-amber-50 text-amber-700', attention: 'border-amber-200 bg-amber-50 text-amber-700',
fail: 'border-rose-200 bg-rose-50 text-rose-700', failed: 'border-rose-200 bg-rose-50 text-rose-700', denied: 'border-rose-200 bg-rose-50 text-rose-700', blocked: 'border-rose-200 bg-rose-50 text-rose-700', deny: 'border-rose-200 bg-rose-50 text-rose-700', block: 'border-rose-200 bg-rose-50 text-rose-700', crit: 'border-rose-200 bg-rose-50 text-rose-700', breach: 'border-rose-200 bg-rose-50 text-rose-700', halted: 'border-rose-200 bg-rose-50 text-rose-700',
};
export function StatusBadge({ value }: { value: string }) {
const tone = TONE[String(value).toLowerCase()] ?? 'border-slate-200 bg-slate-50 text-slate-600';
return <span className={`inline-flex items-center rounded-full border px-2.5 py-1 text-[11px] font-semibold leading-none ${tone}`}>{value}</span>;
}