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:
Taha 2024-01-04 17:27:22 +00:00 committed by GitHub
parent 85c5210cd1
commit c198283c78
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 103 additions and 31 deletions

View file

@ -1,7 +1,4 @@
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 {
static override id = 'card'
static override initial = 'idle'
@ -12,3 +9,12 @@ export class CardShapeTool extends BaseBoxShapeTool {
// 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.
*/

View file

@ -11,31 +11,30 @@ import { cardShapeMigrations } from './card-shape-migrations'
import { cardShapeProps } from './card-shape-props'
import { ICardShape } from './card-shape-types'
// 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.
// There's a guide at the bottom of this file!
export class CardShapeUtil extends ShapeUtil<ICardShape> {
static override type = 'card' as const
// A validation schema for the shape's props (optional)
// [1]
static override props = cardShapeProps
// Migrations for upgrading shapes (optional)
// [2]
static override migrations = cardShapeMigrations
// Flags
// [3]
override isAspectRatioLocked = (_shape: ICardShape) => false
override canResize = (_shape: ICardShape) => true
override canBind = (_shape: ICardShape) => true
// [4]
getDefaultProps(): ICardShape['props'] {
return {
w: 300,
h: 300,
color: 'black',
weight: 'regular',
}
}
// [5]
getGeometry(shape: ICardShape) {
return new Rectangle2d({
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) {
const bounds = this.editor.getShapeGeometry(shape).bounds
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
const [count, setCount] = useState(0)
@ -64,15 +63,13 @@ export class CardShapeUtil extends ShapeUtil<ICardShape> {
justifyContent: 'center',
pointerEvents: 'all',
backgroundColor: theme[shape.props.color].semi,
fontWeight: shape.props.weight,
color: theme[shape.props.color].solid,
}}
>
<h2>Clicks: {count}</h2>
<button
// [b]
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()}
>
{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) {
return <rect width={shape.props.w} height={shape.props.h} />
}
// Events
// [8]
override onResize: TLOnResizeHandler<ICardShape> = (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.
*/

View file

@ -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'
export const WeightStyle = StyleProp.defineEnum('myApp:weight', {
defaultValue: 'regular',
values: ['regular', 'bold'],
})
// Validation for our custom card shape's props, using our custom style + one of tldraw's default styles
// Validation for our custom card shape's props, using one of tldraw's default styles
export const cardShapeProps: ShapeProps<ICardShape> = {
w: T.number,
h: T.number,
color: DefaultColorStyle,
weight: WeightStyle,
}
// To generate your own custom styles, check out the custom styles example.

View file

@ -1,8 +1,5 @@
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
export type ICardShape = TLBaseShape<
'card',
@ -10,6 +7,5 @@ export type ICardShape = TLBaseShape<
w: number
h: number
color: TLDefaultColorStyle
weight: IWeightStyle
}
>

View file

@ -4,9 +4,13 @@ import { CardShapeTool } from './CardShape/CardShapeTool'
import { CardShapeUtil } from './CardShape/CardShapeUtil'
import { uiOverrides } from './ui-overrides'
// There's a guide at the bottom of this file!
// [1]
const customShapeUtils = [CardShapeUtil]
const customTools = [CardShapeTool]
// [2]
export default function CustomConfigExample() {
return (
<div className="tldraw__editor">
@ -21,3 +25,20 @@ export default function CustomConfigExample() {
</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.
*/

View file

@ -1,6 +1,6 @@
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 = {
tools(editor, tools) {
@ -31,3 +31,18 @@ export const uiOverrides: TLUiOverrides = {
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.
*/

View file

@ -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
with more custom functionality, check out the screenshot-tool example.