mirror of
https://github.com/jhbruhn/respira.git
synced 2026-01-27 10:23:41 +00:00
Loading State Improvements: - Add SkeletonLoader component with pattern info, canvas, and connection skeletons - Show loading spinner on file selection and during pattern upload - Display upload progress with enhanced progress bar and percentage - Add success confirmation message when upload completes - Show thread color preview dots inline with pattern info (up to 5 colors) Visual Polish & Animations: - Add custom animations: fadeIn, slideInRight, pulseGlow, skeleton-loading - Enhance all cards with subtle hover shadow effects - Improve header with richer gradient (blue-600 → blue-700 → blue-800) - Polish error messages with icons and improved layouts - Enhance empty state with decorative patterns and feature highlights - Add smooth transitions to all NextStepGuide states - Current color block pulses with blue glow animation - Color blocks have hover states for better interactivity Pattern Upload & Lock Functionality: - Hide upload button after pattern is uploaded (patternUploaded && uploadProgress === 100) - Disable pattern dragging when uploaded with visual lock indicator - Pattern position overlay shows amber background with lock icon when locked - Pattern remains in canvas after deletion for re-editing and re-upload - Delete pattern from cache when deleting from machine to prevent auto-resume - Add LockClosedIcon to indicate locked pattern state Pattern Management: - Keep pattern data in UI after deletion for repositioning and re-uploading - Clear machine-related state but preserve pattern visualization - Reset upload progress and pattern uploaded state on deletion 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
89 lines
3.4 KiB
TypeScript
89 lines
3.4 KiB
TypeScript
interface SkeletonLoaderProps {
|
|
className?: string;
|
|
variant?: 'text' | 'rect' | 'circle';
|
|
}
|
|
|
|
export function SkeletonLoader({ className = '', variant = 'rect' }: SkeletonLoaderProps) {
|
|
const baseClasses = 'animate-pulse bg-gradient-to-r from-gray-200 via-gray-300 to-gray-200 bg-[length:200%_100%]';
|
|
|
|
const variantClasses = {
|
|
text: 'h-4 rounded',
|
|
rect: 'rounded-lg',
|
|
circle: 'rounded-full'
|
|
};
|
|
|
|
return (
|
|
<div className={`${baseClasses} ${variantClasses[variant]} ${className}`} />
|
|
);
|
|
}
|
|
|
|
export function PatternCanvasSkeleton() {
|
|
return (
|
|
<div className="bg-white p-6 rounded-lg shadow-md">
|
|
<div className="flex items-center justify-between mb-4 pb-2 border-b-2 border-gray-300">
|
|
<SkeletonLoader className="h-7 w-40" variant="text" />
|
|
</div>
|
|
<div className="relative w-full h-[600px] border border-gray-300 rounded bg-gray-50 overflow-hidden">
|
|
<div className="flex items-center justify-center h-full">
|
|
<div className="text-center space-y-4">
|
|
<div className="relative w-24 h-24 mx-auto">
|
|
<SkeletonLoader className="w-24 h-24" variant="circle" />
|
|
<div className="absolute inset-0 flex items-center justify-center">
|
|
<svg className="w-12 h-12 text-gray-400 animate-spin" fill="none" viewBox="0 0 24 24">
|
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
|
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<SkeletonLoader className="h-5 w-48 mx-auto" variant="text" />
|
|
<SkeletonLoader className="h-4 w-64 mx-auto" variant="text" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function PatternInfoSkeleton() {
|
|
return (
|
|
<div className="mt-4">
|
|
<SkeletonLoader className="h-6 w-40 mb-4" variant="text" />
|
|
<div className="bg-gray-50 p-4 rounded-lg space-y-3">
|
|
{[1, 2, 3, 4].map((i) => (
|
|
<div key={i} className="flex justify-between">
|
|
<SkeletonLoader className="h-4 w-24" variant="text" />
|
|
<SkeletonLoader className="h-4 w-32" variant="text" />
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function MachineConnectionSkeleton() {
|
|
return (
|
|
<div className="bg-white p-6 rounded-lg shadow-md">
|
|
<div className="flex items-center justify-between mb-4 pb-2 border-b-2 border-gray-300">
|
|
<SkeletonLoader className="h-7 w-48" variant="text" />
|
|
</div>
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between">
|
|
<SkeletonLoader className="h-4 w-16" variant="text" />
|
|
<SkeletonLoader className="h-8 w-32 rounded-lg" />
|
|
</div>
|
|
<div className="bg-gray-50 p-4 rounded-lg space-y-2">
|
|
<div className="flex justify-between">
|
|
<SkeletonLoader className="h-4 w-20" variant="text" />
|
|
<SkeletonLoader className="h-4 w-24" variant="text" />
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<SkeletonLoader className="h-4 w-24" variant="text" />
|
|
<SkeletonLoader className="h-4 w-32" variant="text" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|