'use client'; import * as React from 'react'; import { cn } from '@/lib/utils/cn'; export interface VerticalFaderProps { value: number; // 0.0 to 1.0 level?: number; // 0.0 to 1.0 (for level meter display) onChange: (value: number) => void; min?: number; max?: number; step?: number; className?: string; showDb?: boolean; } export function VerticalFader({ value, level = 0, onChange, min = 0, max = 1, step = 0.01, className, showDb = true, }: VerticalFaderProps) { const trackRef = React.useRef(null); const [isDragging, setIsDragging] = React.useState(false); const updateValue = React.useCallback( (clientY: number) => { if (!trackRef.current) return; const rect = trackRef.current.getBoundingClientRect(); const height = rect.height; const y = Math.max(0, Math.min(height, clientY - rect.top)); // Invert Y (top = max, bottom = min) const percentage = 1 - y / height; const range = max - min; let newValue = min + percentage * range; // Snap to step if (step) { newValue = Math.round(newValue / step) * step; } // Clamp to range newValue = Math.max(min, Math.min(max, newValue)); onChange(newValue); }, [min, max, step, onChange] ); const handleMouseDown = React.useCallback( (e: React.MouseEvent) => { e.preventDefault(); setIsDragging(true); updateValue(e.clientY); }, [updateValue] ); const handleMouseMove = React.useCallback( (e: MouseEvent) => { if (isDragging) { updateValue(e.clientY); } }, [isDragging, updateValue] ); const handleMouseUp = React.useCallback(() => { setIsDragging(false); }, []); React.useEffect(() => { if (isDragging) { window.addEventListener('mousemove', handleMouseMove); window.addEventListener('mouseup', handleMouseUp); return () => { window.removeEventListener('mousemove', handleMouseMove); window.removeEventListener('mouseup', handleMouseUp); }; } }, [isDragging, handleMouseMove, handleMouseUp]); // Convert value to percentage (0-100) const valuePercentage = ((value - min) / (max - min)) * 100; // Convert level to dB for display const db = value === 0 ? -Infinity : 20 * Math.log10(value); const levelDb = level === 0 ? -Infinity : (level * 60) - 60; return (
{/* dB Display */} {showDb && (
{db === -Infinity ? '-∞' : `${db.toFixed(1)}`}
)} {/* Fader Track */}
{/* Volume Level Overlay - subtle fill up to fader handle */}
{/* Level Meter (actual level) - capped at fader handle position */}
{/* Volume Value Fill - Removed to show gradient spectrum */} {/* Fader Handle */}
{/* Scale Marks */}
{[0.25, 0.5, 0.75].map((mark) => (
))}
{/* Level dB Display */} {showDb && (
{levelDb === -Infinity ? '-∞' : `${levelDb.toFixed(0)}`}
)}
); }