{props.emptyTitle ?? "Aucune propriété trouvée dans cette zone."}{props.emptyHint ?? "Élargissez la carte ou modifiez vos filtres."}
);
}
const hidden = total !== undefined && total > count ? total - count : 0;
return (
{count.toLocaleString("fr-CA")} sur la carte
{hidden > 0 ? ` · ${hidden.toLocaleString("fr-CA")} hors carte` : ""}
);
}
/**
* Groupe Ka brand badge — every Ka Maps instance carries the family mark:
* the app's map product name over the "Ka Maps · Groupe Ka" signature.
* Complements (never replaces) the legally required OSM attribution.
*/
export function KaBrandBadge(props: { subtitle?: string }): ReactElement | null {
const map = useKaMap();
if (!map) return null;
const theme = map.getTheme();
return (
{theme.productName}
{props.subtitle ?? "Ka Maps · Groupe Ka"}
);
}
/** 3D tilt toggle — buildings gain their real extruded volumes at street
* zoom; this control tilts the camera to reveal them. Never the default. */
export function Tilt3DControl(props: { label3d?: string; label2d?: string }): ReactElement | null {
const map = useKaMap();
const [tilted, setTilted] = useState(() => map?.isTilted() ?? false);
useEffect(() => {
if (!map) return;
return map.events.on("moveend", () => setTilted(map.isTilted()));
}, [map]);
if (!map) return null;
return (
);
}
/** "Locate me" — geolocation strictly on user action, graceful denial. */
export function LocateControl(props: { label?: string }): ReactElement | null {
const map = useKaMap();
const [state, setState] = useState<"idle" | "busy" | "denied">("idle");
if (!map) return null;
const locate = () => {
if (!("geolocation" in navigator)) {
setState("denied");
return;
}
setState("busy");
navigator.geolocation.getCurrentPosition(
(pos) => {
setState("idle");
map.map.easeTo({
center: [pos.coords.longitude, pos.coords.latitude],
zoom: Math.max(map.map.getZoom(), 14),
duration: 600,
});
},
() => setState("denied"),
{ enableHighAccuracy: true, timeout: 10_000 },
);
};
return (
);
}
/**
* Outil « Dessiner une zone » — démarre/annule le tracé d'un polygone sur
* la carte ; quand une zone est posée, le bouton devient « Effacer la zone ».
* L'app écoute onDraw (KaMapView) pour transformer le polygone en filtre.
*/
export function DrawControl(props: {
labelStart?: string;
labelDrawing?: string;
labelClear?: string;
}): ReactElement | null {
const map = useKaMap();
const [drawing, setDrawing] = useState(false);
const [hasZone, setHasZone] = useState(
() => (map?.getDrawnPolygon() ?? null) !== null,
);
useEffect(() => {
if (!map) return;
setDrawing(map.isDrawing());
setHasZone(map.getDrawnPolygon() !== null);
return map.events.on("draw", ({ polygon, drawing: d }) => {
setDrawing(d);
setHasZone(polygon !== null);
});
}, [map]);
if (!map) return null;
const onClick = () => {
if (drawing) map.cancelDraw();
else if (hasZone) map.clearDrawnPolygon();
else map.startDraw();
};
return (
);
}