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:
@@ -1,12 +1,15 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { Circle, Headphones, MoreHorizontal } from 'lucide-react';
|
||||
import { Circle, Headphones, MoreHorizontal, ChevronRight, ChevronDown } from 'lucide-react';
|
||||
import { CircularKnob } from '@/components/ui/CircularKnob';
|
||||
import { TrackFader } from './TrackFader';
|
||||
import { cn } from '@/lib/utils/cn';
|
||||
|
||||
export interface TrackControlsProps {
|
||||
trackName: string;
|
||||
trackColor: string;
|
||||
collapsed: boolean;
|
||||
volume: number;
|
||||
pan: number;
|
||||
peakLevel: number;
|
||||
@@ -17,6 +20,8 @@ export interface TrackControlsProps {
|
||||
showAutomation?: boolean;
|
||||
showEffects?: boolean;
|
||||
isRecording?: boolean;
|
||||
onNameChange: (name: string) => void;
|
||||
onToggleCollapse: () => void;
|
||||
onVolumeChange: (volume: number) => void;
|
||||
onPanChange: (pan: number) => void;
|
||||
onMuteToggle: () => void;
|
||||
@@ -32,6 +37,9 @@ export interface TrackControlsProps {
|
||||
}
|
||||
|
||||
export function TrackControls({
|
||||
trackName,
|
||||
trackColor,
|
||||
collapsed,
|
||||
volume,
|
||||
pan,
|
||||
peakLevel,
|
||||
@@ -42,6 +50,8 @@ export function TrackControls({
|
||||
showAutomation = false,
|
||||
showEffects = false,
|
||||
isRecording = false,
|
||||
onNameChange,
|
||||
onToggleCollapse,
|
||||
onVolumeChange,
|
||||
onPanChange,
|
||||
onMuteToggle,
|
||||
@@ -55,11 +65,77 @@ export function TrackControls({
|
||||
onPanTouchEnd,
|
||||
className,
|
||||
}: TrackControlsProps) {
|
||||
const [isEditingName, setIsEditingName] = React.useState(false);
|
||||
const [editName, setEditName] = React.useState(trackName);
|
||||
|
||||
const handleNameClick = () => {
|
||||
setIsEditingName(true);
|
||||
setEditName(trackName);
|
||||
};
|
||||
|
||||
const handleNameBlur = () => {
|
||||
setIsEditingName(false);
|
||||
if (editName.trim() && editName !== trackName) {
|
||||
onNameChange(editName.trim());
|
||||
} else {
|
||||
setEditName(trackName);
|
||||
}
|
||||
};
|
||||
|
||||
const handleNameKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleNameBlur();
|
||||
} else if (e.key === 'Escape') {
|
||||
setIsEditingName(false);
|
||||
setEditName(trackName);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
'flex flex-col items-center justify-between h-full px-3 pt-2 pb-4 bg-card/50 border-2 border-accent/50 rounded-lg',
|
||||
'flex flex-col items-center gap-3 px-4 py-3 bg-card/50 border-2 border-accent/50 rounded-lg',
|
||||
className
|
||||
)}>
|
||||
{/* Track Name Header with Collapse Chevron */}
|
||||
<div className="flex items-center gap-1 w-full">
|
||||
<button
|
||||
onClick={onToggleCollapse}
|
||||
className="p-0.5 hover:bg-accent/20 rounded transition-colors flex-shrink-0"
|
||||
title={collapsed ? 'Expand track' : 'Collapse track'}
|
||||
>
|
||||
{collapsed ? (
|
||||
<ChevronRight className="h-3 w-3 text-muted-foreground" />
|
||||
) : (
|
||||
<ChevronDown className="h-3 w-3 text-muted-foreground" />
|
||||
)}
|
||||
</button>
|
||||
<div className="flex-1 flex items-center justify-center min-w-0">
|
||||
{isEditingName ? (
|
||||
<input
|
||||
type="text"
|
||||
value={editName}
|
||||
onChange={(e) => setEditName(e.target.value)}
|
||||
onBlur={handleNameBlur}
|
||||
onKeyDown={handleNameKeyDown}
|
||||
autoFocus
|
||||
className="w-24 text-[10px] font-bold uppercase tracking-wider text-center bg-transparent border-b focus:outline-none px-1"
|
||||
style={{ color: trackColor, borderColor: trackColor }}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
onClick={handleNameClick}
|
||||
className="w-24 text-[10px] font-bold uppercase tracking-wider text-center cursor-text hover:bg-accent/10 px-1 rounded transition-colors truncate"
|
||||
style={{ color: trackColor }}
|
||||
title="Click to edit track name"
|
||||
>
|
||||
{trackName}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* Spacer to balance the chevron and center the label */}
|
||||
<div className="p-0.5 flex-shrink-0 w-4" />
|
||||
</div>
|
||||
|
||||
{/* Pan Control - Top */}
|
||||
<div className="flex justify-center w-full">
|
||||
<CircularKnob
|
||||
@@ -71,7 +147,7 @@ export function TrackControls({
|
||||
max={1}
|
||||
step={0.01}
|
||||
label="PAN"
|
||||
size={40}
|
||||
size={48}
|
||||
formatValue={(value: number) => {
|
||||
if (Math.abs(value) < 0.01) return 'C';
|
||||
if (value < 0) return `${Math.abs(value * 100).toFixed(0)}L`;
|
||||
@@ -94,14 +170,14 @@ export function TrackControls({
|
||||
|
||||
{/* Control Buttons - Bottom */}
|
||||
<div className="flex flex-col gap-1 w-full">
|
||||
{/* Control Buttons Row 1: R/S/M */}
|
||||
<div className="flex items-center gap-0.5 w-full justify-center">
|
||||
{/* Control Buttons Row 1: R/M/S */}
|
||||
<div className="flex items-center gap-1 w-full justify-center">
|
||||
{/* Record Arm */}
|
||||
{onRecordToggle && (
|
||||
<button
|
||||
onClick={onRecordToggle}
|
||||
className={cn(
|
||||
'h-5 w-5 rounded-md flex items-center justify-center transition-all text-[9px] font-bold',
|
||||
'h-8 w-8 rounded-md flex items-center justify-center transition-all text-[11px] font-bold',
|
||||
isRecordEnabled
|
||||
? 'bg-red-500 text-white shadow-md shadow-red-500/30'
|
||||
: 'bg-card hover:bg-accent text-muted-foreground border border-border/50',
|
||||
@@ -109,23 +185,7 @@ export function TrackControls({
|
||||
)}
|
||||
title="Arm track for recording"
|
||||
>
|
||||
<Circle className="h-2.5 w-2.5 fill-current" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Solo Button */}
|
||||
{onSoloToggle && (
|
||||
<button
|
||||
onClick={onSoloToggle}
|
||||
className={cn(
|
||||
'h-5 w-5 rounded-md flex items-center justify-center transition-all text-[9px] font-bold',
|
||||
isSolo
|
||||
? 'bg-yellow-500 text-black shadow-md shadow-yellow-500/30'
|
||||
: 'bg-card hover:bg-accent text-muted-foreground border border-border/50'
|
||||
)}
|
||||
title="Solo track"
|
||||
>
|
||||
<Headphones className="h-2.5 w-2.5" />
|
||||
<Circle className="h-3 w-3 fill-current" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -133,7 +193,7 @@ export function TrackControls({
|
||||
<button
|
||||
onClick={onMuteToggle}
|
||||
className={cn(
|
||||
'h-5 w-5 rounded-md flex items-center justify-center transition-all text-[9px] font-bold',
|
||||
'h-8 w-8 rounded-md flex items-center justify-center transition-all text-[11px] font-bold',
|
||||
isMuted
|
||||
? 'bg-blue-500 text-white shadow-md shadow-blue-500/30'
|
||||
: 'bg-card hover:bg-accent text-muted-foreground border border-border/50'
|
||||
@@ -142,6 +202,22 @@ export function TrackControls({
|
||||
>
|
||||
M
|
||||
</button>
|
||||
|
||||
{/* Solo Button */}
|
||||
{onSoloToggle && (
|
||||
<button
|
||||
onClick={onSoloToggle}
|
||||
className={cn(
|
||||
'h-8 w-8 rounded-md flex items-center justify-center transition-all text-[11px] font-bold',
|
||||
isSolo
|
||||
? 'bg-yellow-500 text-black shadow-md shadow-yellow-500/30'
|
||||
: 'bg-card hover:bg-accent text-muted-foreground border border-border/50'
|
||||
)}
|
||||
title="Solo track"
|
||||
>
|
||||
<Headphones className="h-3 w-3" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user