import React from 'react'; import { cn } from '../../shared/utils'; import { Switch } from '../switch'; import { Label } from '../label'; import { mapOverlayPositionClasses } from '../map/map-overlay-position'; import { useMapTokenColors } from '../map/use-map-token-color'; import type { MapDataLayer } from '../map/map-layers-model'; import type { ColorToken, MapOverlayPosition } from '../map/map.types'; export interface MapLayerPanelProps extends React.HTMLAttributes { /** The same array passed to ``. */ layers: MapDataLayer[]; onToggle: (id: string, visible: boolean) => void; title?: string; /** * `'overlay'` floats over the map and needs a positioned ancestor; * `'inline'` flows in normal layout. * @default 'inline' */ variant?: 'overlay' | 'inline'; /** @default 'top-right' */ position?: MapOverlayPosition; /** @default "Map layers" */ ariaLabel?: string; } /** Layers keep their declared order inside each group; groups keep first-seen order. */ function groupLayers(layers: MapDataLayer[]): Array<[string, MapDataLayer[]]> { const groups = new Map(); layers.forEach(layer => { const key = layer.group ?? ''; const bucket = groups.get(key); if (bucket) bucket.push(layer); else groups.set(key, [layer]); }); return Array.from(groups.entries()); } /** * Shows and hides the named layers of a map. * * @description * With four overlays on screen at once — heat underneath, areas, lines, points * on top — the stack stops being a detail and becomes the thing the user * operates. Each row carries the layer's label, its color swatch and a switch, * grouped by `group`. * * Built on `Switch` and `Label` so the control is operable by keyboard and * announced correctly, instead of a hand-rolled checkbox. * * @ai-rules * 1. Pass the same `layers` array to `` and to this panel. * 2. Toggle by updating that array's `visible` field — the panel is controlled and holds no state. * 3. For `variant="overlay"`, wrap the map and the panel in a `relative` container. */ export const MapLayerPanel = React.forwardRef( ( { layers, onToggle, title, variant = 'inline', position = 'top-right', ariaLabel = 'Map layers', className, ...props }, ref ) => { const tokens = React.useMemo( () => layers.map(layer => layer.colorToken).filter(Boolean) as ColorToken[], [layers] ); const colors = useMapTokenColors(tokens); const groups = React.useMemo(() => groupLayers(layers), [layers]); return (
{title ?

{title}

: null}
{groups.map(([group, groupLayersList]) => (
{group ? (

{group}

) : null}
    {groupLayersList.map(layer => { const controlId = `map-layer-${layer.id}`; return (
  • {layer.colorToken ? (
  • ); })}
))}
); } ); MapLayerPanel.displayName = 'MapLayerPanel';