Skip to content

Commit 2b8b502

Browse files
authored
Flatten color theme object (tldraw#6466)
This PR flattens the color theme object and introduces a new helper, `getColorValue`, that helps pull a color from the theme. This should actually make tldraw#6462 (and other color-related work) much more practical. ## Flattening `DefaultColorThemePalette` This PR flattens the `DefaultColorThemePalette`, removing the nested properties and renaming them instead. ```ts theme.black.frame.fill ``` Becomes ```ts theme.black.frameFill ``` ## `getColorValue` This pull request standardizes and simplifies how color values are accessed throughout the codebase by introducing and consistently using the new `getColorValue` utility function. This change replaces direct property access on the `theme` object with calls to `getColorValue`, improving maintainability and reducing the risk of errors due to inconsistent color property keys. The update affects a wide range of shape utilities, rendering logic, and example components. ```ts theme.black.frameFill ``` Becomes ```ts getColorValue(theme, 'black', 'frameFill') ``` **Refactoring color value access:** * Replaced direct `theme` property access with `getColorValue` for all shape utilities, including `ArrowShapeUtil`, `DrawShapeUtil`, `GeoShapeUtil`, `FrameShapeUtil`, `HighlightShapeUtil`, `LineShapeUtil`, and `NoteShapeUtil`, ensuring consistent and safer color retrieval. * Updated SVG export logic to use `getColorValue` for determining frame background color. * Added `getColorValue` to imports in all affected files to support the new color access pattern. * Updated example components (`CardShapeUtil`, `CustomRenderer`, `ShapeWithTldrawStylesExample`, `SpeechBubbleUtil`) to use `getColorValue` instead of direct theme property access for colors in rendering logic. These changes collectively improve code consistency, make color logic more robust, and pave the way for easier updates or extensions to color theming in the future. ### Change type - [ ] `bugfix` - [x] `improvement` - [ ] `feature` - [x] `api` - [ ] `other` ### API changes - Changes to the `DefaultColorThemePalette` object. Any color customization may need to be refactored.
1 parent da3348c commit 2b8b502

21 files changed

Lines changed: 367 additions & 476 deletions

File tree

apps/examples/src/examples/custom-config/CardShape/CardShapeUtil.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
Rectangle2d,
55
ShapeUtil,
66
TLResizeInfo,
7+
getColorValue,
78
getDefaultColorTheme,
89
resizeBox,
910
} from 'tldraw'
@@ -65,8 +66,8 @@ export class CardShapeUtil extends ShapeUtil<ICardShape> {
6566
alignItems: 'center',
6667
justifyContent: 'center',
6768
pointerEvents: 'all',
68-
backgroundColor: theme[shape.props.color].semi,
69-
color: theme[shape.props.color].solid,
69+
backgroundColor: getColorValue(theme, shape.props.color, 'semi'),
70+
color: getColorValue(theme, shape.props.color, 'solid'),
7071
}}
7172
>
7273
<h2>Clicks: {count}</h2>

apps/examples/src/examples/custom-renderer/CustomRenderer.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useLayoutEffect, useRef } from 'react'
2-
import { TLDrawShape, TLGeoShape, getDefaultColorTheme, useEditor } from 'tldraw'
2+
import { TLDrawShape, TLGeoShape, getColorValue, getDefaultColorTheme, useEditor } from 'tldraw'
33

