@dxos/react-ui-geo 0.8.4-main.ae835ea → 0.8.4-main.bc2380dfbc
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/LICENSE +102 -5
- package/dist/lib/browser/{countries-110m-ZM3ZIEFS.mjs → countries-110m-RE5RNRQG.mjs} +1 -1
- package/dist/lib/browser/data.mjs +4 -3
- package/dist/lib/browser/data.mjs.map +4 -4
- package/dist/lib/browser/index.mjs +355 -449
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/translations.mjs +19 -0
- package/dist/lib/browser/translations.mjs.map +7 -0
- package/dist/lib/node-esm/{countries-110m-3SFASWVD.mjs → countries-110m-4EDBXSFJ.mjs} +1 -1
- package/dist/lib/node-esm/data.mjs +5 -3
- package/dist/lib/node-esm/data.mjs.map +4 -4
- package/dist/lib/node-esm/index.mjs +355 -448
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/translations.mjs +21 -0
- package/dist/lib/node-esm/translations.mjs.map +7 -0
- package/dist/types/data/airports.d.ts +4 -4
- package/dist/types/data/airports.d.ts.map +1 -1
- package/dist/types/data/cities.d.ts.map +1 -1
- package/dist/types/data/countries-110m.d.ts.map +1 -1
- package/dist/types/data/countries-dots-3.d.ts.map +1 -1
- package/dist/types/data/countries-dots-4.d.ts.map +1 -1
- package/dist/types/src/components/Globe/Globe.d.ts +5 -3
- package/dist/types/src/components/Globe/Globe.d.ts.map +1 -1
- package/dist/types/src/components/Globe/Globe.stories.d.ts +6 -4
- package/dist/types/src/components/Globe/Globe.stories.d.ts.map +1 -1
- package/dist/types/src/components/Map/Map.d.ts +19 -5
- package/dist/types/src/components/Map/Map.d.ts.map +1 -1
- package/dist/types/src/components/Map/Map.stories.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Controls.d.ts.map +1 -1
- package/dist/types/src/hooks/context.d.ts +1 -3
- package/dist/types/src/hooks/context.d.ts.map +1 -1
- package/dist/types/src/hooks/useDrag.d.ts.map +1 -1
- package/dist/types/src/hooks/useGlobeZoomHandler.d.ts +1 -1
- package/dist/types/src/hooks/useGlobeZoomHandler.d.ts.map +1 -1
- package/dist/types/src/hooks/useMapZoomHandler.d.ts +1 -1
- package/dist/types/src/hooks/useMapZoomHandler.d.ts.map +1 -1
- package/dist/types/src/hooks/useSpinner.d.ts.map +1 -1
- package/dist/types/src/hooks/useTour.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +0 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +12 -0
- package/dist/types/src/translations.d.ts.map +1 -0
- package/dist/types/src/util/debug.d.ts.map +1 -1
- package/dist/types/src/util/inertia.d.ts.map +1 -1
- package/dist/types/src/util/path.d.ts.map +1 -1
- package/dist/types/src/util/render.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +42 -36
- package/src/components/Globe/Globe.stories.tsx +7 -8
- package/src/components/Globe/Globe.tsx +55 -29
- package/src/components/Map/Map.stories.tsx +9 -8
- package/src/components/Map/Map.tsx +70 -29
- package/src/components/Toolbar/Controls.tsx +12 -14
- package/src/hooks/context.tsx +5 -34
- package/src/hooks/useSpinner.ts +0 -1
- package/src/hooks/useTour.ts +1 -0
- package/src/index.ts +0 -1
- package/src/translations.ts +20 -0
- package/src/util/render.ts +0 -1
- package/dist/lib/browser/chunk-GMWLKTLN.mjs +0 -9
- package/dist/lib/browser/chunk-GMWLKTLN.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-JODBF4CC.mjs +0 -11
- package/dist/lib/node-esm/chunk-JODBF4CC.mjs.map +0 -7
- /package/dist/lib/browser/{countries-110m-ZM3ZIEFS.mjs.map → countries-110m-RE5RNRQG.mjs.map} +0 -0
- /package/dist/lib/node-esm/{countries-110m-3SFASWVD.mjs.map → countries-110m-4EDBXSFJ.mjs.map} +0 -0
package/package.json
CHANGED
|
@@ -1,43 +1,49 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-geo",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.bc2380dfbc",
|
|
4
4
|
"description": "Geo components.",
|
|
5
5
|
"homepage": "https://github.com/dxos",
|
|
6
6
|
"bugs": "https://github.com/dxos/issues",
|
|
7
|
-
"
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/dxos/dxos"
|
|
10
|
+
},
|
|
11
|
+
"license": "FSL-1.1-Apache-2.0",
|
|
8
12
|
"author": "DXOS.org",
|
|
9
13
|
"sideEffects": true,
|
|
10
14
|
"type": "module",
|
|
15
|
+
"imports": {
|
|
16
|
+
"#translations": "./src/translations.ts"
|
|
17
|
+
},
|
|
11
18
|
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"source": "./src/index.ts",
|
|
21
|
+
"types": "./dist/types/src/index.d.ts",
|
|
22
|
+
"browser": "./dist/lib/browser/index.mjs",
|
|
23
|
+
"node": "./dist/lib/node-esm/index.mjs"
|
|
24
|
+
},
|
|
12
25
|
"./data": {
|
|
13
26
|
"source": "./src/data.ts",
|
|
14
27
|
"types": "./dist/types/src/data.d.ts",
|
|
15
28
|
"browser": "./dist/lib/browser/data.mjs",
|
|
16
29
|
"node": "./dist/lib/node-esm/data.mjs"
|
|
17
30
|
},
|
|
18
|
-
"
|
|
19
|
-
"source": "./src/
|
|
20
|
-
"types": "./dist/types/src/
|
|
21
|
-
"browser": "./dist/lib/browser/
|
|
22
|
-
"node": "./dist/lib/node-esm/
|
|
31
|
+
"./translations": {
|
|
32
|
+
"source": "./src/translations.ts",
|
|
33
|
+
"types": "./dist/types/src/translations.d.ts",
|
|
34
|
+
"browser": "./dist/lib/browser/translations.mjs",
|
|
35
|
+
"node": "./dist/lib/node-esm/translations.mjs"
|
|
23
36
|
}
|
|
24
37
|
},
|
|
25
38
|
"types": "dist/types/src/index.d.ts",
|
|
26
|
-
"typesVersions": {
|
|
27
|
-
"*": {
|
|
28
|
-
"data": [
|
|
29
|
-
"dist/types/src/data.d.ts"
|
|
30
|
-
]
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
39
|
"files": [
|
|
34
40
|
"data",
|
|
35
41
|
"dist",
|
|
36
42
|
"src"
|
|
37
43
|
],
|
|
38
44
|
"dependencies": {
|
|
39
|
-
"@
|
|
40
|
-
"@radix-ui/react-context": "
|
|
45
|
+
"@radix-ui/react-compose-refs": "1.1.1",
|
|
46
|
+
"@radix-ui/react-context": "1.1.1",
|
|
41
47
|
"d3": "^7.9.0",
|
|
42
48
|
"d3-geo-projection": "^4.0.0",
|
|
43
49
|
"d3-hexbin": "^0.2.2",
|
|
@@ -49,39 +55,39 @@
|
|
|
49
55
|
"topojson-client": "^3.1.0",
|
|
50
56
|
"topojson-simplify": "^3.0.3",
|
|
51
57
|
"versor": "^0.2.0",
|
|
52
|
-
"@dxos/async": "0.8.4-main.
|
|
53
|
-
"@dxos/
|
|
54
|
-
"@dxos/
|
|
55
|
-
"@dxos/node-std": "0.8.4-main.
|
|
56
|
-
"@dxos/util": "0.8.4-main.
|
|
58
|
+
"@dxos/async": "0.8.4-main.bc2380dfbc",
|
|
59
|
+
"@dxos/log": "0.8.4-main.bc2380dfbc",
|
|
60
|
+
"@dxos/debug": "0.8.4-main.bc2380dfbc",
|
|
61
|
+
"@dxos/node-std": "0.8.4-main.bc2380dfbc",
|
|
62
|
+
"@dxos/util": "0.8.4-main.bc2380dfbc"
|
|
57
63
|
},
|
|
58
64
|
"devDependencies": {
|
|
59
|
-
"@react-three/drei": "^
|
|
60
|
-
"@react-three/fiber": "^9.
|
|
65
|
+
"@react-three/drei": "^10.7.7",
|
|
66
|
+
"@react-three/fiber": "^9.5.0",
|
|
61
67
|
"@types/d3": "^7.4.3",
|
|
62
68
|
"@types/geojson": "^7946.0.14",
|
|
63
69
|
"@types/leaflet": "^1.9.16",
|
|
64
|
-
"@types/react": "~19.2.
|
|
65
|
-
"@types/react-dom": "~19.2.
|
|
70
|
+
"@types/react": "~19.2.7",
|
|
71
|
+
"@types/react-dom": "~19.2.3",
|
|
66
72
|
"@types/three": "0.165.0",
|
|
67
73
|
"@types/topojson-client": "^3.1.4",
|
|
68
74
|
"@types/topojson-simplify": "^3.0.3",
|
|
69
75
|
"@types/topojson-specification": "^1.0.5",
|
|
70
76
|
"JSONStream": "^1.3.5",
|
|
71
77
|
"geojson2h3": "^1.2.0",
|
|
72
|
-
"leva": "^0.
|
|
73
|
-
"react": "~19.2.
|
|
74
|
-
"react-dom": "~19.2.
|
|
75
|
-
"three": "0.
|
|
76
|
-
"@dxos/
|
|
77
|
-
"@dxos/
|
|
78
|
-
"@dxos/react-ui": "0.8.4-main.
|
|
78
|
+
"leva": "^0.10.1",
|
|
79
|
+
"react": "~19.2.3",
|
|
80
|
+
"react-dom": "~19.2.3",
|
|
81
|
+
"three": "^0.178.0",
|
|
82
|
+
"@dxos/ui-theme": "0.8.4-main.bc2380dfbc",
|
|
83
|
+
"@dxos/storybook-utils": "0.8.4-main.bc2380dfbc",
|
|
84
|
+
"@dxos/react-ui": "0.8.4-main.bc2380dfbc"
|
|
79
85
|
},
|
|
80
86
|
"peerDependencies": {
|
|
81
|
-
"react": "
|
|
82
|
-
"react-dom": "
|
|
83
|
-
"@dxos/react-ui": "0.8.4-main.
|
|
84
|
-
"@dxos/
|
|
87
|
+
"react": "~19.2.3",
|
|
88
|
+
"react-dom": "~19.2.3",
|
|
89
|
+
"@dxos/react-ui": "0.8.4-main.bc2380dfbc",
|
|
90
|
+
"@dxos/ui-theme": "0.8.4-main.bc2380dfbc"
|
|
85
91
|
},
|
|
86
92
|
"publishConfig": {
|
|
87
93
|
"access": "public"
|
|
@@ -9,13 +9,12 @@ import React, { useMemo, useRef, useState } from 'react';
|
|
|
9
9
|
import { type Topology } from 'topojson-specification';
|
|
10
10
|
|
|
11
11
|
import { useAsyncState } from '@dxos/react-ui';
|
|
12
|
-
import { withTheme } from '@dxos/react-ui/testing';
|
|
12
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
13
13
|
|
|
14
14
|
import { type Vector, useDrag, useGlobeZoomHandler, useSpinner, useTour } from '../../hooks';
|
|
15
15
|
import { type LatLngLiteral } from '../../types';
|
|
16
16
|
import { type StyleSet, closestPoint } from '../../util';
|
|
17
17
|
import { type ControlProps } from '../Toolbar';
|
|
18
|
-
|
|
19
18
|
import { Globe, type GlobeCanvasProps, type GlobeController, type GlobeRootProps } from './Globe';
|
|
20
19
|
|
|
21
20
|
// TODO(burdon): Load from JSON at runtime?
|
|
@@ -128,7 +127,7 @@ const createTrip = (
|
|
|
128
127
|
);
|
|
129
128
|
};
|
|
130
129
|
|
|
131
|
-
type
|
|
130
|
+
type DefaultStoryProps = Pick<GlobeRootProps, 'zoom' | 'translation' | 'rotation'> &
|
|
132
131
|
Pick<GlobeCanvasProps, 'projection' | 'styles'> & {
|
|
133
132
|
drag?: boolean;
|
|
134
133
|
spin?: boolean;
|
|
@@ -137,7 +136,7 @@ type StoryProps = Pick<GlobeRootProps, 'zoom' | 'translation' | 'rotation'> &
|
|
|
137
136
|
};
|
|
138
137
|
|
|
139
138
|
const DefaultStory = ({
|
|
140
|
-
zoom:
|
|
139
|
+
zoom: zoomProp = 1,
|
|
141
140
|
translation,
|
|
142
141
|
rotation = [0, 0, 0],
|
|
143
142
|
projection,
|
|
@@ -146,7 +145,7 @@ const DefaultStory = ({
|
|
|
146
145
|
spin = false,
|
|
147
146
|
tour = false,
|
|
148
147
|
xAxis = false,
|
|
149
|
-
}:
|
|
148
|
+
}: DefaultStoryProps) => {
|
|
150
149
|
const controller = useRef<GlobeController>(null);
|
|
151
150
|
const [dots] = useAsyncState(async () => {
|
|
152
151
|
const points = (await import('../../../data/countries-dots-3.ts')).default;
|
|
@@ -203,13 +202,13 @@ const DefaultStory = ({
|
|
|
203
202
|
};
|
|
204
203
|
|
|
205
204
|
return (
|
|
206
|
-
<Globe.Root
|
|
205
|
+
<Globe.Root zoom={zoomProp} translation={translation} rotation={rotation}>
|
|
207
206
|
<Globe.Canvas
|
|
208
|
-
ref={controller}
|
|
209
207
|
topology={styles?.dots ? dots : topology}
|
|
210
208
|
projection={projection}
|
|
211
209
|
styles={styles}
|
|
212
210
|
features={tour ? { points: features?.points ?? [] } : features}
|
|
211
|
+
ref={controller}
|
|
213
212
|
/>
|
|
214
213
|
<Globe.Zoom onAction={handleAction} />
|
|
215
214
|
<Globe.Action onAction={handleAction} />
|
|
@@ -227,7 +226,7 @@ const meta = {
|
|
|
227
226
|
title: 'ui/react-ui-geo/Globe',
|
|
228
227
|
component: Globe.Root,
|
|
229
228
|
render: DefaultStory,
|
|
230
|
-
decorators: [withTheme],
|
|
229
|
+
decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
|
|
231
230
|
parameters: {
|
|
232
231
|
layout: 'fullscreen',
|
|
233
232
|
},
|
|
@@ -26,15 +26,17 @@ import React, {
|
|
|
26
26
|
import { useResizeDetector } from 'react-resize-detector';
|
|
27
27
|
import { type Topology } from 'topojson-specification';
|
|
28
28
|
|
|
29
|
-
import { type ThemeMode, type ThemedClassName, useDynamicRef, useThemeContext } from '@dxos/react-ui';
|
|
30
|
-
import { mx } from '@dxos/react-ui-theme';
|
|
31
|
-
|
|
32
29
|
import {
|
|
33
|
-
|
|
34
|
-
type
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
type ThemeMode,
|
|
31
|
+
type ThemedClassName,
|
|
32
|
+
useComposedRefs,
|
|
33
|
+
useControlledState,
|
|
34
|
+
useDynamicRef,
|
|
35
|
+
useThemeContext,
|
|
36
|
+
} from '@dxos/react-ui';
|
|
37
|
+
import { composable, composableProps, mx } from '@dxos/ui-theme';
|
|
38
|
+
|
|
39
|
+
import { GlobeContext, type GlobeContextType, type Point, type Vector, useGlobeContext } from '../../hooks';
|
|
38
40
|
import {
|
|
39
41
|
type Features,
|
|
40
42
|
type StyleSet,
|
|
@@ -126,19 +128,43 @@ const getProjection = (type: GlobeCanvasProps['projection'] = 'orthographic'): G
|
|
|
126
128
|
// Root
|
|
127
129
|
//
|
|
128
130
|
|
|
129
|
-
type GlobeRootProps =
|
|
130
|
-
|
|
131
|
-
const GlobeRoot =
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
131
|
+
type GlobeRootProps = Partial<Pick<GlobeContextType, 'center' | 'zoom' | 'translation' | 'rotation'>>;
|
|
132
|
+
|
|
133
|
+
const GlobeRoot = composable<HTMLDivElement, GlobeRootProps>(
|
|
134
|
+
(
|
|
135
|
+
{ children, center: centerProp, zoom: zoomProp, translation: translationProp, rotation: rotationProp, ...props },
|
|
136
|
+
forwardedRef,
|
|
137
|
+
) => {
|
|
138
|
+
const localRef = useRef<HTMLDivElement>(null);
|
|
139
|
+
const composedRef = useComposedRefs<HTMLDivElement>(localRef, forwardedRef);
|
|
140
|
+
const { width, height } = useResizeDetector<HTMLDivElement>({ targetRef: localRef });
|
|
141
|
+
|
|
142
|
+
const [center, setCenter] = useControlledState(centerProp);
|
|
143
|
+
const [zoom, setZoom] = useControlledState(zoomProp ?? 4);
|
|
144
|
+
const [translation, setTranslation] = useControlledState<Point>(translationProp);
|
|
145
|
+
const [rotation, setRotation] = useControlledState<Vector>(rotationProp);
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<GlobeContext.Provider
|
|
149
|
+
value={{
|
|
150
|
+
size: { width, height },
|
|
151
|
+
center,
|
|
152
|
+
zoom,
|
|
153
|
+
translation,
|
|
154
|
+
rotation,
|
|
155
|
+
setCenter,
|
|
156
|
+
setZoom,
|
|
157
|
+
setTranslation,
|
|
158
|
+
setRotation,
|
|
159
|
+
}}
|
|
160
|
+
>
|
|
161
|
+
<div {...composableProps(props, { classNames: 'relative dx-container' })} ref={composedRef}>
|
|
162
|
+
{children}
|
|
163
|
+
</div>
|
|
164
|
+
</GlobeContext.Provider>
|
|
165
|
+
);
|
|
166
|
+
},
|
|
167
|
+
);
|
|
142
168
|
|
|
143
169
|
//
|
|
144
170
|
// Canvas
|
|
@@ -157,16 +183,16 @@ type GlobeCanvasProps = {
|
|
|
157
183
|
*/
|
|
158
184
|
// TODO(burdon): Move controller to root.
|
|
159
185
|
const GlobeCanvas = forwardRef<GlobeController, GlobeCanvasProps>(
|
|
160
|
-
({ projection:
|
|
186
|
+
({ projection: projectionProp, topology, features, styles: stylesProp }, forwardRef) => {
|
|
161
187
|
const { themeMode } = useThemeContext();
|
|
162
|
-
const styles = useMemo(() =>
|
|
188
|
+
const styles = useMemo(() => stylesProp ?? defaultStyles[themeMode], [stylesProp, themeMode]);
|
|
163
189
|
|
|
164
190
|
// Canvas.
|
|
165
191
|
const [canvas, setCanvas] = useState<HTMLCanvasElement>(null);
|
|
166
192
|
const canvasRef = (canvas: HTMLCanvasElement) => setCanvas(canvas);
|
|
167
193
|
|
|
168
194
|
// Projection.
|
|
169
|
-
const projection = useMemo(() => getProjection(
|
|
195
|
+
const projection = useMemo(() => getProjection(projectionProp), [projectionProp]);
|
|
170
196
|
|
|
171
197
|
// Layers.
|
|
172
198
|
// TODO(burdon): Generate on the fly based on what is visible.
|
|
@@ -200,9 +226,9 @@ const GlobeCanvas = forwardRef<GlobeController, GlobeCanvasProps>(
|
|
|
200
226
|
translation,
|
|
201
227
|
rotation,
|
|
202
228
|
setCenter,
|
|
203
|
-
setZoom: (
|
|
204
|
-
if (typeof
|
|
205
|
-
const is = interpolateNumber(zoomRef.current,
|
|
229
|
+
setZoom: (state) => {
|
|
230
|
+
if (typeof state === 'function') {
|
|
231
|
+
const is = interpolateNumber(zoomRef.current, state(zoomRef.current));
|
|
206
232
|
// Stop easing if already zooming.
|
|
207
233
|
transition()
|
|
208
234
|
.ease(zooming.current ? easeLinear : easeSinOut)
|
|
@@ -212,7 +238,7 @@ const GlobeCanvas = forwardRef<GlobeController, GlobeCanvasProps>(
|
|
|
212
238
|
zooming.current = false;
|
|
213
239
|
});
|
|
214
240
|
} else {
|
|
215
|
-
setZoom(
|
|
241
|
+
setZoom(state);
|
|
216
242
|
}
|
|
217
243
|
},
|
|
218
244
|
setTranslation,
|
|
@@ -259,7 +285,7 @@ const GlobeDebug = ({ position = 'topleft' }: { position?: ControlPosition }) =>
|
|
|
259
285
|
return (
|
|
260
286
|
<div
|
|
261
287
|
className={mx(
|
|
262
|
-
'z-10 absolute w-96 p-2 overflow-hidden border border-green-700 rounded',
|
|
288
|
+
'z-10 absolute w-96 p-2 overflow-hidden border border-green-700 rounded-sm',
|
|
263
289
|
controlPositions[position],
|
|
264
290
|
)}
|
|
265
291
|
>
|
|
@@ -5,11 +5,10 @@
|
|
|
5
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
6
|
import React, { useState } from 'react';
|
|
7
7
|
|
|
8
|
-
import { withTheme } from '@dxos/react-ui/testing';
|
|
8
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
9
9
|
|
|
10
10
|
import { useMapZoomHandler } from '../../hooks';
|
|
11
11
|
import { type GeoMarker } from '../../types';
|
|
12
|
-
|
|
13
12
|
import { Map, type MapController } from './Map';
|
|
14
13
|
|
|
15
14
|
const DefaultStory = ({ markers = [] }: { markers?: GeoMarker[] }) => {
|
|
@@ -17,11 +16,13 @@ const DefaultStory = ({ markers = [] }: { markers?: GeoMarker[] }) => {
|
|
|
17
16
|
const handleZoomAction = useMapZoomHandler(controller);
|
|
18
17
|
|
|
19
18
|
return (
|
|
20
|
-
<Map.Root
|
|
21
|
-
<Map.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
<Map.Root>
|
|
20
|
+
<Map.Content ref={setController}>
|
|
21
|
+
<Map.Tiles />
|
|
22
|
+
<Map.Markers markers={markers} />
|
|
23
|
+
<Map.Zoom position='bottomleft' onAction={handleZoomAction} />
|
|
24
|
+
<Map.Action position='bottomright' />
|
|
25
|
+
</Map.Content>
|
|
25
26
|
</Map.Root>
|
|
26
27
|
);
|
|
27
28
|
};
|
|
@@ -30,7 +31,7 @@ const meta = {
|
|
|
30
31
|
title: 'ui/react-ui-geo/Map',
|
|
31
32
|
component: Map.Root as any,
|
|
32
33
|
render: DefaultStory,
|
|
33
|
-
decorators: [withTheme],
|
|
34
|
+
decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
|
|
34
35
|
parameters: {
|
|
35
36
|
layout: 'fullscreen',
|
|
36
37
|
},
|
|
@@ -6,12 +6,12 @@ import 'leaflet/dist/leaflet.css';
|
|
|
6
6
|
|
|
7
7
|
import { createContext } from '@radix-ui/react-context';
|
|
8
8
|
import L, { Control, type ControlPosition, DomEvent, DomUtil, type LatLngLiteral, latLngBounds } from 'leaflet';
|
|
9
|
-
import React, { type PropsWithChildren, forwardRef, useEffect, useImperativeHandle, useRef
|
|
9
|
+
import React, { type PropsWithChildren, forwardRef, useEffect, useImperativeHandle, useRef } from 'react';
|
|
10
10
|
import { createRoot } from 'react-dom/client';
|
|
11
11
|
import { MapContainer, type MapContainerProps, Marker, Popup, TileLayer, useMap, useMapEvents } from 'react-leaflet';
|
|
12
12
|
|
|
13
|
-
import {
|
|
14
|
-
import { defaultTx, mx } from '@dxos/
|
|
13
|
+
import { type ThemedClassName, ThemeProvider, Tooltip } from '@dxos/react-ui';
|
|
14
|
+
import { composable, composableProps, defaultTx, mx } from '@dxos/ui-theme';
|
|
15
15
|
|
|
16
16
|
import { type GeoMarker } from '../../types';
|
|
17
17
|
import { ActionControls, type ControlProps, ZoomControls, controlPositions } from '../Toolbar';
|
|
@@ -43,23 +43,54 @@ type MapContextValue = {
|
|
|
43
43
|
onChange?: (ev: { center: LatLngLiteral; zoom: number }) => void;
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
-
const [
|
|
46
|
+
const [MapContextProvider, useMapContext] = createContext<MapContextValue>('Map');
|
|
47
47
|
|
|
48
48
|
//
|
|
49
49
|
// Root
|
|
50
50
|
//
|
|
51
51
|
|
|
52
|
-
type MapRootProps =
|
|
52
|
+
type MapRootProps = Pick<MapContextValue, 'onChange'>;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Context provider for the map. Must wrap Map.Content.
|
|
56
|
+
*/
|
|
57
|
+
const MapRoot = composable<HTMLDivElement, MapRootProps>(({ children, onChange, ...props }, forwardedRef) => {
|
|
58
|
+
// TODO(burdon): Use attention: const [attention, setAttention] = useState(false);
|
|
59
|
+
const attention = false;
|
|
60
|
+
return (
|
|
61
|
+
<MapContextProvider attention={attention} onChange={onChange}>
|
|
62
|
+
<div
|
|
63
|
+
{...composableProps(props, {
|
|
64
|
+
role: 'none',
|
|
65
|
+
classNames: 'dx-container grid dx-focus-ring-inset',
|
|
66
|
+
})}
|
|
67
|
+
ref={forwardedRef}
|
|
68
|
+
>
|
|
69
|
+
{children}
|
|
70
|
+
</div>
|
|
71
|
+
</MapContextProvider>
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
MapRoot.displayName = 'Map.Root';
|
|
76
|
+
|
|
77
|
+
//
|
|
78
|
+
// Content
|
|
79
|
+
//
|
|
80
|
+
|
|
81
|
+
type MapContentProps = ThemedClassName<Omit<MapContainerProps, 'children'> & PropsWithChildren>;
|
|
53
82
|
|
|
54
83
|
/**
|
|
55
84
|
* https://react-leaflet.js.org/docs/api-map
|
|
56
85
|
*/
|
|
57
|
-
const
|
|
86
|
+
const MAP_CONTENT_NAME = 'Map.Content';
|
|
87
|
+
|
|
88
|
+
const MapContent = forwardRef<MapController, MapContentProps>(
|
|
58
89
|
(
|
|
59
|
-
{ classNames, scrollWheelZoom = true, doubleClickZoom = true, touchZoom = true, center, zoom,
|
|
90
|
+
{ classNames, scrollWheelZoom = true, doubleClickZoom = true, touchZoom = true, center, zoom, children, ...props },
|
|
60
91
|
forwardedRef,
|
|
61
92
|
) => {
|
|
62
|
-
const
|
|
93
|
+
const { attention } = useMapContext(MAP_CONTENT_NAME);
|
|
63
94
|
const mapRef = useRef<L.Map>(null);
|
|
64
95
|
const map = mapRef.current;
|
|
65
96
|
|
|
@@ -92,37 +123,39 @@ const MapRoot = forwardRef<MapController, MapRootProps>(
|
|
|
92
123
|
}, [map, attention]);
|
|
93
124
|
|
|
94
125
|
return (
|
|
95
|
-
<
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
</
|
|
126
|
+
<MapContainer
|
|
127
|
+
{...props}
|
|
128
|
+
className={mx('group relative grid bg-base-surface!', classNames)}
|
|
129
|
+
attributionControl={false}
|
|
130
|
+
zoomControl={false}
|
|
131
|
+
scrollWheelZoom={scrollWheelZoom}
|
|
132
|
+
doubleClickZoom={doubleClickZoom}
|
|
133
|
+
touchZoom={touchZoom}
|
|
134
|
+
center={center ?? defaults.center}
|
|
135
|
+
zoom={zoom ?? defaults.zoom}
|
|
136
|
+
whenReady={() => {}}
|
|
137
|
+
ref={mapRef}
|
|
138
|
+
>
|
|
139
|
+
{children}
|
|
140
|
+
</MapContainer>
|
|
110
141
|
);
|
|
111
142
|
},
|
|
112
143
|
);
|
|
113
144
|
|
|
114
|
-
|
|
145
|
+
MapContent.displayName = 'Map.Content';
|
|
115
146
|
|
|
116
147
|
//
|
|
117
148
|
// Tiles
|
|
118
149
|
// https://react-leaflet.js.org/docs/api-components/#tilelayer
|
|
119
150
|
//
|
|
120
151
|
|
|
152
|
+
const MAP_TILES_NAME = 'Map.Tiles';
|
|
153
|
+
|
|
121
154
|
type MapTilesProps = {};
|
|
122
155
|
|
|
123
156
|
const MapTiles = (_props: MapTilesProps) => {
|
|
124
157
|
const ref = useRef<L.TileLayer>(null);
|
|
125
|
-
const { onChange } = useMapContext(
|
|
158
|
+
const { onChange } = useMapContext(MAP_TILES_NAME);
|
|
126
159
|
|
|
127
160
|
useMapEvents({
|
|
128
161
|
zoomstart: (ev) => {
|
|
@@ -135,7 +168,7 @@ const MapTiles = (_props: MapTilesProps) => {
|
|
|
135
168
|
|
|
136
169
|
// NOTE: Need to dynamically update data attribute since TileLayer doesn't update, but
|
|
137
170
|
// Tailwind requires setting the property for static analysis.
|
|
138
|
-
const { attention } = useMapContext(
|
|
171
|
+
const { attention } = useMapContext(MAP_TILES_NAME);
|
|
139
172
|
useEffect(() => {
|
|
140
173
|
if (ref.current) {
|
|
141
174
|
ref.current.getContainer().dataset.attention = attention ? '1' : '0';
|
|
@@ -176,7 +209,7 @@ const MapTiles = (_props: MapTilesProps) => {
|
|
|
176
209
|
);
|
|
177
210
|
};
|
|
178
211
|
|
|
179
|
-
MapTiles.displayName =
|
|
212
|
+
MapTiles.displayName = MAP_TILES_NAME;
|
|
180
213
|
|
|
181
214
|
//
|
|
182
215
|
// Markers
|
|
@@ -247,7 +280,7 @@ const CustomControl = ({
|
|
|
247
280
|
useEffect(() => {
|
|
248
281
|
const control = new Control({ position });
|
|
249
282
|
control.onAdd = () => {
|
|
250
|
-
const container = DomUtil.create('div', mx('
|
|
283
|
+
const container = DomUtil.create('div', mx('m-0!', controlPositions[position]));
|
|
251
284
|
DomEvent.disableClickPropagation(container);
|
|
252
285
|
DomEvent.disableScrollPropagation(container);
|
|
253
286
|
|
|
@@ -290,10 +323,18 @@ const MapAction = ({ onAction, position = 'bottomright', ...props }: MapControlP
|
|
|
290
323
|
|
|
291
324
|
export const Map = {
|
|
292
325
|
Root: MapRoot,
|
|
326
|
+
Content: MapContent,
|
|
293
327
|
Tiles: MapTiles,
|
|
294
328
|
Markers: MapMarkers,
|
|
295
329
|
Zoom: MapZoom,
|
|
296
330
|
Action: MapAction,
|
|
297
331
|
};
|
|
298
332
|
|
|
299
|
-
export {
|
|
333
|
+
export {
|
|
334
|
+
type MapController,
|
|
335
|
+
type MapRootProps,
|
|
336
|
+
type MapContentProps,
|
|
337
|
+
type MapTilesProps,
|
|
338
|
+
type MapMarkersProps,
|
|
339
|
+
type MapControlProps,
|
|
340
|
+
};
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
import { type ControlPosition } from 'leaflet';
|
|
6
6
|
import React from 'react';
|
|
7
7
|
|
|
8
|
-
import { IconButton, type ThemedClassName, Toolbar } from '@dxos/react-ui';
|
|
8
|
+
import { IconButton, type ThemedClassName, Toolbar, useTranslation } from '@dxos/react-ui';
|
|
9
|
+
|
|
10
|
+
import { translationKey } from '#translations';
|
|
9
11
|
|
|
10
12
|
export type ControlAction = 'toggle' | 'start' | 'zoom-in' | 'zoom-out';
|
|
11
13
|
|
|
@@ -21,22 +23,20 @@ export const controlPositions: Record<ControlPosition, string> = {
|
|
|
21
23
|
};
|
|
22
24
|
|
|
23
25
|
export const ZoomControls = ({ classNames, onAction }: ControlProps) => {
|
|
26
|
+
const { t } = useTranslation(translationKey);
|
|
27
|
+
|
|
24
28
|
return (
|
|
25
29
|
<Toolbar.Root classNames={['gap-2', classNames]}>
|
|
26
30
|
<IconButton
|
|
27
31
|
icon='ph--plus--regular'
|
|
28
|
-
label='zoom in'
|
|
29
32
|
iconOnly
|
|
30
|
-
|
|
31
|
-
classNames='px-0 aspect-square'
|
|
33
|
+
label={t('zoom-in-icon.button')}
|
|
32
34
|
onClick={() => onAction?.('zoom-in')}
|
|
33
35
|
/>
|
|
34
36
|
<IconButton
|
|
35
37
|
icon='ph--minus--regular'
|
|
36
|
-
label='zoom out'
|
|
37
38
|
iconOnly
|
|
38
|
-
|
|
39
|
-
classNames='px-0 aspect-square'
|
|
39
|
+
label={t('zoom-out-icon.button')}
|
|
40
40
|
onClick={() => onAction?.('zoom-out')}
|
|
41
41
|
/>
|
|
42
42
|
</Toolbar.Root>
|
|
@@ -44,22 +44,20 @@ export const ZoomControls = ({ classNames, onAction }: ControlProps) => {
|
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
export const ActionControls = ({ classNames, onAction }: ControlProps) => {
|
|
47
|
+
const { t } = useTranslation(translationKey);
|
|
48
|
+
|
|
47
49
|
return (
|
|
48
50
|
<Toolbar.Root classNames={['gap-2', classNames]}>
|
|
49
51
|
<IconButton
|
|
50
|
-
icon='ph--
|
|
51
|
-
label='start'
|
|
52
|
+
icon='ph--path--regular'
|
|
52
53
|
iconOnly
|
|
53
|
-
|
|
54
|
-
classNames='px-0 aspect-square'
|
|
54
|
+
label={t('start-icon.button')}
|
|
55
55
|
onClick={() => onAction?.('start')}
|
|
56
56
|
/>
|
|
57
57
|
<IconButton
|
|
58
58
|
icon='ph--globe-hemisphere-west--regular'
|
|
59
|
-
label='toggle'
|
|
60
59
|
iconOnly
|
|
61
|
-
|
|
62
|
-
classNames='px-0 aspect-square'
|
|
60
|
+
label={t('toggle-icon.button')}
|
|
63
61
|
onClick={() => onAction?.('toggle')}
|
|
64
62
|
/>
|
|
65
63
|
</Toolbar.Root>
|
package/src/hooks/context.tsx
CHANGED
|
@@ -2,16 +2,17 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import { type Dispatch, type SetStateAction, createContext, useContext } from 'react';
|
|
6
6
|
|
|
7
7
|
import { raise } from '@dxos/debug';
|
|
8
|
-
import { useControlledState } from '@dxos/react-ui';
|
|
9
8
|
|
|
10
9
|
import { type LatLngLiteral } from '../types';
|
|
11
10
|
|
|
12
11
|
// TODO(burdon): Factor out common geometry types.
|
|
13
12
|
export type Size = { width: number; height: number };
|
|
13
|
+
|
|
14
14
|
export type Point = { x: number; y: number };
|
|
15
|
+
|
|
15
16
|
export type Vector = [number, number, number];
|
|
16
17
|
|
|
17
18
|
export type GlobeContextType = {
|
|
@@ -26,39 +27,9 @@ export type GlobeContextType = {
|
|
|
26
27
|
setRotation: Dispatch<SetStateAction<Vector>>;
|
|
27
28
|
};
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
center: { lat: 51, lng: 0 } as LatLngLiteral,
|
|
31
|
-
zoom: 4,
|
|
32
|
-
} as const;
|
|
33
|
-
|
|
30
|
+
/** @internal */
|
|
34
31
|
// TODO(burdon): Replace with radix.
|
|
35
|
-
const GlobeContext = createContext<GlobeContextType>(undefined);
|
|
36
|
-
|
|
37
|
-
export type GlobeContextProviderProps = PropsWithChildren<
|
|
38
|
-
Partial<Pick<GlobeContextType, 'size' | 'center' | 'zoom' | 'translation' | 'rotation'>>
|
|
39
|
-
>;
|
|
40
|
-
|
|
41
|
-
export const GlobeContextProvider = ({
|
|
42
|
-
children,
|
|
43
|
-
size,
|
|
44
|
-
center: centerParam = defaults.center,
|
|
45
|
-
zoom: zoomParam = defaults.zoom,
|
|
46
|
-
translation: translationParam,
|
|
47
|
-
rotation: rotationParam,
|
|
48
|
-
}: GlobeContextProviderProps) => {
|
|
49
|
-
const [center, setCenter] = useControlledState(centerParam);
|
|
50
|
-
const [zoom, setZoom] = useControlledState(zoomParam);
|
|
51
|
-
const [translation, setTranslation] = useControlledState<Point>(translationParam);
|
|
52
|
-
const [rotation, setRotation] = useControlledState<Vector>(rotationParam);
|
|
53
|
-
|
|
54
|
-
return (
|
|
55
|
-
<GlobeContext.Provider
|
|
56
|
-
value={{ size, center, zoom, translation, rotation, setCenter, setZoom, setTranslation, setRotation }}
|
|
57
|
-
>
|
|
58
|
-
{children}
|
|
59
|
-
</GlobeContext.Provider>
|
|
60
|
-
);
|
|
61
|
-
};
|
|
32
|
+
export const GlobeContext = createContext<GlobeContextType>(undefined);
|
|
62
33
|
|
|
63
34
|
export const useGlobeContext = () => {
|
|
64
35
|
return useContext(GlobeContext) ?? raise(new Error('Missing GlobeContext'));
|