/** * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai * Project: Groupe Ka / Ka Maps * * KaMapDesktopSplit — l'agencement grand écran du Ka Map System v2 : * résultats à gauche, carte à droite, séparés par une poignée glissable * (24 % à 60 % pour la liste, ratio persisté par app). Un bouton sur la * poignée bascule en mode « carte quasi plein écran » (liste repliée) et * inversement. Double-clic sur la poignée : ratio par défaut. */ import { useCallback, useEffect, useRef, useState, type ReactElement, type ReactNode, } from "react"; export type KaSplitLayout = "split" | "map"; const DEFAULT_RATIO = 0.40; const MIN_RATIO = 0.24; const MAX_RATIO = 0.60; export interface KaMapDesktopSplitProps { layout: KaSplitLayout; onLayout: (l: KaSplitLayout) => void; /** Clé localStorage du ratio (ex. "ka-split-lou-ka"). */ storageKey?: string; list: ReactNode; /** La carte. */ children: ReactNode; } export function KaMapDesktopSplit(props: KaMapDesktopSplitProps): ReactElement { const { layout, onLayout } = props; const rootRef = useRef(null); const [ratio, setRatio] = useState(() => { if (props.storageKey) { const saved = Number(localStorage.getItem(props.storageKey)); if (Number.isFinite(saved) && saved >= MIN_RATIO && saved <= MAX_RATIO) { return saved; } } return DEFAULT_RATIO; }); const [dragging, setDragging] = useState(false); const persist = useCallback( (r: number) => { if (props.storageKey) { try { localStorage.setItem(props.storageKey, String(r)); } catch { // stockage plein/privé : le ratio reste pour la session } } }, [props.storageKey], ); const onPointerDown = useCallback( (e: React.PointerEvent) => { if (layout !== "split") return; e.preventDefault(); setDragging(true); (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId); }, [layout], ); const onPointerMove = useCallback( (e: React.PointerEvent) => { if (!dragging || !rootRef.current) return; const rect = rootRef.current.getBoundingClientRect(); const r = (e.clientX - rect.left) / rect.width; setRatio(Math.min(MAX_RATIO, Math.max(MIN_RATIO, r))); }, [dragging], ); const onPointerUp = useCallback( (e: React.PointerEvent) => { if (!dragging) return; setDragging(false); try { (e.currentTarget as HTMLElement).releasePointerCapture(e.pointerId); } catch { // capture déjà relâchée } setRatio((r) => { persist(r); return r; }); }, [dragging, persist], ); // La carte doit se recaler quand la colonne change de largeur. useEffect(() => { const t = setTimeout(() => window.dispatchEvent(new Event("resize")), 220); return () => clearTimeout(t); }, [layout, ratio]); const mapFull = layout === "map"; return (
{props.list}
{ setRatio(DEFAULT_RATIO); persist(DEFAULT_RATIO); }} >
{props.children}
); }