Skip to content

Commit

Permalink
feat(dialog): adding styles for native dialog (#2772)
Browse files Browse the repository at this point in the history
Styles for the dialog element, replacing ngb-modal.

---------

Co-authored-by: Alizé Debray <[email protected]>
  • Loading branch information
gfellerph and alizedebray authored Oct 8, 2024
1 parent 252c768 commit da7b10f
Show file tree
Hide file tree
Showing 16 changed files with 598 additions and 4 deletions.
6 changes: 6 additions & 0 deletions .changeset/kind-buses-trade.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 styling support and documentation for the `<dialog>` element. The dialog will replace the current modal and notification overlay components coming from ng-bootstrap.
5 changes: 5 additions & 0 deletions .changeset/popular-games-rush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@swisspost/design-system-styles': patch
---

Deprecated the ng-bootstrap components Modal and Notification overlay in favor of the new Dialog component. The styles for these ng-bootstrap components will be removed in a future major version.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
describe('Dialog', () => {
it('default', () => {
cy.visit('/iframe.html?id=snapshots--dialog');
cy.get('dialog[open]', { timeout: 30000 }).should('be.visible');
cy.percySnapshot('Dialog', { widths: [1440] });
});
});
2 changes: 1 addition & 1 deletion packages/documentation/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"e2e:watch": "cypress open",
"doctor": "storybook doctor",
"snapshots": "percy exec -- cypress run --config-file ./cypress.snapshot.config.js --record --key 0995e768-43ec-42bd-a127-ff944a2ad8c9",
"lint": "eslint **/*.{js,ts,tsx,mdx}"
"lint": "eslint **/*.{ts,tsx,mdx}"
},
"dependencies": {
"@swisspost/design-system-components": "workspace:9.0.0-next.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Canvas, Controls, Meta, Source } from '@storybook/blocks';
import * as DialogStories from './dialog.stories';
import JSFormData from './samples/js-form-data?raw';
import StylesPackageImport from '@/shared/styles-package-import.mdx';

<Meta of={DialogStories} />

# Dialog

<p className="lead">Communicate crucial information and request user action.</p>

<Canvas sourceState="shown" of={DialogStories.Default} />
<div className="hide-col-default">
<Controls of={DialogStories.Default} />
</div>

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

## Examples

### Form dialog
<Canvas sourceState="hidden" of={DialogStories.Form} />

#### Using form data

Register a `submit` event listener on the form. In the event handler, you have access to all the form field values inside the dialog. The dialog box closes when the form gets submitted.

<Source
code={JSFormData}
language="typescript"
/>

### Custom content dialog
The dialog can also contain arbitrary content.
<Canvas sourceState="hidden" of={DialogStories.Custom} />
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import meta, { Default } from './dialog.stories';
import { html } from 'lit';
import { bombArgs } from '@/utils';
import type { Args, StoryContext, StoryObj } from '@storybook/web-components';

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

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

type Story = StoryObj;

export const Dialog: Story = {
render: (_args: Args, context: StoryContext) => {
return html`
<style>
dialog {
position: static;
margin: 0;
transition: none !important;
}
</style>
<div class="d-flex flex-wrap align-items-start gap-regular p-regular">
${bombArgs({
backgroundColor: ['bg-white', 'bg-primary'],
size: context.argTypes.size.options,
icon: ['none', '1034'],
closeButton: [true, false],
content: [
'Content',
'Contentus momentus vero siteos et accusam iretea et justo. Contentus momentus vero siteos et accusam iretea et justo.',
],
open: [true],
}).map((args: Args) => Default.render?.({ ...context.args, ...args }, context))}
</div>
`;
},
};
235 changes: 235 additions & 0 deletions packages/documentation/src/stories/components/dialog/dialog.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
import { Args, Meta, StoryObj } from '@storybook/web-components';
import { html, nothing } from 'lit-html';

const meta: Meta = {
id: '562eac2b-6dc1-4007-ba8e-4e981cef0cbc',
title: 'Components/Dialog',
parameters: {
design: {
type: 'figma',
url: 'https://www.figma.com/design/xZ0IW0MJO0vnFicmrHiKaY/Components-Post?node-id=20215-22938&m=dev',
},
},
args: {
title: 'Dialog',
content: 'This is a dialog',
size: 'medium',
position: 'center',
icon: 'none',
backgroundColor: 'bg-white',
animation: 'pop-in',
closeButton: true,
open: false,
},
argTypes: {
title: {
name: 'Title',
description: 'Optional title',
control: 'text',
table: { category: 'Content' },
},
content: {
name: 'Content',
description: 'Dialog text',
control: 'text',
table: { category: 'Content' },
},
size: {
name: 'Size',
description: 'Max width of the dialog.',
control: {
type: 'radio',
},
options: ['small', 'medium', 'large'],
table: { category: 'Variant' },
},
position: {
name: 'Position',
description: 'Position of the dialog on the screen',
control: {
type: 'radio',
},
options: ['top', 'center', 'bottom'],
table: { category: 'Variant' },
},
animation: {
name: 'Animation',
description: 'Choose an animation effect for showing and hidding the dialog.',
control: 'radio',
options: ['pop-in', 'slide-in', 'none'],
table: { category: 'Variant' },
},
icon: {
name: 'Icon',
description: 'Display an icon in the dialog.',
control: {
type: 'select',
labels: {
none: 'None',
1034: '1034 (Info)',
2104: '2104 (Danger)',
2106: '2106 (Warning)',
2105: '2105 (Success)',
},
},
options: ['none', '1034', '2105', '2104', '2106'],
table: { category: 'Content' },
},
backgroundColor: {
name: 'Background color',
description: 'The background color of the dialog field',
control: {
type: 'select',
},
options: ['bg-white', 'bg-light', 'bg-primary'],
table: { category: 'Variant' },
},
closeButton: {
name: 'Close button',
description: 'Show a close button to dismiss the dialog',
control: 'boolean',
table: { category: 'Content' },
},
open: {
name: 'Default open',
description: 'Test property for snapshots',
control: 'boolean',
table: { disable: true },
},
},
decorators: [
story =>
html`<div>
<button
id="show-dialog-button"
class="btn btn-secondary"
onclick="this.nextElementSibling.showModal()"
>
Show dialog</button
>${story()}
</div>`,
],
};

export default meta;

const getHeader = (text: string) => {
return html`<h2>${text}</h2>`;
};

const getCloseButton = () => {
return html`<button class="btn btn-close">
<span class="visually-hidden">Close</span>
</button>`;
};

const getControls = () => {
return html`<button class="btn btn-primary">OK</button>
<button class="btn btn-secondary">Cancel</button>`;
};

const Template = {
render: (args: Args) => {
const header = getHeader(args.title);
const body = html`${args.content}`;
const controls = getControls();
const postDialogIcon =
args.icon && args.icon !== 'none'
? html`<post-icon name="${args.icon}"></post-icon>`
: nothing;
const postDialogCloseButton = args.closeButton ? getCloseButton() : nothing;

// Don't declare default values or show empty containers
if (args.backgroundColor === 'bg-white') args.backgroundColor = nothing;
if (args.animation === 'pop-in') args.animation = nothing;
if (args.position === 'center') args.position = nothing;
if (args.size === 'medium') args.size = nothing;

return html`
<dialog
class="${args.backgroundColor}"
data-size="${args.size}"
data-position="${args.position}"
data-animation="${args.animation}"
open="${args.open || nothing}"
>
<form method="dialog" class="dialog-grid">
${postDialogIcon}
<h3 class="dialog-header">${header}</h3>
<div class="dialog-body">${body}</div>
<div class="dialog-controls">${controls}</div>
${postDialogCloseButton}
</form>
</dialog>
`;
},
};

const FormTemplate = {
...Template,
render: (args: Args) => {
return html`
<dialog size="${args.size}">
<form
id="example-dialog-form"
method="dialog"
class="dialog-grid"
onsubmit="console.log(Object.fromEntries(new FormData(event.target)))"
>
<h3 class="dialog-header">Form example</h3>
<div class="dialog-body">
<div class="form-floating mt-3">
<input
id="example-dialog-text-field"
class="form-control form-control-lg"
type="text"
placeholder="Placeholder"
name="example-text-field"
required
/>
<label class="form-label" for="example-dialog-text-field">Label</label>
<div class="form-text">
Hintus textus elare volare cantare hendrerit in vulputate velit esse molestie
consequat, vel illum dolore eu feugiat nulla facilisis.
</div>
</div>
</div>
<div class="dialog-controls">
<button class="btn btn-primary">Confirm</button>
<button class="btn btn-secondary" formnovalidate>Cancel</button>
</div>
</form>
</dialog>
`;
},
};

const CustomContentTemplate = {
...Template,
render: () => {
return html`
<dialog>
<form method="dialog" onsubmit="console.log(event)" class="p-regular-r">
<h2>Custom content</h2>
<p>This is some other content, just placed inside the dialog.</p>
<button class="btn btn-primary">Ok</button>
</form>
</dialog>
`;
},
};

type Story = StoryObj;

export const Default: Story = {
...Template,
};

export const Form: Story = {
...FormTemplate,
};

export const Custom: Story = {
...CustomContentTemplate,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
document.querySelector('#example-dialog-form')?.addEventListener('submit', event => {
if (!event.target) return;
const formData = Object.fromEntries(new FormData(event.target as HTMLFormElement)); // Object containing your form data
console.log(formData);
});
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ import modalBlocking from './modal-blocking.sample?raw';

<NgbComponentAlert component="modal" />

<div className="alert alert-warning mb-4">
This component is deprecated in favor of the <a href="/?path=/docs/562eac2b-6dc1-4007-ba8e-4e981cef0cbc--docs">dialog component</a>.
</div>

<ul>
<li>
<a href="#component-import" target="_self">Component Import</a>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { MetaComponent } from '@root/types';

const meta: MetaComponent = {
id: '9a512414-84c5-473c-a7c8-a434eda9578d',
title: 'Components/Modal',
title: 'Components/Modal (deprecated)',
tags: ['package:Angular'],
parameters: {
badges: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ import basicExampleAngular from './notification-overlay.sample.ts?raw';

<p className="lead">Present the user with important information or a decision before continuing the workflow.</p>

<div className="alert alert-warning mb-4">
This component is deprecated and will be removed in a future major version.
</div>

<ul>
<li>
<a href="#component-import" target="_self">Component Import</a>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { MetaComponent } from '@root/types';

const meta: MetaComponent = {
id: 'aab3f0df-08ca-4e33-90eb-77ffda6528db',
title: 'Components/Notification Overlay',
title: 'Components/Notification Overlay (deprecated)',
tags: ['package:Angular'],
parameters: {
badges: [],
Expand Down
2 changes: 1 addition & 1 deletion packages/migrations/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"copy-files": "copyfiles -f LICENSE README.md package.json CONTRIBUTING.md CHANGELOG.md src/migrations.json dist",
"build": "tsc -p tsconfig.json && pnpm copy-files",
"clean": "rimraf dist",
"lint": "eslint **/*.{js,ts}"
"lint": "eslint **/*.ts"
},
"dependencies": {
"@angular-devkit/core": "=15.0.4",
Expand Down
Loading

0 comments on commit da7b10f

Please sign in to comment.