-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
docs(widgets) Custom Widget Developer Guide #9304
base: master
Are you sure you want to change the base?
Changes from 1 commit
101aaf9
ebd2b50
70248ce
026fd27
41cdfb3
077ddd9
775c7b8
e68bd41
5291a26
dcb07be
8daa0c6
e48c1c9
98a82a4
5366ae2
f17de11
a45ba17
225cd33
2548071
9a1ffe5
90e62a8
afba3b8
5152e49
1392345
06ef666
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,70 @@ | ||||||||||||||||||||||
# Writing Your Own Widget | ||||||||||||||||||||||
|
||||||||||||||||||||||
## Preparations | ||||||||||||||||||||||
|
||||||||||||||||||||||
There are a many ways to build a widget in deck.gl, and it is helpful to consider what approach will serve you best before starting. We've provided guides for commonly used approaches: | ||||||||||||||||||||||
|
||||||||||||||||||||||
* **[Implement a universal widget](./universal-widgets.md)** - A universal widget is compatible with any deck.gl application and is UI framework agnostic. This option is best for developing widgets to be used throughout the deck.gl ecosystem. | ||||||||||||||||||||||
* **[Create a react widget](./react-widgets.md)** - A react widget utilizes the convenience of react to develop the UI for your widget. It is tightly coupled to your react application, being mounted in the same root as the rest of your UI. This option is best for developing widgets custom to your react application. | ||||||||||||||||||||||
chrisgervang marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||
|
||||||||||||||||||||||
|
||||||||||||||||||||||
## Creating The Widget class | ||||||||||||||||||||||
chrisgervang marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||
|
||||||||||||||||||||||
Your widget class must implement the [Widget](../../api-reference/core/widget.md) interface. | ||||||||||||||||||||||
|
||||||||||||||||||||||
```ts | ||||||||||||||||||||||
import type {Widget} from '@deck.gl/core'; | ||||||||||||||||||||||
|
||||||||||||||||||||||
class AwesomeWidget implements Widget { | ||||||||||||||||||||||
constructor(props) { | ||||||||||||||||||||||
this.id = props.id || 'awesome-widget'; | ||||||||||||||||||||||
this.props = { ...props }; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
onAdd() {...} | ||||||||||||||||||||||
onRemove() {...} | ||||||||||||||||||||||
} | ||||||||||||||||||||||
``` | ||||||||||||||||||||||
|
||||||||||||||||||||||
It's most convenient to use TypeScript, but it can also be implemented in JavaScript. | ||||||||||||||||||||||
chrisgervang marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||
|
||||||||||||||||||||||
### Defining Widget Properties | ||||||||||||||||||||||
|
||||||||||||||||||||||
The list of properties is the main API your new widget will provide to | ||||||||||||||||||||||
applications. So it makes sense to carefully consider what properties | ||||||||||||||||||||||
your widget should offer. | ||||||||||||||||||||||
|
||||||||||||||||||||||
You also need to define the default values of the widget's properties. | ||||||||||||||||||||||
|
||||||||||||||||||||||
```ts | ||||||||||||||||||||||
import type {WidgetPlacement} from '@deck.gl/core' | ||||||||||||||||||||||
|
||||||||||||||||||||||
interface AwesomeWidgetProps { | ||||||||||||||||||||||
chrisgervang marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||
id?: string; | ||||||||||||||||||||||
/** | ||||||||||||||||||||||
* Widget positioning within the view. Default: 'top-left'. | ||||||||||||||||||||||
*/ | ||||||||||||||||||||||
placement?: WidgetPlacement; | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. consider exporting a set of base
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like the idea to encourage consistency, though it's still 100% up to the widget authors to decide how they implement this since we're only defining an interface. |
||||||||||||||||||||||
/** | ||||||||||||||||||||||
* View to attach to and interact with. Required when using multiple views. Default: null | ||||||||||||||||||||||
*/ | ||||||||||||||||||||||
viewId?: string | null; | ||||||||||||||||||||||
... | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
class AwesomeWidget implements Widget { | ||||||||||||||||||||||
chrisgervang marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||
constructor(props: AwesomeWidgetProps) { | ||||||||||||||||||||||
this.id = props.id || 'awesome-widget'; | ||||||||||||||||||||||
this.placement = props.placement || 'top-left'; | ||||||||||||||||||||||
this.viewId = props.viewId || null; | ||||||||||||||||||||||
|
||||||||||||||||||||||
this.props = { ...props } | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} | ||||||||||||||||||||||
``` | ||||||||||||||||||||||
|
||||||||||||||||||||||
## Best Practices | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This section looks a little "lost" here at the very end of the page. Maybe lead with something like this before all the examples?. Or maybe the section will grow and it will look more natural. |
||||||||||||||||||||||
|
||||||||||||||||||||||
- **Plan Your API:** Clearly define the properties and events your widget will expose so that its easy for developers to integrate into their applications. | ||||||||||||||||||||||
- **Handle Lifecycle Events:** Implement lifecycle methods like `onAdd`, `onRemove`, and `setProps` to manage the widget's updates effectively. | ||||||||||||||||||||||
- **Optimize for Performance:** Minimize unnecessary DOM re-renders and resource usage by carefully managing state updates. | ||||||||||||||||||||||
- **Ensure Accessibility:** Provide options for styling and interactions that respect user preferences, such as keyboard navigation and screen reader support. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
# React Widgets | ||
|
||
React widgets are a powerful way to integrate custom UI elements into deck.gl applications using the React framework. This guide will walk you through the process of building React-based widgets and best practices. | ||
|
||
We recommend users writing their own react widgets be familiar with implementing the `Widget` interface, consider reviewing the [Universal Widgets](./universal-widgets.md) guide. | ||
|
||
## Why Use React Widgets? | ||
|
||
React widgets leverage the strengths of React’s component model, allowing: | ||
- **Easy Composition:** Reuse and combine components within the React ecosystem. | ||
- **React Lifecycle Integration:** Utilize React’s lifecycle hooks to manage state and updates. | ||
- **Declarative UI:** Define your UI in a predictable and straightforward manner using JSX. | ||
|
||
React widgets are most suitable when you are working on React applications and do not intend to distribute your widget outside of your application. | ||
|
||
## Writing a React Widget | ||
|
||
### Prerequisites | ||
|
||
Ensure your deck.gl project includes the `@deck.gl/react` package to utilize React-specific utilities, such as the [`useWidget`](../../api-reference/react/use-widget.md) hook. | ||
|
||
Install the package if it’s not already included: | ||
|
||
```sh | ||
npm install @deck.gl/react | ||
``` | ||
|
||
### Example: Creating a React Widget | ||
|
||
Below is a step-by-step example of implementing a simple React widget. | ||
|
||
#### Define Your Widget Class | ||
|
||
Start by creating the core widget class, which must implement the [Widget](../../api-reference/core/widget.md) interface. | ||
|
||
```ts | ||
import type { Widget, WidgetPlacement } from '@deck.gl/react';= | ||
chrisgervang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
import { useWidget } from '@deck.gl/react'; | ||
import React, { useRef, RefObject } from 'react'; | ||
|
||
interface RotateWidgetProps { | ||
id?: string | ||
placement?: WidgetPlacement | ||
ref: RefObject<HTMLDivElement> | ||
} | ||
|
||
class RotateWidget { | ||
constructor(props: BearingWidgetProps) { | ||
this.id = props.id || 'bearing-widget'; | ||
this.placement = props.placement || 'top-right'; | ||
this.props = props; | ||
this.viewports = {}; | ||
} | ||
|
||
onAdd({ deck }) { | ||
this.deck = deck; | ||
return this.props.ref.current; | ||
} | ||
|
||
onViewportChange(viewport) { | ||
this.viewports[viewport.id] = viewport; | ||
} | ||
|
||
handleRotate(viewport, bearingDelta) { | ||
const nextBearing = viewport.bearing + bearingDelta; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The thing I am really looking for in a custom widget developer guide is exactly this - how to implement event handling. Can we create a placeholder page for this? |
||
const viewState = { | ||
...viewport, | ||
bearing: nextBearing | ||
}; | ||
this.deck.setProps({ viewState }); | ||
} | ||
|
||
handleCWRotate() { | ||
Object.values(this.viewports).forEach(viewport => this.handleRotate(viewport, 90)); | ||
} | ||
|
||
handleCCWRotate() { | ||
Object.values(this.viewports).forEach(viewport => this.handleRotate(viewport, -90)); | ||
} | ||
} | ||
``` | ||
|
||
#### Create a React Component | ||
|
||
Wrap the widget class in a React component using the [`useWidget`](../../api-reference/react/use-widget.md) hook. | ||
|
||
```tsx | ||
export const RotateReactWidget = (props) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we make sure the component has typed props? |
||
const ref = useRef(); | ||
const widget = useWidget(RotateWidget, { ref, ...props }); | ||
|
||
return ( | ||
<div | ||
ref={ref} | ||
style={{ padding: '10px', backgroundColor: '#f0f0f0', ...props.style }} | ||
className='custom-rotate-widget' | ||
> | ||
<button onClick={() => widget.handleCCWRotate()} style={{ marginRight: '5px' }}> | ||
Rotate CCW | ||
</button> | ||
<button onClick={() => widget.handleCWRotate()}> | ||
Rotate CW | ||
</button> | ||
</div> | ||
); | ||
}; | ||
``` | ||
|
||
This widget controls the bearing of the view its attached to. | ||
|
||
### Styling Your React Widget | ||
|
||
#### Inline Styles | ||
|
||
React widgets can be written to accept `style` props for inline styling. | ||
|
||
```tsx | ||
<RotateReactWidget style={{ backgroundColor: 'blue', color: 'white' }} /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. THis is only true if the widget actually forwards those props as in the example? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. True. I've broken this out into it's own step-by-step section |
||
``` | ||
|
||
#### CSS Classes | ||
|
||
React widgets can use `className` and add styles to their stylesheet. | ||
|
||
```css | ||
.custom-rotate-widget { | ||
padding: 10px; | ||
background-color: #333; | ||
color: white; | ||
} | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.