import { ArrowShapeArrowheadEndStyle, ArrowShapeArrowheadStartStyle, ArrowShapeKindStyle, DefaultColorStyle, DefaultDashStyle, DefaultFillStyle, DefaultFontStyle, DefaultHorizontalAlignStyle, DefaultSizeStyle, DefaultTextAlignStyle, DefaultVerticalAlignStyle, GeoShapeGeoStyle, kickoutOccludedShapes, LineShapeSplineStyle, minBy, TLArrowShapeArrowheadStyle, useEditor, useValue, } from '@tldraw/editor' import React from 'react' import { GeoShapeUtil } from '../../../shapes/geo/GeoShapeUtil' import { defaultGeoTypeDefinitions, GeoTypeDefinition } from '../../../shapes/geo/getGeoShapePath' import { getColorStyleItems, getFontStyleItems, STYLES } from '../../../styles' import { useTranslation } from '../../hooks/useTranslation/useTranslation' import { TldrawUiButtonIcon } from '../primitives/Button/TldrawUiButtonIcon' import { TldrawUiSlider } from '../primitives/TldrawUiSlider' import { TldrawUiToolbar, TldrawUiToolbarButton } from '../primitives/TldrawUiToolbar' import { StylePanelButtonPicker, StylePanelButtonPickerInline } from './StylePanelButtonPicker' import { useStylePanelContext } from './StylePanelContext' import { StylePanelDoubleDropdownPicker } from './StylePanelDoubleDropdownPicker' import { StylePanelDropdownPicker, StylePanelDropdownPickerInline, } from './StylePanelDropdownPicker' import { StylePanelSubheading } from './StylePanelSubheading' /** @public @react */ export function DefaultStylePanelContent() { return ( <> ) } /** @public */ export interface StylePanelSectionProps { children: React.ReactNode } /** @public @react */ export function StylePanelSection({ children }: StylePanelSectionProps) { return
{children}
} /** @public @react */ export function StylePanelColorPicker() { const editor = useEditor() const { styles } = useStylePanelContext() const msg = useTranslation() const color = styles.get(DefaultColorStyle) const items = useValue( 'style panel color items', () => getColorStyleItems(editor.getCurrentTheme().colors[editor.getColorMode()]), [editor] ) if (color === undefined) return null return ( ) } const tldrawSupportedOpacities = [0.1, 0.25, 0.5, 0.75, 1] as const /** @public @react */ export function StylePanelOpacityPicker() { const editor = useEditor() const { onHistoryMark, onOpacityChange, enhancedA11yMode } = useStylePanelContext() const opacity = useValue('opacity', () => editor.getSharedOpacity(), [editor]) const msg = useTranslation() const handleOpacityValueChange = React.useCallback( (value: number) => { onOpacityChange(tldrawSupportedOpacities[value]) }, [onOpacityChange] ) if (opacity === undefined) return null const opacityIndex = opacity.type === 'mixed' ? -1 : tldrawSupportedOpacities.indexOf( minBy(tldrawSupportedOpacities, (supportedOpacity) => Math.abs(supportedOpacity - opacity.value) )! ) return ( <> {enhancedA11yMode && ( {msg('style-panel.opacity')} )} = 0 ? opacityIndex : tldrawSupportedOpacities.length - 1} label={opacity.type === 'mixed' ? 'style-panel.mixed' : `opacity-style.${opacity.value}`} onValueChange={handleOpacityValueChange} steps={tldrawSupportedOpacities.length - 1} title={msg('style-panel.opacity')} onHistoryMark={onHistoryMark} ariaValueModifier={25} /> ) } /** @public @react */ export function StylePanelFillPicker() { const { styles, enhancedA11yMode } = useStylePanelContext() const msg = useTranslation() const fill = styles.get(DefaultFillStyle) if (fill === undefined) return null const title = msg('style-panel.fill') return ( <> {enhancedA11yMode && {title}} ) } /** @public @react */ export function StylePanelDashPicker() { const { styles } = useStylePanelContext() const msg = useTranslation() const dash = styles.get(DefaultDashStyle) if (dash === undefined) return null return ( ) } /** @public @react */ export function StylePanelSizePicker() { const editor = useEditor() const { styles, onValueChange } = useStylePanelContext() const msg = useTranslation() const size = styles.get(DefaultSizeStyle) if (size === undefined) return null return ( { onValueChange(style, value) const selectedShapeIds = editor.getSelectedShapeIds() if (selectedShapeIds.length > 0) { kickoutOccludedShapes(editor, selectedShapeIds) } }} /> ) } /** @public @react */ export function StylePanelFontPicker() { const editor = useEditor() const { styles } = useStylePanelContext() const msg = useTranslation() const font = styles.get(DefaultFontStyle) const items = useValue( 'style panel font items', () => getFontStyleItems(editor.getCurrentTheme()), [editor] ) if (font === undefined) return null return ( ) } /** @public @react */ export function StylePanelTextAlignPicker() { const { styles, enhancedA11yMode } = useStylePanelContext() const msg = useTranslation() const textAlign = styles.get(DefaultTextAlignStyle) if (textAlign === undefined) return null const title = msg('style-panel.align') return ( <> {enhancedA11yMode && {title}} ) } /** @public @react */ export function StylePanelLabelAlignPicker() { const { styles, enhancedA11yMode } = useStylePanelContext() const msg = useTranslation() const labelAlign = styles.get(DefaultHorizontalAlignStyle) const verticalLabelAlign = styles.get(DefaultVerticalAlignStyle) if (labelAlign === undefined) return null const title = msg('style-panel.label-align') return ( <> {enhancedA11yMode && {title}} {verticalLabelAlign === undefined ? ( ) : ( )} ) } /** @public @react */ export function StylePanelGeoShapePicker() { const editor = useEditor() const { styles } = useStylePanelContext() const geo = styles.get(GeoShapeGeoStyle) if (geo === undefined) return null const customGeoTypes = editor.getShapeUtil('geo').options.customGeoTypes const merged: Record = { ...defaultGeoTypeDefinitions, ...customGeoTypes, } const items = Object.entries(merged).map(([value, def]) => ({ value, icon: def.icon })) return ( ) } /** @public @react */ export function StylePanelArrowKindPicker() { const { styles } = useStylePanelContext() const arrowKind = styles.get(ArrowShapeKindStyle) if (arrowKind === undefined) return null return ( ) } /** @public @react */ export function StylePanelArrowheadPicker() { const { styles } = useStylePanelContext() const arrowheadEnd = styles.get(ArrowShapeArrowheadEndStyle) const arrowheadStart = styles.get(ArrowShapeArrowheadStartStyle) if (arrowheadEnd === undefined || arrowheadStart === undefined) return null return ( label={'style-panel.arrowheads'} uiTypeA="arrowheadStart" styleA={ArrowShapeArrowheadStartStyle} itemsA={STYLES.arrowheadStart} valueA={arrowheadStart} uiTypeB="arrowheadEnd" styleB={ArrowShapeArrowheadEndStyle} itemsB={STYLES.arrowheadEnd} valueB={arrowheadEnd} labelA="style-panel.arrowhead-start" labelB="style-panel.arrowhead-end" /> ) } /** @public @react */ export function StylePanelSplinePicker() { const { styles } = useStylePanelContext() const spline = styles.get(LineShapeSplineStyle) if (spline === undefined) return null return ( ) }