mirror of
https://github.com/jhbruhn/respira.git
synced 2026-01-27 10:23:41 +00:00
Add Heroicons and remove button hover lift effect
- Install @heroicons/react package - Replace text symbols with proper heroicons: - Zoom controls: PlusIcon, MinusIcon, ArrowPathIcon - Color block status: CheckCircleIcon, ArrowRightIcon, CircleStackIcon - Resume sewing: PlayIcon - Remove hover:-translate-y-0.5 and active:translate-y-0 from all buttons - Buttons now have stable hover states (color change and shadow only) - Improved UI consistency and accessibility with vector icons 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
cd43a64bc4
commit
bf20c2b378
7 changed files with 42 additions and 24 deletions
10
package-lock.json
generated
10
package-lock.json
generated
|
|
@ -8,6 +8,7 @@
|
||||||
"name": "web",
|
"name": "web",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@heroicons/react": "^2.2.0",
|
||||||
"@tailwindcss/vite": "^4.1.17",
|
"@tailwindcss/vite": "^4.1.17",
|
||||||
"konva": "^10.0.12",
|
"konva": "^10.0.12",
|
||||||
"pyodide": "^0.27.4",
|
"pyodide": "^0.27.4",
|
||||||
|
|
@ -831,6 +832,15 @@
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@heroicons/react": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 16 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@humanfs/core": {
|
"node_modules/@humanfs/core": {
|
||||||
"version": "0.19.1",
|
"version": "0.19.1",
|
||||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@heroicons/react": "^2.2.0",
|
||||||
"@tailwindcss/vite": "^4.1.17",
|
"@tailwindcss/vite": "^4.1.17",
|
||||||
"konva": "^10.0.12",
|
"konva": "^10.0.12",
|
||||||
"pyodide": "^0.27.4",
|
"pyodide": "^0.27.4",
|
||||||
|
|
|
||||||
|
|
@ -55,14 +55,14 @@ export function ConfirmDialog({
|
||||||
<div className="p-4 px-6 flex gap-3 justify-end border-t border-gray-300">
|
<div className="p-4 px-6 flex gap-3 justify-end border-t border-gray-300">
|
||||||
<button
|
<button
|
||||||
onClick={onCancel}
|
onClick={onCancel}
|
||||||
className="px-6 py-3 bg-gray-600 text-white rounded font-semibold text-sm hover:bg-gray-700 transition-all hover:shadow-md hover:-translate-y-0.5 active:translate-y-0 disabled:opacity-50 disabled:cursor-not-allowed disabled:grayscale-[0.3]"
|
className="px-6 py-3 bg-gray-600 text-white rounded font-semibold text-sm hover:bg-gray-700 transition-all hover:shadow-md disabled:opacity-50 disabled:cursor-not-allowed disabled:grayscale-[0.3]"
|
||||||
autoFocus
|
autoFocus
|
||||||
>
|
>
|
||||||
{cancelText}
|
{cancelText}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={onConfirm}
|
onClick={onConfirm}
|
||||||
className={variant === 'danger' ? 'px-6 py-3 bg-red-600 text-white rounded font-semibold text-sm hover:bg-red-700 transition-all hover:shadow-md hover:-translate-y-0.5 active:translate-y-0 disabled:opacity-50 disabled:cursor-not-allowed disabled:grayscale-[0.3]' : 'px-6 py-3 bg-blue-600 text-white rounded font-semibold text-sm hover:bg-blue-700 transition-all hover:shadow-md hover:-translate-y-0.5 active:translate-y-0 disabled:opacity-50 disabled:cursor-not-allowed disabled:grayscale-[0.3]'}
|
className={variant === 'danger' ? 'px-6 py-3 bg-red-600 text-white rounded font-semibold text-sm hover:bg-red-700 transition-all hover:shadow-md disabled:opacity-50 disabled:cursor-not-allowed disabled:grayscale-[0.3]' : 'px-6 py-3 bg-blue-600 text-white rounded font-semibold text-sm hover:bg-blue-700 transition-all hover:shadow-md disabled:opacity-50 disabled:cursor-not-allowed disabled:grayscale-[0.3]'}
|
||||||
>
|
>
|
||||||
{confirmText}
|
{confirmText}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ export function FileUpload({
|
||||||
className="hidden"
|
className="hidden"
|
||||||
disabled={!pyodideReady || isLoading}
|
disabled={!pyodideReady || isLoading}
|
||||||
/>
|
/>
|
||||||
<label htmlFor="file-input" className={`inline-block px-6 py-3 bg-gray-600 text-white rounded font-semibold text-sm cursor-pointer transition-all ${!pyodideReady || isLoading ? 'opacity-50 cursor-not-allowed grayscale-[0.3]' : 'hover:bg-gray-700 hover:shadow-md hover:-translate-y-0.5 active:translate-y-0'}`}>
|
<label htmlFor="file-input" className={`inline-block px-6 py-3 bg-gray-600 text-white rounded font-semibold text-sm cursor-pointer transition-all ${!pyodideReady || isLoading ? 'opacity-50 cursor-not-allowed grayscale-[0.3]' : 'hover:bg-gray-700 hover:shadow-md'}`}>
|
||||||
{isLoading ? 'Loading...' : !pyodideReady ? 'Initializing...' : 'Choose PES File'}
|
{isLoading ? 'Loading...' : !pyodideReady ? 'Initializing...' : 'Choose PES File'}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
|
@ -110,7 +110,7 @@ export function FileUpload({
|
||||||
<button
|
<button
|
||||||
onClick={handleUpload}
|
onClick={handleUpload}
|
||||||
disabled={!isConnected || uploadProgress > 0}
|
disabled={!isConnected || uploadProgress > 0}
|
||||||
className="mt-4 px-6 py-3 bg-blue-600 text-white rounded font-semibold text-sm hover:bg-blue-700 transition-all hover:shadow-md hover:-translate-y-0.5 active:translate-y-0 disabled:opacity-50 disabled:cursor-not-allowed disabled:grayscale-[0.3]"
|
className="mt-4 px-6 py-3 bg-blue-600 text-white rounded font-semibold text-sm hover:bg-blue-700 transition-all hover:shadow-md disabled:opacity-50 disabled:cursor-not-allowed disabled:grayscale-[0.3]"
|
||||||
>
|
>
|
||||||
{uploadProgress > 0
|
{uploadProgress > 0
|
||||||
? `Uploading... ${uploadProgress.toFixed(0)}%`
|
? `Uploading... ${uploadProgress.toFixed(0)}%`
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ export function MachineConnection({
|
||||||
|
|
||||||
{!isConnected ? (
|
{!isConnected ? (
|
||||||
<div className="flex gap-3 mt-4 flex-wrap">
|
<div className="flex gap-3 mt-4 flex-wrap">
|
||||||
<button onClick={onConnect} className="px-6 py-3 bg-blue-600 text-white rounded font-semibold text-sm hover:bg-blue-700 transition-all hover:shadow-md hover:-translate-y-0.5 active:translate-y-0 disabled:opacity-50 disabled:cursor-not-allowed disabled:grayscale-[0.3]">
|
<button onClick={onConnect} className="px-6 py-3 bg-blue-600 text-white rounded font-semibold text-sm hover:bg-blue-700 transition-all hover:shadow-md disabled:opacity-50 disabled:cursor-not-allowed disabled:grayscale-[0.3]">
|
||||||
Connect to Machine
|
Connect to Machine
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -122,10 +122,10 @@ export function MachineConnection({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex gap-3 mt-4 flex-wrap">
|
<div className="flex gap-3 mt-4 flex-wrap">
|
||||||
<button onClick={onRefresh} className="px-6 py-3 bg-gray-600 text-white rounded font-semibold text-sm hover:bg-gray-700 transition-all hover:shadow-md hover:-translate-y-0.5 active:translate-y-0 disabled:opacity-50 disabled:cursor-not-allowed disabled:grayscale-[0.3]">
|
<button onClick={onRefresh} className="px-6 py-3 bg-gray-600 text-white rounded font-semibold text-sm hover:bg-gray-700 transition-all hover:shadow-md disabled:opacity-50 disabled:cursor-not-allowed disabled:grayscale-[0.3]">
|
||||||
Refresh Status
|
Refresh Status
|
||||||
</button>
|
</button>
|
||||||
<button onClick={handleDisconnectClick} className="px-6 py-3 bg-red-600 text-white rounded font-semibold text-sm hover:bg-red-700 transition-all hover:shadow-md hover:-translate-y-0.5 active:translate-y-0 disabled:opacity-50 disabled:cursor-not-allowed disabled:grayscale-[0.3]">
|
<button onClick={handleDisconnectClick} className="px-6 py-3 bg-red-600 text-white rounded font-semibold text-sm hover:bg-red-700 transition-all hover:shadow-md disabled:opacity-50 disabled:cursor-not-allowed disabled:grayscale-[0.3]">
|
||||||
Disconnect
|
Disconnect
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { useEffect, useRef, useState, useCallback } from 'react';
|
import { useEffect, useRef, useState, useCallback } from 'react';
|
||||||
import { Stage, Layer, Group } from 'react-konva';
|
import { Stage, Layer, Group } from 'react-konva';
|
||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
|
import { PlusIcon, MinusIcon, ArrowPathIcon } from '@heroicons/react/24/solid';
|
||||||
import type { PesPatternData } from '../utils/pystitchConverter';
|
import type { PesPatternData } from '../utils/pystitchConverter';
|
||||||
import type { SewingProgress, MachineInfo } from '../types/machine';
|
import type { SewingProgress, MachineInfo } from '../types/machine';
|
||||||
import { calculateInitialScale } from '../utils/konvaRenderers';
|
import { calculateInitialScale } from '../utils/konvaRenderers';
|
||||||
|
|
@ -298,15 +299,15 @@ export function PatternCanvas({ pesData, sewingProgress, machineInfo, initialPat
|
||||||
|
|
||||||
{/* Zoom Controls Overlay */}
|
{/* Zoom Controls Overlay */}
|
||||||
<div className="absolute bottom-5 right-5 flex gap-2 items-center bg-white/95 backdrop-blur-sm px-3 py-2 rounded-lg shadow-lg z-10">
|
<div className="absolute bottom-5 right-5 flex gap-2 items-center bg-white/95 backdrop-blur-sm px-3 py-2 rounded-lg shadow-lg z-10">
|
||||||
<button className="w-8 h-8 p-0 text-lg font-bold border border-gray-300 bg-white rounded cursor-pointer transition-all flex items-center justify-center hover:bg-blue-600 hover:text-white hover:border-blue-600 hover:-translate-y-0.5 hover:shadow-md hover:shadow-blue-600/30 active:translate-y-0 disabled:opacity-50 disabled:cursor-not-allowed" onClick={handleZoomIn} title="Zoom In">
|
<button className="w-8 h-8 p-1 border border-gray-300 bg-white rounded cursor-pointer transition-all flex items-center justify-center hover:bg-blue-600 hover:text-white hover:border-blue-600 hover:shadow-md hover:shadow-blue-600/30 disabled:opacity-50 disabled:cursor-not-allowed" onClick={handleZoomIn} title="Zoom In">
|
||||||
+
|
<PlusIcon className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
<span className="min-w-[50px] text-center text-[13px] font-semibold text-gray-900 select-none">{Math.round(stageScale * 100)}%</span>
|
<span className="min-w-[50px] text-center text-[13px] font-semibold text-gray-900 select-none">{Math.round(stageScale * 100)}%</span>
|
||||||
<button className="w-8 h-8 p-0 text-lg font-bold border border-gray-300 bg-white rounded cursor-pointer transition-all flex items-center justify-center hover:bg-blue-600 hover:text-white hover:border-blue-600 hover:-translate-y-0.5 hover:shadow-md hover:shadow-blue-600/30 active:translate-y-0 disabled:opacity-50 disabled:cursor-not-allowed" onClick={handleZoomOut} title="Zoom Out">
|
<button className="w-8 h-8 p-1 border border-gray-300 bg-white rounded cursor-pointer transition-all flex items-center justify-center hover:bg-blue-600 hover:text-white hover:border-blue-600 hover:shadow-md hover:shadow-blue-600/30 disabled:opacity-50 disabled:cursor-not-allowed" onClick={handleZoomOut} title="Zoom Out">
|
||||||
−
|
<MinusIcon className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
<button className="w-8 h-8 p-0 text-xl font-bold border border-gray-300 bg-white rounded cursor-pointer transition-all flex items-center justify-center hover:bg-blue-600 hover:text-white hover:border-blue-600 hover:-translate-y-0.5 hover:shadow-md hover:shadow-blue-600/30 active:translate-y-0 disabled:opacity-50 disabled:cursor-not-allowed ml-1" onClick={handleZoomReset} title="Reset Zoom">
|
<button className="w-8 h-8 p-1 border border-gray-300 bg-white rounded cursor-pointer transition-all flex items-center justify-center hover:bg-blue-600 hover:text-white hover:border-blue-600 hover:shadow-md hover:shadow-blue-600/30 disabled:opacity-50 disabled:cursor-not-allowed ml-1" onClick={handleZoomReset} title="Reset Zoom">
|
||||||
⟲
|
<ArrowPathIcon className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { CheckCircleIcon, ArrowRightIcon, CircleStackIcon, PlayIcon } from '@heroicons/react/24/solid';
|
||||||
import type { PatternInfo, SewingProgress } from '../types/machine';
|
import type { PatternInfo, SewingProgress } from '../types/machine';
|
||||||
import { MachineStatus } from '../types/machine';
|
import { MachineStatus } from '../types/machine';
|
||||||
import type { PesPatternData } from '../utils/pystitchConverter';
|
import type { PesPatternData } from '../utils/pystitchConverter';
|
||||||
|
|
@ -161,9 +162,13 @@ export function ProgressMonitor({
|
||||||
<span className="font-semibold flex-1">
|
<span className="font-semibold flex-1">
|
||||||
Thread {block.colorIndex + 1}
|
Thread {block.colorIndex + 1}
|
||||||
</span>
|
</span>
|
||||||
<span className={`text-xl font-bold ${isCompleted ? 'text-green-600' : isCurrent ? 'text-blue-600' : 'text-gray-600'}`}>
|
{isCompleted ? (
|
||||||
{isCompleted ? '✓' : isCurrent ? '→' : '○'}
|
<CheckCircleIcon className="w-6 h-6 text-green-600" />
|
||||||
</span>
|
) : isCurrent ? (
|
||||||
|
<ArrowRightIcon className="w-6 h-6 text-blue-600" />
|
||||||
|
) : (
|
||||||
|
<CircleStackIcon className="w-6 h-6 text-gray-400" />
|
||||||
|
)}
|
||||||
<span className="text-sm text-gray-600">
|
<span className="text-sm text-gray-600">
|
||||||
{block.stitchCount} stitches
|
{block.stitchCount} stitches
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -249,12 +254,12 @@ export function ProgressMonitor({
|
||||||
Mask trace complete!
|
Mask trace complete!
|
||||||
</div>
|
</div>
|
||||||
{canStartSewing(machineStatus) && (
|
{canStartSewing(machineStatus) && (
|
||||||
<button onClick={onStartSewing} className="px-6 py-3 bg-blue-600 text-white rounded font-semibold text-sm hover:bg-blue-700 transition-all hover:shadow-md hover:-translate-y-0.5 active:translate-y-0 disabled:opacity-50 disabled:cursor-not-allowed disabled:grayscale-[0.3]">
|
<button onClick={onStartSewing} className="px-6 py-3 bg-blue-600 text-white rounded font-semibold text-sm hover:bg-blue-700 transition-all hover:shadow-md disabled:opacity-50 disabled:cursor-not-allowed disabled:grayscale-[0.3]">
|
||||||
Start Sewing
|
Start Sewing
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{canStartMaskTrace(machineStatus) && (
|
{canStartMaskTrace(machineStatus) && (
|
||||||
<button onClick={onStartMaskTrace} className="px-6 py-3 bg-gray-600 text-white rounded font-semibold text-sm hover:bg-gray-700 transition-all hover:shadow-md hover:-translate-y-0.5 active:translate-y-0 disabled:opacity-50 disabled:cursor-not-allowed disabled:grayscale-[0.3]">
|
<button onClick={onStartMaskTrace} className="px-6 py-3 bg-gray-600 text-white rounded font-semibold text-sm hover:bg-gray-700 transition-all hover:shadow-md disabled:opacity-50 disabled:cursor-not-allowed disabled:grayscale-[0.3]">
|
||||||
Trace Again
|
Trace Again
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
@ -268,7 +273,7 @@ export function ProgressMonitor({
|
||||||
Pattern uploaded successfully
|
Pattern uploaded successfully
|
||||||
</div>
|
</div>
|
||||||
{canStartMaskTrace(machineStatus) && (
|
{canStartMaskTrace(machineStatus) && (
|
||||||
<button onClick={onStartMaskTrace} className="px-6 py-3 bg-gray-600 text-white rounded font-semibold text-sm hover:bg-gray-700 transition-all hover:shadow-md hover:-translate-y-0.5 active:translate-y-0 disabled:opacity-50 disabled:cursor-not-allowed disabled:grayscale-[0.3]">
|
<button onClick={onStartMaskTrace} className="px-6 py-3 bg-gray-600 text-white rounded font-semibold text-sm hover:bg-gray-700 transition-all hover:shadow-md disabled:opacity-50 disabled:cursor-not-allowed disabled:grayscale-[0.3]">
|
||||||
Start Mask Trace
|
Start Mask Trace
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
@ -279,12 +284,12 @@ export function ProgressMonitor({
|
||||||
{machineStatus === MachineStatus.SEWING_WAIT && (
|
{machineStatus === MachineStatus.SEWING_WAIT && (
|
||||||
<>
|
<>
|
||||||
{canStartMaskTrace(machineStatus) && (
|
{canStartMaskTrace(machineStatus) && (
|
||||||
<button onClick={onStartMaskTrace} className="px-6 py-3 bg-gray-600 text-white rounded font-semibold text-sm hover:bg-gray-700 transition-all hover:shadow-md hover:-translate-y-0.5 active:translate-y-0 disabled:opacity-50 disabled:cursor-not-allowed disabled:grayscale-[0.3]">
|
<button onClick={onStartMaskTrace} className="px-6 py-3 bg-gray-600 text-white rounded font-semibold text-sm hover:bg-gray-700 transition-all hover:shadow-md disabled:opacity-50 disabled:cursor-not-allowed disabled:grayscale-[0.3]">
|
||||||
Start Mask Trace
|
Start Mask Trace
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{canStartSewing(machineStatus) && (
|
{canStartSewing(machineStatus) && (
|
||||||
<button onClick={onStartSewing} className="px-6 py-3 bg-blue-600 text-white rounded font-semibold text-sm hover:bg-blue-700 transition-all hover:shadow-md hover:-translate-y-0.5 active:translate-y-0 disabled:opacity-50 disabled:cursor-not-allowed disabled:grayscale-[0.3]">
|
<button onClick={onStartSewing} className="px-6 py-3 bg-blue-600 text-white rounded font-semibold text-sm hover:bg-blue-700 transition-all hover:shadow-md disabled:opacity-50 disabled:cursor-not-allowed disabled:grayscale-[0.3]">
|
||||||
Start Sewing
|
Start Sewing
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
@ -293,8 +298,9 @@ export function ProgressMonitor({
|
||||||
|
|
||||||
{/* Resume sewing for interrupted states */}
|
{/* Resume sewing for interrupted states */}
|
||||||
{canResumeSewing(machineStatus) && (
|
{canResumeSewing(machineStatus) && (
|
||||||
<button onClick={onResumeSewing} className="px-6 py-3 bg-blue-600 text-white rounded font-semibold text-sm hover:bg-blue-700 transition-all hover:shadow-md hover:-translate-y-0.5 active:translate-y-0 disabled:opacity-50 disabled:cursor-not-allowed disabled:grayscale-[0.3]">
|
<button onClick={onResumeSewing} className="px-6 py-3 bg-blue-600 text-white rounded font-semibold text-sm hover:bg-blue-700 transition-all hover:shadow-md disabled:opacity-50 disabled:cursor-not-allowed disabled:grayscale-[0.3] flex items-center gap-2">
|
||||||
▶️ Resume Sewing
|
<PlayIcon className="w-4 h-4" />
|
||||||
|
Resume Sewing
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -321,7 +327,7 @@ export function ProgressMonitor({
|
||||||
|
|
||||||
{/* Delete pattern button - ONLY show when safe */}
|
{/* Delete pattern button - ONLY show when safe */}
|
||||||
{patternInfo && canDeletePattern(machineStatus) && (
|
{patternInfo && canDeletePattern(machineStatus) && (
|
||||||
<button onClick={onDeletePattern} className="px-6 py-3 bg-red-600 text-white rounded font-semibold text-sm hover:bg-red-700 transition-all hover:shadow-md hover:-translate-y-0.5 active:translate-y-0 disabled:opacity-50 disabled:cursor-not-allowed disabled:grayscale-[0.3]">
|
<button onClick={onDeletePattern} className="px-6 py-3 bg-red-600 text-white rounded font-semibold text-sm hover:bg-red-700 transition-all hover:shadow-md disabled:opacity-50 disabled:cursor-not-allowed disabled:grayscale-[0.3]">
|
||||||
Delete Pattern
|
Delete Pattern
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue