feat: streamline track and master controls layout consistency
- Streamlined track controls and master controls to same width (240px) - Fixed track controls container to use full width of parent column - Matched TrackControls card structure with MasterControls (gap-3, no w-full/h-full) - Updated outer container padding from p-2 to p-4 with gap-4 - Adjusted track controls wrapper to center content instead of stretching - Added max-width constraint to PlaybackControls to prevent width changes - Centered transport control buttons in footer 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -111,12 +111,7 @@ export function Track({
|
||||
const canvasRef = React.useRef<HTMLCanvasElement>(null);
|
||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||
const fileInputRef = React.useRef<HTMLInputElement>(null);
|
||||
const [isEditingName, setIsEditingName] = React.useState(false);
|
||||
const [nameInput, setNameInput] = React.useState(
|
||||
String(track.name || "Untitled Track"),
|
||||
);
|
||||
const [themeKey, setThemeKey] = React.useState(0);
|
||||
const inputRef = React.useRef<HTMLInputElement>(null);
|
||||
const [isResizing, setIsResizing] = React.useState(false);
|
||||
const resizeStartRef = React.useRef({ y: 0, height: 0 });
|
||||
const [effectBrowserOpen, setEffectBrowserOpen] = React.useState(false);
|
||||
@@ -286,36 +281,6 @@ export function Track({
|
||||
onUpdateTrack,
|
||||
]);
|
||||
|
||||
const handleNameClick = () => {
|
||||
setIsEditingName(true);
|
||||
setNameInput(String(track.name || "Untitled Track"));
|
||||
};
|
||||
|
||||
const handleNameBlur = () => {
|
||||
setIsEditingName(false);
|
||||
if (nameInput.trim()) {
|
||||
onNameChange(nameInput.trim());
|
||||
} else {
|
||||
setNameInput(String(track.name || "Untitled Track"));
|
||||
}
|
||||
};
|
||||
|
||||
const handleNameKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === "Enter") {
|
||||
inputRef.current?.blur();
|
||||
} else if (e.key === "Escape") {
|
||||
setNameInput(String(track.name || "Untitled Track"));
|
||||
setIsEditingName(false);
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isEditingName && inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
inputRef.current.select();
|
||||
}
|
||||
}, [isEditingName]);
|
||||
|
||||
// Listen for theme changes
|
||||
React.useEffect(() => {
|
||||
const observer = new MutationObserver(() => {
|
||||
@@ -686,7 +651,7 @@ export function Track({
|
||||
<>
|
||||
<div
|
||||
className={cn(
|
||||
"w-48 flex-shrink-0 border-b border-r-4 p-2 flex flex-col gap-2 min-h-0 transition-all duration-200 cursor-pointer border-border",
|
||||
"w-full flex-shrink-0 border-b border-r-4 p-4 flex flex-col gap-4 min-h-0 transition-all duration-200 cursor-pointer border-border",
|
||||
isSelected
|
||||
? "bg-primary/10 border-r-primary"
|
||||
: "bg-card border-r-transparent hover:bg-accent/30",
|
||||
@@ -697,76 +662,38 @@ export function Track({
|
||||
if (onSelect) onSelect();
|
||||
}}
|
||||
>
|
||||
{/* Track Name Row - Integrated collapse (DAW style) */}
|
||||
<div
|
||||
className={cn(
|
||||
"group flex items-center gap-1.5 px-1 py-0.5 rounded cursor-pointer transition-colors",
|
||||
isSelected ? "bg-primary/10" : "hover:bg-accent/50",
|
||||
)}
|
||||
onClick={(e) => {
|
||||
if (!isEditingName) {
|
||||
{/* Collapsed Header */}
|
||||
{track.collapsed && (
|
||||
<div
|
||||
className={cn(
|
||||
"group flex items-center gap-1.5 px-2 py-1 h-full w-full cursor-pointer transition-colors",
|
||||
isSelected ? "bg-primary/10" : "hover:bg-accent/50",
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onToggleCollapse();
|
||||
}
|
||||
}}
|
||||
title={track.collapsed ? "Expand track" : "Collapse track"}
|
||||
>
|
||||
{/* Small triangle indicator */}
|
||||
<div
|
||||
className={cn(
|
||||
"flex-shrink-0 transition-colors",
|
||||
isSelected
|
||||
? "text-primary"
|
||||
: "text-muted-foreground group-hover:text-foreground",
|
||||
)}
|
||||
}}
|
||||
title="Expand track"
|
||||
>
|
||||
{track.collapsed ? (
|
||||
<ChevronRight className="h-3 w-3" />
|
||||
) : (
|
||||
<ChevronDown className="h-3 w-3" />
|
||||
)}
|
||||
<ChevronRight className="h-3 w-3 text-muted-foreground flex-shrink-0" />
|
||||
<div
|
||||
className="h-4 w-0.5 rounded-full flex-shrink-0"
|
||||
style={{ backgroundColor: track.color }}
|
||||
/>
|
||||
<span className="text-xs font-semibold text-foreground truncate flex-1">
|
||||
{String(track.name || "Untitled Track")}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Color stripe (thicker when selected) */}
|
||||
<div
|
||||
className={cn(
|
||||
"h-5 rounded-full flex-shrink-0 transition-all",
|
||||
isSelected ? "w-1" : "w-0.5",
|
||||
)}
|
||||
style={{ backgroundColor: track.color }}
|
||||
></div>
|
||||
<div className="flex-1 min-w-0">
|
||||
{isEditingName ? (
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
value={nameInput}
|
||||
onChange={(e) => setNameInput(e.target.value)}
|
||||
onBlur={handleNameBlur}
|
||||
onKeyDown={handleNameKeyDown}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="w-full px-1 py-0.5 text-xs font-semibold bg-background border border-border rounded"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleNameClick();
|
||||
}}
|
||||
className="px-1 py-0.5 text-xs font-semibold text-foreground truncate"
|
||||
title={String(track.name || "Untitled Track")}
|
||||
>
|
||||
{String(track.name || "Untitled Track")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Track Controls - Only show when not collapsed */}
|
||||
{!track.collapsed && (
|
||||
<div className="flex-1 flex flex-col items-center justify-between min-h-0 overflow-hidden">
|
||||
<div className="flex-1 flex flex-col items-center justify-center min-h-0 overflow-hidden">
|
||||
{/* Integrated Track Controls (Pan + Fader + Buttons) */}
|
||||
<TrackControls
|
||||
trackName={track.name}
|
||||
trackColor={track.color}
|
||||
collapsed={track.collapsed}
|
||||
volume={track.volume}
|
||||
pan={track.pan}
|
||||
peakLevel={
|
||||
@@ -785,6 +712,8 @@ export function Track({
|
||||
showAutomation={track.automation?.showAutomation}
|
||||
showEffects={track.showEffects}
|
||||
isRecording={isRecording}
|
||||
onNameChange={onNameChange}
|
||||
onToggleCollapse={onToggleCollapse}
|
||||
onVolumeChange={onVolumeChange}
|
||||
onPanChange={onPanChange}
|
||||
onMuteToggle={onToggleMute}
|
||||
@@ -844,14 +773,14 @@ export function Track({
|
||||
: "100%",
|
||||
}}
|
||||
>
|
||||
{/* Delete Button - Top Right Overlay */}
|
||||
{/* Delete Button - Top Right Overlay - Stays fixed when scrolling */}
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onRemove();
|
||||
}}
|
||||
className={cn(
|
||||
"absolute top-2 right-2 z-20 h-6 w-6 rounded flex items-center justify-center transition-all",
|
||||
"sticky top-2 right-2 float-right z-20 h-6 w-6 rounded flex items-center justify-center transition-all",
|
||||
"bg-card/80 hover:bg-destructive/90 text-muted-foreground hover:text-white",
|
||||
"border border-border/50 hover:border-destructive",
|
||||
"backdrop-blur-sm shadow-sm hover:shadow-md",
|
||||
@@ -875,11 +804,12 @@ export function Track({
|
||||
) : (
|
||||
!track.collapsed && (
|
||||
<>
|
||||
{/* Empty state - clickable area for upload with drag & drop */}
|
||||
<div
|
||||
className={cn(
|
||||
"absolute inset-0 flex flex-col items-center justify-center text-sm text-muted-foreground hover:text-foreground transition-colors cursor-pointer group",
|
||||
"absolute inset-0 w-full h-full transition-colors cursor-pointer",
|
||||
isDragging
|
||||
? "bg-primary/20 text-primary border-2 border-primary border-dashed"
|
||||
? "bg-primary/20 border-2 border-primary border-dashed"
|
||||
: "hover:bg-accent/50",
|
||||
)}
|
||||
onClick={(e) => {
|
||||
@@ -889,15 +819,7 @@ export function Track({
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleDrop}
|
||||
>
|
||||
<Upload className="h-6 w-6 mb-2 opacity-50 group-hover:opacity-100" />
|
||||
<p>
|
||||
{isDragging
|
||||
? "Drop audio file here"
|
||||
: "Click to load audio file"}
|
||||
</p>
|
||||
<p className="text-xs opacity-75 mt-1">or drag & drop</p>
|
||||
</div>
|
||||
/>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
@@ -909,6 +831,16 @@ export function Track({
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Import Dialog - Also needed in waveform-only mode */}
|
||||
<ImportDialog
|
||||
open={showImportDialog}
|
||||
onClose={handleImportCancel}
|
||||
onImport={handleImport}
|
||||
fileName={pendingFile?.name}
|
||||
sampleRate={fileMetadata.sampleRate}
|
||||
channels={fileMetadata.channels}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user