component · 86/108
Map Controls
Zoom, compass, locate, and custom camera buttons in stacked, bar, and compact variants.
Install
Install manuallyView source on GitHub
Anatomy
- MapFrame
- MapFramePlaceholder
- MapFrameOverlay
- MapControls
- MapControlsGroup
- MapControlsZoomIn
- MapControlsZoomOut
- MapControlsCompass
- MapControlsLocate
- MapControlsButton
Accessibility4 checks
- Controls sit in a labelled group, and every icon button has an accessible name and a matching tooltip.
- Zoom limits use the native disabled state, so keyboard and pointer users meet the same bounds.
- Locate speaks its state in its name, exposes aria-pressed while following, and aria-busy while locating.
- The compass needle animates its bearing reset, and the animation stops under reduced motion.
Maps
Code example
usage.tsx"use client";import { Layers, Maximize2 } from "lucide-react";import { useEffect, useState } from "react";import { MapControls, MapControlsButton, MapControlsCompass, MapControlsGroup, MapControlsLocate, type MapControlsLocateStatus, type MapControlsVariant, MapControlsZoomIn, MapControlsZoomOut,} from "@/components/ui/uai/map-controls";import { MapFrame, MapFrameOverlay, MapFramePlaceholder } from "@/components/ui/uai/map-frame";export function MapControlsPreview({ variant = "stacked" }: { variant?: MapControlsVariant }) { const [zoom, setZoom] = useState(14); const [bearing, setBearing] = useState(-32); const [locate, setLocate] = useState<MapControlsLocateStatus>("idle"); useEffect(() => { if (locate !== "locating") return; const timer = window.setTimeout(() => setLocate("active"), 1200); return () => window.clearTimeout(timer); }, [locate]); return ( <MapFrame aria-label="Delivery zone map" style={{ height: 320 }}> <MapFramePlaceholder style={{ rotate: `${bearing}deg`, scale: 1 + (zoom - 14) * 0.08 }} /> <MapFrameOverlay position={variant === "bar" ? "bottom" : "top-right"}> <MapControls variant={variant}> <MapControlsGroup> <MapControlsZoomIn disabled={zoom >= 18} onClick={() => setZoom((z) => z + 1)} /> <MapControlsZoomOut disabled={zoom <= 10} onClick={() => setZoom((z) => z - 1)} /> </MapControlsGroup> <MapControlsGroup> <MapControlsCompass bearing={bearing} onClick={() => setBearing(0)} /> <MapControlsLocate status={locate} onClick={() => setLocate(locate === "active" ? "idle" : "locating")} /> </MapControlsGroup> <MapControlsGroup> <MapControlsButton label="Map layers"> <Layers strokeWidth={1.75} aria-hidden="true" /> </MapControlsButton> <MapControlsButton label="Full screen"> <Maximize2 strokeWidth={1.75} aria-hidden="true" /> </MapControlsButton> </MapControlsGroup> </MapControls> </MapFrameOverlay> </MapFrame> );}Manual installation
map-controls.tsxLoading component source…