import { tltime } from '@tldraw/editor' import { Slider as _Slider } from 'radix-ui' import React, { useCallback, useEffect, useState } from 'react' import { TLUiTranslationKey } from '../../hooks/useTranslation/TLUiTranslationKey' import { useDirection, useTranslation } from '../../hooks/useTranslation/useTranslation' import { hideAllTooltips, TldrawUiTooltip } from './TldrawUiTooltip' /** @public */ export interface TLUiSliderProps { min?: number steps: number value: number | null label: string title: string onValueChange(value: number): void onHistoryMark?(id: string): void 'data-testid'?: string ariaValueModifier?: number } /** @public @react */ export const TldrawUiSlider = React.forwardRef(function Slider( { onHistoryMark, title, min, steps, value, label, onValueChange, ['data-testid']: testId, ariaValueModifier = 1, }: TLUiSliderProps, ref ) { const msg = useTranslation() const dir = useDirection() const [titleAndLabel, setTitleAndLabel] = useState('') // XXX: Radix starts out our slider with a tabIndex of 0 // This causes some tab focusing issues, most prevelant in MobileStylePanel, // where it grabs the focus. This works around it. const [tabIndex, setTabIndex] = useState(-1) useEffect(() => { setTabIndex(0) }, []) const handleValueChange = useCallback( (value: number[]) => { onValueChange(value[0]) }, [onValueChange] ) const handlePointerDown = useCallback(() => { hideAllTooltips() onHistoryMark?.('click slider') }, [onHistoryMark]) // N.B. This is a bit silly. The Radix slider auto-focuses which // triggers TldrawUiTooltip handleFocus when we dbl-click to edit an image, // which in turn makes the tooltip display prematurely. // This makes it wait until we've focused to show the tooltip. useEffect(() => { const timeout = tltime.setTimeout( 'set title and label', () => { setTitleAndLabel(title + ' — ' + msg(label as TLUiTranslationKey)) }, 0 ) return () => clearTimeout(timeout) }, [label, msg, title]) // N.B. Annoying. For a11y purposes, we need Tab to work. // For some reason, Radix has some custom behavior here // that interferes with tabbing past the slider and then // you get stuck in the slider. const handleKeyEvent = useCallback((event: React.KeyboardEvent) => { if (event.key === 'Tab') { event.stopPropagation() } }, []) return (
<_Slider.Root data-testid={testId} className="tlui-slider" dir={dir} min={min ?? 0} max={steps} step={1} value={value !== null ? [value] : undefined} onPointerDown={handlePointerDown} onValueChange={handleValueChange} onKeyDownCapture={handleKeyEvent} onKeyUpCapture={handleKeyEvent} > <_Slider.Track className="tlui-slider__track" dir={dir}> {value !== null && <_Slider.Range className="tlui-slider__range" dir={dir} />} {value !== null && ( <_Slider.Thumb aria-valuemin={(min ?? 0) * ariaValueModifier} aria-valuenow={value * ariaValueModifier} aria-valuemax={steps * ariaValueModifier} aria-label={titleAndLabel} className="tlui-slider__thumb" dir={dir} ref={ref} tabIndex={tabIndex} /> )}
) })