import { useId, useLayoutEffect, useRef, useState, type ReactNode } from 'react'; import { useI18n } from '../lib/i18n'; import { CATEGORY_ICON } from '../lib/categoryIcon'; import type { Confidence, Fact, PlaceCategory, SourceKind, Status, Verdict, VetState } from '../lib/types'; const SPRITE = `${import.meta.env.BASE_URL}uswds/sprite.svg`; export function Icon({ name, size = 3, className = '' }: { name: string; size?: 2 | 3 | 4 | 5; className?: string }) { return ( ); } /** Category pictogram in a soft square; decorative, the category name is always written next to it. */ export function CategoryIcon({ cat, size = 'md' }: { cat: PlaceCategory; size?: 'sm' | 'md' }) { return ( ); } export const VERDICT_STYLE: Record = { match: { icon: 'check_circle', tag: 'bg-success-dark text-white', alert: 'usa-alert--success', hex: '#00a91c' }, partial: { icon: 'warning', tag: 'bg-warning text-ink', alert: 'usa-alert--warning', hex: '#ffbe2e' }, conflict: { icon: 'report', tag: 'bg-accent-warm-darker text-white', alert: 'usa-alert--warning', hex: '#a2461c' }, incomplete: { icon: 'help', tag: 'bg-base-lighter text-ink', alert: 'usa-alert--info', hex: '#ffffff' }, fail: { icon: 'error', tag: 'bg-error-dark text-white', alert: 'usa-alert--error', hex: '#b50909' }, }; export function VerdictTag({ verdict }: { verdict: Verdict }) { const { t } = useI18n(); const s = VERDICT_STYLE[verdict]; return ( {t(`verdict.${verdict}`)} ); } const STATUS_STYLE: Record = { ok: { icon: 'check_circle', text: 'text-success-dark' }, warn: { icon: 'warning', text: 'text-warning-darker' }, fail: { icon: 'error', text: 'text-error-dark' }, unknown: { icon: 'help', text: 'text-base-dark' }, conflict: { icon: 'report', text: 'text-accent-warm-darker' }, }; export function StatusLabel({ status }: { status: Status }) { const { t } = useI18n(); const s = STATUS_STYLE[status]; return ( {t(`status.${status}`)} ); } const CONF_STYLE: Record = { verified: 'bg-success-dark text-white', mapped: 'bg-primary text-white', stale: 'bg-warning text-ink', inferred: 'bg-base-dark text-white', user: 'bg-accent-warm-darker text-white', none: 'bg-base-lighter text-ink', }; const CONF_ICON: Record = { verified: 'verified', mapped: 'check', stale: 'update', inferred: 'info_outline', user: 'edit', none: 'help_outline', }; export function ConfidenceTag({ confidence }: { confidence: Confidence }) { const { t } = useI18n(); return ( {t(`conf.${confidence}`)} ); } export function SourceLine({ source, confidence, date }: { source: SourceKind; confidence: Confidence; date: string | null }) { const { t, date: fmt } = useI18n(); return (
{t('src.label')}
{t(`src.${source}`)}
{t('fact.updated')}
{date ? fmt(date) : t('fact.dateUnknown')}
{t('fact.confidence')}
); } export function FactCard({ fact, onReport }: { fact: Fact; onReport?: (() => void) | undefined }) { const { t, tm } = useI18n(); const canReport = onReport && ['entrance', 'steps', 'width', 'toilet', 'ramp'].includes(fact.key); return (
  • {t(`fact.title.${fact.key}`)}

    {tm(fact.headline)}

    {fact.detail &&

    {tm(fact.detail)}

    } {fact.alt && (

    {t('fact.otherSource')}: {tm(fact.alt.headline)}

    )}
    {fact.required && {t('fact.required')}} {canReport && ( )}
  • ); } export function Alert({ kind, heading, children, slim = false, role }: { kind: 'info' | 'warning' | 'error' | 'success'; heading?: string; children: ReactNode; slim?: boolean; role?: 'alert' | 'status' }) { return (
    {heading &&

    {heading}

    }
    {children}
    ); } /** Accessible help bubble: hover, focus or click opens it; Escape or blur closes it (WCAG 1.4.13). */ export function InfoTip({ topic, text }: { topic: string; text: string }) { const { t } = useI18n(); const [open, setOpen] = useState(false); const [hover, setHover] = useState(false); const id = useId(); const show = open || hover; const bubble = useRef(null); const [shift, setShift] = useState(0); useLayoutEffect(() => { const el = bubble.current; if (!show || !el) return; el.style.transform = ''; const r = el.getBoundingClientRect(); const over = r.right - (window.innerWidth - 8); setShift(over > 0 ? -Math.min(over, r.left - 8) : 0); }, [show]); return ( setHover(true)} onMouseLeave={() => setHover(false)}> ); } const TRIAGE_STYLE = { 'fast-track': 'bg-primary-dark text-white', review: 'bg-base-lighter text-ink', 'likely-reject': 'bg-base-dark text-white' } as const; const SEVERITY_STYLE = { critical: 'bg-error-dark text-white', major: 'bg-accent-warm-darker text-white', minor: 'bg-warning text-ink', none: 'bg-base-lighter text-ink' } as const; /** Automatic first assessment of a report. Always framed as advisory. */ export function VettingSummary({ v, detailed = false }: { v: VetState | undefined; detailed?: boolean }) { const { t, tm } = useI18n(); if (!v) return null; if (v.state === 'running') return

    {t('vet.running')}

    ; if (v.state === 'failed') { return

    {t(v.reason === 'no-credits' ? 'vet.noCredits' : v.reason === 'skipped' ? 'vet.skipped' : 'vet.error')}

    ; } const r = v.result; return (

    {t(`vet.triage.${r.triage}`)} {t('vet.severityLabel')}: {t(`vet.severity.${r.severity}`)} {t(`vet.validity.${r.validity}`)} {r.flags.map((f) => {t(`vet.flag.${f}`)})}

    {detailed && ( <>

    {t('vet.classLabel')}: {t(`vet.class.${r.accessibilityClass}`)} · {t(`vet.consistency.${r.consistency}`)} · {t('vet.confidence', { pct: Math.round(r.confidence * 100) })}

    {t('dash.queue.reasons')}
      {r.reasons.map((m, i) =>
    • {tm(m)}
    • )}

    {t('vet.provider', { name: r.provider })}

    {t('vet.disclaimer')}

    )}
    ); }