2026-10-14 17:42:59 +00:00
|
|
|
/** Indicador de pasos sin conocimiento del formulario o de sus datos. */
|
|
|
|
|
export function FormProgress({
|
|
|
|
|
step,
|
|
|
|
|
labels,
|
|
|
|
|
label,
|
|
|
|
|
progress,
|
|
|
|
|
completedLabel,
|
|
|
|
|
description,
|
|
|
|
|
}: {
|
|
|
|
|
step: number;
|
|
|
|
|
labels: readonly string[];
|
|
|
|
|
label: string;
|
|
|
|
|
progress: string;
|
|
|
|
|
completedLabel: string;
|
|
|
|
|
description: string;
|
|
|
|
|
}) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="space-y-5">
|
|
|
|
|
<ol aria-label={label} className="flex gap-3 border-b border-border/60 pb-5">
|
|
|
|
|
{labels.map((title, index) => {
|
|
|
|
|
const current = step === index + 1;
|
|
|
|
|
const completed = step > index + 1;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<li
|
|
|
|
|
aria-current={current ? "step" : undefined}
|
|
|
|
|
className="flex min-w-0 flex-1 flex-col items-start gap-2 sm:flex-row sm:items-center sm:gap-3"
|
|
|
|
|
key={title}
|
|
|
|
|
>
|
|
|
|
|
<span
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
className={`flex size-8 shrink-0 items-center justify-center rounded-full border text-sm font-semibold ${current ? "border-primary bg-primary text-primary-foreground" : completed ? "border-primary/30 bg-primary/10 text-primary" : "border-border bg-muted/50 text-muted-foreground"}`}
|
|
|
|
|
>
|
|
|
|
|
{completed ? "✓" : index + 1}
|
|
|
|
|
</span>
|
|
|
|
|
<span
|
|
|
|
|
className={`text-xs leading-snug sm:text-sm ${current ? "font-semibold text-foreground" : "text-muted-foreground"}`}
|
|
|
|
|
>
|
|
|
|
|
{title}
|
|
|
|
|
{completed && <span className="sr-only">{completedLabel}</span>}
|
|
|
|
|
</span>
|
|
|
|
|
</li>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</ol>
|
|
|
|
|
<div className="space-y-2">
|
2026-10-16 09:19:17 +00:00
|
|
|
<div className="flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1 sr-only">
|
2026-10-14 17:42:59 +00:00
|
|
|
<h2 className="text-base font-semibold">{labels[step - 1]}</h2>
|
|
|
|
|
<p
|
|
|
|
|
aria-live="polite"
|
|
|
|
|
className="text-xs text-muted-foreground focus-visible:rounded-sm focus-visible:outline-2 focus-visible:outline-ring"
|
|
|
|
|
key={step}
|
|
|
|
|
ref={focusStep}
|
|
|
|
|
role="status"
|
|
|
|
|
tabIndex={-1}
|
|
|
|
|
>
|
|
|
|
|
{progress}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-sm leading-relaxed text-muted-foreground">{description}</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function focusStep(element: HTMLParagraphElement | null) {
|
2026-10-21 17:31:09 +00:00
|
|
|
// Cada paso empieza arriba; enfocar el anuncio oculto no debe desplazar su indicador.
|
|
|
|
|
element?.closest("[data-slot=form-body]")?.scrollTo({ top: 0 });
|
|
|
|
|
element?.focus({ preventScroll: true });
|
2026-10-14 17:42:59 +00:00
|
|
|
}
|