44
export function CustomRenderer() {
55
const editor = useEditor()
@@ -70,17 +70,17 @@ export function CustomRenderer() {
7070
}
7171
}
7272
}
73-
ctx.strokeStyle = theme[shape.props.color].solid
73+
ctx.strokeStyle = getColorValue(theme, shape.props.color, 'solid')
7474
ctx.lineWidth = 4
7575
ctx.stroke()
7676
if (shape.props.fill !== 'none' && shape.props.isClosed) {
77-
ctx.fillStyle = theme[shape.props.color].semi
77+
ctx.fillStyle = getColorValue(theme, shape.props.color, 'semi')
7878
ctx.fill()
7979
}
8080
} else if (editor.isShapeOfType<TLGeoShape>(shape, 'geo')) {
8181
// Draw a geo shape
8282
const bounds = editor.getShapeGeometry(shape).bounds
83-
ctx.strokeStyle = theme[shape.props.color].solid
83+
ctx.strokeStyle = getColorValue(theme, shape.props.color, 'solid')
8484
ctx.lineWidth = 2
8585
ctx.strokeRect(bounds.minX, bounds.minY, bounds.width, bounds.height)
8686
} else {

apps/examples/src/examples/shape-with-tldraw-styles/ShapeWithTldrawStylesExample.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
BaseBoxShapeUtil,
33
DefaultColorStyle,
44
DefaultSizeStyle,
5+
getColorValue,
56
HTMLContainer,
67
T,
78
TLBaseShape,
@@ -65,7 +66,7 @@ class MyShapeUtil extends BaseBoxShapeUtil<IMyShape> {
6566
style={{
6667
// [3]
6768
fontSize: FONT_SIZES[shape.props.size],
68-
color: theme[shape.props.color].solid,
69+
color: getColorValue(theme, shape.props.color, 'solid'),
6970
}}
7071
>
7172
Select the shape and use the style panel to change the font size and color

apps/examples/src/examples/speech-bubble/SpeechBubble/SpeechBubbleUtil.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
TLResizeInfo,
2020
Vec,
2121
ZERO_INDEX_KEY,
22+
getColorValue,
2223
resizeBox,
2324
structuredClone,
2425
useDefaultColorTheme,
@@ -189,7 +190,7 @@ export class SpeechBubbleUtil extends ShapeUtil<SpeechBubbleShape> {
189190
<path
190191
d={pathData}
191192
strokeWidth={STROKE_SIZES[size]}
192-
stroke={theme[color].solid}
193+
stroke={getColorValue(theme, color, 'solid')}
193194
fill={'none'}
194195
/>
195196
</svg>
@@ -203,7 +204,7 @@ export class SpeechBubbleUtil extends ShapeUtil<SpeechBubbleShape> {
203204
align={align}
204205
verticalAlign="start"
205206
text={text}
206-
labelColor={theme[color].solid}
207+
labelColor={getColorValue(theme, color, 'solid')}
207208
isSelected={isSelected}
208209
wrap
209210
/>

packages/editor/src/lib/exports/getSvgJsx.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
TLGroupShape,
55
TLShape,
66
TLShapeId,
7+
getColorValue,
78
getDefaultColorTheme,
89
} from '@tldraw/tlschema'
910
import { hasOwnProperty, promiseWithResolve, uniqueId } from '@tldraw/utils'
@@ -373,8 +374,7 @@ function SvgExport({
373374
| { options: { showColors: boolean } }
374375
if (frameShapeUtil?.options.showColors) {
375376
const shape = editor.getShape(singleFrameShapeId)! as TLFrameShape
376-
const color = theme[shape.props.color]
377-
backgroundColor = color.frame.fill
377+
backgroundColor = getColorValue(theme, shape.props.color, 'frameFill')
378378
} else {
379379
backgroundColor = theme.solid
380380
}

packages/tldraw/src/lib/shapes/arrow/ArrowShapeUtil.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
clamp,
3232
debugFlags,
3333
exhaustiveSwitchError,
34+
getColorValue,
3435
getDefaultColorTheme,
3536
getFontsFromRichText,
3637
invLerp,
@@ -785,8 +786,8 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
785786
lineHeight={TEXT_PROPS.lineHeight}
786787
align="middle"
787788
verticalAlign="middle"
789+
labelColor={getColorValue(theme, shape.props.labelColor, 'solid')}
788790
richText={shape.props.richText}
789-
labelColor={theme[shape.props.labelColor].solid}
790791
textWidth={labelPosition.box.w - ARROW_LABEL_PADDING * 2 * shape.props.scale}
791792
isSelected={isSelected}
792793
padding={0}
@@ -934,8 +935,8 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
934935
font={shape.props.font}
935936
align="middle"
936937
verticalAlign="middle"
938+
labelColor={getColorValue(theme, shape.props.labelColor, 'solid')}
937939
richText={shape.props.richText}
938-
labelColor={theme[shape.props.labelColor].solid}
939940
bounds={getArrowLabelPosition(this.editor, shape)
940941
.box.clone()
941942
.expandBy(-ARROW_LABEL_PADDING * shape.props.scale)}
@@ -1077,7 +1078,7 @@ const ArrowSvg = track(function ArrowSvg({
10771078
</defs>
10781079
<g
10791080
fill="none"
1080-
stroke={theme[shape.props.color].solid}
1081+
stroke={getColorValue(theme, shape.props.color, 'solid')}
10811082
strokeWidth={strokeWidth}
10821083
strokeLinejoin="round"
10831084
strokeLinecap="round"

packages/tldraw/src/lib/shapes/draw/DrawShapeUtil.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
VecLike,
1515
drawShapeMigrations,
1616
drawShapeProps,
17+
getColorValue,
1718
last,
1819
lerp,
1920
rng,
@@ -289,7 +290,7 @@ function DrawShapeSvg({ shape, zoomOverride }: { shape: TLDrawShape; zoomOverrid
289290
<path
290291
d={svgInk(allPointsFromSegments, options)}
291292
strokeLinecap="round"
292-
fill={theme[shape.props.color].solid}
293+
fill={getColorValue(theme, shape.props.color, 'solid')}
293294
/>
294295
</>
295296
)
@@ -313,8 +314,8 @@ function DrawShapeSvg({ shape, zoomOverride }: { shape: TLDrawShape; zoomOverrid
313314
<path
314315
d={solidStrokePath}
315316
strokeLinecap="round"
316-
fill={isDot ? theme[shape.props.color].solid : 'none'}
317-
stroke={theme[shape.props.color].solid}
317+
fill={isDot ? getColorValue(theme, shape.props.color, 'solid') : 'none'}
318+
stroke={getColorValue(theme, shape.props.color, 'solid')}
318319
strokeWidth={sw}
319320
strokeDasharray={isDot ? 'none' : getDrawShapeStrokeDashArray(shape, sw, dotAdjustment)}
320321
strokeDashoffset="0"

packages/tldraw/src/lib/shapes/frame/FrameShapeUtil.tsx

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
compact,
2020
frameShapeMigrations,
2121
frameShapeProps,
22+
getColorValue,
2223
getDefaultColorTheme,
2324
lerp,
2425
resizeBox,
@@ -220,13 +221,12 @@ export class FrameShapeUtil extends BaseBoxShapeUtil<TLFrameShape> {
220221
)
221222

222223
const showFrameColors = this.options.showColors
223-
224-
const color = theme[shape.props.color]
225-
const frameFill = showFrameColors ? color.frame.fill : theme.black.frame.fill
226-
const frameStroke = showFrameColors ? color.frame.stroke : theme.black.frame.stroke
227-
const frameHeadingStroke = showFrameColors ? color.frame.headingStroke : theme.background
228-
const frameHeadingFill = showFrameColors ? color.frame.headingFill : theme.background
229-
const frameHeadingText = showFrameColors ? color.frame.text : theme.text
224+
const colorToUse = showFrameColors ? shape.props.color : 'black'
225+
const frameFill = getColorValue(theme, colorToUse, 'frameFill')
226+
const frameStroke = getColorValue(theme, colorToUse, 'frameStroke')
227+
const frameHeadingStroke = getColorValue(theme, colorToUse, 'frameHeadingStroke')
228+
const frameHeadingFill = getColorValue(theme, colorToUse, 'frameHeadingFill')
229+
const frameHeadingText = getColorValue(theme, colorToUse, 'frameText')
230230

231231
return (
232232
<>
@@ -277,13 +277,12 @@ export class FrameShapeUtil extends BaseBoxShapeUtil<TLFrameShape> {
277277
const text = createTextJsxFromSpans(this.editor, spans, opts)
278278

279279
const showFrameColors = this.options.showColors
280-
281-
const color = theme[shape.props.color]
282-
const frameFill = showFrameColors ? color.frame.fill : theme.black.frame.fill
283-
const frameStroke = showFrameColors ? color.frame.stroke : theme.black.frame.stroke
284-
const frameHeadingStroke = showFrameColors ? color.frame.headingStroke : theme.background
285-
const frameHeadingFill = showFrameColors ? color.frame.headingFill : theme.background
286-
const frameHeadingText = showFrameColors ? color.frame.text : theme.text
280+
const colorToUse = showFrameColors ? shape.props.color : 'black'
281+
const frameFill = getColorValue(theme, colorToUse, 'frameFill')
282+
const frameStroke = getColorValue(theme, colorToUse, 'frameStroke')
283+
const frameHeadingStroke = getColorValue(theme, colorToUse, 'frameHeadingStroke')
284+
const frameHeadingFill = getColorValue(theme, colorToUse, 'frameHeadingFill')
285+
const frameHeadingText = getColorValue(theme, colorToUse, 'frameText')
287286

288287
return (
289288
<>

packages/tldraw/src/lib/shapes/geo/GeoShapeUtil.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
exhaustiveSwitchError,
1919
geoShapeMigrations,
2020
geoShapeProps,
21+
getColorValue,
2122
getDefaultColorTheme,
2223
getFontsFromRichText,
2324
isEqual,
@@ -220,7 +221,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
220221
verticalAlign={verticalAlign}
221222
richText={richText}
222223
isSelected={isOnlySelected}
223-
labelColor={theme[props.labelColor].solid}
224+
labelColor={getColorValue(theme, props.labelColor, 'solid')}
224225
wrap
225226
/>
226227
</HTMLContainer>
@@ -278,7 +279,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
278279
align={props.align}
279280
verticalAlign={props.verticalAlign}
280281
richText={props.richText}
281-
labelColor={theme[props.labelColor].solid}
282+
labelColor={getColorValue(theme, props.labelColor, 'solid')}
282283
bounds={bounds}
283284
padding={LABEL_PADDING}
284285
/>

packages/tldraw/src/lib/shapes/geo/components/GeoShapeBody.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { TLGeoShape } from '@tldraw/editor'
1+
import { getColorValue, TLGeoShape } from '@tldraw/editor'
22
import { ShapeFill } from '../../shared/ShapeFill'
33
import { STROKE_SIZES } from '../../shared/default-shape-constants'
44
import { useDefaultColorTheme } from '../../shared/useDefaultColorTheme'
@@ -33,7 +33,7 @@ export function GeoShapeBody({
3333
strokeWidth,
3434
forceSolid,
3535
randomSeed: shape.id,
36-
props: { fill: 'none', stroke: theme[color].solid },
36+
props: { fill: 'none', stroke: getColorValue(theme, color, 'solid') },
3737
})}
3838
</>
3939
)

0 commit comments

Comments
 (0)