Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(docs): add CSS-only stepper docs #3092

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/shaggy-weeks-grab.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@swisspost/design-system-documentation': minor
---

Added documentation for the CSS-only stepper and deprecated the stepper based on ng-bootstrap progress bar.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
describe('Stepper', () => {
it('default', () => {
cy.visit('/iframe.html?id=snapshots--stepper');
cy.get('.stepper', { timeout: 30000 }).should('be.visible');
cy.percySnapshot('Steppers', { widths: [320, 1440] });
});
});

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,56 +1,54 @@
import { Meta, Source } from '@storybook/blocks';
import { PostTabHeader, PostTabPanel, PostTabs } from '@swisspost/design-system-components-react';
import StylesPackageImport from '@/shared/styles-package-import.mdx';
import { Canvas, Controls, Meta } from '@storybook/blocks';
import PostComponentDemoLink from '@/shared/post-component-demo-link.mdx';
import NgbComponentImport from '@/shared/nb-bootstrap/ngb-component-import.mdx';
import * as stepperStories from './stepper.stories';
import stepperTemplate from './stepper-template.sample.html?raw';
import stepperComponent from './stepper-component.sample?raw';
import StylesPackageImport from '@/shared/styles-package-import.mdx';
import * as StepperStories from './stepper.stories';

<Meta of={stepperStories} />
<Meta of={StepperStories} />

<div className="docs-title">
# Stepper

<nav>
<link-design of={JSON.stringify(stepperStories)}></link-design>
<PostComponentDemoLink component="stepper" />
<link-design of={JSON.stringify(StepperStories)}></link-design>
</nav>
</div>

<p className="lead">Conveys progress through numbered steps.</p>
<p className="lead">The stepped progression component provides an interactive visual overview of a process. It shows at a glance the amount of steps a user is required to go through, and serves as a guide for each step, indicating its status.</p>

<div className="alert alert-info mb-bigger-big">
<p className="alert-heading">The stepper uses ng-bootstrap's progressbar component.</p>
<div className="alert alert-warning mb-bigger-big">
<p className="alert-heading">The stepper previously used ng-bootstrap's progressbar component, this has been deprecated in favor of the CSS-only stepper documented below.</p>
<div className="d-flex justify-content-end"><PostComponentDemoLink component="stepper" /></div>
</div>

<ul>
<li>
<a href="#component-import" target="_self">Component Import</a>
</li>
<li>
<a href="#style-imports" target="_self">Style Imports</a>
</li>
<li>
<a href="#example" target="_self">Example</a>
</li>
</ul>
<Canvas sourceState="shown" of={StepperStories.Default} />
<div className="hide-col-default">
<Controls of={StepperStories.Default} />
</div>

<StylesPackageImport components={['stepper']} />

## Examples

### Navigational Stepper

You can use a stepper to allow users to navigate to specific steps.
To do so, use `a` elements for the `.stepper-link` of the steps you want to be navigable, and use `span` for the steps you want not to be navigable.
Then, wrap the stepper inside a `<nav>`.

However, it is better not to use an `a` for the current step since the user is already on the page.

<Canvas of={StepperStories.NavigationalStepper} />

<NgbComponentImport module="NgbProgressbarModule" />
### Informational Stepper

<StylesPackageImport components={["progress", "stepper"]} />
If you wish to display the stepper for informational purposes only, you can use `<span>` elements for the `.stepper-link` on all the steps.
In this case, there's no need to wrap the stepper inside a `<nav>`.

## Example
<Canvas of={StepperStories.InformationalStepper} />

<PostTabs>
<PostTabHeader slot="tabs" panel="template">template.html</PostTabHeader>
<PostTabPanel name="template">
<Source code={stepperTemplate} language="html"/>
</PostTabPanel>
### Long Labels

<PostTabHeader slot="tabs" panel="component">component.ts</PostTabHeader>
<PostTabPanel name="component">
<Source code={stepperComponent} language="typescript"/>
</PostTabPanel>
</PostTabs>
When dealing with longer labels that may wrap into multiple lines, it is recommended to use a `title` attribute for the `.stepper-link`.
This will truncate the label with an ellipsis if it exceeds two lines, while allowing the user to read the full label by hovering over it with the mouse.

<Canvas of={StepperStories.LongLabels} />
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { Args, StoryContext, StoryObj } from '@storybook/web-components';
import meta from './stepper.stories';
import { html } from 'lit';
import { bombArgs } from '@/utils';

const { id, ...metaWithoutId } = meta;

export default {
...metaWithoutId,
title: 'Snapshots',
};

type Story = StoryObj;

