Skip to content

Commit

Permalink
feat(styles): component form footer (#3616)
Browse files Browse the repository at this point in the history
Co-authored-by: Alizé Debray <[email protected]>
  • Loading branch information
leagrdv and alizedebray authored Oct 30, 2024
1 parent c607efc commit 8174593
Show file tree
Hide file tree
Showing 10 changed files with 308 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .changeset/gold-chairs-grin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@swisspost/design-system-documentation': minor
'@swisspost/design-system-styles': minor
---

Added Form Footer component.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
describe('Form footer', () => {
it('default', () => {
cy.visit('/iframe.html?id=snapshots--form-footer');
cy.get('.form-footer post-icon', { timeout: 30000 }).should('be.visible');
cy.percySnapshot('Form footer', { widths: [320, 1440] });
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Canvas, Controls, Meta } from '@storybook/blocks';
import * as FormFooterStories from './form-footer.stories';
import StylesPackageImport from '@/shared/styles-package-import.mdx';

<Meta of={FormFooterStories} />

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

<nav>
<link-design of={JSON.stringify(FormFooterStories)}></link-design>
</nav>
</div>

<div className="lead">
The form footer is placed at the end/bottom of a form or a form step, in case the form is splitted with a stepper. It provides workflow-related actions to the form or form step.
</div>

<Canvas sourceState="shown" of={FormFooterStories.Default} />
<div className="hide-col-default">
<Controls of={FormFooterStories.Default} />
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import type { StoryObj } from '@storybook/web-components';
import meta, { FooterArgs } from './form-footer.stories';
import { html } from 'lit';
import { bombArgs } from '@/utils';

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

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

type Story = StoryObj;

export const FormFooter: Story = {
render: () => {
return html`
<div class="d-flex flex-column gap-3">
${['bg-white', 'bg-dark'].map(
bg => html`
<div class="${bg} d-flex flex-column p-3 gap-3">
${bombArgs({
showPrimaryButton: [true, false],
showSecondaryButton: [true, false],
showTertiaryButton: [true, false],
}).map(args => {
const primaryButton = args.showPrimaryButton
? html`<button class="btn btn-primary">
${FooterArgs.primaryButtonText}<post-icon
aria-hidden="true"
name="${FooterArgs.primaryButtonIcon}"
></post-icon>
</button>`
: null;
const secondaryButton = args.showSecondaryButton
? html`<button class="btn btn-secondary">
${FooterArgs.secondaryButtonText}
</button>`
: null;
return html`
<div class="form-footer">
${args.showPrimaryButton || args.showSecondaryButton
? html`
<div class="form-footer-primary-actions">
${primaryButton} ${secondaryButton}
</div>
`
: null}
${args.showTertiaryButton
? html`
<button class="btn btn-tertiary">
<post-icon
aria-hidden="true"
name="${FooterArgs.tertiaryButtonIcon}"
></post-icon
>${FooterArgs.tertiaryButtonText}
</button>
`
: null}
</div>
`;
})}
</div>
`,
)}
</div>
`;
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { Args, StoryObj } from '@storybook/web-components';
import { html } from 'lit';
import { MetaComponent } from '@root/types';

export const FooterArgs = {
showPrimaryButton: true,
primaryButtonText: 'Send',
primaryButtonIcon: '3020',
showSecondaryButton: true,
secondaryButtonText: 'Cancel',
showTertiaryButton: true,
tertiaryButtonText: 'Back',
tertiaryButtonIcon: '3024',
};

const meta: MetaComponent = {
id: 'f2eddf67-2c3c-40c4-bfec-df49bd028001',
title: 'Components/Forms/Form Footer',
tags: ['package:HTML'],
render: render,
parameters: {
badges: [],
design: {
type: 'figma',
url: 'https://www.figma.com/design/JIT5AdGYqv6bDRpfBPV8XR/Foundations-%26-Components-Next-Level?node-id=1498-28215',
},
},
args: {
...FooterArgs,
},
argTypes: {
showPrimaryButton: {
name: 'Show primary button',
description: 'Show or hide the primary button (last one on the right)',
control: { type: 'boolean' },
table: {
category: 'Primary button',
},
},
primaryButtonText: {
name: 'Primary button text',
description: 'Text to display on the primary button',
control: { type: 'text' },
table: {
category: 'Primary button',
},
if: {
arg: 'showPrimaryButton',
},
},
primaryButtonIcon: {
name: 'Primary button icon',
description: 'Icon to display on the primary button',
control: { type: 'text' },
table: {
category: 'Primary button',
},
if: {
arg: 'showPrimaryButton',
},
},
showSecondaryButton: {
name: 'Show secondary button',
description: 'Show or hide the secondary button (first one on the right)',
control: { type: 'boolean' },
table: {
category: 'Secondary button',
},
},
secondaryButtonText: {
name: 'Secondary button text',
description: 'Text to display on the secondary button',
control: { type: 'text' },
table: {
category: 'Secondary button',
},
if: {
arg: 'showSecondaryButton',
},
},
showTertiaryButton: {
name: 'Show tertiary button',
description: 'Show or hide the tertiary button (button on the left)',
control: { type: 'boolean' },
table: {
category: 'Tertiary button',
},
},
tertiaryButtonText: {
name: 'Tertiary button text',
description: 'Text to display on the tertiary button',
control: { type: 'text' },
table: {
category: 'Tertiary button',
},
if: {
arg: 'showTertiaryButton',
},
},
tertiaryButtonIcon: {
name: 'Tertiary button icon',
description: 'Icon to display on the tertiary button',
control: { type: 'text' },
table: {
category: 'Tertiary button',
},
if: {
arg: 'showTertiaryButton',
},
},
},
};

export default meta;

type Story = StoryObj;

export function render(args: Args) {
const primaryButton = args.showPrimaryButton
? html`<button class="btn btn-primary">
${args.primaryButtonText}<post-icon
aria-hidden="true"
name="${args.primaryButtonIcon}"
></post-icon>
</button>`
: null;
const secondaryButton = args.showSecondaryButton
? html`<button class="btn btn-secondary">${args.secondaryButtonText}</button>`
: null;

return html`
<div class="form-footer">
${args.showPrimaryButton || args.showSecondaryButton
? html` <div class="form-footer-primary-actions">${primaryButton} ${secondaryButton}</div> `
: null}
${args.showTertiaryButton
? html`<button class="btn btn-tertiary">
<post-icon aria-hidden="true" name="${args.tertiaryButtonIcon}"></post-icon
>${args.tertiaryButtonText}
</button>`
: null}
</div>
`;
}

export const Default: Story = {};
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,9 @@ To enable screen-readers to detect and read your hints, link the `<input>`
with the aria-describedby attribute to the hint via id.

<Canvas of={FormPatternsStories.Hints} sourceState="shown" />

## Form footer

The form footer is placed at the end/bottom of a form or a form step, in case the form is splitted with a stepper. It provides workflow-related actions to the form or form step.

<Canvas of={FormPatternsStories.Footer} sourceState="shown" />
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { StoryObj } from '@storybook/web-components';
import { html } from 'lit';
import { MetaExtended } from '@root/types';
import * as FormFooterMeta from '../../components/forms/form-footer/form-footer.stories';

const meta: MetaExtended = {
id: 'd83829b2-7de2-48d2-be64-07a80c9caef3',
Expand Down Expand Up @@ -352,3 +353,8 @@ export const Hints: Story = {
</div>
`,
};

export const Footer: Story = {
render: FormFooterMeta.render,
args: FormFooterMeta.FooterArgs,
};
1 change: 1 addition & 0 deletions packages/styles/src/components/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
@use 'close';
@use 'elevation';
@use 'error-container';
@use 'form-footer';
@use 'form-range';
@use 'form-select';
@use 'form-textarea';
Expand Down
18 changes: 18 additions & 0 deletions packages/styles/src/components/form-footer.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
@use '../mixins/utilities' as utility-mx;
@use '../tokens/components';
@use '../functions/tokens' as tokens;

tokens.$default-map: components.$post-form-footer;

.form-footer {
@include utility-mx.responsive-actions();
border-block-start-width: tokens.get('form-footer-border-block-start-width');
border-block-start-color: tokens.get('form-footer-border-start-color');
border-block-start-style: tokens.get('form-footer-border-block-start-style');
padding-block-start: tokens.get('form-footer-padding-block-start');
gap: tokens.get('form-footer-gap');

&-primary-actions {
gap: tokens.get('form-footer-gap');
}
}
26 changes: 26 additions & 0 deletions packages/styles/src/mixins/_utilities.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
@use './../themes/bootstrap/core' as *;
@use '../variables/spacing';
@use '../variables/commons';
@use '../variables/breakpoints';

@mixin reset-list() {
margin: 0;
Expand Down Expand Up @@ -174,3 +176,27 @@
}
}
}

@mixin responsive-actions {
display: flex;
flex-direction: column;
justify-content: space-between;

@include media-breakpoint-up(md) {
flex-direction: row-reverse;

> .btn {
margin-right: auto;
}
}

&-primary-actions {
display: flex;
flex-direction: column;

@include media-breakpoint-up(md) {
flex-direction: row-reverse;
margin-left: auto;
}
}
}

0 comments on commit 8174593

Please sign in to comment.