@optimum-intelligence/opti-ui 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,32 @@
3
3
  All notable changes to `@optimum-intelligence/opti-ui`. Versions follow [semver](https://semver.org/); while
4
4
  the version is `0.x`, minor releases may contain breaking changes, which are marked **Breaking**.
5
5
 
6
+ ## 0.7.0 (2026-11-03)
7
+
8
+ ### Added
9
+
10
+ - Map components in a new `@optimum-intelligence/opti-ui/map` entry, on MapLibre (`maplibre-gl` and
11
+ `@vis.gl/react-maplibre` are installed with the package; import `@optimum-intelligence/opti-ui/map.css`
12
+ once): `Map.Root`, `Map.Routes`, `Map.Areas`,
13
+ `Map.Points`, `Map.Marker`, `Map.Popup`, `Map.Tooltip`, `Map.Overlay`, `Map.Legend`,
14
+ `Map.BaseLayerSwitch`, plus `rasterStyle()` and `useResolvedColors()`. `Map.Root expandable` adds a
15
+ button that expands the map over the window, and `Map.Marker dimmed` fades a pin with the selection.
16
+ - `seriesColor(index)` for the chart palette, and the `--oui-map-route-weight`, `--oui-map-casing-color`
17
+ and `--oui-map-dim-opacity` tokens.
18
+
19
+ ### Changed
20
+
21
+ - **Breaking:** the chart palette has eight tokens, `--oui-color-chart-1` to `-8`, taken from the colour
22
+ tones (blue, warning, green, pink, cyan, purple, yellow, accent) and following dark mode. The values of
23
+ `-1` to `-5` change.
24
+
25
+ ## 0.6.2 (2026-10-27)
26
+
27
+ ### Fixed
28
+
29
+ - Menu, select, combobox and command palette items and date range presets show a pointer cursor.
30
+ - Tags no longer cut off the bottoms of letters like g and p.
31
+
6
32
  ## 0.6.0 (2026-10-26)
7
33
 
8
34
  ### Added
package/README.md CHANGED
@@ -78,7 +78,8 @@ so the components work in Next.js App Router.
78
78
  | Overlays | `Dialog.*` (modal, or a sheet with `placement`), `AlertDialog.*`, `Drawer` (detail side panel), `Popover.*`, `PreviewCard.*`, `Tooltip`, `ToastProvider` + `useToast()`, `CommandPalette` |
79
79
  | Disclosure & navigation | `Tabs.*`, `Accordion.*`, `Collapsible.*`, `Breadcrumbs` + `Breadcrumb`, `Pagination`, `Stepper` |
80
80
  | Feedback & data | `Alert`, `Badge`, `Tag`, `Avatar`, `AvatarGroup`, `Progress`, `Meter`, `Skeleton`, `SkeletonText`, `Spinner`, `Table` (+ parts), `DataTable`, `EmptyState`, `Heading`, `Text`, `Eyebrow`, `Link` |
81
- | Theme | `ThemeProvider`, `createTheme`, `themeToStyle`, `DateConfigProvider`, `useDateConfig`, icons (`CheckIcon`, `ChevronDownIcon`, …) |
81
+ | Maps (`/map` entry) | `Map.Root`, `Map.Routes`, `Map.Areas`, `Map.Points`, `Map.Marker`, `Map.Popup`, `Map.Tooltip`, `Map.Overlay`, `Map.Legend`, `Map.BaseLayerSwitch`, `rasterStyle`, `useResolvedColors` (see [Maps](#maps)) |
82
+ | Theme | `ThemeProvider`, `createTheme`, `themeToStyle`, `seriesColor`, `DateConfigProvider`, `useDateConfig`, icons (`CheckIcon`, `ChevronDownIcon`, …) |
82
83
 
83
84
  Components with a common one-line form can be called directly, and their parts are attached for full
84
85
  control: `<Select items={…} />` or `<Select.Root>…<Select.Trigger/>…</Select.Root>`; the same applies to
@@ -201,6 +202,48 @@ components the same table.
201
202
  `Calendar`, `FileUpload`, `Stepper`) take a `labels` prop for translation, and the date components take a
202
203
  `locale`.
203
204
 
205
+ ## Maps
206
+
207
+ Map components live in a separate entry, `@optimum-intelligence/opti-ui/map`, so apps without maps never
208
+ load MapLibre. `maplibre-gl` and `@vis.gl/react-maplibre` are installed with the package. Import the map
209
+ stylesheet once; it holds MapLibre's CSS in a cascade layer below the library's, so the oui map styles win.
210
+ `Map.Root` sets up MapLibre's web worker itself, which needs Vite (it imports the worker with `?url`).
211
+
212
+ ```tsx
213
+ import '@optimum-intelligence/opti-ui/map.css';
214
+ import { seriesColor } from '@optimum-intelligence/opti-ui';
215
+ import { Map } from '@optimum-intelligence/opti-ui/map';
216
+
217
+ const baseLayers = [
218
+ {
219
+ id: 'streets',
220
+ label: 'Streets',
221
+ style: 'https://tiles.openfreemap.org/styles/bright',
222
+ darkStyle: 'https://tiles.openfreemap.org/styles/dark',
223
+ },
224
+ ];
225
+
226
+ <Map.Root baseLayers={baseLayers} fit="selected">
227
+ <Map.Routes items={trips} selectedIds={selected} onItemClick={(trip) => setSelected([trip.id])} />
228
+ <Map.Marker kind="depot" position={[19.06, 47.49]} />
229
+ <Map.Overlay position="top-left">
230
+ <Map.Legend
231
+ items={trips.map((trip, i) => ({ id: trip.id, label: trip.name, color: seriesColor(i) }))}
232
+ selectionMode="single"
233
+ selectedIds={selected}
234
+ onSelectedIdsChange={setSelected}
235
+ />
236
+ </Map.Overlay>
237
+ </Map.Root>
238
+ ```
239
+
240
+ The library hardcodes no tile provider: every base map comes from `baseLayers`, as a style URL, a style
241
+ object, or `rasterStyle(tilesUrl, attribution)` for a raster tile template. [OpenFreeMap](https://openfreemap.org)
242
+ styles work without an API key. Check the terms of any imagery provider before using it in production;
243
+ the satellite layer in the showcase is for the demo only. Data parts (`Map.Routes`, `Map.Areas`,
244
+ `Map.Points`) take arrays and draw each array as one layer set, so thousands of items are fine; their
245
+ colours follow the `--oui-*` tokens and dark mode.
246
+
204
247
  ## Customization
205
248
 
206
249
  Start with the simplest option that does what you need:
@@ -0,0 +1,373 @@
1
+ import * as React from 'react';
2
+ import { type ControlPosition, type MapLayerMouseEvent, type MapProps, type MapRef, type PopupProps, type StyleSpecification } from '@vis.gl/react-maplibre';
3
+ import { type SegmentedControlProps } from '../toggle/Toggle';
4
+ /** A `[longitude, latitude]` pair. */
5
+ export type MapPosition = [number, number];
6
+ /** Status tone for areas, points and markers. */
7
+ export type MapTone = 'neutral' | 'accent' | 'success' | 'warning' | 'danger';
8
+ /** What `Map.Root` zooms to: all data, the selected items (all data when nothing is selected), or nothing. */
9
+ export type MapFit = 'data' | 'selected' | 'none';
10
+ /** Corner of the map for `Map.Overlay`. */
11
+ export type MapOverlayPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
12
+ /** Pin shape of `Map.Marker`. */
13
+ export type MapMarkerKind = 'stop' | 'depot' | 'vehicle' | 'point';
14
+ /** Selection behaviour of `Map.Legend`. */
15
+ export type MapLegendSelectionMode = 'none' | 'single' | 'multi';
16
+ /** One switchable base map. */
17
+ export interface MapBaseLayer {
18
+ /** Stable id, used by `baseLayer` and `Map.BaseLayerSwitch`. */
19
+ id: string;
20
+ /** Name shown in `Map.BaseLayerSwitch`. */
21
+ label: React.ReactNode;
22
+ /** Style URL or a full style object. Use `rasterStyle()` for a raster tile template. */
23
+ style: string | StyleSpecification;
24
+ /** Style used instead of `style` when the map sits in dark mode. */
25
+ darkStyle?: string | StyleSpecification;
26
+ /** Extra attribution text shown with the style's own. */
27
+ attribution?: string;
28
+ }
29
+ /** Texts built into the map components. */
30
+ export interface MapLabels {
31
+ /** Zoom-in button. */
32
+ zoomIn: string;
33
+ /** Zoom-out button. */
34
+ zoomOut: string;
35
+ /** Legend button that shows every item. */
36
+ showAll: string;
37
+ /** Legend button that hides every item. */
38
+ hideAll: string;
39
+ /** Prefix of a legend item's show button, e.g. "Show Trip 1". */
40
+ show: string;
41
+ /** Prefix of a legend item's hide button. */
42
+ hide: string;
43
+ /** Accessible name of depot markers. */
44
+ depot: string;
45
+ /** Shown over the map when every data part is empty. */
46
+ noData: string;
47
+ /** Button that expands the map to fill the window (`expandable`). */
48
+ expand: string;
49
+ /** Button that returns the expanded map to its place. */
50
+ collapse: string;
51
+ }
52
+ /** A GeoJSON `Polygon`: an outer ring and optional holes, each a list of `[longitude, latitude]`. */
53
+ export interface MapPolygon {
54
+ /** GeoJSON geometry type. */
55
+ type: 'Polygon';
56
+ /** Rings of positions; the first is the outline, the rest are holes. */
57
+ coordinates: number[][][];
58
+ }
59
+ /** A GeoJSON `MultiPolygon`: several polygons drawn as one area. */
60
+ export interface MapMultiPolygon {
61
+ /** GeoJSON geometry type. */
62
+ type: 'MultiPolygon';
63
+ /** One list of rings per polygon. */
64
+ coordinates: number[][][][];
65
+ }
66
+ /** Starting camera: a centre and zoom, or bounds to fit. */
67
+ export type MapView = {
68
+ longitude: number;
69
+ latitude: number;
70
+ zoom: number;
71
+ } | {
72
+ bounds: [MapPosition, MapPosition];
73
+ };
74
+ /**
75
+ * Builds a MapLibre style from a raster tile URL template (`{z}`, `{x}`, `{y}`), for `MapBaseLayer.style`.
76
+ *
77
+ * @example
78
+ * const satellite = rasterStyle('https://tiles.example.com/{z}/{x}/{y}.jpg', '© Example');
79
+ */
80
+ export declare function rasterStyle(tiles: string | string[], attribution?: string, tileSize?: number): StyleSpecification;
81
+ export interface MapRootProps extends Omit<MapProps, 'mapStyle' | 'initialViewState' | 'interactiveLayerIds' | 'cursor' | 'locale' | 'style' | 'children' | 'id' | 'attributionControl' | 'workerUrl' | 'mapLib'> {
82
+ /** Ref to the map instance (`flyTo`, `getMap()`, …), set once the map has loaded MapLibre. */
83
+ ref?: React.Ref<MapRef>;
84
+ /** Base maps to choose from. The first is shown unless `baseLayer` or `defaultBaseLayer` says otherwise. */
85
+ baseLayers: MapBaseLayer[];
86
+ /** Id of the shown base map (controlled). */
87
+ baseLayer?: string;
88
+ /** Id of the initially shown base map when uncontrolled. */
89
+ defaultBaseLayer?: string;
90
+ /** Called with the base map id that `Map.BaseLayerSwitch` picked. */
91
+ onBaseLayerChange?: (id: string) => void;
92
+ /** Starting camera, used until `fit` moves it. */
93
+ initialView?: MapView;
94
+ /**
95
+ * Zooms to the data (`data`) or to the selection, falling back to the data (`selected`). Data changes
96
+ * refit until the user pans or zooms the map; selection changes always refit with `selected`.
97
+ */
98
+ fit?: MapFit;
99
+ /** Translations of the built-in texts; also passed to MapLibre's control strings. */
100
+ labels?: Partial<MapLabels>;
101
+ /** Corner of the zoom buttons, or `false` to hide them. */
102
+ controlsPosition?: ControlPosition | false;
103
+ /**
104
+ * Adds a button, next to the zoom buttons, that expands the map over the whole window (not browser
105
+ * fullscreen) above the page, at the dialog z-index. Escape or the button returns it.
106
+ */
107
+ expandable?: boolean;
108
+ /** Classes for the map container. Set its height in CSS (`--oui-map-height`, default 24rem). */
109
+ className?: string;
110
+ /** Inline styles for the map container. */
111
+ style?: React.CSSProperties;
112
+ /** Data parts (`Map.Routes`, `Map.Areas`, …), markers, popups and overlays. */
113
+ children?: React.ReactNode;
114
+ }
115
+ /**
116
+ * A MapLibre map styled and themed with the library tokens. Holds the base layer state and routes clicks
117
+ * and hovers to the data parts inside it. `Map.Root` › data parts (`Map.Areas`, `Map.Routes`,
118
+ * `Map.Points`) and `Map.Marker`s, with `Map.Overlay`s for legends, switches and cards on top.
119
+ * Needs `@optimum-intelligence/opti-ui/map.css` imported; MapLibre's worker is set up for Vite apps.
120
+ *
121
+ * @example
122
+ * <Map.Root
123
+ * baseLayers={[{ id: 'streets', label: 'Streets', style: 'https://tiles.openfreemap.org/styles/bright' }]}
124
+ * initialView={{ longitude: 19.04, latitude: 47.5, zoom: 10 }}
125
+ * >
126
+ * <Map.Routes
127
+ * items={[{ id: 'trip-1', coordinates: [[19.0, 47.48], [19.08, 47.52]] }]}
128
+ * selectedIds={selected}
129
+ * onItemClick={(trip) => setSelected([trip.id])}
130
+ * />
131
+ * <Map.Marker kind="depot" position={[19.0, 47.48]} />
132
+ * </Map.Root>
133
+ */
134
+ declare function MapRoot({ ref, baseLayers, baseLayer, defaultBaseLayer, onBaseLayerChange, initialView, fit, labels, controlsPosition, expandable, className, style, children, onClick, onMouseMove, onMouseOut, ...props }: MapRootProps): React.JSX.Element;
135
+ export interface MapRouteItem {
136
+ /** Unique id; reported back by `onItemClick` and used in `selectedIds`. */
137
+ id: string;
138
+ /** Line vertices as `[longitude, latitude]`. */
139
+ coordinates: MapPosition[];
140
+ /** Any CSS colour, `var(--oui-…)` included. Wins over `colorIndex`. */
141
+ color?: string;
142
+ /**
143
+ * Series index for `seriesColor()`. Defaults to the item's index in `items`, so pass it (or `color`)
144
+ * when you filter `items`; otherwise colours shift and stop matching the legend.
145
+ */
146
+ colorIndex?: number;
147
+ /** Draws a dashed line, e.g. for a planned or return leg. */
148
+ dashed?: boolean;
149
+ }
150
+ export interface MapRoutesProps {
151
+ /** Routes to draw. All of them go into one GeoJSON source, so thousands are fine. */
152
+ items: MapRouteItem[];
153
+ /** Ids of selected routes. While it is not empty, other routes fade to `--oui-map-dim-opacity`. */
154
+ selectedIds?: string[];
155
+ /** Called with the clicked route. */
156
+ onItemClick?: (item: MapRouteItem, event: MapLayerMouseEvent) => void;
157
+ /** Called with the route under the pointer, and with `null` when the pointer leaves it. */
158
+ onItemHover?: (item: MapRouteItem | null, event: MapLayerMouseEvent) => void;
159
+ }
160
+ /**
161
+ * Lines such as trips or tracks, drawn as one source with a casing, a solid and a dashed layer. Selected
162
+ * routes get a halo in the surface colour; hover and selection use feature state, so they never rebuild
163
+ * the data. Colours come from `color` or the chart palette.
164
+ *
165
+ * @example
166
+ * <Map.Routes
167
+ * items={trips.map((t) => ({ id: t.id, coordinates: t.path, dashed: t.planned }))}
168
+ * selectedIds={selected}
169
+ * onItemClick={(trip) => setSelected([trip.id])}
170
+ * />
171
+ */
172
+ declare function MapRoutes({ items, selectedIds, onItemClick, onItemHover }: MapRoutesProps): React.JSX.Element;
173
+ export interface MapAreaItem {
174
+ /** Unique id; reported back by `onItemClick` and used in `selectedIds`. */
175
+ id: string;
176
+ /** Outline of the area, in `[longitude, latitude]` coordinates. */
177
+ geometry: MapPolygon | MapMultiPolygon;
178
+ /** Fill and outline colour from the status tokens (`-subtle` fill, `-border` outline). */
179
+ tone?: MapTone;
180
+ }
181
+ export interface MapAreasProps {
182
+ /** Areas to draw, all in one GeoJSON source. */
183
+ items: MapAreaItem[];
184
+ /** Ids of selected areas. While it is not empty, other areas fade to `--oui-map-dim-opacity`. */
185
+ selectedIds?: string[];
186
+ /** Called with the clicked area. */
187
+ onItemClick?: (item: MapAreaItem, event: MapLayerMouseEvent) => void;
188
+ /** Called with the area under the pointer, and with `null` when the pointer leaves it. */
189
+ onItemHover?: (item: MapAreaItem | null, event: MapLayerMouseEvent) => void;
190
+ }
191
+ /**
192
+ * Polygons such as fields, zones or service areas, as one fill and one outline layer. Selected and
193
+ * hovered areas get a heavier outline.
194
+ *
195
+ * @example
196
+ * <Map.Areas items={fields.map((f) => ({ id: f.id, geometry: f.shape, tone: f.late ? 'warning' : 'success' }))} />
197
+ */
198
+ declare function MapAreas({ items, selectedIds, onItemClick, onItemHover }: MapAreasProps): React.JSX.Element;
199
+ export interface MapPointItem {
200
+ /** Unique id; reported back by `onItemClick` and used in `selectedIds`. */
201
+ id: string;
202
+ /** `[longitude, latitude]`. */
203
+ coordinates: MapPosition;
204
+ /** Any CSS colour, `var(--oui-…)` included. Wins over `tone` and `colorIndex`. */
205
+ color?: string;
206
+ /** Status colour. Wins over `colorIndex`. */
207
+ tone?: MapTone;
208
+ /**
209
+ * Series index for `seriesColor()`. Defaults to the item's index in `items`, so pass it (or `color`)
210
+ * when you filter `items`; otherwise colours shift and stop matching the legend.
211
+ */
212
+ colorIndex?: number;
213
+ }
214
+ export interface MapPointsProps {
215
+ /** Points to draw, all in one GeoJSON source and one circle layer. */
216
+ items: MapPointItem[];
217
+ /** Ids of selected points. While it is not empty, other points fade to `--oui-map-dim-opacity`. */
218
+ selectedIds?: string[];
219
+ /** Called with the clicked point. */
220
+ onItemClick?: (item: MapPointItem, event: MapLayerMouseEvent) => void;
221
+ /** Called with the point under the pointer, and with `null` when the pointer leaves it. */
222
+ onItemHover?: (item: MapPointItem | null, event: MapLayerMouseEvent) => void;
223
+ }
224
+ /**
225
+ * Many simple points (sensors, stops, events) as one circle layer. For a few dozen styled pins with HTML
226
+ * content, use `Map.Marker` instead.
227
+ *
228
+ * @example
229
+ * <Map.Points items={sensors.map((s) => ({ id: s.id, coordinates: s.position, tone: s.ok ? 'success' : 'danger' }))} />
230
+ */
231
+ declare function MapPoints({ items, selectedIds, onItemClick, onItemHover }: MapPointsProps): React.JSX.Element;
232
+ export interface MapMarkerProps {
233
+ /** `[longitude, latitude]` of the pin. */
234
+ position: MapPosition;
235
+ /** Pin shape: a numbered `stop`, a `depot`, a `vehicle` or a small `point`. */
236
+ kind?: MapMarkerKind;
237
+ /** Number shown in a `stop` pin. */
238
+ sequence?: number;
239
+ /** Any CSS colour for the pin. Wins over `tone`. */
240
+ color?: string;
241
+ /** Status colour for the pin. */
242
+ tone?: MapTone;
243
+ /** Accessible name and tooltip text. Depots default to the `depot` label. */
244
+ label?: string;
245
+ /** Marks the pin as selected (larger, with a ring). */
246
+ selected?: boolean;
247
+ /** Fades the pin to `--oui-map-dim-opacity`, e.g. for stops of trips that are not selected. */
248
+ dimmed?: boolean;
249
+ /** Called when the pin is clicked. */
250
+ onClick?: () => void;
251
+ /** Custom pin content, replacing the built-in shape. */
252
+ children?: React.ReactNode;
253
+ /** Classes for the pin element. */
254
+ className?: string;
255
+ }
256
+ /**
257
+ * An HTML pin at one position: numbered stops, depots, vehicles. Each marker is a DOM element, so use it
258
+ * for dozens of pins, not thousands (use `Map.Points` for those).
259
+ *
260
+ * @example
261
+ * <Map.Marker kind="stop" sequence={3} position={[19.05, 47.5]} color={seriesColor(0)} />
262
+ */
263
+ declare function MapMarker({ position, kind, sequence, color, tone, label, selected, dimmed, onClick, children, className, }: MapMarkerProps): React.JSX.Element;
264
+ export interface MapPopupProps extends Omit<PopupProps, 'longitude' | 'latitude'> {
265
+ /** `[longitude, latitude]` the popup points at. */
266
+ position: MapPosition;
267
+ }
268
+ /**
269
+ * A popup anchored to a position, styled as a library card. Render it while something is selected and
270
+ * close it from `onClose`.
271
+ *
272
+ * @example
273
+ * {picked && (
274
+ * <Map.Popup position={picked.position} onClose={() => setPicked(null)}>
275
+ * <strong>{picked.name}</strong>
276
+ * </Map.Popup>
277
+ * )}
278
+ */
279
+ declare function MapPopup({ position, className, offset, maxWidth, ...props }: MapPopupProps): React.JSX.Element;
280
+ /**
281
+ * Hover content: a popup without a close button that stays open on map clicks. Show it from
282
+ * `onItemHover` and hide it when the item is `null`.
283
+ *
284
+ * @example
285
+ * {hovered && <Map.Tooltip position={hovered.position}>{hovered.name}</Map.Tooltip>}
286
+ */
287
+ declare function MapTooltip({ position, className, offset, ...props }: MapPopupProps): React.JSX.Element;
288
+ export interface MapOverlayProps extends React.HTMLAttributes<HTMLDivElement> {
289
+ /** Ref to the overlay element. */
290
+ ref?: React.Ref<HTMLDivElement>;
291
+ /** Corner the overlay sits in. Overlays in the same corner as the zoom buttons cover them. */
292
+ position?: MapOverlayPosition;
293
+ }
294
+ /** Content floating over a corner of the map: stat cards, selects, a legend or `Map.BaseLayerSwitch`. */
295
+ declare function MapOverlay({ position, className, ref, ...props }: MapOverlayProps): React.JSX.Element;
296
+ export interface MapLegendItem {
297
+ /** Id matching the data items it stands for. */
298
+ id: string;
299
+ /** Text of the entry. */
300
+ label: React.ReactNode;
301
+ /** Swatch colour: any CSS colour, e.g. `seriesColor(i)`. */
302
+ color: string;
303
+ /** Whether the entry's data is shown. Defaults to `true`. */
304
+ visible?: boolean;
305
+ /** Number shown at the end of the entry. */
306
+ count?: React.ReactNode;
307
+ }
308
+ export interface MapLegendProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
309
+ /** Ref to the legend element. */
310
+ ref?: React.Ref<HTMLDivElement>;
311
+ /** Entries in order. */
312
+ items: MapLegendItem[];
313
+ /** Whether clicking an entry selects it: `single` keeps one, `multi` toggles each. */
314
+ selectionMode?: MapLegendSelectionMode;
315
+ /** Ids of the selected entries (controlled). */
316
+ selectedIds?: string[];
317
+ /** Called with the new selected ids. */
318
+ onSelectedIdsChange?: (ids: string[]) => void;
319
+ /** Called with the ids to show or hide. Adds an eye button to every entry. */
320
+ onVisibilityChange?: (ids: string[], visible: boolean) => void;
321
+ /** Adds a show all / hide all button when `onVisibilityChange` is set. */
322
+ showAllToggle?: boolean;
323
+ }
324
+ /**
325
+ * The key to a map's colours, with optional selection and show/hide per entry. Plain DOM, so it also
326
+ * works outside the map; inside `Map.Root` it takes the root's labels. Put it in a `Map.Overlay`.
327
+ *
328
+ * @example
329
+ * <Map.Legend
330
+ * items={trips.map((t, i) => ({ id: t.id, label: t.name, color: seriesColor(i), visible: !hidden.includes(t.id) }))}
331
+ * selectionMode="multi"
332
+ * selectedIds={selected}
333
+ * onSelectedIdsChange={setSelected}
334
+ * onVisibilityChange={(ids, visible) => setHidden((h) => (visible ? h.filter((id) => !ids.includes(id)) : [...h, ...ids]))}
335
+ * showAllToggle
336
+ * />
337
+ */
338
+ declare function MapLegend({ items, selectionMode, selectedIds, onSelectedIdsChange, onVisibilityChange, showAllToggle, className, ref, ...props }: MapLegendProps): React.JSX.Element;
339
+ export interface MapBaseLayerSwitchProps extends Omit<SegmentedControlProps, 'options' | 'value' | 'defaultValue' | 'onValueChange'> {
340
+ }
341
+ /** Segmented switch between the root's `baseLayers` (e.g. streets and satellite). Put it in a `Map.Overlay`. */
342
+ declare function MapBaseLayerSwitch({ size, ...props }: MapBaseLayerSwitchProps): React.JSX.Element;
343
+ /**
344
+ * Map components on MapLibre, from `@optimum-intelligence/opti-ui/map`. `Map.Root` holds the map; inside
345
+ * it, `Map.Areas`, `Map.Routes` and `Map.Points` draw arrays of items as single GeoJSON layers,
346
+ * `Map.Marker` adds HTML pins, `Map.Popup` and `Map.Tooltip` show content at a position, and
347
+ * `Map.Overlay` floats `Map.Legend`, `Map.BaseLayerSwitch` or cards over a corner.
348
+ *
349
+ * @example
350
+ * <Map.Root baseLayers={baseLayers} fit="selected">
351
+ * <Map.Areas items={zones} />
352
+ * <Map.Routes items={trips} selectedIds={selected} onItemClick={(t) => setSelected([t.id])} />
353
+ * <Map.Overlay position="top-left">
354
+ * <Map.Legend items={legend} selectionMode="single" selectedIds={selected} onSelectedIdsChange={setSelected} />
355
+ * </Map.Overlay>
356
+ * <Map.Overlay position="bottom-left">
357
+ * <Map.BaseLayerSwitch />
358
+ * </Map.Overlay>
359
+ * </Map.Root>
360
+ */
361
+ export declare const Map: {
362
+ Root: typeof MapRoot;
363
+ Routes: typeof MapRoutes;
364
+ Areas: typeof MapAreas;
365
+ Points: typeof MapPoints;
366
+ Marker: typeof MapMarker;
367
+ Popup: typeof MapPopup;
368
+ Tooltip: typeof MapTooltip;
369
+ Overlay: typeof MapOverlay;
370
+ Legend: typeof MapLegend;
371
+ BaseLayerSwitch: typeof MapBaseLayerSwitch;
372
+ };
373
+ export {};