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:
Jan-Henrik 2025-12-05 23:44:48 +01:00
parent cd43a64bc4
commit bf20c2b378
7 changed files with 42 additions and 24 deletions

10
package-lock.json generated
View file

@ -8,6 +8,7 @@
"name": "web",
"version": "0.0.0",
"dependencies": {
"@heroicons/react": "^2.2.0",
"@tailwindcss/vite": "^4.1.17",
"konva": "^10.0.12",
"pyodide": "^0.27.4",
@ -831,6 +832,15 @@
"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": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",

View file

@ -10,6 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
"@heroicons/react": "^2.2.0",
"@tailwindcss/vite": "^4.1.17",
"konva": "^10.0.12",
"pyodide": "^0.27.4",

View file

@ -55,14 +55,14 @@ export function ConfirmDialog({
<div className="p-4 px-6 flex gap-3 justify-end border-t border-gray-300">
<button
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
>
{cancelText}
</button>
<button
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}
</button>

View file

@ -74,7 +74,7 @@ export function FileUpload({
className="hidden"
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'}
</label>
@ -110,7 +110,7 @@ export function FileUpload({
<button
onClick={handleUpload}
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
? `Uploading... ${uploadProgress.toFixed(0)}%`

View file

@ -68,7 +68,7 @@ export function MachineConnection({
{!isConnected ? (
<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
</button>
</div>
@ -122,10 +122,10 @@ export function MachineConnection({
)}
<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
</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
</button>
</div>

View file

@ -1,6 +1,7 @@
import { useEffect, useRef, useState, useCallback } from 'react';
import { Stage, Layer, Group } from 'react-konva';
import Konva from 'konva';
import { PlusIcon, MinusIcon, ArrowPathIcon } from '@heroicons/react/24/solid';
import type { PesPatternData } from '../utils/pystitchConverter';
import type { SewingProgress, MachineInfo } from '../types/machine';
import { calculateInitialScale } from '../utils/konvaRenderers';
@ -298,15 +299,15 @@ export function PatternCanvas({ pesData, sewingProgress, machineInfo, initialPat
{/* 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">
<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>
<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 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>
</div>
</>

View file

@ -1,3 +1,4 @@
import { CheckCircleIcon, ArrowRightIcon, CircleStackIcon, PlayIcon } from '@heroicons/react/24/solid';
import type { PatternInfo, SewingProgress } from '../types/machine';
import { MachineStatus } from '../types/machine';
import type { PesPatternData } from '../utils/pystitchConverter';
@ -161,9 +162,13 @@ export function ProgressMonitor({
<span className="font-semibold flex-1">
Thread {block.colorIndex + 1}
</span>
<span className={`text-xl font-bold ${isCompleted ? 'text-green-600' : isCurrent ? 'text-blue-600' : 'text-gray-600'}`}>
{isCompleted ? '✓' : isCurrent ? '→' : '○'}
</span>
{isCompleted ? (
<CheckCircleIcon className="w-6 h-6 text-green-600" />
) : 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">
{block.stitchCount} stitches
</span>
@ -249,12 +254,12 @@ export function ProgressMonitor({
Mask trace complete!
</div>
{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
</button>
)}
{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
</button>
)}
@ -268,7 +273,7 @@ export function ProgressMonitor({
Pattern uploaded successfully
</div>
{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
</button>
)}
@ -279,12 +284,12 @@ export function ProgressMonitor({
{machineStatus === MachineStatus.SEWING_WAIT && (
<>
{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
</button>
)}
{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
</button>
)}
@ -293,8 +298,9 @@ export function ProgressMonitor({
{/* Resume sewing for interrupted states */}
{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]">
Resume Sewing
<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">
<PlayIcon className="w-4 h-4" />
Resume Sewing
</button>
)}
@ -321,7 +327,7 @@ export function ProgressMonitor({
{/* Delete pattern button - ONLY show when safe */}
{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
</button>
)}