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:
parent
c198283c78
commit
d66b4af69d
4 changed files with 141 additions and 17 deletions
|
@ -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.
|
||||
|
||||
*/
|
||||
|
|
|
@ -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.
|
||||
|
||||
*/
|
||||
|
|
|
@ -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.
|
||||
|
||||
*/
|
||||
|
|
|
@ -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.
|
||||
|
||||
*/
|
||||
|
|
Loading…
Reference in a new issue