Skip to content

Commit

Permalink
feat(stepper): add step change event (#2779)
Browse files Browse the repository at this point in the history
  • Loading branch information
andreipadolin authored Nov 9, 2021
1 parent 07db7f5 commit 1b871f4
Show file tree
Hide file tree
Showing 9 changed files with 240 additions and 8 deletions.
6 changes: 6 additions & 0 deletions src/app/playground-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1231,6 +1231,12 @@ export const PLAYGROUND_COMPONENTS: ComponentLink[] = [
component: 'StepperLinearComponent',
name: 'Stepper Linear',
},
{
path: 'stepper-step-change-event.component',
link: '/stepper/stepper-step-change-event.component',
component: 'StepperStepChangeEventComponent',
name: 'Stepper Step Change Event',
},
],
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
[class.selected]="isStepSelected(step)"
[class.completed]="!isStepSelected(step) && step.completed"
[class.noninteractive]="disableStepNavigation"
(click)="!disableStepNavigation && step.select()">
(click)="changeStep(step)">
<div class="label-index">
<span *ngIf="!step.completed || isStepSelected(step)">{{ index + 1 }}</span>
<nb-icon *ngIf="!isStepSelected(step) && step.completed" icon="checkmark-outline" pack="nebular-essentials">
Expand Down
53 changes: 50 additions & 3 deletions src/framework/theme/components/stepper/stepper.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
import {
Component,
ContentChildren,
EventEmitter,
HostBinding,
Input,
Output,
QueryList,
TemplateRef,
} from '@angular/core';
Expand All @@ -18,6 +20,13 @@ import { NbStepComponent } from './step.component';

export type NbStepperOrientation = 'vertical' | 'horizontal';

export interface NbStepChangeEvent {
index: number;
step: NbStepComponent;
previouslySelectedIndex: number;
previouslySelectedStep: NbStepComponent;
}

/**
* Stepper component
*
Expand Down Expand Up @@ -81,6 +90,9 @@ export type NbStepperOrientation = 'vertical' | 'horizontal';
* 'nbStepperPrevious' and 'nbStepperNext' buttons.
* @stacked-example(Disabled steps navigation, stepper/stepper-disabled-step-nav.component)
*
* Use `stepChange` output to listening to step change event. This event emits `NbStepChangeEvent` object.
* @stacked-example(Step change event, stepper/stepper-step-change-event.component)
*
* @styles
*
* stepper-step-text-color:
Expand Down Expand Up @@ -112,7 +124,6 @@ export type NbStepperOrientation = 'vertical' | 'horizontal';
providers: [{ provide: NB_STEPPER, useExisting: NbStepperComponent }],
})
export class NbStepperComponent {

/**
* Selected step index
*/
Expand All @@ -128,9 +139,19 @@ export class NbStepperComponent {

this.markCurrentStepInteracted();
if (this.canBeSelected(index)) {
const previouslySelectedIndex = this._selectedIndex;
const previouslySelectedStep = this.selected;
this._selectedIndex = index;

this.stepChange.emit({
index: this.selectedIndex,
step: this.selected,
previouslySelectedIndex,
previouslySelectedStep,
});
}
}

protected _selectedIndex: number = 0;

/**
Expand Down Expand Up @@ -180,6 +201,12 @@ export class NbStepperComponent {
protected _linear = true;
static ngAcceptInputType_linear: NbBooleanInput;

/**
* Emits when step changed
* @type {EventEmitter<NbStepChangeEvent>}
*/
@Output() stepChange = new EventEmitter<NbStepChangeEvent>();

