86 lines
2.5 KiB
TypeScript
86 lines
2.5 KiB
TypeScript
import { useRef } from 'react'
|
|
import {
|
|
formatKeyframeStrength,
|
|
nudgeKeyframeStrength,
|
|
strengthFromPointer,
|
|
} from '../lib/keyframe-strength'
|
|
|
|
interface KeyframeStrengthRailProps {
|
|
strength: number
|
|
label: string
|
|
onStrengthChange: (strength: number) => void
|
|
}
|
|
|
|
export function KeyframeStrengthRail({
|
|
strength,
|
|
label,
|
|
onStrengthChange,
|
|
}: KeyframeStrengthRailProps) {
|
|
const railRef = useRef<HTMLDivElement>(null)
|
|
const draggingRef = useRef(false)
|
|
const clamped = Math.min(1, Math.max(0, strength))
|
|
const percent = formatKeyframeStrength(strength)
|
|
|
|
const applyFromClientY = (clientY: number) => {
|
|
const rect = railRef.current?.getBoundingClientRect()
|
|
if (!rect) return
|
|
onStrengthChange(strengthFromPointer(clientY, rect))
|
|
}
|
|
|
|
return (
|
|
<div
|
|
ref={railRef}
|
|
role="slider"
|
|
aria-orientation="vertical"
|
|
tabIndex={0}
|
|
data-keyframe-strength
|
|
aria-label={`Strength for keyframe at ${label}`}
|
|
aria-valuemin={0}
|
|
aria-valuemax={100}
|
|
aria-valuenow={Math.round(clamped * 100)}
|
|
aria-valuetext={percent}
|
|
title={`Strength ${percent}`}
|
|
className="absolute left-0 top-0 z-20 h-11 w-3 cursor-ns-resize touch-none"
|
|
onPointerDown={(event) => {
|
|
event.preventDefault()
|
|
event.stopPropagation()
|
|
draggingRef.current = true
|
|
event.currentTarget.setPointerCapture(event.pointerId)
|
|
applyFromClientY(event.clientY)
|
|
}}
|
|
onPointerMove={(event) => {
|
|
if (!draggingRef.current) return
|
|
event.stopPropagation()
|
|
applyFromClientY(event.clientY)
|
|
}}
|
|
onPointerUp={(event) => {
|
|
draggingRef.current = false
|
|
event.stopPropagation()
|
|
}}
|
|
onPointerCancel={() => {
|
|
draggingRef.current = false
|
|
}}
|
|
onLostPointerCapture={() => {
|
|
draggingRef.current = false
|
|
}}
|
|
onKeyDown={(event) => {
|
|
if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown') return
|
|
event.preventDefault()
|
|
event.stopPropagation()
|
|
onStrengthChange(nudgeKeyframeStrength(strength, event.key === 'ArrowUp' ? 1 : -1))
|
|
}}
|
|
>
|
|
<div className="relative mx-auto h-full w-0.5 rounded-full bg-zinc-700">
|
|
<div
|
|
className="absolute bottom-0 w-full rounded-full bg-blue-400"
|
|
style={{ height: `${clamped * 100}%` }}
|
|
/>
|
|
<div
|
|
className="absolute left-1/2 h-1.5 w-1.5 -translate-x-1/2 -translate-y-1/2 rounded-full bg-blue-200 shadow"
|
|
style={{ top: `${(1 - clamped) * 100}%` }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|