@tracktor/map 1.6.4 β†’ 1.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/README.md CHANGED
@@ -1,114 +1,759 @@
1
- # πŸ“ MarkerMap - React Map Library
1
+ # πŸ—ΊοΈ @tracktor/map
2
2
 
3
- **MarkerMap** is a React library built on top of Mapbox GL JS. It simplifies rendering interactive maps with customizable markers in your React applications.
3
+ A modern, lightweight React map library built on top of Mapbox GL JS and react-map-gl. Designed for simplicity, flexibility, and visual elegance.
4
4
 
5
- ## πŸš€ Installation
5
+ Easily combine markers, routes, GeoJSON features, isochrones, and nearest-point calculations β€” all with a declarative, type-safe API.
6
+
7
+ [![npm version](https://img.shields.io/npm/v/@tracktor/map.svg)](https://www.npmjs.com/package/@tracktor/map)
8
+ [![License](https://img.shields.io/badge/license-UNLICENSED-red.svg)](LICENSE)
9
+
10
+ ---
6
11
 
12
+ ## πŸš€ Installation
7
13
  ```bash
8
14
  npm install @tracktor/map
9
15
  ```
10
16
 
11
17
  or
12
-
13
18
  ```bash
14
19
  yarn add @tracktor/map
15
20
  ```
16
21
 
17
- ## πŸ“¦ Dependencies
22
+ or
23
+ ```bash
24
+ bun add @tracktor/map
25
+ ```
26
+
27
+ ---
28
+
29
+ ## βš™οΈ Requirements
30
+
31
+ | Dependency | Version | Purpose |
32
+ |------------|---------|---------|
33
+ | `react` | 17+ / 18+ / 19+ | Core React runtime |
34
+ | `react-dom` | 17+ / 18+ / 19+ | React DOM rendering |
35
+ | `mapbox-gl` | β‰₯3.0.0 | Map rendering engine |
36
+ | `@tracktor/design-system` | β‰₯4.0.0 | UI theming and components |
37
+ | `@mui/icons-material` | * | Material UI icons |
38
+ | `@mui/x-license` | * | MUI X license integration |
18
39
 
19
- This library depends on:
40
+ πŸͺΆ **You'll also need a Mapbox access token** to render maps. Get one at [mapbox.com](https://account.mapbox.com/access-tokens/).
20
41
 
21
- - `@tracktor/design-system` for theming and styling
22
- - `mapbox-gl` for map rendering
23
- - React 19+
42
+ ---
24
43
 
25
- ## πŸ”§ Usage
44
+ ## ✨ Features
26
45
 
46
+ βœ… **Declarative API** β€” manage complex map interactions with simple props
47
+ βœ… **Markers & Popups** β€” customizable React components or image-based icons
48
+ βœ… **Routing & Isochrones** β€” visualize travel-time areas or compute optimal routes
49
+ βœ… **GeoJSON Layers** β€” render vector data dynamically
50
+ βœ… **Nearest Marker Search** β€” find and highlight closest points instantly
51
+ βœ… **Type-safe API** β€” full TypeScript support with smart IntelliSense
52
+ βœ… **Responsive Design** β€” automatically adapts to any container or screen size
53
+ βœ… **Built for performance** β€” minimal re-renders, efficient map updates
54
+ βœ… **Multiple Routing Engines** β€” supports both OSRM (free) and Mapbox Directions API
55
+ βœ… **Flexible Map Styles** β€” works with Mapbox styles, OpenStreetMap, or custom raster tiles
56
+
57
+ ---
58
+
59
+ ## 🧩 Quick Start
27
60
  ```tsx
28
61
  import { MapProvider, MarkerMap } from "@tracktor/map";
29
62
 
30
63
  const markers = [
31
- {
64
+ {
65
+ id: 1,
66
+ lng: 2.3522,
67
+ lat: 48.8566,
68
+ Tooltip: <div>Paris</div>,
69
+ color: "primary",
70
+ variant: "default",
71
+ },
72
+ {
73
+ id: 2,
74
+ lng: -0.1276,
75
+ lat: 51.5074,
76
+ Tooltip: <div>London</div>,
77
+ color: "secondary",
78
+ variant: "default",
79
+ },
80
+ ];
81
+
82
+ function App() {
83
+ return (
84
+ <MapProvider
85
+ licenseMuiX="your-muix-license"
86
+ licenceMapbox="your-mapbox-token"
87
+ >
88
+ <MarkerMap
89
+ markers={markers}
90
+ center={[2.3522, 48.8566]}
91
+ zoom={5}
92
+ fitBounds
93
+ height={500}
94
+ width="100%"
95
+ onMapClick={(lng, lat, marker) => {
96
+ console.log("Clicked at:", lng, lat);
97
+ if (marker) console.log("Marker clicked:", marker);
98
+ }}
99
+ />
100
+ </MapProvider>
101
+ );
102
+ }
103
+ ```
104
+
105
+ ---
106
+
107
+ ## 🧭 Components Overview
108
+
109
+ ### `MapProvider`
110
+
111
+ Wraps your map components and injects required providers (theme, tokens, MUI X license).
112
+
113
+ **Required Props:**
114
+ - `licenseMuiX` β€” Your MUI X license key
115
+ - `licenceMapbox` β€” Your Mapbox access token
116
+ ```tsx
117
+ <MapProvider
118
+ licenseMuiX="your-license"
119
+ licenceMapbox="your-token"
120
+ >
121
+ {/* Your map components */}
122
+ </MapProvider>
123
+ ```
124
+
125
+ ### `MapView` / `MarkerMap`
126
+
127
+ Main map component that handles:
128
+ - Marker rendering with custom icons or React components
129
+ - Interactive popups with hover/click modes
130
+ - Automatic bounds fitting
131
+ - Map click events
132
+ - Responsive container sizing
133
+
134
+ ### Specialized Components
135
+
136
+ - **`RouteMap`** β†’ Draw routes between two points using OSRM or Mapbox
137
+ - **`IsochroneMap`** β†’ Compute and display travel-time polygons
138
+ - **`FeatureMap`** β†’ Display custom GeoJSON layers with styling
139
+
140
+ ---
141
+
142
+ ## 🧱 Props Reference
143
+
144
+ ### `MapView` Props
145
+
146
+ #### Core Map Props
147
+
148
+ | Prop | Type | Default | Description |
149
+ |------|------|---------|-------------|
150
+ | `center` | `LngLatLike \| number[]` | `[2.3522, 48.8566]` | Initial map center coordinates [lng, lat] |
151
+ | `zoom` | `number` | `5` | Initial zoom level (0-22) |
152
+ | `width` | `string \| number` | `"100%"` | Map container width |
153
+ | `height` | `string \| number` | `300` | Map container height |
154
+ | `loading` | `boolean` | `false` | Show skeleton loader |
155
+ | `square` | `boolean` | `false` | Enforce 1:1 aspect ratio |
156
+ | `containerStyle` | `SxProps` | `undefined` | Custom MUI sx styles |
157
+
158
+ #### Map Appearance
159
+
160
+ | Prop | Type | Default | Description |
161
+ |------|------|---------|-------------|
162
+ | `theme` | `"light" \| "dark"` | `"light"` | Map color theme |
163
+ | `baseMapView` | `"street" \| "satellite"` | `"street"` | Base map layer type |
164
+ | `mapStyle` | `string` | - | Custom Mapbox style URL |
165
+ | `projection` | `ProjectionSpecification` | `"mercator"` | Map projection system |
166
+
167
+ #### Interaction Props
168
+
169
+ | Prop | Type | Default | Description |
170
+ |------|------|---------|-------------|
171
+ | `cooperativeGestures` | `boolean` | `true` | Require modifier key for zoom/pan |
172
+ | `doubleClickZoom` | `boolean` | `true` | Enable double-click to zoom |
173
+ | `onMapClick` | `(lng, lat, marker?) => void` | - | Callback for map clicks (includes clicked marker if applicable) |
174
+
175
+ #### Marker Props
176
+
177
+ | Prop | Type | Default | Description |
178
+ |------|------|---------|-------------|
179
+ | `markers` | `MarkerProps[]` | `[]` | Array of markers to display |
180
+ | `markerImageURL` | `string` | - | Custom marker icon URL |
181
+ | `openPopup` | `string \| number` | `undefined` | ID of marker with open popup |
182
+ | `openPopupOnHover` | `boolean` | `false` | Open popups on hover instead of click |
183
+ | `popupMaxWidth` | `string` | `"300px"` | Maximum popup width |
184
+
185
+ #### Bounds & Animation
186
+
187
+ | Prop | Type | Default | Description |
188
+ |------|------|---------|-------------|
189
+ | `fitBounds` | `boolean` | `true` | Auto-fit map to show all markers |
190
+ | `fitBoundsPadding` | `number` | `0` | Padding (px) around fitted bounds |
191
+ | `fitBoundDuration` | `number` | `500` | Animation duration (ms) |
192
+ | `disableAnimation` | `boolean` | `false` | Disable all animations |
193
+ | `fitBoundsAnimationKey` | `unknown` | - | Change to re-trigger fit bounds |
194
+
195
+ ---
196
+
197
+ ### Marker Props (`MarkerProps`)
198
+
199
+ | Prop | Type | Required | Description |
200
+ |------|------|----------|-------------|
201
+ | `id` | `string \| number` | βœ… | Unique marker identifier |
202
+ | `lng` | `number` | βœ… | Longitude coordinate |
203
+ | `lat` | `number` | βœ… | Latitude coordinate |
204
+ | `Tooltip` | `ReactNode` | - | Content for popup/tooltip |
205
+ | `IconComponent` | `React.ComponentType` | - | Custom React icon component |
206
+ | `iconProps` | `object` | - | Props passed to IconComponent |
207
+ | `color` | `string` | - | Marker color (MUI palette) |
208
+ | `variant` | `string` | - | Marker style variant |
209
+
210
+ **Example with custom icon:**
211
+ ```tsx
212
+ import LocationOnIcon from '@mui/icons-material/LocationOn';
213
+
214
+ const marker = {
215
+ id: 1,
216
+ lng: 2.3522,
217
+ lat: 48.8566,
218
+ IconComponent: LocationOnIcon,
219
+ iconProps: { fontSize: 'large', color: 'error' },
220
+ Tooltip: <div>Custom Icon Marker</div>
221
+ };
222
+ ```
223
+
224
+ ---
225
+
226
+ ### Itinerary Props (`itineraryParams`)
227
+
228
+ Draw a route between two points with customizable styling and routing engines.
229
+
230
+ | Prop | Type | Default | Description |
231
+ |------|------|---------|-------------|
232
+ | `from` | `[number, number]` | - | Route starting point [lng, lat] |
233
+ | `to` | `[number, number]` | - | Route ending point [lng, lat] |
234
+ | `profile` | `"driving" \| "walking" \| "cycling"` | `"driving"` | Transportation mode |
235
+ | `engine` | `"OSRM" \| "Mapbox"` | `"OSRM"` | Routing service to use |
236
+ | `itineraryLineStyle` | `Partial<ItineraryLineStyle>` | `{ color: "#3b82f6", width: 4, opacity: 0.8 }` | Route line appearance |
237
+ | `initialRoute` | `Feature<LineString>` | - | Precomputed GeoJSON route |
238
+ | `onRouteComputed` | `(route) => void` | - | Callback fired when route is computed |
239
+ | `itineraryLabel` | `ReactNode` | - | Label displayed along the route (e.g., "12 min") |
240
+
241
+ **Example:**
242
+ ```tsx
243
+ <MapView
244
+ itineraryParams={{
245
+ from: [2.3522, 48.8566], // Paris
246
+ to: [-0.1276, 51.5074], // London
247
+ profile: "driving",
248
+ engine: "OSRM",
249
+ itineraryLineStyle: {
250
+ color: "#10b981",
251
+ width: 5,
252
+ opacity: 0.9
253
+ },
254
+ itineraryLabel: <span>Route principale</span>,
255
+ onRouteComputed: (route) => {
256
+ console.log("Route computed:", route);
257
+ }
258
+ }}
259
+ />
260
+ ```
261
+
262
+ ---
263
+
264
+ ### Nearest Marker Search (`findNearestMarker`)
265
+
266
+ Find and highlight the closest marker to a given point within a maximum distance.
267
+
268
+ | Prop | Type | Default | Description |
269
+ |------|------|---------|-------------|
270
+ | `origin` | `[number, number]` | - | Starting point for search [lng, lat] |
271
+ | `destinations` | `Array<{id, lng, lat}>` | - | Candidate destinations |
272
+ | `maxDistanceMeters` | `number` | - | Maximum search radius in meters |
273
+ | `profile` | `"driving" \| "walking" \| "cycling"` | `"driving"` | Routing profile for distance calculation |
274
+ | `engine` | `"OSRM" \| "Mapbox"` | `"OSRM"` | Routing engine to use |
275
+ | `onNearestFound` | `(results) => void` | - | Callback with all nearest results |
276
+ | `initialNearestResults` | `NearestResult[]` | - | Precomputed nearest results |
277
+ | `itineraryLineStyle` | `Partial<ItineraryLineStyle>` | - | Style override for auto-generated itinerary |
278
+
279
+ **NearestResult Type:**
280
+ ```tsx
281
+ interface NearestResult {
282
+ id: number | string;
283
+ point: [number, number]; // [lng, lat]
284
+ distance: number; // in meters
285
+ routeFeature?: Feature<LineString> | null;
286
+ }
287
+ ```
288
+
289
+ **Example:**
290
+ ```tsx
291
+ <MapView
292
+ findNearestMarker={{
293
+ origin: [2.3522, 48.8566],
294
+ destinations: markers.map(m => ({
295
+ id: m.id,
296
+ lng: m.lng,
297
+ lat: m.lat
298
+ })),
299
+ maxDistanceMeters: 5000,
300
+ profile: "walking",
301
+ engine: "OSRM",
302
+ onNearestFound: (results) => {
303
+ console.log(`Found ${results.length} markers within range`);
304
+ results.forEach(r => {
305
+ console.log(`Marker ${r.id} at ${r.distance}m`);
306
+ });
307
+ }
308
+ }}
309
+ />
310
+ ```
311
+
312
+ ---
313
+
314
+ ### Isochrone Props (`isochrone`)
315
+
316
+ Compute and display areas reachable within specific time intervals.
317
+
318
+ | Prop | Type | Default | Description |
319
+ |------|------|---------|-------------|
320
+ | `origin` | `[number, number]` | - | Center point for isochrone [lng, lat] |
321
+ | `profile` | `"driving" \| "walking" \| "cycling"` | `"driving"` | Transportation mode |
322
+ | `intervals` | `number[]` | `[5, 10, 15]` | Time intervals in minutes |
323
+ | `onIsochroneLoaded` | `(data) => void` | - | Callback with GeoJSON result |
324
+
325
+ **Example:**
326
+ ```tsx
327
+ <MapView
328
+ isochrone={{
329
+ origin: [2.3522, 48.8566],
330
+ profile: "driving",
331
+ intervals: [5, 10, 15, 20], // 5, 10, 15, 20 minutes
332
+ onIsochroneLoaded: (geojson) => {
333
+ console.log("Isochrone data:", geojson);
334
+ }
335
+ }}
336
+ />
337
+ ```
338
+
339
+ ---
340
+
341
+ ### GeoJSON Features (`features`)
342
+
343
+ Display custom vector features like polygons, lines, or points.
344
+
345
+ | Prop | Type | Description |
346
+ |------|------|-------------|
347
+ | `features` | `Feature \| Feature[] \| FeatureCollection` | GeoJSON data to render |
348
+
349
+ **Example:**
350
+ ```tsx
351
+ <MapView
352
+ features={{
353
+ type: "Feature",
354
+ geometry: {
355
+ type: "LineString",
356
+ coordinates: [
357
+ [2.3, 48.8],
358
+ [2.4, 48.9],
359
+ [2.5, 48.85]
360
+ ]
361
+ },
362
+ properties: {
363
+ color: "#ef4444"
364
+ }
365
+ }}
366
+ />
367
+ ```
368
+
369
+ ---
370
+
371
+ ## 🧠 Advanced Use Cases
372
+
373
+ ### 🧭 Real-time GPS Tracking
374
+ ```tsx
375
+ function LiveTracking() {
376
+ const [position, setPosition] = useState([2.3522, 48.8566]);
377
+
378
+ useEffect(() => {
379
+ const watchId = navigator.geolocation.watchPosition((pos) => {
380
+ setPosition([pos.coords.longitude, pos.coords.latitude]);
381
+ });
382
+ return () => navigator.geolocation.clearWatch(watchId);
383
+ }, []);
384
+
385
+ return (
386
+ <MapView
387
+ markers={[{
388
+ id: 'current',
389
+ lng: position[0],
390
+ lat: position[1],
391
+ Tooltip: <div>You are here</div>
392
+ }]}
393
+ center={position}
394
+ zoom={15}
395
+ fitBounds={false}
396
+ />
397
+ );
398
+ }
399
+ ```
400
+
401
+ ### πŸ”„ Dynamic Data with React Query
402
+ ```tsx
403
+ import { useQuery } from '@tanstack/react-query';
404
+
405
+ function DynamicMarkers() {
406
+ const { data: markers } = useQuery({
407
+ queryKey: ['locations'],
408
+ queryFn: fetchLocations,
409
+ refetchInterval: 5000 // Refresh every 5s
410
+ });
411
+
412
+ return (
413
+ <MapView
414
+ markers={markers}
415
+ fitBounds
416
+ fitBoundsAnimationKey={markers?.length}
417
+ />
418
+ );
419
+ }
420
+ ```
421
+
422
+ ### 🎨 Custom Marker Components
423
+ ```tsx
424
+ function CustomMarker({ isActive, count }) {
425
+ return (
426
+ <Box
427
+ sx={{
428
+ width: 40,
429
+ height: 40,
430
+ borderRadius: '50%',
431
+ bgcolor: isActive ? 'success.main' : 'grey.500',
432
+ display: 'flex',
433
+ alignItems: 'center',
434
+ justifyContent: 'center',
435
+ color: 'white',
436
+ fontWeight: 'bold',
437
+ border: '2px solid white',
438
+ boxShadow: 2
439
+ }}
440
+ >
441
+ {count}
442
+ </Box>
443
+ );
444
+ }
445
+
446
+ <MapView
447
+ markers={[{
32
448
  id: 1,
33
449
  lng: 2.3522,
34
450
  lat: 48.8566,
35
- Tooltip: <div>Paris</div>,
36
- iconImage: "marker-icon.png",
37
- size: 40,
38
- onClick: () => alert("Marker clicked!"),
39
- },
40
- ];
451
+ IconComponent: CustomMarker,
452
+ iconProps: { isActive: true, count: 5 }
453
+ }]}
454
+ />
455
+ ```
456
+
457
+ ### πŸ—ΊοΈ Multi-Route Comparison
458
+ ```tsx
459
+ <MapView
460
+ features={[
461
+ {
462
+ type: "Feature",
463
+ geometry: {
464
+ type: "LineString",
465
+ coordinates: route1
466
+ },
467
+ properties: { color: "#3b82f6" }
468
+ },
469
+ {
470
+ type: "Feature",
471
+ geometry: {
472
+ type: "LineString",
473
+ coordinates: route2
474
+ },
475
+ properties: { color: "#ef4444" }
476
+ }
477
+ ]}
478
+ />
479
+ ```
480
+
481
+ ### 🎯 Click-to-Add Markers
482
+ ```tsx
483
+ function InteractiveMap() {
484
+ const [markers, setMarkers] = useState([]);
41
485
 
42
- <MapProvider licenseMuiX="your-muix-licence" licenceMapbox="your-mapbox-licence">
43
- <MarkerMap
486
+ const handleMapClick = (lng, lat) => {
487
+ setMarkers(prev => [...prev, {
488
+ id: Date.now(),
489
+ lng,
490
+ lat,
491
+ Tooltip: <div>Point {markers.length + 1}</div>
492
+ }]);
493
+ };
494
+
495
+ return (
496
+ <MapView
44
497
  markers={markers}
45
- center={[2.3522, 48.8566]}
46
- zoom={10}
47
- fitBounds
48
- height={400}
49
- width="100%"
498
+ onMapClick={handleMapClick}
50
499
  />
51
- </MapProvider>
500
+ );
501
+ }
52
502
  ```
53
503
 
54
- ## 🧩 Props
55
-
56
- ### MarkerMap Props
57
-
58
- | Prop | Type | Description |
59
- |--------------------|--------------------------------------|--------------------------------------------------------|
60
- | `markers` | `MarkerProps[]` | List of markers to display |
61
- | `center` | `LngLatLike` or `[number, number]` | Initial map center coordinates |
62
- | `zoom` | `number` | Initial zoom level |
63
- | `fitBounds` | `boolean` | Automatically fit the map to the bounds of all markers |
64
- | `fitBoundsPadding` | `number` | Padding around markers when fitting bounds |
65
- | `zoomFlyFrom` | `number` | Zoom level to fly in from |
66
- | `popupMaxWidth` | `string` | Max width for popups |
67
- | `width` | `number` or `string` | Map width |
68
- | `height` | `number` or `string` | Map height |
69
- | `loading` | `boolean` | Whether to show a loading state |
70
- | `markerImageURL` | `string` | Default marker image URL |
71
- | `containerStyle` | `SxProps` | Custom styles for the map container |
72
- | `disableFlyTo` | `boolean` | Disable flyTo animation on marker click |
73
- | `flyToDuration` | `number` | Duration of the flyTo animation |
74
- | `fitBoundDuration` | `number` | Duration of the fitBounds animation |
75
- | `square` | `boolean` | Forces the map to be square-shaped |
76
- | `openPopup` | `number` or `string` | ID of the marker with an open popup |
77
- | `openPopupOnHover` | `boolean` | Automatically open popups on marker hover |
78
- | `onMapClick` | `(lng: number, lat: number) => void` | Callback triggered when clicking on the map |
79
-
80
- ### MarkerProps
81
-
82
- | Prop | Type | Description |
83
- |-------------|----------------------|---------------------------------------------------------------|
84
- | `id` | `number` or `string` | Unique marker identifier |
85
- | `lng` | `number` | Longitude |
86
- | `lat` | `number` | Latitude |
87
- | `Tooltip` | `ReactNode` | Tooltip content displayed in a popup |
88
- | `iconImage` | `string` | Image URL used as marker icon |
89
- | `size` | `number` | Marker size in pixels |
90
- | `zIndex` | `number` | Z-index to control stacking order |
91
- | `onClick` | `() => void` | Function triggered on marker click |
92
- | `type` | `string` | Custom marker type (optional, for filtering or styling) |
93
- | `name` | `string` | Name of the marker |
94
- | `Icon` | `ReactNode` | Custom React component to render instead of default image |
95
-
96
-
97
- ## πŸ§‘β€πŸ’» Contributing
98
-
99
- Contributions are welcome! Please follow the coding conventions and include tests when necessary.
504
+ ### πŸš— Combined Routing & Nearest Search
505
+ ```tsx
506
+ function DeliveryMap() {
507
+ const [origin] = useState([2.3522, 48.8566]);
508
+ const [destinations] = useState([
509
+ { id: 1, lng: 2.35, lat: 48.86 },
510
+ { id: 2, lng: 2.36, lat: 48.85 },
511
+ { id: 3, lng: 2.34, lat: 48.87 }
512
+ ]);
100
513
 
101
- ## πŸ“„ License
514
+ return (
515
+ <MapView
516
+ markers={destinations.map(d => ({
517
+ id: d.id,
518
+ lng: d.lng,
519
+ lat: d.lat,
520
+ Tooltip: <div>Destination {d.id}</div>
521
+ }))}
522
+ findNearestMarker={{
523
+ origin,
524
+ destinations,
525
+ maxDistanceMeters: 10000,
526
+ profile: "driving",
527
+ engine: "OSRM",
528
+ itineraryLineStyle: {
529
+ color: "#22c55e",
530
+ width: 4,
531
+ opacity: 0.8
532
+ },
533
+ onNearestFound: (results) => {
534
+ console.log("Nearest destinations:", results);
535
+ }
536
+ }}
537
+ />
538
+ );
539
+ }
540
+ ```
541
+
542
+ ---
543
+
544
+ ## πŸ’‘ Tips & Best Practices
545
+
546
+ ### Performance Optimization
547
+
548
+ - **Memoize marker data** to prevent unnecessary re-renders
549
+ - **Use `fitBoundsAnimationKey`** to control when bounds recalculate
550
+ - **Disable animations** for large datasets: `disableAnimation={true}`
551
+ - **Debounce dynamic updates** when tracking real-time data
552
+ - **Use `initialRoute` and `initialNearestResults`** to avoid redundant API calls
553
+
554
+ ### UX Improvements
555
+
556
+ - Combine `openPopupOnHover` and `disableAnimation` for smooth interactions
557
+ - Use `fitBoundsPadding` to ensure markers aren't at screen edges
558
+ - Set appropriate `popupMaxWidth` for mobile responsiveness
559
+ - Provide visual feedback with custom `IconComponent` states
560
+ - Use `itineraryLabel` to display route duration or distance
561
+
562
+ ### Routing Best Practices
563
+
564
+ - **Use OSRM** (free) for basic routing needs
565
+ - **Use Mapbox** for production apps requiring SLA and support
566
+ - Cache route results with `initialRoute` to minimize API calls
567
+ - Handle network errors gracefully with `onRouteComputed` callback
568
+ - Combine `findNearestMarker` with `itineraryParams` for optimal routing workflows
569
+
570
+ ---
571
+
572
+ ## πŸ§‘β€πŸ’» Development
573
+
574
+ ### Prerequisites
575
+
576
+ - **Bun** β‰₯1.1.0 (recommended) or Node.js 18+
577
+ - Git
578
+
579
+ ### Setup
580
+ ```bash
581
+ # Clone the repository
582
+ git clone https://github.com/tracktor-tech/tracktor-map.git
583
+ cd tracktor-map
584
+
585
+ # Install dependencies
586
+ bun install
587
+
588
+ # Start development sandbox
589
+ bun run sandbox
590
+ # or
591
+ bun run dev:sandbox
592
+ ```
593
+
594
+ ### Available Scripts
595
+
596
+ | Command | Description |
597
+ |---------|-------------|
598
+ | `bun run sandbox` | Start interactive development playground |
599
+ | `bun run build` | Build library for production |
600
+ | `bun run build:sandbox` | Build sandbox demo site |
601
+ | `bun run deploy:sandbox` | Deploy sandbox to GitHub Pages |
602
+ | `bun run lint` | Check code quality and types |
603
+ | `bun run lint:fix` | Auto-fix linting issues |
604
+ | `bun run test` | Run test suite |
605
+ | `bun run test:watch` | Run tests in watch mode |
606
+ | `bun run version` | Bump version with changelog |
607
+ | `bun run release` | Build and publish to npm |
608
+
609
+ ### Project Structure
610
+ ```
611
+ @tracktor/map/
612
+ β”œβ”€β”€ src/
613
+ β”‚ β”œβ”€β”€ components/ # Reusable map and UI components (Marker, Popup, etc.)
614
+ β”‚ β”œβ”€β”€ constants/ # Shared configuration values and styling constants
615
+ β”‚ β”œβ”€β”€ context/ # React context providers (e.g. map state)
616
+ β”‚ β”œβ”€β”€ features/ # Core map features (routes, isochrones, nearest, etc.)
617
+ β”‚ β”œβ”€β”€ services/ # External APIs and utility services
618
+ β”‚ β”œβ”€β”€ types/ # TypeScript interfaces and types
619
+ β”‚ β”œβ”€β”€ utils/ # Generic helpers and formatting functions
620
+ β”‚ └── main.ts # Library entry point
621
+ β”‚
622
+ β”œβ”€β”€ sandbox/ # Development playground (example app & live demos)
623
+ β”‚ β”œβ”€β”€ context/ # Demo context providers
624
+ β”‚ β”œβ”€β”€ examples/ # Interactive usage examples
625
+ β”‚ β”œβ”€β”€ features/ # Components used in the docs/demo
626
+ β”‚ β”œβ”€β”€ public/ # Static assets (images, previews, etc.)
627
+ β”‚ β”œβ”€β”€ App.tsx # Sandbox root component
628
+ β”‚ └── index.tsx # Sandbox entry file
629
+ β”‚
630
+ └── test/ # Unit and integration tests
631
+ ```
632
+
633
+ ### Testing
634
+ ```bash
635
+ # Run all tests
636
+ bun test
637
+
638
+ # Watch mode
639
+ bun test:watch
640
+
641
+ # Run specific test file
642
+ bun test src/components/MapView.test.tsx
643
+ ```
102
644
 
103
- MIT Β© [Kevin Graff / Tracktor]
645
+ ### Contributing
104
646
 
647
+ We welcome contributions! Please:
105
648
 
106
- ## GH-Pages
107
- This project uses GitHub Pages for documentation hosting. To deploy the documentation, run:
649
+ 1. Fork the repository
650
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
651
+ 3. Make your changes
652
+ 4. Run tests and linting (`bun run test && bun run lint`)
653
+ 5. Commit your changes (`git commit -m 'Add amazing feature'`)
654
+ 6. Push to the branch (`git push origin feature/amazing-feature`)
655
+ 7. Open a Pull Request
656
+
657
+ **Code Style:**
658
+ - Follow existing patterns and conventions
659
+ - Use TypeScript for all new code
660
+ - Add tests for new features
661
+ - Update documentation as needed
662
+
663
+ ---
664
+
665
+ ## πŸ“˜ Documentation & Examples
666
+
667
+ Explore interactive examples and comprehensive API documentation:
668
+
669
+ πŸ‘‰ **[Live Documentation & Sandbox](https://tracktor.github.io/map)**
670
+
671
+ The sandbox includes:
672
+ - Interactive code examples
673
+ - Live preview of all features
674
+ - Copy-paste ready snippets
675
+ - API reference with search
676
+
677
+ ---
678
+
679
+ ## πŸ“¦ Publishing & Deployment
680
+
681
+ ### Publish to npm
682
+ ```bash
683
+ # Update version and generate changelog
684
+ bun run version
108
685
 
686
+ # Build and publish
687
+ bun run release
109
688
  ```
110
- bun run deploy:example
689
+
690
+ ### Deploy Sandbox to GitHub Pages
691
+ ```bash
692
+ bun run deploy:sandbox
111
693
  ```
112
- This will build the documentation and push it to the `gh-pages` branch.
113
694
 
114
- GH-Pages URL: https://tracktor.github.io/map/
695
+ This will:
696
+ 1. Build the sandbox with production optimizations
697
+ 2. Generate a 404.html for client-side routing
698
+ 3. Push to the `gh-pages` branch
699
+ 4. Update the live documentation site
700
+
701
+ ---
702
+
703
+ ## πŸ”§ Troubleshooting
704
+
705
+ ### Common Issues
706
+
707
+ **Map not rendering:**
708
+ - Verify your Mapbox token is valid
709
+ - Check browser console for errors
710
+ - Ensure mapbox-gl CSS is imported
711
+
712
+ **TypeScript errors:**
713
+ - Run `bun install` to update type definitions
714
+ - Check peer dependency versions match
715
+
716
+ **Performance issues:**
717
+ - Reduce marker count or use clustering
718
+ - Disable animations for large datasets
719
+ - Memoize marker data
720
+ - Use `initialRoute` and `initialNearestResults` for cached data
721
+
722
+ **Routing not working:**
723
+ - Verify coordinates are in [lng, lat] format (not lat, lng)
724
+ - Check that routing engine is accessible
725
+ - Ensure profile matches your use case
726
+ - Verify maxDistanceMeters is reasonable for nearest search
727
+
728
+ ### Getting Help
729
+
730
+ - πŸ“– Check the [documentation](https://github.com/Tracktor/map)
731
+ - πŸ› [Report bugs](https://github.com/tracktor-tech/tracktor-map/issues)
732
+ - πŸ’¬ Join discussions in GitHub Discussions
733
+
734
+ ---
735
+
736
+ ## πŸ“„ License
737
+
738
+ **UNLICENSED** β€” This package is proprietary software.
739
+ Β© [Tracktor β€” Kevin Graff]
740
+
741
+ ---
742
+
743
+ ## 🧭 Links
744
+
745
+ - πŸ“¦ **npm**: [@tracktor/map](https://www.npmjs.com/package/@tracktor/map)
746
+ - πŸ’» **GitHub**: [@tracktor/map](https://github.com/Tracktor/map)
747
+ - 🌐 **Docs**: [tracktor.github.io/map](https://tracktor.github.io/map)
748
+ - 🎨 **Design System**: [@tracktor/design-system](https://www.npmjs.com/package/@tracktor/design-system)
749
+ - Sandbox Demo: [tracktor.github.io/map/sandbox](https://tracktor.github.io/map)
750
+
751
+ ---
752
+
753
+ ## πŸ™ Acknowledgments
754
+
755
+ Built with:
756
+ - [Mapbox GL JS](https://docs.mapbox.com/mapbox-gl-js/) β€” Powerful map rendering
757
+ - [react-map-gl](https://visgl.github.io/react-map-gl/) β€” React wrapper for Mapbox
758
+ - [OSRM](http://project-osrm.org/) β€” Free routing engine
759
+ - [@tracktor/design-system](https://www.npmjs.com/package/@tracktor/design-system) β€” UI components