@HostBinding('class.vertical')
get vertical() {
return this.orientation === 'vertical';
Expand All @@ -205,12 +232,32 @@ export class NbStepperComponent {
this.selectedIndex = Math.max(this.selectedIndex - 1, 0);
}

/**
* Select step if navigation is not disabled
* @param { NbStepComponent } step
*/
changeStep(step: NbStepComponent) {
if (!this.disableStepNavigation) {
step.select();
}
}

/**
* Reset stepper and stepControls to initial state
* */
reset() {
const previouslySelectedIndex = this.selectedIndex;
const previouslySelectedStep = this.selected;

this._selectedIndex = 0;
this.steps.forEach(step => step.reset());
this.steps.forEach((step) => step.reset());

this.stepChange.emit({
index: this.selectedIndex,
step: this.selected,
previouslySelectedIndex,
previouslySelectedStep,
});
}

isStepSelected(step: NbStepComponent) {
Expand All @@ -233,7 +280,7 @@ export class NbStepperComponent {

protected canBeSelected(indexToCheck: number): boolean {
const noSteps = !this.steps || this.steps.length === 0;
if (noSteps || indexToCheck < 0 || indexToCheck >= this.steps.length) {
if (noSteps || indexToCheck < 0 || indexToCheck >= this.steps.length || indexToCheck === this.selectedIndex) {
return false;
}

Expand Down
115 changes: 111 additions & 4 deletions src/framework/theme/components/stepper/stepper.spec.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,123 @@
import { Component, QueryList, ViewChild, ViewChildren } from '@angular/core';
import { By } from '@angular/platform-browser';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NbStepComponent, NbStepperComponent, NbStepperModule, NbThemeModule } from '@nebular/theme';

import { NbStepperComponent, NbStepComponent, NbIconModule } from '@nebular/theme';
@Component({
selector: 'nb-step-changed-test',
template: `
<nb-stepper [selectedIndex]="1">
<nb-step [label]="labelOne">
<ng-template #labelOne>First step</ng-template>
</nb-step>
<nb-step [label]="labelTwo">
<ng-template #labelTwo>Seconds step</ng-template>
</nb-step>
<nb-step [label]="labelThree">
<ng-template #labelThree>First step</ng-template>
</nb-step>
</nb-stepper>
`,
})
export class NbStepChangeTestComponent {
@ViewChild(NbStepperComponent) stepper: NbStepperComponent;
@ViewChildren(NbStepComponent) steps: QueryList<NbStepComponent>;
}

describe('Stepper: Step Change', () => {
let testComponent: NbStepChangeTestComponent;
let stepper: NbStepperComponent;
let fixture: ComponentFixture<NbStepChangeTestComponent>;
let stepChangeSpy;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [NbThemeModule.forRoot(), NbStepperModule],
declarations: [NbStepChangeTestComponent],
});

fixture = TestBed.createComponent(NbStepChangeTestComponent);
fixture.detectChanges();
testComponent = fixture.componentInstance;
stepper = testComponent.stepper;
stepChangeSpy = jasmine.createSpy('step change spy');
});

it('should emit step change on next method', () => {
stepper.stepChange.subscribe(stepChangeSpy);
stepper.next();
fixture.detectChanges();

expect(stepChangeSpy).toHaveBeenCalled();
});

it('should emit step change by clicking on step', () => {
stepper.stepChange.subscribe(stepChangeSpy);
const step = fixture.debugElement.query(By.css('.step'));
step.triggerEventHandler('click', null);
fixture.detectChanges();

expect(stepChangeSpy).toHaveBeenCalled();
});

it('should not emit step change if navigation is disabled', () => {
stepper.stepChange.subscribe(stepChangeSpy);
stepper.disableStepNavigation = true;
const step = fixture.debugElement.query(By.css('.step'));
step.triggerEventHandler('click', null);

fixture.detectChanges();
expect(stepChangeSpy).not.toHaveBeenCalled();
});

it('should not emit step change when calling next on the last step', () => {
stepper.selectedIndex = 2;
stepper.stepChange.subscribe(stepChangeSpy);
stepper.next();

expect(stepChangeSpy).not.toHaveBeenCalled();
});

it('should not emit step change when calling prev on the first step', () => {
stepper.selectedIndex = 0;
stepper.stepChange.subscribe(stepChangeSpy);
stepper.previous();

expect(stepChangeSpy).not.toHaveBeenCalled();
});

it('should emit step change when changing step via selected input', () => {
stepper.stepChange.subscribe(stepChangeSpy);
const thirdStep = testComponent.steps.get(2);
stepper.selected = thirdStep;
fixture.detectChanges();

expect(stepChangeSpy).toHaveBeenCalled();
const [changeEvent] = stepChangeSpy.calls.first().args;
expect(changeEvent.index).toEqual(2);
expect(changeEvent.step).toEqual(thirdStep);
expect(changeEvent.previouslySelectedIndex).toEqual(1);
expect(changeEvent.previouslySelectedStep).toEqual(testComponent.steps.get(1));
});

it('should emit step change when changing step via selectedIndex input', () => {
stepper.stepChange.subscribe(stepChangeSpy);
stepper.selectedIndex = 2;
fixture.detectChanges();

expect(stepChangeSpy).toHaveBeenCalled();
});
});

// TODO: this definitely requires more testing!
describe('Component: NbStepper', () => {
let stepper: NbStepperComponent;
let fixture: ComponentFixture<NbStepperComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [NbIconModule],
declarations: [NbStepComponent, NbStepperComponent],
imports: [NbThemeModule.forRoot(), NbStepperModule],
});

