@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.
Files changed (67) hide show
  1. package/LICENSE +102 -5
  2. package/dist/lib/browser/{countries-110m-ZM3ZIEFS.mjs → countries-110m-RE5RNRQG.mjs} +1 -1
  3. package/dist/lib/browser/data.mjs +4 -3
  4. package/dist/lib/browser/data.mjs.map +4 -4
  5. package/dist/lib/browser/index.mjs +355 -449
  6. package/dist/lib/browser/index.mjs.map +3 -3
  7. package/dist/lib/browser/meta.json +1 -1
  8. package/dist/lib/browser/translations.mjs +19 -0
  9. package/dist/lib/browser/translations.mjs.map +7 -0
  10. package/dist/lib/node-esm/{countries-110m-3SFASWVD.mjs → countries-110m-4EDBXSFJ.mjs} +1 -1
  11. package/dist/lib/node-esm/data.mjs +5 -3
  12. package/dist/lib/node-esm/data.mjs.map +4 -4
  13. package/dist/lib/node-esm/index.mjs +355 -448
  14. package/dist/lib/node-esm/index.mjs.map +3 -3
  15. package/dist/lib/node-esm/meta.json +1 -1
  16. package/dist/lib/node-esm/translations.mjs +21 -0
  17. package/dist/lib/node-esm/translations.mjs.map +7 -0
  18. package/dist/types/data/airports.d.ts +4 -4
  19. package/dist/types/data/airports.d.ts.map +1 -1
  20. package/dist/types/data/cities.d.ts.map +1 -1
  21. package/dist/types/data/countries-110m.d.ts.map +1 -1
  22. package/dist/types/data/countries-dots-3.d.ts.map +1 -1
  23. package/dist/types/data/countries-dots-4.d.ts.map +1 -1
  24. package/dist/types/src/components/Globe/Globe.d.ts +5 -3
  25. package/dist/types/src/components/Globe/Globe.d.ts.map +1 -1
  26. package/dist/types/src/components/Globe/Globe.stories.d.ts +6 -4
  27. package/dist/types/src/components/Globe/Globe.stories.d.ts.map +1 -1
  28. package/dist/types/src/components/Map/Map.d.ts +19 -5
  29. package/dist/types/src/components/Map/Map.d.ts.map +1 -1
  30. package/dist/types/src/components/Map/Map.stories.d.ts.map +1 -1
  31. package/dist/types/src/components/Toolbar/Controls.d.ts.map +1 -1
  32. package/dist/types/src/hooks/context.d.ts +1 -3
  33. package/dist/types/src/hooks/context.d.ts.map +1 -1
  34. package/dist/types/src/hooks/useDrag.d.ts.map +1 -1
  35. package/dist/types/src/hooks/useGlobeZoomHandler.d.ts +1 -1
  36. package/dist/types/src/hooks/useGlobeZoomHandler.d.ts.map +1 -1
  37. package/dist/types/src/hooks/useMapZoomHandler.d.ts +1 -1
  38. package/dist/types/src/hooks/useMapZoomHandler.d.ts.map +1 -1
  39. package/dist/types/src/hooks/useSpinner.d.ts.map +1 -1
  40. package/dist/types/src/hooks/useTour.d.ts.map +1 -1
  41. package/dist/types/src/index.d.ts +0 -1
  42. package/dist/types/src/index.d.ts.map +1 -1
  43. package/dist/types/src/translations.d.ts +12 -0
  44. package/dist/types/src/translations.d.ts.map +1 -0
  45. package/dist/types/src/util/debug.d.ts.map +1 -1
  46. package/dist/types/src/util/inertia.d.ts.map +1 -1
  47. package/dist/types/src/util/path.d.ts.map +1 -1
  48. package/dist/types/src/util/render.d.ts.map +1 -1
  49. package/dist/types/tsconfig.tsbuildinfo +1 -1
  50. package/package.json +42 -36
  51. package/src/components/Globe/Globe.stories.tsx +7 -8
  52. package/src/components/Globe/Globe.tsx +55 -29
  53. package/src/components/Map/Map.stories.tsx +9 -8
  54. package/src/components/Map/Map.tsx +70 -29
  55. package/src/components/Toolbar/Controls.tsx +12 -14
  56. package/src/hooks/context.tsx +5 -34
  57. package/src/hooks/useSpinner.ts +0 -1
  58. package/src/hooks/useTour.ts +1 -0
  59. package/src/index.ts +0 -1
  60. package/src/translations.ts +20 -0
  61. package/src/util/render.ts +0 -1
  62. package/dist/lib/browser/chunk-GMWLKTLN.mjs +0 -9
  63. package/dist/lib/browser/chunk-GMWLKTLN.mjs.map +0 -7
  64. package/dist/lib/node-esm/chunk-JODBF4CC.mjs +0 -11
  65. package/dist/lib/node-esm/chunk-JODBF4CC.mjs.map +0 -7
  66. /package/dist/lib/browser/{countries-110m-ZM3ZIEFS.mjs.map → countries-110m-RE5RNRQG.mjs.map} +0 -0
  67. /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.ae835ea",
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
- "license": "MIT",
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/index.ts",
20
- "types": "./dist/types/src/index.d.ts",
21
- "browser": "./dist/lib/browser/index.mjs",
22
- "node": "./dist/lib/node-esm/index.mjs"
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
- "@preact-signals/safe-react": "^0.9.0",
40
- "@radix-ui/react-context": "^1.0.5",
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.ae835ea",
53
- "@dxos/debug": "0.8.4-main.ae835ea",
54
- "@dxos/log": "0.8.4-main.ae835ea",
55
- "@dxos/node-std": "0.8.4-main.ae835ea",
56
- "@dxos/util": "0.8.4-main.ae835ea"
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": "^9.99.0",
60
- "@react-three/fiber": "^9.3.0",
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.2",
65
- "@types/react-dom": "~19.2.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.9.35",
73
- "react": "~19.2.0",
74
- "react-dom": "~19.2.0",
75
- "three": "0.165.0",
76
- "@dxos/storybook-utils": "0.8.4-main.ae835ea",
77
- "@dxos/react-ui-theme": "0.8.4-main.ae835ea",
78
- "@dxos/react-ui": "0.8.4-main.ae835ea"
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": "^19.0.0",
82
- "react-dom": "^19.0.0",
83
- "@dxos/react-ui": "0.8.4-main.ae835ea",
84
- "@dxos/react-ui-theme": "0.8.4-main.ae835ea"
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 StoryProps = Pick<GlobeRootProps, 'zoom' | 'translation' | 'rotation'> &
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: _zoom = 1,
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
- }: StoryProps) => {
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 classNames='absolute inset-0' zoom={_zoom} translation={translation} rotation={rotation}>
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
- GlobeContextProvider,
34
- type GlobeContextProviderProps,
35
- type GlobeContextType,
36
- useGlobeContext,
37
- } from '../../hooks';
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 = PropsWithChildren<ThemedClassName<GlobeContextProviderProps>>;
130
-
131
- const GlobeRoot = ({ classNames, children, ...props }: GlobeRootProps) => {
132
- const { ref, width, height } = useResizeDetector<HTMLDivElement>();
133
-
134
- return (
135
- <div ref={ref} className={mx('relative flex grow overflow-hidden', classNames)}>
136
- <GlobeContextProvider size={{ width, height }} {...props}>
137
- {children}
138
- </GlobeContextProvider>
139
- </div>
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: projectionParam, topology, features, styles: stylesParam }, forwardRef) => {
186
+ ({ projection: projectionProp, topology, features, styles: stylesProp }, forwardRef) => {
161
187
  const { themeMode } = useThemeContext();
162
- const styles = useMemo(() => stylesParam ?? defaultStyles[themeMode], [stylesParam, themeMode]);
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(projectionParam), [projectionParam]);
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: (s) => {
204
- if (typeof s === 'function') {
205
- const is = interpolateNumber(zoomRef.current, s(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(s);
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 ref={setController}>
21
- <Map.Tiles />
22
- <Map.Markers markers={markers} />
23
- <Map.Zoom position='bottomleft' onAction={handleZoomAction} />
24
- <Map.Action position='bottomright' />
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, useState } from 'react';
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 { ThemeProvider, type ThemedClassName, Tooltip } from '@dxos/react-ui';
14
- import { defaultTx, mx } from '@dxos/react-ui-theme';
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 [MapContextProvier, useMapContext] = createContext<MapContextValue>('Map');
46
+ const [MapContextProvider, useMapContext] = createContext<MapContextValue>('Map');
47
47
 
48
48
  //
49
49
  // Root
50
50
  //
51
51
 
52
- type MapRootProps = ThemedClassName<MapContainerProps & Pick<MapContextValue, 'onChange'>>;
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 MapRoot = forwardRef<MapController, MapRootProps>(
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, onChange, ...props },
90
+ { classNames, scrollWheelZoom = true, doubleClickZoom = true, touchZoom = true, center, zoom, children, ...props },
60
91
  forwardedRef,
61
92
  ) => {
62
- const [attention, setAttention] = useState(false);
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
- <MapContextProvier attention={attention} onChange={onChange}>
96
- <MapContainer
97
- {...props}
98
- ref={mapRef}
99
- className={mx('group relative grid bs-full is-full !bg-baseSurface dx-focus-ring-inset', classNames)}
100
- attributionControl={false}
101
- zoomControl={false}
102
- scrollWheelZoom={scrollWheelZoom}
103
- doubleClickZoom={doubleClickZoom}
104
- touchZoom={touchZoom}
105
- center={center ?? defaults.center}
106
- zoom={zoom ?? defaults.zoom}
107
- // whenReady={() => {}}
108
- />
109
- </MapContextProvier>
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
- MapRoot.displayName = 'Map.Root';
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(MapTiles.displayName);
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(MapTiles.displayName);
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 = 'Map.Tiles';
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('!m-0', controlPositions[position]));
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 { type MapController, type MapRootProps, type MapTilesProps, type MapMarkersProps, type MapControlProps };
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
- size={5}
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
- size={5}
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--play--regular'
51
- label='start'
52
+ icon='ph--path--regular'
52
53
  iconOnly
53
- size={5}
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
- size={5}
62
- classNames='px-0 aspect-square'
60
+ label={t('toggle-icon.button')}
63
61
  onClick={() => onAction?.('toggle')}
64
62
  />
65
63
  </Toolbar.Root>
@@ -2,16 +2,17 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import React, { type Dispatch, type PropsWithChildren, type SetStateAction, createContext, useContext } from 'react';
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
- const defaults = {
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'));