// ----------------------------------------------------------------------------- // Job·Ka — Agrégateur d'offres d'emploi (province de Québec) // Auteur : Simon-Pierre Boucher — contact@spboucher.ai // fiche/ui.tsx : primitives d'interface de la fiche d'offre (refonte // premium 2026-09-07, même socle que la fiche Lou-Ka v3) : // SectionCard · Accordion · StatTile · StatusBadge · Skeleton · EmptyState · // ErrorState · MoreButton · SourceLine · useToast — sans dépendance externe, // ARIA correcte, cibles tactiles ≥ 44 px, animations légères. // ----------------------------------------------------------------------------- import { ReactNode, useCallback, useEffect, useId, useState } from "react"; import { createPortal } from "react-dom"; import { Ico } from "../components/Icons"; export type Tone = "good" | "warn" | "bad" | "neutral" | "info"; /* --- carte de section ------------------------------------------------------ */ export function SectionCard({ id, title, icon, sub, aside, children, className = "", label }: { id?: string; title?: ReactNode; icon?: ReactNode; sub?: ReactNode; aside?: ReactNode; children: ReactNode; className?: string; label?: string; }) { return (
{(title || aside) && (
{title &&

{icon}{title}

} {sub &&

{sub}

}
{aside &&
{aside}
}
)} {children}
); } /* --- accordéon accessible (bouton + région, animation grid-rows) ----------- */ export function Accordion({ title, meta, children, defaultOpen = false, small = false, onToggle }: { title: ReactNode; meta?: ReactNode; children: ReactNode; defaultOpen?: boolean; small?: boolean; onToggle?: (open: boolean) => void; }) { const [open, setOpen] = useState(defaultOpen); const id = useId(); return (
{children}
); } /* --- tuile KPI ------------------------------------------------------------- */ export function StatTile({ value, unit, label, accent = false, anim = true }: { value: ReactNode; unit?: ReactNode; label: ReactNode; accent?: boolean; anim?: boolean; }) { return (
8 ? "wrap" : ""}`}>{value}{unit && {unit}}
{label}
); } /* --- pastille d'état ------------------------------------------------------- */ export function StatusBadge({ tone = "neutral", children, lg = false }: { tone?: Tone; children: ReactNode; lg?: boolean; }) { return {children}; } /* --- squelettes ------------------------------------------------------------ */ export function Skeleton({ h = 14, w, r, className = "" }: { h?: number | string; w?: number | string; r?: number; className?: string }) { return