Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
42537e1
feat(components): `post-stepper` and `post-stepper-item` components
leagrdv Oct 23, 2025
37fd099
update readme
leagrdv Oct 23, 2025
ffb500f
Update labels of current step
leagrdv Oct 23, 2025
37fdd5f
fix code smell
leagrdv Oct 23, 2025
96d988b
update icon
leagrdv Oct 23, 2025
2f433df
add installation link + update css
leagrdv Oct 23, 2025
2ee0158
Merge branch 'main' into 5706-web-component-stepper-horizontal
leagrdv Oct 27, 2025
7251219
address PR comments + simplify mobile stepper
leagrdv Nov 10, 2025
e7f63c9
Merge branch 'main' into 5706-web-component-stepper-horizontal
leagrdv Nov 10, 2025
9c2f27e
use args from component + small UI fix on mobile
leagrdv Nov 10, 2025
5bd5691
update status
leagrdv Nov 10, 2025
9a2d7e8
migration guide fix
leagrdv Nov 10, 2025
e82d284
Merge branch 'main' into 5706-web-component-stepper-horizontal
leagrdv Nov 11, 2025
d9569a2
Merge branch 'main' into 5706-web-component-stepper-horizontal
leagrdv Nov 13, 2025
247cb01
handle html content
leagrdv Nov 13, 2025
032190a
Merge branch 'main' into 5706-web-component-stepper-horizontal
leagrdv Nov 13, 2025
11e8fc8
add index placeholder for active step
leagrdv Nov 13, 2025
6280a3f
Merge branch '5706-web-component-stepper-horizontal' of https://githuโ€ฆ
leagrdv Nov 13, 2025
602b458
Merge branch 'main' into 5706-web-component-stepper-horizontal
leagrdv Nov 13, 2025
c2a0c11
update angular and react integration
leagrdv Nov 13, 2025
f768143
Merge branch '5706-web-component-stepper-horizontal' of https://githuโ€ฆ
leagrdv Nov 13, 2025
846f51f
fix tests
leagrdv Nov 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/empty-coins-dig.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@swisspost/design-system-styles': major
'@swisspost/design-system-components': minor
'@swisspost/design-system-documentation': patch
---

Removed the HTML stepper component and replaced it with new `post-stepper` and `post-stepper-item` web components.
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,18 @@ <h2>Post Tooltip</h2>
<button class="btn btn-secondary btn-large">Button</button>
</post-tooltip-trigger>
</div>

<div class="my-24">
<h2>Post Stepper</h2>
<post-stepper
completedLabel="Completed step"
currentLabel="Current step"
stepLabel="Step"
[currentIndex]="1"
>
<post-stepper-item> Step 1 </post-stepper-item>
<post-stepper-item> Step 2 </post-stepper-item>
<post-stepper-item> Step 3 </post-stepper-item>
<post-stepper-item> Step 4 </post-stepper-item>
</post-stepper>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
PostTabHeader,
PostTabPanel,
PostTooltipTrigger,
PostStepper,
PostStepperItem,
} from 'components';

@Component({
Expand All @@ -39,9 +41,10 @@ import {
PostTabHeader,
PostTabPanel,
PostTooltipTrigger,
]
PostStepper,
PostStepperItem,
],
})

