@dxos/react-ui-geo 0.8.4-main.c4373fc → 0.8.4-main.c85a9c8dae
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/lib/browser/index.mjs +369 -451
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +369 -451
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/Globe/Globe.d.ts.map +1 -1
- package/dist/types/src/components/Map/Map.d.ts +7 -10
- package/dist/types/src/components/Map/Map.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 -1
- package/dist/types/src/hooks/context.d.ts.map +1 -1
- package/dist/types/src/hooks/useGlobeZoomHandler.d.ts.map +1 -1
- package/dist/types/src/hooks/useTour.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -0
- 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/tsconfig.tsbuildinfo +1 -1
- package/package.json +26 -23
- package/src/components/Globe/Globe.stories.tsx +2 -2
- package/src/components/Globe/Globe.tsx +10 -9
- package/src/components/Map/Map.stories.tsx +2 -2
- package/src/components/Map/Map.tsx +25 -53
- package/src/components/Toolbar/Controls.tsx +12 -14
- package/src/hooks/context.tsx +14 -8
- package/src/hooks/useGlobeZoomHandler.ts +8 -2
- package/src/hooks/useTour.ts +1 -0
- package/src/index.ts +1 -0
- package/src/translations.ts +20 -0
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-geo",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.c85a9c8dae",
|
|
4
4
|
"description": "Geo components.",
|
|
5
5
|
"homepage": "https://github.com/dxos",
|
|
6
6
|
"bugs": "https://github.com/dxos/issues",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/dxos/dxos"
|
|
10
|
+
},
|
|
7
11
|
"license": "MIT",
|
|
8
12
|
"author": "DXOS.org",
|
|
9
13
|
"sideEffects": true,
|
|
@@ -36,8 +40,7 @@
|
|
|
36
40
|
"src"
|
|
37
41
|
],
|
|
38
42
|
"dependencies": {
|
|
39
|
-
"@
|
|
40
|
-
"@radix-ui/react-context": "^1.0.5",
|
|
43
|
+
"@radix-ui/react-context": "1.1.1",
|
|
41
44
|
"d3": "^7.9.0",
|
|
42
45
|
"d3-geo-projection": "^4.0.0",
|
|
43
46
|
"d3-hexbin": "^0.2.2",
|
|
@@ -49,39 +52,39 @@
|
|
|
49
52
|
"topojson-client": "^3.1.0",
|
|
50
53
|
"topojson-simplify": "^3.0.3",
|
|
51
54
|
"versor": "^0.2.0",
|
|
52
|
-
"@dxos/
|
|
53
|
-
"@dxos/
|
|
54
|
-
"@dxos/
|
|
55
|
-
"@dxos/node-std": "0.8.4-main.
|
|
56
|
-
"@dxos/util": "0.8.4-main.
|
|
55
|
+
"@dxos/debug": "0.8.4-main.c85a9c8dae",
|
|
56
|
+
"@dxos/log": "0.8.4-main.c85a9c8dae",
|
|
57
|
+
"@dxos/async": "0.8.4-main.c85a9c8dae",
|
|
58
|
+
"@dxos/node-std": "0.8.4-main.c85a9c8dae",
|
|
59
|
+
"@dxos/util": "0.8.4-main.c85a9c8dae"
|
|
57
60
|
},
|
|
58
61
|
"devDependencies": {
|
|
59
|
-
"@react-three/drei": "^
|
|
60
|
-
"@react-three/fiber": "^9.
|
|
62
|
+
"@react-three/drei": "^10.7.7",
|
|
63
|
+
"@react-three/fiber": "^9.5.0",
|
|
61
64
|
"@types/d3": "^7.4.3",
|
|
62
65
|
"@types/geojson": "^7946.0.14",
|
|
63
66
|
"@types/leaflet": "^1.9.16",
|
|
64
|
-
"@types/react": "~19.2.
|
|
65
|
-
"@types/react-dom": "~19.2.
|
|
67
|
+
"@types/react": "~19.2.7",
|
|
68
|
+
"@types/react-dom": "~19.2.3",
|
|
66
69
|
"@types/three": "0.165.0",
|
|
67
70
|
"@types/topojson-client": "^3.1.4",
|
|
68
71
|
"@types/topojson-simplify": "^3.0.3",
|
|
69
72
|
"@types/topojson-specification": "^1.0.5",
|
|
70
73
|
"JSONStream": "^1.3.5",
|
|
71
74
|
"geojson2h3": "^1.2.0",
|
|
72
|
-
"leva": "^0.
|
|
73
|
-
"react": "~19.2.
|
|
74
|
-
"react-dom": "~19.2.
|
|
75
|
-
"three": "0.
|
|
76
|
-
"@dxos/react-ui": "0.8.4-main.
|
|
77
|
-
"@dxos/
|
|
78
|
-
"@dxos/storybook-utils": "0.8.4-main.
|
|
75
|
+
"leva": "^0.10.1",
|
|
76
|
+
"react": "~19.2.3",
|
|
77
|
+
"react-dom": "~19.2.3",
|
|
78
|
+
"three": "^0.178.0",
|
|
79
|
+
"@dxos/react-ui": "0.8.4-main.c85a9c8dae",
|
|
80
|
+
"@dxos/ui-theme": "0.8.4-main.c85a9c8dae",
|
|
81
|
+
"@dxos/storybook-utils": "0.8.4-main.c85a9c8dae"
|
|
79
82
|
},
|
|
80
83
|
"peerDependencies": {
|
|
81
|
-
"react": "
|
|
82
|
-
"react-dom": "
|
|
83
|
-
"@dxos/react-ui": "0.8.4-main.
|
|
84
|
-
"@dxos/
|
|
84
|
+
"react": "~19.2.3",
|
|
85
|
+
"react-dom": "~19.2.3",
|
|
86
|
+
"@dxos/react-ui": "0.8.4-main.c85a9c8dae",
|
|
87
|
+
"@dxos/ui-theme": "0.8.4-main.c85a9c8dae"
|
|
85
88
|
},
|
|
86
89
|
"publishConfig": {
|
|
87
90
|
"access": "public"
|
|
@@ -9,7 +9,7 @@ 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';
|
|
@@ -227,7 +227,7 @@ const meta = {
|
|
|
227
227
|
title: 'ui/react-ui-geo/Globe',
|
|
228
228
|
component: Globe.Root,
|
|
229
229
|
render: DefaultStory,
|
|
230
|
-
decorators: [withTheme],
|
|
230
|
+
decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
|
|
231
231
|
parameters: {
|
|
232
232
|
layout: 'fullscreen',
|
|
233
233
|
},
|
|
@@ -27,7 +27,7 @@ import { useResizeDetector } from 'react-resize-detector';
|
|
|
27
27
|
import { type Topology } from 'topojson-specification';
|
|
28
28
|
|
|
29
29
|
import { type ThemeMode, type ThemedClassName, useDynamicRef, useThemeContext } from '@dxos/react-ui';
|
|
30
|
-
import { mx } from '@dxos/
|
|
30
|
+
import { mx } from '@dxos/ui-theme';
|
|
31
31
|
|
|
32
32
|
import {
|
|
33
33
|
GlobeContextProvider,
|
|
@@ -130,6 +130,7 @@ type GlobeRootProps = PropsWithChildren<ThemedClassName<GlobeContextProviderProp
|
|
|
130
130
|
|
|
131
131
|
const GlobeRoot = ({ classNames, children, ...props }: GlobeRootProps) => {
|
|
132
132
|
const { ref, width, height } = useResizeDetector<HTMLDivElement>();
|
|
133
|
+
|
|
133
134
|
return (
|
|
134
135
|
<div ref={ref} className={mx('relative flex grow overflow-hidden', classNames)}>
|
|
135
136
|
<GlobeContextProvider size={{ width, height }} {...props}>
|
|
@@ -156,16 +157,16 @@ type GlobeCanvasProps = {
|
|
|
156
157
|
*/
|
|
157
158
|
// TODO(burdon): Move controller to root.
|
|
158
159
|
const GlobeCanvas = forwardRef<GlobeController, GlobeCanvasProps>(
|
|
159
|
-
({ projection:
|
|
160
|
+
({ projection: projectionProp, topology, features, styles: stylesProp }, forwardRef) => {
|
|
160
161
|
const { themeMode } = useThemeContext();
|
|
161
|
-
const styles = useMemo(() =>
|
|
162
|
+
const styles = useMemo(() => stylesProp ?? defaultStyles[themeMode], [stylesProp, themeMode]);
|
|
162
163
|
|
|
163
164
|
// Canvas.
|
|
164
165
|
const [canvas, setCanvas] = useState<HTMLCanvasElement>(null);
|
|
165
166
|
const canvasRef = (canvas: HTMLCanvasElement) => setCanvas(canvas);
|
|
166
167
|
|
|
167
168
|
// Projection.
|
|
168
|
-
const projection = useMemo(() => getProjection(
|
|
169
|
+
const projection = useMemo(() => getProjection(projectionProp), [projectionProp]);
|
|
169
170
|
|
|
170
171
|
// Layers.
|
|
171
172
|
// TODO(burdon): Generate on the fly based on what is visible.
|
|
@@ -199,9 +200,9 @@ const GlobeCanvas = forwardRef<GlobeController, GlobeCanvasProps>(
|
|
|
199
200
|
translation,
|
|
200
201
|
rotation,
|
|
201
202
|
setCenter,
|
|
202
|
-
setZoom: (
|
|
203
|
-
if (typeof
|
|
204
|
-
const is = interpolateNumber(zoomRef.current,
|
|
203
|
+
setZoom: (state) => {
|
|
204
|
+
if (typeof state === 'function') {
|
|
205
|
+
const is = interpolateNumber(zoomRef.current, state(zoomRef.current));
|
|
205
206
|
// Stop easing if already zooming.
|
|
206
207
|
transition()
|
|
207
208
|
.ease(zooming.current ? easeLinear : easeSinOut)
|
|
@@ -211,7 +212,7 @@ const GlobeCanvas = forwardRef<GlobeController, GlobeCanvasProps>(
|
|
|
211
212
|
zooming.current = false;
|
|
212
213
|
});
|
|
213
214
|
} else {
|
|
214
|
-
setZoom(
|
|
215
|
+
setZoom(state);
|
|
215
216
|
}
|
|
216
217
|
},
|
|
217
218
|
setTranslation,
|
|
@@ -258,7 +259,7 @@ const GlobeDebug = ({ position = 'topleft' }: { position?: ControlPosition }) =>
|
|
|
258
259
|
return (
|
|
259
260
|
<div
|
|
260
261
|
className={mx(
|
|
261
|
-
'z-10 absolute w-96 p-2 overflow-hidden border border-green-700 rounded',
|
|
262
|
+
'z-10 absolute w-96 p-2 overflow-hidden border border-green-700 rounded-sm',
|
|
262
263
|
controlPositions[position],
|
|
263
264
|
)}
|
|
264
265
|
>
|
|
@@ -5,7 +5,7 @@
|
|
|
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';
|
|
@@ -30,7 +30,7 @@ const meta = {
|
|
|
30
30
|
title: 'ui/react-ui-geo/Map',
|
|
31
31
|
component: Map.Root as any,
|
|
32
32
|
render: DefaultStory,
|
|
33
|
-
decorators: [withTheme],
|
|
33
|
+
decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
|
|
34
34
|
parameters: {
|
|
35
35
|
layout: 'fullscreen',
|
|
36
36
|
},
|
|
@@ -8,12 +8,10 @@ import { createContext } from '@radix-ui/react-context';
|
|
|
8
8
|
import L, { Control, type ControlPosition, DomEvent, DomUtil, type LatLngLiteral, latLngBounds } from 'leaflet';
|
|
9
9
|
import React, { type PropsWithChildren, forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
|
10
10
|
import { createRoot } from 'react-dom/client';
|
|
11
|
-
import type
|
|
12
|
-
import { MapContainer, Marker, Popup, TileLayer, useMap } from 'react-leaflet';
|
|
11
|
+
import { MapContainer, type MapContainerProps, Marker, Popup, TileLayer, useMap, useMapEvents } from 'react-leaflet';
|
|
13
12
|
|
|
14
|
-
import { debounce } from '@dxos/async';
|
|
15
13
|
import { ThemeProvider, type ThemedClassName, Tooltip } from '@dxos/react-ui';
|
|
16
|
-
import { defaultTx, mx } from '@dxos/
|
|
14
|
+
import { defaultTx, mx } from '@dxos/ui-theme';
|
|
17
15
|
|
|
18
16
|
import { type GeoMarker } from '../../types';
|
|
19
17
|
import { ActionControls, type ControlProps, ZoomControls, controlPositions } from '../Toolbar';
|
|
@@ -25,7 +23,7 @@ import { ActionControls, type ControlProps, ZoomControls, controlPositions } fro
|
|
|
25
23
|
const defaults = {
|
|
26
24
|
center: { lat: 51, lng: 0 } as L.LatLngLiteral,
|
|
27
25
|
zoom: 4,
|
|
28
|
-
};
|
|
26
|
+
} as const;
|
|
29
27
|
|
|
30
28
|
//
|
|
31
29
|
// Controller
|
|
@@ -42,6 +40,7 @@ type MapController = {
|
|
|
42
40
|
|
|
43
41
|
type MapContextValue = {
|
|
44
42
|
attention?: boolean;
|
|
43
|
+
onChange?: (ev: { center: LatLngLiteral; zoom: number }) => void;
|
|
45
44
|
};
|
|
46
45
|
|
|
47
46
|
const [MapContextProvier, useMapContext] = createContext<MapContextValue>('Map');
|
|
@@ -50,27 +49,14 @@ const [MapContextProvier, useMapContext] = createContext<MapContextValue>('Map')
|
|
|
50
49
|
// Root
|
|
51
50
|
//
|
|
52
51
|
|
|
53
|
-
type MapRootProps = ThemedClassName<
|
|
54
|
-
MapContainerProps & {
|
|
55
|
-
onChange?: (ev: { center: LatLngLiteral; zoom: number }) => void;
|
|
56
|
-
}
|
|
57
|
-
>;
|
|
52
|
+
type MapRootProps = ThemedClassName<MapContainerProps & Pick<MapContextValue, 'onChange'>>;
|
|
58
53
|
|
|
59
54
|
/**
|
|
60
55
|
* https://react-leaflet.js.org/docs/api-map
|
|
61
56
|
*/
|
|
62
57
|
const MapRoot = forwardRef<MapController, MapRootProps>(
|
|
63
58
|
(
|
|
64
|
-
{
|
|
65
|
-
classNames,
|
|
66
|
-
scrollWheelZoom = true,
|
|
67
|
-
doubleClickZoom = true,
|
|
68
|
-
touchZoom = true,
|
|
69
|
-
center = defaults.center,
|
|
70
|
-
zoom = defaults.zoom,
|
|
71
|
-
onChange,
|
|
72
|
-
...props
|
|
73
|
-
},
|
|
59
|
+
{ classNames, scrollWheelZoom = true, doubleClickZoom = true, touchZoom = true, center, zoom, onChange, ...props },
|
|
74
60
|
forwardedRef,
|
|
75
61
|
) => {
|
|
76
62
|
const [attention, setAttention] = useState(false);
|
|
@@ -90,32 +76,6 @@ const MapRoot = forwardRef<MapController, MapRootProps>(
|
|
|
90
76
|
[],
|
|
91
77
|
);
|
|
92
78
|
|
|
93
|
-
// Events.
|
|
94
|
-
useEffect(() => {
|
|
95
|
-
if (!map) {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const handler = debounce(() => {
|
|
100
|
-
setAttention(true);
|
|
101
|
-
onChange?.({
|
|
102
|
-
center: map.getCenter(),
|
|
103
|
-
zoom: map.getZoom(),
|
|
104
|
-
});
|
|
105
|
-
}, 100);
|
|
106
|
-
|
|
107
|
-
map.on('move', handler);
|
|
108
|
-
map.on('zoom', handler);
|
|
109
|
-
map.on('focus', () => setAttention(true));
|
|
110
|
-
map.on('blur', () => setAttention(false));
|
|
111
|
-
return () => {
|
|
112
|
-
map.off('move');
|
|
113
|
-
map.off('zoom');
|
|
114
|
-
map.off('focus');
|
|
115
|
-
map.off('blur');
|
|
116
|
-
};
|
|
117
|
-
}, [map, onChange]);
|
|
118
|
-
|
|
119
79
|
// Enable/disable scroll wheel zoom.
|
|
120
80
|
// TODO(burdon): Use attention:
|
|
121
81
|
// const {hasAttention} = useAttention(props.id);
|
|
@@ -132,18 +92,18 @@ const MapRoot = forwardRef<MapController, MapRootProps>(
|
|
|
132
92
|
}, [map, attention]);
|
|
133
93
|
|
|
134
94
|
return (
|
|
135
|
-
<MapContextProvier attention={attention}>
|
|
95
|
+
<MapContextProvier attention={attention} onChange={onChange}>
|
|
136
96
|
<MapContainer
|
|
137
97
|
{...props}
|
|
138
98
|
ref={mapRef}
|
|
139
|
-
className={mx('group relative grid
|
|
99
|
+
className={mx('group relative grid h-full w-full !bg-base-surface dx-focus-ring-inset', classNames)}
|
|
140
100
|
attributionControl={false}
|
|
141
101
|
zoomControl={false}
|
|
142
102
|
scrollWheelZoom={scrollWheelZoom}
|
|
143
103
|
doubleClickZoom={doubleClickZoom}
|
|
144
104
|
touchZoom={touchZoom}
|
|
145
|
-
center={center}
|
|
146
|
-
zoom={zoom}
|
|
105
|
+
center={center ?? defaults.center}
|
|
106
|
+
zoom={zoom ?? defaults.zoom}
|
|
147
107
|
// whenReady={() => {}}
|
|
148
108
|
/>
|
|
149
109
|
</MapContextProvier>
|
|
@@ -158,14 +118,26 @@ MapRoot.displayName = 'Map.Root';
|
|
|
158
118
|
// https://react-leaflet.js.org/docs/api-components/#tilelayer
|
|
159
119
|
//
|
|
160
120
|
|
|
121
|
+
const MAP_TILES_NAME = 'Map.Tiles';
|
|
122
|
+
|
|
161
123
|
type MapTilesProps = {};
|
|
162
124
|
|
|
163
125
|
const MapTiles = (_props: MapTilesProps) => {
|
|
164
126
|
const ref = useRef<L.TileLayer>(null);
|
|
127
|
+
const { onChange } = useMapContext(MAP_TILES_NAME);
|
|
128
|
+
|
|
129
|
+
useMapEvents({
|
|
130
|
+
zoomstart: (ev) => {
|
|
131
|
+
onChange?.({
|
|
132
|
+
center: ev.target.getCenter(),
|
|
133
|
+
zoom: ev.target.getZoom(),
|
|
134
|
+
});
|
|
135
|
+
},
|
|
136
|
+
});
|
|
165
137
|
|
|
166
138
|
// NOTE: Need to dynamically update data attribute since TileLayer doesn't update, but
|
|
167
139
|
// Tailwind requires setting the property for static analysis.
|
|
168
|
-
const { attention } = useMapContext(
|
|
140
|
+
const { attention } = useMapContext(MAP_TILES_NAME);
|
|
169
141
|
useEffect(() => {
|
|
170
142
|
if (ref.current) {
|
|
171
143
|
ref.current.getContainer().dataset.attention = attention ? '1' : '0';
|
|
@@ -206,7 +178,7 @@ const MapTiles = (_props: MapTilesProps) => {
|
|
|
206
178
|
);
|
|
207
179
|
};
|
|
208
180
|
|
|
209
|
-
MapTiles.displayName =
|
|
181
|
+
MapTiles.displayName = MAP_TILES_NAME;
|
|
210
182
|
|
|
211
183
|
//
|
|
212
184
|
// Markers
|
|
@@ -277,7 +249,7 @@ const CustomControl = ({
|
|
|
277
249
|
useEffect(() => {
|
|
278
250
|
const control = new Control({ position });
|
|
279
251
|
control.onAdd = () => {
|
|
280
|
-
const container = DomUtil.create('div', mx('
|
|
252
|
+
const container = DomUtil.create('div', mx('m-0!', controlPositions[position]));
|
|
281
253
|
DomEvent.disableClickPropagation(container);
|
|
282
254
|
DomEvent.disableScrollPropagation(container);
|
|
283
255
|
|
|
@@ -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
|
@@ -26,6 +26,12 @@ export type GlobeContextType = {
|
|
|
26
26
|
setRotation: Dispatch<SetStateAction<Vector>>;
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
+
const defaults = {
|
|
30
|
+
center: { lat: 51, lng: 0 } as LatLngLiteral,
|
|
31
|
+
zoom: 4,
|
|
32
|
+
} as const;
|
|
33
|
+
|
|
34
|
+
// TODO(burdon): Replace with radix.
|
|
29
35
|
const GlobeContext = createContext<GlobeContextType>(undefined);
|
|
30
36
|
|
|
31
37
|
export type GlobeContextProviderProps = PropsWithChildren<
|
|
@@ -35,15 +41,15 @@ export type GlobeContextProviderProps = PropsWithChildren<
|
|
|
35
41
|
export const GlobeContextProvider = ({
|
|
36
42
|
children,
|
|
37
43
|
size,
|
|
38
|
-
center:
|
|
39
|
-
zoom:
|
|
40
|
-
translation:
|
|
41
|
-
rotation:
|
|
44
|
+
center: centerProp = defaults.center,
|
|
45
|
+
zoom: zoomProp = defaults.zoom,
|
|
46
|
+
translation: translationProp,
|
|
47
|
+
rotation: rotationProp,
|
|
42
48
|
}: GlobeContextProviderProps) => {
|
|
43
|
-
const [center, setCenter] = useControlledState(
|
|
44
|
-
const [zoom, setZoom] = useControlledState(
|
|
45
|
-
const [translation, setTranslation] = useControlledState<Point>(
|
|
46
|
-
const [rotation, setRotation] = useControlledState<Vector>(
|
|
49
|
+
const [center, setCenter] = useControlledState(centerProp);
|
|
50
|
+
const [zoom, setZoom] = useControlledState(zoomProp);
|
|
51
|
+
const [translation, setTranslation] = useControlledState<Point>(translationProp);
|
|
52
|
+
const [rotation, setRotation] = useControlledState<Vector>(rotationProp);
|
|
47
53
|
|
|
48
54
|
return (
|
|
49
55
|
<GlobeContext.Provider
|
|
@@ -6,6 +6,8 @@ import { useCallback } from 'react';
|
|
|
6
6
|
|
|
7
7
|
import { type ControlProps, type GlobeController } from '../components';
|
|
8
8
|
|
|
9
|
+
const ZOOM_FACTOR = 0.1;
|
|
10
|
+
|
|
9
11
|
export const useGlobeZoomHandler = (controller: GlobeController | null | undefined): ControlProps['onAction'] => {
|
|
10
12
|
return useCallback<ControlProps['onAction']>(
|
|
11
13
|
(event) => {
|
|
@@ -15,11 +17,15 @@ export const useGlobeZoomHandler = (controller: GlobeController | null | undefin
|
|
|
15
17
|
|
|
16
18
|
switch (event) {
|
|
17
19
|
case 'zoom-in': {
|
|
18
|
-
controller.setZoom((zoom) =>
|
|
20
|
+
controller.setZoom((zoom) => {
|
|
21
|
+
return zoom * (1 + ZOOM_FACTOR);
|
|
22
|
+
});
|
|
19
23
|
break;
|
|
20
24
|
}
|
|
21
25
|
case 'zoom-out': {
|
|
22
|
-
controller.setZoom((zoom) =>
|
|
26
|
+
controller.setZoom((zoom) => {
|
|
27
|
+
return zoom * (1 - ZOOM_FACTOR);
|
|
28
|
+
});
|
|
23
29
|
break;
|
|
24
30
|
}
|
|
25
31
|
}
|
package/src/hooks/useTour.ts
CHANGED
|
@@ -34,6 +34,7 @@ export const useTour = (
|
|
|
34
34
|
options: TourOptions = {},
|
|
35
35
|
): [boolean, Dispatch<SetStateAction<boolean>>] => {
|
|
36
36
|
const selection = useMemo(() => d3Selection(), []);
|
|
37
|
+
// TODO(burdon): Redo controlled state.
|
|
37
38
|
const [running, setRunning] = useState(options.running ?? false);
|
|
38
39
|
useEffect(() => {
|
|
39
40
|
if (!running) {
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Resource } from '@dxos/react-ui';
|
|
6
|
+
|
|
7
|
+
export const translationKey = '@dxos/react-ui-geo';
|
|
8
|
+
|
|
9
|
+
export const translations = [
|
|
10
|
+
{
|
|
11
|
+
'en-US': {
|
|
12
|
+
[translationKey]: {
|
|
13
|
+
'zoom in icon button': 'Zoom in',
|
|
14
|
+
'zoom out icon button': 'Zoom out',
|
|
15
|
+
'start icon button': 'Start',
|
|
16
|
+
'toggle icon button': 'Toggle',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
] as const satisfies Resource[];
|