tldraw/apps/examples/src/16-custom-styles/CardShape.tsx
Steve Ruiz 910be6073f
[refactor] reduce dependencies on shape utils in editor (#1693)
We'd like to make the @tldraw/editor layer more independent of specific
shapes. Unfortunately there are many places where shape types and
certain shape behavior is deeply embedded in the Editor. This PR begins
to refactor out dependencies between the editor library and shape utils.

It does this in two ways:
- removing shape utils from the arguments of `isShapeOfType`, replacing
with a generic
- removing shape utils from the arguments of `getShapeUtil`, replacing
with a generic
- moving custom arrow info cache out of the util and into the editor
class
- changing the a tool's `shapeType` to be a string instead of a shape
util

We're here trading type safety based on inferred types—"hey editor, give
me your instance of this shape util class"—for knowledge at the point of
call—"hey editor, give me a shape util class of this type; and trust me
it'll be an instance this shape util class". Likewise for shapes.

### A note on style 

We haven't really established our conventions or style when it comes to
types, but I'm increasingly of the opinion that we should defer to the
point of call to narrow a type based on generics (keeping the types in
typescript land) rather than using arguments, which blur into JavaScript
land.

### Change Type

- [x] `major` — Breaking change

### Test Plan

- [x] Unit Tests

### Release Notes

- removes shape utils from the arguments of `isShapeOfType`, replacing
with a generic
- removes shape utils from the arguments of `getShapeUtil`, replacing
with a generic
- moves custom arrow info cache out of the util and into the editor
class
- changes the a tool's `shapeType` to be a string instead of a shape
util
2023-07-07 13:56:31 +00:00

103 lines
2.6 KiB
TypeScript

import {
BaseBoxShapeTool,
BaseBoxShapeUtil,
DefaultColorStyle,
HTMLContainer,
StyleProp,
TLBaseShape,
TLDefaultColorStyle,
defineShape,
getDefaultColorTheme,
} from '@tldraw/tldraw'
import { T } from '@tldraw/validate'
// 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.
export const MyFilterStyle = StyleProp.defineEnum('myApp:filter', {
defaultValue: 'none',
values: ['none', 'invert', 'grayscale', 'blur'],
})
export type MyFilterStyle = T.TypeOf<typeof MyFilterStyle>
export type CardShape = TLBaseShape<
'card',
{
w: number
h: number
color: TLDefaultColorStyle
filter: MyFilterStyle
}
>
export class CardShapeUtil extends BaseBoxShapeUtil<CardShape> {
static override type = 'card' as const
override isAspectRatioLocked = (_shape: CardShape) => false
override canResize = (_shape: CardShape) => true
override canBind = (_shape: CardShape) => true
override getDefaultProps(): CardShape['props'] {
return {
w: 300,
h: 300,
color: 'black',
filter: 'none',
}
}
component(shape: CardShape) {
const bounds = this.editor.getBounds(shape)
const theme = getDefaultColorTheme(this.editor)
return (
<HTMLContainer
id={shape.id}
style={{
border: `4px solid ${theme[shape.props.color].solid}`,
borderRadius: 4,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
pointerEvents: 'all',
filter: this.filterStyleToCss(shape.props.filter),
backgroundColor: theme[shape.props.color].semi,
}}
>
🍇🫐🍏🍋🍊🍒 {bounds.w.toFixed()}x{bounds.h.toFixed()} 🍒🍊🍋🍏🫐🍇
</HTMLContainer>
)
}
// 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} />
}
filterStyleToCss(filter: MyFilterStyle) {
if (filter === 'invert') return 'invert(100%)'
if (filter === 'grayscale') return 'grayscale(100%)'
if (filter === 'blur') return 'blur(10px)'
return 'none'
}
}
// 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'
override shapeType = 'card'
}
export const CardShape = defineShape('card', {
util: CardShapeUtil,
// to use a style prop, you need to describe all the props in your shape.
props: {
w: T.number,
h: T.number,
// You can re-use tldraw built-in styles...
color: DefaultColorStyle,
// ...or your own custom styles.
filter: MyFilterStyle,
},
})