mirror of
https://github.com/jhbruhn/respira.git
synced 2026-01-27 02:13:41 +00:00
Dark Mode Implementation: - Add Tailwind config with darkMode: 'media' for system preference detection - Update all 9 components with 200+ dark mode variants - Semantic color backgrounds with semi-transparent overlays in dark mode - Proper text contrast (gray-900/gray-100) for readability - Enhanced borders, shadows, and focus rings for dark backgrounds Component Dark Mode Updates: - App.tsx: Header gradient, error banners, empty states - MachineConnection: Status badges with proper dark variants for all states - FileUpload: Pattern info cards, thread swatches, upload progress - PatternCanvas: Canvas background, overlays, zoom controls - ProgressMonitor: Color blocks, progress bars, state indicators with colored icons - NextStepGuide: All status boxes (blue/yellow/cyan/green/red) - WorkflowStepper: Progress indicators and step states - ConfirmDialog: Modal overlays and dialog backgrounds - KonvaComponents: Grid lines and origin markers Pattern Visibility Improvements: - Pattern shows full opacity (1.0) when unlocked for easy positioning - Pattern shows reduced opacity (0.75) for unstitched areas when locked/uploading - Helps distinguish completed vs pending stitches during sewing - Pattern locks during upload to prevent accidental repositioning - Canvas dragging disabled when pattern is uploading or uploaded Status Indicator Enhancements: - Machine status badges: All states (idle/active/waiting/complete/error) have dark variants - Progress monitor state icons: Colored icons (blue/yellow/green/red) in both modes - Color blocks: Proper backgrounds and borders for completed/current/pending states - All semantic colors maintain visibility and meaning in dark mode Canvas Lock Behavior: - Pattern locked during upload (uploadProgress > 0 && < 100) - Pattern locked after upload (patternUploaded = true) - Lock indicator shows amber background with lock icon - Cursor changes prevent confusion about draggability - Full opacity during positioning, transparency during progress tracking 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
126 lines
4.7 KiB
TypeScript
126 lines
4.7 KiB
TypeScript
import { CheckCircleIcon } from '@heroicons/react/24/solid';
|
|
import { MachineStatus } from '../types/machine';
|
|
|
|
interface WorkflowStepperProps {
|
|
machineStatus: MachineStatus;
|
|
isConnected: boolean;
|
|
hasPattern: boolean;
|
|
patternUploaded: boolean;
|
|
}
|
|
|
|
interface Step {
|
|
id: number;
|
|
label: string;
|
|
description: string;
|
|
}
|
|
|
|
const steps: Step[] = [
|
|
{ id: 1, label: 'Connect', description: 'Connect to machine' },
|
|
{ id: 2, label: 'Load Pattern', description: 'Choose PES file' },
|
|
{ id: 3, label: 'Upload', description: 'Upload to machine' },
|
|
{ id: 4, label: 'Mask Trace', description: 'Trace pattern area' },
|
|
{ id: 5, label: 'Start Sewing', description: 'Begin embroidery' },
|
|
{ id: 6, label: 'Monitor', description: 'Watch progress' },
|
|
{ id: 7, label: 'Complete', description: 'Finish and remove' },
|
|
];
|
|
|
|
function getCurrentStep(machineStatus: MachineStatus, isConnected: boolean, hasPattern: boolean, patternUploaded: boolean): number {
|
|
if (!isConnected) return 1;
|
|
if (!hasPattern) return 2;
|
|
if (!patternUploaded) return 3;
|
|
|
|
// After upload, determine step based on machine status
|
|
switch (machineStatus) {
|
|
case MachineStatus.IDLE:
|
|
case MachineStatus.MASK_TRACE_LOCK_WAIT:
|
|
case MachineStatus.MASK_TRACING:
|
|
return 4;
|
|
|
|
case MachineStatus.MASK_TRACE_COMPLETE:
|
|
case MachineStatus.SEWING_WAIT:
|
|
return 5;
|
|
|
|
case MachineStatus.SEWING:
|
|
case MachineStatus.COLOR_CHANGE_WAIT:
|
|
case MachineStatus.PAUSE:
|
|
case MachineStatus.STOP:
|
|
case MachineStatus.SEWING_INTERRUPTION:
|
|
return 6;
|
|
|
|
case MachineStatus.SEWING_COMPLETE:
|
|
return 7;
|
|
|
|
default:
|
|
return 4;
|
|
}
|
|
}
|
|
|
|
export function WorkflowStepper({ machineStatus, isConnected, hasPattern, patternUploaded }: WorkflowStepperProps) {
|
|
const currentStep = getCurrentStep(machineStatus, isConnected, hasPattern, patternUploaded);
|
|
|
|
return (
|
|
<div className="relative max-w-5xl mx-auto mt-4" role="navigation" aria-label="Workflow progress">
|
|
{/* Progress bar background */}
|
|
<div className="absolute top-5 left-0 right-0 h-1 bg-blue-400/20 dark:bg-blue-600/20 rounded-full" style={{ left: '24px', right: '24px' }} />
|
|
|
|
{/* Progress bar fill */}
|
|
<div
|
|
className="absolute top-5 left-0 h-1 bg-gradient-to-r from-green-500 to-blue-500 dark:from-green-600 dark:to-blue-600 transition-all duration-500 rounded-full"
|
|
style={{
|
|
left: '24px',
|
|
width: `calc(${((currentStep - 1) / (steps.length - 1)) * 100}% - 24px)`
|
|
}}
|
|
role="progressbar"
|
|
aria-valuenow={currentStep}
|
|
aria-valuemin={1}
|
|
aria-valuemax={steps.length}
|
|
aria-label={`Step ${currentStep} of ${steps.length}`}
|
|
/>
|
|
|
|
{/* Steps */}
|
|
<div className="flex justify-between relative">
|
|
{steps.map((step) => {
|
|
const isComplete = step.id < currentStep;
|
|
const isCurrent = step.id === currentStep;
|
|
const isUpcoming = step.id > currentStep;
|
|
|
|
return (
|
|
<div
|
|
key={step.id}
|
|
className="flex flex-col items-center"
|
|
style={{ flex: 1 }}
|
|
role="listitem"
|
|
aria-current={isCurrent ? 'step' : undefined}
|
|
>
|
|
{/* Step circle */}
|
|
<div
|
|
className={`
|
|
w-10 h-10 rounded-full flex items-center justify-center font-bold text-xs transition-all duration-300 border-2 shadow-md
|
|
${isComplete ? 'bg-green-500 dark:bg-green-600 border-green-400 dark:border-green-500 text-white shadow-green-500/30 dark:shadow-green-600/30' : ''}
|
|
${isCurrent ? 'bg-blue-600 dark:bg-blue-700 border-blue-500 dark:border-blue-600 text-white scale-110 shadow-blue-600/40 dark:shadow-blue-700/40 ring-2 ring-blue-300 dark:ring-blue-500 ring-offset-2 dark:ring-offset-gray-900' : ''}
|
|
${isUpcoming ? 'bg-blue-700 dark:bg-blue-800 border-blue-500/30 dark:border-blue-600/30 text-blue-200/70 dark:text-blue-300/70' : ''}
|
|
`}
|
|
aria-label={`${step.label}: ${isComplete ? 'completed' : isCurrent ? 'current' : 'upcoming'}`}
|
|
>
|
|
{isComplete ? (
|
|
<CheckCircleIcon className="w-6 h-6" aria-hidden="true" />
|
|
) : (
|
|
step.id
|
|
)}
|
|
</div>
|
|
|
|
{/* Step label */}
|
|
<div className="mt-2 text-center">
|
|
<div className={`text-xs font-semibold leading-tight ${
|
|
isCurrent ? 'text-white' : isComplete ? 'text-green-200 dark:text-green-300' : 'text-blue-300/70 dark:text-blue-400/70'
|
|
}`}>
|
|
{step.label}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|