import { useState, type ReactNode } from 'react'; import { Link } from 'react-router-dom'; import { AlertTriangle, Check, ChevronLeft, ChevronRight, Copy, ExternalLink as ExtIcon, Inbox } from 'lucide-react'; import { FIDELITY_INFO, fidelityColor, fidelityKey } from '../lib/fidelity'; import { chainLabel, chainPath } from '../lib/format'; export function Section({ id, title, kicker, actions, children, className = '', flush = false, }: { id?: string; title?: ReactNode; kicker?: ReactNode; actions?: ReactNode; children: ReactNode; className?: string; flush?: boolean; }) { return (
{(title || actions) && (
{title &&

{title}

} {kicker &&

{kicker}

}
{actions &&
{actions}
}
)} {children}
); } export function Badge({ children, tone = 'neutral', title, }: { children: ReactNode; tone?: 'neutral' | 'accent' | 'outline'; title?: string; }) { return ( {children} ); } export function Swatch({ color, shape = 'square' }: { color: string; shape?: 'square' | 'dot' }) { return ; } export function FidelityBadge({ value }: { value: string | null | undefined }) { const key = fidelityKey(value); const info = FIDELITY_INFO[key]; return ( {info.label} ); } export function LigandBadge({ status }: { status: string | null | undefined }) { if (!status) return —; return ( {status === 'holo' ? 'Holo' : 'Apo'} ); } export function ChainLink({ pdb, chain, strong = false }: { pdb: string; chain: string; strong?: boolean }) { return ( {chainLabel(pdb, chain)} ); } export function ExternalLink({ href, children, className = '' }: { href: string; children: ReactNode; className?: string }) { return ( {children} ); } export function CopyButton({ text, label = 'Copy' }: { text: string; label?: string }) { const [done, setDone] = useState(false); return ( ); } export function Spinner({ label = 'Loading…' }: { label?: string }) { return (
{label}
); } export function Skeleton({ height = 16, width = '100%' }: { height?: number; width?: number | string }) { return ; } export function ErrorState({ title = 'Something went wrong', message, retry = true, }: { title?: string; message?: string; retry?: boolean; }) { return (
{title} {message &&

{message}

} {retry && ( )}
); } export function EmptyState({ title, children }: { title: string; children?: ReactNode }) { return (
{title} {children &&

{children}

}
); } export function Pagination({ page, pageCount, onPage, total, pageSize, }: { page: number; pageCount: number; onPage: (p: number) => void; total: number; pageSize: number; }) { if (total === 0) return null; const from = page * pageSize + 1; const to = Math.min(total, (page + 1) * pageSize); return ( ); } export function Segmented({ value, options, onChange, label, }: { value: T; options: Array<{ value: T; label: ReactNode; count?: number }>; onChange: (v: T) => void; label: string; }) { return (
{options.map((o) => ( ))}
); } export function Fact({ label, children, hint }: { label: ReactNode; children: ReactNode; hint?: string }) { return (
{label}
{children}
); } /** Small horizontal meter (0–1). */ export function Meter({ value, color = 'var(--accent)' }: { value: number | null | undefined; color?: string }) { const v = value == null ? 0 : Math.max(0, Math.min(1, value)); return ( ); } /** UniProt accession(s); multi-chain sequences can carry several joined by ';'. */ export function UniProtLinks({ ids }: { ids: string | null | undefined }) { if (!ids) return null; return ( <> {ids .split(';') .filter(Boolean) .map((id) => ( UniProt {id} ))} ); }