From 7044f83848c47cb5cf21599ae814f71638696960 Mon Sep 17 00:00:00 2001 From: debraya Date: Tue, 7 Feb 2023 16:53:50 +0100 Subject: [PATCH 01/27] feat(components): add alert component update alert story --- .changeset/sweet-singers-trade.md | 6 + packages/components/src/components.d.ts | 53 +++++++ .../src/components/post-alert/post-alert.scss | 6 + .../src/components/post-alert/post-alert.tsx | 129 ++++++++++++++++++ .../components/src/utils/property-checkers.ts | 8 ++ .../src/utils/tests/property-checkers.spec.ts | 47 ++++++- .../components/post-alert/post-alert.docs.mdx | 11 ++ .../post-alert/post-alert.stories.tsx | 23 ++++ 8 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 .changeset/sweet-singers-trade.md create mode 100644 packages/components/src/components/post-alert/post-alert.scss create mode 100644 packages/components/src/components/post-alert/post-alert.tsx create mode 100644 packages/documentation/src/stories/components/post-alert/post-alert.docs.mdx create mode 100644 packages/documentation/src/stories/components/post-alert/post-alert.stories.tsx diff --git a/.changeset/sweet-singers-trade.md b/.changeset/sweet-singers-trade.md new file mode 100644 index 0000000000..1c08e0c7fe --- /dev/null +++ b/.changeset/sweet-singers-trade.md @@ -0,0 +1,6 @@ +--- +'@swisspost/design-system-documentation': minor +'@swisspost/design-system-components': minor +--- + +Created an alert component. diff --git a/packages/components/src/components.d.ts b/packages/components/src/components.d.ts index 182db1cfa4..92b6038062 100644 --- a/packages/components/src/components.d.ts +++ b/packages/components/src/components.d.ts @@ -6,6 +6,28 @@ */ import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; export namespace Components { + interface PostAlert { + /** + * The label to use for the close button of a dismissible alert. + */ + "dismissLabel": string; + /** + * If `true`, a close button (×) is displayed and the alert can be dismissed by the user. + */ + "dismissible": boolean; + /** + * If `true`, the alert is positioned at the bottom of the window, from edge to edge. + */ + "fixed": boolean; + /** + * The icon to display in the alert. If `null`, no icon will be displayed. By default, the icon depends on the alert type. + */ + "icon": string; + /** + * The type of the alert. We provide styles for the following types: `'primary'`, `'success'`, `'danger'`, `'warning'`, `'info'`. + */ + "type": string; + } interface PostCollapsible { /** * If `true`, the element is initially collapsed otherwise it is displayed. @@ -22,6 +44,12 @@ export namespace Components { } } declare global { + interface HTMLPostAlertElement extends Components.PostAlert, HTMLStencilElement { + } + var HTMLPostAlertElement: { + prototype: HTMLPostAlertElement; + new (): HTMLPostAlertElement; + }; interface HTMLPostCollapsibleElement extends Components.PostCollapsible, HTMLStencilElement { } var HTMLPostCollapsibleElement: { @@ -29,10 +57,33 @@ declare global { new (): HTMLPostCollapsibleElement; }; interface HTMLElementTagNameMap { + "post-alert": HTMLPostAlertElement; "post-collapsible": HTMLPostCollapsibleElement; } } declare namespace LocalJSX { + interface PostAlert { + /** + * The label to use for the close button of a dismissible alert. + */ + "dismissLabel"?: string; + /** + * If `true`, a close button (×) is displayed and the alert can be dismissed by the user. + */ + "dismissible"?: boolean; + /** + * If `true`, the alert is positioned at the bottom of the window, from edge to edge. + */ + "fixed"?: boolean; + /** + * The icon to display in the alert. If `null`, no icon will be displayed. By default, the icon depends on the alert type. + */ + "icon"?: string; + /** + * The type of the alert. We provide styles for the following types: `'primary'`, `'success'`, `'danger'`, `'warning'`, `'info'`. + */ + "type"?: string; + } interface PostCollapsible { /** * If `true`, the element is initially collapsed otherwise it is displayed. @@ -44,6 +95,7 @@ declare namespace LocalJSX { "headingLevel"?: number; } interface IntrinsicElements { + "post-alert": PostAlert; "post-collapsible": PostCollapsible; } } @@ -51,6 +103,7 @@ export { LocalJSX as JSX }; declare module "@stencil/core" { export namespace JSX { interface IntrinsicElements { + "post-alert": LocalJSX.PostAlert & JSXBase.HTMLAttributes; "post-collapsible": LocalJSX.PostCollapsible & JSXBase.HTMLAttributes; } } diff --git a/packages/components/src/components/post-alert/post-alert.scss b/packages/components/src/components/post-alert/post-alert.scss new file mode 100644 index 0000000000..1f47da3ae6 --- /dev/null +++ b/packages/components/src/components/post-alert/post-alert.scss @@ -0,0 +1,6 @@ +@use '@swisspost/design-system-styles/components/alert'; +@use '@swisspost/design-system-styles/components/icons'; + +:host { + display: block; +} diff --git a/packages/components/src/components/post-alert/post-alert.tsx b/packages/components/src/components/post-alert/post-alert.tsx new file mode 100644 index 0000000000..ed983a4717 --- /dev/null +++ b/packages/components/src/components/post-alert/post-alert.tsx @@ -0,0 +1,129 @@ +/* + * Copyright 2023 by Swiss Post, Information Technology + */ + +import { Component, Element, h, Prop, State, Watch } from '@stencil/core'; +import { checkBoolean, checkNonEmptyString, checkOneOf, checkPattern } from '../../utils'; + +@Component({ + tag: 'post-alert', + styleUrl: 'post-alert.scss', + shadow: true, +}) +export class PostAlert { + /** + * The type of the alert. + * + * We provide styles for the following types: `'primary'`, `'success'`, `'danger'`, `'warning'`, `'info'`. + */ + @Prop() type = 'primary'; + + /** + * If `true`, the alert is positioned at the bottom of the window, from edge to edge. + */ + @Prop() fixed = false; + + /** + * If `true`, a close button (×) is displayed and the alert can be dismissed by the user. + */ + @Prop() dismissible = false; + + /** + * The label to use for the close button of a dismissible alert. + */ + @Prop() dismissLabel: string; + + /** + * The icon to display in the alert. + * + * If `null`, no icon will be displayed. + * By default, the icon depends on the alert type. + */ + @Prop() icon: string; + + @State() alertClasses: string[] = ['alert']; + @State() hasHeading: boolean; + + @Element() host: HTMLElement; + + @Watch('type') + validateType(alertType = this.type) { + const alertTypes = ['primary', 'success', 'danger', 'warning', 'info']; + checkOneOf(alertType, alertTypes, 'The post-alert requires a type.'); + + alertTypes.forEach(type => this.toggleAlertClass(`alert-${type}`, type === alertType)); + } + + @Watch('fixed') + validateFixed(isFixed = this.fixed) { + checkBoolean(isFixed, 'The post-alert "fixed" prop should be a boolean.'); + + if (isFixed) { + this.toggleAlertClass('alert-fixed-bottom', isFixed); + } + } + + @Watch('dismissible') + validateDismissible(isDismissible = this.dismissible) { + checkBoolean(isDismissible, 'The post-alert "dismissible" prop should be a boolean.'); + + if (isDismissible) { + checkNonEmptyString(this.dismissLabel, 'Dismissible post-alert\'s require a "dismiss-label" prop.'); + } + } + + @Watch('icon') + validateIcon(newValue = this.icon) { + const alertIcon = JSON.parse(newValue); + + this.toggleAlertClass('no-icon', alertIcon === null); + this.removeAlertClass(/^pi-/); + + if (alertIcon) { + const iconNamePattern = /success|warn|info|error-(black|red)|\d{4}/; + checkPattern(alertIcon, iconNamePattern, 'The post-alert "icon" prop should be a 4-digits string.'); + + this.addAlertClass(`pi-${alertIcon}`); + } + } + + componentWillLoad() { + this.validateType(); + this.validateFixed(); + this.validateDismissible(); + this.validateIcon(); + + this.hasHeading = this.host.querySelectorAll('[slot="heading"]').length > 0; + } + + private toggleAlertClass(className: string, force: boolean) { + const classInList = this.alertClasses.includes(className); + + if (classInList && !force) { + this.removeAlertClass(className); + } else if (!classInList && force) { + this.addAlertClass(className); + } + } + + private removeAlertClass(className: string | RegExp) { + this.alertClasses = this.alertClasses.filter(c => { + return typeof className === 'string' ? c === className : !className.test(c); + }); + } + + private addAlertClass(className: string) { + this.alertClasses = [...this.alertClasses, className]; + } + + render() { + return ( + + ); + } + +} diff --git a/packages/components/src/utils/property-checkers.ts b/packages/components/src/utils/property-checkers.ts index 2471b5802b..0bf439e182 100644 --- a/packages/components/src/utils/property-checkers.ts +++ b/packages/components/src/utils/property-checkers.ts @@ -5,3 +5,11 @@ export function checkOneOf(value: T, possibleValues: T[], errorMessage: strin export function checkBoolean(value: unknown, errorMessage: string) { if (typeof value !== 'boolean') throw new Error(errorMessage); } + +export function checkNonEmptyString(value: unknown, errorMessage: string) { + if (typeof value !== 'string' || !value.trim().length) throw new Error(errorMessage); +} + +export function checkPattern(value: string, pattern: RegExp, errorMessage: string) { + if (!pattern.test(value)) throw new Error(errorMessage); +} diff --git a/packages/components/src/utils/tests/property-checkers.spec.ts b/packages/components/src/utils/tests/property-checkers.spec.ts index 120aab4fe2..902e2d2545 100644 --- a/packages/components/src/utils/tests/property-checkers.spec.ts +++ b/packages/components/src/utils/tests/property-checkers.spec.ts @@ -2,7 +2,7 @@ * Copyright 2022 by Swiss Post, Information Technology */ -import { checkBoolean, checkOneOf } from '../property-checkers'; +import { checkBoolean, checkNonEmptyString, checkOneOf, checkPattern } from '../property-checkers'; describe('property-checkers', () => { let errorMessage: string; @@ -51,4 +51,49 @@ describe('property-checkers', () => { }); }); }); + + describe('nonEmptyStringChecker', () => { + beforeEach(() => { + checker = checkNonEmptyString; + errorMessage = 'Is an empty string.'; + }); + + it('should not throw an error if the value is a non-empty string', () => { + expect(runCheckerWithValue('I am a non-empty string.')).not.toThrow(); + }); + + it('should throw the provided error if the value is not a string', () => { + [undefined, null, NaN, 1, true, {}, [], () => {}].forEach((notString) => { + expect(runCheckerWithValue(notString)).toThrow(errorMessage); + }); + }); + + it('should throw the provided error if the value is an empty string', () => { + ['', ' '].forEach((emptyString) => { + expect(runCheckerWithValue(emptyString)).toThrow(errorMessage); + }); + }); + }); + + describe('patternChecker', () => { + beforeEach(() => { + checker = checkPattern; + checkerParameters = [/[a-z]{5}/]; + errorMessage = 'Is an empty string.'; + }); + + it('should not throw an error if the value matches the provided pattern', () => { + expect(runCheckerWithValue('hello')).not.toThrow(); + }); + + it('should throw the provided error if the value is not a string', () => { + [undefined, null, NaN, 1, true, {}, [], () => {}].forEach((notString) => { + expect(runCheckerWithValue(notString)).toThrow(errorMessage); + }); + }); + + it('should throw the provided error if the value does not match the provided pattern', () => { + expect(runCheckerWithValue('WORLD')).toThrow(errorMessage); + }); + }); }); diff --git a/packages/documentation/src/stories/components/post-alert/post-alert.docs.mdx b/packages/documentation/src/stories/components/post-alert/post-alert.docs.mdx new file mode 100644 index 0000000000..6751d3cf71 --- /dev/null +++ b/packages/documentation/src/stories/components/post-alert/post-alert.docs.mdx @@ -0,0 +1,11 @@ +import { Meta, Canvas, Story, Source, ArgsTable } from '@storybook/addon-docs'; + + + +# Alert + + + + + + diff --git a/packages/documentation/src/stories/components/post-alert/post-alert.stories.tsx b/packages/documentation/src/stories/components/post-alert/post-alert.stories.tsx new file mode 100644 index 0000000000..da7d6cd1da --- /dev/null +++ b/packages/documentation/src/stories/components/post-alert/post-alert.stories.tsx @@ -0,0 +1,23 @@ +import { StoryFn } from '@storybook/react'; +import { PostAlert } from '@swisspost/design-system-components-react'; +import { ComponentProps } from 'react'; +import React from 'react'; +import { definedProperties } from '../../../utils'; + +type PostAlertArgs = ComponentProps & { content: string }; + +export default { + title: 'Components/post-alert', + component: PostAlert.displayName, +}; + +const Template: StoryFn = args => ( + +