fixture = TestBed.createComponent(NbStepperComponent);
Expand Down
5 changes: 5 additions & 0 deletions src/playground/with-layout/stepper/stepper-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { StepperValidationComponent } from './stepper-validation.component';
import { StepperVerticalComponent } from './stepper-vertical.component';
import { StepperDisabledStepNavComponent } from './stepper-disabled-step-nav.component';
import { StepperLinearComponent } from './stepper-linear.component';
import { StepperStepChangeEventComponent } from './stepper-step-change-event.component';

const routes: Route[] = [
{
Expand All @@ -38,6 +39,10 @@ const routes: Route[] = [
path: 'stepper-linear.component',
component: StepperLinearComponent,
},
{
path: 'stepper-step-change-event.component',
component: StepperStepChangeEventComponent,
},
];

@NgModule({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<nb-card>
<nb-card-body>
<p *ngIf="changeEvent">Previous step: {{ changeEvent.previouslySelectedIndex }}</p>
<p *ngIf="changeEvent">Current step: {{ changeEvent.index }}</p>

<nb-stepper orientation="horizontal" (stepChange)="handleStepChange($event)">
<nb-step [label]="labelOne">
<ng-template #labelOne>First step</ng-template>
<h4>Step content #1</h4>
<p class="lorem">
Lorizzle ipsum dolizzle stuff fizzle, consectetuer adipiscing break it down. Nullizzle sapien velizzle, my
shizz pimpin', shizzle my nizzle crocodizzle shut the shizzle up, gravida vizzle, dang.
</p>
<button nbButton disabled nbStepperNext>prev</button>
<button nbButton nbStepperNext>next</button>
</nb-step>
<nb-step [label]="labelTwo">
<ng-template #labelTwo>Second step</ng-template>
<h4>Step content #2</h4>
<p class="lorem">
Lorizzle ipsum dolizzle stuff fizzle, consectetuer adipiscing break it down. Nullizzle sapien velizzle, my
shizz pimpin', shizzle my nizzle crocodizzle shut the shizzle up, gravida vizzle, dang.
</p>
<button nbButton nbStepperPrevious>prev</button>
<button nbButton nbStepperNext>next</button>
</nb-step>
<nb-step label="Third step">
<h4>Step content #3</h4>
<p class="lorem">
Lorizzle ipsum dolizzle stuff fizzle, consectetuer adipiscing break it down. Nullizzle sapien velizzle, my
shizz pimpin', shizzle my nizzle crocodizzle shut the shizzle up, gravida vizzle, dang.
</p>
<button nbButton nbStepperPrevious>prev</button>
<button nbButton nbStepperNext>next</button>
</nb-step>
<nb-step [label]="labelFour">
<ng-template #labelFour>Fourth step</ng-template>
<h4>Step content #4</h4>
<p class="lorem">
Lorizzle ipsum dolizzle stuff fizzle, consectetuer adipiscing break it down. Nullizzle sapien velizzle, my
shizz pimpin', shizzle my nizzle crocodizzle shut the shizzle up, gravida vizzle, dang.
</p>
<button nbButton nbStepperPrevious>prev</button>
<button nbButton disabled nbStepperNext>next</button>
</nb-step>
</nb-stepper>
</nb-card-body>
</nb-card>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
button {
margin: 0.5rem;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Component } from '@angular/core';
import { NbStepChangeEvent } from '@nebular/theme';

@Component({
templateUrl: './stepper-step-change-event.component.html',
styleUrls: ['./stepper-step-change-event.component.scss'],
})
export class StepperStepChangeEventComponent {
changeEvent: NbStepChangeEvent;

handleStepChange(e: NbStepChangeEvent): void {
this.changeEvent = e;
}
}
2 changes: 2 additions & 0 deletions src/playground/with-layout/stepper/stepper.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { StepperValidationComponent } from './stepper-validation.component';
import { StepperVerticalComponent } from './stepper-vertical.component';
import { StepperDisabledStepNavComponent } from './stepper-disabled-step-nav.component';
import { StepperLinearComponent } from './stepper-linear.component';
import { StepperStepChangeEventComponent } from './stepper-step-change-event.component';

@NgModule({
declarations: [
Expand All @@ -24,6 +25,7 @@ import { StepperLinearComponent } from './stepper-linear.component';
StepperVerticalComponent,
StepperDisabledStepNavComponent,
StepperLinearComponent,
StepperStepChangeEventComponent,
],
imports: [
CommonModule,
Expand Down

0 comments on commit 1b871f4

Please sign in to comment.