@optimum-intelligence/opti-ui 0.6.2 → 0.7.1
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 +31 -0
- package/README.md +45 -1
- package/dist/components/map/Map.d.ts +374 -0
- package/dist/components/map/Map.js +2032 -0
- package/dist/components/map/Map.js.map +1 -0
- package/dist/components/map/colors.d.ts +16 -0
- package/dist/components/map/colors.js +99 -0
- package/dist/components/map/colors.js.map +1 -0
- package/dist/components/map/worker.d.ts +6 -0
- package/dist/components/map/worker.js +12 -0
- package/dist/components/map/worker.js.map +1 -0
- package/dist/components.json +481 -2
- package/dist/index.js +3 -2
- package/dist/llms-full.txt +278 -0
- package/dist/llms.txt +7 -0
- package/dist/map/index.d.ts +3 -0
- package/dist/map/index.js +4 -0
- package/dist/map.css +3 -0
- package/dist/opti-ui.css +427 -6
- package/dist/theme/index.d.ts +1 -0
- package/dist/theme/series.d.ts +9 -0
- package/dist/theme/series.js +17 -0
- package/dist/theme/series.js.map +1 -0
- package/dist/vendor/maplibre-gl/dist/maplibre-gl-worker.mjs_raw.js +7 -0
- package/dist/vendor/maplibre-gl/dist/maplibre-gl-worker.mjs_raw.js.map +1 -0
- package/package.json +9 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,37 @@
|
|
|
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.1 (2026-11-03)
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- `Map.Root` works in any app without bundler config: MapLibre's worker is now bundled into the library
|
|
11
|
+
and started from a `blob:` URL instead of a `?url` import that only Vite could resolve. A strict
|
|
12
|
+
Content-Security-Policy needs `worker-src blob:`.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- `maplibre-gl` is pinned to `6.16.0`, so the bundled worker always matches the installed MapLibre.
|
|
17
|
+
|
|
18
|
+
## 0.7.0 (2026-11-03)
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- Map components in a new `@optimum-intelligence/opti-ui/map` entry, on MapLibre (`maplibre-gl` and
|
|
23
|
+
`@vis.gl/react-maplibre` are installed with the package; import `@optimum-intelligence/opti-ui/map.css`
|
|
24
|
+
once): `Map.Root`, `Map.Routes`, `Map.Areas`,
|
|
25
|
+
`Map.Points`, `Map.Marker`, `Map.Popup`, `Map.Tooltip`, `Map.Overlay`, `Map.Legend`,
|
|
26
|
+
`Map.BaseLayerSwitch`, plus `rasterStyle()` and `useResolvedColors()`. `Map.Root expandable` adds a
|
|
27
|
+
button that expands the map over the window, and `Map.Marker dimmed` fades a pin with the selection.
|
|
28
|
+
- `seriesColor(index)` for the chart palette, and the `--oui-map-route-weight`, `--oui-map-casing-color`
|
|
29
|
+
and `--oui-map-dim-opacity` tokens.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- **Breaking:** the chart palette has eight tokens, `--oui-color-chart-1` to `-8`, taken from the colour
|
|
34
|
+
tones (blue, warning, green, pink, cyan, purple, yellow, accent) and following dark mode. The values of
|
|
35
|
+
`-1` to `-5` change.
|
|
36
|
+
|
|
6
37
|
## 0.6.2 (2026-10-27)
|
|
7
38
|
|
|
8
39
|
### Fixed
|
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
|
-
|
|
|
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,49 @@ 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` starts MapLibre's web worker itself from a bundled copy, so no bundler config is needed. The
|
|
211
|
+
worker runs from a `blob:` URL: with a strict Content-Security-Policy, allow `worker-src blob:`.
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
import '@optimum-intelligence/opti-ui/map.css';
|
|
215
|
+
import { seriesColor } from '@optimum-intelligence/opti-ui';
|
|
216
|
+
import { Map } from '@optimum-intelligence/opti-ui/map';
|
|
217
|
+
|
|
218
|
+
const baseLayers = [
|
|
219
|
+
{
|
|
220
|
+
id: 'streets',
|
|
221
|
+
label: 'Streets',
|
|
222
|
+
style: 'https://tiles.openfreemap.org/styles/bright',
|
|
223
|
+
darkStyle: 'https://tiles.openfreemap.org/styles/dark',
|
|
224
|
+
},
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
<Map.Root baseLayers={baseLayers} fit="selected">
|
|
228
|
+
<Map.Routes items={trips} selectedIds={selected} onItemClick={(trip) => setSelected([trip.id])} />
|
|
229
|
+
<Map.Marker kind="depot" position={[19.06, 47.49]} />
|
|
230
|
+
<Map.Overlay position="top-left">
|
|
231
|
+
<Map.Legend
|
|
232
|
+
items={trips.map((trip, i) => ({ id: trip.id, label: trip.name, color: seriesColor(i) }))}
|
|
233
|
+
selectionMode="single"
|
|
234
|
+
selectedIds={selected}
|
|
235
|
+
onSelectedIdsChange={setSelected}
|
|
236
|
+
/>
|
|
237
|
+
</Map.Overlay>
|
|
238
|
+
</Map.Root>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
The library hardcodes no tile provider: every base map comes from `baseLayers`, as a style URL, a style
|
|
242
|
+
object, or `rasterStyle(tilesUrl, attribution)` for a raster tile template. [OpenFreeMap](https://openfreemap.org)
|
|
243
|
+
styles work without an API key. Check the terms of any imagery provider before using it in production;
|
|
244
|
+
the satellite layer in the showcase is for the demo only. Data parts (`Map.Routes`, `Map.Areas`,
|
|
245
|
+
`Map.Points`) take arrays and draw each array as one layer set, so thousands of items are fine; their
|
|
246
|
+
colours follow the `--oui-*` tokens and dark mode.
|
|
247
|
+
|
|
204
248
|
## Customization
|
|
205
249
|
|
|
206
250
|
Start with the simplest option that does what you need:
|
|
@@ -0,0 +1,374 @@
|
|
|
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 bundled and runs from a blob URL, so a
|
|
120
|
+
* strict Content-Security-Policy needs `worker-src blob:`.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* <Map.Root
|
|
124
|
+
* baseLayers={[{ id: 'streets', label: 'Streets', style: 'https://tiles.openfreemap.org/styles/bright' }]}
|
|
125
|
+
* initialView={{ longitude: 19.04, latitude: 47.5, zoom: 10 }}
|
|
126
|
+
* >
|
|
127
|
+
* <Map.Routes
|
|
128
|
+
* items={[{ id: 'trip-1', coordinates: [[19.0, 47.48], [19.08, 47.52]] }]}
|
|
129
|
+
* selectedIds={selected}
|
|
130
|
+
* onItemClick={(trip) => setSelected([trip.id])}
|
|
131
|
+
* />
|
|
132
|
+
* <Map.Marker kind="depot" position={[19.0, 47.48]} />
|
|
133
|
+
* </Map.Root>
|
|
134
|
+
*/
|
|
135
|
+
declare function MapRoot({ ref, baseLayers, baseLayer, defaultBaseLayer, onBaseLayerChange, initialView, fit, labels, controlsPosition, expandable, className, style, children, onClick, onMouseMove, onMouseOut, ...props }: MapRootProps): React.JSX.Element;
|
|
136
|
+
export interface MapRouteItem {
|
|
137
|
+
/** Unique id; reported back by `onItemClick` and used in `selectedIds`. */
|
|
138
|
+
id: string;
|
|
139
|
+
/** Line vertices as `[longitude, latitude]`. */
|
|
140
|
+
coordinates: MapPosition[];
|
|
141
|
+
/** Any CSS colour, `var(--oui-…)` included. Wins over `colorIndex`. */
|
|
142
|
+
color?: string;
|
|
143
|
+
/**
|
|
144
|
+
* Series index for `seriesColor()`. Defaults to the item's index in `items`, so pass it (or `color`)
|
|
145
|
+
* when you filter `items`; otherwise colours shift and stop matching the legend.
|
|
146
|
+
*/
|
|
147
|
+
colorIndex?: number;
|
|
148
|
+
/** Draws a dashed line, e.g. for a planned or return leg. */
|
|
149
|
+
dashed?: boolean;
|
|
150
|
+
}
|
|
151
|
+
export interface MapRoutesProps {
|
|
152
|
+
/** Routes to draw. All of them go into one GeoJSON source, so thousands are fine. */
|
|
153
|
+
items: MapRouteItem[];
|
|
154
|
+
/** Ids of selected routes. While it is not empty, other routes fade to `--oui-map-dim-opacity`. */
|
|
155
|
+
selectedIds?: string[];
|
|
156
|
+
/** Called with the clicked route. */
|
|
157
|
+
onItemClick?: (item: MapRouteItem, event: MapLayerMouseEvent) => void;
|
|
158
|
+
/** Called with the route under the pointer, and with `null` when the pointer leaves it. */
|
|
159
|
+
onItemHover?: (item: MapRouteItem | null, event: MapLayerMouseEvent) => void;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Lines such as trips or tracks, drawn as one source with a casing, a solid and a dashed layer. Selected
|
|
163
|
+
* routes get a halo in the surface colour; hover and selection use feature state, so they never rebuild
|
|
164
|
+
* the data. Colours come from `color` or the chart palette.
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* <Map.Routes
|
|
168
|
+
* items={trips.map((t) => ({ id: t.id, coordinates: t.path, dashed: t.planned }))}
|
|
169
|
+
* selectedIds={selected}
|
|
170
|
+
* onItemClick={(trip) => setSelected([trip.id])}
|
|
171
|
+
* />
|
|
172
|
+
*/
|
|
173
|
+
declare function MapRoutes({ items, selectedIds, onItemClick, onItemHover }: MapRoutesProps): React.JSX.Element;
|
|
174
|
+
export interface MapAreaItem {
|
|
175
|
+
/** Unique id; reported back by `onItemClick` and used in `selectedIds`. */
|
|
176
|
+
id: string;
|
|
177
|
+
/** Outline of the area, in `[longitude, latitude]` coordinates. */
|
|
178
|
+
geometry: MapPolygon | MapMultiPolygon;
|
|
179
|
+
/** Fill and outline colour from the status tokens (`-subtle` fill, `-border` outline). */
|
|
180
|
+
tone?: MapTone;
|
|
181
|
+
}
|
|
182
|
+
export interface MapAreasProps {
|
|
183
|
+
/** Areas to draw, all in one GeoJSON source. */
|
|
184
|
+
items: MapAreaItem[];
|
|
185
|
+
/** Ids of selected areas. While it is not empty, other areas fade to `--oui-map-dim-opacity`. */
|
|
186
|
+
selectedIds?: string[];
|
|
187
|
+
/** Called with the clicked area. */
|
|
188
|
+
onItemClick?: (item: MapAreaItem, event: MapLayerMouseEvent) => void;
|
|
189
|
+
/** Called with the area under the pointer, and with `null` when the pointer leaves it. */
|
|
190
|
+
onItemHover?: (item: MapAreaItem | null, event: MapLayerMouseEvent) => void;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Polygons such as fields, zones or service areas, as one fill and one outline layer. Selected and
|
|
194
|
+
* hovered areas get a heavier outline.
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* <Map.Areas items={fields.map((f) => ({ id: f.id, geometry: f.shape, tone: f.late ? 'warning' : 'success' }))} />
|
|
198
|
+
*/
|
|
199
|
+
declare function MapAreas({ items, selectedIds, onItemClick, onItemHover }: MapAreasProps): React.JSX.Element;
|
|
200
|
+
export interface MapPointItem {
|
|
201
|
+
/** Unique id; reported back by `onItemClick` and used in `selectedIds`. */
|
|
202
|
+
id: string;
|
|
203
|
+
/** `[longitude, latitude]`. */
|
|
204
|
+
coordinates: MapPosition;
|
|
205
|
+
/** Any CSS colour, `var(--oui-…)` included. Wins over `tone` and `colorIndex`. */
|
|
206
|
+
color?: string;
|
|
207
|
+
/** Status colour. Wins over `colorIndex`. */
|
|
208
|
+
tone?: MapTone;
|
|
209
|
+
/**
|
|
210
|
+
* Series index for `seriesColor()`. Defaults to the item's index in `items`, so pass it (or `color`)
|
|
211
|
+
* when you filter `items`; otherwise colours shift and stop matching the legend.
|
|
212
|
+
*/
|
|
213
|
+
colorIndex?: number;
|
|
214
|
+
}
|
|
215
|
+
export interface MapPointsProps {
|
|
216
|
+
/** Points to draw, all in one GeoJSON source and one circle layer. */
|
|
217
|
+
items: MapPointItem[];
|
|
218
|
+
/** Ids of selected points. While it is not empty, other points fade to `--oui-map-dim-opacity`. */
|
|
219
|
+
selectedIds?: string[];
|
|
220
|
+
/** Called with the clicked point. */
|
|
221
|
+
onItemClick?: (item: MapPointItem, event: MapLayerMouseEvent) => void;
|
|
222
|
+
/** Called with the point under the pointer, and with `null` when the pointer leaves it. */
|
|
223
|
+
onItemHover?: (item: MapPointItem | null, event: MapLayerMouseEvent) => void;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Many simple points (sensors, stops, events) as one circle layer. For a few dozen styled pins with HTML
|
|
227
|
+
* content, use `Map.Marker` instead.
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* <Map.Points items={sensors.map((s) => ({ id: s.id, coordinates: s.position, tone: s.ok ? 'success' : 'danger' }))} />
|
|
231
|
+
*/
|
|
232
|
+
declare function MapPoints({ items, selectedIds, onItemClick, onItemHover }: MapPointsProps): React.JSX.Element;
|
|
233
|
+
export interface MapMarkerProps {
|
|
234
|
+
/** `[longitude, latitude]` of the pin. */
|
|
235
|
+
position: MapPosition;
|
|
236
|
+
/** Pin shape: a numbered `stop`, a `depot`, a `vehicle` or a small `point`. */
|
|
237
|
+
kind?: MapMarkerKind;
|
|
238
|
+
/** Number shown in a `stop` pin. */
|
|
239
|
+
sequence?: number;
|
|
240
|
+
/** Any CSS colour for the pin. Wins over `tone`. */
|
|
241
|
+
color?: string;
|
|
242
|
+
/** Status colour for the pin. */
|
|
243
|
+
tone?: MapTone;
|
|
244
|
+
/** Accessible name and tooltip text. Depots default to the `depot` label. */
|
|
245
|
+
label?: string;
|
|
246
|
+
/** Marks the pin as selected (larger, with a ring). */
|
|
247
|
+
selected?: boolean;
|
|
248
|
+
/** Fades the pin to `--oui-map-dim-opacity`, e.g. for stops of trips that are not selected. */
|
|
249
|
+
dimmed?: boolean;
|
|
250
|
+
/** Called when the pin is clicked. */
|
|
251
|
+
onClick?: () => void;
|
|
252
|
+
/** Custom pin content, replacing the built-in shape. */
|
|
253
|
+
children?: React.ReactNode;
|
|
254
|
+
/** Classes for the pin element. */
|
|
255
|
+
className?: string;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* An HTML pin at one position: numbered stops, depots, vehicles. Each marker is a DOM element, so use it
|
|
259
|
+
* for dozens of pins, not thousands (use `Map.Points` for those).
|
|
260
|
+
*
|
|
261
|
+
* @example
|
|
262
|
+
* <Map.Marker kind="stop" sequence={3} position={[19.05, 47.5]} color={seriesColor(0)} />
|
|
263
|
+
*/
|
|
264
|
+
declare function MapMarker({ position, kind, sequence, color, tone, label, selected, dimmed, onClick, children, className, }: MapMarkerProps): React.JSX.Element;
|
|
265
|
+
export interface MapPopupProps extends Omit<PopupProps, 'longitude' | 'latitude'> {
|
|
266
|
+
/** `[longitude, latitude]` the popup points at. */
|
|
267
|
+
position: MapPosition;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* A popup anchored to a position, styled as a library card. Render it while something is selected and
|
|
271
|
+
* close it from `onClose`.
|
|
272
|
+
*
|
|
273
|
+
* @example
|
|
274
|
+
* {picked && (
|
|
275
|
+
* <Map.Popup position={picked.position} onClose={() => setPicked(null)}>
|
|
276
|
+
* <strong>{picked.name}</strong>
|
|
277
|
+
* </Map.Popup>
|
|
278
|
+
* )}
|
|
279
|
+
*/
|
|
280
|
+
declare function MapPopup({ position, className, offset, maxWidth, ...props }: MapPopupProps): React.JSX.Element;
|
|
281
|
+
/**
|
|
282
|
+
* Hover content: a popup without a close button that stays open on map clicks. Show it from
|
|
283
|
+
* `onItemHover` and hide it when the item is `null`.
|
|
284
|
+
*
|
|
285
|
+
* @example
|
|
286
|
+
* {hovered && <Map.Tooltip position={hovered.position}>{hovered.name}</Map.Tooltip>}
|
|
287
|
+
*/
|
|
288
|
+
declare function MapTooltip({ position, className, offset, ...props }: MapPopupProps): React.JSX.Element;
|
|
289
|
+
export interface MapOverlayProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
290
|
+
/** Ref to the overlay element. */
|
|
291
|
+
ref?: React.Ref<HTMLDivElement>;
|
|
292
|
+
/** Corner the overlay sits in. Overlays in the same corner as the zoom buttons cover them. */
|
|
293
|
+
position?: MapOverlayPosition;
|
|
294
|
+
}
|
|
295
|
+
/** Content floating over a corner of the map: stat cards, selects, a legend or `Map.BaseLayerSwitch`. */
|
|
296
|
+
declare function MapOverlay({ position, className, ref, ...props }: MapOverlayProps): React.JSX.Element;
|
|
297
|
+
export interface MapLegendItem {
|
|
298
|
+
/** Id matching the data items it stands for. */
|
|
299
|
+
id: string;
|
|
300
|
+
/** Text of the entry. */
|
|
301
|
+
label: React.ReactNode;
|
|
302
|
+
/** Swatch colour: any CSS colour, e.g. `seriesColor(i)`. */
|
|
303
|
+
color: string;
|
|
304
|
+
/** Whether the entry's data is shown. Defaults to `true`. */
|
|
305
|
+
visible?: boolean;
|
|
306
|
+
/** Number shown at the end of the entry. */
|
|
307
|
+
count?: React.ReactNode;
|
|
308
|
+
}
|
|
309
|
+
export interface MapLegendProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
|
|
310
|
+
/** Ref to the legend element. */
|
|
311
|
+
ref?: React.Ref<HTMLDivElement>;
|
|
312
|
+
/** Entries in order. */
|
|
313
|
+
items: MapLegendItem[];
|
|
314
|
+
/** Whether clicking an entry selects it: `single` keeps one, `multi` toggles each. */
|
|
315
|
+
selectionMode?: MapLegendSelectionMode;
|
|
316
|
+
/** Ids of the selected entries (controlled). */
|
|
317
|
+
selectedIds?: string[];
|
|
318
|
+
/** Called with the new selected ids. */
|
|
319
|
+
onSelectedIdsChange?: (ids: string[]) => void;
|
|
320
|
+
/** Called with the ids to show or hide. Adds an eye button to every entry. */
|
|
321
|
+
onVisibilityChange?: (ids: string[], visible: boolean) => void;
|
|
322
|
+
/** Adds a show all / hide all button when `onVisibilityChange` is set. */
|
|
323
|
+
showAllToggle?: boolean;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* The key to a map's colours, with optional selection and show/hide per entry. Plain DOM, so it also
|
|
327
|
+
* works outside the map; inside `Map.Root` it takes the root's labels. Put it in a `Map.Overlay`.
|
|
328
|
+
*
|
|
329
|
+
* @example
|
|
330
|
+
* <Map.Legend
|
|
331
|
+
* items={trips.map((t, i) => ({ id: t.id, label: t.name, color: seriesColor(i), visible: !hidden.includes(t.id) }))}
|
|
332
|
+
* selectionMode="multi"
|
|
333
|
+
* selectedIds={selected}
|
|
334
|
+
* onSelectedIdsChange={setSelected}
|
|
335
|
+
* onVisibilityChange={(ids, visible) => setHidden((h) => (visible ? h.filter((id) => !ids.includes(id)) : [...h, ...ids]))}
|
|
336
|
+
* showAllToggle
|
|
337
|
+
* />
|
|
338
|
+
*/
|
|
339
|
+
declare function MapLegend({ items, selectionMode, selectedIds, onSelectedIdsChange, onVisibilityChange, showAllToggle, className, ref, ...props }: MapLegendProps): React.JSX.Element;
|
|
340
|
+
export interface MapBaseLayerSwitchProps extends Omit<SegmentedControlProps, 'options' | 'value' | 'defaultValue' | 'onValueChange'> {
|
|
341
|
+
}
|
|
342
|
+
/** Segmented switch between the root's `baseLayers` (e.g. streets and satellite). Put it in a `Map.Overlay`. */
|
|
343
|
+
declare function MapBaseLayerSwitch({ size, ...props }: MapBaseLayerSwitchProps): React.JSX.Element;
|
|
344
|
+
/**
|
|
345
|
+
* Map components on MapLibre, from `@optimum-intelligence/opti-ui/map`. `Map.Root` holds the map; inside
|
|
346
|
+
* it, `Map.Areas`, `Map.Routes` and `Map.Points` draw arrays of items as single GeoJSON layers,
|
|
347
|
+
* `Map.Marker` adds HTML pins, `Map.Popup` and `Map.Tooltip` show content at a position, and
|
|
348
|
+
* `Map.Overlay` floats `Map.Legend`, `Map.BaseLayerSwitch` or cards over a corner.
|
|
349
|
+
*
|
|
350
|
+
* @example
|
|
351
|
+
* <Map.Root baseLayers={baseLayers} fit="selected">
|
|
352
|
+
* <Map.Areas items={zones} />
|
|
353
|
+
* <Map.Routes items={trips} selectedIds={selected} onItemClick={(t) => setSelected([t.id])} />
|
|
354
|
+
* <Map.Overlay position="top-left">
|
|
355
|
+
* <Map.Legend items={legend} selectionMode="single" selectedIds={selected} onSelectedIdsChange={setSelected} />
|
|
356
|
+
* </Map.Overlay>
|
|
357
|
+
* <Map.Overlay position="bottom-left">
|
|
358
|
+
* <Map.BaseLayerSwitch />
|
|
359
|
+
* </Map.Overlay>
|
|
360
|
+
* </Map.Root>
|
|
361
|
+
*/
|
|
362
|
+
export declare const Map: {
|
|
363
|
+
Root: typeof MapRoot;
|
|
364
|
+
Routes: typeof MapRoutes;
|
|
365
|
+
Areas: typeof MapAreas;
|
|
366
|
+
Points: typeof MapPoints;
|
|
367
|
+
Marker: typeof MapMarker;
|
|
368
|
+
Popup: typeof MapPopup;
|
|
369
|
+
Tooltip: typeof MapTooltip;
|
|
370
|
+
Overlay: typeof MapOverlay;
|
|
371
|
+
Legend: typeof MapLegend;
|
|
372
|
+
BaseLayerSwitch: typeof MapBaseLayerSwitch;
|
|
373
|
+
};
|
|
374
|
+
export {};
|