import { StylePanelSection, StylePanelSubheading, StylePanelColorPicker, StylePanelDashPicker, StylePanelFillPicker, StylePanelGeoShapePicker, StylePanelSizePicker, StylePanelSplinePicker, StylePanelFontPicker, StylePanelTextAlignPicker, StylePanelArrowheadPicker, StylePanelArrowKindPicker, TldrawUiSlider, StylePanelContextProvider, TLUiStylePanelProps, useEditor, track, getDefaultColorTheme, TldrawUiButton, TldrawUiButtonCheck } from 'tldraw' import { useState } from 'react' import { getBrushOpacityForTool, updateBrushOpacityForTool, toolBrushOpacityAtom } from '../utils/brushUtils' import { getEraserSettings } from '../utils/eraserUtils' const COLORS = [ 'black', 'grey', 'light-grey', 'white', 'blue', 'light-blue', 'turquoise', 'green', 'light-green', 'yellow', 'orange', 'light-red', 'red', 'light-violet', 'violet' ] export const CustomStylePanel = track((props: TLUiStylePanelProps) => { const editor = useEditor() const styles = props.styles ?? editor.getSharedStyles() const selectedShapes = editor.getSelectedShapes() const toolId = editor.getCurrentToolId() const toolBrush = getBrushOpacityForTool(toolId) const isGeo = selectedShapes.some(s => s.type === 'geo') const hasText = selectedShapes.some(s => 'text' in s.props || s.type === 'text') const isArrow = selectedShapes.some(s => s.type === 'arrow' || s.type === 'line') // Eraser Settings const [eraserSettings, setEraserSettings] = useState(getEraserSettings) const updateEraserSetting = (key: string) => { const newSettings = { ...eraserSettings, [key]: !(eraserSettings as any)[key] } setEraserSettings(newSettings) localStorage.setItem('tldraw_eraser_settings', JSON.stringify(newSettings)) } // Drawing tools don't use fill const drawingTools = ['draw', 'highlight', 'eraser', 'laser'] const isDrawingTool = drawingTools.includes(toolId) && selectedShapes.length === 0 // Get color theme for swatches const theme = getDefaultColorTheme({ isDarkMode: editor.user.getIsDarkMode() }) const getOpacity = (key: 'borderOpacity' | 'fillOpacity', defaultValue: number) => { if (selectedShapes.length === 0) { const toolVal = toolBrush[key] return toolVal !== undefined ? toolVal : defaultValue } const values = selectedShapes.map(s => (s.meta[key] as number) ?? defaultValue) return values.every(v => v === values[0]) ? values[0] : defaultValue } const borderOpacity = getOpacity('borderOpacity', 1.0) const fillOpacity = getOpacity('fillOpacity', 0.6) const handleOpacityChange = (key: 'borderOpacity' | 'fillOpacity', value: number) => { updateBrushOpacityForTool(toolId, { [key]: value }) if (selectedShapes.length > 0) { editor.updateShapes(selectedShapes.map(s => ({ id: s.id, type: s.type, meta: { ...s.meta, [key]: value } }))) } } // Get/set fill color (stored in meta.fillColor) const getFillColor = () => { if (selectedShapes.length === 0) return 'black' const colors = selectedShapes.map(s => (s.meta.fillColor as string) || 'black') return colors.every(c => c === colors[0]) ? colors[0] : 'black' } const setFillColor = (color: string) => { if (selectedShapes.length > 0) { editor.updateShapes(selectedShapes.map(s => ({ id: s.id, type: s.type, meta: { ...s.meta, fillColor: color } }))) } } return (
{toolId === 'eraser' && ( Erase Only
{[ { label: 'Scribble', key: 'scribble' }, { label: 'Text', key: 'text' }, { label: 'Shapes', key: 'shapes' }, { label: 'Images', key: 'images' } ].map(({ label, key }) => ( updateEraserSetting(key)} style={{ justifyContent: 'space-between', width: '100%', padding: '4px 8px', height: '32px', background: (eraserSettings as any)[key] ? 'var(--color-selected-primary)' : 'transparent', color: (eraserSettings as any)[key] ? 'var(--color-selected-contrast)' : 'inherit', }} > {label} ))}
)} {isGeo && ( Shape )} {isArrow && } Stroke handleOpacityChange('borderOpacity', value / 100)} min={0} steps={100} label="Stroke Opacity" title="Stroke Opacity" /> {isGeo && !isDrawingTool && ( Fill
{COLORS.map(color => { const currentFill = getFillColor() const isActive = currentFill === color const themeColor = (theme as any)[color] const colorValue = themeColor?.solid || '#000' return (
handleOpacityChange('fillOpacity', value / 100)} min={0} steps={100} label="Fill Opacity" title="Fill Opacity" />
)} {hasText && ( Text )} {isArrow && ( Arrow )}
) }) export function getInitialMetaForOpacity(editor: any) { const toolId = editor.getCurrentToolId() const all = toolBrushOpacityAtom.get() const brush = all[toolId] || all.default return { fillOpacity: brush.fillOpacity, borderOpacity: brush.borderOpacity, fillColor: 'black', // Default fill color } }