Add custom tool examples (#3064)
Two examples: One in the UI section that shows how to add a tool to the toolbar along with an icon One in the shapes and tools section that shows a simple sticker tool with no child states I'll go over the copy again before it's merged, but don't want to spend too long on it right now in case the feeling is that these should both be a single example. Next: The [minimal example](https://tldraw.dev/examples/editor-api/only-editor) is currently the best example we have of a tool with child states. I think this should be adapted and copied/moved over to the custom shapes and tools category. closes tld-2266 - [x] `documentation` — Changes to the documentation only[^2] ### Release Notes - Adds a simple custom tool example
This commit is contained in:
parent
0813e54ca2
commit
e543797b81
6 changed files with 245 additions and 0 deletions
3
apps/examples/public/heart-icon.svg
Normal file
3
apps/examples/public/heart-icon.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M12 20.4l-1.8-1.6C4.8 13.4 2 10.5 2 7.2 2 4.1 4.1 2 7.2 2c1.9 0 3.7 1 4.8 2.6C13.1 3 15 2 16.8 2 19.9 2 22 4.1 22 7.2c0 3.3-2.8 6.2-8.2 11.6L12 20.4z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 232 B |
12
apps/examples/src/examples/add-tool-to-toolbar/README.md
Normal file
12
apps/examples/src/examples/add-tool-to-toolbar/README.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
title: Add a tool to the Toolbar
|
||||
component: ./ToolInToolbarExample.tsx
|
||||
category: ui
|
||||
priority: 1
|
||||
---
|
||||
|
||||
Add your custom tool to the toolbar
|
||||
|
||||
---
|
||||
|
||||
You can make an icon for your custom tool appear on tldraw's toolbar. To do this you will need to override the toolbar component, pass in a custom component for the keyboard shortcuts dialog, and pass in an asset url for your icon. This example shows how to do that. For more information on how to implement custom tools, check out the custom tool example.
|
|
@ -0,0 +1,115 @@
|
|||
import {
|
||||
DefaultKeyboardShortcutsDialog,
|
||||
DefaultKeyboardShortcutsDialogContent,
|
||||
TLComponents,
|
||||
TLUiAssetUrlOverrides,
|
||||
TLUiOverrides,
|
||||
Tldraw,
|
||||
TldrawUiMenuItem,
|
||||
toolbarItem,
|
||||
useTools,
|
||||
} from 'tldraw'
|
||||
import 'tldraw/tldraw.css'
|
||||
import { StickerTool } from './sticker-tool-util'
|
||||
|
||||
// There's a guide at the bottom of this file!
|
||||
|
||||
// [1]
|
||||
const uiOverrides: TLUiOverrides = {
|
||||
tools(editor, tools) {
|
||||
// Create a tool item in the ui's context.
|
||||
tools.sticker = {
|
||||
id: 'sticker',
|
||||
icon: 'heart-icon',
|
||||
label: 'Sticker',
|
||||
kbd: 's',
|
||||
onSelect: () => {
|
||||
editor.setCurrentTool('sticker')
|
||||
},
|
||||
}
|
||||
return tools
|
||||
},
|
||||
toolbar(_app, toolbar, { tools }) {
|
||||
// Add the tool item from the context to the toolbar.
|
||||
toolbar.splice(4, 0, toolbarItem(tools.sticker))
|
||||
return toolbar
|
||||
},
|
||||
}
|
||||
|
||||
// [2]
|
||||
const components: TLComponents = {
|
||||
KeyboardShortcutsDialog: (props) => {
|
||||
const tools = useTools()
|
||||
return (
|
||||
<DefaultKeyboardShortcutsDialog {...props}>
|
||||
<DefaultKeyboardShortcutsDialogContent />
|
||||
{/* Ideally, we'd interleave this into the tools group */}
|
||||
<TldrawUiMenuItem {...tools['sticker']} />
|
||||
</DefaultKeyboardShortcutsDialog>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
// [3]
|
||||
export const customAssetUrls: TLUiAssetUrlOverrides = {
|
||||
icons: {
|
||||
'heart-icon': '/heart-icon.svg',
|
||||
},
|
||||
}
|
||||
|
||||
// [4]
|
||||
const customTools = [StickerTool]
|
||||
export default function CustomToolExample() {
|
||||
return (
|
||||
<div className="tldraw__editor">
|
||||
<Tldraw
|
||||
// Pass in the array of custom tool classes
|
||||
tools={customTools}
|
||||
// Set the initial state to the sticker tool
|
||||
initialState="sticker"
|
||||
// Pass in our ui overrides
|
||||
overrides={uiOverrides}
|
||||
// pass in our custom components
|
||||
components={components}
|
||||
// pass in our custom asset urls
|
||||
assetUrls={customAssetUrls}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
Introduction:
|
||||
You can make an icon for your custom tool appear on tldraw's toolbar. To do this
|
||||
you will need to override the toolbar component, pass in a custom component for
|
||||
the keyboard shortcuts dialog, and pass in an asset url for your icon. This
|
||||
example shows how to do that. For more information on how to implement custom
|
||||
tools, check out the custom tool example.
|
||||
|
||||
[1]
|
||||
First, we define the uiOverrides object. We can override the tools function to
|
||||
add our custom tool to the ui's context. We can also override the toolbar function
|
||||
to add our custom tool to the toolbar. We are going to splice it into the toolbar
|
||||
so it appears in between the eraser and arrow tools.
|
||||
|
||||
[2]
|
||||
Next, we want to override the default keyboard shortcuts dialog so that the
|
||||
shortcut for our custom tool appears in the dialog. We don't want to change its
|
||||
appearance very much, so we can use the DefaultKeyboardShortcutsDialog component
|
||||
and pass in the DefaultKeyboardShortcutsDialogContent component. With the useTools
|
||||
hook, we can get the tools from context and pass in the sticker tool to the keyboard
|
||||
shortcuts dialog. This will make the keyboard shortcut for the sticker tool appear
|
||||
in the dialog.
|
||||
|
||||
[3]
|
||||
We need to make sure the editor knows where to find the icon for our custom tool.
|
||||
We do this by defining the customAssetUrls object and passing in the asset url for
|
||||
our icon.
|
||||
|
||||
[4]
|
||||
Finally, we define the customTools array. This array contains the custom tool
|
||||
class. We then pass the customTools array, the uiOverrides object, the
|
||||
components object, and the customAssetUrls object to the Tldraw component as
|
||||
props. This will make the icon for the custom tool appear on the toolbar.
|
||||
|
||||
*/
|
|
@ -0,0 +1,22 @@
|
|||
import { StateNode } from 'tldraw'
|
||||
|
||||
// Check out the custom tool example for a more detailed explanation of the tool class.
|
||||
|
||||
const OFFSET = 12
|
||||
export class StickerTool extends StateNode {
|
||||
static override id = 'sticker'
|
||||
|
||||
override onEnter = () => {
|
||||
this.editor.setCursor({ type: 'cross', rotation: 0 })
|
||||
}
|
||||
|
||||
override onPointerDown = () => {
|
||||
const { currentPagePoint } = this.editor.inputs
|
||||
this.editor.createShape({
|
||||
type: 'text',
|
||||
x: currentPagePoint.x - OFFSET,
|
||||
y: currentPagePoint.y - OFFSET,
|
||||
props: { text: '❤️' },
|
||||
})
|
||||
}
|
||||
}
|
81
apps/examples/src/examples/custom-tool/CustomToolExample.tsx
Normal file
81
apps/examples/src/examples/custom-tool/CustomToolExample.tsx
Normal file
|
@ -0,0 +1,81 @@
|
|||
import { StateNode, Tldraw } from 'tldraw'
|
||||
import 'tldraw/tldraw.css'
|
||||
|
||||
// There's a guide at the bottom of this file!
|
||||
|
||||
const OFFSET = 12
|
||||
|
||||
// [1]
|
||||
class StickerTool extends StateNode {
|
||||
static override id = 'sticker'
|
||||
|
||||
// [a]
|
||||
override onEnter = () => {
|
||||
this.editor.setCursor({ type: 'cross', rotation: 0 })
|
||||
}
|
||||
|
||||
// [b]
|
||||
override onPointerDown = () => {
|
||||
const { currentPagePoint } = this.editor.inputs
|
||||
this.editor.createShape({
|
||||
type: 'text',
|
||||
x: currentPagePoint.x - OFFSET,
|
||||
y: currentPagePoint.y - OFFSET,
|
||||
props: { text: '❤️' },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// [2]
|
||||
const customTools = [StickerTool]
|
||||
export default function CustomToolExample() {
|
||||
return (
|
||||
<div className="tldraw__editor">
|
||||
<Tldraw
|
||||
// Pass in the array of custom tool classes
|
||||
tools={customTools}
|
||||
// Set the initial state to the sticker tool
|
||||
initialState="sticker"
|
||||
// hide the ui
|
||||
hideUi
|
||||
// Put some helpful text on the canvas
|
||||
onMount={(editor) => {
|
||||
editor.createShape({
|
||||
type: 'text',
|
||||
x: 100,
|
||||
y: 100,
|
||||
props: { text: 'Click anywhere to add a sticker' },
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
Introduction:
|
||||
|
||||
Tools are nodes in tldraw's state machine. They are responsible for handling user input.
|
||||
You can create custom tools by extending the `StateNode` class and overriding its methods.
|
||||
In this example we make a very simple sticker tool that adds a heart emoji to the canvas
|
||||
when you click.
|
||||
|
||||
[1]
|
||||
We extend the `StateNode` class to create a new tool called `StickerTool`. We set its id
|
||||
to "sticker". We are not implementing any child states in this example, so we don't need
|
||||
to set an initial state or define any children states. To see an example of a custom tool
|
||||
with child states, check out the screenshot tool or minimal examples.
|
||||
|
||||
[a] The onEnter method is called when the tool is activated. We use it to set the cursor
|
||||
to a crosshair.
|
||||
|
||||
[b] The onPointerDown method is called when the user clicks on the canvas. We use it to
|
||||
create a new shape at the click location. We can get the click location from the
|
||||
editor's inputs.
|
||||
|
||||
[2]
|
||||
We pass our custom tool to the Tldraw component using the `tools` prop. We also set the
|
||||
initial state to our custom tool. We hide the ui and add some helpful text to the canvas
|
||||
using the `onMount` prop. This is not necessary for the tool to work but it helps make the
|
||||
example more visually clear.
|
||||
*/
|
12
apps/examples/src/examples/custom-tool/README.md
Normal file
12
apps/examples/src/examples/custom-tool/README.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
title: Custom tool
|
||||
component: ./CustomToolExample.tsx
|
||||
category: shapes/tools
|
||||
priority: 1
|
||||
---
|
||||
|
||||
Create a custom tool
|
||||
|
||||
---
|
||||
|
||||
Tools are nodes in tldraw's state machine. They are responsible for handling user input. You can create custom tools by extending the `StateNode` class and overriding its methods. In this example we make a very simple sticker tool that adds a heart emoji to the canvas when you click.
|
Loading…
Reference in a new issue