import type { ReactNode } from 'react'; import { cn } from '@/lib/cn'; import { eventStyle, subtypeLabel } from '@/lib/event-styles'; import { importanceSteps, significanceBand } from '@/lib/format'; import { CONFIDENCE_LABELS, SENSOR_TIER_LABELS } from '@/lib/site'; const base = 'inline-flex items-center gap-1 whitespace-nowrap rounded-[3px] px-1.5 py-[1px] text-[11px] font-medium leading-4 tracking-wide'; /** Event type chip with its fixed hue; `sub` appends the careful subtype label. */ export function EventTypeBadge({ type, subtype, className, small = false }: { type: string; subtype?: string | null; className?: string; small?: boolean }) { const s = eventStyle(type); return ( {s.label} {subtype && · {subtypeLabel(subtype)}} ); } const CONF: Record = { VERIFIED: 'text-positive bg-positive-soft', HIGH_CONFIDENCE: 'text-positive bg-positive-soft', LIKELY: 'text-accent bg-accent-soft', INFERRED: 'text-warning bg-warning-soft', LOW_CONFIDENCE: 'text-danger bg-danger-soft', }; /** Confidence label chip (spec §50). Renders nothing when missing — never invents a status. */ export function ConfidenceBadge({ label, value, className, withValue = false }: { label: string | null | undefined; value?: number | null; className?: string; withValue?: boolean }) { if (!label) return null; const k = label.toUpperCase(); return ( {CONFIDENCE_LABELS[k] ?? label} {withValue && value !== null && value !== undefined && {Math.round(value * 100)} %} ); } /** Importance as a stepped meter (▮▮▮▯). */ export function ImportanceMeter({ importance, className }: { importance: number | null | undefined; className?: string }) { const n = importanceSteps(importance); const pct = importance === null || importance === undefined ? null : Math.round((importance > 1 ? importance : importance * 100)); return ( {[1, 2, 3].map((i) => ( ))} ); } const SIG: Record = { noise: 'text-ink-3 bg-surface-2', minor: 'text-ink-2 bg-surface-2', meaningful: 'text-accent bg-accent-soft', major: 'text-warning bg-warning-soft', critical: 'text-danger bg-danger-soft' }; export function SignificanceBadge({ value, className }: { value: number | null | undefined; className?: string }) { const band = significanceBand(value); if (!band) return null; return ( {band} {(value as number).toFixed(2)} ); } /** Sensor tier A–E (crawl cadence). */ export function SensorTierBadge({ tier, className, withLabel = false }: { tier: string | null | undefined; className?: string; withLabel?: boolean }) { if (!tier) return null; const t = tier.toLowerCase(); return ( {tier} {withLabel && · {SENSOR_TIER_LABELS[tier]}} ); } /** Company tier 1–4 (importance band). */ export function CompanyTierBadge({ tier, className }: { tier: number | null | undefined; className?: string }) { if (!tier) return null; return ( T{tier} ); } const STATUS: Record = { active: 'text-positive bg-positive-soft', ok: 'text-positive bg-positive-soft', healthy: 'text-positive bg-positive-soft', open: 'text-positive bg-positive-soft', listed: 'text-positive bg-positive-soft', current: 'text-positive bg-positive-soft', pending: 'text-accent bg-accent-soft', running: 'text-accent bg-accent-soft', review: 'text-warning bg-warning-soft', paused: 'text-warning bg-warning-soft', stale: 'text-warning bg-warning-soft', possibly_inactive: 'text-warning bg-warning-soft', failing: 'text-danger bg-danger-soft', failed: 'text-danger bg-danger-soft', dead: 'text-danger bg-danger-soft', blocked: 'text-danger bg-danger-soft', website_unavailable: 'text-danger bg-danger-soft', retracted: 'text-danger bg-danger-soft', retired: 'text-ink-3 bg-surface-2', duplicate: 'text-ink-3 bg-surface-2', no_longer_listed: 'text-ink-3 bg-surface-2', removed: 'text-ink-3 bg-surface-2', superseded: 'text-ink-3 bg-surface-2', done: 'text-ink-2 bg-surface-2', }; const STATUS_LABEL: Record = { no_longer_listed: 'no longer listed', removed: 'no longer listed', possibly_inactive: 'possibly inactive', website_unavailable: 'website unavailable' }; export function StatusBadge({ status, className }: { status: string | null | undefined; className?: string }) { if (!status) return null; const k = status.toLowerCase(); return {STATUS_LABEL[k] ?? k.replace(/_/g, ' ')}; } /** ISO country chip (no flag emoji — consistent across platforms). */ export function CountryChip({ code, name, className, link = true }: { code: string | null | undefined; name?: string | null; className?: string; link?: boolean }) { if (!code) return null; const inner = ( {code.toUpperCase()} ); if (!link) return inner; return ( {inner} ); } export function Chip({ children, className, tone = 'neutral', title }: { children: ReactNode; className?: string; tone?: 'neutral' | 'accent' | 'positive' | 'warning' | 'danger' | 'outline'; title?: string }) { return ( {children} ); } export function OriginBadge({ origin, model, prompt }: { origin: string | null | undefined; model?: string | null; prompt?: string | null }) { if (!origin) return null; const llm = origin === 'llm' || origin === 'hybrid'; return ( {origin} {llm && model && · {model}} ); }