Add inline behaviour example (#3113)
This PR adds an example demonstrating some common practices for using tldraw as an inline block. For example, in Notion-like applications. This includes: - Making sure that only one editor has focus at a time. - Always defaulting to the hand tool when you click into an editor. - Deselecting everything when an editor loses focus. - Hiding the UI when an editor is not focused. - Disabling edge scrolling by default. - Using a stripped down UI to make the most of the available space. - Removing actions from the context menu to match the stripped down UI. ### 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 **Inline behavior** example. - [ ] Unit Tests - [ ] End to end tests ### Release Notes - Docs: Added an example for inline behaviour.
This commit is contained in:
parent
a8477d00fa
commit
016dcdc56a
3 changed files with 226 additions and 1 deletions
205
apps/examples/src/examples/inline-behavior/InlineBehavior.tsx
Normal file
205
apps/examples/src/examples/inline-behavior/InlineBehavior.tsx
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
import { createContext, useContext, useState } from 'react'
|
||||||
|
import {
|
||||||
|
ArrangeMenuSubmenu,
|
||||||
|
ClipboardMenuGroup,
|
||||||
|
ConversionsMenuGroup,
|
||||||
|
ConvertToBookmarkMenuItem,
|
||||||
|
ConvertToEmbedMenuItem,
|
||||||
|
DefaultContextMenu,
|
||||||
|
EditLinkMenuItem,
|
||||||
|
Editor,
|
||||||
|
FitFrameToContentMenuItem,
|
||||||
|
GroupMenuItem,
|
||||||
|
RemoveFrameMenuItem,
|
||||||
|
ReorderMenuSubmenu,
|
||||||
|
SelectAllMenuItem,
|
||||||
|
TLUiContextMenuProps,
|
||||||
|
Tldraw,
|
||||||
|
TldrawUiMenuGroup,
|
||||||
|
ToggleAutoSizeMenuItem,
|
||||||
|
ToggleLockMenuItem,
|
||||||
|
UngroupMenuItem,
|
||||||
|
useEditor,
|
||||||
|
useValue,
|
||||||
|
} from 'tldraw'
|
||||||
|
import 'tldraw/tldraw.css'
|
||||||
|
|
||||||
|
// There's a guide at the bottom of this page!
|
||||||
|
|
||||||
|
// [1]
|
||||||
|
const focusedEditorContext = createContext(
|
||||||
|
{} as {
|
||||||
|
focusedEditor: Editor | null
|
||||||
|
setFocusedEditor: (id: Editor | null) => void
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// [2]
|
||||||
|
function blurEditor(editor: Editor) {
|
||||||
|
editor.selectNone()
|
||||||
|
editor.setCurrentTool('hand')
|
||||||
|
editor.updateInstanceState({ isFocused: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function InlineBehaviorExample() {
|
||||||
|
const [focusedEditor, setFocusedEditor] = useState<Editor | null>(null)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<focusedEditorContext.Provider value={{ focusedEditor, setFocusedEditor }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
margin: 20,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 20,
|
||||||
|
}}
|
||||||
|
// [3]
|
||||||
|
onPointerDown={() => {
|
||||||
|
if (!focusedEditor) return
|
||||||
|
blurEditor(focusedEditor)
|
||||||
|
setFocusedEditor(null)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<InlineBlock persistenceKey="block-a" />
|
||||||
|
<InlineBlock persistenceKey="block-b" />
|
||||||
|
<InlineBlock persistenceKey="block-c" />
|
||||||
|
</div>
|
||||||
|
</focusedEditorContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function InlineBlock({ persistenceKey }: { persistenceKey: string }) {
|
||||||
|
const { focusedEditor, setFocusedEditor } = useContext(focusedEditorContext)
|
||||||
|
const [editor, setEditor] = useState<Editor>()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{ width: 600, height: 400, maxWidth: '100%' }}
|
||||||
|
// [4]
|
||||||
|
onFocus={() => {
|
||||||
|
if (!editor) return
|
||||||
|
if (focusedEditor && focusedEditor !== editor) {
|
||||||
|
blurEditor(focusedEditor)
|
||||||
|
}
|
||||||
|
editor.updateInstanceState({ isFocused: true })
|
||||||
|
setFocusedEditor(editor)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tldraw
|
||||||
|
persistenceKey={persistenceKey}
|
||||||
|
autoFocus={false}
|
||||||
|
// [5]
|
||||||
|
hideUi={focusedEditor !== editor}
|
||||||
|
// [6]
|
||||||
|
components={{
|
||||||
|
HelpMenu: null,
|
||||||
|
NavigationPanel: null,
|
||||||
|
MainMenu: null,
|
||||||
|
PageMenu: null,
|
||||||
|
ContextMenu: CustomContextMenu,
|
||||||
|
}}
|
||||||
|
// [7]
|
||||||
|
onMount={(editor) => {
|
||||||
|
setEditor(editor)
|
||||||
|
editor.setCurrentTool('hand')
|
||||||
|
editor.user.updateUserPreferences({ edgeScrollSpeed: 0 })
|
||||||
|
editor.updateInstanceState({ isDebugMode: false })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// [8]
|
||||||
|
function CustomContextMenu(props: TLUiContextMenuProps) {
|
||||||
|
const editor = useEditor()
|
||||||
|
|
||||||
|
const selectToolActive = useValue(
|
||||||
|
'isSelectToolActive',
|
||||||
|
() => editor.getCurrentToolId() === 'select',
|
||||||
|
[editor]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DefaultContextMenu {...props}>
|
||||||
|
{selectToolActive && (
|
||||||
|
<>
|
||||||
|
<TldrawUiMenuGroup id="misc">
|
||||||
|
<GroupMenuItem />
|
||||||
|
<UngroupMenuItem />
|
||||||
|
<EditLinkMenuItem />
|
||||||
|
<ToggleAutoSizeMenuItem />
|
||||||
|
<RemoveFrameMenuItem />
|
||||||
|
<FitFrameToContentMenuItem />
|
||||||
|
<ConvertToEmbedMenuItem />
|
||||||
|
<ConvertToBookmarkMenuItem />
|
||||||
|
<ToggleLockMenuItem />
|
||||||
|
</TldrawUiMenuGroup>
|
||||||
|
<TldrawUiMenuGroup id="modify">
|
||||||
|
<ArrangeMenuSubmenu />
|
||||||
|
<ReorderMenuSubmenu />
|
||||||
|
{/* <MoveToPageMenu /> */}
|
||||||
|
</TldrawUiMenuGroup>
|
||||||
|
<ClipboardMenuGroup />
|
||||||
|
<ConversionsMenuGroup />
|
||||||
|
<TldrawUiMenuGroup id="select-all">
|
||||||
|
<SelectAllMenuItem />
|
||||||
|
</TldrawUiMenuGroup>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</DefaultContextMenu>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
This example demonstrates some common best practices for using tldraw as an
|
||||||
|
inline block within a larger document editor.
|
||||||
|
|
||||||
|
It includes:
|
||||||
|
|
||||||
|
- Making sure that only one editor has focus at a time.
|
||||||
|
- Always defaulting to the hand tool when you click into an editor.
|
||||||
|
- Deselecting everything when an editor loses focus.
|
||||||
|
- Hiding the UI when an editor is not focused.
|
||||||
|
- Disabling edge scrolling by default.
|
||||||
|
- Using a stripped down UI to make the most of the available space.
|
||||||
|
- Removing actions from the context menu to match the stripped down UI.
|
||||||
|
|
||||||
|
[1]
|
||||||
|
We use a context to manage which editor is currently focused. This allows us to
|
||||||
|
have multiple editors on the same page, without them interfering with each
|
||||||
|
other, or hijacking any keyboard shortcuts. For more information about handling
|
||||||
|
focus, check out the 'Multiple editors' and 'Editor focus' examples.
|
||||||
|
|
||||||
|
[2]
|
||||||
|
We have a helper function that we call on any editor that loses focus. We
|
||||||
|
deselect everything, and switch back to the hand tool, essentially 'resetting'
|
||||||
|
the user's tool state.
|
||||||
|
|
||||||
|
[3]
|
||||||
|
When the user clicks anywhere on the page outside of an editor, we blur the
|
||||||
|
currently focused editor.
|
||||||
|
|
||||||
|
[4]
|
||||||
|
When the user clicks into an editor, we focus it, and blur any other editor.
|
||||||
|
|
||||||
|
[5]
|
||||||
|
We hide the UI of any unfocused editor.
|
||||||
|
|
||||||
|
[6]
|
||||||
|
We disable many of tldraw's default UI components to make the most of the
|
||||||
|
available space. We also pass through a custom context menu component. Check out
|
||||||
|
point [8] for more information about that.
|
||||||
|
|
||||||
|
[7]
|
||||||
|
When an editor mounts, we default to the hand tool, and disable edge scrolling.
|
||||||
|
We also store a reference to the editor so that we can access it later. For the
|
||||||
|
purposes of this example, we also disable debug mode, so that you can see the
|
||||||
|
full effect of the stripped down UI.
|
||||||
|
|
||||||
|
[8]
|
||||||
|
For our custom context menu, we've copied the default context menu contents, and
|
||||||
|
we've commented out the 'Move to page' action. This is because we've removed
|
||||||
|
the Pages menu, so we don't want any drawings getting lost on a different page!
|
||||||
|
|
||||||
|
*/
|
20
apps/examples/src/examples/inline-behavior/README.md
Normal file
20
apps/examples/src/examples/inline-behavior/README.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
---
|
||||||
|
title: Inline behavior
|
||||||
|
component: ./InlineBehavior.tsx
|
||||||
|
category: ui
|
||||||
|
priority: 3
|
||||||
|
---
|
||||||
|
|
||||||
|
tldraw can be used as an inline block within a larger document editor.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
This example demonstrates some common best practices for using tldraw as an inline block within a larger editor. It includes:
|
||||||
|
|
||||||
|
- Making sure that only one editor has focus at a time.
|
||||||
|
- Always defaulting to the hand tool when you click into an editor.
|
||||||
|
- Deselecting everything when an editor loses focus.
|
||||||
|
- Hiding the UI when an editor is not focused.
|
||||||
|
- Disabling edge scrolling by default.
|
||||||
|
- Using a stripped down UI to make the most of the available space.
|
||||||
|
- Removing actions from the context menu to match the stripped down UI.
|
|
@ -4,7 +4,7 @@ import 'tldraw/tldraw.css'
|
||||||
|
|
||||||
// There's a guide at the bottom of this page!
|
// There's a guide at the bottom of this page!
|
||||||
|
|
||||||
//[1]
|
// [1]
|
||||||
const focusedEditorContext = createContext(
|
const focusedEditorContext = createContext(
|
||||||
{} as {
|
{} as {
|
||||||
focusedEditor: string | null
|
focusedEditor: string | null
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue