SPB Git forge

spb/ka-maps

Public
6commits 1branches 0releases
448.0 KBsize
maindefault branch
29 days agolast push
TypeScript 87.7% CSS 12.3%
6.0 KB · 210 lines tsx
Raw Blame History
1/**2 * Author: Simon-Pierre Boucher3 * Contact: contact@spboucher.ai4 * Project: Groupe Ka / Ka Maps5 *6 * KaMapToolbar — LA grappe de contrôles du Ka Map System v2. Fini les7 * pilules indépendantes qui flottent partout : une seule colonne compacte8 * de boutons iconographiques (zoom, 3D, dessiner, me localiser, filtres…),9 * regroupés par segments hairline. Chaque bouton expose son libellé en10 * infobulle/aria — le chrome reste minimal, la carte respire.11 */1213import {14  useEffect,15  useState,16  type ReactElement,17  type ReactNode,18} from "react";19import { useKaMap } from "./KaMapView.js";2021/* ------------------------------------------------------------------ icônes */2223function Icon({ d, filled }: { d: string; filled?: boolean }): ReactElement {24  return (25    <svg26      width="17"27      height="17"28      viewBox="0 0 24 24"29      fill={filled ? "currentColor" : "none"}30      stroke="currentColor"31      strokeWidth="2"32      strokeLinecap="round"33      strokeLinejoin="round"34      aria-hidden="true"35    >36      <path d={d} />37    </svg>38  );39}4041const ICONS = {42  plus: "M12 5v14M5 12h14",43  minus: "M5 12h14",44  locate: "M12 2v3M12 19v3M2 12h3M19 12h3M12 8a4 4 0 100 8 4 4 0 000-8z",45  draw: "M12 19l7-7 3 3-7 7-3-3zM18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5zM2 2l7.586 7.586M11 13a2 2 0 100-4 2 2 0 000 4z",46  layers: "M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5",47  filters: "M21 4h-7M10 4H3M21 12h-9M8 12H3M21 20h-5M12 20H3M14 2v4M8 10v4M16 18v4",48  close: "M18 6L6 18M6 6l12 12",49};5051/* ------------------------------------------------------------- primitives */5253export interface KaToolbarButtonProps {54  label: string;55  onClick: () => void;56  active?: boolean;57  disabled?: boolean;58  icon?: keyof typeof ICONS;59  /** Icône/contenu custom (prime sur `icon`). */60  children?: ReactNode;61  badge?: string | number;62}6364export function KaToolbarButton(props: KaToolbarButtonProps): ReactElement {65  return (66    <button67      type="button"68      className={`ka-tb-btn${props.active ? " on" : ""}`}69      onClick={props.onClick}70      disabled={props.disabled}71      aria-label={props.label}72      aria-pressed={props.active}73      title={props.label}74    >75      {props.children ?? (props.icon ? <Icon d={ICONS[props.icon]} /> : null)}76      {props.badge != null && props.badge !== 0 ? (77        <span className="ka-tb-badge">{props.badge}</span>78      ) : null}79    </button>80  );81}8283/** Conteneur : colonne de segments (chaque enfant direct = un groupe). */84export function KaMapToolbar(props: {85  children: ReactNode;86  className?: string;87}): ReactElement {88  return (89    <div className={`ka-toolbar ${props.className ?? ""}`} role="toolbar" aria-label="Outils de carte">90      {props.children}91    </div>92  );93}9495export function KaToolbarGroup(props: { children: ReactNode }): ReactElement {96  return <div className="ka-tb-group">{props.children}</div>;97}9899/* ------------------------------------------------------- boutons intégrés */100101/** Zoom +/− (remplace le NavigationControl natif — passer navControl:false). */102export function KaToolbarZoom(): ReactElement | null {103  const map = useKaMap();104  if (!map) return null;105  return (106    <>107      <KaToolbarButton label="Zoom avant" icon="plus" onClick={() => map.map.zoomIn()} />108      <KaToolbarButton label="Zoom arrière" icon="minus" onClick={() => map.map.zoomOut()} />109    </>110  );111}112113/** Bascule 3D — libellé texte court, état visuel accentué. */114export function KaToolbar3D(): ReactElement | null {115  const map = useKaMap();116  const [tilted, setTilted] = useState(() => map?.isTilted() ?? false);117118  useEffect(() => {119    if (!map) return;120    return map.events.on("moveend", () => setTilted(map.isTilted()));121  }, [map]);122123  if (!map) return null;124  return (125    <KaToolbarButton126      label={tilted ? "Vue à plat (2D)" : "Vue en relief (3D)"}127      active={tilted}128      onClick={() => map.setTilt(!tilted)}129    >130      <span className="ka-tb-txt">{tilted ? "2D" : "3D"}</span>131    </KaToolbarButton>132  );133}134135/** Dessiner une zone — icône crayon ; état actif pendant le tracé et tant136 *  qu'une zone est posée (re-clic : annule ou efface). */137export function KaToolbarDraw(): ReactElement | null {138  const map = useKaMap();139  const [drawing, setDrawing] = useState(false);140  const [hasZone, setHasZone] = useState(false);141142  useEffect(() => {143    if (!map) return;144    setDrawing(map.isDrawing());145    setHasZone(map.getDrawnPolygon() !== null);146    return map.events.on("draw", ({ polygon, drawing: d }) => {147      setDrawing(d);148      setHasZone(polygon !== null);149    });150  }, [map]);151152  if (!map) return null;153  return (154    <KaToolbarButton155      label={156        drawing157          ? "Annuler le tracé"158          : hasZone159            ? "Effacer la zone dessinée"160            : "Dessiner une zone"161      }162      active={drawing || hasZone}163      icon="draw"164      onClick={() => {165        if (drawing) map.cancelDraw();166        else if (hasZone) map.clearDrawnPolygon();167        else map.startDraw();168      }}169    />170  );171}172173/** Me localiser — géolocalisation sur geste explicite uniquement. */174export function KaToolbarLocate(props: {175  /** Recentrage effectué — l'app peut synchroniser sa liste. */176  onLocated?: (pos: { lat: number; lng: number }) => void;177}): ReactElement | null {178  const map = useKaMap();179  const [state, setState] = useState<"idle" | "busy" | "denied">("idle");180181  if (!map) return null;182183  const locate = () => {184    if (!("geolocation" in navigator)) {185      setState("denied");186      return;187    }188    setState("busy");189    navigator.geolocation.getCurrentPosition(190      (pos) => {191        setState("idle");192        const at = { lat: pos.coords.latitude, lng: pos.coords.longitude };193        map.flyTo(at, { zoom: Math.max(map.map.getZoom(), 13.5), duration: 700 });194        props.onLocated?.(at);195      },196      () => setState("denied"),197      { enableHighAccuracy: true, timeout: 10_000 },198    );199  };200201  return (202    <KaToolbarButton203      label={state === "denied" ? "Géolocalisation indisponible" : "Me localiser"}204      icon="locate"205      disabled={state === "busy"}206      onClick={locate}207    />208  );209}210