Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(components): add alert component #1085

Merged
merged 34 commits into from
Sep 29, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7044f83
feat(components): add alert component
alizedebray Feb 7, 2023
b79b33f
feat(components): add alert close method
alizedebray Feb 8, 2023
07160b1
feat(components): fix pattern checker and corresponding tests
alizedebray Feb 8, 2023
187459f
Merge branch 'main' into 1006-component-alert
gfellerph Feb 20, 2023
f89d587
chore: update lockfile and add generated docs file
gfellerph Feb 20, 2023
a360e1e
Merge branch 'main' into 1006-component-alert
alizedebray Sep 11, 2023
d0e4a3b
fix alert height
alizedebray Sep 11, 2023
13a13ec
add host element
alizedebray Sep 11, 2023
cf26081
update story args
alizedebray Sep 11, 2023
6fe2cdb
use post-icons
alizedebray Sep 12, 2023
7b742c6
refactor the post-alert component
alizedebray Sep 22, 2023
3bcdd59
Write post-alert docs
alizedebray Sep 25, 2023
afe108b
Fix dismissible post-alert stories
alizedebray Sep 26, 2023
c1d63e5
Adapt HTML alert
alizedebray Sep 26, 2023
7c7e4be
Update snapshot tests
alizedebray Sep 26, 2023
0be415e
Add snapshot tests
alizedebray Sep 26, 2023
5a7a161
Remove unnecessary function
alizedebray Sep 26, 2023
f4615c0
Merge branch 'main' into 1006-component-alert
alizedebray Sep 26, 2023
4513ea4
Update components.d.ts
alizedebray Sep 26, 2023
c0efa28
Update _notification.scss
alizedebray Sep 26, 2023
1f8299a
Update .changeset/sweet-singers-trade.md
alizedebray Sep 27, 2023
346f33b
Update packages/components/src/components/post-alert/post-alert.scss
alizedebray Sep 27, 2023
8282d83
Remove unnecessary styles
alizedebray Sep 27, 2023
1019295
Merge branch '1006-component-alert' of https://github.com/swisspost/d…
alizedebray Sep 27, 2023
def8fd1
Update MDN link label
alizedebray Sep 27, 2023
06f45b1
Add description for alert's innerHTML control
alizedebray Sep 27, 2023
bc27af3
Make the alert's dismissLabel control required
alizedebray Sep 27, 2023
6d15b46
Add changeset for changes in the alert's gap
alizedebray Sep 27, 2023
0a97860
Merge branch 'main' into 1006-component-alert
alizedebray Sep 27, 2023
02cd74b
Merge branch 'main' into 1006-component-alert
imagoiq Sep 28, 2023
782fbc1
Merge branch 'main' into 1006-component-alert
alizedebray Sep 28, 2023
c079ddb
Add keys to JSX arrays
alizedebray Sep 29, 2023
59b92cf
Add icon stories for HTML alerts
alizedebray Sep 29, 2023
227a511
Update alert styles
alizedebray Sep 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/sweet-singers-trade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@swisspost/design-system-documentation': minor
'@swisspost/design-system-components': minor
---

Created an alert component.
alizedebray marked this conversation as resolved.
Show resolved Hide resolved
9 changes: 7 additions & 2 deletions packages/components/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,8 @@
}
],
"@stencil-community/strict-boolean-conditions": "off",
"@stencil-community/required-prefix": ["error", ["post"]],
"@stencil-community/required-prefix": ["error", ["post-"]],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't is covered by another rule, the fact that web components should be named with a dash? At least the browser throw a DOMexception error with invalid name: https://webcomponents.guide/learn/components/naming-your-components/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but if you write postalert there is no error.

