diff --git a/docs/modules/widgets/README.md b/docs/modules/widgets/README.md index bb949e6c..137a9a29 100644 --- a/docs/modules/widgets/README.md +++ b/docs/modules/widgets/README.md @@ -39,6 +39,7 @@ export function App() { ``` The [Pan and Zoom widgets example](/examples/widgets/pan-and-zoom-controls) shows the controls managing an orthographic view over abstract data. +The [React widgets example](/examples/widgets/react-widgets) demonstrates adding the same controls as JSX children via the React wrappers in `@deck.gl-community/react`. ### HTML overlays diff --git a/examples/widgets/react-widgets/app.tsx b/examples/widgets/react-widgets/app.tsx new file mode 100644 index 00000000..f48bd8f2 --- /dev/null +++ b/examples/widgets/react-widgets/app.tsx @@ -0,0 +1,94 @@ +// deck.gl-community +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +import {useCallback, useMemo, useState} from 'react'; +import DeckGL from '@deck.gl/react'; +import {OrthographicView} from '@deck.gl/core'; +import {ScatterplotLayer} from '@deck.gl/layers'; +import {PanWidget, ZoomRangeWidget} from '@deck.gl-community/react'; + +import '@deck.gl/widgets/stylesheet.css'; + +const INITIAL_VIEW_STATE = { + target: [0, 0], + zoom: 0 +}; + +const view = new OrthographicView({id: 'ortho'}); + +export function App() { + const [viewState, setViewState] = useState(INITIAL_VIEW_STATE); + + const data = useMemo(() => { + const points: {position: [number, number]; color: [number, number, number, number]}[] = []; + const size = 10; + + for (let x = -size; x <= size; x++) { + for (let y = -size; y <= size; y++) { + const distance = Math.sqrt(x * x + y * y); + const intensity = Math.max(0, 1 - distance / size); + points.push({ + position: [x * 20, y * 20], + color: [255 * intensity, 128 + 80 * intensity, 200, 200] + }); + } + } + + return points; + }, []); + + const layers = useMemo( + () => [ + new ScatterplotLayer({ + id: 'points', + data, + getPosition: (d) => d.position, + getFillColor: (d) => d.color, + radiusMinPixels: 4, + radiusMaxPixels: 12, + radiusUnits: 'pixels', + pickable: false + }) + ], + [data] + ); + + const handleViewStateChange = useCallback(({viewState: nextViewState}) => { + setViewState(nextViewState); + }, []); + + return ( + + + + +
+

React wrapper widgets

+

+ This example renders the Pan and Zoom range widgets as JSX children using the React wrappers from{' '} + @deck.gl-community/react. Each widget registers itself through the shared{' '} + <DeckGL> context, so there is no need to pass a widgets array prop. +

+
+
+ ); +} diff --git a/examples/widgets/react-widgets/index.html b/examples/widgets/react-widgets/index.html new file mode 100644 index 00000000..87586f11 --- /dev/null +++ b/examples/widgets/react-widgets/index.html @@ -0,0 +1,12 @@ + + + + + + React Widgets | deck.gl-community + + +
+ + + diff --git a/examples/widgets/react-widgets/index.tsx b/examples/widgets/react-widgets/index.tsx new file mode 100644 index 00000000..bc6d42b0 --- /dev/null +++ b/examples/widgets/react-widgets/index.tsx @@ -0,0 +1,20 @@ +// deck.gl-community +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +import React from 'react'; +import {createRoot} from 'react-dom/client'; + +import {App} from './app'; + +const container = document.getElementById('app'); +if (!container) { + throw new Error('Unable to find #app container'); +} + +const root = createRoot(container); +root.render( + + + +); diff --git a/examples/widgets/react-widgets/package.json b/examples/widgets/react-widgets/package.json new file mode 100644 index 00000000..3275b70c --- /dev/null +++ b/examples/widgets/react-widgets/package.json @@ -0,0 +1,23 @@ +{ + "name": "widgets-react-wrappers-example", + "version": "9.2.0", + "private": true, + "license": "MIT", + "type": "module", + "scripts": { + "start": "vite --open", + "start-local": "vite --config ../../vite.config.local.mjs" + }, + "dependencies": { + "@deck.gl-community/react": "workspace:*", + "@deck.gl/core": "~9.2.1", + "@deck.gl/layers": "~9.2.1", + "@deck.gl/react": "~9.2.1", + "@deck.gl/widgets": "~9.2.1", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "vite": "7.1.1" + } +} diff --git a/examples/widgets/react-widgets/tsconfig.json b/examples/widgets/react-widgets/tsconfig.json new file mode 100644 index 00000000..ec517aad --- /dev/null +++ b/examples/widgets/react-widgets/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "baseUrl": "./", + "paths": { + "*": ["./node_modules/*", "./*"] + } + }, + "include": ["./**/*.ts", "./**/*.tsx"], + "exclude": ["dist", "node_modules"] +} diff --git a/modules/react/package.json b/modules/react/package.json index e078cffb..3787a508 100644 --- a/modules/react/package.json +++ b/modules/react/package.json @@ -34,6 +34,8 @@ "src" ], "dependencies": { + "@deck.gl-community/widgets": "^9.2.0-beta.3", + "@deck.gl/react": "~9.2.1", "@types/styled-react-modal": "^1.2.5", "boxicons": "^2.1.4", "prop-types": "^15.8.1", diff --git a/modules/react/src/index.ts b/modules/react/src/index.ts index 83a4f66f..d22c5a89 100644 --- a/modules/react/src/index.ts +++ b/modules/react/src/index.ts @@ -12,4 +12,10 @@ export {EditorModal as Modal} from './components/modal'; export {Button} from './components/modal'; export {Icon} from './components/icon'; +// Widgets (from @deck.gl-community/widgets) +export {PanWidget} from './widgets/pan-widget'; +export {ZoomRangeWidget} from './widgets/zoom-range-widget'; +export {HtmlOverlayWidget} from './widgets/html-overlay-widget'; +export {HtmlTooltipWidget} from './widgets/html-tooltip-widget'; + // Overlays migrated to @deck.gl-community/widgets diff --git a/modules/react/src/widgets/html-overlay-widget.tsx b/modules/react/src/widgets/html-overlay-widget.tsx new file mode 100644 index 00000000..9711d445 --- /dev/null +++ b/modules/react/src/widgets/html-overlay-widget.tsx @@ -0,0 +1,15 @@ +// deck.gl-community +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +/* eslint-disable import/no-unresolved */ +import {HtmlOverlayWidget as _HtmlOverlayWidget} from '@deck.gl-community/widgets'; +import type {HtmlOverlayWidgetProps} from '@deck.gl-community/widgets'; +/* eslint-enable import/no-unresolved */ +import {useWidget} from '@deck.gl/react'; + +/** React wrapper for the HtmlOverlayWidget. */ +export const HtmlOverlayWidget = (props: HtmlOverlayWidgetProps = {}) => { + useWidget(_HtmlOverlayWidget, props); + return null; +}; diff --git a/modules/react/src/widgets/html-tooltip-widget.tsx b/modules/react/src/widgets/html-tooltip-widget.tsx new file mode 100644 index 00000000..c3fa2a5f --- /dev/null +++ b/modules/react/src/widgets/html-tooltip-widget.tsx @@ -0,0 +1,15 @@ +// deck.gl-community +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +/* eslint-disable import/no-unresolved */ +import {HtmlTooltipWidget as _HtmlTooltipWidget} from '@deck.gl-community/widgets'; +import type {HtmlTooltipWidgetProps} from '@deck.gl-community/widgets'; +/* eslint-enable import/no-unresolved */ +import {useWidget} from '@deck.gl/react'; + +/** React wrapper for the HtmlTooltipWidget. */ +export const HtmlTooltipWidget = (props: HtmlTooltipWidgetProps = {}) => { + useWidget(_HtmlTooltipWidget, props); + return null; +}; diff --git a/modules/react/src/widgets/pan-widget.tsx b/modules/react/src/widgets/pan-widget.tsx new file mode 100644 index 00000000..e98e56c7 --- /dev/null +++ b/modules/react/src/widgets/pan-widget.tsx @@ -0,0 +1,15 @@ +// deck.gl-community +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +/* eslint-disable import/no-unresolved */ +import {PanWidget as _PanWidget} from '@deck.gl-community/widgets'; +import type {PanWidgetProps} from '@deck.gl-community/widgets'; +/* eslint-enable import/no-unresolved */ +import {useWidget} from '@deck.gl/react'; + +/** React wrapper for the PanWidget. */ +export const PanWidget = (props: PanWidgetProps = {}) => { + useWidget(_PanWidget, props); + return null; +}; diff --git a/modules/react/src/widgets/zoom-range-widget.tsx b/modules/react/src/widgets/zoom-range-widget.tsx new file mode 100644 index 00000000..2b53d48c --- /dev/null +++ b/modules/react/src/widgets/zoom-range-widget.tsx @@ -0,0 +1,15 @@ +// deck.gl-community +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +/* eslint-disable import/no-unresolved */ +import {ZoomRangeWidget as _ZoomRangeWidget} from '@deck.gl-community/widgets'; +import type {ZoomRangeWidgetProps} from '@deck.gl-community/widgets'; +/* eslint-enable import/no-unresolved */ +import {useWidget} from '@deck.gl/react'; + +/** React wrapper for the ZoomRangeWidget. */ +export const ZoomRangeWidget = (props: ZoomRangeWidgetProps = {}) => { + useWidget(_ZoomRangeWidget, props); + return null; +}; diff --git a/modules/react/tsconfig.json b/modules/react/tsconfig.json index 175b131b..b051e7a9 100644 --- a/modules/react/tsconfig.json +++ b/modules/react/tsconfig.json @@ -1,11 +1,22 @@ { "extends": "../../tsconfig.json", "include": ["src/**/*"], - "exclude": ["node_modules"], + "exclude": ["node_modules", "../../node_modules/@deck.gl/core/src/**/*"], "compilerOptions": { "composite": true, "rootDir": "src", "outDir": "dist", - "noEmit": false - } + "noEmit": false, + "baseUrl": ".", + "allowJs": false, + "checkJs": false, + "moduleResolution": "bundler", + "paths": { + "@deck.gl-community/widgets": ["../widgets/dist/index.d.ts"], + "@deck.gl-community/widgets/*": ["../widgets/dist/*"], + "@deck.gl/core": ["../../node_modules/@deck.gl/core/dist/index.d.ts"], + "@deck.gl/core/*": ["../../node_modules/@deck.gl/core/dist/*"] + } + }, + "references": [{"path": "../widgets"}] } diff --git a/website/src/examples-sidebar.js b/website/src/examples-sidebar.js index 49aaf202..e4efdec2 100644 --- a/website/src/examples-sidebar.js +++ b/website/src/examples-sidebar.js @@ -59,7 +59,8 @@ const sidebars = { label: '@deck.gl-community/widgets', items: [ "widgets/html-overlays", - "widgets/pan-and-zoom-controls" + "widgets/pan-and-zoom-controls", + "widgets/react-widgets" ] }, // { diff --git a/website/src/examples/widgets/react-widgets.mdx b/website/src/examples/widgets/react-widgets.mdx new file mode 100644 index 00000000..27e9d29c --- /dev/null +++ b/website/src/examples/widgets/react-widgets.mdx @@ -0,0 +1,5 @@ +# React Widgets + +import Demo from './react-widgets'; + + diff --git a/website/src/examples/widgets/react-widgets.tsx b/website/src/examples/widgets/react-widgets.tsx new file mode 100644 index 00000000..e9982c8b --- /dev/null +++ b/website/src/examples/widgets/react-widgets.tsx @@ -0,0 +1,25 @@ +import React, {Component} from 'react'; +import BrowserOnly from '@docusaurus/BrowserOnly'; + +import {GITHUB_TREE} from '../../constants/defaults'; +import {App} from '../../../../examples/widgets/react-widgets/app'; + +import {makeExample} from '../../components'; + +class ReactWidgetsDemo extends Component { + static title = 'React Widgets'; + + static code = `${GITHUB_TREE}/examples/widgets/react-widgets`; + + static renderInfo(meta) { + return <>; + } + + render() { + const {...otherProps} = this.props; + + return {() => }; + } +} + +export default makeExample(ReactWidgetsDemo, {addInfoPanel: false}); diff --git a/yarn.lock b/yarn.lock index 39e065c8..72e4ec58 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1428,6 +1428,8 @@ __metadata: version: 0.0.0-use.local resolution: "@deck.gl-community/react@workspace:modules/react" dependencies: + "@deck.gl-community/widgets": "npm:^9.2.0-beta.3" + "@deck.gl/react": "npm:~9.2.1" "@types/styled-react-modal": "npm:^1.2.5" boxicons: "npm:^2.1.4" prop-types: "npm:^15.8.1" @@ -1444,7 +1446,7 @@ __metadata: languageName: unknown linkType: soft -"@deck.gl-community/widgets@workspace:*, @deck.gl-community/widgets@workspace:modules/widgets": +"@deck.gl-community/widgets@npm:^9.2.0-beta.3, @deck.gl-community/widgets@workspace:*, @deck.gl-community/widgets@workspace:modules/widgets": version: 0.0.0-use.local resolution: "@deck.gl-community/widgets@workspace:modules/widgets" dependencies: @@ -20657,6 +20659,21 @@ __metadata: languageName: unknown linkType: soft +"widgets-react-wrappers-example@workspace:examples/widgets/react-widgets": + version: 0.0.0-use.local + resolution: "widgets-react-wrappers-example@workspace:examples/widgets/react-widgets" + dependencies: + "@deck.gl-community/react": "workspace:*" + "@deck.gl/core": "npm:~9.2.1" + "@deck.gl/layers": "npm:~9.2.1" + "@deck.gl/react": "npm:~9.2.1" + "@deck.gl/widgets": "npm:~9.2.1" + react: "npm:^18.3.1" + react-dom: "npm:^18.3.1" + vite: "npm:7.1.1" + languageName: unknown + linkType: soft + "word-wrap@npm:^1.2.5": version: 1.2.5 resolution: "word-wrap@npm:1.2.5"