export const Stepper: Story = {
render: (_args: Args, context: StoryContext) => {
const longSteps = [
'Curabitur sed velit ullamcorper, molestie nunc a, dignissim ante. Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
'Ut sed consectetur odio. Curabitur vel pulvinar est. Maecenas quam arcu, sagittis et libero aliquet, egestas luctus nisi.',
'Nam pretium nec neque sed vulputate. Sed non augue libero. Vivamus consequat mauris id ligula cursus, sit amet faucibus ipsum.',
'Sed vulputate lacinia eros, sit amet mattis sem luctus sit amet. Vestibulum pharetra tortor a laoreet malesuada.',
];
return html`
<div class="d-flex flex-column gap-1">
${['bg-white', 'bg-dark'].map(
bg => html`
<div class="${bg} d-flex flex-column gap-regular p-regular">
${bombArgs({
currentStepNumber: meta.argTypes?.currentStepNumber?.options,
}).map((args: Args) => meta.render?.({ ...context.args, ...args }, context))}
${meta.render?.({ ...context.args, ...{ steps: longSteps } }, context)}
</div>
`,
)}
</div>
`;
},
};
Original file line number Diff line number Diff line change
@@ -1,22 +1,152 @@
import type { StoryObj } from '@storybook/web-components';
import { Args, StoryObj } from '@storybook/web-components';
import { html } from 'lit';
import { MetaComponent } from '@root/types';
import { ifDefined } from 'lit/directives/if-defined.js';
import { useArgs } from '@storybook/preview-api';

const defaultSteps = ['Sender', 'Product', 'Other details', 'Order summary'];

const meta: MetaComponent = {
id: '7dc546d9-e248-4d06-befe-3ad62fcd310f',
title: 'Components/Stepper',
tags: ['package:Angular'],
render: renderStepper,
parameters: {
badges: [],
controls: {
exclude: ['steps'],
},
design: {
type: 'figma',
url: 'https://www.figma.com/file/xZ0IW0MJO0vnFicmrHiKaY/Components-Post?type=design&node-id=20952-29106&mode=design&t=38qLaYwWdirTcHdb-4',
},
},
args: {
currentStepNumber: 3,
navigableSteps: 'all',
processName: 'Registration Form',
steps: defaultSteps,
},
argTypes: {
navigableSteps: {
name: 'Navigable Steps',
description: 'Defines which steps in the current process the user can navigate to.',
control: {
type: 'radio',
labels: {
all: 'All steps',
completedOnly: 'Completed Steps only',
none: 'None',
},
},
options: ['all', 'completedOnly', 'none'],
table: {
category: 'General',
},
},
currentStepNumber: {
name: 'Current Step Number',
description: 'The number of the step the user is currently at in the process.',
control: {
type: 'select',
},
options: Object.keys(defaultSteps).map(key => parseInt(key, 10) + 1),
table: {
category: 'General',
},
},
processName: {
name: 'Process Name',
description:
'A straightforward, self-explanatory name for the current process, used for assistive technologies.',
control: {
type: 'text',
},
table: {
category: 'General',
},
},
},
};

export default meta;

export const Default: StoryObj = {
render: () => html``,
// RENDERER
function getStepperItem(args: Args, step: string, index: number) {
const currentStepIndex = args.currentStepNumber - 1;
const isCompletedStep = index < currentStepIndex;
const isCurrentStep = index === currentStepIndex;
const isNextStep = index > currentStepIndex;
const isLink =
(isCompletedStep && args.navigableSteps !== 'none') ||
(isNextStep && args.navigableSteps === 'all');

let status = 'Current';
if (isCompletedStep) status = 'Completed';
if (isNextStep) status = 'Next';

const text = html`<span class="visually-hidden">${status} step:</span> ${step}`;
const title = step !== defaultSteps[index] ? step : undefined;

return html`
<li aria-current=${ifDefined(isCurrentStep ? 'step' : undefined)} class="stepper-item">
${isLink
? html`
<a class="stepper-link" href="../step-${index + 1}" title=${ifDefined(title)}>
${text}
</a>
`
: html`<span class="stepper-link" title=${ifDefined(title)}>${text}</span>`}
</li>
`;
}

function renderStepper(args: Args) {
const [_, updateArgs] = useArgs();

setTimeout(() => {
const stepperLinks = document.querySelectorAll('.stepper-link');
stepperLinks.forEach((link, index) => {
if (link.tagName === 'SPAN') return;

link.addEventListener('click', e => {
e.preventDefault();
updateArgs({ currentStepNumber: index + 1 });
});
});
});

const isNav = args.navigableSteps !== 'none';
const stepper = html`<ol
class="stepper"
aria-label=${ifDefined(isNav ? undefined : args.processName + ' Progress')}
>
${args.steps.map((step: string, index: number) => getStepperItem(args, step, index))}
</ol>`;

return args.navigableSteps === 'none'
? stepper
: html` <nav aria-label="${args.processName}">${stepper}</nav> `;
}

export const Default: StoryObj = {};

export const NavigationalStepper: StoryObj = {
args: {
navigableSteps: 'completedOnly',
},
};

export const InformationalStepper: StoryObj = {
args: {
navigableSteps: 'none',
},
};

export const LongLabels: StoryObj = {
args: {
steps: [
'Nullam luctus mi sit amet nisl suscipit, nec tempor justo varius',
...defaultSteps.slice(1),
],
},
};
Loading