fix: align automation lane with waveform, improve header layout

Automation lane improvements:
- Lane now aligns exactly with waveform width using two-column layout
- Added 180px left spacer to match track controls sidebar
- Playhead marker now aligns perfectly with waveform

Automation header improvements:
- Dropdown has fixed width (min-w-[120px] max-w-[200px]) instead of flex-1
- Eye icon (show/hide) is now positioned absolutely on the right
- Cleaner, more compact header layout

Visual consistency:
- Removed redundant border-b from AutomationLane (handled by parent)
- Automation lane and waveform now perfectly aligned vertically

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-18 18:53:08 +01:00
parent eb445bfa3a
commit 3cc4cb555a
3 changed files with 75 additions and 69 deletions

View File

@@ -69,7 +69,7 @@ export function AutomationHeader({
return ( return (
<div <div
className={cn( className={cn(
'flex items-center gap-2 px-2 py-1 bg-muted/50 border-b border-border/30 flex-shrink-0', 'relative flex items-center gap-2 px-2 py-1 bg-muted/50 border-b border-border/30 flex-shrink-0',
className className
)} )}
> >
@@ -86,7 +86,7 @@ export function AutomationHeader({
<select <select
value={selectedParameterId} value={selectedParameterId}
onChange={(e) => onParameterChange?.(e.target.value)} onChange={(e) => onParameterChange?.(e.target.value)}
className="text-xs font-medium text-foreground flex-1 min-w-0 bg-background/50 border border-border/30 rounded px-1.5 py-0.5 hover:bg-background/80 focus:outline-none focus:ring-1 focus:ring-primary" className="text-xs font-medium text-foreground w-auto min-w-[120px] max-w-[200px] bg-background/50 border border-border/30 rounded px-1.5 py-0.5 hover:bg-background/80 focus:outline-none focus:ring-1 focus:ring-primary"
> >
{availableParameters.map((param) => ( {availableParameters.map((param) => (
<option key={param.id} value={param.id}> <option key={param.id} value={param.id}>
@@ -95,7 +95,7 @@ export function AutomationHeader({
))} ))}
</select> </select>
) : ( ) : (
<span className="text-xs font-medium text-foreground flex-1 min-w-0 truncate"> <span className="text-xs font-medium text-foreground truncate">
{parameterName} {parameterName}
</span> </span>
)} )}
@@ -142,13 +142,13 @@ export function AutomationHeader({
</div> </div>
)} )}
{/* Show/hide toggle */} {/* Show/hide toggle - Positioned absolutely on the right */}
<Button <Button
variant="ghost" variant="ghost"
size="icon-sm" size="icon-sm"
onClick={onToggleVisible} onClick={onToggleVisible}
title={visible ? 'Hide automation' : 'Show automation'} title={visible ? 'Hide automation' : 'Show automation'}
className="h-5 w-5 flex-shrink-0" className="absolute right-2 h-5 w-5 flex-shrink-0"
> >
{visible ? ( {visible ? (
<Eye className="h-3 w-3" /> <Eye className="h-3 w-3" />

View File

@@ -294,7 +294,7 @@ export function AutomationLane({
if (!lane.visible) return null; if (!lane.visible) return null;
return ( return (
<div className={cn('flex flex-col border-b border-border/50', className)} style={{ height: lane.height + 30 }}> <div className={cn('flex flex-col', className)} style={{ height: lane.height + 30 }}>
{/* Header */} {/* Header */}
<AutomationHeader <AutomationHeader
parameterName={lane.parameterName} parameterName={lane.parameterName}

View File

@@ -766,69 +766,75 @@ export function Track({
} }
return selectedLane ? ( return selectedLane ? (
<div className="bg-background/30"> <div className="flex border-b border-border">
<AutomationLane {/* Left: Sidebar spacer to align with track controls */}
key={selectedLane.id} <div className="w-[180px] flex-shrink-0 bg-background/30" />
lane={selectedLane}
duration={duration} {/* Right: Automation lane matching waveform width */}
zoom={zoom} <div className="flex-1">
currentTime={currentTime} <AutomationLane
availableParameters={availableParameters} key={selectedLane.id}
selectedParameterId={selectedParameterId} lane={selectedLane}
onParameterChange={(parameterId) => { duration={duration}
onUpdateTrack(track.id, { zoom={zoom}
automation: { ...track.automation, selectedParameterId: parameterId }, currentTime={currentTime}
}); availableParameters={availableParameters}
}} selectedParameterId={selectedParameterId}
onUpdateLane={(updates) => { onParameterChange={(parameterId) => {
const updatedLanes = track.automation.lanes.map((l) => onUpdateTrack(track.id, {
l.id === selectedLane.id ? { ...l, ...updates } : l automation: { ...track.automation, selectedParameterId: parameterId },
); });
onUpdateTrack(track.id, { }}
automation: { ...track.automation, lanes: updatedLanes }, onUpdateLane={(updates) => {
}); const updatedLanes = track.automation.lanes.map((l) =>
}} l.id === selectedLane.id ? { ...l, ...updates } : l
onAddPoint={(time, value) => { );
const newPoint = createAutomationPoint({ onUpdateTrack(track.id, {
time, automation: { ...track.automation, lanes: updatedLanes },
value, });
curve: 'linear', }}
}); onAddPoint={(time, value) => {
const updatedLanes = track.automation.lanes.map((l) => const newPoint = createAutomationPoint({
l.id === selectedLane.id time,
? { ...l, points: [...l.points, newPoint] } value,
: l curve: 'linear',
); });
onUpdateTrack(track.id, { const updatedLanes = track.automation.lanes.map((l) =>
automation: { ...track.automation, lanes: updatedLanes }, l.id === selectedLane.id
}); ? { ...l, points: [...l.points, newPoint] }
}} : l
onUpdatePoint={(pointId, updates) => { );
const updatedLanes = track.automation.lanes.map((l) => onUpdateTrack(track.id, {
l.id === selectedLane.id automation: { ...track.automation, lanes: updatedLanes },
? { });
...l, }}
points: l.points.map((p) => onUpdatePoint={(pointId, updates) => {
p.id === pointId ? { ...p, ...updates } : p const updatedLanes = track.automation.lanes.map((l) =>
), l.id === selectedLane.id
} ? {
: l ...l,
); points: l.points.map((p) =>
onUpdateTrack(track.id, { p.id === pointId ? { ...p, ...updates } : p
automation: { ...track.automation, lanes: updatedLanes }, ),
}); }
}} : l
onRemovePoint={(pointId) => { );
const updatedLanes = track.automation.lanes.map((l) => onUpdateTrack(track.id, {
l.id === selectedLane.id automation: { ...track.automation, lanes: updatedLanes },
? { ...l, points: l.points.filter((p) => p.id !== pointId) } });
: l }}
); onRemovePoint={(pointId) => {
onUpdateTrack(track.id, { const updatedLanes = track.automation.lanes.map((l) =>
automation: { ...track.automation, lanes: updatedLanes }, l.id === selectedLane.id
}); ? { ...l, points: l.points.filter((p) => p.id !== pointId) }
}} : l
/> );
onUpdateTrack(track.id, {
automation: { ...track.automation, lanes: updatedLanes },
});
}}
/>
</div>
</div> </div>
) : null; ) : null;
})()} })()}