annotate custom config example (#2404)
Annotates the custom config example with more detail ### Change Type - [ ] `patch` — Bug fix - [ ] `minor` — New feature - [ ] `major` — Breaking change - [ ] `dependencies` — Changes to package dependencies[^1] - [x] `documentation` — Changes to the documentation only[^2] - [ ] `tests` — Changes to any test code only[^2] - [ ] `internal` — Any other changes that don't affect the published package[^2] - [ ] I don't know [^1]: publishes a `patch` release, for devDependencies use `internal` [^2]: will not publish a new version ### Test Plan 1. Add a step-by-step description of how to test your PR here. 2. - [ ] Unit Tests - [ ] End to end tests ### Release Notes - Adds annotation to the custom config example with a bit more detail
This commit is contained in:
parent
85c5210cd1
commit
c198283c78
7 changed files with 103 additions and 31 deletions
|
@ -1,7 +1,4 @@
|
||||||
import { BaseBoxShapeTool, TLClickEvent } from '@tldraw/tldraw'
|
import { BaseBoxShapeTool, TLClickEvent } from '@tldraw/tldraw'
|
||||||
|
|
||||||
// A tool used to create our custom card shapes. Extending the base
|
|
||||||
// box shape tool gives us a lot of functionality for free.
|
|
||||||
export class CardShapeTool extends BaseBoxShapeTool {
|
export class CardShapeTool extends BaseBoxShapeTool {
|
||||||
static override id = 'card'
|
static override id = 'card'
|
||||||
static override initial = 'idle'
|
static override initial = 'idle'
|
||||||
|
@ -12,3 +9,12 @@ export class CardShapeTool extends BaseBoxShapeTool {
|
||||||
// check the BaseBoxShapeTool source as an example
|
// check the BaseBoxShapeTool source as an example
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
This file contains our custom tool. The tool is a StateNode with the `id` "card".
|
||||||
|
|
||||||
|
We get a lot of functionality for free by extending the BaseBoxShapeTool. but we can
|
||||||
|
handle events in out own way by overriding methods like onDoubleClick. For an example
|
||||||
|
of a tool with more custom functionality, check out the screenshot-tool example.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
|
@ -11,31 +11,30 @@ import { cardShapeMigrations } from './card-shape-migrations'
|
||||||
import { cardShapeProps } from './card-shape-props'
|
import { cardShapeProps } from './card-shape-props'
|
||||||
import { ICardShape } from './card-shape-types'
|
import { ICardShape } from './card-shape-types'
|
||||||
|
|
||||||
// A utility class for the card shape. This is where you define
|
// There's a guide at the bottom of this file!
|
||||||
// the shape's behavior, how it renders (its component and
|
|
||||||
// indicator), and how it handles different events.
|
|
||||||
|
|
||||||
export class CardShapeUtil extends ShapeUtil<ICardShape> {
|
export class CardShapeUtil extends ShapeUtil<ICardShape> {
|
||||||
static override type = 'card' as const
|
static override type = 'card' as const
|
||||||
// A validation schema for the shape's props (optional)
|
// [1]
|
||||||
static override props = cardShapeProps
|
static override props = cardShapeProps
|
||||||
// Migrations for upgrading shapes (optional)
|
// [2]
|
||||||
static override migrations = cardShapeMigrations
|
static override migrations = cardShapeMigrations
|
||||||
|
|
||||||
// Flags
|
// [3]
|
||||||
override isAspectRatioLocked = (_shape: ICardShape) => false
|
override isAspectRatioLocked = (_shape: ICardShape) => false
|
||||||
override canResize = (_shape: ICardShape) => true
|
override canResize = (_shape: ICardShape) => true
|
||||||
override canBind = (_shape: ICardShape) => true
|
override canBind = (_shape: ICardShape) => true
|
||||||
|
|
||||||
|
// [4]
|
||||||
getDefaultProps(): ICardShape['props'] {
|
getDefaultProps(): ICardShape['props'] {
|
||||||
return {
|
return {
|
||||||
w: 300,
|
w: 300,
|
||||||
h: 300,
|
h: 300,
|
||||||
color: 'black',
|
color: 'black',
|
||||||
weight: 'regular',
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [5]
|
||||||
getGeometry(shape: ICardShape) {
|
getGeometry(shape: ICardShape) {
|
||||||
return new Rectangle2d({
|
return new Rectangle2d({
|
||||||
width: shape.props.w,
|
width: shape.props.w,
|
||||||
|
@ -44,12 +43,12 @@ export class CardShapeUtil extends ShapeUtil<ICardShape> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render method — the React component that will be rendered for the shape
|
// [6]
|
||||||
component(shape: ICardShape) {
|
component(shape: ICardShape) {
|
||||||
const bounds = this.editor.getShapeGeometry(shape).bounds
|
const bounds = this.editor.getShapeGeometry(shape).bounds
|
||||||
const theme = getDefaultColorTheme({ isDarkMode: this.editor.user.getIsDarkMode() })
|
const theme = getDefaultColorTheme({ isDarkMode: this.editor.user.getIsDarkMode() })
|
||||||
|
|
||||||
// Unfortunately eslint will think this is a class components
|
//[a]
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
const [count, setCount] = useState(0)
|
const [count, setCount] = useState(0)
|
||||||
|
|
||||||
|
@ -64,15 +63,13 @@ export class CardShapeUtil extends ShapeUtil<ICardShape> {
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
pointerEvents: 'all',
|
pointerEvents: 'all',
|
||||||
backgroundColor: theme[shape.props.color].semi,
|
backgroundColor: theme[shape.props.color].semi,
|
||||||
fontWeight: shape.props.weight,
|
|
||||||
color: theme[shape.props.color].solid,
|
color: theme[shape.props.color].solid,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<h2>Clicks: {count}</h2>
|
<h2>Clicks: {count}</h2>
|
||||||
<button
|
<button
|
||||||
|
// [b]
|
||||||
onClick={() => setCount((count) => count + 1)}
|
onClick={() => setCount((count) => count + 1)}
|
||||||
// You need to stop the pointer down event on buttons
|
|
||||||
// that should prevent shape selection or click and drag
|
|
||||||
onPointerDown={(e) => e.stopPropagation()}
|
onPointerDown={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{bounds.w.toFixed()}x{bounds.h.toFixed()}
|
{bounds.w.toFixed()}x{bounds.h.toFixed()}
|
||||||
|
@ -81,13 +78,54 @@ export class CardShapeUtil extends ShapeUtil<ICardShape> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Indicator — used when hovering over a shape or when it's selected; must return only SVG elements here
|
// [7]
|
||||||
indicator(shape: ICardShape) {
|
indicator(shape: ICardShape) {
|
||||||
return <rect width={shape.props.w} height={shape.props.h} />
|
return <rect width={shape.props.w} height={shape.props.h} />
|
||||||
}
|
}
|
||||||
|
|
||||||
// Events
|
// [8]
|
||||||
override onResize: TLOnResizeHandler<ICardShape> = (shape, info) => {
|
override onResize: TLOnResizeHandler<ICardShape> = (shape, info) => {
|
||||||
return resizeBox(shape, info)
|
return resizeBox(shape, info)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
A utility class for the card shape. This is where you define the shape's behavior,
|
||||||
|
how it renders (its component and indicator), and how it handles different events.
|
||||||
|
|
||||||
|
[1]
|
||||||
|
A validation schema for the shape's props (optional)
|
||||||
|
Check out card-shape-props.ts for more info.
|
||||||
|
|
||||||
|
[2]
|
||||||
|
Migrations for upgrading shapes (optional)
|
||||||
|
Check out card-shape-migrations.ts for more info.
|
||||||
|
|
||||||
|
[3]
|
||||||
|
Letting the editor know if the shape's aspect ratio is locked, and whether it
|
||||||
|
can be resized or bound to other shapes.
|
||||||
|
|
||||||
|
[4]
|
||||||
|
The default props the shape will be rendered with when click-creating one.
|
||||||
|
|
||||||
|
[5]
|
||||||
|
We use this to calculate the shape's geometry for hit-testing, bindings and
|
||||||
|
doing other geometric calculations.
|
||||||
|
|
||||||
|
[6]
|
||||||
|
Render method — the React component that will be rendered for the shape. It takes the
|
||||||
|
shape as an argument. HTMLContainer is just a div that's being used to wrap our text
|
||||||
|
and button. We can get the shape's bounds using our own getGeometry method.
|
||||||
|
|
||||||
|
- [a] Check it out! We can do normal React stuff here like using setState.
|
||||||
|
Annoying: eslint sometimes thinks this is a class component, but it's not.
|
||||||
|
|
||||||
|
- [b] You need to stop the pointer down event on buttons, otherwise the editor will
|
||||||
|
think you're trying to select drag the shape.
|
||||||
|
|
||||||
|
[7]
|
||||||
|
Indicator — used when hovering over a shape or when it's selected; must return only SVG elements here
|
||||||
|
|
||||||
|
[8]
|
||||||
|
Resize handler — called when the shape is resized. Sometimes you'll want to do some
|
||||||
|
custom logic here, but for our purposes, this is fine.
|
||||||
|
*/
|
||||||
|
|
|
@ -1,15 +1,11 @@
|
||||||
import { DefaultColorStyle, ShapeProps, StyleProp, T } from '@tldraw/tldraw'
|
import { DefaultColorStyle, ShapeProps, T } from '@tldraw/tldraw'
|
||||||
import { ICardShape } from './card-shape-types'
|
import { ICardShape } from './card-shape-types'
|
||||||
|
|
||||||
export const WeightStyle = StyleProp.defineEnum('myApp:weight', {
|
// Validation for our custom card shape's props, using one of tldraw's default styles
|
||||||
defaultValue: 'regular',
|
|
||||||
values: ['regular', 'bold'],
|
|
||||||
})
|
|
||||||
|
|
||||||
// Validation for our custom card shape's props, using our custom style + one of tldraw's default styles
|
|
||||||
export const cardShapeProps: ShapeProps<ICardShape> = {
|
export const cardShapeProps: ShapeProps<ICardShape> = {
|
||||||
w: T.number,
|
w: T.number,
|
||||||
h: T.number,
|
h: T.number,
|
||||||
color: DefaultColorStyle,
|
color: DefaultColorStyle,
|
||||||
weight: WeightStyle,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// To generate your own custom styles, check out the custom styles example.
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
import { TLBaseShape, TLDefaultColorStyle } from '@tldraw/tldraw'
|
import { TLBaseShape, TLDefaultColorStyle } from '@tldraw/tldraw'
|
||||||
|
|
||||||
// We'll have a custom style called weight
|
|
||||||
export type IWeightStyle = 'regular' | 'bold'
|
|
||||||
|
|
||||||
// A type for our custom card shape
|
// A type for our custom card shape
|
||||||
export type ICardShape = TLBaseShape<
|
export type ICardShape = TLBaseShape<
|
||||||
'card',
|
'card',
|
||||||
|
@ -10,6 +7,5 @@ export type ICardShape = TLBaseShape<
|
||||||
w: number
|
w: number
|
||||||
h: number
|
h: number
|
||||||
color: TLDefaultColorStyle
|
color: TLDefaultColorStyle
|
||||||
weight: IWeightStyle
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
|
@ -4,9 +4,13 @@ import { CardShapeTool } from './CardShape/CardShapeTool'
|
||||||
import { CardShapeUtil } from './CardShape/CardShapeUtil'
|
import { CardShapeUtil } from './CardShape/CardShapeUtil'
|
||||||
import { uiOverrides } from './ui-overrides'
|
import { uiOverrides } from './ui-overrides'
|
||||||
|
|
||||||
|
// There's a guide at the bottom of this file!
|
||||||
|
|
||||||
|
// [1]
|
||||||
const customShapeUtils = [CardShapeUtil]
|
const customShapeUtils = [CardShapeUtil]
|
||||||
const customTools = [CardShapeTool]
|
const customTools = [CardShapeTool]
|
||||||
|
|
||||||
|
// [2]
|
||||||
export default function CustomConfigExample() {
|
export default function CustomConfigExample() {
|
||||||
return (
|
return (
|
||||||
<div className="tldraw__editor">
|
<div className="tldraw__editor">
|
||||||
|
@ -21,3 +25,20 @@ export default function CustomConfigExample() {
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Introduction:
|
||||||
|
|
||||||
|
This example shows how to create a custom shape, and add your own icon for it to the toolbar.
|
||||||
|
Check out CardShapeUtil.tsx and CardShapeTool.tsx to see how we define the shape util and tool.
|
||||||
|
Check out ui-overrides.ts for more info on how to add your icon to the toolbar.
|
||||||
|
|
||||||
|
[1]
|
||||||
|
We define an array to hold the custom shape util and custom tool. It's important to do this outside of
|
||||||
|
any React component so that this array doesn't get redefined on every render.
|
||||||
|
|
||||||
|
[2]
|
||||||
|
Now we'll pass these arrays into the Tldraw component's props, along with our ui overrides.
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { TLUiMenuGroup, TLUiOverrides, menuItem, toolbarItem } from '@tldraw/tldraw'
|
import { TLUiMenuGroup, TLUiOverrides, menuItem, toolbarItem } from '@tldraw/tldraw'
|
||||||
|
|
||||||
// In order to see select our custom shape tool, we need to add it to the ui.
|
// There's a guide at the bottom of this file!
|
||||||
|
|
||||||
export const uiOverrides: TLUiOverrides = {
|
export const uiOverrides: TLUiOverrides = {
|
||||||
tools(editor, tools) {
|
tools(editor, tools) {
|
||||||
|
@ -31,3 +31,18 @@ export const uiOverrides: TLUiOverrides = {
|
||||||
return keyboardShortcutsMenu
|
return keyboardShortcutsMenu
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
This file contains overrides for the Tldraw UI. These overrides are used to add your custom tools
|
||||||
|
to the toolbar and the keyboard shortcuts menu.
|
||||||
|
|
||||||
|
We do this by providing a custom toolbar override to the Tldraw component. This override is a
|
||||||
|
function that takes the current editor, the default toolbar items, and the default tools.
|
||||||
|
It returns the new toolbar items. We use the toolbarItem helper to create a new toolbar item
|
||||||
|
for our custom tool. We then splice it into the toolbar items array at the 4th index. This puts
|
||||||
|
it after the eraser tool. We'll pass our overrides object into the Tldraw component's `overrides`
|
||||||
|
prop.
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
|
@ -7,7 +7,7 @@ export class SpeechBubbleTool extends BaseBoxShapeTool {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
This file contains our speech bubble tool. The tool is a StateNode with the `id` "screenshot".
|
This file contains our speech bubble tool. The tool is a StateNode with the `id` "speech-bubble".
|
||||||
|
|
||||||
We get a lot of functionality for free by extending the BaseBoxShapeTool. For an example of a tool
|
We get a lot of functionality for free by extending the BaseBoxShapeTool. For an example of a tool
|
||||||
with more custom functionality, check out the screenshot-tool example.
|
with more custom functionality, check out the screenshot-tool example.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue