@swisper.ai/web-map 0.0.2 → 0.0.4
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/dist/index.css +1 -1
- package/dist/index.es.js +76 -76
- package/dist/index.es.js.map +1 -1
- package/dist/map-pin.d.ts.map +1 -1
- package/dist/user-location-pin.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/__tests__/map-user-location.spec.tsx +2 -6
- package/src/base-map.json +1 -0
- package/src/map-marker.json +1 -0
- package/src/map-marker.stories.tsx +6 -6
- package/src/map-marker.tsx +1 -1
- package/src/map-pin.json +1 -0
- package/src/map-pin.tsx +2 -1
- package/src/map-user-location.json +1 -0
- package/src/map-user-location.stories.tsx +11 -12
- package/src/map-view.json +1 -0
- package/src/map-view.stories.tsx +5 -6
- package/src/user-location-pin.json +1 -0
- package/src/user-location-pin.tsx +2 -1
- /package/src/__tests__/{load-map-lib.spec.ts → load-map-lib.test.ts} +0 -0
package/dist/index.es.js
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
1
|
import { createContext as e, useContext as t, useEffect as n, useMemo as r, useRef as i } from "react";
|
|
2
2
|
import { Layer as a, Map as o, Marker as s, Marker as c, Source as l, useMap as u } from "@vis.gl/react-maplibre";
|
|
3
3
|
import { ErrorIcon as d } from "@swisper.ai/icons";
|
|
4
|
-
import { Tooltip as f, Typography as p
|
|
5
|
-
import { cn as
|
|
4
|
+
import { Tooltip as f, Typography as p } from "@swisper.ai/ui";
|
|
5
|
+
import { cn as m } from "@swisper.ai/web-core";
|
|
6
6
|
import "maplibre-gl/dist/maplibre-gl.css";
|
|
7
|
-
import { jsx as
|
|
7
|
+
import { jsx as h, jsxs as g } from "react/jsx-runtime";
|
|
8
8
|
//#region src/constants.ts
|
|
9
|
-
var
|
|
10
|
-
function
|
|
9
|
+
var _ = "https://tiles.openfreemap.org/styles/liberty", v = 48, y = 15;
|
|
10
|
+
function b(e) {
|
|
11
11
|
return e ?? "https://tiles.openfreemap.org/styles/liberty";
|
|
12
12
|
}
|
|
13
13
|
//#endregion
|
|
14
14
|
//#region src/load-map-lib.ts
|
|
15
|
-
var
|
|
16
|
-
function
|
|
17
|
-
return
|
|
15
|
+
var x;
|
|
16
|
+
function S() {
|
|
17
|
+
return x ??= Promise.all([import("maplibre-gl"), import("./maplibre-worker-source-DgoyX01U.js")]).then(([e, { default: t }]) => {
|
|
18
18
|
let n = new Blob([t], { type: "text/javascript" });
|
|
19
19
|
return e.setWorkerUrl(URL.createObjectURL(n)), e;
|
|
20
|
-
}),
|
|
20
|
+
}), x;
|
|
21
21
|
}
|
|
22
22
|
//#endregion
|
|
23
23
|
//#region src/utils/get-bounds-from-points.ts
|
|
24
|
-
function
|
|
24
|
+
function C(e) {
|
|
25
25
|
if (e.length === 0) return null;
|
|
26
26
|
let t = e[0][0], n = e[0][1], r = e[0][0], i = e[0][1];
|
|
27
27
|
for (let [a, o] of e) t = Math.min(t, a), n = Math.min(n, o), r = Math.max(r, a), i = Math.max(i, o);
|
|
28
28
|
return [[t, n], [r, i]];
|
|
29
29
|
}
|
|
30
|
-
function
|
|
30
|
+
function w(e) {
|
|
31
31
|
let [[t, n], [r, i]] = e;
|
|
32
32
|
return [(t + r) / 2, (n + i) / 2];
|
|
33
33
|
}
|
|
34
|
-
function
|
|
34
|
+
function T(e) {
|
|
35
35
|
let [[t, n], [r, i]] = e;
|
|
36
36
|
return t === r && n === i;
|
|
37
37
|
}
|
|
38
38
|
//#endregion
|
|
39
39
|
//#region src/base-map.tsx
|
|
40
|
-
function
|
|
41
|
-
if (
|
|
40
|
+
function E(e, t, n) {
|
|
41
|
+
if (T(t)) {
|
|
42
42
|
let [[n, r]] = t;
|
|
43
43
|
e.setCenter([n, r]), e.setZoom(15);
|
|
44
44
|
return;
|
|
@@ -48,30 +48,30 @@ function D(e, t, n) {
|
|
|
48
48
|
duration: 0
|
|
49
49
|
});
|
|
50
50
|
}
|
|
51
|
-
function
|
|
52
|
-
let y = i(null),
|
|
51
|
+
function D({ children: e, className: t, testId: r, ariaLabel: a, hasError: s, isLoading: c, initialViewState: l, bounds: u = null, fitBoundsPadding: d = 48, mapStyleUrl: f, mapRef: p, onLoad: _, errorLabel: v = "Could not load map. Try again later." }) {
|
|
52
|
+
let y = i(null), x = p ?? y, C = b(f);
|
|
53
53
|
return n(() => {
|
|
54
54
|
if (!u) return;
|
|
55
|
-
let e =
|
|
56
|
-
e &&
|
|
55
|
+
let e = x.current?.getMap();
|
|
56
|
+
e && E(e, u, d);
|
|
57
57
|
}, [
|
|
58
58
|
u,
|
|
59
59
|
d,
|
|
60
|
-
|
|
61
|
-
]), /* @__PURE__ */
|
|
60
|
+
x
|
|
61
|
+
]), /* @__PURE__ */ g("div", {
|
|
62
62
|
"data-testid": r,
|
|
63
63
|
"data-rounded": !0,
|
|
64
64
|
"data-loading": c,
|
|
65
65
|
"aria-invalid": s,
|
|
66
66
|
"data-error": s,
|
|
67
|
-
className:
|
|
67
|
+
className: m("relative size-full", "bg-[#f8f4f1]", "data-[error=true]:bg-[#EB0000]/10", "data-[rounded=true]:rounded-lg overflow-hidden", t),
|
|
68
68
|
role: "application",
|
|
69
69
|
"aria-label": a,
|
|
70
|
-
children: [s && /* @__PURE__ */
|
|
71
|
-
ref:
|
|
72
|
-
mapLib:
|
|
70
|
+
children: [s && /* @__PURE__ */ h(O, { label: v }), /* @__PURE__ */ h(o, {
|
|
71
|
+
ref: x,
|
|
72
|
+
mapLib: S(),
|
|
73
73
|
initialViewState: l,
|
|
74
|
-
mapStyle:
|
|
74
|
+
mapStyle: C,
|
|
75
75
|
style: {
|
|
76
76
|
width: "100%",
|
|
77
77
|
height: "100%",
|
|
@@ -81,21 +81,21 @@ function O({ children: e, className: t, testId: r, ariaLabel: a, hasError: s, is
|
|
|
81
81
|
attributionControl: !1,
|
|
82
82
|
onLoad: () => {
|
|
83
83
|
if (u) {
|
|
84
|
-
let e =
|
|
85
|
-
e &&
|
|
84
|
+
let e = x.current?.getMap();
|
|
85
|
+
e && E(e, u, d);
|
|
86
86
|
}
|
|
87
|
-
|
|
87
|
+
_?.();
|
|
88
88
|
},
|
|
89
89
|
children: e
|
|
90
90
|
})]
|
|
91
91
|
});
|
|
92
92
|
}
|
|
93
|
-
function
|
|
94
|
-
return /* @__PURE__ */
|
|
93
|
+
function O({ label: e }) {
|
|
94
|
+
return /* @__PURE__ */ h("div", {
|
|
95
95
|
className: "absolute inset-0 flex items-center justify-center",
|
|
96
|
-
children: /* @__PURE__ */
|
|
96
|
+
children: /* @__PURE__ */ g("div", {
|
|
97
97
|
className: "flex flex-col items-center gap-8",
|
|
98
|
-
children: [/* @__PURE__ */
|
|
98
|
+
children: [/* @__PURE__ */ h(d, { fontSize: "large" }), /* @__PURE__ */ h(p, {
|
|
99
99
|
variant: "body-sm",
|
|
100
100
|
className: "text-text-error",
|
|
101
101
|
children: e
|
|
@@ -105,17 +105,17 @@ function k({ label: e }) {
|
|
|
105
105
|
}
|
|
106
106
|
//#endregion
|
|
107
107
|
//#region src/map-context.tsx
|
|
108
|
-
var
|
|
109
|
-
function
|
|
110
|
-
let e = t(
|
|
108
|
+
var k = e(null);
|
|
109
|
+
function A() {
|
|
110
|
+
let e = t(k);
|
|
111
111
|
if (!e) throw Error("useMapView must be used within a MapView component");
|
|
112
112
|
return e;
|
|
113
113
|
}
|
|
114
114
|
//#endregion
|
|
115
115
|
//#region src/map-view.tsx
|
|
116
|
-
function
|
|
117
|
-
let u = i(null), d = r(() =>
|
|
118
|
-
center:
|
|
116
|
+
function j({ fitPoints: e, selectedId: t = null, hoveredId: n = null, onSelect: a, onHover: o, flyToSelected: s = !0, children: c, ...l }) {
|
|
117
|
+
let u = i(null), d = r(() => C(e), [e]), f = r(() => d ? {
|
|
118
|
+
center: w(d),
|
|
119
119
|
zoom: 12
|
|
120
120
|
} : {
|
|
121
121
|
center: [0, 0],
|
|
@@ -134,9 +134,9 @@ function M({ fitPoints: e, selectedId: t = null, hoveredId: n = null, onSelect:
|
|
|
134
134
|
a,
|
|
135
135
|
o
|
|
136
136
|
]);
|
|
137
|
-
return /* @__PURE__ */
|
|
137
|
+
return /* @__PURE__ */ h(k.Provider, {
|
|
138
138
|
value: p,
|
|
139
|
-
children: /* @__PURE__ */
|
|
139
|
+
children: /* @__PURE__ */ h(D, {
|
|
140
140
|
...l,
|
|
141
141
|
initialViewState: f,
|
|
142
142
|
bounds: d,
|
|
@@ -147,18 +147,18 @@ function M({ fitPoints: e, selectedId: t = null, hoveredId: n = null, onSelect:
|
|
|
147
147
|
}
|
|
148
148
|
//#endregion
|
|
149
149
|
//#region src/map-marker.tsx
|
|
150
|
-
var
|
|
151
|
-
function
|
|
152
|
-
return t(
|
|
150
|
+
var M = e(null);
|
|
151
|
+
function N() {
|
|
152
|
+
return t(M);
|
|
153
153
|
}
|
|
154
|
-
function
|
|
154
|
+
function P({ isHovered: e, isSelected: t }) {
|
|
155
155
|
return e ? 3 : t ? 2 : 1;
|
|
156
156
|
}
|
|
157
|
-
function
|
|
158
|
-
let { mapRef: d, selectedId: f, hoveredId: p, flyToSelected:
|
|
157
|
+
function F({ id: e, longitude: t, latitude: i, label: a, anchor: o = "bottom", flyToOnSelect: s, className: l, children: u }) {
|
|
158
|
+
let { mapRef: d, selectedId: f, hoveredId: p, flyToSelected: g, onSelect: _, onHover: v } = A(), y = e === f, b = e === p, x = r(() => ({
|
|
159
159
|
isSelected: y,
|
|
160
160
|
isHovered: b
|
|
161
|
-
}), [y, b]), S = s ??
|
|
161
|
+
}), [y, b]), S = s ?? g;
|
|
162
162
|
return n(() => {
|
|
163
163
|
if (!y || !S) return;
|
|
164
164
|
let e = d.current?.getMap();
|
|
@@ -172,15 +172,15 @@ function I({ id: e, longitude: t, latitude: i, label: a, anchor: o = "bottom", f
|
|
|
172
172
|
t,
|
|
173
173
|
i,
|
|
174
174
|
d
|
|
175
|
-
]), /* @__PURE__ */
|
|
175
|
+
]), /* @__PURE__ */ h(c, {
|
|
176
176
|
longitude: t,
|
|
177
177
|
latitude: i,
|
|
178
178
|
anchor: o,
|
|
179
|
-
style: { zIndex:
|
|
179
|
+
style: { zIndex: P(x) },
|
|
180
180
|
onClick: (t) => {
|
|
181
181
|
t.originalEvent.stopPropagation(), _?.(e);
|
|
182
182
|
},
|
|
183
|
-
children: /* @__PURE__ */
|
|
183
|
+
children: /* @__PURE__ */ h("button", {
|
|
184
184
|
type: "button",
|
|
185
185
|
"aria-label": a,
|
|
186
186
|
"aria-pressed": y,
|
|
@@ -189,7 +189,7 @@ function I({ id: e, longitude: t, latitude: i, label: a, anchor: o = "bottom", f
|
|
|
189
189
|
onMouseEnter: () => v?.(e),
|
|
190
190
|
onMouseLeave: () => v?.(null),
|
|
191
191
|
className: m("cursor-pointer transition-[filter] duration-150 ease-out", l),
|
|
192
|
-
children: /* @__PURE__ */
|
|
192
|
+
children: /* @__PURE__ */ h(M.Provider, {
|
|
193
193
|
value: x,
|
|
194
194
|
children: typeof u == "function" ? u(x) : u
|
|
195
195
|
})
|
|
@@ -198,36 +198,36 @@ function I({ id: e, longitude: t, latitude: i, label: a, anchor: o = "bottom", f
|
|
|
198
198
|
}
|
|
199
199
|
//#endregion
|
|
200
200
|
//#region src/map-pin.tsx
|
|
201
|
-
var
|
|
202
|
-
function
|
|
201
|
+
var I = m("[--map-pin-bg:#252931]", "[--map-pin-bg-selected:#0D212B]", "[--map-pin-overlay-hover:rgba(187,195,204,0.06)]", "[--map-pin-overlay-selected:rgba(187,195,204,0.12)]", "[--map-pin-fg:#BBC3CC]", "[--map-pin-fg-expanded:#E4E8EB]", "[--map-pin-fg-selected:var(--color-primary-main)]", "[--map-pin-divider:rgba(187,195,204,0.6)]", "[--map-pin-divider-selected:color-mix(in_srgb,var(--color-primary-main)_40%,transparent)]");
|
|
202
|
+
function L(e, t) {
|
|
203
203
|
return `linear-gradient(${e}, ${e}), ${t}`;
|
|
204
204
|
}
|
|
205
|
-
function
|
|
206
|
-
let s =
|
|
207
|
-
return /* @__PURE__ */
|
|
205
|
+
function R({ label: e, value: t, isSelected: n, isHovered: r, className: i, classNames: a, "data-testid": o = "map-pin" }) {
|
|
206
|
+
let s = N(), c = n ?? s?.isSelected ?? !1, l = r ?? s?.isHovered ?? !1, u = c || l, d = t != null && t !== "", f = u || !d, _ = c ? L("var(--map-pin-overlay-selected)", "var(--map-pin-bg-selected)") : l ? L("var(--map-pin-overlay-hover)", "var(--map-pin-bg)") : "var(--map-pin-bg)", v = c ? "text-[color:var(--map-pin-fg-selected)]" : u ? "text-[color:var(--map-pin-fg-expanded)]" : "text-[color:var(--map-pin-fg)]";
|
|
207
|
+
return /* @__PURE__ */ g("div", {
|
|
208
208
|
"data-testid": o,
|
|
209
209
|
"data-selected": c || void 0,
|
|
210
210
|
"data-hovered": l || void 0,
|
|
211
|
-
className: m(
|
|
212
|
-
children: [/* @__PURE__ */
|
|
211
|
+
className: m(I, "flex flex-col items-center isolate drop-shadow-[0px_1px_2px_rgba(228,232,235,0.3)]", i, a?.root),
|
|
212
|
+
children: [/* @__PURE__ */ h("div", {
|
|
213
213
|
className: m("relative z-2 flex h-[20px] w-full items-center overflow-hidden rounded-[8px] px-4 -mb-1.25", a?.pill),
|
|
214
|
-
style: { background:
|
|
215
|
-
children: /* @__PURE__ */
|
|
214
|
+
style: { background: _ },
|
|
215
|
+
children: /* @__PURE__ */ g("div", {
|
|
216
216
|
className: "flex items-center px-4",
|
|
217
|
-
children: [d && /* @__PURE__ */
|
|
217
|
+
children: [d && /* @__PURE__ */ h(p, {
|
|
218
218
|
variant: "caption",
|
|
219
219
|
className: m("shrink-0 whitespace-nowrap leading-[1.3]", v, a?.value),
|
|
220
220
|
children: t
|
|
221
|
-
}), /* @__PURE__ */
|
|
221
|
+
}), /* @__PURE__ */ h("div", {
|
|
222
222
|
className: m("grid self-stretch transition-[grid-template-columns] duration-200 ease-out motion-reduce:transition-none", f ? "grid-cols-[1fr]" : "grid-cols-[0fr]"),
|
|
223
|
-
children: /* @__PURE__ */
|
|
223
|
+
children: /* @__PURE__ */ h("div", {
|
|
224
224
|
className: "flex min-w-0 items-center overflow-hidden",
|
|
225
|
-
children: /* @__PURE__ */
|
|
225
|
+
children: /* @__PURE__ */ g("div", {
|
|
226
226
|
className: m("flex w-max items-center gap-4", d && "pl-4"),
|
|
227
|
-
children: [d && /* @__PURE__ */
|
|
227
|
+
children: [d && /* @__PURE__ */ h("span", {
|
|
228
228
|
"aria-hidden": "true",
|
|
229
229
|
className: m("w-px self-stretch shrink-0", c ? "bg-[color:var(--map-pin-divider-selected)]" : "bg-[color:var(--map-pin-divider)]", a?.divider)
|
|
230
|
-
}), /* @__PURE__ */
|
|
230
|
+
}), /* @__PURE__ */ h(p, {
|
|
231
231
|
variant: "caption",
|
|
232
232
|
className: m("max-w-[84px] truncate whitespace-nowrap leading-[1.3] transition-opacity duration-200 ease-out motion-reduce:transition-none", f ? "opacity-100" : "opacity-0", v, a?.label),
|
|
233
233
|
children: e
|
|
@@ -236,16 +236,16 @@ function z({ label: e, value: t, isSelected: n, isHovered: r, className: i, clas
|
|
|
236
236
|
})
|
|
237
237
|
})]
|
|
238
238
|
})
|
|
239
|
-
}), /* @__PURE__ */
|
|
239
|
+
}), /* @__PURE__ */ h("div", {
|
|
240
240
|
className: "relative z-1 flex w-full items-center justify-center",
|
|
241
241
|
"aria-hidden": "true",
|
|
242
|
-
children: /* @__PURE__ */
|
|
242
|
+
children: /* @__PURE__ */ h("svg", {
|
|
243
243
|
width: 8,
|
|
244
244
|
height: 8,
|
|
245
245
|
viewBox: "0 0 8 8",
|
|
246
246
|
fill: "none",
|
|
247
247
|
className: m("size-8 shrink-0", a?.caret),
|
|
248
|
-
children: /* @__PURE__ */
|
|
248
|
+
children: /* @__PURE__ */ h("path", {
|
|
249
249
|
d: "M4 8L0 0H8L4 8Z",
|
|
250
250
|
fill: c ? "var(--map-pin-bg-selected)" : "var(--map-pin-bg)"
|
|
251
251
|
})
|
|
@@ -255,10 +255,10 @@ function z({ label: e, value: t, isSelected: n, isHovered: r, className: i, clas
|
|
|
255
255
|
}
|
|
256
256
|
//#endregion
|
|
257
257
|
//#region src/user-location-pin.tsx
|
|
258
|
-
function
|
|
259
|
-
return /* @__PURE__ */
|
|
258
|
+
function z({ className: e, label: t = "My location" }) {
|
|
259
|
+
return /* @__PURE__ */ h(f, {
|
|
260
260
|
tooltip: t,
|
|
261
|
-
children: /* @__PURE__ */
|
|
261
|
+
children: /* @__PURE__ */ h("div", {
|
|
262
262
|
"data-testid": "user-location-pin",
|
|
263
263
|
role: "img",
|
|
264
264
|
"aria-label": t,
|
|
@@ -268,16 +268,16 @@ function B({ className: e, label: t = "My location" }) {
|
|
|
268
268
|
}
|
|
269
269
|
//#endregion
|
|
270
270
|
//#region src/map-user-location.tsx
|
|
271
|
-
function
|
|
272
|
-
return e ? /* @__PURE__ */
|
|
271
|
+
function B({ coordinates: e, children: t, label: n }) {
|
|
272
|
+
return e ? /* @__PURE__ */ h(c, {
|
|
273
273
|
longitude: e.longitude,
|
|
274
274
|
latitude: e.latitude,
|
|
275
275
|
anchor: "center",
|
|
276
276
|
style: { zIndex: 3 },
|
|
277
|
-
children: t ?? /* @__PURE__ */
|
|
277
|
+
children: t ?? /* @__PURE__ */ h(z, { label: n })
|
|
278
278
|
}) : null;
|
|
279
279
|
}
|
|
280
280
|
//#endregion
|
|
281
|
-
export {
|
|
281
|
+
export { D as BaseMap, v as DEFAULT_FIT_BOUNDS_PADDING, _ as DEFAULT_MAP_STYLE_URL, a as Layer, F as MapMarker, R as MapPin, B as MapUserLocation, j as MapView, s as Marker, y as SINGLE_POINT_ZOOM, l as Source, z as UserLocationPin, w as getBoundsCentroid, C as getBoundsFromPoints, T as isDegenerateBounds, S as loadMapLib, b as resolveMapStyleUrl, u as useMap, N as useMapMarker, A as useMapView };
|
|
282
282
|
|
|
283
283
|
//# sourceMappingURL=index.es.js.map
|
package/dist/index.es.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.es.js","names":["cn","MapGL","Marker","Marker","Marker$1","cn$1","DEFAULT_MAP_STYLE_URL","DEFAULT_FIT_BOUNDS_PADDING","SINGLE_POINT_ZOOM","resolveMapStyleUrl","mapStyleUrl","mapLib","loadMapLib","lib","workerSource","blob","getBoundsFromPoints","points","minLng","minLat","maxLng","maxLat","lng","lat","getBoundsCentroid","bounds","isDegenerateBounds","fitMapToBounds","map","BaseMap","externalMapRef","internalMapRef","useRef","mapRef","resolvedMapStyleUrl","useEffect","fitBoundsPadding","testId","isLoading","hasError","className","ariaLabel","BaseMapErrorLayer","errorLabel","Map","ErrorIcon","Typography","label","MapViewContext","createContext","useMapView","context","useContext","MapView","baseMapProps","useMemo","fitPoints","initialViewState","value","selectedId","hoveredId","flyToSelected","onSelect","onHover","MapMarkerContext","useMapMarker","getMarkerZIndex","isHovered","isSelected","MapMarker","id","state","shouldFlyTo","flyToOnSelect","longitude","latitude","e","cn","children","PIN_PALETTE","overlay","color","base","MapPin","isSelectedProp","isHoveredProp","dataTestId","marker","isExpanded","hasValue","showLabel","pillBg","textClassName","classNames","UserLocationPin","Tooltip","MapUserLocation","coordinates","Layer","Marker","Source","useMap"],"sources":["../src/constants.ts","../src/load-map-lib.ts","../src/utils/get-bounds-from-points.ts","../src/base-map.tsx","../src/map-context.tsx","../src/map-view.tsx","../src/map-marker.tsx","../src/map-pin.tsx","../src/user-location-pin.tsx","../src/map-user-location.tsx"],"sourcesContent":["export const DEFAULT_MAP_STYLE_URL = 'https://tiles.openfreemap.org/styles/liberty';\nexport const DEFAULT_FIT_BOUNDS_PADDING = 48;\nexport const SINGLE_POINT_ZOOM = 15;\n\n/**\n * No `import.meta.env` fallback: Vite replaces it when this library is built, so a\n * consumer's `VITE_MAP_STYLE_URL` would never be read. Pass `mapStyleUrl` instead.\n */\nexport function resolveMapStyleUrl(mapStyleUrl?: string): string {\n return mapStyleUrl ?? DEFAULT_MAP_STYLE_URL;\n}\n","import type * as maplibre from 'maplibre-gl';\n\ntype MapLib = typeof maplibre;\n\nlet mapLib: Promise<MapLib> | undefined;\n\n/**\n * maplibre-gl, with its worker pointed at a blob URL built from the source this\n * package ships (see `vite-plugin-maplibre-worker.ts`). Pass it to `<Map mapLib>`.\n *\n * Both halves are dynamic imports, so the ~500 kB worker source is fetched with\n * the first map rather than with every import of the package. Memoised: every\n * map shares one worker URL.\n */\nexport function loadMapLib(): Promise<MapLib> {\n mapLib ??= Promise.all([import('maplibre-gl'), import('./worker/maplibre-worker-source')]).then(\n ([lib, { default: workerSource }]) => {\n const blob = new Blob([workerSource], { type: 'text/javascript' });\n lib.setWorkerUrl(URL.createObjectURL(blob));\n return lib;\n },\n );\n\n return mapLib;\n}\n","export type MapLngLat = [number, number];\nexport type MapBounds = [MapLngLat, MapLngLat];\n\n/**\n * Computes a southwest/northeast bounding box from lng/lat points.\n * Returns null when there are no points.\n */\nexport function getBoundsFromPoints(points: MapLngLat[]): MapBounds | null {\n if (points.length === 0) {\n return null;\n }\n\n let minLng = points[0][0];\n let minLat = points[0][1];\n let maxLng = points[0][0];\n let maxLat = points[0][1];\n\n for (const [lng, lat] of points) {\n minLng = Math.min(minLng, lng);\n minLat = Math.min(minLat, lat);\n maxLng = Math.max(maxLng, lng);\n maxLat = Math.max(maxLat, lat);\n }\n\n return [\n [minLng, minLat],\n [maxLng, maxLat],\n ];\n}\n\nexport function getBoundsCentroid(bounds: MapBounds): MapLngLat {\n const [[minLng, minLat], [maxLng, maxLat]] = bounds;\n return [(minLng + maxLng) / 2, (minLat + maxLat) / 2];\n}\n\nexport function isDegenerateBounds(bounds: MapBounds): boolean {\n const [[minLng, minLat], [maxLng, maxLat]] = bounds;\n return minLng === maxLng && minLat === maxLat;\n}\n","import { Map as MapGL } from '@vis.gl/react-maplibre';\nimport type { MapRef } from '@vis.gl/react-maplibre';\nimport type { Map as MapLibreMap } from 'maplibre-gl';\nimport { useEffect, useRef, type ReactNode, type RefObject } from 'react';\n\nimport { ErrorIcon } from '@swisper.ai/icons';\nimport { Typography } from '@swisper.ai/ui';\nimport { cn } from '@swisper.ai/web-core';\n\nimport { DEFAULT_FIT_BOUNDS_PADDING, resolveMapStyleUrl, SINGLE_POINT_ZOOM } from './constants';\nimport { loadMapLib } from './load-map-lib';\nimport { isDegenerateBounds, type MapBounds, type MapLngLat } from './utils/get-bounds-from-points';\n\nimport 'maplibre-gl/dist/maplibre-gl.css';\n\ninterface BaseMapProps {\n children?: ReactNode;\n className?: string;\n testId: string;\n ariaLabel: string;\n hasError?: boolean;\n isLoading?: boolean;\n initialViewState: { center: MapLngLat | null; zoom: number };\n bounds?: MapBounds | null;\n fitBoundsPadding?: number;\n mapStyleUrl?: string;\n mapRef?: RefObject<MapRef>;\n onLoad?: () => void;\n /**\n * Shown when `hasError` is set. A prop rather than a translation lookup, so the\n * package does not tie consumers to one i18n library or namespace.\n */\n errorLabel?: string;\n}\n\nfunction fitMapToBounds(map: MapLibreMap, bounds: MapBounds, padding: number) {\n if (isDegenerateBounds(bounds)) {\n const [[lng, lat]] = bounds;\n map.setCenter([lng, lat]);\n map.setZoom(SINGLE_POINT_ZOOM);\n return;\n }\n\n map.fitBounds(bounds, { padding, duration: 0 });\n}\n\nexport function BaseMap({\n children,\n className,\n testId,\n ariaLabel,\n hasError,\n isLoading,\n initialViewState,\n bounds = null,\n fitBoundsPadding = DEFAULT_FIT_BOUNDS_PADDING,\n mapStyleUrl,\n mapRef: externalMapRef,\n onLoad,\n errorLabel = 'Could not load map. Try again later.',\n}: BaseMapProps) {\n const internalMapRef = useRef<MapRef>(null);\n const mapRef = externalMapRef ?? internalMapRef;\n const resolvedMapStyleUrl = resolveMapStyleUrl(mapStyleUrl);\n\n useEffect(() => {\n if (!bounds) {\n return;\n }\n\n const map = mapRef.current?.getMap();\n if (!map) {\n return;\n }\n\n fitMapToBounds(map, bounds, fitBoundsPadding);\n }, [bounds, fitBoundsPadding, mapRef]);\n\n return (\n <div\n data-testid={testId}\n data-rounded={true}\n data-loading={isLoading}\n aria-invalid={hasError}\n data-error={hasError}\n className={cn(\n 'relative size-full',\n 'bg-[#f8f4f1]',\n 'data-[error=true]:bg-[#EB0000]/10',\n 'data-[rounded=true]:rounded-lg overflow-hidden',\n className,\n )}\n role=\"application\"\n aria-label={ariaLabel}\n >\n {hasError && <BaseMapErrorLayer label={errorLabel} />}\n\n <MapGL\n ref={mapRef}\n mapLib={loadMapLib()}\n initialViewState={initialViewState}\n mapStyle={resolvedMapStyleUrl}\n style={{\n width: '100%',\n height: '100%',\n opacity: isLoading || hasError ? 0 : 1,\n pointerEvents: hasError ? 'none' : 'auto',\n }}\n attributionControl={false}\n onLoad={() => {\n if (bounds) {\n const map = mapRef.current?.getMap();\n if (map) {\n fitMapToBounds(map, bounds, fitBoundsPadding);\n }\n }\n onLoad?.();\n }}\n >\n {children}\n </MapGL>\n </div>\n );\n}\n\nfunction BaseMapErrorLayer({ label }: { label: string }) {\n return (\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <div className=\"flex flex-col items-center gap-8\">\n <ErrorIcon fontSize=\"large\" />\n <Typography variant=\"body-sm\" className=\"text-text-error\">\n {label}\n </Typography>\n </div>\n </div>\n );\n}\n","import type { MapRef } from '@vis.gl/react-maplibre';\nimport { createContext, useContext, type RefObject } from 'react';\n\nexport type MapItemId = string | number;\n\nexport interface MapViewContextValue {\n mapRef: RefObject<MapRef>;\n selectedId: MapItemId | null;\n hoveredId: MapItemId | null;\n flyToSelected: boolean;\n onSelect?: (id: MapItemId) => void;\n onHover?: (id: MapItemId | null) => void;\n}\n\nexport const MapViewContext = createContext<MapViewContextValue | null>(null);\n\nexport function useMapView() {\n const context = useContext(MapViewContext);\n\n if (!context) {\n throw new Error('useMapView must be used within a MapView component');\n }\n\n return context;\n}\n","import type { MapRef } from '@vis.gl/react-maplibre';\nimport { useMemo, useRef, type ReactNode } from 'react';\n\nimport { BaseMap } from './base-map';\nimport { MapViewContext, type MapItemId, type MapViewContextValue } from './map-context';\nimport {\n getBoundsCentroid,\n getBoundsFromPoints,\n type MapLngLat,\n} from './utils/get-bounds-from-points';\n\nexport interface MapViewProps<TId extends MapItemId = MapItemId> {\n /** Points the map frames on load and whenever they change. */\n fitPoints: MapLngLat[];\n selectedId?: TId | null;\n hoveredId?: TId | null;\n onSelect?: (id: TId) => void;\n onHover?: (id: TId | null) => void;\n /** Ease to a marker when it becomes selected, so it is never off screen. */\n flyToSelected?: boolean;\n children?: ReactNode;\n testId: string;\n ariaLabel: string;\n className?: string;\n hasError?: boolean;\n isLoading?: boolean;\n fitBoundsPadding?: number;\n mapStyleUrl?: string;\n onLoad?: () => void;\n /** Shown when `hasError` is set; pass a translated string. */\n errorLabel?: string;\n}\n\n/**\n * Interactive map root: frames `fitPoints` and shares selection / hover state\n * with the `MapMarker`s composed inside it.\n */\nexport function MapView<TId extends MapItemId = MapItemId>({\n fitPoints,\n selectedId = null,\n hoveredId = null,\n onSelect,\n onHover,\n flyToSelected = true,\n children,\n ...baseMapProps\n}: MapViewProps<TId>) {\n const mapRef = useRef<MapRef>(null);\n\n const bounds = useMemo(() => getBoundsFromPoints(fitPoints), [fitPoints]);\n\n const initialViewState = useMemo(() => {\n if (bounds) {\n // With a bounds fit called on load/change we still need an initial\n // center or MapGL defaults to null-island. Use the geographic centroid.\n return { center: getBoundsCentroid(bounds), zoom: 12 };\n }\n return { center: [0, 0] as MapLngLat, zoom: 2 };\n }, [bounds]);\n\n const value = useMemo<MapViewContextValue>(\n () => ({\n mapRef,\n selectedId,\n hoveredId,\n flyToSelected,\n // Markers deal in the widened id type; the caller only ever gets back ids it passed in.\n onSelect: onSelect as MapViewContextValue['onSelect'],\n onHover: onHover as MapViewContextValue['onHover'],\n }),\n [selectedId, hoveredId, flyToSelected, onSelect, onHover],\n );\n\n return (\n <MapViewContext.Provider value={value}>\n <BaseMap\n {...baseMapProps}\n initialViewState={initialViewState}\n bounds={bounds}\n mapRef={mapRef}\n >\n {children}\n </BaseMap>\n </MapViewContext.Provider>\n );\n}\n","import { Marker } from '@vis.gl/react-maplibre';\nimport { createContext, useContext, useEffect, useMemo, type ReactNode } from 'react';\n\nimport { cn } from '@swisper.ai/ui';\n\nimport { useMapView, type MapItemId } from './map-context';\n\nexport interface MapMarkerState {\n isSelected: boolean;\n isHovered: boolean;\n}\n\nconst MapMarkerContext = createContext<MapMarkerState | null>(null);\n\n/** Selection state of the enclosing `MapMarker`, or null outside of one. */\nexport function useMapMarker() {\n return useContext(MapMarkerContext);\n}\n\nexport interface MapMarkerProps {\n id: MapItemId;\n longitude: number;\n latitude: number;\n /** Accessible name of the marker button. */\n label: string;\n anchor?: 'center' | 'top' | 'bottom' | 'left' | 'right';\n /** Ease to this marker when selected; defaults to the `MapView`'s `flyToSelected`. */\n flyToOnSelect?: boolean;\n className?: string;\n children: ReactNode | ((state: MapMarkerState) => ReactNode);\n}\n\nfunction getMarkerZIndex({ isHovered, isSelected }: MapMarkerState) {\n if (isHovered) return 3;\n if (isSelected) return 2;\n return 1;\n}\n\n/**\n * A selectable, hoverable point on a `MapView`. Renders any pin as its child —\n * `MapPin` by default, or a render function receiving the marker state.\n */\nexport function MapMarker({\n id,\n longitude,\n latitude,\n label,\n anchor = 'bottom',\n flyToOnSelect,\n className,\n children,\n}: MapMarkerProps) {\n const { mapRef, selectedId, hoveredId, flyToSelected, onSelect, onHover } = useMapView();\n\n const isSelected = id === selectedId;\n const isHovered = id === hoveredId;\n const state = useMemo(() => ({ isSelected, isHovered }), [isSelected, isHovered]);\n\n // Fly to the selected pin so it's always in view when chosen from outside the map.\n const shouldFlyTo = flyToOnSelect ?? flyToSelected;\n\n useEffect(() => {\n if (!isSelected || !shouldFlyTo) return;\n const map = mapRef.current?.getMap();\n if (!map) return;\n map.easeTo({ center: [longitude, latitude], duration: 400 });\n }, [isSelected, shouldFlyTo, longitude, latitude, mapRef]);\n\n return (\n <Marker\n longitude={longitude}\n latitude={latitude}\n anchor={anchor}\n style={{ zIndex: getMarkerZIndex(state) }}\n onClick={(e) => {\n // Prevent the map's click handler from firing and deselecting.\n e.originalEvent.stopPropagation();\n onSelect?.(id);\n }}\n >\n <button\n type=\"button\"\n aria-label={label}\n aria-pressed={isSelected}\n data-selected={isSelected || undefined}\n data-hovered={isHovered || undefined}\n onMouseEnter={() => onHover?.(id)}\n onMouseLeave={() => onHover?.(null)}\n className={cn('cursor-pointer transition-[filter] duration-150 ease-out', className)}\n >\n <MapMarkerContext.Provider value={state}>\n {typeof children === 'function' ? children(state) : children}\n </MapMarkerContext.Provider>\n </button>\n </Marker>\n );\n}\n","import type { ReactNode } from 'react';\n\nimport { Typography, cn } from '@swisper.ai/ui';\n\nimport { useMapMarker } from './map-marker';\n\n/**\n * Pin palette as CSS custom properties so a caller can restyle it with a class,\n * e.g. `className=\"[--map-pin-bg:#fff] [--map-pin-fg:#000]\"`.\n * Defaults are the Figma PlacePointer values (brand/secondary/700, selected\n * Pill #0D212B + 12% secondary overlay, hover 6% overlay).\n */\nconst PIN_PALETTE = cn(\n '[--map-pin-bg:#252931]',\n '[--map-pin-bg-selected:#0D212B]',\n '[--map-pin-overlay-hover:rgba(187,195,204,0.06)]',\n '[--map-pin-overlay-selected:rgba(187,195,204,0.12)]',\n '[--map-pin-fg:#BBC3CC]',\n '[--map-pin-fg-expanded:#E4E8EB]',\n '[--map-pin-fg-selected:var(--color-primary-main)]',\n '[--map-pin-divider:rgba(187,195,204,0.6)]',\n '[--map-pin-divider-selected:color-mix(in_srgb,var(--color-primary-main)_40%,transparent)]',\n);\n\nfunction overlay(color: string, base: string) {\n return `linear-gradient(${color}, ${color}), ${base}`;\n}\n\nexport interface MapPinClassNames {\n root?: string;\n pill?: string;\n value?: string;\n divider?: string;\n label?: string;\n caret?: string;\n}\n\nexport interface MapPinProps {\n /** Shown on hover / selection, or always when there is no `value`. */\n label: string;\n /** Short lead text always shown in the pill, e.g. a rating or a price. */\n value?: ReactNode;\n /** Defaults to the enclosing `MapMarker`'s state. */\n isSelected?: boolean;\n /** Defaults to the enclosing `MapMarker`'s state. */\n isHovered?: boolean;\n className?: string;\n classNames?: MapPinClassNames;\n 'data-testid'?: string;\n}\n\n/**\n * Map callout pin: `value`-only by default; expands to `value | label` on\n * hover/selection (Figma PlacePointer Default / Hover / selected Pill).\n */\nexport function MapPin({\n label,\n value,\n isSelected: isSelectedProp,\n isHovered: isHoveredProp,\n className,\n classNames,\n 'data-testid': dataTestId = 'map-pin',\n}: MapPinProps) {\n const marker = useMapMarker();\n const isSelected = isSelectedProp ?? marker?.isSelected ?? false;\n const isHovered = isHoveredProp ?? marker?.isHovered ?? false;\n\n const isExpanded = isSelected || isHovered;\n const hasValue = value != null && value !== '';\n const showLabel = isExpanded || !hasValue;\n\n const pillBg = isSelected\n ? overlay('var(--map-pin-overlay-selected)', 'var(--map-pin-bg-selected)')\n : isHovered\n ? overlay('var(--map-pin-overlay-hover)', 'var(--map-pin-bg)')\n : 'var(--map-pin-bg)';\n\n const textClassName = isSelected\n ? 'text-[color:var(--map-pin-fg-selected)]'\n : isExpanded\n ? 'text-[color:var(--map-pin-fg-expanded)]'\n : 'text-[color:var(--map-pin-fg)]';\n\n return (\n <div\n data-testid={dataTestId}\n data-selected={isSelected || undefined}\n data-hovered={isHovered || undefined}\n className={cn(\n PIN_PALETTE,\n 'flex flex-col items-center isolate drop-shadow-[0px_1px_2px_rgba(228,232,235,0.3)]',\n className,\n classNames?.root,\n )}\n >\n <div\n className={cn(\n 'relative z-2 flex h-[20px] w-full items-center overflow-hidden rounded-[8px] px-4 -mb-1.25',\n classNames?.pill,\n )}\n style={{ background: pillBg }}\n >\n <div className=\"flex items-center px-4\">\n {hasValue && (\n <Typography\n variant=\"caption\"\n className={cn(\n 'shrink-0 whitespace-nowrap leading-[1.3]',\n textClassName,\n classNames?.value,\n )}\n >\n {value}\n </Typography>\n )}\n\n {/* 0fr → 1fr animates to the label's intrinsic width; the inner\n `w-max` keeps it from reflowing while the column collapses. */}\n <div\n className={cn(\n 'grid self-stretch transition-[grid-template-columns] duration-200 ease-out motion-reduce:transition-none',\n showLabel ? 'grid-cols-[1fr]' : 'grid-cols-[0fr]',\n )}\n >\n <div className=\"flex min-w-0 items-center overflow-hidden\">\n <div className={cn('flex w-max items-center gap-4', hasValue && 'pl-4')}>\n {hasValue && (\n <span\n aria-hidden=\"true\"\n className={cn(\n 'w-px self-stretch shrink-0',\n isSelected\n ? 'bg-[color:var(--map-pin-divider-selected)]'\n : 'bg-[color:var(--map-pin-divider)]',\n classNames?.divider,\n )}\n />\n )}\n\n <Typography\n variant=\"caption\"\n className={cn(\n 'max-w-[84px] truncate whitespace-nowrap leading-[1.3] transition-opacity duration-200 ease-out motion-reduce:transition-none',\n showLabel ? 'opacity-100' : 'opacity-0',\n textClassName,\n classNames?.label,\n )}\n >\n {label}\n </Typography>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <div className=\"relative z-1 flex w-full items-center justify-center\" aria-hidden=\"true\">\n <svg\n width={8}\n height={8}\n viewBox=\"0 0 8 8\"\n fill=\"none\"\n className={cn('size-8 shrink-0', classNames?.caret)}\n >\n <path\n d=\"M4 8L0 0H8L4 8Z\"\n fill={isSelected ? 'var(--map-pin-bg-selected)' : 'var(--map-pin-bg)'}\n />\n </svg>\n </div>\n </div>\n );\n}\n","import { Tooltip, cn } from '@swisper.ai/ui';\n\nexport interface UserLocationPinProps {\n className?: string;\n /** Tooltip and accessible name; pass a translated string. */\n label?: string;\n}\n\n/** Map marker for the user's own position: a blue dot with a white ring. */\nexport function UserLocationPin({ className, label = 'My location' }: UserLocationPinProps) {\n return (\n <Tooltip tooltip={label}>\n <div\n data-testid=\"user-location-pin\"\n // The dot is a bare `div`, so without a label it is invisible to\n // screen readers — the tooltip only covers pointer users.\n role=\"img\"\n aria-label={label}\n className={cn(\n 'size-3.5 rounded-full bg-blue-500 ring-2 ring-white',\n 'shadow-[0px_1px_3px_rgba(0,0,0,0.35)]',\n className,\n )}\n />\n </Tooltip>\n );\n}\n","import { Marker } from '@vis.gl/react-maplibre';\nimport type { ReactNode } from 'react';\n\nimport { UserLocationPin } from './user-location-pin';\n\nexport interface MapUserLocationProps {\n coordinates: Pick<GeolocationCoordinates, 'longitude' | 'latitude'> | null | undefined;\n /** Custom marker; defaults to `UserLocationPin`. */\n children?: ReactNode;\n /** Label of the default `UserLocationPin`; ignored when `children` is set. */\n label?: string;\n}\n\n/** The user's own position on a `MapView`; renders nothing until it is known. */\nexport function MapUserLocation({ coordinates, children, label }: MapUserLocationProps) {\n if (!coordinates) return null;\n\n return (\n <Marker\n longitude={coordinates.longitude}\n latitude={coordinates.latitude}\n anchor=\"center\"\n // At the same level as the hovered and selected pins.\n style={{ zIndex: 3 }}\n >\n {children ?? <UserLocationPin label={label} />}\n </Marker>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.es.js","names":["MapGL","Marker","Marker","Marker$1","DEFAULT_MAP_STYLE_URL","DEFAULT_FIT_BOUNDS_PADDING","SINGLE_POINT_ZOOM","resolveMapStyleUrl","mapStyleUrl","mapLib","loadMapLib","lib","workerSource","blob","getBoundsFromPoints","points","minLng","minLat","maxLng","maxLat","lng","lat","getBoundsCentroid","bounds","isDegenerateBounds","fitMapToBounds","map","BaseMap","externalMapRef","internalMapRef","useRef","mapRef","resolvedMapStyleUrl","useEffect","fitBoundsPadding","testId","isLoading","hasError","cn","className","ariaLabel","BaseMapErrorLayer","errorLabel","Map","ErrorIcon","Typography","label","MapViewContext","createContext","useMapView","context","useContext","MapView","baseMapProps","useMemo","fitPoints","initialViewState","value","selectedId","hoveredId","flyToSelected","onSelect","onHover","MapMarkerContext","useMapMarker","getMarkerZIndex","isHovered","isSelected","MapMarker","id","state","shouldFlyTo","flyToOnSelect","longitude","latitude","e","children","PIN_PALETTE","overlay","color","base","MapPin","isSelectedProp","isHoveredProp","dataTestId","marker","isExpanded","hasValue","showLabel","pillBg","textClassName","classNames","UserLocationPin","Tooltip","MapUserLocation","coordinates","Layer","Marker","Source","useMap"],"sources":["../src/constants.ts","../src/load-map-lib.ts","../src/utils/get-bounds-from-points.ts","../src/base-map.tsx","../src/map-context.tsx","../src/map-view.tsx","../src/map-marker.tsx","../src/map-pin.tsx","../src/user-location-pin.tsx","../src/map-user-location.tsx"],"sourcesContent":["export const DEFAULT_MAP_STYLE_URL = 'https://tiles.openfreemap.org/styles/liberty';\nexport const DEFAULT_FIT_BOUNDS_PADDING = 48;\nexport const SINGLE_POINT_ZOOM = 15;\n\n/**\n * No `import.meta.env` fallback: Vite replaces it when this library is built, so a\n * consumer's `VITE_MAP_STYLE_URL` would never be read. Pass `mapStyleUrl` instead.\n */\nexport function resolveMapStyleUrl(mapStyleUrl?: string): string {\n return mapStyleUrl ?? DEFAULT_MAP_STYLE_URL;\n}\n","import type * as maplibre from 'maplibre-gl';\n\ntype MapLib = typeof maplibre;\n\nlet mapLib: Promise<MapLib> | undefined;\n\n/**\n * maplibre-gl, with its worker pointed at a blob URL built from the source this\n * package ships (see `vite-plugin-maplibre-worker.ts`). Pass it to `<Map mapLib>`.\n *\n * Both halves are dynamic imports, so the ~500 kB worker source is fetched with\n * the first map rather than with every import of the package. Memoised: every\n * map shares one worker URL.\n */\nexport function loadMapLib(): Promise<MapLib> {\n mapLib ??= Promise.all([import('maplibre-gl'), import('./worker/maplibre-worker-source')]).then(\n ([lib, { default: workerSource }]) => {\n const blob = new Blob([workerSource], { type: 'text/javascript' });\n lib.setWorkerUrl(URL.createObjectURL(blob));\n return lib;\n },\n );\n\n return mapLib;\n}\n","export type MapLngLat = [number, number];\nexport type MapBounds = [MapLngLat, MapLngLat];\n\n/**\n * Computes a southwest/northeast bounding box from lng/lat points.\n * Returns null when there are no points.\n */\nexport function getBoundsFromPoints(points: MapLngLat[]): MapBounds | null {\n if (points.length === 0) {\n return null;\n }\n\n let minLng = points[0][0];\n let minLat = points[0][1];\n let maxLng = points[0][0];\n let maxLat = points[0][1];\n\n for (const [lng, lat] of points) {\n minLng = Math.min(minLng, lng);\n minLat = Math.min(minLat, lat);\n maxLng = Math.max(maxLng, lng);\n maxLat = Math.max(maxLat, lat);\n }\n\n return [\n [minLng, minLat],\n [maxLng, maxLat],\n ];\n}\n\nexport function getBoundsCentroid(bounds: MapBounds): MapLngLat {\n const [[minLng, minLat], [maxLng, maxLat]] = bounds;\n return [(minLng + maxLng) / 2, (minLat + maxLat) / 2];\n}\n\nexport function isDegenerateBounds(bounds: MapBounds): boolean {\n const [[minLng, minLat], [maxLng, maxLat]] = bounds;\n return minLng === maxLng && minLat === maxLat;\n}\n","import { Map as MapGL } from '@vis.gl/react-maplibre';\nimport type { MapRef } from '@vis.gl/react-maplibre';\nimport type { Map as MapLibreMap } from 'maplibre-gl';\nimport { useEffect, useRef, type ReactNode, type RefObject } from 'react';\n\nimport { ErrorIcon } from '@swisper.ai/icons';\nimport { Typography } from '@swisper.ai/ui';\nimport { cn } from '@swisper.ai/web-core';\n\nimport { DEFAULT_FIT_BOUNDS_PADDING, resolveMapStyleUrl, SINGLE_POINT_ZOOM } from './constants';\nimport { loadMapLib } from './load-map-lib';\nimport { isDegenerateBounds, type MapBounds, type MapLngLat } from './utils/get-bounds-from-points';\n\nimport 'maplibre-gl/dist/maplibre-gl.css';\n\ninterface BaseMapProps {\n children?: ReactNode;\n className?: string;\n testId: string;\n ariaLabel: string;\n hasError?: boolean;\n isLoading?: boolean;\n initialViewState: { center: MapLngLat | null; zoom: number };\n bounds?: MapBounds | null;\n fitBoundsPadding?: number;\n mapStyleUrl?: string;\n mapRef?: RefObject<MapRef>;\n onLoad?: () => void;\n /**\n * Shown when `hasError` is set. A prop rather than a translation lookup, so the\n * package does not tie consumers to one i18n library or namespace.\n */\n errorLabel?: string;\n}\n\nfunction fitMapToBounds(map: MapLibreMap, bounds: MapBounds, padding: number) {\n if (isDegenerateBounds(bounds)) {\n const [[lng, lat]] = bounds;\n map.setCenter([lng, lat]);\n map.setZoom(SINGLE_POINT_ZOOM);\n return;\n }\n\n map.fitBounds(bounds, { padding, duration: 0 });\n}\n\nexport function BaseMap({\n children,\n className,\n testId,\n ariaLabel,\n hasError,\n isLoading,\n initialViewState,\n bounds = null,\n fitBoundsPadding = DEFAULT_FIT_BOUNDS_PADDING,\n mapStyleUrl,\n mapRef: externalMapRef,\n onLoad,\n errorLabel = 'Could not load map. Try again later.',\n}: BaseMapProps) {\n const internalMapRef = useRef<MapRef>(null);\n const mapRef = externalMapRef ?? internalMapRef;\n const resolvedMapStyleUrl = resolveMapStyleUrl(mapStyleUrl);\n\n useEffect(() => {\n if (!bounds) {\n return;\n }\n\n const map = mapRef.current?.getMap();\n if (!map) {\n return;\n }\n\n fitMapToBounds(map, bounds, fitBoundsPadding);\n }, [bounds, fitBoundsPadding, mapRef]);\n\n return (\n <div\n data-testid={testId}\n data-rounded={true}\n data-loading={isLoading}\n aria-invalid={hasError}\n data-error={hasError}\n className={cn(\n 'relative size-full',\n 'bg-[#f8f4f1]',\n 'data-[error=true]:bg-[#EB0000]/10',\n 'data-[rounded=true]:rounded-lg overflow-hidden',\n className,\n )}\n role=\"application\"\n aria-label={ariaLabel}\n >\n {hasError && <BaseMapErrorLayer label={errorLabel} />}\n\n <MapGL\n ref={mapRef}\n mapLib={loadMapLib()}\n initialViewState={initialViewState}\n mapStyle={resolvedMapStyleUrl}\n style={{\n width: '100%',\n height: '100%',\n opacity: isLoading || hasError ? 0 : 1,\n pointerEvents: hasError ? 'none' : 'auto',\n }}\n attributionControl={false}\n onLoad={() => {\n if (bounds) {\n const map = mapRef.current?.getMap();\n if (map) {\n fitMapToBounds(map, bounds, fitBoundsPadding);\n }\n }\n onLoad?.();\n }}\n >\n {children}\n </MapGL>\n </div>\n );\n}\n\nfunction BaseMapErrorLayer({ label }: { label: string }) {\n return (\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <div className=\"flex flex-col items-center gap-8\">\n <ErrorIcon fontSize=\"large\" />\n <Typography variant=\"body-sm\" className=\"text-text-error\">\n {label}\n </Typography>\n </div>\n </div>\n );\n}\n","import type { MapRef } from '@vis.gl/react-maplibre';\nimport { createContext, useContext, type RefObject } from 'react';\n\nexport type MapItemId = string | number;\n\nexport interface MapViewContextValue {\n mapRef: RefObject<MapRef>;\n selectedId: MapItemId | null;\n hoveredId: MapItemId | null;\n flyToSelected: boolean;\n onSelect?: (id: MapItemId) => void;\n onHover?: (id: MapItemId | null) => void;\n}\n\nexport const MapViewContext = createContext<MapViewContextValue | null>(null);\n\nexport function useMapView() {\n const context = useContext(MapViewContext);\n\n if (!context) {\n throw new Error('useMapView must be used within a MapView component');\n }\n\n return context;\n}\n","import type { MapRef } from '@vis.gl/react-maplibre';\nimport { useMemo, useRef, type ReactNode } from 'react';\n\nimport { BaseMap } from './base-map';\nimport { MapViewContext, type MapItemId, type MapViewContextValue } from './map-context';\nimport {\n getBoundsCentroid,\n getBoundsFromPoints,\n type MapLngLat,\n} from './utils/get-bounds-from-points';\n\nexport interface MapViewProps<TId extends MapItemId = MapItemId> {\n /** Points the map frames on load and whenever they change. */\n fitPoints: MapLngLat[];\n selectedId?: TId | null;\n hoveredId?: TId | null;\n onSelect?: (id: TId) => void;\n onHover?: (id: TId | null) => void;\n /** Ease to a marker when it becomes selected, so it is never off screen. */\n flyToSelected?: boolean;\n children?: ReactNode;\n testId: string;\n ariaLabel: string;\n className?: string;\n hasError?: boolean;\n isLoading?: boolean;\n fitBoundsPadding?: number;\n mapStyleUrl?: string;\n onLoad?: () => void;\n /** Shown when `hasError` is set; pass a translated string. */\n errorLabel?: string;\n}\n\n/**\n * Interactive map root: frames `fitPoints` and shares selection / hover state\n * with the `MapMarker`s composed inside it.\n */\nexport function MapView<TId extends MapItemId = MapItemId>({\n fitPoints,\n selectedId = null,\n hoveredId = null,\n onSelect,\n onHover,\n flyToSelected = true,\n children,\n ...baseMapProps\n}: MapViewProps<TId>) {\n const mapRef = useRef<MapRef>(null);\n\n const bounds = useMemo(() => getBoundsFromPoints(fitPoints), [fitPoints]);\n\n const initialViewState = useMemo(() => {\n if (bounds) {\n // With a bounds fit called on load/change we still need an initial\n // center or MapGL defaults to null-island. Use the geographic centroid.\n return { center: getBoundsCentroid(bounds), zoom: 12 };\n }\n return { center: [0, 0] as MapLngLat, zoom: 2 };\n }, [bounds]);\n\n const value = useMemo<MapViewContextValue>(\n () => ({\n mapRef,\n selectedId,\n hoveredId,\n flyToSelected,\n // Markers deal in the widened id type; the caller only ever gets back ids it passed in.\n onSelect: onSelect as MapViewContextValue['onSelect'],\n onHover: onHover as MapViewContextValue['onHover'],\n }),\n [selectedId, hoveredId, flyToSelected, onSelect, onHover],\n );\n\n return (\n <MapViewContext.Provider value={value}>\n <BaseMap\n {...baseMapProps}\n initialViewState={initialViewState}\n bounds={bounds}\n mapRef={mapRef}\n >\n {children}\n </BaseMap>\n </MapViewContext.Provider>\n );\n}\n","import { Marker } from '@vis.gl/react-maplibre';\nimport { createContext, useContext, useEffect, useMemo, type ReactNode } from 'react';\n\nimport { cn } from '@swisper.ai/web-core';\n\nimport { useMapView, type MapItemId } from './map-context';\n\nexport interface MapMarkerState {\n isSelected: boolean;\n isHovered: boolean;\n}\n\nconst MapMarkerContext = createContext<MapMarkerState | null>(null);\n\n/** Selection state of the enclosing `MapMarker`, or null outside of one. */\nexport function useMapMarker() {\n return useContext(MapMarkerContext);\n}\n\nexport interface MapMarkerProps {\n id: MapItemId;\n longitude: number;\n latitude: number;\n /** Accessible name of the marker button. */\n label: string;\n anchor?: 'center' | 'top' | 'bottom' | 'left' | 'right';\n /** Ease to this marker when selected; defaults to the `MapView`'s `flyToSelected`. */\n flyToOnSelect?: boolean;\n className?: string;\n children: ReactNode | ((state: MapMarkerState) => ReactNode);\n}\n\nfunction getMarkerZIndex({ isHovered, isSelected }: MapMarkerState) {\n if (isHovered) return 3;\n if (isSelected) return 2;\n return 1;\n}\n\n/**\n * A selectable, hoverable point on a `MapView`. Renders any pin as its child —\n * `MapPin` by default, or a render function receiving the marker state.\n */\nexport function MapMarker({\n id,\n longitude,\n latitude,\n label,\n anchor = 'bottom',\n flyToOnSelect,\n className,\n children,\n}: MapMarkerProps) {\n const { mapRef, selectedId, hoveredId, flyToSelected, onSelect, onHover } = useMapView();\n\n const isSelected = id === selectedId;\n const isHovered = id === hoveredId;\n const state = useMemo(() => ({ isSelected, isHovered }), [isSelected, isHovered]);\n\n // Fly to the selected pin so it's always in view when chosen from outside the map.\n const shouldFlyTo = flyToOnSelect ?? flyToSelected;\n\n useEffect(() => {\n if (!isSelected || !shouldFlyTo) return;\n const map = mapRef.current?.getMap();\n if (!map) return;\n map.easeTo({ center: [longitude, latitude], duration: 400 });\n }, [isSelected, shouldFlyTo, longitude, latitude, mapRef]);\n\n return (\n <Marker\n longitude={longitude}\n latitude={latitude}\n anchor={anchor}\n style={{ zIndex: getMarkerZIndex(state) }}\n onClick={(e) => {\n // Prevent the map's click handler from firing and deselecting.\n e.originalEvent.stopPropagation();\n onSelect?.(id);\n }}\n >\n <button\n type=\"button\"\n aria-label={label}\n aria-pressed={isSelected}\n data-selected={isSelected || undefined}\n data-hovered={isHovered || undefined}\n onMouseEnter={() => onHover?.(id)}\n onMouseLeave={() => onHover?.(null)}\n className={cn('cursor-pointer transition-[filter] duration-150 ease-out', className)}\n >\n <MapMarkerContext.Provider value={state}>\n {typeof children === 'function' ? children(state) : children}\n </MapMarkerContext.Provider>\n </button>\n </Marker>\n );\n}\n","import type { ReactNode } from 'react';\n\nimport { Typography } from '@swisper.ai/ui';\nimport { cn } from '@swisper.ai/web-core';\n\nimport { useMapMarker } from './map-marker';\n\n/**\n * Pin palette as CSS custom properties so a caller can restyle it with a class,\n * e.g. `className=\"[--map-pin-bg:#fff] [--map-pin-fg:#000]\"`.\n * Defaults are the Figma PlacePointer values (brand/secondary/700, selected\n * Pill #0D212B + 12% secondary overlay, hover 6% overlay).\n */\nconst PIN_PALETTE = cn(\n '[--map-pin-bg:#252931]',\n '[--map-pin-bg-selected:#0D212B]',\n '[--map-pin-overlay-hover:rgba(187,195,204,0.06)]',\n '[--map-pin-overlay-selected:rgba(187,195,204,0.12)]',\n '[--map-pin-fg:#BBC3CC]',\n '[--map-pin-fg-expanded:#E4E8EB]',\n '[--map-pin-fg-selected:var(--color-primary-main)]',\n '[--map-pin-divider:rgba(187,195,204,0.6)]',\n '[--map-pin-divider-selected:color-mix(in_srgb,var(--color-primary-main)_40%,transparent)]',\n);\n\nfunction overlay(color: string, base: string) {\n return `linear-gradient(${color}, ${color}), ${base}`;\n}\n\nexport interface MapPinClassNames {\n root?: string;\n pill?: string;\n value?: string;\n divider?: string;\n label?: string;\n caret?: string;\n}\n\nexport interface MapPinProps {\n /** Shown on hover / selection, or always when there is no `value`. */\n label: string;\n /** Short lead text always shown in the pill, e.g. a rating or a price. */\n value?: ReactNode;\n /** Defaults to the enclosing `MapMarker`'s state. */\n isSelected?: boolean;\n /** Defaults to the enclosing `MapMarker`'s state. */\n isHovered?: boolean;\n className?: string;\n classNames?: MapPinClassNames;\n 'data-testid'?: string;\n}\n\n/**\n * Map callout pin: `value`-only by default; expands to `value | label` on\n * hover/selection (Figma PlacePointer Default / Hover / selected Pill).\n */\nexport function MapPin({\n label,\n value,\n isSelected: isSelectedProp,\n isHovered: isHoveredProp,\n className,\n classNames,\n 'data-testid': dataTestId = 'map-pin',\n}: MapPinProps) {\n const marker = useMapMarker();\n const isSelected = isSelectedProp ?? marker?.isSelected ?? false;\n const isHovered = isHoveredProp ?? marker?.isHovered ?? false;\n\n const isExpanded = isSelected || isHovered;\n const hasValue = value != null && value !== '';\n const showLabel = isExpanded || !hasValue;\n\n const pillBg = isSelected\n ? overlay('var(--map-pin-overlay-selected)', 'var(--map-pin-bg-selected)')\n : isHovered\n ? overlay('var(--map-pin-overlay-hover)', 'var(--map-pin-bg)')\n : 'var(--map-pin-bg)';\n\n const textClassName = isSelected\n ? 'text-[color:var(--map-pin-fg-selected)]'\n : isExpanded\n ? 'text-[color:var(--map-pin-fg-expanded)]'\n : 'text-[color:var(--map-pin-fg)]';\n\n return (\n <div\n data-testid={dataTestId}\n data-selected={isSelected || undefined}\n data-hovered={isHovered || undefined}\n className={cn(\n PIN_PALETTE,\n 'flex flex-col items-center isolate drop-shadow-[0px_1px_2px_rgba(228,232,235,0.3)]',\n className,\n classNames?.root,\n )}\n >\n <div\n className={cn(\n 'relative z-2 flex h-[20px] w-full items-center overflow-hidden rounded-[8px] px-4 -mb-1.25',\n classNames?.pill,\n )}\n style={{ background: pillBg }}\n >\n <div className=\"flex items-center px-4\">\n {hasValue && (\n <Typography\n variant=\"caption\"\n className={cn(\n 'shrink-0 whitespace-nowrap leading-[1.3]',\n textClassName,\n classNames?.value,\n )}\n >\n {value}\n </Typography>\n )}\n\n {/* 0fr → 1fr animates to the label's intrinsic width; the inner\n `w-max` keeps it from reflowing while the column collapses. */}\n <div\n className={cn(\n 'grid self-stretch transition-[grid-template-columns] duration-200 ease-out motion-reduce:transition-none',\n showLabel ? 'grid-cols-[1fr]' : 'grid-cols-[0fr]',\n )}\n >\n <div className=\"flex min-w-0 items-center overflow-hidden\">\n <div className={cn('flex w-max items-center gap-4', hasValue && 'pl-4')}>\n {hasValue && (\n <span\n aria-hidden=\"true\"\n className={cn(\n 'w-px self-stretch shrink-0',\n isSelected\n ? 'bg-[color:var(--map-pin-divider-selected)]'\n : 'bg-[color:var(--map-pin-divider)]',\n classNames?.divider,\n )}\n />\n )}\n\n <Typography\n variant=\"caption\"\n className={cn(\n 'max-w-[84px] truncate whitespace-nowrap leading-[1.3] transition-opacity duration-200 ease-out motion-reduce:transition-none',\n showLabel ? 'opacity-100' : 'opacity-0',\n textClassName,\n classNames?.label,\n )}\n >\n {label}\n </Typography>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <div className=\"relative z-1 flex w-full items-center justify-center\" aria-hidden=\"true\">\n <svg\n width={8}\n height={8}\n viewBox=\"0 0 8 8\"\n fill=\"none\"\n className={cn('size-8 shrink-0', classNames?.caret)}\n >\n <path\n d=\"M4 8L0 0H8L4 8Z\"\n fill={isSelected ? 'var(--map-pin-bg-selected)' : 'var(--map-pin-bg)'}\n />\n </svg>\n </div>\n </div>\n );\n}\n","import { Tooltip } from '@swisper.ai/ui';\nimport { cn } from '@swisper.ai/web-core';\n\nexport interface UserLocationPinProps {\n className?: string;\n /** Tooltip and accessible name; pass a translated string. */\n label?: string;\n}\n\n/** Map marker for the user's own position: a blue dot with a white ring. */\nexport function UserLocationPin({ className, label = 'My location' }: UserLocationPinProps) {\n return (\n <Tooltip tooltip={label}>\n <div\n data-testid=\"user-location-pin\"\n // The dot is a bare `div`, so without a label it is invisible to\n // screen readers — the tooltip only covers pointer users.\n role=\"img\"\n aria-label={label}\n className={cn(\n 'size-3.5 rounded-full bg-blue-500 ring-2 ring-white',\n 'shadow-[0px_1px_3px_rgba(0,0,0,0.35)]',\n className,\n )}\n />\n </Tooltip>\n );\n}\n","import { Marker } from '@vis.gl/react-maplibre';\nimport type { ReactNode } from 'react';\n\nimport { UserLocationPin } from './user-location-pin';\n\nexport interface MapUserLocationProps {\n coordinates: Pick<GeolocationCoordinates, 'longitude' | 'latitude'> | null | undefined;\n /** Custom marker; defaults to `UserLocationPin`. */\n children?: ReactNode;\n /** Label of the default `UserLocationPin`; ignored when `children` is set. */\n label?: string;\n}\n\n/** The user's own position on a `MapView`; renders nothing until it is known. */\nexport function MapUserLocation({ coordinates, children, label }: MapUserLocationProps) {\n if (!coordinates) return null;\n\n return (\n <Marker\n longitude={coordinates.longitude}\n latitude={coordinates.latitude}\n anchor=\"center\"\n // At the same level as the hovered and selected pins.\n style={{ zIndex: 3 }}\n >\n {children ?? <UserLocationPin label={label} />}\n </Marker>\n );\n}\n"],"mappings":"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"}
|
package/dist/map-pin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"map-pin.d.ts","sourceRoot":"","sources":["../src/map-pin.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"map-pin.d.ts","sourceRoot":"","sources":["../src/map-pin.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AA6BvC,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,qDAAqD;IACrD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,qDAAqD;IACrD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAgB,MAAM,CAAC,EACrB,KAAK,EACL,KAAK,EACL,UAAU,EAAE,cAAc,EAC1B,SAAS,EAAE,aAAa,EACxB,SAAS,EACT,UAAU,EACV,aAAa,EAAE,UAAsB,GACtC,EAAE,WAAW,+BA8Gb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-location-pin.d.ts","sourceRoot":"","sources":["../src/user-location-pin.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"user-location-pin.d.ts","sourceRoot":"","sources":["../src/user-location-pin.tsx"],"names":[],"mappings":"AAGA,MAAM,WAAW,oBAAoB;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,4EAA4E;AAC5E,wBAAgB,eAAe,CAAC,EAAE,SAAS,EAAE,KAAqB,EAAE,EAAE,oBAAoB,+BAiBzF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swisper.ai/web-map",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "Interactive MapLibre maps with selectable markers for Swisper web apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -42,16 +42,16 @@
|
|
|
42
42
|
"tailwind.css"
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@swisper.ai/icons": "0.0.
|
|
46
|
-
"@swisper.ai/tailwind-config": "0.0.
|
|
47
|
-
"@swisper.ai/ui": "0.
|
|
45
|
+
"@swisper.ai/icons": "0.0.3",
|
|
46
|
+
"@swisper.ai/tailwind-config": "0.0.3",
|
|
47
|
+
"@swisper.ai/ui": "0.1.1",
|
|
48
48
|
"@swisper.ai/web-core": "0.0.2",
|
|
49
49
|
"@vis.gl/react-maplibre": "^8.1.3",
|
|
50
50
|
"maplibre-gl": "^6.4.1"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@fintama/vite-config": "0.
|
|
54
|
-
"@fintama/vitest-config": "0.
|
|
53
|
+
"@fintama/swisper-int-vite-config": "0.0.1",
|
|
54
|
+
"@fintama/swisper-int-vitest-config": "0.0.1",
|
|
55
55
|
"@storybook/react-vite": "10.6.0",
|
|
56
56
|
"@swisper.ai/int-eslint-config": "0.0.1",
|
|
57
57
|
"@swisper.ai/int-typescript-config": "0.0.1",
|
|
@@ -2,7 +2,7 @@ import { cleanup, render, screen } from '@testing-library/react';
|
|
|
2
2
|
import type { ReactNode } from 'react';
|
|
3
3
|
import { afterEach, describe, expect, test, vi } from 'vitest';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { withTooltip } from '@swisper.ai/ui';
|
|
6
6
|
|
|
7
7
|
import { MapUserLocation } from '../map-user-location';
|
|
8
8
|
|
|
@@ -26,11 +26,7 @@ describe('MapUserLocation', () => {
|
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
test('shows the default dot once the position is known', () => {
|
|
29
|
-
render(
|
|
30
|
-
<TooltipProvider>
|
|
31
|
-
<MapUserLocation coordinates={ZURICH} />
|
|
32
|
-
</TooltipProvider>,
|
|
33
|
-
);
|
|
29
|
+
render(withTooltip(() => <MapUserLocation coordinates={ZURICH} />));
|
|
34
30
|
|
|
35
31
|
expect(screen.getByRole('img')).not.toBeNull();
|
|
36
32
|
});
|
package/src/base-map.json
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"$schema": "../../int-eslint-config/schemas/component.schema.json",
|
|
3
3
|
"component": "base-map",
|
|
4
4
|
"purpose": "The bare MapLibre map surface with loading and error states, framing and style URL, but no selection context.",
|
|
5
|
+
"anatomy": ["import { BaseMap } from '@swisper.ai/web-map';", "", "<BaseMap />"],
|
|
5
6
|
"useWhen": [
|
|
6
7
|
"Drawing custom sources, layers or plain markers that need no shared selection or hover",
|
|
7
8
|
"Building a new map component that manages its own view state through mapRef",
|
package/src/map-marker.json
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"$schema": "../../int-eslint-config/schemas/component.schema.json",
|
|
3
3
|
"component": "map-marker",
|
|
4
4
|
"purpose": "A selectable, hoverable point on a MapView that wraps its pin in a button and passes the marker's selection and hover state down to it.",
|
|
5
|
+
"anatomy": ["import { MapMarker } from '@swisper.ai/web-map';", "", "<MapMarker />"],
|
|
5
6
|
"useWhen": [
|
|
6
7
|
"Placing a place, result or entity on a web-map/map-view that the user can select",
|
|
7
8
|
"A pin must change look when its place is selected or hovered, in the map or in a list beside it"
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
2
|
import { useState, type ReactNode } from 'react';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { withTooltip } from '@swisper.ai/ui';
|
|
5
|
+
import { cn } from '@swisper.ai/web-core';
|
|
5
6
|
|
|
6
7
|
import { PLACE_POINTS, PLACES } from './__storybook__/fixtures';
|
|
7
8
|
import { MapMarker, type MapMarkerProps, type MapMarkerState } from './map-marker';
|
|
@@ -59,12 +60,11 @@ const meta: Meta<MapMarkerStoryArgs> = {
|
|
|
59
60
|
tags: ['autodocs'],
|
|
60
61
|
decorators: [
|
|
61
62
|
(Story) => (
|
|
62
|
-
<
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
</div>
|
|
66
|
-
</TooltipProvider>
|
|
63
|
+
<div className="h-[600px] w-[900px]">
|
|
64
|
+
<Story />
|
|
65
|
+
</div>
|
|
67
66
|
),
|
|
67
|
+
withTooltip,
|
|
68
68
|
],
|
|
69
69
|
args: {
|
|
70
70
|
anchor: 'bottom',
|
package/src/map-marker.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Marker } from '@vis.gl/react-maplibre';
|
|
2
2
|
import { createContext, useContext, useEffect, useMemo, type ReactNode } from 'react';
|
|
3
3
|
|
|
4
|
-
import { cn } from '@swisper.ai/
|
|
4
|
+
import { cn } from '@swisper.ai/web-core';
|
|
5
5
|
|
|
6
6
|
import { useMapView, type MapItemId } from './map-context';
|
|
7
7
|
|
package/src/map-pin.json
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"$schema": "../../int-eslint-config/schemas/component.schema.json",
|
|
3
3
|
"component": "map-pin",
|
|
4
4
|
"purpose": "The default callout pin for a map marker: a pill showing a short value that expands to value and place name on hover or selection.",
|
|
5
|
+
"anatomy": ["import { MapPin } from '@swisper.ai/web-map';", "", "<MapPin />"],
|
|
5
6
|
"useWhen": [
|
|
6
7
|
"Marking a place on a web-map/map-view with a short value the user compares at a glance, such as a rating or a price",
|
|
7
8
|
"Marking named places where the name should appear only on hover or selection to keep the map uncluttered",
|
package/src/map-pin.tsx
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"$schema": "../../int-eslint-config/schemas/component.schema.json",
|
|
3
3
|
"component": "map-user-location",
|
|
4
4
|
"purpose": "Draws the user's own position on a map once it is known, with UserLocationPin as the default marker.",
|
|
5
|
+
"anatomy": ["import { MapUserLocation } from '@swisper.ai/web-map';", "", "<MapUserLocation />"],
|
|
5
6
|
"useWhen": [
|
|
6
7
|
"Showing where the user is relative to the places on a web-map/map-view",
|
|
7
8
|
"The position comes from navigator.geolocation or another source that may resolve late or not at all"
|