diff --git a/apps/web-twig-demo/assets/scripts/toast-dynamic.ts b/apps/web-twig-demo/assets/scripts/toast-dynamic.ts
new file mode 100644
index 0000000000..bab4eea434
--- /dev/null
+++ b/apps/web-twig-demo/assets/scripts/toast-dynamic.ts
@@ -0,0 +1,22 @@
+import { Toast } from '@lmc-eu/spirit-web/src/js/index.esm';
+
+const addDynamicToast = (event, containerId) => {
+ const formElement = event.target.closest('form');
+ const config = {
+ color: formElement.querySelector('#toast-color').value,
+ containerId,
+ content: formElement.querySelector('#toast-content').value,
+ hasIcon: formElement.querySelector('#toast-has-icon').checked,
+ id: `my-dynamic-toast-${Date.now()}`,
+ isDismissible: formElement.querySelector('#toast-is-dismissible').checked,
+ };
+
+ const toast = new Toast(null, config);
+ toast.show();
+ console.log('Created dynamic toast with config:', config);
+};
+
+// Make it available in the global scope
+window.addDynamicToast = addDynamicToast;
+
+export default addDynamicToast;
diff --git a/apps/web-twig-demo/webpack.config.js b/apps/web-twig-demo/webpack.config.js
index 7cc9dc1197..0fb34aa36b 100644
--- a/apps/web-twig-demo/webpack.config.js
+++ b/apps/web-twig-demo/webpack.config.js
@@ -25,6 +25,7 @@ Encore
* and one CSS file (e.g. app.css) if your JavaScript imports CSS.
*/
.addEntry('app', './assets/app.ts')
+ .addEntry('toastDynamic', './assets/scripts/toast-dynamic.ts')
.addEntry('fileUploaderImagePreview', './assets/scripts/file-uploader-image-preview.ts')
.addEntry('fileUploaderMetaData', './assets/scripts/file-uploader-meta-data.ts')
.addEntry('formValidations', './assets/scripts/form-validations.ts')
diff --git a/packages/web-twig/src/Resources/components/Toast/README.md b/packages/web-twig/src/Resources/components/Toast/README.md
index 65fcd97226..b57085091a 100644
--- a/packages/web-twig/src/Resources/components/Toast/README.md
+++ b/packages/web-twig/src/Resources/components/Toast/README.md
@@ -101,27 +101,41 @@ sorted from top to bottom for the `top` vertical alignment, and from bottom to t
👉 Please note the _actual_ order in the DOM is followed when users tab over the interface, no matter the _visual_
order of the toast queue.
-#### Toast Queue Limitations
+#### Collapsing
+
+The collapsible Toast queue is turned on by default and can hold up to 3 ToastBar components.
+When the queue is full, the oldest ToastBar components are collapsed at the start of
+the queue and are only accessible by closing the newer ones.
-While the Toast queue becomes scrollable when it does not fit the screen, we recommend displaying only a few toasts at
-once for several reasons:
+#### Scrolling
-⚠️ **We strongly discourage from displaying too many toasts at once as it may cause the page to be unusable,
-especially on mobile screens. As of now, there is no automatic stacking of the toast queue items. It is the
-responsibility of the developer to ensure that the Toast queue does not overflow the screen.**
+By default, the Toast queue collapses when there are more than 3 ToastBars. To turn off this behavior and make the queue scrollable when it does not fit the screen,
+set the `isCollapsible` prop to `false`.
-⚠️ Please note that scrolling is only available on pointer-equipped devices (mouse, trackpad). Furthermore, scrolling is
-only possible when the cursor is placed over the toast message boxes. This way the page content behind the toast
-messages can remain accessible.
+⚠️ Please note that scrolling is not available on iOS devices due to a limitation in the WebKit engine.
👉 Please note that the initial scroll position is always at the **top** of the queue.
+```html
+
+
+
+```
+
+#### Toast Queue Limitations
+
+👉 Please note only the _visible_ ToastBar components are scrollable. Collapsed items are not accessible until visible
+items are dismissed.
+
+👉 For the sake of simplicity, the collapsible items limit cannot be configured at the moment.
+
### API
-| Name | Type | Default | Required | Description |
-| ------------ | ----------------------------------------------------------- | -------- | -------- | --------------------------------------- |
-| `alignmentX` | [[AlignmentX dictionary][dictionary-alignment] \| `object`] | `center` | ✕ | Horizontal alignment of the toast queue |
-| `alignmentY` | [`top` \| `bottom` \| `object`] | `bottom` | ✕ | Vertical alignment of the toast queue |
+| Name | Type | Default | Required | Description |
+| --------------- | ----------------------------------------------------------- | -------- | -------- | ----------------------------------------------------------------- |
+| `alignmentX` | [[AlignmentX dictionary][dictionary-alignment] \| `object`] | `center` | ✕ | Horizontal alignment of the toast queue |
+| `alignmentY` | [`top` \| `bottom` \| `object`] | `bottom` | ✕ | Vertical alignment of the toast queue |
+| `isCollapsible` | `bool` | `true` | ✕ | If true, Toast queue collapses if there are more than 3 ToastBars |
On top of the API options, the components accept [additional attributes][readme-additional-attributes].
If you need more control over the styling of a component, you can use [style props][readme-style-props]
@@ -184,7 +198,7 @@ to use the **inverted underlined** variant of the link (for all ToastBar colors)
👉 **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.
+[Toast accessibility][scott-o-hara-toast] at Scott O'Hara's blog.
### Colors
@@ -199,9 +213,14 @@ For example:
```
-### Opening the ToastBar
+### Basic Interactions
-Use our JavaScript plugin to open a Toast **that is present in the DOM,** e.g.:
+For basic use cases, you can simply place the ToastBar component inside the Toast container and show/hide it using our
+JavaScript plugin.
+
+#### Showing the Static ToastBar
+
+Use our JavaScript plugin to show a Toast **that is present in the DOM,** e.g.:
```twig
```
@@ -229,12 +248,13 @@ To make the ToastBar dismissible, add the `isDismissible` prop along with a uniq
| Name | Type | Default | Required | Description |
| --------------- | ------------------------------------------------------------ | ---------- | -------- | -------------------------------------------------------------------- |
-| `color` | [[Emotion Color dictionary][dictionary-color] \| `inverted`] | `inverted` | ✕ | Color variant |
| `closeLabel` | `string` | `Close` | ✕ | Close label |
+| `color` | [[Emotion Color dictionary][dictionary-color] \| `inverted`] | `inverted` | ✕ | Color variant |
| `hasIcon` | `bool` | `false` \* | ✕ | If true, an icon is shown along the message |
| `iconName` | `string` | `info` \* | ✕ | Name of a custom icon to be shown along the message |
| `id` | `string` | — | ✕ | Optional ToastBar ID. Required when `isDismissible` is set to `true` |
| `isDismissible` | `bool` | `false` | ✕ | If true, ToastBar can be dismissed by user |
+| `isTemplate` | `bool` | `false` | ✕ | If true, ToastBar will be adjusted for rendering inside `` |
| `isOpen` | `bool` | `true` | ✕ | If true, ToastBar is visible |
(\*) For each emotion color, a default icon is defined.
@@ -256,6 +276,68 @@ and [escape hatches][readme-escape-hatches].
```
+### Creating Dynamic ToastBars
+
+To create ToastBar components dynamically, make sure to add the ToastBar template inside the [``][mdn-template] tag.
+The `` tag must be inserted anywhere inside the Toast container. Our [JavaScript Toast plugin][web-toast-js-plugin] will then pick up
+the template and apply it on any toasts to be shown to the user, using the configuration provided.
+The template `ToastBar` has to have the `isTemplate` prop set.
+
+⚠️ In order to make the dynamic ToastBar icons work, you need to include the SVG sprites in your project. You
+can use the `Icon` component with `isSymbol` prop. Otherwise, the icons will not be displayed as the JS plugin
+does not render the icons by itself, it just sets the `use` tag with the correct `xlink:href` attribute.
+Also, do not forget to set the `hidden` attribute on the wrapping element to hide the icons from the screen.
+
+```twig
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+Or preconfigure the template with some default values:
+
+```twig
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+Then configure and create a new Toast instance and call the `show` method on it, for example:
+
+```js
+import Toast from '@lmc-eu/spirit-web/dist/js/Toast';
+
+const toast = new Toast(null, {
+ 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
+ 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
+ isDismissible: true,
+});
+
+toast.show();
+```
+
## JavaScript Plugin
For full functionality you need to provide JavaScript which will handle the toggling of the Toast component.
@@ -270,15 +352,17 @@ Or feel free to write controlling scripts yourself.
👉 Check the [component's docs in the web package][web-js-api] to see the full documentation and API of the plugin.
-[web-toast]: https://github.com/lmc-eu/spirit-design-system/tree/main/packages/web/src/scss/components/Toast
-[web-readme]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web/README.md
-[web-js-api]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web/src/scss/components/Toast/README.md#javascript-api
-[mdn-role-log]: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/log_role
-[mdn-aria-live]: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-live
[dictionary-alignment]: https://github.com/lmc-eu/spirit-design-system/blob/main/docs/DICTIONARIES.md#alignment
[dictionary-color]: https://github.com/lmc-eu/spirit-design-system/blob/main/docs/DICTIONARIES.md#color
+[icon-package]: https://github.com/lmc-eu/spirit-design-system/tree/main/packages/icons
+[mdn-aria-live]: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-live
+[mdn-role-log]: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/log_role
+[mdn-template]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template
[readme-additional-attributes]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-twig/README.md#additional-attributes
[readme-escape-hatches]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-twig/README.md#escape-hatches
[readme-style-props]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-twig/README.md#style-props
[scott-o-hara-toast]: https://www.scottohara.me/blog/2019/07/08/a-toast-to-a11y-toasts.html
-[icon-package]: https://github.com/lmc-eu/spirit-design-system/tree/main/packages/icons
+[web-js-api]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web/src/scss/components/Toast/README.md#javascript-api
+[web-readme]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web/README.md
+[web-toast-js-plugin]: https://github.com/lmc-eu/spirit-design-system/tree/main/packages/web/src/scss/components/Toast#javascript-plugin-api
+[web-toast]: https://github.com/lmc-eu/spirit-design-system/tree/main/packages/web/src/scss/components/Toast
diff --git a/packages/web-twig/src/Resources/components/Toast/Toast.stories.twig b/packages/web-twig/src/Resources/components/Toast/Toast.stories.twig
index f8ddff32ff..56d0abb390 100644
--- a/packages/web-twig/src/Resources/components/Toast/Toast.stories.twig
+++ b/packages/web-twig/src/Resources/components/Toast/Toast.stories.twig
@@ -2,8 +2,12 @@
{% block content %}
-
- {% include '@components/Toast/stories/ToastAlignment.twig' %}
+
+ {% include '@components/Toast/stories/ToastStaticToast.twig' %}
+
+
+
+ {% include '@components/Toast/stories/ToastDynamicToastQueue.twig' %}
diff --git a/packages/web-twig/src/Resources/components/Toast/Toast.twig b/packages/web-twig/src/Resources/components/Toast/Toast.twig
index 41513b8572..97918720b4 100644
--- a/packages/web-twig/src/Resources/components/Toast/Toast.twig
+++ b/packages/web-twig/src/Resources/components/Toast/Toast.twig
@@ -1,10 +1,12 @@
{# API #}
{%- set props = props | default([]) -%}
-{%- set _alignmentX = props.alignmentX | default('center') %}
-{%- set _alignmentY = props.alignmentY | default('bottom') %}
+{%- set _alignmentX = props.alignmentX | default('center') -%}
+{%- set _alignmentY = props.alignmentY | default('bottom') -%}
+{%- set _isCollapsible = props.isCollapsible ?? true -%}
{# Class names #}
{%- set _rootClassName = _spiritClassPrefix ~ 'Toast' -%}
+{%- set _rootCollapsibleClassName = _isCollapsible ? _spiritClassPrefix ~ 'Toast--collapsible' : null -%}
{%- set _queueClassName = _spiritClassPrefix ~ 'Toast__queue' -%}
{# Miscellaneous #}
@@ -22,7 +24,7 @@
{%- endif -%}
{%- endfor -%}
-{%- set _classNames = [ _rootClassName, _styleProps.className ] | merge(_alignmentClasses) -%}
+{%- set _classNames = [ _rootClassName, _rootCollapsibleClassName, _styleProps.className ] | merge(_alignmentClasses) -%}
-
+
{% block content %}{% endblock %}
diff --git a/packages/web-twig/src/Resources/components/Toast/ToastBar.twig b/packages/web-twig/src/Resources/components/Toast/ToastBar.twig
index 011bb65ac2..d865fad5ae 100644
--- a/packages/web-twig/src/Resources/components/Toast/ToastBar.twig
+++ b/packages/web-twig/src/Resources/components/Toast/ToastBar.twig
@@ -6,15 +6,17 @@
{%- set _iconName = props.iconName | default(null) -%}
{%- set _id = props.id | default(null) -%}
{%- set _isDismissible = props.isDismissible | default(false) -%}
+{%- set _isTemplate = props.isTemplate | default(false) -%}
{%- set _isOpen = props.isOpen ?? true -%}
{# Class names #}
{%- set _rootClassName = _spiritClassPrefix ~ 'ToastBar' -%}
{%- set _rootColorClassName = _spiritClassPrefix ~ 'ToastBar--' ~ _color -%}
-{%- set _rootDismissibleClassName = _isDismissible is same as(true) ? _spiritClassPrefix ~ 'ToastBar--dismissible' : null -%}
+{%- set _rootDismissibleClassName = _isDismissible is same as(true) and _isTemplate is same as(false) ? _spiritClassPrefix ~ 'ToastBar--dismissible' : null -%}
+{%- set _boxClassName = _spiritClassPrefix ~ 'ToastBar__box' -%}
{%- set _contentClassName = _spiritClassPrefix ~ 'ToastBar__content' -%}
{%- set _messageClassName = _spiritClassPrefix ~ 'ToastBar__message' -%}
-{%- set _isOpenClassName = _isOpen ? 'is-open' : 'is-hidden' -%}
+{%- set _isOpenClassName = _isOpen or _isTemplate ? 'is-open' : 'is-hidden' -%}
{# Attributes #}
{%- set _idAttr = _id ? 'id="' ~ _id | escape('html_attr') ~ '"' : null -%}
@@ -31,34 +33,44 @@
{%- set _iconNameByColor = 'danger' -%}
{% endif %}
{%- set _iconNameValue = _iconName | default(_iconNameByColor) -%}
-{%- set _mainPropsWithoutReservedAttributes = props | filter((value, prop) => prop is not same as('id')) -%}
+{%- set _mainPropsWithoutReservedAttributes = props | filter((value, prop) => prop not in ['id', 'data-spirit-populate-field']) -%}
-
- {% if _hasIcon or _iconName %}
-
- {% endif %}
-
- {% block content %}{% endblock %}
+
+
+ {% if _hasIcon or _iconName %}
+ {% if _isTemplate %}
+
+ {% else %}
+
+ {% endif %}
+ {% endif %}
+
+ {% block content %}{% endblock %}
+
+ {% if _isDismissible is same as(true) %}
+
+ {% endif %}
- {% if _isDismissible is same as(true) %}
-
- {% endif %}