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!
|
||||
|
||||
//[1]
|
||||
// [1]
|
||||
const focusedEditorContext = createContext(
|
||||
{} as {
|
||||
focusedEditor: string | null
|
||||
|
|
Loading…
Reference in a new issue