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 (
)
}