Skip to content

Commit

Permalink
Feat(web): Add Message and Link for ToastBar #DS-1213
Browse files Browse the repository at this point in the history
  • Loading branch information
curdaj committed May 24, 2024
1 parent 235935e commit eb1971d
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ describe('ToastBarLink', () => {
restPropsTest(ToastBarLink, 'a');

beforeEach(() => {
render(<ToastBarLink href="example-href">Example action</ToastBarLink>);
render(<ToastBarLink href="#example-href">Example action</ToastBarLink>);
});

it('should render with correct href', () => {
const element = screen.getByRole('link');

expect(element).toBeInTheDocument();
expect(element).toHaveAttribute('href', 'example-href');
expect(element).toHaveAttribute('href', '#example-href');
});

it('should render children', () => {
Expand Down
14 changes: 10 additions & 4 deletions packages/web/src/js/Toast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const SELECTOR_ICON_ELEMENT = `[${ATTRIBUTE_DATA_POPULATE_FIELD}="icon"]`;
const SELECTOR_CLOSE_BUTTON_ELEMENT = `[${ATTRIBUTE_DATA_POPULATE_FIELD}="close-button"]`;
const SELECTOR_DISMISS_TRIGGER_ELEMENT = `[${ATTRIBUTE_DATA_DISMISS}="${NAME}"]`;
const SELECTOR_MESSAGE_ELEMENT = `[${ATTRIBUTE_DATA_POPULATE_FIELD}="message"]`;
const SELECTOR_LINK_ELEMENT = `[${ATTRIBUTE_DATA_POPULATE_FIELD}="link"]`;

// Keep in sync with transitions in `scss/Toast/_theme.scss`.
export const PROPERTY_NAME_SLOWEST_TRANSITION = {
Expand All @@ -54,8 +55,9 @@ type Config = {
autoCloseInterval: number;
color: Color;
containerId: string;
content: HTMLElement | string;
enableAutoClose: boolean;
message: HTMLElement | string;
link: HTMLElement | string;
hasIcon: boolean;
iconName: string;
id: string;
Expand Down Expand Up @@ -180,8 +182,8 @@ class Toast extends BaseComponent {
}

const config = this.config as Config;
if (!config.content) {
warning(false, 'Toast content is required, nothing given.');
if (!config.message) {
warning(false, 'Toast message is required, nothing given.');

return null;
}
Expand All @@ -190,14 +192,18 @@ class Toast extends BaseComponent {
const iconElement = template.querySelector(SELECTOR_ICON_ELEMENT) as HTMLElement;
const closeButtonElement = template.querySelector(SELECTOR_CLOSE_BUTTON_ELEMENT) as HTMLElement;
const messageElement = template.querySelector(SELECTOR_MESSAGE_ELEMENT) as HTMLElement;
const linkElement = template.querySelector(SELECTOR_LINK_ELEMENT) as HTMLElement;

itemElement!.setAttribute('id', config.id);
itemElement!.setAttribute('data-spirit-color', config.color);

this.updateOrRemoveIcon(iconElement);
this.updateOrRemoveCloseButton(closeButtonElement);

messageElement!.innerHTML = typeof config.content === 'string' ? config.content : config.content.outerHTML;
messageElement!.innerHTML = typeof config.message === 'string' ? config.message : config.message.outerHTML;

linkElement!.setAttribute('href', '#');
linkElement!.innerHTML = typeof config.link === 'string' ? config.link : config.link.outerHTML;

return itemElement;
}
Expand Down
76 changes: 41 additions & 35 deletions packages/web/src/scss/components/Toast/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Toast is a composition of a few subcomponents:

- [Toast](#toast)
- [ToastBar](#toastbar)
- [ToastBarMessage](#toastbarmessage)
- [ToastBarLink](#toastbarlink)

## JavaScript Plugin

Expand Down Expand Up @@ -164,8 +166,10 @@ Minimum example:
```html
<div class="ToastBar ToastBar--inverted">
<div class="ToastBar__box">
<div class="ToastBar__content">
<div class="ToastBar__message">Message only</div>
<div class="ToastBar__container">
<div class="ToastBar__content">
<div class="text-truncate-multiline" data-spirit-populate-field="message">Message only</div>
</div>
</div>
</div>
</div>
Expand All @@ -178,41 +182,43 @@ An icon can be added to the ToastBar component:
```html
<div class="ToastBar ToastBar--inverted">
<div class="ToastBar__box">
<div class="ToastBar__content">
<div class="ToastBar__container">
<svg width="20" height="20" aria-hidden="true">
<use xlink:href="/icons/svg/sprite.svg#info" />
</svg>
<div class="ToastBar__message">Message with icon</div>
<div class="ToastBar__content">
<div class="text-truncate-multiline" data-spirit-populate-field="message">Message with icon</div>
</div>
</div>
</div>
</div>
```

### Action Link
### ToastBar Components

The content of `ToastBar` can be assembled from the following subcomponents:

#### ToastBarMessage

An action link can be added to the ToastBar component:
`ToastBarMessage` is a subcomponent designated for the main message in `ToastBar`.

Usage example:

```html
<div class="ToastBar ToastBar--inverted">
<div class="ToastBar__box">
<div class="ToastBar__container">
<div class="ToastBar__content">
<div class="text-truncate-multiline" data-spirit-populate-field="message">Message with action</div>
<div class="text-truncate-multiline">Message with action</div>
</div>
</div>
</div>
</div>
```

#### API

| Name | Type | Default | Required | Description |
| ---------- | -------- | ------- | -------- | ------------------------------ |
| `children` | `string` ||| Content of the ToastBarMessage |

#### ToastBarLink

`ToastBarLink` is a component designated to create an action link within `ToastBar`.
`ToastBarLink` is a subcomponent designated to create an action link within `ToastBar`.

Usage example:

Expand All @@ -221,24 +227,14 @@ Usage example:
<div class="ToastBar__box">
<div class="ToastBar__container">
<div class="ToastBar__content">
<div class="text-truncate-multiline" data-spirit-populate-field="message">Message with action</div>
<div class="text-truncate-multiline">Message with action</div>
<a href="#" class="link-inverted link-underlined ToastBar__link">Action</a>
</div>
</div>
</div>
</div>
```

#### API

| Name | Type | Default | Required | Description |
| -------------- | ------------------------------------------------ | ---------- | -------- | ------------------------------ |
| `children` | `string` ||| Content of the ToastBarLink |
| `color` | [Action Link Color dictionary][dictionary-color] | `inverted` || Color of the link |
| `href` | `string` ||| ToastBarLink's href attribute |
| `isDisabled` | `bool` | `false` || Whether is the link disabled |
| `isUnderlined` | `bool` | `true` || Whether is the link underlined |

👉 **Do not put any important actions** like "Undo" in the ToastBar component (unless there are other means to perform
said action), as it is very hard (if not impossible) to reach for users with assistive technologies. Read more about
[Toast accessibility][scott-o-hara-toast] at Scott O'Hara's blog.
Expand All @@ -253,8 +249,10 @@ For example:
```html
<div class="ToastBar ToastBar--success">
<div class="ToastBar__box">
<div class="ToastBar__content">
<div class="ToastBar__message">Success message</div>
<div class="ToastBar__container">
<div class="ToastBar__content">
<div class="text-truncate-multiline">Success message</div>
</div>
</div>
</div>
</div>
Expand Down Expand Up @@ -290,7 +288,7 @@ button:
```html
<div id="my-dismissible-toast" class="ToastBar ToastBar--inverted ToastBar--dismissible">
<div class="ToastBar__box">
<div class="ToastBar__content">
<div class="ToastBar__container">
<div class="ToastBar__message">Dismissible message</div>
</div>
<button
Expand Down Expand Up @@ -332,13 +330,13 @@ button:
<!-- ToastBar: start -->
<div id="my-dismissible-toast" class="ToastBar ToastBar--inverted ToastBar--dismissible is-hidden">
<div class="ToastBar__box">
<div class="ToastBar__content">
<div class="ToastBar__container">
<svg width="20" height="20" aria-hidden="true">
<use xlink:href="/icons/svg/sprite.svg#info" />
</svg>
<div class="ToastBar__message">
Toast message
<a href="#" class="link-inverted link-underlined">Action</a>
<div class="ToastBar__content">
<div class="text-truncate-multiline">Toast message</div>
<a href="#" class="link-inverted link-underlined ToastBar__link">Action</a>
</div>
</div>
<button
Expand Down Expand Up @@ -375,11 +373,14 @@ the template and apply it on any toasts to be shown to the user, using the confi
<template data-spirit-snippet="item">
<div class="ToastBar is-hidden" data-spirit-color="inverted" data-spirit-populate-field="item">
<div class="ToastBar__box">
<div class="ToastBar__content">
<div class="ToastBar__container">
<svg width="20" height="20" aria-hidden="true" data-spirit-populate-field="icon">
<use xlink:href="/icons/svg/sprite.svg#info" />
</svg>
<div class="ToastBar__message" data-spirit-populate-field="message"></div>
<div class="ToastBar__content">
<div class="text-truncate-multiline" data-spirit-populate-field="message"></div>
<a href="#" class="link-inverted link-underlined ToastBar__link" data-spirit-populate-field="link"></a>
</div>
</div>
<button
type="button"
Expand Down Expand Up @@ -410,8 +411,13 @@ const toast = new Toast(null, {
autoCloseInterval: 3000 // Set interval after ToastBar will be closed in ms, default: 3000
color: 'informative', // One of ['inverted' (default), 'success', 'warning, 'danger', 'informative']
containerId: 'toast-example', // Must match the ID of the Toast container in HTML
content: 'Hello, this is my toast message!', // Can be plain text or HTML
enableAutoClose: true, // If true, ToastBar will close after `autoCloseInterval`, default: true
message: 'Hello, this is my toast message!', // Can be plain text or HTML
link: {
// Optional link configuration
href: 'https://example.com', // Optional link URL
text: 'Action link', // Optional link text
}, // Optional link URL
hasIcon: true,
iconName: 'info', // Optional icon name used as the #fragment in the SVG sprite URL
id: 'my-toast', // An ID is required for dismissible ToastBar
Expand Down
12 changes: 5 additions & 7 deletions packages/web/src/scss/components/Toast/_ToastBar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -48,32 +48,30 @@
align-items: start;
}

.ToastBar__content:has(> svg:first-child) {
.ToastBar__container:has(> svg:first-child) {
display: grid;
grid-template-columns: auto 1fr;
column-gap: theme.$bar-content-gap;
}

.ToastBar:is(.ToastBar--dismissible, :has([data-spirit-dismiss='toast'])) .ToastBar__content {
.ToastBar:is(.ToastBar--dismissible, :has([data-spirit-dismiss='toast'])) .ToastBar__container {
align-self: center; // 4.
}

.ToastBar__container {
.ToastBar__content {
@include typography.generate(theme.$bar-typography);

display: flex;
flex-wrap: wrap; // 5.
gap: theme.$bar-message-gap-y theme.$bar-message-gap-x;
}

.ToastBar__container > :is(a, button):last-child {
.ToastBar__link {
font-weight: 400;
}

// stylelint-disable-next-line selector-max-specificity -- Specificity is needed to precisely target the action.
.ToastBar:is(.ToastBar--dismissible, :has([data-spirit-dismiss='toast']))
.ToastBar__container
> :is(a, button):last-child {
.ToastBar:is(.ToastBar--dismissible, :has([data-spirit-dismiss='toast'])) .ToastBar__link {
margin-inline-end: theme.$bar-action-margin-inline-end; // 6.
}

Expand Down
3 changes: 2 additions & 1 deletion packages/web/src/scss/components/Toast/dynamic-toast.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ export const addDynamicToast = (event, containerId) => {
containerId,
autoCloseInterval: formElement.querySelector('#toast-auto-close-interval').value,
color: formElement.querySelector('#toast-color').value,
content: formElement.querySelector('#toast-content').value,
enableAutoClose: formElement.querySelector('#toast-enable-auto-close').checked,
message: formElement.querySelector('#toast-message').value,
link: formElement.querySelector('#toast-link').value,
hasIcon: formElement.querySelector('#toast-has-icon').checked,
id: `my-dynamic-toast-${Date.now()}`,
isDismissible: formElement.querySelector('#toast-is-dismissible').checked,
Expand Down
Loading

0 comments on commit eb1971d

Please sign in to comment.