Well done!

+

Aww yeah, you successfully read this important alert message. This example text is going to run a bit longer so + that you can see how spacing within an alert works with this kind of content.

+
+

Whenever you need to, be sure to use margin utilities to keep things nice and tidy.

+
+); +export const Default = Template.bind({}); From b79b33f79287cf29f21f18b0e07fa0597a434d5d Mon Sep 17 00:00:00 2001 From: debraya Date: Wed, 8 Feb 2023 14:50:19 +0100 Subject: [PATCH 02/27] feat(components): add alert close method --- packages/components/src/components.d.ts | 4 ++ .../src/components/post-alert/post-alert.scss | 1 + .../src/components/post-alert/post-alert.tsx | 49 ++++++++++++++++--- .../post-alert/post-alert.stories.tsx | 1 - 4 files changed, 46 insertions(+), 9 deletions(-) diff --git a/packages/components/src/components.d.ts b/packages/components/src/components.d.ts index 92b6038062..746982fc14 100644 --- a/packages/components/src/components.d.ts +++ b/packages/components/src/components.d.ts @@ -7,6 +7,10 @@ import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; export namespace Components { interface PostAlert { + /** + * Triggers alert closing programmatically (same as clicking on the close button (×)). + */ + "close": () => Promise; /** * The label to use for the close button of a dismissible alert. */ diff --git a/packages/components/src/components/post-alert/post-alert.scss b/packages/components/src/components/post-alert/post-alert.scss index 1f47da3ae6..e5ad0e91d3 100644 --- a/packages/components/src/components/post-alert/post-alert.scss +++ b/packages/components/src/components/post-alert/post-alert.scss @@ -1,5 +1,6 @@ @use '@swisspost/design-system-styles/components/alert'; @use '@swisspost/design-system-styles/components/icons'; +@use '@swisspost/design-system-styles/components/transitions'; :host { display: block; diff --git a/packages/components/src/components/post-alert/post-alert.tsx b/packages/components/src/components/post-alert/post-alert.tsx index ed983a4717..58fbee620d 100644 --- a/packages/components/src/components/post-alert/post-alert.tsx +++ b/packages/components/src/components/post-alert/post-alert.tsx @@ -2,8 +2,8 @@ * Copyright 2023 by Swiss Post, Information Technology */ -import { Component, Element, h, Prop, State, Watch } from '@stencil/core'; -import { checkBoolean, checkNonEmptyString, checkOneOf, checkPattern } from '../../utils'; +import { Component, Element, h, Method, Prop, State, Watch } from '@stencil/core'; +import { checkBoolean, checkNonEmptyString, checkOneOf, checkPattern, onTransitionEnd } from '../../utils'; @Component({ tag: 'post-alert', @@ -41,14 +41,16 @@ export class PostAlert { */ @Prop() icon: string; - @State() alertClasses: string[] = ['alert']; + @State() alertClasses: string[] = [ 'alert' ]; @State() hasHeading: boolean; @Element() host: HTMLElement; + alertElement: HTMLElement; + @Watch('type') validateType(alertType = this.type) { - const alertTypes = ['primary', 'success', 'danger', 'warning', 'info']; + const alertTypes = [ 'primary', 'success', 'danger', 'warning', 'info' ]; checkOneOf(alertType, alertTypes, 'The post-alert requires a type.'); alertTypes.forEach(type => this.toggleAlertClass(`alert-${type}`, type === alertType)); @@ -67,6 +69,9 @@ export class PostAlert { validateDismissible(isDismissible = this.dismissible) { checkBoolean(isDismissible, 'The post-alert "dismissible" prop should be a boolean.'); + const dismissibleClasses = 'alert-dismissible fade show'; + dismissibleClasses.split(' ').forEach(dismissibleClass => this.toggleAlertClass(dismissibleClass, isDismissible)); + if (isDismissible) { checkNonEmptyString(this.dismissLabel, 'Dismissible post-alert\'s require a "dismiss-label" prop.'); } @@ -96,6 +101,21 @@ export class PostAlert { this.hasHeading = this.host.querySelectorAll('[slot="heading"]').length > 0; } + componentDidLoad() { + this.alertElement = this.host.shadowRoot.querySelector('.alert'); + } + + /** + * Triggers alert closing programmatically (same as clicking on the close button (×)). + */ + @Method() + async close() { + this.removeAlertClass('show'); + await onTransitionEnd(this.alertElement).then(() => { + this.host.remove(); + }); + } + private toggleAlertClass(className: string, force: boolean) { const classInList = this.alertClasses.includes(className); @@ -108,19 +128,32 @@ export class PostAlert { private removeAlertClass(className: string | RegExp) { this.alertClasses = this.alertClasses.filter(c => { - return typeof className === 'string' ? c === className : !className.test(c); + return (typeof className === 'string') ? c !== className : !className.test(c); }); } private addAlertClass(className: string) { - this.alertClasses = [...this.alertClasses, className]; + this.alertClasses = [ ...this.alertClasses, className ]; } render() { return ( ); diff --git a/packages/documentation/src/stories/components/post-alert/post-alert.stories.tsx b/packages/documentation/src/stories/components/post-alert/post-alert.stories.tsx index da7d6cd1da..15ab789454 100644 --- a/packages/documentation/src/stories/components/post-alert/post-alert.stories.tsx +++ b/packages/documentation/src/stories/components/post-alert/post-alert.stories.tsx @@ -2,7 +2,6 @@ import { StoryFn } from '@storybook/react'; import { PostAlert } from '@swisspost/design-system-components-react'; import { ComponentProps } from 'react'; import React from 'react'; -import { definedProperties } from '../../../utils'; type PostAlertArgs = ComponentProps & { content: string }; From 07160b16046d8938f27d0497f7492baa19d1b0f1 Mon Sep 17 00:00:00 2001 From: debraya Date: Wed, 8 Feb 2023 15:36:06 +0100 Subject: [PATCH 03/27] feat(components): fix pattern checker and corresponding tests --- packages/components/src/utils/property-checkers.ts | 2 +- packages/components/src/utils/tests/property-checkers.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/src/utils/property-checkers.ts b/packages/components/src/utils/property-checkers.ts index 0bf439e182..9aa2aa25a5 100644 --- a/packages/components/src/utils/property-checkers.ts +++ b/packages/components/src/utils/property-checkers.ts @@ -11,5 +11,5 @@ export function checkNonEmptyString(value: unknown, errorMessage: string) { } export function checkPattern(value: string, pattern: RegExp, errorMessage: string) { - if (!pattern.test(value)) throw new Error(errorMessage); + if (typeof value !== 'string' || !pattern.test(value)) throw new Error(errorMessage); } diff --git a/packages/components/src/utils/tests/property-checkers.spec.ts b/packages/components/src/utils/tests/property-checkers.spec.ts index 902e2d2545..a23f4eb2f1 100644 --- a/packages/components/src/utils/tests/property-checkers.spec.ts +++ b/packages/components/src/utils/tests/property-checkers.spec.ts @@ -79,7 +79,7 @@ describe('property-checkers', () => { beforeEach(() => { checker = checkPattern; checkerParameters = [/[a-z]{5}/]; - errorMessage = 'Is an empty string.'; + errorMessage = 'Does not match pattern.'; }); it('should not throw an error if the value matches the provided pattern', () => { From f89d58758f0579df327cc95c367ed93fa966b54b Mon Sep 17 00:00:00 2001 From: Philipp Gfeller Date: Mon, 20 Feb 2023 11:50:33 +0100 Subject: [PATCH 04/27] chore: update lockfile and add generated docs file --- .../src/components/post-alert/readme.md | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 packages/components/src/components/post-alert/readme.md diff --git a/packages/components/src/components/post-alert/readme.md b/packages/components/src/components/post-alert/readme.md new file mode 100644 index 0000000000..83c57eaa95 --- /dev/null +++ b/packages/components/src/components/post-alert/readme.md @@ -0,0 +1,34 @@ +# post-alert + + + + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| -------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------- | --------- | ----------- | +| `dismissLabel` | `dismiss-label` | The label to use for the close button of a dismissible alert. | `string` | `undefined` | +| `dismissible` | `dismissible` | If `true`, a close button (×) is displayed and the alert can be dismissed by the user. | `boolean` | `false` | +| `fixed` | `fixed` | If `true`, the alert is positioned at the bottom of the window, from edge to edge. | `boolean` | `false` | +| `icon` | `icon` | The icon to display in the alert. If `null`, no icon will be displayed. By default, the icon depends on the alert type. | `string` | `undefined` | +| `type` | `type` | The type of the alert. We provide styles for the following types: `'primary'`, `'success'`, `'danger'`, `'warning'`, `'info'`. | `string` | `'primary'` | + + +## Methods + +### `close() => Promise` + +Triggers alert closing programmatically (same as clicking on the close button (×)). + +#### Returns + +Type: `Promise` + + + + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* From d0e4a3b134951ce3c083e104bf2e8ac502750960 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliz=C3=A9=20Debray?= Date: Mon, 11 Sep 2023 15:04:59 +0200 Subject: [PATCH 05/27] fix alert height --- packages/styles/src/mixins/_notification.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/styles/src/mixins/_notification.scss b/packages/styles/src/mixins/_notification.scss index 32770e6ffe..5e474b7146 100644 --- a/packages/styles/src/mixins/_notification.scss +++ b/packages/styles/src/mixins/_notification.scss @@ -17,6 +17,7 @@ $hr-margin-block: map.get(notification.$notification-hr-margin-block-map, $size); position: relative; + box-sizing: border-box; min-height: 2 * $padding-y + $icon-size; box-shadow: notification.$notification-box-shadow; border-radius: notification.$notification-border-radius; From 13a13ec70af445d7bc742dddace9c8b028939fd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliz=C3=A9=20Debray?= Date: Mon, 11 Sep 2023 15:39:12 +0200 Subject: [PATCH 06/27] add host element --- .../src/components/post-alert/post-alert.tsx | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/packages/components/src/components/post-alert/post-alert.tsx b/packages/components/src/components/post-alert/post-alert.tsx index 63ebfd314f..d43b82b266 100644 --- a/packages/components/src/components/post-alert/post-alert.tsx +++ b/packages/components/src/components/post-alert/post-alert.tsx @@ -1,4 +1,5 @@ -import { Component, Element, h, Method, Prop, State, Watch } from '@stencil/core'; +import { Component, Element, h, Host, Method, Prop, State, Watch } from '@stencil/core'; +import { version } from '../../../package.json'; import { checkNonEmpty, checkOneOf, checkPattern, checkType, onTransitionEnd } from '../../utils'; @Component({ @@ -135,24 +136,26 @@ export class PostAlert { render() { return ( - + + + ); } From cf260817f9f89a7292387cd254def15c17500963 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliz=C3=A9=20Debray?= Date: Mon, 11 Sep 2023 16:17:33 +0200 Subject: [PATCH 07/27] update story args --- .../src/components/post-alert/post-alert.tsx | 2 +- .../post-alert/post-alert.stories.ts | 36 +++++++++++++++++-- .../documentation/src/utils/get-attributes.ts | 16 +++++++++ packages/documentation/src/utils/index.ts | 1 + 4 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 packages/documentation/src/utils/get-attributes.ts diff --git a/packages/components/src/components/post-alert/post-alert.tsx b/packages/components/src/components/post-alert/post-alert.tsx index d43b82b266..7d7b7d22cf 100644 --- a/packages/components/src/components/post-alert/post-alert.tsx +++ b/packages/components/src/components/post-alert/post-alert.tsx @@ -77,7 +77,7 @@ export class PostAlert { @Watch('icon') validateIcon(newValue = this.icon) { - const alertIcon = JSON.parse(newValue); + const alertIcon = newValue === 'null' ? null : newValue; this.toggleAlertClass('no-icon', alertIcon === null); this.removeAlertClass(/^pi-/); diff --git a/packages/documentation/src/stories/components/post-alert/post-alert.stories.ts b/packages/documentation/src/stories/components/post-alert/post-alert.stories.ts index 0340f93e07..ac18361e0f 100644 --- a/packages/documentation/src/stories/components/post-alert/post-alert.stories.ts +++ b/packages/documentation/src/stories/components/post-alert/post-alert.stories.ts @@ -1,6 +1,8 @@ import { Meta, StoryObj } from '@storybook/web-components'; import { html } from 'lit'; import { BADGE } from '../../../../.storybook/constants'; +import { spread } from '@open-wc/lit-helpers'; +import { definedProperties, getAttributes } from '../../../utils'; const meta: Meta = { title: 'Components/Post Alert', @@ -9,14 +11,44 @@ const meta: Meta = { parameters: { badges: [BADGE.NEEDS_REVISION], }, + args: { + dismissLabel: 'Dismiss' + }, + argTypes: { + dismissLabel: { + if: { + arg: 'dismissible', + }, + }, + icon: { + control: { + type: 'select', + labels: { + 'null': 'No Icon', + '1001': 'Envelope (1001)', + '2023': 'Cog (2023)', + '2025': 'Send (2025)', + '2035': 'Home (2035)', + '2101': 'Bubble (2101)', + }, + }, + options: ['null', '1001', '2023', '2025', '2035', '2101'], + }, + type: { + control: { + type: 'select', + }, + options: [ 'primary', 'success', 'danger', 'warning', 'info' ], + } + } }; export default meta; // RENDERER -function renderAlert() { +function renderAlert(args: Partial) { return html` - +

Titulum

Contentus momentus vero siteos et accusam iretea et justo.
diff --git a/packages/documentation/src/utils/get-attributes.ts b/packages/documentation/src/utils/get-attributes.ts new file mode 100644 index 0000000000..bc03fb3693 --- /dev/null +++ b/packages/documentation/src/utils/get-attributes.ts @@ -0,0 +1,16 @@ +import { Args } from '@storybook/web-components'; + +export function getAttributes( + args: Args, + condition: (arg: Args) => boolean = (args: Args) => !!args +): Args { + return Object.fromEntries( + Object + .entries(args) + .filter(([_key, value]) => condition(value)) + .map(([key, value]) => [ + key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(), + typeof value === 'object' ? JSON.stringify(value) : value + ]) + ); +} diff --git a/packages/documentation/src/utils/index.ts b/packages/documentation/src/utils/index.ts index 540d902d4f..72410b3996 100644 --- a/packages/documentation/src/utils/index.ts +++ b/packages/documentation/src/utils/index.ts @@ -1,2 +1,3 @@ export * from './component-properties'; +export * from './get-attributes'; export * from './map-classes'; From 6fe2cdb180ad9e4c57e996ea30b925ab8cfbc944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliz=C3=A9=20Debray?= Date: Tue, 12 Sep 2023 09:33:13 +0200 Subject: [PATCH 08/27] use post-icons --- packages/components/src/components.d.ts | 8 ++-- .../src/components/post-alert/post-alert.tsx | 23 +++++------ .../src/components/post-alert/readme.md | 27 +++++++++---- .../src/components/post-icon/readme.md | 13 +++++++ .../post-alert/post-alert.stories.ts | 4 +- packages/styles/src/mixins/_color.scss | 9 ++++- packages/styles/src/mixins/_notification.scss | 38 +++++++++++++------ 7 files changed, 82 insertions(+), 40 deletions(-) diff --git a/packages/components/src/components.d.ts b/packages/components/src/components.d.ts index 913e37f629..c5a28796e7 100644 --- a/packages/components/src/components.d.ts +++ b/packages/components/src/components.d.ts @@ -24,9 +24,9 @@ export namespace Components { */ "fixed": false; /** - * The icon to display in the alert. If `null`, no icon will be displayed. By default, the icon depends on the alert type. + * The icon to display in the alert. If `true`, the icon depends on the alert type. If `false`, no icon is displayed. */ - "icon": string | null; + "icon": boolean | string; /** * The type of the alert. We provide styles for the following types: `'primary'`, `'success'`, `'danger'`, `'warning'`, `'info'`. */ @@ -170,9 +170,9 @@ declare namespace LocalJSX { */ "fixed"?: false; /** - * The icon to display in the alert. If `null`, no icon will be displayed. By default, the icon depends on the alert type. + * The icon to display in the alert. If `true`, the icon depends on the alert type. If `false`, no icon is displayed. */ - "icon"?: string | null; + "icon"?: boolean | string; /** * The type of the alert. We provide styles for the following types: `'primary'`, `'success'`, `'danger'`, `'warning'`, `'info'`. */ diff --git a/packages/components/src/components/post-alert/post-alert.tsx b/packages/components/src/components/post-alert/post-alert.tsx index 7d7b7d22cf..ccf3c543e2 100644 --- a/packages/components/src/components/post-alert/post-alert.tsx +++ b/packages/components/src/components/post-alert/post-alert.tsx @@ -14,6 +14,7 @@ export class PostAlert { @State() alertClasses: string[] = [ 'alert' ]; @State() hasHeading: boolean; + @State() iconName: string | null; /** * The label to use for the close button of a dismissible alert. @@ -70,23 +71,18 @@ export class PostAlert { /** * The icon to display in the alert. * - * If `null`, no icon will be displayed. - * By default, the icon depends on the alert type. + * If `true`, the icon depends on the alert type. + * If `false`, no icon is displayed. */ - @Prop() readonly icon: string | null; + @Prop() readonly icon: boolean | string = true; @Watch('icon') - validateIcon(newValue = this.icon) { - const alertIcon = newValue === 'null' ? null : newValue; + validateIcon(alertIcon = this.icon) { + this.toggleAlertClass('no-icon', alertIcon === 'false'); - this.toggleAlertClass('no-icon', alertIcon === null); - this.removeAlertClass(/^pi-/); - - if (alertIcon) { - const iconNamePattern = /success|warn|info|error-(black|red)|\d{4}/; - checkPattern(alertIcon, iconNamePattern, 'The post-alert "icon" prop should be a 4-digits string.'); - - this.addAlertClass(`pi-${alertIcon}`); + this.iconName = typeof alertIcon === 'string' && !['true', 'false'].includes(alertIcon) ? alertIcon : null; + if (this.iconName) { + checkPattern(alertIcon, /true|false|\d{4}/, 'The post-alert "icon" prop should be a 4-digits string.'); } } @@ -138,6 +134,7 @@ export class PostAlert { return ( ); diff --git a/packages/components/src/components/post-alert/readme.md b/packages/components/src/components/post-alert/readme.md index fe47b0e8ca..fef0312e51 100644 --- a/packages/components/src/components/post-alert/readme.md +++ b/packages/components/src/components/post-alert/readme.md @@ -7,20 +7,20 @@ ## Properties -| Property | Attribute | Description | Type | Default | -| -------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------- | ----------- | -| `dismissLabel` | `dismiss-label` | The label to use for the close button of a dismissible alert. | `string` | `undefined` | -| `dismissible` | `dismissible` | If `true`, a close button (×) is displayed and the alert can be dismissed by the user. | `boolean` | `false` | -| `fixed` | `fixed` | If `true`, the alert is positioned at the bottom of the window, from edge to edge. | `boolean` | `false` | -| `icon` | `icon` | The icon to display in the alert. If `true`, the icon depends on the alert type. If `false`, no icon is displayed. | `boolean \| string` | `true` | -| `type` | `type` | The type of the alert. We provide styles for the following types: `'primary'`, `'success'`, `'danger'`, `'warning'`, `'info'`. | `string` | `'primary'` | +| Property | Attribute | Description | Type | Default | +| -------------- | --------------- | ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | ----------- | +| `dismissLabel` | `dismiss-label` | The label to use for the close button of a dismissible alert. | `string` | `undefined` | +| `dismissible` | `dismissible` | If `true`, a close button (×) is displayed and the alert can be dismissed by the user. | `boolean` | `false` | +| `fixed` | `fixed` | If `true`, the alert is positioned at the bottom of the window, from edge to edge. | `boolean` | `false` | +| `icon` | `icon` | The icon to display in the alert. By default, the icon depends on the alert type. If `none`, no icon is displayed. | `string` | `undefined` | +| `type` | `type` | The type of the alert. | `"danger" \| "gray" \| "info" \| "primary" \| "success" \| "warning"` | `'primary'` | ## Methods -### `close() => Promise` +### `dismiss() => Promise` -Triggers alert closing programmatically (same as clicking on the close button (×)). +Triggers alert dismissal programmatically (same as clicking on the close button (×)). #### Returns diff --git a/packages/components/src/utils/property-checkers/check-one-of.ts b/packages/components/src/utils/property-checkers/check-one-of.ts index b14c755cdd..68c19df699 100644 --- a/packages/components/src/utils/property-checkers/check-one-of.ts +++ b/packages/components/src/utils/property-checkers/check-one-of.ts @@ -1,3 +1,3 @@ -export function checkOneOf(value: T, possibleValues: T[], error: string) { +export function checkOneOf(value: T, possibleValues: readonly T[], error: string) { if (!possibleValues.includes(value)) throw new Error(error); } diff --git a/packages/documentation/src/stories/components/post-alert/post-alert.stories.ts b/packages/documentation/src/stories/components/post-alert/post-alert.stories.ts index f62fb29d7b..e52eeed2f4 100644 --- a/packages/documentation/src/stories/components/post-alert/post-alert.stories.ts +++ b/packages/documentation/src/stories/components/post-alert/post-alert.stories.ts @@ -1,13 +1,13 @@ -import { Meta, StoryObj } from '@storybook/web-components'; +import { Meta, StoryContext, StoryFn, StoryObj } from '@storybook/web-components'; import { html } from 'lit'; +import { ifDefined } from 'lit/directives/if-defined.js'; import { BADGE } from '../../../../.storybook/constants'; -import { spread } from '@open-wc/lit-helpers'; -import { definedProperties, getAttributes } from '../../../utils'; const meta: Meta = { title: 'Components/Post Alert', component: 'post-alert', render: renderAlert, + decorators: [externalControl], parameters: { badges: [BADGE.NEEDS_REVISION], }, @@ -24,33 +24,76 @@ const meta: Meta = { control: { type: 'select', labels: { - 'false': 'No Icon', - '1001': 'Envelope (1001)', - '2023': 'Cog (2023)', - '2025': 'Send (2025)', - '2035': 'Home (2035)', - '2101': 'Bubble (2101)', + '1001': '1001 (Envelope)', + '2023': '2023 (Cog)', + '2025': '2025 (Send)', + '2035': '2035 (Home)', + '2101': '2101 (Bubble)', }, }, - options: ['false', '1001', '2023', '2025', '2035', '2101'], + options: ['none', '1001', '2023', '2025', '2035', '2101'], }, - type: { - control: { - type: 'select', - }, - options: [ 'primary', 'success', 'danger', 'warning', 'info' ], - } } }; export default meta; +// DECORATORS +function externalControl(story: StoryFn, { args, context }: StoryContext) { + let alert: HTMLPostAlertElement; + + const toggleButton = html` + + ${args.fixed ? 'Toggle Alert' : 'Show Alert'} + + `; + + const showAlert = (e: Event) => { + e.preventDefault(); + if (alert.parentNode) { + void alert.dismiss(); + } else { + document.getElementById('alert-container')?.appendChild(alert); + } + } + + setTimeout(() => { + alert = document.querySelector('post-alert') as HTMLPostAlertElement; + }); + + return args.fixed + ? html` + ${toggleButton} + ${story(args, context)} + ` + : html` +
+ ${toggleButton} +
+ ${story(args, context)} +
+
+ `; +} + // RENDERER function renderAlert(args: Partial) { return html` - +

Titulum

Contentus momentus vero siteos et accusam iretea et justo. + +
`; } diff --git a/packages/styles/src/components/alert.scss b/packages/styles/src/components/alert.scss index 86ae905eac..28f303fb19 100644 --- a/packages/styles/src/components/alert.scss +++ b/packages/styles/src/components/alert.scss @@ -45,10 +45,7 @@ flex: 0 0 auto; display: flex; align-items: center; - - > .btn { - margin-inline-start: notification.$notification-buttons-margin-start; - } + gap: notification.$notification-buttons-gap; } @include media-breakpoint-up(md) { diff --git a/packages/styles/src/variables/components/_notification.scss b/packages/styles/src/variables/components/_notification.scss index 969ad320b0..8868a8e85f 100644 --- a/packages/styles/src/variables/components/_notification.scss +++ b/packages/styles/src/variables/components/_notification.scss @@ -11,7 +11,7 @@ $notification-body-gap: spacing.$size-micro !default; $notification-font-weight: type.$font-weight-light !default; $notification-link-font-weight: type.$font-weight-normal !default; $notification-heading-font-weight: type.$headings-font-weight !default; -$notification-buttons-margin-start: spacing.$size-mini !default; +$notification-buttons-gap: spacing.$size-mini !default; $notification-padding-x-map: () !default; $notification-padding-x-map: map-merge( @@ -82,3 +82,6 @@ $notification-variants: join( 'error' color.$error 2104 ) ); + +// deprecated +$notification-buttons-margin-start: $notification-buttons-gap !default; From 3bcdd5970154a81dc1802a8b99baa0dbb6327e8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliz=C3=A9=20Debray?= Date: Mon, 25 Sep 2023 08:45:07 +0200 Subject: [PATCH 10/27] Write post-alert docs --- packages/components/src/components.d.ts | 8 +- .../src/components/post-alert/post-alert.scss | 5 +- .../stories/components/alert/alert.docs.mdx | 118 +++++++++++---- .../alert/alert.snapshot.stories.ts | 4 +- .../{ => standard-html}/alert.stories.ts | 2 +- .../{ => standard-html}/getAlertClasses.ts | 30 ++-- .../web-component/alert-dismiss.sample.ts | 3 + .../alert/web-component/post-alert.stories.ts | 135 ++++++++++++++++++ .../components/post-alert/post-alert.docs.mdx | 9 -- .../post-alert/post-alert.stories.ts | 104 -------------- packages/documentation/src/utils/index.ts | 1 + .../documentation/src/utils/spread-args.ts | 68 +++++++++ 12 files changed, 327 insertions(+), 160 deletions(-) rename packages/documentation/src/stories/components/alert/{ => standard-html}/alert.stories.ts (99%) rename packages/documentation/src/stories/components/alert/{ => standard-html}/getAlertClasses.ts (96%) create mode 100644 packages/documentation/src/stories/components/alert/web-component/alert-dismiss.sample.ts create mode 100644 packages/documentation/src/stories/components/alert/web-component/post-alert.stories.ts delete mode 100644 packages/documentation/src/stories/components/post-alert/post-alert.docs.mdx delete mode 100644 packages/documentation/src/stories/components/post-alert/post-alert.stories.ts create mode 100644 packages/documentation/src/utils/spread-args.ts diff --git a/packages/components/src/components.d.ts b/packages/components/src/components.d.ts index 1839899ba6..1566173ce8 100644 --- a/packages/components/src/components.d.ts +++ b/packages/components/src/components.d.ts @@ -20,11 +20,11 @@ export namespace Components { /** * If `true`, a close button (×) is displayed and the alert can be dismissed by the user. */ - "dismissible": false; + "dismissible": boolean; /** * If `true`, the alert is positioned at the bottom of the window, from edge to edge. */ - "fixed": false; + "fixed": boolean; /** * The icon to display in the alert. By default, the icon depends on the alert type. If `none`, no icon is displayed. */ @@ -166,11 +166,11 @@ declare namespace LocalJSX { /** * If `true`, a close button (×) is displayed and the alert can be dismissed by the user. */ - "dismissible"?: false; + "dismissible"?: boolean; /** * If `true`, the alert is positioned at the bottom of the window, from edge to edge. */ - "fixed"?: false; + "fixed"?: boolean; /** * The icon to display in the alert. By default, the icon depends on the alert type. If `none`, no icon is displayed. */ diff --git a/packages/components/src/components/post-alert/post-alert.scss b/packages/components/src/components/post-alert/post-alert.scss index aa9a9a9de5..205dc99a9a 100644 --- a/packages/components/src/components/post-alert/post-alert.scss +++ b/packages/components/src/components/post-alert/post-alert.scss @@ -4,6 +4,10 @@ :host { display: block; + + ::slotted(*) { + margin: 0 !important; + } } .btn-close > .visually-hidden { @@ -14,6 +18,5 @@ .alert-heading > ::slotted(h#{$i}) { font-size: inherit !important; font-weight: inherit !important; - margin: 0 !important; } } diff --git a/packages/documentation/src/stories/components/alert/alert.docs.mdx b/packages/documentation/src/stories/components/alert/alert.docs.mdx index 571d0a110a..dea5b0aecd 100644 --- a/packages/documentation/src/stories/components/alert/alert.docs.mdx +++ b/packages/documentation/src/stories/components/alert/alert.docs.mdx @@ -1,6 +1,10 @@ -import { Canvas, Controls, Meta } from '@storybook/blocks'; -import * as AlertStories from './alert.stories'; +import { Canvas, Controls, Meta, Source } from '@storybook/blocks'; +import LinkTo from '@storybook/addon-links/react'; +import { PostAlert, PostTabs, PostTabHeader, PostTabPanel } from '@swisspost/design-system-components-react'; import StylesPackageImport from '../../../shared/styles-package-import.mdx'; +import * as AlertStories from './standard-html/alert.stories'; +import * as PostAlertStories from './web-component/post-alert.stories'; +import AlertDismissSample from './web-component/alert-dismiss.sample?raw'; @@ -14,41 +18,107 @@ import StylesPackageImport from '../../../shared/styles-package-import.mdx'; Alerts are available for any length of text, as well as an optional close button. By default, alerts are rendered with a contextually appropriate icon. - - + +

There are various methods to integrate this component into your project.

+

We advise opting for the "Standard HTML" approach for alerts that remain static on the page and using the "Web Component" method for dismissible alerts.

+
- + + Standard HTML + + + -## Specific examples of application + -The following examples show some of the different characteristics of the component. These can differ in the type of visualization, the HTML structure, as well as when, how and why they are displayed. + ## Specific examples of application -### Additional Content + The following examples show some of the different characteristics of the component. These can differ in the type of visualization, the HTML structure, as well as when, how and why they are displayed. -Alerts can also contain additional HTML elements like headings, paragraphs, lists, dividers, and more. + ### Additional Content - + Alerts can also contain additional HTML elements like headings, paragraphs, lists, dividers, and more. -### Dismissible + -Such an alert can be removed from the UI by the user, by clicking on a dismiss button. -This button should normally be inside the alert (as the first child), but you can also use a button which is not included in the alert's HTML. -In the end, it's just a button with an `onClick()` event. As this is a CSS-only component, you have to define the logic yourself. + ### Dismissible -Dismiss buttons without text should always provide a `dismiss-label` prop, which defines the hidden text associated with the button. + Such an alert can be removed from the UI by the user, by clicking on a dismiss button. + This button should normally be inside the alert (as the first child), but you can also use a button which is not included in the alert's HTML. + In the end, it's just a button with an `onClick()` event. As this is a CSS-only component, you have to define the logic yourself. - + Dismiss buttons without text should always provide a `dismiss-label` prop, which defines the hidden text associated with the button. -### Action Buttons + -To render an action alert with buttons, the alert element requires an additional class as well as additional HTML content. Thus, everything will be rendered as it should be. + ### Action Buttons - + To render an action alert with buttons, the alert element requires an additional class as well as additional HTML content. Thus, everything will be rendered as it should be. -### Fixed + -A fixed alert (at the bottom of the viewport) is obtained by adding the class `.alert-fixed-bottom` to the alert element. -As this is a CSS-only component, you have to define the logic to show/hide/toggle the alert yourself. -Such an alert is normally also dismissible, so please notice the information for a dismissible alert as well. + ### Fixed - + A fixed alert (at the bottom of the viewport) is obtained by adding the class `.alert-fixed-bottom` to the alert element. + As this is a CSS-only component, you have to define the logic to show/hide/toggle the alert yourself. + Such an alert is normally also dismissible, so please notice the information for a dismissible alert as well. + + + + + Web Component + + + + + ## Installation + + The `` element is part of the `@swisspost/design-system-components` package. + For more information, read the getting started with components guide. + + ## Examples + + ### Contents + + Alerts can contain various HTML elements like paragraphs, lists, icons and dividers. + + By default all children of the `` are placed in the body. + Use the `heading` slot to place a child in the heading, and the `actions` slot for action buttons. + Learn more in Mozilla's slots documentation. + + + + ### Custom Icon + + Alerts come with a preassigned icon based on their type. + You have the option to customize this icon by assigning the desired icon's name to the `icon` property of the alert. + Find the icon you need with the icon search page. + + + + If you prefer not to display any icon, you can set the `icon` property to `none`. + + + + ### Dismissal + + The `dismissible` property can be set to enable users to dismiss the alert. + When present, it specifies that the alert should contain a dismiss button, + and you must provide a label for this button using the `dismiss-label` property. + Although the label remains hidden from view, it is essential for ensuring accessibility for users of assistive technologies. + + + + Alternatively, you can use any button, including action buttons within the alert, as a trigger for dismissing the alert using the `.dismiss()` method. + This method operates asynchronously and returns a promise that resolves once the fade-out animation has finished. + + + + ### Banner + + To present an alert as a banner anchored at the bottom of the page, simply add the `fixed` property. + Such alerts are typically intended to be dismissible. + + + + diff --git a/packages/documentation/src/stories/components/alert/alert.snapshot.stories.ts b/packages/documentation/src/stories/components/alert/alert.snapshot.stories.ts index 9a565013f9..be97705d59 100644 --- a/packages/documentation/src/stories/components/alert/alert.snapshot.stories.ts +++ b/packages/documentation/src/stories/components/alert/alert.snapshot.stories.ts @@ -1,8 +1,8 @@ import { Args, StoryObj } from '@storybook/web-components'; import { html } from 'lit'; import { bombArgs } from '../../utilities/bombArgs'; -import alertMeta from './alert.stories'; -import { getAlertClasses } from './getAlertClasses'; +import alertMeta from './standard-html/alert.stories'; +import { getAlertClasses } from './standard-html/getAlertClasses'; export default { ...alertMeta, diff --git a/packages/documentation/src/stories/components/alert/alert.stories.ts b/packages/documentation/src/stories/components/alert/standard-html/alert.stories.ts similarity index 99% rename from packages/documentation/src/stories/components/alert/alert.stories.ts rename to packages/documentation/src/stories/components/alert/standard-html/alert.stories.ts index 04e599b209..581210f8f8 100644 --- a/packages/documentation/src/stories/components/alert/alert.stories.ts +++ b/packages/documentation/src/stories/components/alert/standard-html/alert.stories.ts @@ -4,7 +4,7 @@ import { Args, Meta, StoryContext, StoryFn, StoryObj } from '@storybook/web-comp import { html, nothing } from 'lit'; import { unsafeHTML } from 'lit/directives/unsafe-html.js'; import { getAlertClasses } from './getAlertClasses'; -import { BADGE } from '../../../../.storybook/constants'; +import { BADGE } from '../../../../../.storybook/constants'; const meta: Meta = { title: 'Components/Alert', diff --git a/packages/documentation/src/stories/components/alert/getAlertClasses.ts b/packages/documentation/src/stories/components/alert/standard-html/getAlertClasses.ts similarity index 96% rename from packages/documentation/src/stories/components/alert/getAlertClasses.ts rename to packages/documentation/src/stories/components/alert/standard-html/getAlertClasses.ts index dee36d623a..44f0898cee 100644 --- a/packages/documentation/src/stories/components/alert/getAlertClasses.ts +++ b/packages/documentation/src/stories/components/alert/standard-html/getAlertClasses.ts @@ -1,15 +1,15 @@ -import { Args } from '@storybook/web-components'; - -export const getAlertClasses = (args: Args): string => - [ - 'alert', - args.variant, - args.icon !== 'null' ? `pi-${args.icon}` : '', - args.noIcon ? 'no-icon' : '', - args.dismissible ? 'alert-dismissible' : '', - args.fixed ? 'alert-fixed-bottom' : '', - args.action ? 'alert-action' : '', - args.show ? '' : 'd-none', - ] - .filter(c => c) - .join(' '); +import { Args } from '@storybook/web-components'; + +export const getAlertClasses = (args: Args): string => + [ + 'alert', + args.variant, + args.icon !== 'null' ? `pi-${args.icon}` : '', + args.noIcon ? 'no-icon' : '', + args.dismissible ? 'alert-dismissible' : '', + args.fixed ? 'alert-fixed-bottom' : '', + args.action ? 'alert-action' : '', + args.show ? '' : 'd-none', + ] + .filter(c => c) + .join(' '); diff --git a/packages/documentation/src/stories/components/alert/web-component/alert-dismiss.sample.ts b/packages/documentation/src/stories/components/alert/web-component/alert-dismiss.sample.ts new file mode 100644 index 0000000000..520e575729 --- /dev/null +++ b/packages/documentation/src/stories/components/alert/web-component/alert-dismiss.sample.ts @@ -0,0 +1,3 @@ +const alert = document.getElementById('alertId') as HTMLPostAlertElement; + +alert.dismiss().then(() => {}); diff --git a/packages/documentation/src/stories/components/alert/web-component/post-alert.stories.ts b/packages/documentation/src/stories/components/alert/web-component/post-alert.stories.ts new file mode 100644 index 0000000000..b375253337 --- /dev/null +++ b/packages/documentation/src/stories/components/alert/web-component/post-alert.stories.ts @@ -0,0 +1,135 @@ +import { Meta, StoryContext, StoryFn, StoryObj } from '@storybook/web-components'; +import { html } from 'lit'; +import { BADGE } from '../../../../../.storybook/constants'; +import { spreadArgs } from '../../../../utils'; + +const meta: Meta = { + title: 'Components/Post Alert', + component: 'post-alert', + render: renderAlert, + decorators: [externalControl], + parameters: { + badges: [BADGE.NEEDS_REVISION], + }, + args: { + dismissible: false, + dismissLabel: 'Dismiss', + fixed: false, + innerHTML: '

Contentus momentus vero siteos et accusam iretea et justo.

', + }, + argTypes: { + dismissLabel: { + if: { + arg: 'dismissible', + }, + }, + icon: { + control: { + type: 'select', + labels: { + '1001': '1001 (Envelope)', + '2023': '2023 (Cog)', + '2025': '2025 (Send)', + '2035': '2035 (Home)', + '2101': '2101 (Bubble)', + }, + }, + options: ['none', '1001', '2023', '2025', '2035', '2101'], + }, + } +}; + +export default meta; + +// DECORATORS +function externalControl(story: StoryFn, context: StoryContext) { + const {args, canvasElement} = context; + let alert: HTMLPostAlertElement; + + const toggleButton = html` + Show Alert + `; + + const toggleAlert = (e?: Event) => { + if (e) e.preventDefault(); + + const alertButton = canvasElement.querySelector('#alert-button') as HTMLElement; + const alertContainer = canvasElement.querySelector('#alert-container') as HTMLElement; + + if (alert.parentNode) { + void alert.dismiss(); + alertButton.textContent = 'Show Alert'; + } else { + alertContainer.appendChild(alert); + alertButton.textContent = 'Hide Alert'; + } + } + + setTimeout(() => { + alert = canvasElement.querySelector('post-alert') as HTMLPostAlertElement; + if (args.fixed) void alert.remove(); + }); + + return html` +
+ ${toggleButton} +
+ ${story(args, context)} +
+
+ `; +} + + + +// RENDERER +function renderAlert(args: Partial) { + return html` + + `; +} + +// STORIES +type Story = StoryObj; + +export const Default: Story = {}; + +export const Contents: Story = { + args: { + innerHTML: + '

Titulum

' + + '
    ' + + '
  • Un orde redlis titem
  • ' + + '
  • An deven moreun orde redlis titem
  • ' + + '
' + + '
' + + '

Contentum momentum ipsum tipsum sit amet, consetetur sadipscing elitr.

' + + '' + + '', + } +} + +export const CustomIcon: Story = { + args: { + icon: '1001', + } +}; + +export const NoIcon: Story = { + args: { + icon: 'none', + } +}; + +export const Dismissible: Story = { + args: { + dismissible: true, + } +}; + +export const Fixed: Story = { + args: { + dismissible: true, + fixed: true, + } +}; diff --git a/packages/documentation/src/stories/components/post-alert/post-alert.docs.mdx b/packages/documentation/src/stories/components/post-alert/post-alert.docs.mdx deleted file mode 100644 index 11c2708c0f..0000000000 --- a/packages/documentation/src/stories/components/post-alert/post-alert.docs.mdx +++ /dev/null @@ -1,9 +0,0 @@ -import { Canvas, Controls, Meta } from '@storybook/blocks'; -import * as PostAlertStories from './post-alert.stories'; - - - -# Alert - - - diff --git a/packages/documentation/src/stories/components/post-alert/post-alert.stories.ts b/packages/documentation/src/stories/components/post-alert/post-alert.stories.ts deleted file mode 100644 index e52eeed2f4..0000000000 --- a/packages/documentation/src/stories/components/post-alert/post-alert.stories.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { Meta, StoryContext, StoryFn, StoryObj } from '@storybook/web-components'; -import { html } from 'lit'; -import { ifDefined } from 'lit/directives/if-defined.js'; -import { BADGE } from '../../../../.storybook/constants'; - -const meta: Meta = { - title: 'Components/Post Alert', - component: 'post-alert', - render: renderAlert, - decorators: [externalControl], - parameters: { - badges: [BADGE.NEEDS_REVISION], - }, - args: { - dismissLabel: 'Dismiss' - }, - argTypes: { - dismissLabel: { - if: { - arg: 'dismissible', - }, - }, - icon: { - control: { - type: 'select', - labels: { - '1001': '1001 (Envelope)', - '2023': '2023 (Cog)', - '2025': '2025 (Send)', - '2035': '2035 (Home)', - '2101': '2101 (Bubble)', - }, - }, - options: ['none', '1001', '2023', '2025', '2035', '2101'], - }, - } -}; - -export default meta; - -// DECORATORS -function externalControl(story: StoryFn, { args, context }: StoryContext) { - let alert: HTMLPostAlertElement; - - const toggleButton = html` - - ${args.fixed ? 'Toggle Alert' : 'Show Alert'} - - `; - - const showAlert = (e: Event) => { - e.preventDefault(); - if (alert.parentNode) { - void alert.dismiss(); - } else { - document.getElementById('alert-container')?.appendChild(alert); - } - } - - setTimeout(() => { - alert = document.querySelector('post-alert') as HTMLPostAlertElement; - }); - - return args.fixed - ? html` - ${toggleButton} - ${story(args, context)} - ` - : html` -
- ${toggleButton} -
- ${story(args, context)} -
-
- `; -} - -// RENDERER -function renderAlert(args: Partial) { - return html` - -

Titulum

- Contentus momentus vero siteos et accusam iretea et justo. - - -
- `; -} - -// STORIES -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/packages/documentation/src/utils/index.ts b/packages/documentation/src/utils/index.ts index 72410b3996..fc4ca0d03f 100644 --- a/packages/documentation/src/utils/index.ts +++ b/packages/documentation/src/utils/index.ts @@ -1,3 +1,4 @@ export * from './component-properties'; export * from './get-attributes'; export * from './map-classes'; +export * from './spread-args'; diff --git a/packages/documentation/src/utils/spread-args.ts b/packages/documentation/src/utils/spread-args.ts new file mode 100644 index 0000000000..40ffa7d228 --- /dev/null +++ b/packages/documentation/src/utils/spread-args.ts @@ -0,0 +1,68 @@ +import { Args } from '@storybook/web-components'; +import { ElementPart, nothing } from 'lit'; +import { AsyncDirective, directive } from 'lit-html/async-directive.js'; + +type Props = Partial; +type Attrs = Record; + +function formatAttrs(argKey: string, argValue: any): Attrs { + // key to kebab case + const attrKey = argKey.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); + + // value to string + switch (typeof argValue) { + case 'object': + case 'function': + return { [attrKey]: JSON.stringify(argValue) }; + case 'boolean': + return { [attrKey]: argValue ? '' : null }; + default: + return { [attrKey]: argValue ?? '' }; + } +} + +export class SpreadArgsDirective extends AsyncDirective { + prevAttrs: Attrs = {}; + + render(_args: Args) { + return nothing; + } + + update(part: ElementPart, [args]: Parameters) { + const element = part.element as T; + + const props: Props = {}; + const attrs: Attrs = {}; + + Object + .entries(args) + .forEach(([argKey, argValue]) => { + if (argKey in HTMLElement.prototype) { + Object.assign(props, {[argKey]: argValue}); + } else { + Object.assign(attrs, formatAttrs(argKey, argValue)); + } + }); + + // remove previously set attributes + Object + .keys(this.prevAttrs) + .forEach(attrKey => element.removeAttribute(attrKey)); + + // set new attributes + Object + .entries(attrs) + .filter(([_key, value]) => value !== null) + .forEach(([attrKey, attrValue]) => element.setAttribute(attrKey, attrValue ?? '')); + + // set properties + Object + .entries(args) + .forEach(([argKey, argValue]) => Object.assign(element, {[argKey]: argValue})); + + // save attributes to be able to remove them on next run + this.prevAttrs = attrs; + } +} + +export const spreadArgs = directive(SpreadArgsDirective); From afe108b0c524e3ba358411d81ebbd2144b58b500 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliz=C3=A9=20Debray?= Date: Tue, 26 Sep 2023 11:47:41 +0200 Subject: [PATCH 11/27] Fix dismissible post-alert stories --- packages/components/src/components.d.ts | 8 ++++ .../src/components/post-alert/post-alert.tsx | 13 ++++- .../src/components/post-alert/readme.md | 7 +++ .../alert/web-component/post-alert.stories.ts | 47 +++++++++++-------- .../web-component/post-alert.styles.scss | 2 + 5 files changed, 55 insertions(+), 22 deletions(-) create mode 100644 packages/documentation/src/stories/components/alert/web-component/post-alert.styles.scss diff --git a/packages/components/src/components.d.ts b/packages/components/src/components.d.ts index 1566173ce8..fe5f8a1fe0 100644 --- a/packages/components/src/components.d.ts +++ b/packages/components/src/components.d.ts @@ -104,6 +104,10 @@ export namespace Components { "show": (panelName: string) => Promise; } } +export interface PostAlertCustomEvent extends CustomEvent { + detail: T; + target: HTMLPostAlertElement; +} export interface PostTabsCustomEvent extends CustomEvent { detail: T; target: HTMLPostTabsElement; @@ -175,6 +179,10 @@ declare namespace LocalJSX { * The icon to display in the alert. By default, the icon depends on the alert type. If `none`, no icon is displayed. */ "icon"?: string; + /** + * An event emitted when the alert element is dismissed, after the transition. It has no payload and only relevant for dismissible alerts. + */ + "onDismissed"?: (event: PostAlertCustomEvent) => void; /** * The type of the alert. */ diff --git a/packages/components/src/components/post-alert/post-alert.tsx b/packages/components/src/components/post-alert/post-alert.tsx index 20aec97fc0..a83cd2dc2a 100644 --- a/packages/components/src/components/post-alert/post-alert.tsx +++ b/packages/components/src/components/post-alert/post-alert.tsx @@ -1,4 +1,4 @@ -import { Component, Element, h, Host, Method, Prop, State, Watch } from '@stencil/core'; +import { Component, Element, Event, EventEmitter, h, Host, Method, Prop, State, Watch } from '@stencil/core'; import { version } from '../../../package.json'; import { fadeOut } from '../../animations'; import { checkEmptyOrOneOf, checkEmptyOrPattern, checkNonEmpty, checkType } from '../../utils'; @@ -72,6 +72,12 @@ export class PostAlert { checkEmptyOrOneOf(type, ALERT_TYPES, `The post-alert requires a type form: ${ALERT_TYPES.join(', ')}`); } + /** + * An event emitted when the alert element is dismissed, after the transition. + * It has no payload and only relevant for dismissible alerts. + */ + @Event() dismissed: EventEmitter; + connectedCallback() { this.validateDismissible(); this.validateFixed(); @@ -96,8 +102,11 @@ export class PostAlert { @Method() async dismiss() { const dismissal = fadeOut(this.host); - dismissal.onfinish = () => this.host.remove(); + await dismissal.finished; + + this.host.remove(); + this.dismissed.emit(); } render() { diff --git a/packages/components/src/components/post-alert/readme.md b/packages/components/src/components/post-alert/readme.md index fef0312e51..ac947230d1 100644 --- a/packages/components/src/components/post-alert/readme.md +++ b/packages/components/src/components/post-alert/readme.md @@ -16,6 +16,13 @@ | `type` | `type` | The type of the alert. | `"danger" \| "gray" \| "info" \| "primary" \| "success" \| "warning"` | `'primary'` | +## Events + +| Event | Description | Type | +| ----------- | --------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | +| `dismissed` | An event emitted when the alert element is dismissed, after the transition. It has no payload and only relevant for dismissible alerts. | `CustomEvent` | + + ## Methods ### `dismiss() => Promise` diff --git a/packages/documentation/src/stories/components/alert/web-component/post-alert.stories.ts b/packages/documentation/src/stories/components/alert/web-component/post-alert.stories.ts index b375253337..b8cb46e628 100644 --- a/packages/documentation/src/stories/components/alert/web-component/post-alert.stories.ts +++ b/packages/documentation/src/stories/components/alert/web-component/post-alert.stories.ts @@ -2,6 +2,7 @@ import { Meta, StoryContext, StoryFn, StoryObj } from '@storybook/web-components import { html } from 'lit'; import { BADGE } from '../../../../../.storybook/constants'; import { spreadArgs } from '../../../../utils'; +import './post-alert.styles.scss'; const meta: Meta = { title: 'Components/Post Alert', @@ -43,39 +44,46 @@ export default meta; // DECORATORS function externalControl(story: StoryFn, context: StoryContext) { - const {args, canvasElement} = context; let alert: HTMLPostAlertElement; + let button: HTMLButtonElement; + const {args, canvasElement} = context; - const toggleButton = html` - Show Alert - `; - - const toggleAlert = (e?: Event) => { - if (e) e.preventDefault(); - - const alertButton = canvasElement.querySelector('#alert-button') as HTMLElement; - const alertContainer = canvasElement.querySelector('#alert-container') as HTMLElement; + const toggleAlert = async (e: Event) => { + e.preventDefault(); + const alertContainer = canvasElement.querySelector('.alert-container') as HTMLElement; if (alert.parentNode) { - void alert.dismiss(); - alertButton.textContent = 'Show Alert'; + await alert.dismiss(); } else { + if (!args.fixed) button.hidden = true; alertContainer.appendChild(alert); - alertButton.textContent = 'Hide Alert'; + alert.shadowRoot?.querySelector('button')?.focus(); } } setTimeout(() => { alert = canvasElement.querySelector('post-alert') as HTMLPostAlertElement; - if (args.fixed) void alert.remove(); + button = canvasElement.querySelector('.alert-button') as HTMLButtonElement; + + if (args.fixed) { + alert.remove(); + } else { + button.hidden = true; + alert.addEventListener('dismissed', () => { + button.hidden = false; + button.focus(); + }); + } }); return html` -
- ${toggleButton} -
- ${story(args, context)} -
+ ${args.fixed ? 'Toggle Fixed Alert' : 'Reset Alert'} +
+ ${story(args, context)}
`; } @@ -129,7 +137,6 @@ export const Dismissible: Story = { export const Fixed: Story = { args: { - dismissible: true, fixed: true, } }; diff --git a/packages/documentation/src/stories/components/alert/web-component/post-alert.styles.scss b/packages/documentation/src/stories/components/alert/web-component/post-alert.styles.scss new file mode 100644 index 0000000000..4a9c272dc7 --- /dev/null +++ b/packages/documentation/src/stories/components/alert/web-component/post-alert.styles.scss @@ -0,0 +1,2 @@ +@use '@swisspost/design-system-styles/core' as post; + From c1d63e5c73ed08389123f95a68ecdba048565b03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliz=C3=A9=20Debray?= Date: Tue, 26 Sep 2023 16:22:44 +0200 Subject: [PATCH 12/27] Adapt HTML alert --- .../stories/components/alert/alert.docs.mdx | 47 ++--- .../alert/standard-html/alert.stories.ts | 185 +++++------------- .../alert/standard-html/getAlertClasses.ts | 6 +- 3 files changed, 68 insertions(+), 170 deletions(-) diff --git a/packages/documentation/src/stories/components/alert/alert.docs.mdx b/packages/documentation/src/stories/components/alert/alert.docs.mdx index dea5b0aecd..d97fc33c0f 100644 --- a/packages/documentation/src/stories/components/alert/alert.docs.mdx +++ b/packages/documentation/src/stories/components/alert/alert.docs.mdx @@ -11,57 +11,47 @@ import AlertDismissSample from './web-component/alert-dismiss.sample?raw'; # Alert
- Provide contextual feedback messages for typical user actions with the handful of available and flexible alert - messages. + Use alerts to communicate brief, important, and potentially time-sensitive messages to the user.
-Alerts are available for any length of text, as well as an optional close button. -By default, alerts are rendered with a contextually appropriate icon. +Alerts are intended to attract the user's attention without interrupting their ongoing task.

There are various methods to integrate this component into your project.

We advise opting for the "Standard HTML" approach for alerts that remain static on the page and using the "Web Component" method for dismissible alerts.

- + Standard HTML - - +
+ +
- ## Specific examples of application + - The following examples show some of the different characteristics of the component. These can differ in the type of visualization, the HTML structure, as well as when, how and why they are displayed. + ## Examples - ### Additional Content + ### Contents - Alerts can also contain additional HTML elements like headings, paragraphs, lists, dividers, and more. + Alerts can contain various HTML elements like headings, paragraphs, lists and dividers. - ### Dismissible - - Such an alert can be removed from the UI by the user, by clicking on a dismiss button. - This button should normally be inside the alert (as the first child), but you can also use a button which is not included in the alert's HTML. - In the end, it's just a button with an `onClick()` event. As this is a CSS-only component, you have to define the logic yourself. - - Dismiss buttons without text should always provide a `dismiss-label` prop, which defines the hidden text associated with the button. - - - ### Action Buttons - To render an action alert with buttons, the alert element requires an additional class as well as additional HTML content. Thus, everything will be rendered as it should be. + To include action buttons within an alert, apply the `.alert-action` class to the alert element, + enclose the alert's content within a `.alert-content` container, and place the action buttons within a `.alert-buttons` wrapper. - ### Fixed + ### Banner + + To present an alert as a banner anchored at the bottom of the page, simply add the class `.alert-fixed-bottom`. + **These alerts are typically designed to be dismissible, therefore it is advisable to employ a web component for their implementation.** - A fixed alert (at the bottom of the viewport) is obtained by adding the class `.alert-fixed-bottom` to the alert element. - As this is a CSS-only component, you have to define the logic to show/hide/toggle the alert yourself. - Such an alert is normally also dismissible, so please notice the information for a dismissible alert as well.
@@ -69,11 +59,12 @@ By default, alerts are rendered with a contextually appropriate icon. Web Component + ## Installation - The `` element is part of the `@swisspost/design-system-components` package. + The `` element is part of the `@swisspost/design-system-components` package. For more information, read the getting started with components guide. ## Examples @@ -82,7 +73,7 @@ By default, alerts are rendered with a contextually appropriate icon. Alerts can contain various HTML elements like paragraphs, lists, icons and dividers. - By default all children of the `` are placed in the body. + By default all children of the `` are placed in the body. Use the `heading` slot to place a child in the heading, and the `actions` slot for action buttons. Learn more in Mozilla's slots documentation. diff --git a/packages/documentation/src/stories/components/alert/standard-html/alert.stories.ts b/packages/documentation/src/stories/components/alert/standard-html/alert.stories.ts index 581210f8f8..e291f2947a 100644 --- a/packages/documentation/src/stories/components/alert/standard-html/alert.stories.ts +++ b/packages/documentation/src/stories/components/alert/standard-html/alert.stories.ts @@ -19,108 +19,60 @@ const meta: Meta = { args: { title: 'Titulum', content: '

Contentus momentus vero siteos et accusam iretea et justo.

', - variant: 'alert-primary', - noIcon: false, - icon: 'null', - dismissible: false, - fixed: false, - action: false, show: true, + action: false, + fixed: false, + icon: undefined, + type: 'alert-primary', }, argTypes: { title: { name: 'Title', control: { type: 'text' }, - table: { category: 'Content' }, }, content: { name: 'Content', control: { type: 'text' }, - table: { category: 'Content' }, - }, - variant: { - name: 'Variant', - description: 'Defines a style variant.', - control: { - type: 'radio', - labels: { - 'alert-primary': 'Primary', - 'alert-success': 'Success', - 'alert-danger': 'Danger', - 'alert-warning': 'Warning', - 'alert-info': 'Info', - 'alert-gray': 'Gray', - }, - }, - options: ['alert-primary', 'alert-success', 'alert-danger', 'alert-warning', 'alert-info', 'alert-gray'], - table: { - category: 'General', - }, - }, - noIcon: { - name: 'No Icon', - description: 'Removes the predefined icon completely.', - control: { - type: 'boolean', - }, - table: { - category: 'General', - }, }, - icon: { - name: 'Icon', - description: 'Defines a custom icon.', - if: { - arg: 'noIcon', - truthy: false, - }, - control: { - type: 'select', - labels: { - 'null': 'Default', - '1001': 'Envelope (1001)', - '2023': 'Cog (2023)', - '2025': 'Send (2025)', - '2035': 'Home (2035)', - '2101': 'Bubble (2101)', - }, - }, - options: ['null', '1001', '2023', '2025', '2035', '2101'], - table: { - category: 'General', - }, - }, - dismissible: { - name: 'Dismissible', - description: - 'Adds the dismissible styles.Do not forget to add the structural adjustments!', + show: { + name: 'Show', control: { type: 'boolean' }, - table: { - category: 'Variations', - }, + table: { disable: true }, }, action: { name: 'Action Buttons', description: - 'Adds the action button styles.Do not forget to add the structural adjustments!', + 'If `true`, the alert contains action buttons on its right side.Alert content must then be wrapped in a `.alert-content` container.', control: { type: 'boolean' }, - table: { - category: 'Variations', - }, }, fixed: { name: 'Fixed', description: - 'Adds the fixed styles.Do not forget to add the structural adjustments!', + 'If `true`, the alert anchored at the bottom of the page, from edge to edge.', control: { type: 'boolean' }, - table: { - category: 'Variations', + }, + icon: { + name: 'Icon', + description: 'The icon to display in the alert. By default, the icon depends on the alert type.', + control: { + type: 'select', + labels: { + 'pi-1001': 'pi-1001 (Envelope)', + 'pi-2023': 'pi-2023 (Cog)', + 'pi-2025': 'pi-2025 (Send)', + 'pi-2035': 'pi-2035 (Home)', + 'pi-2101': 'pi-2101 (Bubble)', + }, }, + options: ['no-icon', 'pi-1001', 'pi-2023', 'pi-2025', 'pi-2035', 'pi-2101'], }, - show: { - name: 'Show', - control: { type: 'boolean' }, - table: { disable: true }, + type: { + name: 'Type', + description: 'The type of the alert.', + control: { + type: 'select', + }, + options: ['alert-primary', 'alert-success', 'alert-danger', 'alert-warning', 'alert-info', 'alert-gray'], }, }, }; @@ -131,26 +83,31 @@ export default meta; function externalControl(story: StoryFn, { args, context }: StoryContext) { const [_, updateArgs] = useArgs(); + const toggleAlert = (e: MouseEvent, args: Args, updateArgs: Function) => { + e.preventDefault(); + updateArgs({ show: !args.show }); + } + + if (!args.fixed && !args.show) updateArgs({ show: true }); + const button = html` - - ${args.fixed ? 'Toggle Alert' : 'Show Alert'} - + Toggle Fixed Alert `; return html` - ${args.fixed || !args.show ? button : nothing} ${story(args, context)} + ${args.fixed ? button : nothing} + ${story(args, context)} `; } // RENDERER -function toggleAlert(e: MouseEvent, args: Args, updateArgs: Function) { - e.preventDefault(); - updateArgs({ show: !args.show }); -} -function renderAlert(args: Args) { - const [_, updateArgs] = useArgs(); +function renderAlert(args: Args) { const classes = getAlertClasses(args); const content = html` @@ -160,19 +117,6 @@ function renderAlert(args: Args) { return html`