fix(stepper): add disabled state to prevent navigation and provide visual feedback

This commit is contained in:
2026-08-24 14:07:12 -03:00
parent 7fbdd3844f
commit 8861e42fff
5 changed files with 53 additions and 8 deletions

View File

@@ -6,7 +6,11 @@
} @else {
<div class="checkout-page">
<div class="checkout-page__stepper-col">
<app-stepper #stepper [initialStepIndex]="checkoutStepIndex()">
<app-stepper
#stepper
[initialStepIndex]="checkoutStepIndex()"
[disabled]="hasSubmittedTransfer()"
>
<app-step label="Datos" [isValid]="isStep1Valid()">
<app-checkout-data-step
[form]="form"

View File

@@ -1,25 +1,27 @@
<div class="stepper-container">
<!-- Horizontal indicator bar -->
<div class="stepper-header">
<div class="stepper-header" [class.is-disabled]="disabled()" [attr.aria-disabled]="disabled()">
@for (step of steps(); track step; let i = $index; let last = $last) {
<div
class="stepper-header__item"
[ngClass]="{
'is-active': currentStepIndex() === i,
'is-completed': currentStepIndex() > i
'is-completed': currentStepIndex() > i,
}"
>
<!-- Connecting line before (except first) -->
@if (i > 0) {
<div class="stepper-header__line" [ngClass]="{ 'is-completed': currentStepIndex() > i - 1 }"></div>
<div
class="stepper-header__line"
[ngClass]="{ 'is-completed': currentStepIndex() > i - 1 }"
></div>
}
<!-- Circle indicator -->
<div
class="stepper-header__circle"
[attr.aria-label]="step.label()"
[class.is-clickable]="currentStepIndex() > i"
[class.is-clickable]="!disabled() && currentStepIndex() > i"
(click)="goToStep(i)"
></div>
@@ -33,5 +35,4 @@
<div class="stepper-body">
<ng-content></ng-content>
</div>
</div>

View File

@@ -13,6 +13,14 @@
margin-bottom: 2rem;
position: relative;
&.is-disabled {
opacity: 0.5;
.stepper-header__circle {
cursor: not-allowed;
}
}
&__item {
display: flex;
flex-direction: column;

View File

@@ -6,7 +6,7 @@ import { StepComponent } from './step.component';
@Component({
imports: [StepperComponent, StepComponent],
template: `
<app-stepper #stepper>
<app-stepper #stepper [disabled]="stepperDisabled()">
<app-step label="Step 1" [isValid]="step1Valid()">
<div id="content-1">Content 1</div>
</app-step>
@@ -20,6 +20,7 @@ class TestHostComponent {
@ViewChild('stepper') stepper!: StepperComponent;
step1Valid = signal(true);
step2Valid = signal(true);
stepperDisabled = signal(false);
}
describe('StepperComponent & StepComponent', () => {
@@ -137,4 +138,28 @@ describe('StepperComponent & StepComponent', () => {
expect(component.stepper.currentStepIndex()).toBe(0);
expect(fixture.nativeElement.querySelector('#content-1')).not.toBeNull();
});
it('blocks all navigation and shows visual feedback while disabled', async () => {
const { fixture, component } = await setup();
component.stepperDisabled.set(true);
fixture.detectChanges();
component.stepper.next();
expect(component.stepper.currentStepIndex()).toBe(0);
component.stepperDisabled.set(false);
fixture.detectChanges();
component.stepper.next();
expect(component.stepper.currentStepIndex()).toBe(1);
component.stepperDisabled.set(true);
fixture.detectChanges();
component.stepper.previous();
component.stepper.goToStep(0);
expect(component.stepper.currentStepIndex()).toBe(1);
const header = fixture.nativeElement.querySelector('.stepper-header');
expect(header.classList.contains('is-disabled')).toBe(true);
expect(header.getAttribute('aria-disabled')).toBe('true');
});
});

View File

@@ -18,9 +18,12 @@ import { NgClass } from '@angular/common';
export class StepperComponent {
readonly steps = contentChildren(StepComponent);
readonly initialStepIndex = input(0);
readonly disabled = input(false);
readonly currentStepIndex = linkedSignal(() => this.initialStepIndex());
next() {
if (this.disabled()) return;
const currentSteps = this.steps();
const currentIndex = this.currentStepIndex();
if (currentIndex < currentSteps.length - 1) {
@@ -32,6 +35,8 @@ export class StepperComponent {
}
previous() {
if (this.disabled()) return;
const currentIndex = this.currentStepIndex();
if (currentIndex > 0) {
this.currentStepIndex.set(currentIndex - 1);
@@ -39,6 +44,8 @@ export class StepperComponent {
}
goToStep(index: number) {
if (this.disabled()) return;
const targetIndex = index;
// Only allow navigating to completed steps or the current one
if (targetIndex < this.currentStepIndex()) {