export class HomeComponent {
isCollapsed = false;
}
188 changes: 188 additions & 0 deletions packages/components/cypress/e2e/stepper.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
describe('stepper', { baseUrl: null }, () => {
beforeEach(() => {
cy.visit('./cypress/fixtures/post-stepper.test.html');
});

afterEach(() => {
cy.reload();
});

it('should render the post-stepper component', () => {
cy.get('post-stepper').should('exist');
});

// Errors

it('should log an error if the current label is missing', () => {
cy.window().then(win => {
cy.spy(win.console, 'error').as('consoleError');
});
cy.get('post-stepper').invoke('attr', 'current-label', null);
cy.get('@consoleError').should('be.called');
});

it('should log an error if the completed label is missing', () => {
cy.window().then(win => {
cy.spy(win.console, 'error').as('consoleError');
});
cy.get('post-stepper').invoke('attr', 'completed-label', null);
cy.get('@consoleError').should('be.called');
});

it('should log an error if the mobile step label is missing', () => {
cy.window().then(win => {
cy.spy(win.console, 'error').as('consoleError');
});
cy.get('post-stepper').invoke('attr', 'step-label', null);
cy.get('@consoleError').should('be.called');
});

// Dynamic classes

it('should set all inactive classes when current-index is negative', () => {
cy.get('post-stepper')
.invoke('attr', 'current-index', -1)
.wait(100)
.find('post-stepper-item')
.then($items => {
expect($items.filter('.stepper-item-inactive').length).to.equal(5);
});
});

it('should set first step as active when current-index is 0', () => {
cy.get('post-stepper')
.invoke('attr', 'current-index', 0)
.wait(100)
.find('post-stepper-item')
.then($items => {
expect($items[0]).to.have.class('stepper-item-current');
expect($items.filter('.stepper-item-inactive').length).to.equal(4);
});
});

it('should set third step as active when current-index is 2', () => {
cy.get('post-stepper')
.invoke('attr', 'current-index', 2)
.wait(100)
.find('post-stepper-item')
.then($items => {
expect($items[2]).to.have.class('stepper-item-current');
expect($items.filter('.stepper-item-completed').length).to.equal(2);
expect($items.filter('.stepper-item-inactive').length).to.equal(2);
});
});

it('should set all steps as complete but the last when current-index is last element', () => {
cy.get('post-stepper')
.invoke('attr', 'current-index', 4)
.wait(100)
.find('post-stepper-item')
.then($items => {
expect($items[4]).to.have.class('stepper-item-current');
expect($items.filter('.stepper-item-completed').length).to.equal(4);
expect($items.filter('.stepper-item-inactive').length).to.equal(0);
});
});

it('should set all steps as complete when current-index is bigger than the steps length', () => {
cy.get('post-stepper')
.invoke('attr', 'current-index', 5)
.wait(100)
.find('post-stepper-item')
.then($items => {
expect($items.filter('.stepper-item-current').length).to.equal(0);
expect($items.filter('.stepper-item-completed').length).to.equal(5);
expect($items.filter('.stepper-item-inactive').length).to.equal(0);
});
});

// Accessibility and labels

it('should set aria-current="step" on the current step', () => {
cy.get('post-stepper')
.invoke('attr', 'current-index', 1)
.wait(100)
.find('post-stepper-item')
.eq(1)
.should('have.attr', 'aria-current', 'step');
});

it('should set aria-live="polite" on the current step', () => {
cy.get('post-stepper')
.invoke('attr', 'current-index', 1)
.wait(100)
.find('post-stepper-item')
.eq(1)
.should('have.attr', 'aria-live', 'polite');
});

it('should not have aria-live="polite" on future step', () => {
cy.get('post-stepper')
.invoke('attr', 'current-index', 1)
.wait(100)
.find('post-stepper-item')
.eq(2)
.should('not.have.attr', 'aria-live');
});

it('should set current label on the current step', () => {
cy.get('post-stepper')
.invoke('attr', 'current-index', 1)
.wait(100)
.find('post-stepper-item')
.eq(1)
.find('.step-hidden-label')
.should('have.text', 'Current step:');
});

it('should set completed label on completed step', () => {
cy.get('post-stepper')
.invoke('attr', 'current-index', 1)
.wait(100)
.find('post-stepper-item')
.eq(0)
.find('.step-hidden-label')
.should('have.text', 'Completed step:');
});

it('should set correct mobile label on current step', () => {
cy.get('post-stepper')
.invoke('attr', 'current-index', 1)
.wait(100)
.find('post-stepper-item')
.eq(1)
.find('.step-mobile-label')
.should('have.text', 'Step 2:');
});

// Dynamically added/removed steps

it('should add correct class when a new step is added dynamically', () => {
cy.get('post-stepper').find('post-stepper-item').should('have.length', 5);
cy.get('post-stepper').then($stepper => {
$stepper[0].appendChild(document.createElement('post-stepper-item'));
cy.get('post-stepper')
.wait(100)
.find('post-stepper-item')
.should('have.length', 6)
.last()
.should('have.class', 'stepper-item-inactive');
});
});

it('should throw an error if there is only one step', () => {
cy.window().then(win => {
cy.spy(win.console, 'error').as('consoleError');
});
cy.get('post-stepper').find('post-stepper-item').should('have.length', 5);

cy.get('post-stepper').then($stepper => {
const stepper = $stepper[0];
const allButFirstStep = stepper.querySelectorAll('post-stepper-item:not(:first-child)');
allButFirstStep.forEach(step => step.remove());
});

cy.get('post-stepper').find('post-stepper-item').should('have.length', 1);
cy.get('@consoleError').should('be.called');
});
});
22 changes: 22 additions & 0 deletions packages/components/cypress/fixtures/post-stepper.test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link
href="../../node_modules/@swisspost/design-system-styles/post-default.css"
rel="stylesheet"
/>
<script src="../../dist/post-components/post-components.esm.js" type="module"></script>
</head>
<body>
<post-stepper completed-label="Completed step" current-label="Current step" step-label="Step">
<post-stepper-item> Step 1</post-stepper-item>
<post-stepper-item> Step 2</post-stepper-item>
<post-stepper-item> Step 3</post-stepper-item>
<post-stepper-item> Step 4</post-stepper-item>
<post-stepper-item> Step 5</post-stepper-item>
</post-stepper>
</body>
</html>
60 changes: 60 additions & 0 deletions packages/components/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,27 @@ export namespace Components {
*/
"stars": number;
}
interface PostStepper {
/**
* "Completed step" label for accessibility
*/
"completedLabel": string;
/**
* Defines the currently active step
* @default -1
*/
"currentIndex": number;
/**
* "Current step" label for accessibility
*/
"currentLabel": string;
/**
* "Step" label for mobile view
*/
"stepLabel": string;
}
interface PostStepperItem {
}
interface PostTabHeader {
/**
* The name of the panel controlled by the tab header.
Expand Down Expand Up @@ -851,6 +872,18 @@ declare global {
prototype: HTMLPostRatingElement;
new (): HTMLPostRatingElement;
};
interface HTMLPostStepperElement extends Components.PostStepper, HTMLStencilElement {
}
var HTMLPostStepperElement: {
prototype: HTMLPostStepperElement;
new (): HTMLPostStepperElement;
};
interface HTMLPostStepperItemElement extends Components.PostStepperItem, HTMLStencilElement {
}
var HTMLPostStepperItemElement: {
prototype: HTMLPostStepperItemElement;
new (): HTMLPostStepperItemElement;
};
interface HTMLPostTabHeaderElement extends Components.PostTabHeader, HTMLStencilElement {
}
var HTMLPostTabHeaderElement: {
Expand Down Expand Up @@ -928,6 +961,8 @@ declare global {
"post-popover": HTMLPostPopoverElement;
"post-popovercontainer": HTMLPostPopovercontainerElement;
"post-rating": HTMLPostRatingElement;
"post-stepper": HTMLPostStepperElement;
"post-stepper-item": HTMLPostStepperItemElement;
"post-tab-header": HTMLPostTabHeaderElement;
"post-tab-panel": HTMLPostTabPanelElement;
"post-tabs": HTMLPostTabsElement;
Expand Down Expand Up @@ -1323,6 +1358,27 @@ declare namespace LocalJSX {
*/
"stars"?: number;
}
interface PostStepper {
/**
* "Completed step" label for accessibility
*/
"completedLabel": string;
/**
* Defines the currently active step
* @default -1
*/
"currentIndex"?: number;
/**
* "Current step" label for accessibility
*/
"currentLabel": string;
/**
* "Step" label for mobile view
*/
"stepLabel": string;
}
interface PostStepperItem {
}
interface PostTabHeader {
/**
* The name of the panel controlled by the tab header.
Expand Down Expand Up @@ -1419,6 +1475,8 @@ declare namespace LocalJSX {
"post-popover": PostPopover;
"post-popovercontainer": PostPopovercontainer;
"post-rating": PostRating;
"post-stepper": PostStepper;
"post-stepper-item": PostStepperItem;
"post-tab-header": PostTabHeader;
"post-tab-panel": PostTabPanel;
"post-tabs": PostTabs;
Expand Down Expand Up @@ -1466,6 +1524,8 @@ declare module "@stencil/core" {
"post-popover": LocalJSX.PostPopover & JSXBase.HTMLAttributes<HTMLPostPopoverElement>;
"post-popovercontainer": LocalJSX.PostPopovercontainer & JSXBase.HTMLAttributes<HTMLPostPopovercontainerElement>;
"post-rating": LocalJSX.PostRating & JSXBase.HTMLAttributes<HTMLPostRatingElement>;
"post-stepper": LocalJSX.PostStepper & JSXBase.HTMLAttributes<HTMLPostStepperElement>;
"post-stepper-item": LocalJSX.PostStepperItem & JSXBase.HTMLAttributes<HTMLPostStepperItemElement>;
"post-tab-header": LocalJSX.PostTabHeader & JSXBase.HTMLAttributes<HTMLPostTabHeaderElement>;
"post-tab-panel": LocalJSX.PostTabPanel & JSXBase.HTMLAttributes<HTMLPostTabPanelElement>;
"post-tabs": LocalJSX.PostTabs & JSXBase.HTMLAttributes<HTMLPostTabsElement>;
Expand Down
Loading
Loading