import { Editor, TLNullableShapeProps, TLStyleItem, useEditor } from '@tldraw/editor'
import React, { useCallback } from 'react'
import { minBy } from '@tldraw/utils'
import { useValue } from 'signia-react'
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
import { Button } from '../primitives/Button'
import { ButtonPicker } from '../primitives/ButtonPicker'
import { Slider } from '../primitives/Slider'
import { DoubleDropdownPicker } from './DoubleDropdownPicker'
import { DropdownPicker } from './DropdownPicker'
interface StylePanelProps {
isMobile?: boolean
}
/** @internal */
export const StylePanel = function StylePanel({ isMobile }: StylePanelProps) {
const editor = useEditor()
const props = useValue('props', () => editor.props, [editor])
const opacity = useValue('opacity', () => editor.opacity, [editor])
const toolShapeType = useValue('toolShapeType', () => editor.root.current.value?.shapeType, [
editor,
])
const handlePointerOut = useCallback(() => {
if (!isMobile) {
editor.isChangingStyle = false
}
}, [editor, isMobile])
if (!props && !toolShapeType) return null
const { geo, arrowheadEnd, arrowheadStart, spline, font } = props ?? {}
const hideGeo = geo === undefined
const hideArrowHeads = arrowheadEnd === undefined && arrowheadStart === undefined
const hideSpline = spline === undefined
const hideText = font === undefined
return (
{!hideText &&
}
{!(hideGeo && hideArrowHeads && hideSpline) && (
)}
)
}
const { styles } = Editor
function useStyleChangeCallback() {
const editor = useEditor()
return React.useCallback(
(item: TLStyleItem, squashing: boolean) => {
editor.batch(() => {
editor.setProp(item.type, item.id, false, squashing)
editor.isChangingStyle = true
})
},
[editor]
)
}
const tldrawSupportedOpacities = [0.1, 0.25, 0.5, 0.75, 1] as const
function CommonStylePickerSet({
props,
opacity,
}: {
props: TLNullableShapeProps
opacity: number | null
}) {
const editor = useEditor()
const msg = useTranslation()
const handleValueChange = useStyleChangeCallback()
const handleOpacityValueChange = React.useCallback(
(value: number, ephemeral: boolean) => {
const item = tldrawSupportedOpacities[value]
editor.setOpacity(item, ephemeral)
editor.isChangingStyle = true
},
[editor]
)
const { color, fill, dash, size } = props
if (
color === undefined &&
fill === undefined &&
dash === undefined &&
size === undefined &&
opacity === undefined
) {
return null
}
const showPickers = fill !== undefined || dash !== undefined || size !== undefined
const opacityIndex =
opacity === null
? -1
: tldrawSupportedOpacities.indexOf(
minBy(tldrawSupportedOpacities, (supportedOpacity) =>
Math.abs(supportedOpacity - opacity)
)!
)
return (
<>
{color === undefined ? null : (
)}
{opacity === undefined ? null : (
= 0 ? opacityIndex : tldrawSupportedOpacities.length - 1}
label={opacity ? `opacity-style.${opacity}` : 'style-panel.mixed'}
onValueChange={handleOpacityValueChange}
steps={tldrawSupportedOpacities.length - 1}
title={msg('style-panel.opacity')}
/>
)}
{showPickers && (
{fill === undefined ? null : (
)}
{dash === undefined ? null : (
)}
{size === undefined ? null : (
)}
)}
>
)
}
function TextStylePickerSet({ props }: { props: TLNullableShapeProps }) {
const msg = useTranslation()
const handleValueChange = useStyleChangeCallback()
const { font, align, verticalAlign } = props
if (font === undefined && align === undefined) {
return null
}
return (
{font === undefined ? null : (
)}
{align === undefined ? null : (
{verticalAlign === undefined ? (
) : (
)}
)}
)
}
function GeoStylePickerSet({ props }: { props: TLNullableShapeProps }) {
const handleValueChange = useStyleChangeCallback()
const { geo } = props
if (geo === undefined) {
return null
}
return (
)
}
function SplineStylePickerSet({ props }: { props: TLNullableShapeProps }) {
const handleValueChange = useStyleChangeCallback()
const { spline } = props
if (spline === undefined) {
return null
}
return (
)
}
function ArrowheadStylePickerSet({ props }: { props: TLNullableShapeProps }) {
const handleValueChange = useStyleChangeCallback()
const { arrowheadEnd, arrowheadStart } = props
if (arrowheadEnd === undefined && arrowheadStart === undefined) {
return null
}
return (
)
}