- 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>
165 lines
4.7 KiB
TypeScript
165 lines
4.7 KiB
TypeScript
'use client';
|
|
|
|
import * as React from 'react';
|
|
import { Eye, EyeOff, ChevronDown, ChevronUp } from 'lucide-react';
|
|
import { Button } from '@/components/ui/Button';
|
|
import { cn } from '@/lib/utils/cn';
|
|
import type { AutomationMode } from '@/types/automation';
|
|
|
|
export interface AutomationHeaderProps {
|
|
parameterName: string;
|
|
currentValue?: number;
|
|
visible: boolean;
|
|
mode: AutomationMode;
|
|
color?: string;
|
|
onToggleVisible?: () => void;
|
|
onModeChange?: (mode: AutomationMode) => void;
|
|
onHeightChange?: (delta: number) => void;
|
|
className?: string;
|
|
formatter?: (value: number) => string;
|
|
// Parameter selection
|
|
availableParameters?: Array<{ id: string; name: string }>;
|
|
selectedParameterId?: string;
|
|
onParameterChange?: (parameterId: string) => void;
|
|
}
|
|
|
|
const MODE_LABELS: Record<AutomationMode, string> = {
|
|
read: 'R',
|
|
write: 'W',
|
|
touch: 'T',
|
|
latch: 'L',
|
|
};
|
|
|
|
const MODE_COLORS: Record<AutomationMode, string> = {
|
|
read: 'text-muted-foreground',
|
|
write: 'text-red-500',
|
|
touch: 'text-yellow-500',
|
|
latch: 'text-orange-500',
|
|
};
|
|
|
|
export function AutomationHeader({
|
|
parameterName,
|
|
currentValue,
|
|
visible,
|
|
mode,
|
|
color,
|
|
onToggleVisible,
|
|
onModeChange,
|
|
onHeightChange,
|
|
className,
|
|
formatter,
|
|
availableParameters,
|
|
selectedParameterId,
|
|
onParameterChange,
|
|
}: AutomationHeaderProps) {
|
|
const modes: AutomationMode[] = ['read', 'write', 'touch', 'latch'];
|
|
const currentModeIndex = modes.indexOf(mode);
|
|
|
|
const handleCycleModeClick = () => {
|
|
if (!onModeChange) return;
|
|
const nextIndex = (currentModeIndex + 1) % modes.length;
|
|
onModeChange(modes[nextIndex]);
|
|
};
|
|
|
|
const formatValue = (value: number) => {
|
|
if (formatter) return formatter(value);
|
|
return value.toFixed(2);
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'flex items-center gap-2 px-3 py-1.5 bg-muted border-t border-b border-border/30 flex-shrink-0',
|
|
className
|
|
)}
|
|
>
|
|
{/* Automation label - always visible */}
|
|
<span className="text-xs font-medium flex-shrink-0">Automation</span>
|
|
|
|
{/* Color indicator */}
|
|
{color && (
|
|
<div
|
|
className="w-1 h-4 rounded-full flex-shrink-0"
|
|
style={{ backgroundColor: color }}
|
|
/>
|
|
)}
|
|
|
|
{/* Parameter name / selector */}
|
|
{availableParameters && availableParameters.length > 1 ? (
|
|
<select
|
|
value={selectedParameterId}
|
|
onChange={(e) => onParameterChange?.(e.target.value)}
|
|
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) => (
|
|
<option key={param.id} value={param.id}>
|
|
{param.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
) : (
|
|
<span className="text-xs font-medium text-foreground truncate">
|
|
{parameterName}
|
|
</span>
|
|
)}
|
|
|
|
{/* Current value display */}
|
|
{currentValue !== undefined && (
|
|
<span className="text-[10px] font-mono text-muted-foreground px-1.5 py-0.5 bg-background/50 rounded">
|
|
{formatValue(currentValue)}
|
|
</span>
|
|
)}
|
|
|
|
{/* Automation mode button */}
|
|
<Button
|
|
variant="ghost"
|
|
size="icon-sm"
|
|
onClick={handleCycleModeClick}
|
|
title={`Automation mode: ${mode} (click to cycle)`}
|
|
className={cn('h-5 w-5 text-[10px] font-bold flex-shrink-0', MODE_COLORS[mode])}
|
|
>
|
|
{MODE_LABELS[mode]}
|
|
</Button>
|
|
|
|
{/* Height controls */}
|
|
{onHeightChange && (
|
|
<div className="flex flex-col gap-0 flex-shrink-0">
|
|
<Button
|
|
variant="ghost"
|
|
size="icon-sm"
|
|
onClick={() => onHeightChange(20)}
|
|
title="Increase lane height"
|
|
className="h-3 w-4 p-0"
|
|
>
|
|
<ChevronUp className="h-2.5 w-2.5" />
|
|
</Button>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon-sm"
|
|
onClick={() => onHeightChange(-20)}
|
|
title="Decrease lane height"
|
|
className="h-3 w-4 p-0"
|
|
>
|
|
<ChevronDown className="h-2.5 w-2.5" />
|
|
</Button>
|
|
</div>
|
|
)}
|
|
|
|
{/* Show/hide toggle - Positioned absolutely on the right */}
|
|
<Button
|
|
variant="ghost"
|
|
size="icon-sm"
|
|
onClick={onToggleVisible}
|
|
title={visible ? 'Hide automation' : 'Show automation'}
|
|
className="absolute right-2 h-5 w-5 flex-shrink-0"
|
|
>
|
|
{visible ? (
|
|
<Eye className="h-3 w-3" />
|
|
) : (
|
|
<EyeOff className="h-3 w-3 text-muted-foreground" />
|
|
)}
|
|
</Button>
|
|
</div>
|
|
);
|
|
}
|