This commit is contained in:
Andras Schmelczer 2026-09-11 16:09:28 +01:00
commit d8f2407ddf

96
src/components/Fields.tsx Normal file
View file

@ -0,0 +1,96 @@
import { useId, useState } from 'react';
import type { Issue } from '../lib/types';
import { AlertCircle, Info, Check } from 'lucide-react';
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 (
<div className="field">
<span className="field-label">
<label htmlFor={id}>{label}</label>
{tooltip && (
<span className="field-tooltip">
<button
type="button"
className="tooltip-button"
aria-label={`What is ${label.toLowerCase()}?`}
aria-expanded={showTooltip}
aria-controls={`${id}-tooltip`}
aria-describedby={showTooltip ? `${id}-tooltip` : undefined}
onClick={() => setShowTooltip((shown) => !shown)}
onBlur={() => setShowTooltip(false)}
onKeyDown={(event) => {
if (event.key === 'Escape') setShowTooltip(false);
}}
>
?
</button>
{showTooltip && (
<span id={`${id}-tooltip`} role="tooltip" className="tooltip-content">
{tooltip}
</span>
)}
</span>
)}
</span>
<span className="number-input">
<input
id={id}
type="number"
inputMode="decimal"
value={value}
min={min}
max={max}
step={step}
aria-describedby={hint ? `${id}-hint` : undefined}
onChange={(e) => onChange(Number(e.target.value))}
/>
<span>{unit}</span>
</span>
{hint && <small id={`${id}-hint`}>{hint}</small>}
</div>
);
}
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 (
<div className={`issues ${compact ? 'compact' : ''}`} role="status">
{unique.map((issue, i) => (
<div className={`issue ${issue.severity}`} key={i}>
{issue.severity === 'info' ? <Info size={16} /> : <AlertCircle size={16} />}
<span>{issue.message}</span>
</div>
))}
</div>
);
}
export function ClosedBadge({ closed }: { closed: boolean }) {
return (
<span className={`badge ${closed ? 'success' : 'pending'}`}>
{closed ? <Check size={12} /> : <AlertCircle size={12} />}{' '}
{closed ? 'Outline closed' : 'Check outline'}
</span>
);
}