@local-logic/maps 0.0.1 → 0.0.3
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 +12 -0
- package/package.json +1 -1
- package/src/components/Map/Root/BaseMap/index.tsx +1 -3
- package/src/components/Map/Root/BaseMap/styles.ts +1 -2
- package/src/components/Map/Root/Layers/Google/index.tsx +97 -0
- package/src/components/Map/Root/Layers/Mapbox/index.tsx +74 -0
- package/src/components/Map/Root/Layers/Maptiler/index.tsx +74 -0
- package/src/components/Map/Root/Layers/index.tsx +32 -0
- package/src/components/Map/Root/Layers/types.ts +15 -0
- package/src/components/Map/Root/Layers/utils.ts +64 -0
- package/src/components/Map/Root/Markers/Cluster/index.tsx +7 -6
- package/src/components/Map/Root/Markers/Cluster/styles.ts +9 -12
- package/src/components/Map/Root/Markers/Google/index.tsx +11 -11
- package/src/components/Map/Root/Markers/Mapbox/index.tsx +20 -9
- package/src/components/Map/Root/Markers/Maptiler/index.tsx +19 -11
- package/src/components/Map/Root/Markers/index.tsx +42 -22
- package/src/components/Map/Root/Markers/styles.ts +16 -8
- package/src/components/Map/Root/Markers/types.ts +19 -2
- package/src/components/Map/Root/Popup/Google/index.tsx +57 -0
- package/src/components/Map/Root/Popup/Google/popup.css +15 -0
- package/src/components/Map/Root/Popup/Mapbox/index.tsx +36 -0
- package/src/components/Map/Root/Popup/Mapbox/popup.css +15 -0
- package/src/components/Map/Root/Popup/Maptiler/index.tsx +37 -0
- package/src/components/Map/Root/Popup/Maptiler/popup.css +15 -0
- package/src/components/Map/Root/Popup/index.tsx +32 -0
- package/src/components/Map/Root/Popup/styles.ts +4 -0
- package/src/components/Map/Root/Popup/types.ts +20 -0
- package/src/components/Map/Root/index.tsx +14 -1
- package/src/components/Map/Root/types.ts +1 -13
- package/src/components/Map/index.stories.tsx +86 -70
- package/src/components/Map/storybook-data.ts +255 -0
- package/src/components/Map/Root/Markers/Google/styles.ts +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @local-logic/maps
|
|
2
2
|
|
|
3
|
+
## 0.0.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- de9946a: LL22-5133: Added layer support to map package
|
|
8
|
+
|
|
9
|
+
## 0.0.2
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 9dcc72d: LL22-5132: Added popup support for markers and clusters
|
|
14
|
+
|
|
3
15
|
## 0.0.1
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
export const wrapper = `
|
|
2
|
-
export const map = `absolute w-full h-full`;
|
|
1
|
+
export const wrapper = `w-full h-full`;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import React, { useState, useEffect, useContext } from "react";
|
|
2
|
+
|
|
3
|
+
// Work inspired from:
|
|
4
|
+
// https://github.com/visgl/react-google-maps/blob/main/examples/geometry/src/components/polygon.tsx
|
|
5
|
+
// https://visgl.github.io/react-google-maps/examples/geometry
|
|
6
|
+
|
|
7
|
+
import { GoogleMapsContext } from "@vis.gl/react-google-maps";
|
|
8
|
+
|
|
9
|
+
import { getGooglePolygonFromLocalLogicGeometry } from "../utils";
|
|
10
|
+
|
|
11
|
+
import type { Props, Layer as LayerType } from "../types";
|
|
12
|
+
|
|
13
|
+
function getLayerPaint(type: LayerType["type"], color: string) {
|
|
14
|
+
switch (type) {
|
|
15
|
+
case "fill":
|
|
16
|
+
return {
|
|
17
|
+
fillColor: color,
|
|
18
|
+
fillOpacity: 0.25,
|
|
19
|
+
};
|
|
20
|
+
case "line":
|
|
21
|
+
return {
|
|
22
|
+
strokeColor: color,
|
|
23
|
+
strokeWeight: 2,
|
|
24
|
+
};
|
|
25
|
+
default:
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default function GoogleLayers({ sources }: Props) {
|
|
31
|
+
const [map, setMap] = useState<google.maps.Map | null>(null);
|
|
32
|
+
const [polygonRefs, setPolygonRefs] = useState<google.maps.Polygon[]>([]);
|
|
33
|
+
const mapContext = useContext(GoogleMapsContext);
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (!mapContext) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
setMap(mapContext.map);
|
|
41
|
+
}, [mapContext]);
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
// Remove existing polygons
|
|
45
|
+
polygonRefs.forEach((polygonRef) => {
|
|
46
|
+
polygonRef?.setMap(null);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (!map || typeof sources === "undefined" || sources.length === 0) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Create new polygons
|
|
54
|
+
const newPolygons = sources.map((source) => {
|
|
55
|
+
const polygonOptions = {
|
|
56
|
+
paths: getGooglePolygonFromLocalLogicGeometry(source.geometry),
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
if (Array.isArray(source.layer)) {
|
|
60
|
+
source.layer.forEach((layer) => {
|
|
61
|
+
const layerPaint = getLayerPaint(layer.type, layer.color);
|
|
62
|
+
Object.assign(polygonOptions, layerPaint);
|
|
63
|
+
});
|
|
64
|
+
} else {
|
|
65
|
+
const layerPaint = getLayerPaint(source.layer.type, source.layer.color);
|
|
66
|
+
Object.assign(polygonOptions, layerPaint);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const newPolygon = new google.maps.Polygon({
|
|
70
|
+
// Set default options otherwise the polygon remains visible (even if eg: we only want the outline)
|
|
71
|
+
// We then overrider the default options with the layer options
|
|
72
|
+
strokeWeight: 0,
|
|
73
|
+
fillOpacity: 0,
|
|
74
|
+
...polygonOptions,
|
|
75
|
+
});
|
|
76
|
+
newPolygon.setMap(map);
|
|
77
|
+
|
|
78
|
+
return newPolygon;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
setPolygonRefs(newPolygons);
|
|
82
|
+
// Set the map for each polygon
|
|
83
|
+
newPolygons.forEach((polygonRef) => {
|
|
84
|
+
polygonRef.setMap(map);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Clean up the polygons when the component unmounts or when sources change
|
|
88
|
+
// eslint-disable-next-line consistent-return
|
|
89
|
+
return () => {
|
|
90
|
+
newPolygons.forEach((polygonRef) => {
|
|
91
|
+
polygonRef?.setMap(null);
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
}, [map, sources]);
|
|
95
|
+
|
|
96
|
+
return <></>;
|
|
97
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
|
+
|
|
3
|
+
import { Source, Layer } from "react-map-gl/mapbox";
|
|
4
|
+
|
|
5
|
+
import { getFeatureCollectionFromLocalLogicGeometry } from "../utils";
|
|
6
|
+
|
|
7
|
+
import type { Props, Layer as LayerType } from "../types";
|
|
8
|
+
|
|
9
|
+
function getLayerPaint(type: LayerType["type"], color: string) {
|
|
10
|
+
switch (type) {
|
|
11
|
+
case "fill":
|
|
12
|
+
return {
|
|
13
|
+
"fill-color": color,
|
|
14
|
+
"fill-opacity": 0.15,
|
|
15
|
+
};
|
|
16
|
+
case "line":
|
|
17
|
+
return {
|
|
18
|
+
"line-color": color,
|
|
19
|
+
"line-width": 2,
|
|
20
|
+
};
|
|
21
|
+
default:
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default function MapboxLayers({ sources }: Props) {
|
|
27
|
+
const geometrySources = useMemo(() => {
|
|
28
|
+
if (!Array.isArray(sources) || sources.length === 0) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return sources.map((geometry) => ({
|
|
33
|
+
...geometry,
|
|
34
|
+
geoJson: geometry.geometry
|
|
35
|
+
? getFeatureCollectionFromLocalLogicGeometry(geometry.geometry)
|
|
36
|
+
: null,
|
|
37
|
+
}));
|
|
38
|
+
}, [sources]);
|
|
39
|
+
|
|
40
|
+
if (geometrySources?.length === 0) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<>
|
|
46
|
+
{geometrySources?.map((source) => (
|
|
47
|
+
<div key={source.key}>
|
|
48
|
+
{source.geoJson && (
|
|
49
|
+
<Source id={source.key} key={source.key} type="geojson" data={source.geoJson}>
|
|
50
|
+
{source.layer &&
|
|
51
|
+
(Array.isArray(source.layer) ? (
|
|
52
|
+
source.layer.map((layer, index) => (
|
|
53
|
+
<Layer
|
|
54
|
+
key={`${source.key}-${index}`}
|
|
55
|
+
{...{
|
|
56
|
+
...{ type: layer.type, paint: getLayerPaint(layer.type, layer.color) },
|
|
57
|
+
}}
|
|
58
|
+
/>
|
|
59
|
+
))
|
|
60
|
+
) : (
|
|
61
|
+
<Layer
|
|
62
|
+
{...{
|
|
63
|
+
type: source.layer.type,
|
|
64
|
+
paint: getLayerPaint(source.layer.type, source.layer.color),
|
|
65
|
+
}}
|
|
66
|
+
/>
|
|
67
|
+
))}
|
|
68
|
+
</Source>
|
|
69
|
+
)}
|
|
70
|
+
</div>
|
|
71
|
+
))}
|
|
72
|
+
</>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
|
+
|
|
3
|
+
import { Source, Layer } from "react-map-gl/maplibre";
|
|
4
|
+
|
|
5
|
+
import { getFeatureCollectionFromLocalLogicGeometry } from "../utils";
|
|
6
|
+
|
|
7
|
+
import type { Props, Layer as LayerType } from "../types";
|
|
8
|
+
|
|
9
|
+
function getLayerPaint(type: LayerType["type"], color: string) {
|
|
10
|
+
switch (type) {
|
|
11
|
+
case "fill":
|
|
12
|
+
return {
|
|
13
|
+
"fill-color": color,
|
|
14
|
+
"fill-opacity": 0.25,
|
|
15
|
+
};
|
|
16
|
+
case "line":
|
|
17
|
+
return {
|
|
18
|
+
"line-color": color,
|
|
19
|
+
"line-width": 2,
|
|
20
|
+
};
|
|
21
|
+
default:
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default function MaptilerLayers({ sources }: Props) {
|
|
27
|
+
const geometrySources = useMemo(() => {
|
|
28
|
+
if (!Array.isArray(sources) || sources.length === 0) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return sources.map((geometry) => ({
|
|
33
|
+
...geometry,
|
|
34
|
+
geoJson: geometry.geometry
|
|
35
|
+
? getFeatureCollectionFromLocalLogicGeometry(geometry.geometry)
|
|
36
|
+
: null,
|
|
37
|
+
}));
|
|
38
|
+
}, [sources]);
|
|
39
|
+
|
|
40
|
+
if (geometrySources?.length === 0) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<>
|
|
46
|
+
{geometrySources?.map((source) => (
|
|
47
|
+
<div key={source.key}>
|
|
48
|
+
{source.geoJson && (
|
|
49
|
+
<Source id={source.key} key={source.key} type="geojson" data={source.geoJson}>
|
|
50
|
+
{source.layer &&
|
|
51
|
+
(Array.isArray(source.layer) ? (
|
|
52
|
+
source.layer.map((layer, index) => (
|
|
53
|
+
<Layer
|
|
54
|
+
key={`${source.key}-${index}`}
|
|
55
|
+
{...{
|
|
56
|
+
...{ type: layer.type, paint: getLayerPaint(layer.type, layer.color) },
|
|
57
|
+
}}
|
|
58
|
+
/>
|
|
59
|
+
))
|
|
60
|
+
) : (
|
|
61
|
+
<Layer
|
|
62
|
+
{...{
|
|
63
|
+
type: source.layer.type,
|
|
64
|
+
paint: getLayerPaint(source.layer.type, source.layer.color),
|
|
65
|
+
}}
|
|
66
|
+
/>
|
|
67
|
+
))}
|
|
68
|
+
</Source>
|
|
69
|
+
)}
|
|
70
|
+
</div>
|
|
71
|
+
))}
|
|
72
|
+
</>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React, { lazy, useMemo } from "react";
|
|
2
|
+
|
|
3
|
+
import type { Props } from "./types";
|
|
4
|
+
|
|
5
|
+
import { useRootElement } from "../context";
|
|
6
|
+
|
|
7
|
+
const MaptilerLayers = lazy(() => import("./Maptiler"));
|
|
8
|
+
const GoogleLayers = lazy(() => import("./Google"));
|
|
9
|
+
const MapboxLayers = lazy(() => import("./Mapbox"));
|
|
10
|
+
|
|
11
|
+
export function Layers(props: Props) {
|
|
12
|
+
const { mapProvider } = useRootElement();
|
|
13
|
+
|
|
14
|
+
const BaseLayers = useMemo(() => {
|
|
15
|
+
switch (mapProvider?.name) {
|
|
16
|
+
case "maptiler":
|
|
17
|
+
return MaptilerLayers;
|
|
18
|
+
case "google":
|
|
19
|
+
return GoogleLayers;
|
|
20
|
+
case "mapbox":
|
|
21
|
+
return MapboxLayers;
|
|
22
|
+
default:
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}, [mapProvider]);
|
|
26
|
+
|
|
27
|
+
if (!BaseLayers) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return <BaseLayers {...props} />;
|
|
32
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type Layer = {
|
|
2
|
+
type: "fill" | "line";
|
|
3
|
+
color: string;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export type Source = {
|
|
7
|
+
key: string;
|
|
8
|
+
layer: Layer | Layer[];
|
|
9
|
+
// Expected [lat, lng] format
|
|
10
|
+
geometry: number[][][][];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type Props = {
|
|
14
|
+
sources?: Source[];
|
|
15
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { FeatureCollection, MultiPolygon } from "geojson";
|
|
2
|
+
|
|
3
|
+
type Geometry =
|
|
4
|
+
| number[][]
|
|
5
|
+
| number[][][]
|
|
6
|
+
| number[][][][]
|
|
7
|
+
| string[][]
|
|
8
|
+
| string[][][]
|
|
9
|
+
| string[][][][];
|
|
10
|
+
|
|
11
|
+
export function getFeatureCollectionFromLocalLogicGeometry(geometry: Geometry = []) {
|
|
12
|
+
/* Geometries need formatting before being added as a Source in the Map.
|
|
13
|
+
* Geometries from v3/schools have a format of [lat, lng]
|
|
14
|
+
* Geojson accepts polygons with a format of [lng, lat]
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const geometryLatLngFormat = (arr: object): object =>
|
|
18
|
+
Object.values(arr).map((a) => {
|
|
19
|
+
if (Array.isArray(a[0])) {
|
|
20
|
+
return geometryLatLngFormat(a);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return [a[1], a[0]];
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const finalCoordinates = geometryLatLngFormat(geometry);
|
|
27
|
+
|
|
28
|
+
const responseObject = {
|
|
29
|
+
type: "FeatureCollection",
|
|
30
|
+
features: [
|
|
31
|
+
{
|
|
32
|
+
type: "Feature",
|
|
33
|
+
geometry: {
|
|
34
|
+
type: "MultiPolygon",
|
|
35
|
+
coordinates: finalCoordinates,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
} as FeatureCollection<MultiPolygon>;
|
|
40
|
+
|
|
41
|
+
return responseObject;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function getGooglePolygonFromLocalLogicGeometry(geometry: Geometry = []) {
|
|
45
|
+
/* Geometries need formatting before being added as a Source in the Map.
|
|
46
|
+
* Geometries from v3/schools have a format of [lat, lng]
|
|
47
|
+
* Google Maps accepts polygons with a format of { lat: number, lng: number }
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
const geometryLatLngFormat = (arr: object): object =>
|
|
51
|
+
Object.values(arr).map((a) => {
|
|
52
|
+
if (Array.isArray(a[0])) {
|
|
53
|
+
return geometryLatLngFormat(a.flat());
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { lat: a[0], lng: a[1] };
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const finalCoordinates = geometryLatLngFormat(
|
|
60
|
+
geometry
|
|
61
|
+
) as google.maps.MVCArray<google.maps.LatLng>;
|
|
62
|
+
|
|
63
|
+
return finalCoordinates;
|
|
64
|
+
}
|
|
@@ -7,9 +7,10 @@ import * as styles from "./styles";
|
|
|
7
7
|
export function Cluster({ cluster }: { cluster: ClusterPoint }) {
|
|
8
8
|
// Taking a somewhat similar calculation to what LocalLogic SDK used to
|
|
9
9
|
// do for cluster diameter (bigger depending on count)
|
|
10
|
-
const diameter = useMemo(
|
|
11
|
-
20 + cluster.properties.point_count * 4
|
|
12
|
-
|
|
10
|
+
const diameter = useMemo(
|
|
11
|
+
() => 20 + cluster.properties.point_count * 4,
|
|
12
|
+
[cluster.properties.point_count]
|
|
13
|
+
);
|
|
13
14
|
|
|
14
15
|
return (
|
|
15
16
|
<div
|
|
@@ -17,10 +18,10 @@ export function Cluster({ cluster }: { cluster: ClusterPoint }) {
|
|
|
17
18
|
style={{
|
|
18
19
|
width: `${diameter}px`,
|
|
19
20
|
height: `${diameter}px`,
|
|
20
|
-
fontSize: `${diameter / 2.5}px
|
|
21
|
+
fontSize: `${diameter / 2.5}px`,
|
|
21
22
|
}}
|
|
22
23
|
>
|
|
23
24
|
{cluster.properties.point_count}
|
|
24
25
|
</div>
|
|
25
|
-
)
|
|
26
|
-
}
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
export const container = `
|
|
2
|
-
rounded-full
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
border-
|
|
11
|
-
border-solid
|
|
12
|
-
border-base-white
|
|
13
|
-
shadow-sm
|
|
2
|
+
rounded-full leading-none font-sans border-2
|
|
3
|
+
border-solid shadow-sm
|
|
4
|
+
flex justify-center items-center
|
|
5
|
+
bg-primary-100 text-base-white border-base-white
|
|
6
|
+
transition-all
|
|
7
|
+
|
|
8
|
+
hover:bg-base-white
|
|
9
|
+
hover:text-primary-100
|
|
10
|
+
hover:border-primary-100
|
|
14
11
|
`;
|
|
@@ -8,15 +8,14 @@ import type { MapMarkerProps } from "../types";
|
|
|
8
8
|
import { Cluster } from "../Cluster";
|
|
9
9
|
|
|
10
10
|
import * as globalStyles from "../styles";
|
|
11
|
-
import * as styles from "./styles";
|
|
12
11
|
|
|
13
|
-
export default function GoogleMarkers({ clusters, children }: MapMarkerProps) {
|
|
14
|
-
return clusters
|
|
12
|
+
export default function GoogleMarkers({ clusters, onClick, children }: MapMarkerProps) {
|
|
13
|
+
return clusters?.map((cluster, index) => {
|
|
15
14
|
const longitude = cluster.geometry.coordinates[0];
|
|
16
15
|
const latitude = cluster.geometry.coordinates[1];
|
|
17
16
|
|
|
18
17
|
const handleOnClick = () => {
|
|
19
|
-
|
|
18
|
+
onClick?.(cluster);
|
|
20
19
|
};
|
|
21
20
|
|
|
22
21
|
return (
|
|
@@ -27,18 +26,19 @@ export default function GoogleMarkers({ clusters, children }: MapMarkerProps) {
|
|
|
27
26
|
lng: longitude,
|
|
28
27
|
}}
|
|
29
28
|
onClick={handleOnClick}
|
|
30
|
-
className={styles.marker}
|
|
31
29
|
>
|
|
32
|
-
<div className={globalStyles.container}>
|
|
30
|
+
<div className={globalStyles.container} data-is-cluster={!!cluster.properties.cluster}>
|
|
33
31
|
{cluster.properties.cluster ? (
|
|
34
32
|
<Cluster cluster={cluster} />
|
|
35
33
|
) : (
|
|
36
34
|
<>
|
|
37
|
-
{
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
<div className={globalStyles.iconWrapper}>
|
|
36
|
+
{cluster.properties.icon ? (
|
|
37
|
+
<cluster.properties.icon className={globalStyles.icon} />
|
|
38
|
+
) : (
|
|
39
|
+
<MapPin className={globalStyles.icon} />
|
|
40
|
+
)}
|
|
41
|
+
</div>
|
|
42
42
|
{children}
|
|
43
43
|
</>
|
|
44
44
|
)}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
3
|
import { MapPin } from "@phosphor-icons/react";
|
|
4
|
-
import { Marker as MapboxMarker } from "react-map-gl/mapbox";
|
|
4
|
+
import { Marker as MapboxMarker, type MarkerEvent } from "react-map-gl/mapbox";
|
|
5
5
|
|
|
6
6
|
import type { MapMarkerProps } from "../types";
|
|
7
7
|
|
|
@@ -9,28 +9,39 @@ import { Cluster } from "../Cluster";
|
|
|
9
9
|
|
|
10
10
|
import * as styles from "../styles";
|
|
11
11
|
|
|
12
|
-
export default function MapboxMarkers({ clusters, children }: MapMarkerProps) {
|
|
13
|
-
return clusters
|
|
12
|
+
export default function MapboxMarkers({ clusters, onClick, children }: MapMarkerProps) {
|
|
13
|
+
return clusters?.map((cluster, index) => {
|
|
14
14
|
const longitude = cluster.geometry.coordinates[0];
|
|
15
15
|
const latitude = cluster.geometry.coordinates[1];
|
|
16
16
|
|
|
17
|
+
const handleOnClick = (event: MarkerEvent<MouseEvent>) => {
|
|
18
|
+
// If we let the click event propagates to the map, it will immediately close the popup
|
|
19
|
+
// with `closeOnClick: true`
|
|
20
|
+
// https://github.com/visgl/react-map-gl/blob/bdfcfd2d6cfdc641cddb5337d7ed477e5c3c28d5/examples/controls/src/app.tsx#L31-36
|
|
21
|
+
event.originalEvent.stopPropagation();
|
|
22
|
+
onClick?.(cluster);
|
|
23
|
+
};
|
|
24
|
+
|
|
17
25
|
return (
|
|
18
26
|
<MapboxMarker
|
|
19
27
|
key={`map-marker-${longitude}-${latitude}-${index}`}
|
|
20
28
|
anchor="bottom"
|
|
21
29
|
longitude={longitude}
|
|
22
30
|
latitude={latitude}
|
|
31
|
+
onClick={handleOnClick}
|
|
23
32
|
>
|
|
24
|
-
<div className={styles.container}>
|
|
33
|
+
<div className={styles.container} data-is-cluster={!!cluster.properties.cluster}>
|
|
25
34
|
{cluster.properties.cluster ? (
|
|
26
35
|
<Cluster cluster={cluster} />
|
|
27
36
|
) : (
|
|
28
37
|
<>
|
|
29
|
-
{
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
<div className={styles.iconWrapper}>
|
|
39
|
+
{cluster.properties.icon ? (
|
|
40
|
+
<cluster.properties.icon className={styles.icon} />
|
|
41
|
+
) : (
|
|
42
|
+
<MapPin className={styles.icon} />
|
|
43
|
+
)}
|
|
44
|
+
</div>
|
|
34
45
|
{children}
|
|
35
46
|
</>
|
|
36
47
|
)}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
3
|
import { MapPin } from "@phosphor-icons/react";
|
|
4
|
-
import { Marker as MaplibreMarker } from "react-map-gl/maplibre";
|
|
4
|
+
import { Marker as MaplibreMarker, type MarkerEvent } from "react-map-gl/maplibre";
|
|
5
5
|
|
|
6
6
|
import type { MapMarkerProps } from "../types";
|
|
7
7
|
|
|
@@ -9,31 +9,39 @@ import { Cluster } from "../Cluster";
|
|
|
9
9
|
|
|
10
10
|
import * as styles from "../styles";
|
|
11
11
|
|
|
12
|
-
export default function MaptilerMarkers({
|
|
13
|
-
clusters,
|
|
14
|
-
children,
|
|
15
|
-
}: MapMarkerProps) {
|
|
12
|
+
export default function MaptilerMarkers({ clusters, onClick, children }: MapMarkerProps) {
|
|
16
13
|
return clusters?.map((cluster, index) => {
|
|
17
14
|
const longitude = cluster.geometry.coordinates[0];
|
|
18
15
|
const latitude = cluster.geometry.coordinates[1];
|
|
19
16
|
|
|
17
|
+
const handleOnClick = (event: MarkerEvent<MouseEvent>) => {
|
|
18
|
+
// If we let the click event propagates to the map, it will immediately close the popup
|
|
19
|
+
// with `closeOnClick: true`
|
|
20
|
+
// https://github.com/visgl/react-map-gl/blob/bdfcfd2d6cfdc641cddb5337d7ed477e5c3c28d5/examples/controls/src/app.tsx#L31-36
|
|
21
|
+
event.originalEvent.stopPropagation();
|
|
22
|
+
onClick?.(cluster);
|
|
23
|
+
};
|
|
24
|
+
|
|
20
25
|
return (
|
|
21
26
|
<MaplibreMarker
|
|
22
27
|
key={`map-marker-${longitude}-${latitude}-${index}`}
|
|
23
28
|
anchor="bottom"
|
|
24
29
|
longitude={longitude}
|
|
25
30
|
latitude={latitude}
|
|
31
|
+
onClick={handleOnClick}
|
|
26
32
|
>
|
|
27
|
-
<div className={styles.container}>
|
|
33
|
+
<div className={styles.container} data-is-cluster={!!cluster.properties.cluster}>
|
|
28
34
|
{cluster.properties.cluster ? (
|
|
29
35
|
<Cluster cluster={cluster} />
|
|
30
36
|
) : (
|
|
31
37
|
<>
|
|
32
|
-
{
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
<div className={styles.iconWrapper}>
|
|
39
|
+
{cluster.properties.icon ? (
|
|
40
|
+
<cluster.properties.icon className={styles.icon} />
|
|
41
|
+
) : (
|
|
42
|
+
<MapPin className={styles.icon} />
|
|
43
|
+
)}
|
|
44
|
+
</div>
|
|
37
45
|
{children}
|
|
38
46
|
</>
|
|
39
47
|
)}
|