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}
))}
>
);
}