refactor: improve UX with 2-tab sidebar layout (Option A)

Changed from confusing 3-tab layout to cleaner 2-tab design inspired by
professional DAWs like Ableton Live:

**Tracks Tab:**
- Track management actions (Add/Import/Clear)
- Track count with selected track indicator
- Contextual help text
- Selected track's effects shown below (when track is selected)
- Clear visual separation with border-top

**Master Tab:**
- Master channel description
- Master effects chain
- Preset management for master effects
- Clear that these apply to final mix

Benefits:
- Clear separation: track operations vs master operations
- Contextual: selecting a track shows its effects in same tab
- Less cognitive load than 3 tabs
- Follows DAW conventions (track strip vs master strip)
- Selected track name shown in status area

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-18 07:41:16 +01:00
parent f640f2f9d4
commit a8f2391400

View File

@@ -75,7 +75,7 @@ export function SidePanel({
className,
}: SidePanelProps) {
const [isCollapsed, setIsCollapsed] = React.useState(false);
const [activeTab, setActiveTab] = React.useState<'tracks' | 'trackFx' | 'masterFx'>('tracks');
const [activeTab, setActiveTab] = React.useState<'tracks' | 'master'>('tracks');
const [presetDialogOpen, setPresetDialogOpen] = React.useState(false);
const selectedTrack = tracks.find((t) => t.id === selectedTrackId);
@@ -107,27 +107,21 @@ export function SidePanel({
<div className="flex items-center gap-1">
<Button
variant={activeTab === 'tracks' ? 'secondary' : 'ghost'}
size="icon-sm"
size="sm"
onClick={() => setActiveTab('tracks')}
title="Tracks"
>
<Music2 className="h-4 w-4" />
<Music2 className="h-4 w-4 mr-1.5" />
Tracks
</Button>
<Button
variant={activeTab === 'trackFx' ? 'secondary' : 'ghost'}
size="icon-sm"
onClick={() => setActiveTab('trackFx')}
title="Track Effects"
variant={activeTab === 'master' ? 'secondary' : 'ghost'}
size="sm"
onClick={() => setActiveTab('master')}
title="Master"
>
<Link2 className="h-4 w-4" />
</Button>
<Button
variant={activeTab === 'masterFx' ? 'secondary' : 'ghost'}
size="icon-sm"
onClick={() => setActiveTab('masterFx')}
title="Master Effects"
>
<Link2 className="h-4 w-4 text-primary" />
<Link2 className="h-4 w-4 mr-1.5 text-primary" />
Master
</Button>
</div>
<Button
@@ -147,7 +141,7 @@ export function SidePanel({
{/* Track Actions */}
<div className="space-y-2">
<h3 className="text-xs font-semibold text-muted-foreground uppercase">
Multi-Track Editor
Track Management
</h3>
<div className="flex gap-2">
<Button
@@ -182,17 +176,25 @@ export function SidePanel({
)}
</div>
{/* Track List - Simplified */}
{/* Track List Summary */}
{tracks.length > 0 ? (
<div className="space-y-2">
<h3 className="text-xs font-semibold text-muted-foreground uppercase">
Tracks ({tracks.length})
</h3>
<div className="flex items-center justify-between">
<h3 className="text-xs font-semibold text-muted-foreground uppercase">
Tracks ({tracks.length})
</h3>
{selectedTrack && (
<span className="text-xs text-primary">
{String(selectedTrack.name || 'Untitled Track')} selected
</span>
)}
</div>
<div className="text-xs text-muted-foreground">
<p className="mb-2">
Track controls are located on the left side of each track in the timeline.
<p>
{selectedTrack
? 'Track controls are on the left of each track. Effects for the selected track are shown below.'
: 'Click a track\'s waveform to select it and edit its effects below.'}
</p>
<p>Click a track to select it and apply effects from the Effect Chain tab.</p>
</div>
</div>
) : (
@@ -203,96 +205,97 @@ export function SidePanel({
</p>
</div>
)}
{/* Selected Track Effects */}
{selectedTrack && (
<div className="space-y-2 pt-3 border-t border-border">
<div className="flex items-center justify-between">
<h3 className="text-xs font-semibold text-muted-foreground uppercase">
Track Effects
</h3>
{trackEffectChain && trackEffectChain.effects.length > 0 && (
<Button
variant="ghost"
size="icon-sm"
onClick={onClearTrackChain}
title="Clear all effects"
>
<Trash2 className="h-4 w-4 text-destructive" />
</Button>
)}
</div>
<EffectRack
chain={trackEffectChain!}
onToggleEffect={onToggleTrackEffect}
onRemoveEffect={onRemoveTrackEffect}
onReorderEffects={onReorderTrackEffects}
/>
</div>
)}
</>
)}
{activeTab === 'trackFx' && (
<div className="space-y-2">
<div className="flex items-center justify-between">
{activeTab === 'master' && (
<>
{/* Master Channel Info */}
<div className="space-y-2">
<h3 className="text-xs font-semibold text-muted-foreground uppercase">
Track Effects
{selectedTrack && (
<span className="text-primary ml-2">({String(selectedTrack.name || 'Untitled Track')})</span>
)}
Master Channel
</h3>
<div className="flex gap-1">
{trackEffectChain && trackEffectChain.effects.length > 0 && (
<Button
variant="ghost"
size="icon-sm"
onClick={onClearTrackChain}
title="Clear all effects"
>
<Trash2 className="h-4 w-4 text-destructive" />
</Button>
)}
</div>
</div>
{!selectedTrack ? (
<div className="text-center py-8">
<Link2 className="h-12 w-12 mx-auto text-muted-foreground/50 mb-2" />
<p className="text-sm text-muted-foreground">
Select a track to apply effects
<div className="text-xs text-muted-foreground">
<p>
Master effects are applied to the final mix of all tracks.
</p>
</div>
) : (
<EffectRack
chain={trackEffectChain!}
onToggleEffect={onToggleTrackEffect}
onRemoveEffect={onRemoveTrackEffect}
onReorderEffects={onReorderTrackEffects}
/>
)}
</div>
)}
</div>
{activeTab === 'masterFx' && (
<div className="space-y-2">
<div className="flex items-center justify-between">
<h3 className="text-xs font-semibold text-muted-foreground uppercase">
Master Effects
</h3>
<div className="flex gap-1">
<Button
variant="ghost"
size="icon-sm"
onClick={() => setPresetDialogOpen(true)}
title="Manage presets"
>
<FolderOpen className="h-4 w-4" />
</Button>
{masterEffectChain.effects.length > 0 && (
{/* Master Effects */}
<div className="space-y-2 pt-3 border-t border-border">
<div className="flex items-center justify-between">
<h3 className="text-xs font-semibold text-muted-foreground uppercase">
Master Effects
</h3>
<div className="flex gap-1">
<Button
variant="ghost"
size="icon-sm"
onClick={onClearMasterChain}
title="Clear all effects"
onClick={() => setPresetDialogOpen(true)}
title="Manage presets"
>
<Trash2 className="h-4 w-4 text-destructive" />
<FolderOpen className="h-4 w-4" />
</Button>
)}
{masterEffectChain.effects.length > 0 && (
<Button
variant="ghost"
size="icon-sm"
onClick={onClearMasterChain}
title="Clear all effects"
>
<Trash2 className="h-4 w-4 text-destructive" />
</Button>
)}
</div>
</div>
</div>
<EffectRack
chain={masterEffectChain}
onToggleEffect={onToggleMasterEffect}
onRemoveEffect={onRemoveMasterEffect}
onReorderEffects={onReorderMasterEffects}
/>
<PresetManager
open={presetDialogOpen}
onClose={() => setPresetDialogOpen(false)}
currentChain={masterEffectChain}
presets={masterEffectPresets}
onSavePreset={onSaveMasterPreset}
onLoadPreset={onLoadMasterPreset}
onDeletePreset={onDeleteMasterPreset}
onExportPreset={() => {}}
onImportPreset={(preset) => onSaveMasterPreset(preset)}
/>
</div>
<EffectRack
chain={masterEffectChain}
onToggleEffect={onToggleMasterEffect}
onRemoveEffect={onRemoveMasterEffect}
onReorderEffects={onReorderMasterEffects}
/>
<PresetManager
open={presetDialogOpen}
onClose={() => setPresetDialogOpen(false)}
currentChain={masterEffectChain}
presets={masterEffectPresets}
onSavePreset={onSaveMasterPreset}
onLoadPreset={onLoadMasterPreset}
onDeletePreset={onDeleteMasterPreset}
onExportPreset={() => {}}
onImportPreset={(preset) => onSaveMasterPreset(preset)}
/>
</div>
</>
)}
</div>
</div>