import { useEffect, useId, useRef, useState } from 'react'; import type { InputHTMLAttributes } from 'react'; import type { Issue } from '../lib/types'; import { AlertCircle, Info } from 'lucide-react'; const numberFromDraft = (text: string): number | null => { if (!/^-?(?:\d+(?:\.\d*)?|\.\d+)$/.test(text)) return null; const number = Number(text); return Number.isFinite(number) ? number : null; }; /** Only unfinished typing is local; every complete numeric value is committed immediately. */ export function NumericInput({ value, onChange, onBlur, onKeyDown, ...props }: Omit, 'value' | 'onChange' | 'type'> & { value: number; onChange: (value: number) => void; }) { const [draft, setDraft] = useState(String(value)); const emitted = useRef(value); useEffect(() => { if (value !== emitted.current) setDraft(String(value)); emitted.current = value; }, [value]); return ( { const text = event.target.value; setDraft(text); const number = numberFromDraft(text); if (number !== null && number !== value) { emitted.current = number; onChange(number); } }} onBlur={(event) => { setDraft(String(value)); onBlur?.(event); }} onKeyDown={(event) => { onKeyDown?.(event); if (!event.defaultPrevented && (event.key === 'Enter' || event.key === 'Escape')) { event.preventDefault(); event.currentTarget.blur(); } }} /> ); } export function NumberField({ label, value, onChange, unit = 'mm', min = 0, max, step = 1, hint, tooltip, }: { label: string; value: number; onChange: (n: number) => void; unit?: string; min?: number; max?: number; step?: number; hint?: string; tooltip?: string; }) { const id = useId(); const [showTooltip, setShowTooltip] = useState(false); return (
{tooltip && ( {showTooltip && ( {tooltip} )} )} {unit} {hint && {hint}}
); } export function Issues({ issues, compact = false }: { issues: Issue[]; compact?: boolean }) { const unique = issues.filter( (issue, i) => issues.findIndex((item) => item.message === issue.message) === i, ); return (
{unique.map((issue, i) => (
{issue.severity === 'info' ? : } {issue.message}
))}
); }