"@stencil-community/async-methods": "error",
"@stencil-community/ban-prefix": ["error", ["stencil", "stnl", "st"]],
"@stencil-community/decorators-context": "error",
"@stencil-community/decorators-style": [
"error",
Expand All @@ -51,6 +50,12 @@
"listen": "multiline"
}
],
"@stencil-community/class-pattern": [
"error",
{
"pattern": "^Post.*(?!Component)$"
}
],
"@stencil-community/element-type": "error",
"@stencil-community/host-data-deprecated": "error",
"@stencil-community/methods-must-be-public": "error",
Expand Down
30 changes: 30 additions & 0 deletions packages/components/cypress/e2e/alert.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
describe('alert', () => {
describe('default', () => {
beforeEach(() => {
cy.getComponent('post-alert');
});

it('should render', () => {
cy.get('@alert').should('exist');
});

it('should not have a close button', () => {
cy.get('@alert').find('.btn-close').should('not.exist');
});
});

describe('dismissible', () => {
beforeEach(() => {
cy.getComponent('post-alert', 'dismissible');
});

it('should have a close button', () => {
cy.get('@alert').find('.btn-close').should('be.visible');
});

it('should be removed after the dismiss button is clicked', () => {
cy.get('@alert').find('.btn-close').click();
cy.get('@alert').should('not.exist');
});
});
});
4 changes: 3 additions & 1 deletion packages/components/cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ chai.use(isInViewport);

Cypress.Commands.add('getComponent', (component: string, story = 'default') => {
cy.visit(`/iframe.html?id=components-${component}--${story}`);
cy.get(`post-${component}`).as(component);

const alias = component.replace(/^post-/, '');
cy.get(`post-${alias}`).as(alias);
});

