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%
2.4 KB · 77 lines tsx
Raw Blame History
1/**2 * Author: Simon-Pierre Boucher3 * Contact: contact@spboucher.ai4 * Project: Groupe Ka / Ka Maps5 *6 * Selection preview shell: floating panel on desktop, bottom sheet on7 * mobile. The card CONTENT is app-owned (render prop) so each app keeps8 * its own design language; the framework owns placement, lifecycle,9 * dismissal and accessibility. React all the way — no setHTML, no XSS.10 */1112import { useEffect, useState, type ReactElement, type ReactNode } from "react";13import type { MapProperty } from "../types/index.js";14import { useKaMap } from "./KaMapView.js";1516export interface PropertyPreviewProps {17  /** App-owned card renderer for the selected property. */18  render: (property: MapProperty, close: () => void) => ReactNode;19  /** Viewport width (px) under which the bottom-sheet layout is used. */20  mobileBreakpoint?: number;21  closeLabel?: string;22}2324export function PropertyPreview(props: PropertyPreviewProps): ReactElement | null {25  const map = useKaMap();26  const [property, setProperty] = useState<MapProperty | null>(null);27  const [mobile, setMobile] = useState(false);28  const breakpoint = props.mobileBreakpoint ?? 780;2930  useEffect(() => {31    if (!map) return;32    return map.events.on("select", ({ propertyId }) => {33      setProperty(propertyId ? map.getProperty(propertyId) ?? null : null);34    });35  }, [map]);3637  useEffect(() => {38    const mq = window.matchMedia(`(max-width: ${breakpoint}px)`);39    const update = () => setMobile(mq.matches);40    update();41    mq.addEventListener("change", update);42    return () => mq.removeEventListener("change", update);43  }, [breakpoint]);4445  useEffect(() => {46    if (!property) return;47    const onKey = (e: KeyboardEvent) => {48      if (e.key === "Escape") close();49    };50    window.addEventListener("keydown", onKey);51    return () => window.removeEventListener("keydown", onKey);52    // eslint-disable-next-line react-hooks/exhaustive-deps53  }, [property]);5455  if (!map || !property) return null;5657  const close = () => map.select(null, "app");5859  return (60    <div61      className={mobile ? "ka-preview ka-preview-sheet" : "ka-preview ka-preview-panel"}62      role="dialog"63      aria-label={property.address ?? "Propriété sélectionnée"}64    >65      <button66        type="button"67        className="ka-preview-close"68        onClick={close}69        aria-label={props.closeLabel ?? "Fermer"}70      >71        ×72      </button>73      {props.render(property, close)}74    </div>75  );76}77