mirror of
https://github.com/jhbruhn/respira.git
synced 2026-01-27 10:23:41 +00:00
Redesign UI for fixed-size window with floating guide overlay
- Convert NextStepGuide to collapsible floating overlay in bottom-right - Implement fixed viewport layout (no page scrolling on desktop) - Make color blocks scrollable with dynamic gradient indicator - Add responsive behavior: scrollable on mobile, fixed on desktop - Increase overlay opacity for better readability - Enable full-height columns that expand to fill available space 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
0dbfc751cb
commit
bf3e397ddb
4 changed files with 367 additions and 265 deletions
36
src/App.tsx
36
src/App.tsx
|
|
@ -108,9 +108,9 @@ function App() {
|
||||||
const StatusIcon = stateIcons[stateVisual.iconName];
|
const StatusIcon = stateIcons[stateVisual.iconName];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex flex-col bg-gray-50 dark:bg-gray-900">
|
<div className="h-screen flex flex-col bg-gray-50 dark:bg-gray-900 overflow-hidden">
|
||||||
<header className="bg-gradient-to-r from-blue-600 via-blue-700 to-blue-800 dark:from-blue-700 dark:via-blue-800 dark:to-blue-900 px-4 sm:px-6 lg:px-8 py-3 shadow-lg border-b-2 border-blue-900/20 dark:border-blue-800/30">
|
<header className="bg-gradient-to-r from-blue-600 via-blue-700 to-blue-800 dark:from-blue-700 dark:via-blue-800 dark:to-blue-900 px-4 sm:px-6 lg:px-8 py-3 shadow-lg border-b-2 border-blue-900/20 dark:border-blue-800/30 flex-shrink-0">
|
||||||
<div className="max-w-[1600px] mx-auto grid grid-cols-1 lg:grid-cols-[280px_1fr] gap-4 lg:gap-8 items-center">
|
<div className="grid grid-cols-1 lg:grid-cols-[280px_1fr] gap-4 lg:gap-8 items-center">
|
||||||
{/* Machine Connection Status - Responsive width column */}
|
{/* Machine Connection Status - Responsive width column */}
|
||||||
<div className="flex items-center gap-3 w-full lg:w-[280px]">
|
<div className="flex items-center gap-3 w-full lg:w-[280px]">
|
||||||
<div className="w-2.5 h-2.5 bg-green-400 rounded-full animate-pulse shadow-lg shadow-green-400/50" style={{ visibility: machine.isConnected ? 'visible' : 'hidden' }}></div>
|
<div className="w-2.5 h-2.5 bg-green-400 rounded-full animate-pulse shadow-lg shadow-green-400/50" style={{ visibility: machine.isConnected ? 'visible' : 'hidden' }}></div>
|
||||||
|
|
@ -178,7 +178,7 @@ function App() {
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="flex-1 p-4 sm:p-5 lg:p-6 max-w-[1600px] w-full mx-auto">
|
<div className="flex-1 p-4 sm:p-5 lg:p-6 w-full overflow-y-auto lg:overflow-hidden flex flex-col">
|
||||||
{/* Global errors */}
|
{/* Global errors */}
|
||||||
{machine.error && (
|
{machine.error && (
|
||||||
<div className={`px-6 py-4 rounded-lg border-l-4 mb-6 shadow-md hover:shadow-lg transition-shadow animate-fadeIn ${
|
<div className={`px-6 py-4 rounded-lg border-l-4 mb-6 shadow-md hover:shadow-lg transition-shadow animate-fadeIn ${
|
||||||
|
|
@ -225,9 +225,9 @@ function App() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-[400px_1fr] gap-4 md:gap-5 lg:gap-6">
|
<div className="flex-1 grid grid-cols-1 lg:grid-cols-[400px_1fr] gap-4 md:gap-5 lg:gap-6 lg:overflow-hidden">
|
||||||
{/* Left Column - Controls */}
|
{/* Left Column - Controls */}
|
||||||
<div className="flex flex-col gap-4 md:gap-5 lg:gap-6">
|
<div className="flex flex-col gap-4 md:gap-5 lg:gap-6 lg:overflow-hidden">
|
||||||
{/* Connect Button - Show when disconnected */}
|
{/* Connect Button - Show when disconnected */}
|
||||||
{!machine.isConnected && (
|
{!machine.isConnected && (
|
||||||
<div className="bg-white dark:bg-gray-800 p-4 rounded-lg shadow-md border-l-4 border-gray-400 dark:border-gray-600">
|
<div className="bg-white dark:bg-gray-800 p-4 rounded-lg shadow-md border-l-4 border-gray-400 dark:border-gray-600">
|
||||||
|
|
@ -284,6 +284,7 @@ function App() {
|
||||||
|
|
||||||
{/* Progress Monitor - Show when pattern is uploaded */}
|
{/* Progress Monitor - Show when pattern is uploaded */}
|
||||||
{machine.isConnected && patternUploaded && (
|
{machine.isConnected && patternUploaded && (
|
||||||
|
<div className="lg:flex-1 lg:min-h-0">
|
||||||
<ProgressMonitor
|
<ProgressMonitor
|
||||||
machineStatus={machine.machineStatus}
|
machineStatus={machine.machineStatus}
|
||||||
patternInfo={machine.patternInfo}
|
patternInfo={machine.patternInfo}
|
||||||
|
|
@ -295,11 +296,12 @@ function App() {
|
||||||
onDeletePattern={handleDeletePattern}
|
onDeletePattern={handleDeletePattern}
|
||||||
isDeleting={machine.isDeleting}
|
isDeleting={machine.isDeleting}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right Column - Pattern Preview */}
|
{/* Right Column - Pattern Preview */}
|
||||||
<div className="flex flex-col gap-4 md:gap-5 lg:gap-6">
|
<div className="flex flex-col lg:overflow-hidden lg:h-full">
|
||||||
{pesData ? (
|
{pesData ? (
|
||||||
<PatternCanvas
|
<PatternCanvas
|
||||||
pesData={pesData}
|
pesData={pesData}
|
||||||
|
|
@ -311,9 +313,9 @@ function App() {
|
||||||
isUploading={machine.uploadProgress > 0 && machine.uploadProgress < 100}
|
isUploading={machine.uploadProgress > 0 && machine.uploadProgress < 100}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md animate-fadeIn">
|
<div className="lg:h-full bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md animate-fadeIn flex flex-col">
|
||||||
<h2 className="text-base lg:text-lg font-semibold mb-4 pb-2 border-b-2 border-gray-300 dark:border-gray-600 dark:text-white">Pattern Preview</h2>
|
<h2 className="text-base lg:text-lg font-semibold mb-4 pb-2 border-b-2 border-gray-300 dark:border-gray-600 dark:text-white flex-shrink-0">Pattern Preview</h2>
|
||||||
<div className="flex items-center justify-center h-[400px] sm:h-[500px] lg:h-[600px] max-h-[70vh] bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-700 dark:to-gray-800 rounded-lg border-2 border-dashed border-gray-300 dark:border-gray-600 relative overflow-hidden">
|
<div className="h-[400px] sm:h-[500px] lg:flex-1 flex items-center justify-center bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-700 dark:to-gray-800 rounded-lg border-2 border-dashed border-gray-300 dark:border-gray-600 relative overflow-hidden">
|
||||||
{/* Decorative background pattern */}
|
{/* Decorative background pattern */}
|
||||||
<div className="absolute inset-0 opacity-5 dark:opacity-10">
|
<div className="absolute inset-0 opacity-5 dark:opacity-10">
|
||||||
<div className="absolute top-10 left-10 w-32 h-32 border-4 border-gray-400 dark:border-gray-500 rounded-full"></div>
|
<div className="absolute top-10 left-10 w-32 h-32 border-4 border-gray-400 dark:border-gray-500 rounded-full"></div>
|
||||||
|
|
@ -354,8 +356,14 @@ function App() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Next Step Guide - Below pattern preview */}
|
{/* Bluetooth Device Picker (Electron only) */}
|
||||||
|
<BluetoothDevicePicker />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Next Step Guide - Fixed floating overlay */}
|
||||||
<NextStepGuide
|
<NextStepGuide
|
||||||
machineStatus={machine.machineStatus}
|
machineStatus={machine.machineStatus}
|
||||||
isConnected={machine.isConnected}
|
isConnected={machine.isConnected}
|
||||||
|
|
@ -366,12 +374,6 @@ function App() {
|
||||||
errorCode={machine.machineError}
|
errorCode={machine.machineError}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Bluetooth Device Picker (Electron only) */}
|
|
||||||
<BluetoothDevicePicker />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { InformationCircleIcon, ExclamationTriangleIcon } from '@heroicons/react/24/solid';
|
import { useState, useEffect } from 'react';
|
||||||
|
import { InformationCircleIcon, ExclamationTriangleIcon, XMarkIcon } from '@heroicons/react/24/solid';
|
||||||
import { MachineStatus } from '../types/machine';
|
import { MachineStatus } from '../types/machine';
|
||||||
import { getErrorDetails } from '../utils/errorCodeHelpers';
|
import { getErrorDetails } from '../utils/errorCodeHelpers';
|
||||||
|
|
||||||
|
|
@ -21,6 +22,15 @@ export function NextStepGuide({
|
||||||
errorMessage,
|
errorMessage,
|
||||||
errorCode
|
errorCode
|
||||||
}: NextStepGuideProps) {
|
}: NextStepGuideProps) {
|
||||||
|
const [isExpanded, setIsExpanded] = useState(true);
|
||||||
|
|
||||||
|
// Expand when state changes
|
||||||
|
useEffect(() => {
|
||||||
|
setIsExpanded(true);
|
||||||
|
}, [machineStatus, isConnected, hasPattern, patternUploaded, hasError]);
|
||||||
|
|
||||||
|
// Render guide content based on state
|
||||||
|
const renderContent = () => {
|
||||||
// Don't show if there's an error - show detailed error guidance instead
|
// Don't show if there's an error - show detailed error guidance instead
|
||||||
if (hasError) {
|
if (hasError) {
|
||||||
const errorDetails = getErrorDetails(errorCode);
|
const errorDetails = getErrorDetails(errorCode);
|
||||||
|
|
@ -28,20 +38,20 @@ export function NextStepGuide({
|
||||||
// Check if this is informational (like initialization steps) vs a real error
|
// Check if this is informational (like initialization steps) vs a real error
|
||||||
if (errorDetails?.isInformational) {
|
if (errorDetails?.isInformational) {
|
||||||
return (
|
return (
|
||||||
<div className="bg-blue-50 dark:bg-blue-900/20 border-l-4 border-blue-600 dark:border-blue-500 p-6 rounded-lg shadow-md animate-fadeIn">
|
<div className="bg-blue-50 dark:bg-blue-900/95 border-l-4 border-blue-600 dark:border-blue-500 p-4 rounded-lg shadow-lg backdrop-blur-sm">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-3">
|
||||||
<InformationCircleIcon className="w-8 h-8 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-1" />
|
<InformationCircleIcon className="w-6 h-6 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3 className="text-lg font-semibold text-blue-900 dark:text-blue-200 mb-2">
|
<h3 className="text-base font-semibold text-blue-900 dark:text-blue-200 mb-2">
|
||||||
{errorDetails.title}
|
{errorDetails.title}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-blue-800 dark:text-blue-300 mb-3">
|
<p className="text-sm text-blue-800 dark:text-blue-300 mb-3">
|
||||||
{errorDetails.description}
|
{errorDetails.description}
|
||||||
</p>
|
</p>
|
||||||
{errorDetails.solutions && errorDetails.solutions.length > 0 && (
|
{errorDetails.solutions && errorDetails.solutions.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<h4 className="font-semibold text-blue-900 dark:text-blue-200 mb-2">Steps:</h4>
|
<h4 className="text-sm font-semibold text-blue-900 dark:text-blue-200 mb-2">Steps:</h4>
|
||||||
<ol className="list-decimal list-inside text-sm text-blue-700 dark:text-blue-300 space-y-2">
|
<ol className="list-decimal list-inside text-sm text-blue-700 dark:text-blue-300 space-y-1.5">
|
||||||
{errorDetails.solutions.map((solution, index) => (
|
{errorDetails.solutions.map((solution, index) => (
|
||||||
<li key={index} className="pl-2">{solution}</li>
|
<li key={index} className="pl-2">{solution}</li>
|
||||||
))}
|
))}
|
||||||
|
|
@ -56,20 +66,20 @@ export function NextStepGuide({
|
||||||
|
|
||||||
// Regular error display for actual errors
|
// Regular error display for actual errors
|
||||||
return (
|
return (
|
||||||
<div className="bg-red-50 dark:bg-red-900/20 border-l-4 border-red-600 dark:border-red-500 p-6 rounded-lg shadow-md animate-fadeIn">
|
<div className="bg-red-50 dark:bg-red-900/95 border-l-4 border-red-600 dark:border-red-500 p-4 rounded-lg shadow-lg backdrop-blur-sm">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-3">
|
||||||
<ExclamationTriangleIcon className="w-8 h-8 text-red-600 dark:text-red-400 flex-shrink-0 mt-1" />
|
<ExclamationTriangleIcon className="w-6 h-6 text-red-600 dark:text-red-400 flex-shrink-0 mt-0.5" />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3 className="text-lg font-semibold text-red-900 dark:text-red-200 mb-2">
|
<h3 className="text-base font-semibold text-red-900 dark:text-red-200 mb-2">
|
||||||
{errorDetails?.title || 'Error Occurred'}
|
{errorDetails?.title || 'Error Occurred'}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-red-800 dark:text-red-300 mb-3">
|
<p className="text-sm text-red-800 dark:text-red-300 mb-3">
|
||||||
{errorDetails?.description || errorMessage || 'An error occurred. Please check the machine and try again.'}
|
{errorDetails?.description || errorMessage || 'An error occurred. Please check the machine and try again.'}
|
||||||
</p>
|
</p>
|
||||||
{errorDetails?.solutions && errorDetails.solutions.length > 0 && (
|
{errorDetails?.solutions && errorDetails.solutions.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<h4 className="font-semibold text-red-900 dark:text-red-200 mb-2">How to Fix:</h4>
|
<h4 className="text-sm font-semibold text-red-900 dark:text-red-200 mb-2">How to Fix:</h4>
|
||||||
<ol className="list-decimal list-inside text-sm text-red-700 dark:text-red-300 space-y-2">
|
<ol className="list-decimal list-inside text-sm text-red-700 dark:text-red-300 space-y-1.5">
|
||||||
{errorDetails.solutions.map((solution, index) => (
|
{errorDetails.solutions.map((solution, index) => (
|
||||||
<li key={index} className="pl-2">{solution}</li>
|
<li key={index} className="pl-2">{solution}</li>
|
||||||
))}
|
))}
|
||||||
|
|
@ -77,7 +87,7 @@ export function NextStepGuide({
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{errorCode !== undefined && (
|
{errorCode !== undefined && (
|
||||||
<p className="text-xs text-red-600 dark:text-red-400 mt-4 font-mono">
|
<p className="text-xs text-red-600 dark:text-red-400 mt-3 font-mono">
|
||||||
Error Code: 0x{errorCode.toString(16).toUpperCase().padStart(2, '0')}
|
Error Code: 0x{errorCode.toString(16).toUpperCase().padStart(2, '0')}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
@ -90,12 +100,12 @@ export function NextStepGuide({
|
||||||
// Determine what to show based on current state
|
// Determine what to show based on current state
|
||||||
if (!isConnected) {
|
if (!isConnected) {
|
||||||
return (
|
return (
|
||||||
<div className="bg-blue-50 dark:bg-blue-900/20 border-l-4 border-blue-600 dark:border-blue-500 p-6 rounded-lg shadow-md animate-fadeIn">
|
<div className="bg-blue-50 dark:bg-blue-900/95 border-l-4 border-blue-600 dark:border-blue-500 p-4 rounded-lg shadow-lg backdrop-blur-sm">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-3">
|
||||||
<InformationCircleIcon className="w-8 h-8 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-1" />
|
<InformationCircleIcon className="w-6 h-6 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3 className="text-lg font-semibold text-blue-900 dark:text-blue-200 mb-2">Step 1: Connect to Machine</h3>
|
<h3 className="text-base font-semibold text-blue-900 dark:text-blue-200 mb-2">Step 1: Connect to Machine</h3>
|
||||||
<p className="text-blue-800 dark:text-blue-300 mb-3">To get started, connect to your Brother embroidery machine via Bluetooth.</p>
|
<p className="text-sm text-blue-800 dark:text-blue-300 mb-3">To get started, connect to your Brother embroidery machine via Bluetooth.</p>
|
||||||
<ul className="list-disc list-inside text-sm text-blue-700 dark:text-blue-300 space-y-1">
|
<ul className="list-disc list-inside text-sm text-blue-700 dark:text-blue-300 space-y-1">
|
||||||
<li>Make sure your machine is powered on</li>
|
<li>Make sure your machine is powered on</li>
|
||||||
<li>Enable Bluetooth on your machine</li>
|
<li>Enable Bluetooth on your machine</li>
|
||||||
|
|
@ -109,12 +119,12 @@ export function NextStepGuide({
|
||||||
|
|
||||||
if (!hasPattern) {
|
if (!hasPattern) {
|
||||||
return (
|
return (
|
||||||
<div className="bg-blue-50 dark:bg-blue-900/20 border-l-4 border-blue-600 dark:border-blue-500 p-6 rounded-lg shadow-md animate-fadeIn">
|
<div className="bg-blue-50 dark:bg-blue-900/95 border-l-4 border-blue-600 dark:border-blue-500 p-4 rounded-lg shadow-lg backdrop-blur-sm">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-3">
|
||||||
<InformationCircleIcon className="w-8 h-8 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-1" />
|
<InformationCircleIcon className="w-6 h-6 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3 className="text-lg font-semibold text-blue-900 dark:text-blue-200 mb-2">Step 2: Load Your Pattern</h3>
|
<h3 className="text-base font-semibold text-blue-900 dark:text-blue-200 mb-2">Step 2: Load Your Pattern</h3>
|
||||||
<p className="text-blue-800 dark:text-blue-300 mb-3">Choose a PES embroidery file from your computer to preview and upload.</p>
|
<p className="text-sm text-blue-800 dark:text-blue-300 mb-3">Choose a PES embroidery file from your computer to preview and upload.</p>
|
||||||
<ul className="list-disc list-inside text-sm text-blue-700 dark:text-blue-300 space-y-1">
|
<ul className="list-disc list-inside text-sm text-blue-700 dark:text-blue-300 space-y-1">
|
||||||
<li>Click "Choose PES File" in the Pattern File section</li>
|
<li>Click "Choose PES File" in the Pattern File section</li>
|
||||||
<li>Select your embroidery design (.pes file)</li>
|
<li>Select your embroidery design (.pes file)</li>
|
||||||
|
|
@ -129,12 +139,12 @@ export function NextStepGuide({
|
||||||
|
|
||||||
if (!patternUploaded) {
|
if (!patternUploaded) {
|
||||||
return (
|
return (
|
||||||
<div className="bg-blue-50 dark:bg-blue-900/20 border-l-4 border-blue-600 dark:border-blue-500 p-6 rounded-lg shadow-md animate-fadeIn">
|
<div className="bg-blue-50 dark:bg-blue-900/95 border-l-4 border-blue-600 dark:border-blue-500 p-4 rounded-lg shadow-lg backdrop-blur-sm">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-3">
|
||||||
<InformationCircleIcon className="w-8 h-8 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-1" />
|
<InformationCircleIcon className="w-6 h-6 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3 className="text-lg font-semibold text-blue-900 dark:text-blue-200 mb-2">Step 3: Upload Pattern to Machine</h3>
|
<h3 className="text-base font-semibold text-blue-900 dark:text-blue-200 mb-2">Step 3: Upload Pattern to Machine</h3>
|
||||||
<p className="text-blue-800 dark:text-blue-300 mb-3">Send your pattern to the embroidery machine to prepare for sewing.</p>
|
<p className="text-sm text-blue-800 dark:text-blue-300 mb-3">Send your pattern to the embroidery machine to prepare for sewing.</p>
|
||||||
<ul className="list-disc list-inside text-sm text-blue-700 dark:text-blue-300 space-y-1">
|
<ul className="list-disc list-inside text-sm text-blue-700 dark:text-blue-300 space-y-1">
|
||||||
<li>Review the pattern preview to ensure it's positioned correctly</li>
|
<li>Review the pattern preview to ensure it's positioned correctly</li>
|
||||||
<li>Check the pattern size matches your hoop</li>
|
<li>Check the pattern size matches your hoop</li>
|
||||||
|
|
@ -151,12 +161,12 @@ export function NextStepGuide({
|
||||||
switch (machineStatus) {
|
switch (machineStatus) {
|
||||||
case MachineStatus.IDLE:
|
case MachineStatus.IDLE:
|
||||||
return (
|
return (
|
||||||
<div className="bg-blue-50 dark:bg-blue-900/20 border-l-4 border-blue-600 dark:border-blue-500 p-6 rounded-lg shadow-md">
|
<div className="bg-blue-50 dark:bg-blue-900/95 border-l-4 border-blue-600 dark:border-blue-500 p-4 rounded-lg shadow-lg backdrop-blur-sm">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-3">
|
||||||
<InformationCircleIcon className="w-8 h-8 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-1" />
|
<InformationCircleIcon className="w-6 h-6 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3 className="text-lg font-semibold text-blue-900 dark:text-blue-200 mb-2">Step 4: Start Mask Trace</h3>
|
<h3 className="text-base font-semibold text-blue-900 dark:text-blue-200 mb-2">Step 4: Start Mask Trace</h3>
|
||||||
<p className="text-blue-800 dark:text-blue-300 mb-3">The mask trace helps the machine understand the pattern boundaries.</p>
|
<p className="text-sm text-blue-800 dark:text-blue-300 mb-3">The mask trace helps the machine understand the pattern boundaries.</p>
|
||||||
<ul className="list-disc list-inside text-sm text-blue-700 dark:text-blue-300 space-y-1">
|
<ul className="list-disc list-inside text-sm text-blue-700 dark:text-blue-300 space-y-1">
|
||||||
<li>Click "Start Mask Trace" button in the Sewing Progress section</li>
|
<li>Click "Start Mask Trace" button in the Sewing Progress section</li>
|
||||||
<li>The machine will trace the pattern outline</li>
|
<li>The machine will trace the pattern outline</li>
|
||||||
|
|
@ -169,12 +179,12 @@ export function NextStepGuide({
|
||||||
|
|
||||||
case MachineStatus.MASK_TRACE_LOCK_WAIT:
|
case MachineStatus.MASK_TRACE_LOCK_WAIT:
|
||||||
return (
|
return (
|
||||||
<div className="bg-yellow-50 dark:bg-yellow-900/20 border-l-4 border-yellow-600 dark:border-yellow-500 p-6 rounded-lg shadow-md animate-fadeIn">
|
<div className="bg-yellow-50 dark:bg-yellow-900/95 border-l-4 border-yellow-600 dark:border-yellow-500 p-4 rounded-lg shadow-lg backdrop-blur-sm">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-3">
|
||||||
<InformationCircleIcon className="w-8 h-8 text-yellow-600 dark:text-yellow-400 flex-shrink-0 mt-1" />
|
<InformationCircleIcon className="w-6 h-6 text-yellow-600 dark:text-yellow-400 flex-shrink-0 mt-0.5" />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3 className="text-lg font-semibold text-yellow-900 dark:text-yellow-200 mb-2">Machine Action Required</h3>
|
<h3 className="text-base font-semibold text-yellow-900 dark:text-yellow-200 mb-2">Machine Action Required</h3>
|
||||||
<p className="text-yellow-800 dark:text-yellow-300 mb-3">The machine is ready to trace the pattern outline.</p>
|
<p className="text-sm text-yellow-800 dark:text-yellow-300 mb-3">The machine is ready to trace the pattern outline.</p>
|
||||||
<ul className="list-disc list-inside text-sm text-yellow-700 dark:text-yellow-300 space-y-1">
|
<ul className="list-disc list-inside text-sm text-yellow-700 dark:text-yellow-300 space-y-1">
|
||||||
<li><strong>Press the button on your machine</strong> to confirm and start the mask trace</li>
|
<li><strong>Press the button on your machine</strong> to confirm and start the mask trace</li>
|
||||||
<li>Ensure the hoop is properly attached</li>
|
<li>Ensure the hoop is properly attached</li>
|
||||||
|
|
@ -187,12 +197,12 @@ export function NextStepGuide({
|
||||||
|
|
||||||
case MachineStatus.MASK_TRACING:
|
case MachineStatus.MASK_TRACING:
|
||||||
return (
|
return (
|
||||||
<div className="bg-cyan-50 dark:bg-cyan-900/20 border-l-4 border-cyan-600 dark:border-cyan-500 p-6 rounded-lg shadow-md animate-fadeIn">
|
<div className="bg-cyan-50 dark:bg-cyan-900/95 border-l-4 border-cyan-600 dark:border-cyan-500 p-4 rounded-lg shadow-lg backdrop-blur-sm">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-3">
|
||||||
<InformationCircleIcon className="w-8 h-8 text-cyan-600 dark:text-cyan-400 flex-shrink-0 mt-1" />
|
<InformationCircleIcon className="w-6 h-6 text-cyan-600 dark:text-cyan-400 flex-shrink-0 mt-0.5" />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3 className="text-lg font-semibold text-cyan-900 dark:text-cyan-200 mb-2">Mask Trace In Progress</h3>
|
<h3 className="text-base font-semibold text-cyan-900 dark:text-cyan-200 mb-2">Mask Trace In Progress</h3>
|
||||||
<p className="text-cyan-800 dark:text-cyan-300 mb-3">The machine is tracing the pattern boundary. Please wait...</p>
|
<p className="text-sm text-cyan-800 dark:text-cyan-300 mb-3">The machine is tracing the pattern boundary. Please wait...</p>
|
||||||
<ul className="list-disc list-inside text-sm text-cyan-700 dark:text-cyan-300 space-y-1">
|
<ul className="list-disc list-inside text-sm text-cyan-700 dark:text-cyan-300 space-y-1">
|
||||||
<li>Watch the machine trace the outline</li>
|
<li>Watch the machine trace the outline</li>
|
||||||
<li>Verify the pattern fits within your hoop</li>
|
<li>Verify the pattern fits within your hoop</li>
|
||||||
|
|
@ -206,12 +216,12 @@ export function NextStepGuide({
|
||||||
case MachineStatus.MASK_TRACE_COMPLETE:
|
case MachineStatus.MASK_TRACE_COMPLETE:
|
||||||
case MachineStatus.SEWING_WAIT:
|
case MachineStatus.SEWING_WAIT:
|
||||||
return (
|
return (
|
||||||
<div className="bg-green-50 dark:bg-green-900/20 border-l-4 border-green-600 dark:border-green-500 p-6 rounded-lg shadow-md animate-fadeIn">
|
<div className="bg-green-50 dark:bg-green-900/95 border-l-4 border-green-600 dark:border-green-500 p-4 rounded-lg shadow-lg backdrop-blur-sm">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-3">
|
||||||
<InformationCircleIcon className="w-8 h-8 text-green-600 dark:text-green-400 flex-shrink-0 mt-1" />
|
<InformationCircleIcon className="w-6 h-6 text-green-600 dark:text-green-400 flex-shrink-0 mt-0.5" />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3 className="text-lg font-semibold text-green-900 dark:text-green-200 mb-2">Step 5: Ready to Sew!</h3>
|
<h3 className="text-base font-semibold text-green-900 dark:text-green-200 mb-2">Step 5: Ready to Sew!</h3>
|
||||||
<p className="text-green-800 dark:text-green-300 mb-3">The machine is ready to begin embroidering your pattern.</p>
|
<p className="text-sm text-green-800 dark:text-green-300 mb-3">The machine is ready to begin embroidering your pattern.</p>
|
||||||
<ul className="list-disc list-inside text-sm text-green-700 dark:text-green-300 space-y-1">
|
<ul className="list-disc list-inside text-sm text-green-700 dark:text-green-300 space-y-1">
|
||||||
<li>Verify your thread colors are correct</li>
|
<li>Verify your thread colors are correct</li>
|
||||||
<li>Ensure the fabric is properly hooped</li>
|
<li>Ensure the fabric is properly hooped</li>
|
||||||
|
|
@ -224,12 +234,12 @@ export function NextStepGuide({
|
||||||
|
|
||||||
case MachineStatus.SEWING:
|
case MachineStatus.SEWING:
|
||||||
return (
|
return (
|
||||||
<div className="bg-cyan-50 dark:bg-cyan-900/20 border-l-4 border-cyan-600 dark:border-cyan-500 p-6 rounded-lg shadow-md animate-fadeIn">
|
<div className="bg-cyan-50 dark:bg-cyan-900/95 border-l-4 border-cyan-600 dark:border-cyan-500 p-4 rounded-lg shadow-lg backdrop-blur-sm">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-3">
|
||||||
<InformationCircleIcon className="w-8 h-8 text-cyan-600 dark:text-cyan-400 flex-shrink-0 mt-1" />
|
<InformationCircleIcon className="w-6 h-6 text-cyan-600 dark:text-cyan-400 flex-shrink-0 mt-0.5" />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3 className="text-lg font-semibold text-cyan-900 dark:text-cyan-200 mb-2">Step 6: Sewing In Progress</h3>
|
<h3 className="text-base font-semibold text-cyan-900 dark:text-cyan-200 mb-2">Step 6: Sewing In Progress</h3>
|
||||||
<p className="text-cyan-800 dark:text-cyan-300 mb-3">Your embroidery is being stitched. Monitor the progress below.</p>
|
<p className="text-sm text-cyan-800 dark:text-cyan-300 mb-3">Your embroidery is being stitched. Monitor the progress below.</p>
|
||||||
<ul className="list-disc list-inside text-sm text-cyan-700 dark:text-cyan-300 space-y-1">
|
<ul className="list-disc list-inside text-sm text-cyan-700 dark:text-cyan-300 space-y-1">
|
||||||
<li>Watch the progress bar and current stitch count</li>
|
<li>Watch the progress bar and current stitch count</li>
|
||||||
<li>The machine will pause when a color change is needed</li>
|
<li>The machine will pause when a color change is needed</li>
|
||||||
|
|
@ -242,12 +252,12 @@ export function NextStepGuide({
|
||||||
|
|
||||||
case MachineStatus.COLOR_CHANGE_WAIT:
|
case MachineStatus.COLOR_CHANGE_WAIT:
|
||||||
return (
|
return (
|
||||||
<div className="bg-yellow-50 dark:bg-yellow-900/20 border-l-4 border-yellow-600 dark:border-yellow-500 p-6 rounded-lg shadow-md animate-fadeIn">
|
<div className="bg-yellow-50 dark:bg-yellow-900/95 border-l-4 border-yellow-600 dark:border-yellow-500 p-4 rounded-lg shadow-lg backdrop-blur-sm">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-3">
|
||||||
<InformationCircleIcon className="w-8 h-8 text-yellow-600 dark:text-yellow-400 flex-shrink-0 mt-1" />
|
<InformationCircleIcon className="w-6 h-6 text-yellow-600 dark:text-yellow-400 flex-shrink-0 mt-0.5" />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3 className="text-lg font-semibold text-yellow-900 dark:text-yellow-200 mb-2">Thread Change Required</h3>
|
<h3 className="text-base font-semibold text-yellow-900 dark:text-yellow-200 mb-2">Thread Change Required</h3>
|
||||||
<p className="text-yellow-800 dark:text-yellow-300 mb-3">The machine needs a different thread color to continue.</p>
|
<p className="text-sm text-yellow-800 dark:text-yellow-300 mb-3">The machine needs a different thread color to continue.</p>
|
||||||
<ul className="list-disc list-inside text-sm text-yellow-700 dark:text-yellow-300 space-y-1">
|
<ul className="list-disc list-inside text-sm text-yellow-700 dark:text-yellow-300 space-y-1">
|
||||||
<li>Check the color blocks section to see which thread is needed</li>
|
<li>Check the color blocks section to see which thread is needed</li>
|
||||||
<li>Change to the correct thread color</li>
|
<li>Change to the correct thread color</li>
|
||||||
|
|
@ -262,12 +272,12 @@ export function NextStepGuide({
|
||||||
case MachineStatus.STOP:
|
case MachineStatus.STOP:
|
||||||
case MachineStatus.SEWING_INTERRUPTION:
|
case MachineStatus.SEWING_INTERRUPTION:
|
||||||
return (
|
return (
|
||||||
<div className="bg-yellow-50 dark:bg-yellow-900/20 border-l-4 border-yellow-600 dark:border-yellow-500 p-6 rounded-lg shadow-md animate-fadeIn">
|
<div className="bg-yellow-50 dark:bg-yellow-900/95 border-l-4 border-yellow-600 dark:border-yellow-500 p-4 rounded-lg shadow-lg backdrop-blur-sm">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-3">
|
||||||
<InformationCircleIcon className="w-8 h-8 text-yellow-600 dark:text-yellow-400 flex-shrink-0 mt-1" />
|
<InformationCircleIcon className="w-6 h-6 text-yellow-600 dark:text-yellow-400 flex-shrink-0 mt-0.5" />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3 className="text-lg font-semibold text-yellow-900 dark:text-yellow-200 mb-2">Sewing Paused</h3>
|
<h3 className="text-base font-semibold text-yellow-900 dark:text-yellow-200 mb-2">Sewing Paused</h3>
|
||||||
<p className="text-yellow-800 dark:text-yellow-300 mb-3">The embroidery has been paused or interrupted.</p>
|
<p className="text-sm text-yellow-800 dark:text-yellow-300 mb-3">The embroidery has been paused or interrupted.</p>
|
||||||
<ul className="list-disc list-inside text-sm text-yellow-700 dark:text-yellow-300 space-y-1">
|
<ul className="list-disc list-inside text-sm text-yellow-700 dark:text-yellow-300 space-y-1">
|
||||||
<li>Check if everything is okay with the machine</li>
|
<li>Check if everything is okay with the machine</li>
|
||||||
<li>Click "Resume Sewing" when ready to continue</li>
|
<li>Click "Resume Sewing" when ready to continue</li>
|
||||||
|
|
@ -280,12 +290,12 @@ export function NextStepGuide({
|
||||||
|
|
||||||
case MachineStatus.SEWING_COMPLETE:
|
case MachineStatus.SEWING_COMPLETE:
|
||||||
return (
|
return (
|
||||||
<div className="bg-green-50 dark:bg-green-900/20 border-l-4 border-green-600 dark:border-green-500 p-6 rounded-lg shadow-md animate-fadeIn">
|
<div className="bg-green-50 dark:bg-green-900/95 border-l-4 border-green-600 dark:border-green-500 p-4 rounded-lg shadow-lg backdrop-blur-sm">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-3">
|
||||||
<InformationCircleIcon className="w-8 h-8 text-green-600 dark:text-green-400 flex-shrink-0 mt-1" />
|
<InformationCircleIcon className="w-6 h-6 text-green-600 dark:text-green-400 flex-shrink-0 mt-0.5" />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3 className="text-lg font-semibold text-green-900 dark:text-green-200 mb-2">Step 7: Embroidery Complete!</h3>
|
<h3 className="text-base font-semibold text-green-900 dark:text-green-200 mb-2">Step 7: Embroidery Complete!</h3>
|
||||||
<p className="text-green-800 dark:text-green-300 mb-3">Your embroidery is finished. Great work!</p>
|
<p className="text-sm text-green-800 dark:text-green-300 mb-3">Your embroidery is finished. Great work!</p>
|
||||||
<ul className="list-disc list-inside text-sm text-green-700 dark:text-green-300 space-y-1">
|
<ul className="list-disc list-inside text-sm text-green-700 dark:text-green-300 space-y-1">
|
||||||
<li>Remove the hoop from the machine</li>
|
<li>Remove the hoop from the machine</li>
|
||||||
<li>Press the Accept button on the machine</li>
|
<li>Press the Accept button on the machine</li>
|
||||||
|
|
@ -301,4 +311,44 @@ export function NextStepGuide({
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Render floating overlay
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* Collapsed state - small info button in bottom-right */}
|
||||||
|
{!isExpanded && (
|
||||||
|
<div className="fixed bottom-4 right-4 z-50 animate-fadeIn">
|
||||||
|
<button
|
||||||
|
onClick={() => setIsExpanded(true)}
|
||||||
|
className="w-12 h-12 bg-blue-600 dark:bg-blue-700 hover:bg-blue-700 dark:hover:bg-blue-600 text-white rounded-full shadow-lg flex items-center justify-center transition-all hover:scale-110 animate-pulse"
|
||||||
|
aria-label="Show next step guide"
|
||||||
|
title="Show next step guide"
|
||||||
|
>
|
||||||
|
<InformationCircleIcon className="w-7 h-7" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Expanded state - floating overlay */}
|
||||||
|
{isExpanded && (
|
||||||
|
<div className="fixed bottom-4 right-4 z-50 w-[90vw] max-w-[400px] max-h-[40vh] overflow-y-auto animate-slideInRight">
|
||||||
|
<div className="relative rounded-lg overflow-hidden shadow-xl">
|
||||||
|
{/* Close button */}
|
||||||
|
<button
|
||||||
|
onClick={() => setIsExpanded(false)}
|
||||||
|
className="absolute top-2 right-2 z-10 w-6 h-6 bg-white/90 dark:bg-gray-800/90 hover:bg-white dark:hover:bg-gray-700 rounded-full flex items-center justify-center transition-colors shadow-md"
|
||||||
|
aria-label="Minimize guide"
|
||||||
|
title="Minimize guide"
|
||||||
|
>
|
||||||
|
<XMarkIcon className="w-4 h-4 text-gray-700 dark:text-gray-300" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
{renderContent()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -189,8 +189,8 @@ export function PatternCanvas({ pesData, sewingProgress, machineInfo, initialPat
|
||||||
const iconColor = pesData ? 'text-teal-600 dark:text-teal-400' : 'text-gray-600 dark:text-gray-400';
|
const iconColor = pesData ? 'text-teal-600 dark:text-teal-400' : 'text-gray-600 dark:text-gray-400';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`bg-white dark:bg-gray-800 p-4 rounded-lg shadow-md border-l-4 ${borderColor}`}>
|
<div className={`lg:h-full bg-white dark:bg-gray-800 p-4 rounded-lg shadow-md border-l-4 ${borderColor} flex flex-col`}>
|
||||||
<div className="flex items-start gap-3 mb-3">
|
<div className="flex items-start gap-3 mb-3 flex-shrink-0">
|
||||||
<PhotoIcon className={`w-6 h-6 ${iconColor} flex-shrink-0 mt-0.5`} />
|
<PhotoIcon className={`w-6 h-6 ${iconColor} flex-shrink-0 mt-0.5`} />
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-1">Pattern Preview</h3>
|
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-1">Pattern Preview</h3>
|
||||||
|
|
@ -203,7 +203,7 @@ export function PatternCanvas({ pesData, sewingProgress, machineInfo, initialPat
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative w-full h-[400px] sm:h-[500px] lg:h-[600px] max-h-[70vh] border border-gray-300 dark:border-gray-600 rounded bg-gray-50 dark:bg-gray-900 overflow-hidden" ref={containerRef}>
|
<div className="relative w-full h-[400px] sm:h-[500px] lg:flex-1 lg:min-h-0 border border-gray-300 dark:border-gray-600 rounded bg-gray-50 dark:bg-gray-900 overflow-hidden" ref={containerRef}>
|
||||||
{containerSize.width > 0 && (
|
{containerSize.width > 0 && (
|
||||||
<Stage
|
<Stage
|
||||||
width={containerSize.width}
|
width={containerSize.width}
|
||||||
|
|
@ -291,7 +291,7 @@ export function PatternCanvas({ pesData, sewingProgress, machineInfo, initialPat
|
||||||
|
|
||||||
{/* Placeholder overlay when no pattern is loaded */}
|
{/* Placeholder overlay when no pattern is loaded */}
|
||||||
{!pesData && (
|
{!pesData && (
|
||||||
<div className="flex items-center justify-center h-[400px] sm:h-[500px] lg:h-[600px] text-gray-600 dark:text-gray-400 italic">
|
<div className="flex items-center justify-center h-full text-gray-600 dark:text-gray-400 italic">
|
||||||
Load a PES file to preview the pattern
|
Load a PES file to preview the pattern
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { useRef, useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
CheckCircleIcon,
|
CheckCircleIcon,
|
||||||
ArrowRightIcon,
|
ArrowRightIcon,
|
||||||
|
|
@ -42,6 +43,10 @@ export function ProgressMonitor({
|
||||||
onResumeSewing,
|
onResumeSewing,
|
||||||
isDeleting = false,
|
isDeleting = false,
|
||||||
}: ProgressMonitorProps) {
|
}: ProgressMonitorProps) {
|
||||||
|
const currentBlockRef = useRef<HTMLDivElement>(null);
|
||||||
|
const colorBlocksScrollRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [showGradient, setShowGradient] = useState(true);
|
||||||
|
|
||||||
// State indicators
|
// State indicators
|
||||||
const isMaskTraceComplete =
|
const isMaskTraceComplete =
|
||||||
machineStatus === MachineStatus.MASK_TRACE_COMPLETE;
|
machineStatus === MachineStatus.MASK_TRACE_COMPLETE;
|
||||||
|
|
@ -108,6 +113,40 @@ export function ProgressMonitor({
|
||||||
currentStitch >= block.startStitch && currentStitch < block.endStitch,
|
currentStitch >= block.startStitch && currentStitch < block.endStitch,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Auto-scroll to current block
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentBlockRef.current) {
|
||||||
|
currentBlockRef.current.scrollIntoView({
|
||||||
|
behavior: "smooth",
|
||||||
|
block: "nearest",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [currentBlockIndex]);
|
||||||
|
|
||||||
|
// Handle scroll to detect if at bottom
|
||||||
|
const handleColorBlocksScroll = () => {
|
||||||
|
if (colorBlocksScrollRef.current) {
|
||||||
|
const { scrollTop, scrollHeight, clientHeight } = colorBlocksScrollRef.current;
|
||||||
|
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 5; // 5px threshold
|
||||||
|
setShowGradient(!isAtBottom);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check initial scroll state and update on resize
|
||||||
|
useEffect(() => {
|
||||||
|
const checkScrollable = () => {
|
||||||
|
if (colorBlocksScrollRef.current) {
|
||||||
|
const { scrollHeight, clientHeight } = colorBlocksScrollRef.current;
|
||||||
|
const isScrollable = scrollHeight > clientHeight;
|
||||||
|
setShowGradient(isScrollable);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
checkScrollable();
|
||||||
|
window.addEventListener('resize', checkScrollable);
|
||||||
|
return () => window.removeEventListener('resize', checkScrollable);
|
||||||
|
}, [colorBlocks]);
|
||||||
|
|
||||||
const stateIndicatorColors = {
|
const stateIndicatorColors = {
|
||||||
idle: "bg-blue-50 dark:bg-blue-900/20 border-blue-600",
|
idle: "bg-blue-50 dark:bg-blue-900/20 border-blue-600",
|
||||||
info: "bg-blue-50 dark:bg-blue-900/20 border-blue-600",
|
info: "bg-blue-50 dark:bg-blue-900/20 border-blue-600",
|
||||||
|
|
@ -122,7 +161,7 @@ export function ProgressMonitor({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white dark:bg-gray-800 p-4 rounded-lg shadow-md border-l-4 border-purple-600 dark:border-purple-500">
|
<div className="lg:h-full bg-white dark:bg-gray-800 p-4 rounded-lg shadow-md border-l-4 border-purple-600 dark:border-purple-500 flex flex-col lg:overflow-hidden">
|
||||||
<div className="flex items-start gap-3 mb-3">
|
<div className="flex items-start gap-3 mb-3">
|
||||||
<ChartBarIcon className="w-6 h-6 text-purple-600 dark:text-purple-400 flex-shrink-0 mt-0.5" />
|
<ChartBarIcon className="w-6 h-6 text-purple-600 dark:text-purple-400 flex-shrink-0 mt-0.5" />
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
|
|
@ -246,11 +285,16 @@ export function ProgressMonitor({
|
||||||
|
|
||||||
{/* Color Blocks */}
|
{/* Color Blocks */}
|
||||||
{colorBlocks.length > 0 && (
|
{colorBlocks.length > 0 && (
|
||||||
<div className="mb-3">
|
<div className="mb-3 lg:flex-1 lg:min-h-0 flex flex-col">
|
||||||
<h4 className="text-xs font-semibold mb-2 text-gray-700 dark:text-gray-300">
|
<h4 className="text-xs font-semibold mb-2 text-gray-700 dark:text-gray-300 flex-shrink-0">
|
||||||
Color Blocks
|
Color Blocks
|
||||||
</h4>
|
</h4>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="relative lg:flex-1 lg:min-h-0">
|
||||||
|
<div
|
||||||
|
ref={colorBlocksScrollRef}
|
||||||
|
onScroll={handleColorBlocksScroll}
|
||||||
|
className="lg:absolute lg:inset-0 flex flex-col gap-2 lg:overflow-y-auto scroll-smooth pr-1 [&::-webkit-scrollbar]:w-1 [&::-webkit-scrollbar-track]:bg-gray-100 dark:[&::-webkit-scrollbar-track]:bg-gray-700 [&::-webkit-scrollbar-thumb]:bg-blue-600 dark:[&::-webkit-scrollbar-thumb]:bg-blue-500 [&::-webkit-scrollbar-thumb]:rounded-full"
|
||||||
|
>
|
||||||
{colorBlocks.map((block, index) => {
|
{colorBlocks.map((block, index) => {
|
||||||
const isCompleted = currentStitch >= block.endStitch;
|
const isCompleted = currentStitch >= block.endStitch;
|
||||||
const isCurrent = index === currentBlockIndex;
|
const isCurrent = index === currentBlockIndex;
|
||||||
|
|
@ -268,6 +312,7 @@ export function ProgressMonitor({
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
|
ref={isCurrent ? currentBlockRef : null}
|
||||||
className={`p-2.5 rounded-lg border-2 transition-all duration-300 ${
|
className={`p-2.5 rounded-lg border-2 transition-all duration-300 ${
|
||||||
isCompleted
|
isCompleted
|
||||||
? "border-green-600 bg-green-50 dark:bg-green-900/20"
|
? "border-green-600 bg-green-50 dark:bg-green-900/20"
|
||||||
|
|
@ -359,11 +404,16 @@ export function ProgressMonitor({
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
{/* Gradient overlay to indicate more content below - only on desktop and when not at bottom */}
|
||||||
|
{showGradient && (
|
||||||
|
<div className="hidden lg:block absolute bottom-0 left-0 right-0 h-8 bg-gradient-to-t from-white dark:from-gray-800 to-transparent pointer-events-none" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Action buttons */}
|
{/* Action buttons */}
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2 flex-shrink-0">
|
||||||
{/* Resume has highest priority when available */}
|
{/* Resume has highest priority when available */}
|
||||||
{canResumeSewing(machineStatus) && (
|
{canResumeSewing(machineStatus) && (
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue