// ----------------------------------------------------------------------------- // Job·Ka — Agrégateur d'offres d'emploi (province de Québec) // Auteur : Simon-Pierre Boucher — contact@spboucher.ai // fiche/BottomSheet.tsx : panneau générique — bottom sheet sur mobile (hauteur // initiale 68 % du viewport, glisser vers le haut = plein écran, glisser // vers le bas = fermeture), modale centrée à partir de 900 px (CSS). // Portail à la racine, verrou du défilement (.ka-scroll-lock), Escape, // focus initial, aria-modal. Utilisé pour lieux, stations, assistant Ka. // ----------------------------------------------------------------------------- import { ReactNode, useEffect, useRef, useState } from "react"; import { createPortal } from "react-dom"; import { Ico } from "../components/Icons"; export default function BottomSheet({ open, onClose, title, sub, children, footer, tall = false }: { open: boolean; onClose: () => void; title: ReactNode; sub?: ReactNode; children: ReactNode; footer?: ReactNode; tall?: boolean; }) { const [full, setFull] = useState(tall); const panel = useRef(null); const drag = useRef<{ y0: number; t0: number } | null>(null); useEffect(() => { if (!open) return; setFull(tall); document.documentElement.classList.add("ka-scroll-lock"); const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); }; window.addEventListener("keydown", onKey); const t = setTimeout(() => panel.current?.focus(), 30); return () => { document.documentElement.classList.remove("ka-scroll-lock"); window.removeEventListener("keydown", onKey); clearTimeout(t); }; }, [open, onClose, tall]); if (!open) return null; const onDown = (e: React.PointerEvent) => { drag.current = { y0: e.clientY, t0: Date.now() }; }; const onUp = (e: React.PointerEvent) => { if (!drag.current) return; const dy = e.clientY - drag.current.y0; drag.current = null; if (dy > 90) onClose(); else if (dy < -60) setFull(true); }; return createPortal( <>