Annotate custom styles example (#2405)

Adds annotation to the custom styles example

### 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

- Add annotation to the custom styles example

---------

Co-authored-by: Steve Ruiz <steveruizok@gmail.com>
This commit is contained in:
Taha 2024-01-04 17:32:34 +00:00 committed by GitHub
parent c198283c78
commit d66b4af69d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 141 additions and 17 deletions

View file

@ -10,8 +10,9 @@ import {
getDefaultColorTheme,
} from '@tldraw/tldraw'
// Define a style that can be used across multiple shapes.
// The ID (myApp:filter) must be globally unique, so we recommend prefixing it with a namespace.
// There's a guide at the bottom of this file!
// [1]
export const MyFilterStyle = StyleProp.defineEnum('myApp:filter', {
defaultValue: 'none',
values: ['none', 'invert', 'grayscale', 'blur'],
@ -19,6 +20,7 @@ export const MyFilterStyle = StyleProp.defineEnum('myApp:filter', {
export type MyFilterStyle = T.TypeOf<typeof MyFilterStyle>
// [2]
export type CardShape = TLBaseShape<
'card',
{
@ -29,15 +31,15 @@ export type CardShape = TLBaseShape<
}
>
//[3]
export class CardShapeUtil extends BaseBoxShapeUtil<CardShape> {
static override type = 'card' as const
//[a]
static override props = {
w: T.number,
h: T.number,
// You can re-use tldraw built-in styles...
color: DefaultColorStyle,
// ...or your own custom styles.
filter: MyFilterStyle,
}
@ -54,6 +56,7 @@ export class CardShapeUtil extends BaseBoxShapeUtil<CardShape> {
}
}
// [b]
component(shape: CardShape) {
const bounds = this.editor.getShapeGeometry(shape).bounds
const theme = getDefaultColorTheme({ isDarkMode: this.editor.user.getIsDarkMode() })
@ -77,11 +80,11 @@ export class CardShapeUtil extends BaseBoxShapeUtil<CardShape> {
)
}
// Indicator — used when hovering over a shape or when it's selected; must return only SVG elements here
indicator(shape: CardShape) {
return <rect width={shape.props.w} height={shape.props.h} />
}
//[c]
filterStyleToCss(filter: MyFilterStyle) {
if (filter === 'invert') return 'invert(100%)'
if (filter === 'grayscale') return 'grayscale(100%)'
@ -90,17 +93,49 @@ export class CardShapeUtil extends BaseBoxShapeUtil<CardShape> {
}
}
// Extending the base box shape tool gives us a lot of functionality for free.
// [4]
export class CardShapeTool extends BaseBoxShapeTool {
static override id = 'card'
static override initial = 'idle'
override shapeType = 'card'
props = {
w: T.number,
h: T.number,
// You can re-use tldraw built-in styles...
color: DefaultColorStyle,
// ...or your own custom styles.
filter: MyFilterStyle,
}
}
/*
Introduction:
This file contains the logic for how the custom shape and style work. This guide will
mostly focus on the custom style features, for a more in-depth look at creating a custom
shape check out the custom shapes/tools example. For a closer look at creating more
custom tool interactions, checkout out the screenshot example.
[1]
This is where we define our custom style. We use the `StyleProp.defineEnum` method to
define an enum style. This will create a style that can be one of the values we pass
in to the `values` property. We also pass in a `defaultValue` property, this will be
the default value for the style. It's important that the StyleProp is unique, so we
reccomend prefixing it with your app name.
[2]
Defining our shape's type. Here we import a type for color, the default tldraw style,
and also use our own type for our custom style: filter.
[3]
This is our util, where we define the logic for our shape, it's geometry, resize behaviour
and render method.
- [a] The props for our shape. We can import a validator for the default color style
and use our own for our custom style.
- [b] The render method for our custom shape, this is where we tell the browser how
how to render our different styles using the style attribute. Using the
getDefaultColorTheme function along with the getIsDarkMode method gives us access
to the tldraw default colorsand ensures they stay up to date when switching between
light and dark mode.
We apply our filter style using a method we've defined on the shape util called
filterStyleToCss
- [c] This is our method for converting the style the user selected into CSS
Check out FilterStyleUi.tsx to see how we render the UI for our custom style.
[4]
This is our tool, it's very simple, we just define the id and initial state. Extending the
BaseBoxShapeTool gives us a lot of the default behaviour for free.
*/

View file

@ -4,9 +4,13 @@ import { CardShapeTool, CardShapeUtil } from './CardShape'
import { FilterStyleUi } from './FilterStyleUi'
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 CustomStylesExample() {
return (
<div className="tldraw__editor">
@ -21,3 +25,30 @@ export default function CustomStylesExample() {
</div>
)
}
/*
Introduction:
This example shows how to create your own custom styles to use with your shapes.
It also shows how to create a very simple ui for your styles. In this example, we
create a custom style for a card shape that lets the user apply a filter to blur,
invert or grayscale the card.
[1]
We define an array to hold the custom shape util and cusom tool. It's important to
do this outside of any React component so that this array doesn't get redefined on
every render. We'll pass this into the Tldraw component's `shapeUtils` and `tools`
props.
Check out CardShape.tsx to see how we define the shape util, tool and the custom
style.
[2]
We pass the custom shape util and tool into the Tldraw component's `shapeUtils` and
`tools` props. We also pass in the custom ui overrides, this will make an icon for
our shape/tool appear on the toolbar (see ui-overrides.ts). And render our
FilterStyleUi component inside the Tldraw component.
Check out FilterStyleUi.tsx to see how we render this Ui only when the user has
selected a shape that uses the custom style.
*/

View file

@ -1,18 +1,33 @@
import { track, useEditor } from '@tldraw/tldraw'
import { MyFilterStyle } from './CardShape'
// There's a guide at the bottom of this file!
//[1]
export const FilterStyleUi = track(function FilterStyleUi() {
const editor = useEditor()
//[2]
const filterStyle = editor.getSharedStyles().get(MyFilterStyle)
// if the filter style isn't in sharedStyles, it means it's not relevant to the current tool/selection
if (!filterStyle) return null
return (
<div style={{ position: 'absolute', zIndex: 300, top: 64, left: 12 }}>
<div
className="tlui-style-panel__wrapper"
style={{
position: 'absolute',
zIndex: 300,
top: 50,
left: 8,
padding: 15,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
filter:{' '}
<select
value={filterStyle.type === 'mixed' ? 'mixed' : filterStyle.value}
// [3]
onChange={(e) => {
editor.batch(() => {
if (editor.isIn('select')) {
@ -33,3 +48,30 @@ export const FilterStyleUi = track(function FilterStyleUi() {
</div>
)
})
/*
Introduction:
This is a an example of how to create a custom ui for your custom style. We want
to render the UI when the user has selected our card tool, or when they've selected a card
shape. Here, we've chosen a drop-down to let the user select the filter type, and we render
it in the top left corner of the editor. You could render your UI anywhere you want. Check
out the zones example to see how to render your UI in a particular zone, or the custom-ui
example if you want to redo the entire ui.
[1]
We use the `track` function to wrap our component. This makes our component reactive- it will
re-render whenever the signals it is tracking change. Check out the signia docs for more:
https://signia.tldraw.dev/docs/API/signia_react/functions/track
[2]
Here we check if the user has selected a shape that uses our custom style, or if they've
selected a tool associated with our custom style. If they haven't, we return null and don't
render anything.
[3]
Here we add an event handler for when the user changes the value of the dropdown. We use the
`batch` method to batch our changes into a single undoable action. We check if the user has
selected any shapes, and if they have, we set the style for those shapes. We also set the style
for any shapes the user creates next.
*/

View file

@ -1,5 +1,7 @@
import { TLUiMenuGroup, TLUiOverrides, menuItem, toolbarItem } from '@tldraw/tldraw'
// There's a guide at the bottom of this file!
export const uiOverrides: TLUiOverrides = {
tools(editor, tools) {
tools.card = {
@ -26,3 +28,17 @@ export const uiOverrides: TLUiOverrides = {
return keyboardShortcutsMenu
},
}
/*
Here we add our custom tool to the toolbar. 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.
For this example the icon we use is the same as the color icon. For an example
of how to add a custom icon, see the screenshot or speech-bubble examples.
*/