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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user