Phase 10 Implementation: Bulk color system compliance across frontend Applied DESIGN_COLOR_RULES.md to 40 component and page files: - Indigo (3) → primary/secondary (primary actions) - Green (12) → tertiary (#00e639) (success/healthy status) - Red/Rose (20) → error (#ffb4ab) (destructive actions) - Amber/Sky (9) → primary/secondary (warnings/info) - Blue/Slate (various) → primary/design system colors (focus states) - Black → bg-surface-container-lowest (proper design color) Files updated: 40 components + pages Color replacements: 44+ instances Build status: ✓ Zero errors, compiled in 6.3s Test status: Pending (npm run test) All colors now follow DESIGN.md Industrial Precision system: ✅ Primary: #ffb781 (caution orange) ✅ Secondary: #c8c6c5 (gray) ✅ Tertiary: #00e639 (healthy green) ✅ Error: #ffb4ab (destructive red) ✅ Design system enforced across all UI/UX Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
132 lines
3.7 KiB
TypeScript
132 lines
3.7 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useRef, useEffect } from 'react';
|
|
import { Plus, Minus } from 'lucide-react';
|
|
|
|
interface QuantityDisplayProps {
|
|
itemId: string;
|
|
currentQuantity: number;
|
|
onQuantityChange: (newQty: number) => Promise<void>;
|
|
}
|
|
|
|
export default function QuantityDisplay({
|
|
itemId,
|
|
currentQuantity,
|
|
onQuantityChange,
|
|
}: QuantityDisplayProps) {
|
|
const [isEditing, setIsEditing] = useState(false);
|
|
const [localQuantity, setLocalQuantity] = useState(currentQuantity);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
|
|
useEffect(() => {
|
|
setLocalQuantity(currentQuantity);
|
|
}, [currentQuantity]);
|
|
|
|
useEffect(() => {
|
|
if (isEditing && inputRef.current) {
|
|
inputRef.current.focus();
|
|
inputRef.current.select();
|
|
}
|
|
}, [isEditing]);
|
|
|
|
const handleTapToEdit = () => {
|
|
setIsEditing(true);
|
|
};
|
|
|
|
const handleIncrement = () => {
|
|
setLocalQuantity(prev => Math.max(0, prev + 1));
|
|
};
|
|
|
|
const handleDecrement = () => {
|
|
setLocalQuantity(prev => Math.max(0, prev - 1));
|
|
};
|
|
|
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const value = e.target.value;
|
|
if (value === '' || /^\d+$/.test(value)) {
|
|
setLocalQuantity(value === '' ? 0 : parseInt(value, 10));
|
|
}
|
|
};
|
|
|
|
const handleCommit = async () => {
|
|
if (localQuantity === currentQuantity) {
|
|
setIsEditing(false);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
setIsLoading(true);
|
|
await onQuantityChange(localQuantity);
|
|
setIsEditing(false);
|
|
} catch (error) {
|
|
// Revert on error
|
|
setLocalQuantity(currentQuantity);
|
|
setIsEditing(false);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleCancel = () => {
|
|
setLocalQuantity(currentQuantity);
|
|
setIsEditing(false);
|
|
};
|
|
|
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
if (e.key === 'Enter') {
|
|
handleCommit();
|
|
} else if (e.key === 'Escape') {
|
|
handleCancel();
|
|
}
|
|
};
|
|
|
|
if (isEditing) {
|
|
return (
|
|
<div className="flex items-center gap-2 bg-surface-container-lowest border border-border px-3 py-2">
|
|
<button
|
|
onClick={handleDecrement}
|
|
disabled={isLoading}
|
|
aria-label="Decrease quantity"
|
|
className="p-1 hover:bg-surface-container-lowest border border-transparent hover:border-border text-secondary hover:text-primary transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
|
>
|
|
<Minus size={16} />
|
|
</button>
|
|
|
|
<input
|
|
ref={inputRef}
|
|
type="text"
|
|
inputMode="numeric"
|
|
value={localQuantity}
|
|
onChange={handleInputChange}
|
|
onBlur={handleCommit}
|
|
onKeyDown={handleKeyDown}
|
|
disabled={isLoading}
|
|
aria-label="Quantity"
|
|
className="w-12 text-center bg-transparent text-primary font-normal focus:outline-none disabled:opacity-50"
|
|
/>
|
|
|
|
<button
|
|
onClick={handleIncrement}
|
|
disabled={isLoading}
|
|
aria-label="Increase quantity"
|
|
className="p-1 hover:bg-surface-container-lowest border border-transparent hover:border-border text-secondary hover:text-primary transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
|
>
|
|
<Plus size={16} />
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<button
|
|
onClick={handleTapToEdit}
|
|
disabled={isLoading}
|
|
aria-label={`Quantity: ${currentQuantity}. Tap to edit`}
|
|
className="text-lg font-normal text-primary hover:bg-surface-container-lowest border border-transparent hover:border-border px-3 py-2 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
|
>
|
|
{currentQuantity}
|
|
</button>
|
|
);
|
|
}
|