feat(data): modernize SectionCard chrome (Batch 0 — Data overhaul)

Replace the tiny grey uppercase section titles with a modern header: optional
leading icon in a soft chip, sentence-case high-contrast title, calm subtitle,
a right-aligned rotating chevron, and optional statusDot/badge slots. API is
unchanged (title/subtitle/collapsible/summary/storageKey/actions preserved) so
no section internals change — purely the shared card chrome for the Data page.

Build clean; client suite 46 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
null 2026-07-03 14:57:03 -05:00
parent 5aa5c0cc0e
commit 212117a61a
1 changed files with 30 additions and 10 deletions

View File

@ -1,6 +1,14 @@
import React, { useEffect, useState } from 'react';
import { cn } from '@/lib/utils';
import { ChevronDown, ChevronRight } from 'lucide-react';
import { ChevronDown } from 'lucide-react';
// At-a-glance health tones for the optional SectionCard status dot.
const DOT_TONES = {
green: 'bg-emerald-500',
amber: 'bg-amber-500',
red: 'bg-rose-500',
gray: 'bg-muted-foreground/40',
};
export function fmt(isoStr) {
if (!isoStr) return '—';
@ -23,6 +31,9 @@ export function importErrorState(err, fallback) {
export function SectionCard({
title,
subtitle,
icon: Icon, // optional lucide icon component, shown in a soft chip
statusDot, // optional 'green' | 'amber' | 'red' | 'gray' health dot
badge, // optional node rendered next to the title (e.g. a count pill)
children,
className,
collapsible = false,
@ -44,35 +55,44 @@ export function SectionCard({
const headerContent = (
<>
{collapsible && (
open
? <ChevronDown className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
: <ChevronRight className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
{Icon && (
<span className="grid h-9 w-9 shrink-0 place-items-center rounded-lg border border-border/60 bg-muted/40 text-muted-foreground">
<Icon className="h-5 w-5" />
</span>
)}
<div className="min-w-0 flex-1">
<h2 className="text-xs font-bold uppercase tracking-widest text-muted-foreground">{title}</h2>
{subtitle && <p className="mt-1 text-sm text-muted-foreground">{subtitle}</p>}
<div className="flex items-center gap-2">
{statusDot && <span className={cn('h-2 w-2 shrink-0 rounded-full', DOT_TONES[statusDot] || DOT_TONES.gray)} />}
<h2 className="truncate text-base font-semibold text-foreground">{title}</h2>
{badge}
</div>
{subtitle && <p className="mt-0.5 text-sm text-muted-foreground">{subtitle}</p>}
{collapsible && !open && summary && (
<p className="mt-1 truncate text-xs font-medium text-muted-foreground/80">{summary}</p>
)}
</div>
{actions && <div className="shrink-0">{actions}</div>}
{collapsible && (
<ChevronDown
className={cn('mt-1 h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200', open && 'rotate-180')}
/>
)}
</>
);
return (
<div className={cn('table-surface mb-6', className)}>
<div className={cn('table-surface mb-5', className)}>
{collapsible ? (
<button
type="button"
onClick={() => setOpen(value => !value)}
className="flex w-full items-start gap-2 border-b border-border/50 px-6 py-4 text-left transition-colors hover:bg-muted/20"
className="flex w-full items-start gap-3 border-b border-border/50 px-5 py-4 text-left transition-colors hover:bg-muted/20"
aria-expanded={open}
>
{headerContent}
</button>
) : (
<div className="flex items-start gap-2 border-b border-border/50 px-6 py-4">
<div className="flex items-start gap-3 border-b border-border/50 px-5 py-4">
{headerContent}
</div>
)}