diff --git a/web/src/components/ui/NumericStepper.tsx b/web/src/components/ui/NumericStepper.tsx index 0c99759..006b422 100644 --- a/web/src/components/ui/NumericStepper.tsx +++ b/web/src/components/ui/NumericStepper.tsx @@ -1,3 +1,5 @@ +import { useState, useRef, useEffect } from 'react'; + interface Props { label: string; value: number; @@ -8,6 +10,17 @@ interface Props { } export default function NumericStepper({ label, value, onChange, min = 0, max, step = 1 }: Props) { + const [editing, setEditing] = useState(false); + const [editValue, setEditValue] = useState(''); + const inputRef = useRef(null); + + useEffect(() => { + if (editing && inputRef.current) { + inputRef.current.focus(); + inputRef.current.select(); + } + }, [editing]); + const decrement = () => { const next = value - step; onChange(min !== undefined ? Math.max(min, next) : next); @@ -18,6 +31,26 @@ export default function NumericStepper({ label, value, onChange, min = 0, max, s onChange(max !== undefined ? Math.min(max, next) : next); }; + const startEditing = () => { + setEditValue(String(value)); + setEditing(true); + }; + + const commitEdit = () => { + setEditing(false); + const parsed = parseInt(editValue, 10); + if (isNaN(parsed)) return; // discard invalid input + let clamped = parsed; + if (min !== undefined) clamped = Math.max(min, clamped); + if (max !== undefined) clamped = Math.min(max, clamped); + if (clamped !== value) onChange(clamped); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') commitEdit(); + if (e.key === 'Escape') setEditing(false); + }; + return (
{label} @@ -30,7 +63,23 @@ export default function NumericStepper({ label, value, onChange, min = 0, max, s > - - {value} + {editing ? ( + setEditValue(e.target.value)} + onBlur={commitEdit} + onKeyDown={handleKeyDown} + min={min} + max={max} + /> + ) : ( + + {value} + + )}