feat: redesign track controls to Ableton Live style
Major UI Redesign: - Reduced track control width from 288px to 192px (33% narrower) - Replaced horizontal sliders with vertical fader and circular knob - More compact, professional appearance matching Ableton Live - Global settings dialog replaces inline recording settings New Components Created: - VerticalFader.tsx: Vertical volume control with integrated level meter - Shows volume dB at top, level dB at bottom - Level meter displayed as background gradient - Draggable handle for precise control - CircularKnob.tsx: Rotary pan control - SVG-based rotary knob with arc indicator - Vertical drag interaction (200px sensitivity) - Displays L/C/R values - GlobalSettingsDialog.tsx: Centralized settings - Tabbed interface (Recording, Playback, Interface) - Recording settings moved from inline to dialog - Accessible via gear icon in header - Modal dialog with backdrop Track Control Panel Changes: - Track name: More compact (text-xs) - Buttons: Smaller (h-6 w-6), text-based S/M buttons - Record button: Circle indicator instead of icon - Pan: Circular knob (40px) instead of horizontal slider - Volume: Vertical fader with integrated meter - Removed: Inline recording settings panel Header Changes: - Added Settings button (gear icon) before ThemeToggle - Opens GlobalSettingsDialog on click - Clean, accessible from anywhere Props Cleanup: - Removed recordingSettings props from Track/TrackList - Removed onInputGainChange, onRecordMonoChange, onSampleRateChange props - Settings now managed globally via dialog Technical Details: - VerticalFader uses mouse drag for smooth control - CircularKnob rotates -135° to +135° (270° range) - Global event listeners for drag interactions - Proper cleanup on unmount Benefits: ✅ 33% narrower tracks = more tracks visible ✅ Professional Ableton-style appearance ✅ Cleaner, less cluttered interface ✅ Global settings accessible anywhere ✅ Better use of vertical space ✅ Consistent with industry-standard DAWs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
189
components/settings/GlobalSettingsDialog.tsx
Normal file
189
components/settings/GlobalSettingsDialog.tsx
Normal file
@@ -0,0 +1,189 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { X } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { RecordingSettings } from '@/components/recording/RecordingSettings';
|
||||
import { cn } from '@/lib/utils/cn';
|
||||
import type { RecordingSettings as RecordingSettingsType } from '@/lib/hooks/useRecording';
|
||||
|
||||
export interface GlobalSettingsDialogProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
recordingSettings: RecordingSettingsType;
|
||||
onInputGainChange: (gain: number) => void;
|
||||
onRecordMonoChange: (mono: boolean) => void;
|
||||
onSampleRateChange: (sampleRate: number) => void;
|
||||
}
|
||||
|
||||
type TabType = 'recording' | 'playback' | 'interface';
|
||||
|
||||
export function GlobalSettingsDialog({
|
||||
open,
|
||||
onClose,
|
||||
recordingSettings,
|
||||
onInputGainChange,
|
||||
onRecordMonoChange,
|
||||
onSampleRateChange,
|
||||
}: GlobalSettingsDialogProps) {
|
||||
const [activeTab, setActiveTab] = React.useState<TabType>('recording');
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
className="fixed inset-0 bg-background/80 backdrop-blur-sm z-40"
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
||||
{/* Dialog */}
|
||||
<div className="fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-2xl z-50">
|
||||
<div className="bg-card border border-border rounded-lg shadow-2xl overflow-hidden">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between px-6 py-4 border-b border-border">
|
||||
<h2 className="text-lg font-semibold">Settings</h2>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={onClose}
|
||||
title="Close"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="flex border-b border-border bg-muted/30">
|
||||
<button
|
||||
onClick={() => setActiveTab('recording')}
|
||||
className={cn(
|
||||
'px-6 py-3 text-sm font-medium transition-colors relative',
|
||||
activeTab === 'recording'
|
||||
? 'text-foreground bg-card'
|
||||
: 'text-muted-foreground hover:text-foreground hover:bg-muted/50'
|
||||
)}
|
||||
>
|
||||
Recording
|
||||
{activeTab === 'recording' && (
|
||||
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-primary" />
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('playback')}
|
||||
className={cn(
|
||||
'px-6 py-3 text-sm font-medium transition-colors relative',
|
||||
activeTab === 'playback'
|
||||
? 'text-foreground bg-card'
|
||||
: 'text-muted-foreground hover:text-foreground hover:bg-muted/50'
|
||||
)}
|
||||
>
|
||||
Playback
|
||||
{activeTab === 'playback' && (
|
||||
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-primary" />
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('interface')}
|
||||
className={cn(
|
||||
'px-6 py-3 text-sm font-medium transition-colors relative',
|
||||
activeTab === 'interface'
|
||||
? 'text-foreground bg-card'
|
||||
: 'text-muted-foreground hover:text-foreground hover:bg-muted/50'
|
||||
)}
|
||||
>
|
||||
Interface
|
||||
{activeTab === 'interface' && (
|
||||
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-primary" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="p-6 max-h-[60vh] overflow-y-auto custom-scrollbar">
|
||||
{activeTab === 'recording' && (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h3 className="text-sm font-medium mb-3">Recording Settings</h3>
|
||||
<RecordingSettings
|
||||
settings={recordingSettings}
|
||||
onInputGainChange={onInputGainChange}
|
||||
onRecordMonoChange={onRecordMonoChange}
|
||||
onSampleRateChange={onSampleRateChange}
|
||||
className="border-0 bg-transparent p-0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="pt-4 border-t border-border">
|
||||
<h3 className="text-sm font-medium mb-2">Note</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
These settings apply globally to all recordings. Arm a track (red button)
|
||||
to enable recording on that specific track.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'playback' && (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h3 className="text-sm font-medium mb-2">Playback Settings</h3>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Configure audio playback preferences.
|
||||
</p>
|
||||
|
||||
<div className="space-y-3 text-sm text-muted-foreground">
|
||||
<div className="flex items-center justify-between p-3 bg-muted/50 rounded">
|
||||
<span>Buffer Size</span>
|
||||
<span className="font-mono">Auto</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between p-3 bg-muted/50 rounded">
|
||||
<span>Output Latency</span>
|
||||
<span className="font-mono">~20ms</span>
|
||||
</div>
|
||||
<p className="text-xs italic">
|
||||
Advanced playback settings coming soon...
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'interface' && (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h3 className="text-sm font-medium mb-2">Interface Settings</h3>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Customize the editor appearance and behavior.
|
||||
</p>
|
||||
|
||||
<div className="space-y-3 text-sm text-muted-foreground">
|
||||
<div className="flex items-center justify-between p-3 bg-muted/50 rounded">
|
||||
<span>Theme</span>
|
||||
<span>Use theme toggle in header</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between p-3 bg-muted/50 rounded">
|
||||
<span>Default Track Height</span>
|
||||
<span className="font-mono">180px</span>
|
||||
</div>
|
||||
<p className="text-xs italic">
|
||||
More interface options coming soon...
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="flex items-center justify-end gap-2 px-6 py-4 border-t border-border bg-muted/30">
|
||||
<Button variant="default" onClick={onClose}>
|
||||
Done
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user