Cypress.Commands.add('checkVisibility', (visibility: 'visible' | 'hidden') => {
Expand Down
67 changes: 67 additions & 0 deletions packages/components/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,39 @@
* It contains typing information for all components that exist in this project.
*/
import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
import { AlertType } from "./components/post-alert/alert-types";
import { BackgroundColor } from "./components/post-tooltip/types";
import { Placement } from "@floating-ui/dom";
export { AlertType } from "./components/post-alert/alert-types";
export { BackgroundColor } from "./components/post-tooltip/types";
export { Placement } from "@floating-ui/dom";
export namespace Components {
interface PostAlert {
/**
* Triggers alert dismissal programmatically (same as clicking on the close button (×)).
*/
"dismiss": () => Promise<void>;
/**
* 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. By default, the icon depends on the alert type. If `none`, no icon is displayed.
*/
"icon": string;
/**
* The type of the alert.
*/
"type": AlertType;
}
interface PostCollapsible {
/**
* If `true`, the element is initially collapsed otherwise it is displayed.
Expand Down Expand Up @@ -105,11 +133,21 @@ export namespace Components {
"toggle": (target: HTMLElement, force?: boolean) => Promise<void>;
}
}
export interface PostAlertCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLPostAlertElement;
}
export interface PostTabsCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLPostTabsElement;
}
declare global {
interface HTMLPostAlertElement extends Components.PostAlert, HTMLStencilElement {
}
var HTMLPostAlertElement: {
prototype: HTMLPostAlertElement;
new (): HTMLPostAlertElement;
};
interface HTMLPostCollapsibleElement extends Components.PostCollapsible, HTMLStencilElement {
}
var HTMLPostCollapsibleElement: {
Expand Down Expand Up @@ -150,6 +188,7 @@ declare global {
new (): HTMLPostTooltipElement;
};
interface HTMLElementTagNameMap {
"post-alert": HTMLPostAlertElement;
"post-collapsible": HTMLPostCollapsibleElement;
"post-icon": HTMLPostIconElement;
"post-tab-header": HTMLPostTabHeaderElement;
Expand All @@ -159,6 +198,32 @@ declare global {
}
}
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. 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>) => void;
/**
* The type of the alert.
*/
"type"?: AlertType;
}
interface PostCollapsible {
/**
* If `true`, the element is initially collapsed otherwise it is displayed.
Expand Down Expand Up @@ -235,6 +300,7 @@ declare namespace LocalJSX {
"placement"?: Placement;
}
interface IntrinsicElements {
"post-alert": PostAlert;
"post-collapsible": PostCollapsible;
"post-icon": PostIcon;
"post-tab-header": PostTabHeader;
Expand All @@ -247,6 +313,7 @@ export { LocalJSX as JSX };
declare module "@stencil/core" {
export namespace JSX {
interface IntrinsicElements {
"post-alert": LocalJSX.PostAlert & JSXBase.HTMLAttributes<HTMLPostAlertElement>;
"post-collapsible": LocalJSX.PostCollapsible & JSXBase.HTMLAttributes<HTMLPostCollapsibleElement>;
/**
* @class PostIcon - representing a stencil component
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const ALERT_TYPES = ['primary', 'success', 'danger', 'warning', 'info', 'gray'] as const;

export type AlertType = typeof ALERT_TYPES[number];
22 changes: 22 additions & 0 deletions packages/components/src/components/post-alert/post-alert.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
@use '@swisspost/design-system-styles/components/alert';
@use '@swisspost/design-system-styles/components/close';
@use '@swisspost/design-system-styles/core' as post;

:host {
display: block;

::slotted(*) {
margin: 0 !important;
}
}

.btn-close > .visually-hidden {
alizedebray marked this conversation as resolved.
Show resolved Hide resolved
@include post.visually-hidden();
}

@for $i from 1 through 6 {
.alert-heading > ::slotted(h#{$i}) {
font-size: inherit !important;
font-weight: inherit !important;
}
}
152 changes: 152 additions & 0 deletions packages/components/src/components/post-alert/post-alert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
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';
import { ALERT_TYPES, AlertType } from './alert-types';

@Component({
tag: 'post-alert',
styleUrl: 'post-alert.scss',
shadow: true,
})
export class PostAlert {
@Element() host: HTMLPostAlertElement;

@State() classes: string;
@State() hasActions: boolean;
@State() hasHeading: boolean;
@State() onDismissButtonClick = () => this.dismiss();

/**
* If `true`, a close button (×) is displayed and the alert can be dismissed by the user.
*/
@Prop() readonly dismissible: boolean = false;

@Watch('dismissible')
validateDismissible(isDismissible = this.dismissible) {
checkType(isDismissible, 'boolean', 'The post-alert "dismissible" prop should be a boolean.');
setTimeout(() => this.validateDismissLabel());
}

/**
* The label to use for the close button of a dismissible alert.
*/
@Prop() readonly dismissLabel: string;

@Watch('dismissLabel')
validateDismissLabel(dismissLabel = this.dismissLabel) {
if (this.dismissible) {
checkNonEmpty(dismissLabel, 'Dismissible post-alert\'s require a "dismiss-label" prop.');
}
}

/**
* If `true`, the alert is positioned at the bottom of the window, from edge to edge.
*/
@Prop() readonly fixed: boolean = false;

@Watch('fixed')
validateFixed(isFixed = this.fixed) {
checkType(isFixed, 'boolean', 'The post-alert "fixed" prop should be a boolean.');
}

/**
* The icon to display in the alert. By default, the icon depends on the alert type.
*
* If `none`, no icon is displayed.
*/
@Prop() readonly icon: string;

@Watch('icon')
validateIcon(icon = this.icon) {
checkEmptyOrPattern(icon, /\d{4}|none/, 'The post-alert "icon" prop should be a 4-digits string.');
}

/**
* The type of the alert.
*/
@Prop() readonly type: AlertType = 'primary';

@Watch('type')
validateType(type = this.type) {
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<void>;

connectedCallback() {
this.validateDismissible();
this.validateFixed();
this.validateIcon();
this.validateType();
}

componentWillRender() {
this.hasHeading = this.host.querySelectorAll('[slot=heading]').length > 0;
this.hasActions = this.host.querySelectorAll('[slot=actions]').length > 0;

this.classes = `alert alert-${this.type ?? 'primary'}`;
if (this.dismissible) this.classes += ' alert-dismissible';
if (this.hasActions) this.classes += ' alert-action';
if (this.fixed) this.classes += ' alert-fixed-bottom';
if (this.icon === 'none') this.classes += ' no-icon';
}

/**
* Triggers alert dismissal programmatically (same as clicking on the close button (×)).
*/
@Method()
async dismiss() {
const dismissal = fadeOut(this.host);

await dismissal.finished;

this.host.remove();
this.dismissed.emit();
}

render() {
const defaultAlertContent = [
this.icon && this.icon !== 'none' && (
<post-icon name={this.icon} />
),
this.hasHeading && (
<div class="alert-heading">
<slot name="heading"/>
</div>
),
<slot/>
];

const actionAlertContent = [
<div class="alert-content">
{defaultAlertContent}
</div>,
<div class="alert-buttons">
<slot name="actions"/>
</div>,
];

return (
<Host data-version={version}>
<div role="alert" class={this.classes}>
{this.dismissible && (
<button class="btn-close" onClick={this.onDismissButtonClick}>
<span class="visually-hidden">{this.dismissLabel}</span>
</button>
)}

{this.hasActions
? actionAlertContent
: defaultAlertContent
}
</div>
</Host>
);
}

}
Loading