Add example for external UI (#2846)
This PR adds an example for how to add external UI components. It's something that I've been asked for help with in the past, and it's something I wish more consumers would do when using tldraw inline - it would help the editor feel less cramped. Therefore, I'd like to have it is as an example we can point people to. ![2024-02-19 at 11 53 18 - Maroon Boar](https://github.com/tldraw/tldraw/assets/15892272/f043bb77-3181-4a7d-b736-2b6a5012e208) > Some other context: > We've talked about giving this sorta thing more first-class support in the future, eg: with a `useEditorInstance` hook or something. Closes TLD-2128 ### Change Type - [x] `documentation` — Changes to the documentation only[^2] [^1]: publishes a `patch` release, for devDependencies use `internal` [^2]: will not publish a new version ### Test Plan 1. Try out the External UI example. 2. Make sure you can change tool by pressing the toolbar buttons. 3. Make sure you can change tool by pressing keys (eg: d, e) - [ ] Unit Tests - [ ] End to end tests ### Release Notes - Docs: Added external UI example. --------- Co-authored-by: Steve Ruiz <steveruizok@gmail.com>
This commit is contained in:
parent
d731951fcf
commit
7a09581081
7 changed files with 202 additions and 3 deletions
|
@ -0,0 +1,74 @@
|
||||||
|
import { Editor, Tldraw, useValue } from '@tldraw/tldraw'
|
||||||
|
import '@tldraw/tldraw/tldraw.css'
|
||||||
|
import { createContext, useContext, useState } from 'react'
|
||||||
|
import './external-ui.css'
|
||||||
|
|
||||||
|
// There's a guide at the bottom of this file!
|
||||||
|
|
||||||
|
// [1]
|
||||||
|
const editorContext = createContext({} as { editor: Editor })
|
||||||
|
|
||||||
|
export default function ExternalUiExample2() {
|
||||||
|
const [editor, setEditor] = useState<Editor | null>(null)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ margin: 32, width: 600 }}>
|
||||||
|
<div style={{ height: 400 }}>
|
||||||
|
<Tldraw
|
||||||
|
// [2]
|
||||||
|
onMount={(editor) => setEditor(editor)}
|
||||||
|
components={{ Toolbar: null }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/* [3] */}
|
||||||
|
{editor && (
|
||||||
|
<editorContext.Provider value={{ editor }}>
|
||||||
|
<ExternalToolbar />
|
||||||
|
</editorContext.Provider>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// [4]
|
||||||
|
const ExternalToolbar = () => {
|
||||||
|
const { editor } = useContext(editorContext)
|
||||||
|
|
||||||
|
const currentToolId = useValue('current tool id', () => editor?.getCurrentToolId(), [editor])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="external-toolbar">
|
||||||
|
<button
|
||||||
|
className="external-button"
|
||||||
|
data-isactive={currentToolId === 'select'}
|
||||||
|
onClick={() => editor.setCurrentTool('select')}
|
||||||
|
>
|
||||||
|
Select
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="external-button"
|
||||||
|
data-isactive={currentToolId === 'draw'}
|
||||||
|
onClick={() => editor.setCurrentTool('draw')}
|
||||||
|
>
|
||||||
|
Pencil
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
[1]
|
||||||
|
Use React context to store the editor at a higher place in the React component tree.
|
||||||
|
|
||||||
|
[2]
|
||||||
|
Use the `onMount` prop to get the editor instance and store it in state.
|
||||||
|
|
||||||
|
[3]
|
||||||
|
When we have an editor in state, render the context provider and its descendants.
|
||||||
|
|
||||||
|
[4]
|
||||||
|
You can access the editor from any of the provider's descendants.
|
||||||
|
*/
|
15
apps/examples/src/examples/external-ui-context/README.md
Normal file
15
apps/examples/src/examples/external-ui-context/README.md
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
---
|
||||||
|
title: External UI (Context)
|
||||||
|
component: ./ExternalUiContextExample.tsx
|
||||||
|
category: ui
|
||||||
|
---
|
||||||
|
|
||||||
|
This example shows how to control the tldraw editor from an external UI, outside of the `Tldraw` component. This example shows how to pass a reference
|
||||||
|
to the editor around using React Context.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
This example shows how to control the tldraw editor from an external UI, outside
|
||||||
|
of the `Tldraw` component. There are a few ways of doing this—for example, by putting the editor on the window object, passing it around via props, or using React context.
|
||||||
|
|
||||||
|
In this example, we use React context to distribute a reference to the editor to child components.
|
|
@ -0,0 +1,20 @@
|
||||||
|
.external-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 8px;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.external-button {
|
||||||
|
pointer-events: all;
|
||||||
|
padding: 4px 12px;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.external-button[data-isactive='true'] {
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
}
|
56
apps/examples/src/examples/external-ui/ExternalUiExample.tsx
Normal file
56
apps/examples/src/examples/external-ui/ExternalUiExample.tsx
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import { Editor, Tldraw, useValue } from '@tldraw/tldraw'
|
||||||
|
import '@tldraw/tldraw/tldraw.css'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import './external-ui.css'
|
||||||
|
|
||||||
|
// There's a guide at the bottom of this file!
|
||||||
|
|
||||||
|
export default function ExternalUiExample() {
|
||||||
|
// [1]
|
||||||
|
const [editor, setEditor] = useState<Editor | null>(null)
|
||||||
|
|
||||||
|
const currentToolId = useValue('current tool id', () => editor?.getCurrentToolId(), [editor])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ margin: 32, width: 600 }}>
|
||||||
|
<div style={{ height: 400 }}>
|
||||||
|
<Tldraw
|
||||||
|
// [2]
|
||||||
|
onMount={(editor) => setEditor(editor)}
|
||||||
|
components={{ Toolbar: null }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/* [3] */}
|
||||||
|
<div>
|
||||||
|
<div className="external-toolbar">
|
||||||
|
<button
|
||||||
|
className="external-button"
|
||||||
|
data-isactive={currentToolId === 'select'}
|
||||||
|
onClick={() => editor?.setCurrentTool('select')}
|
||||||
|
>
|
||||||
|
Select
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="external-button"
|
||||||
|
data-isactive={currentToolId === 'draw'}
|
||||||
|
onClick={() => editor?.setCurrentTool('draw')}
|
||||||
|
>
|
||||||
|
Pencil
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
[1]
|
||||||
|
Use React state to store the editor instance.
|
||||||
|
|
||||||
|
[2]
|
||||||
|
Use the `onMount` prop to get the editor instance and store it in state.
|
||||||
|
|
||||||
|
[3]
|
||||||
|
Use data from the editor instance or use the editor's methods to control the editor.
|
||||||
|
Note that these callbacks also need to work if the editor isn't mounted yet.
|
||||||
|
*/
|
14
apps/examples/src/examples/external-ui/README.md
Normal file
14
apps/examples/src/examples/external-ui/README.md
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
title: External UI
|
||||||
|
component: ./ExternalUiExample.tsx
|
||||||
|
category: ui
|
||||||
|
---
|
||||||
|
|
||||||
|
This example shows how to control the tldraw editor from an external UI, outside of the `Tldraw` component.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
This example shows how to control the tldraw editor from an external UI, outside
|
||||||
|
of the `Tldraw` component. There are a few ways of doing this—for example, by putting the editor on the window object, passing it around via props, or using React context.
|
||||||
|
|
||||||
|
In this example, we'll just put the editor instance in state and use it in the same component. See the External UI Example 2 for an alternative (and more realistic) solution using React context.
|
20
apps/examples/src/examples/external-ui/external-ui.css
Normal file
20
apps/examples/src/examples/external-ui/external-ui.css
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
.external-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 8px;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.external-button {
|
||||||
|
pointer-events: all;
|
||||||
|
padding: 4px 12px;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.external-button[data-isactive='true'] {
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
}
|
|
@ -102,6 +102,6 @@ and viewing it.
|
||||||
default, the first page is shown.
|
default, the first page is shown.
|
||||||
|
|
||||||
[3] You can customize the appearance of the image by passing other props to the
|
[3] You can customize the appearance of the image by passing other props to the
|
||||||
`TldrawImage` component. For example, you can toggle the background, set the
|
`TldrawImage` component. For example, you can toggle the background, set
|
||||||
dark mode, and specify the viewport bounds.
|
the dark mode, and specify the viewport bounds.
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in a new issue