+
${getIndentedTextHtml(props, slots)}
diff --git a/packages/components/src/components/image/style.css b/packages/components/src/components/image/style.css
index 73aedac2e9..41767094a7 100644
--- a/packages/components/src/components/image/style.css
+++ b/packages/components/src/components/image/style.css
@@ -1,3 +1,6 @@
+:host {
+ display: inline-block;
+}
img {
max-height: 100%;
max-width: 100%;
diff --git a/packages/components/src/components/input-checkbox/component.tsx b/packages/components/src/components/input-checkbox/component.tsx
index 76ba26b050..a1cc030309 100644
--- a/packages/components/src/components/input-checkbox/component.tsx
+++ b/packages/components/src/components/input-checkbox/component.tsx
@@ -30,11 +30,9 @@ import { API, InputCheckboxIconsProp, InputCheckboxVariant, States } from './typ
})
export class KolInputCheckbox implements API {
@Element() private readonly host?: HTMLKolInputCheckboxElement;
- private ref?: HTMLInputElement;
private readonly catchRef = (ref?: HTMLInputElement) => {
- this.ref = ref;
- propagateFocus(this.host, this.ref);
+ propagateFocus(this.host, ref);
};
public render(): JSX.Element {
@@ -52,7 +50,7 @@ export class KolInputCheckbox implements API {
}}
data-role={this.state._variant === 'button' ? 'button' : undefined}
onKeyPress={this.state._variant === 'button' ? this.onChange : undefined}
- tabIndex={this.state._variant === 'button' ? 0 : undefined}
+ tabIndex={this.state._variant === 'button' && !this.state._disabled ? 0 : undefined}
_alert={this.state._alert}
_disabled={this.state._disabled}
_error={this.state._error}
diff --git a/packages/components/src/components/input-radio/component.tsx b/packages/components/src/components/input-radio/component.tsx
index 2d6b74c18b..58ec49feed 100644
--- a/packages/components/src/components/input-radio/component.tsx
+++ b/packages/components/src/components/input-radio/component.tsx
@@ -31,11 +31,9 @@ import { API, States } from './types';
})
export class KolInputRadio implements API {
@Element() private readonly host?: HTMLKolInputRadioElement;
- private ref?: HTMLInputElement;
private readonly catchRef = (ref?: HTMLInputElement) => {
- this.ref = ref;
- propagateFocus(this.host, this.ref);
+ propagateFocus(this.host, ref);
};
public render(): JSX.Element {
diff --git a/packages/components/src/components/link-button/component.tsx b/packages/components/src/components/link-button/component.tsx
index 4925cc503a..06322f8766 100644
--- a/packages/components/src/components/link-button/component.tsx
+++ b/packages/components/src/components/link-button/component.tsx
@@ -24,11 +24,9 @@ import { Props } from './types';
})
export class KolLinkButton implements Props {
@Element() private readonly host?: HTMLKolLinkButtonElement;
- private ref?: HTMLKolLinkWcElement;
private readonly catchRef = (ref?: HTMLKolLinkWcElement) => {
- this.ref = ref;
- propagateFocus(this.host, this.ref);
+ propagateFocus(this.host, ref);
};
public render(): JSX.Element {
diff --git a/packages/components/src/components/link/readme.md b/packages/components/src/components/link/readme.md
index d08739e564..49fc1cd5fe 100644
--- a/packages/components/src/components/link/readme.md
+++ b/packages/components/src/components/link/readme.md
@@ -33,22 +33,18 @@ Eingabe von Leerzeichen eingefügt werden. Zusätzliche Leerzeichen vergrößern
### Link innerhalb eines Fließtextes mit Icon und Text
-
- Ich bin ein Link mit Icon links
-
- Ich bin ein Link mit Icon rechts
-
- Lorem, ipsum dolor sit amet consectetur adipisicing elit. Ea optio deleniti fuga quos molestias, voluptate nobis
- nemo, incidunt excepturi facilis, amet ducimus minus quae corporis eligendi cum distinctio. Fugit, repellendus.
- Ich bin ein Link mit Icon links. Lorem, ipsum dolor sit amet consectetur adipisicing
- elit. Ea optio deleniti fuga quos molestias, voluptate nobis nemo, incidunt excepturi
- Ich bin ein Link mit Icon rechts
-
- facilis, amet ducimus minus quae corporis eligendi cum distinctio. Fugit, repellendus.
-
+
+ Ich bin ein Link mit Icon links
+
+ Ich bin ein Link mit Icon rechts
+
+ Lorem, ipsum dolor sit amet consectetur adipisicing elit. Ea optio deleniti fuga quos molestias, voluptate nobis
+ nemo, incidunt excepturi facilis, amet ducimus minus quae corporis eligendi cum distinctio. Fugit, repellendus.
+ Ich bin ein Link mit Icon links. Lorem, ipsum dolor sit amet consectetur adipisicing
+ elit. Ea optio deleniti fuga quos molestias, voluptate nobis nemo, incidunt excepturi
+ Ich bin ein Link mit Icon rechts
+ facilis, amet ducimus minus quae corporis eligendi cum distinctio. Fugit, repellendus.
+
-
-## Methods
-
-### `enqueue(toast: Toast) => Promise
`
-
-#### Returns
-
-Type: `Promise`
-
-## Dependencies
-
-### Depends on
-
-- [kol-button](../button)
-- [kol-toast](../toast)
-
-### Graph
-
-```mermaid
-graph TD;
- kol-toast-container --> kol-button
- kol-toast-container --> kol-toast
- kol-button --> kol-button-wc
- kol-button-wc --> kol-span-wc
- kol-button-wc --> kol-tooltip-wc
- kol-span-wc --> kol-icon
- kol-tooltip-wc --> kol-span-wc
- kol-toast --> kol-alert
- kol-alert --> kol-alert-wc
- kol-alert-wc --> kol-heading-wc
- kol-alert-wc --> kol-button-wc
- kol-alert-wc --> kol-icon
- style kol-toast-container fill:#f9f,stroke:#333,stroke-width:4px
-```
-
----
diff --git a/packages/components/src/components/toast-container/style.css b/packages/components/src/components/toast-container/style.css
deleted file mode 100644
index dddae75d3c..0000000000
--- a/packages/components/src/components/toast-container/style.css
+++ /dev/null
@@ -1,9 +0,0 @@
-:host {
- display: flex;
- flex-direction: column;
- position: fixed;
- z-index: 200;
-}
-.close-all {
- align-self: flex-end;
-}
diff --git a/packages/components/src/components/toast/component.tsx b/packages/components/src/components/toast/component.tsx
index 1e8c94ced5..02bbe8558a 100644
--- a/packages/components/src/components/toast/component.tsx
+++ b/packages/components/src/components/toast/component.tsx
@@ -1,37 +1,69 @@
-import { Component, h, JSX, Prop, State, Watch } from '@stencil/core';
+import { Component, h, Host, JSX, Prop, State, Watch } from '@stencil/core';
+import { HeadingLevel } from '../../types/heading-level';
+import { HasCloserPropType, validateHasCloser } from '../../types/props/has-closer';
import { LabelPropType, validateLabel } from '../../types/props/label';
+import { ShowPropType, validateShow } from '../../types/props/show';
import { KoliBriToastEventCallbacks } from '../../types/toast';
-import { setState, watchValidator } from '../../utils/prop.validators';
+import { setState, watchBoolean, watchNumber, watchValidator } from '../../utils/prop.validators';
import { AlertType } from '../alert/types';
-import { ToastStatus, toastStatusOptions } from '../toast-container/types';
+import { watchHeadingLevel } from '../heading/validation';
import { API, States } from './types';
/**
* @slot - Der Inhalt der Meldung.
+ * @deprecated - Use ToastService - see toaster
*/
@Component({
tag: 'kol-toast',
- shadow: true,
styleUrls: {
default: './style.css',
},
+ shadow: true,
})
export class KolToast implements API {
+ /**
+ * Defines whether the screen-readers should read out the notification.
+ */
+ @Prop() public _alert?: boolean = true;
+
+ /**
+ * Defines whether the element can be closed.
+ * @TODO: Change type back to `HasCloserPropType` after Stencil#4663 has been resolved.
+ */
+ @Prop() public _hasCloser?: boolean = false;
+
+ /**
+ * Deprecated: Gibt die Beschriftung der Komponente an.
+ * @deprecated Use _label.
+ */
+ @Prop() public _heading?: string = '';
+
/**
* Defines the visible or semantic label of the component (e.g. aria-label, label, headline, caption, summary, etc.).
*/
- @Prop() public _label!: LabelPropType;
+ @Prop() public _label?: LabelPropType;
+
+ /**
+ * Defines which H-level from 1-6 the heading has. 0 specifies no heading and is shown as bold text.
+ */
+ @Prop() public _level?: HeadingLevel = 1;
/**
- * Defines the event callback functions for the component.
+ * Gibt die EventCallback-Function für das Schließen des Toasts an.
*/
@Prop() public _on?: KoliBriToastEventCallbacks;
/**
- * Defines the current toast status.
+ * Makes the element show up.
+ * @TODO: Change type back to `ShowPropType` after Stencil#4663 has been resolved.
*/
- @Prop() public _status!: ToastStatus;
+ @Prop({ mutable: true, reflect: true }) public _show?: boolean = true;
+
+ /**
+ * Gibt an, wie viele Millisekunden der Toast eingeblendet werden soll.
+ */
+ @Prop() public _showDuration?: number = 10000;
/**
* Defines either the type of the component or of the components interactive element.
@@ -39,15 +71,36 @@ export class KolToast implements API {
@Prop() public _type?: AlertType = 'default';
@State() public state: States = {
- _label: '...',
- _status: 'adding',
+ _alert: true,
+ _level: 1,
+ _show: true,
};
+ @Watch('_alert')
+ public validateAlert(value?: boolean): void {
+ watchBoolean(this, '_alert', value);
+ }
+
+ @Watch('_hasCloser')
+ public validateHasCloser(value?: HasCloserPropType): void {
+ validateHasCloser(this, value);
+ }
+
+ @Watch('_heading')
+ public validateHeading(value?: string): void {
+ this.validateLabel(value);
+ }
+
@Watch('_label')
public validateLabel(value?: LabelPropType): void {
validateLabel(this, value);
}
+ @Watch('_level')
+ public validateLevel(value?: HeadingLevel): void {
+ watchHeadingLevel(this, value);
+ }
+
@Watch('_on')
public validateOn(value?: KoliBriToastEventCallbacks): void {
if (typeof value === 'object' && (typeof value?.onClose === 'function' || value.onClose === true)) {
@@ -55,15 +108,18 @@ export class KolToast implements API {
}
}
- @Watch('_status')
- public validateStatus(status?: ToastStatus): void {
- watchValidator(
- this,
- '_status',
- (status) => typeof status === 'string' && toastStatusOptions.includes(status),
- new Set('String {adding, settled, removing}'),
- status
- );
+ @Watch('_show')
+ public validateShow(value?: ShowPropType): void {
+ validateShow(this, value, { hooks: { afterPatch: this.handleShowAndDuration } });
+ }
+
+ @Watch('_showDuration')
+ public validateShowDuration(value?: number): void {
+ watchNumber(this, '_showDuration', value, {
+ hooks: {
+ afterPatch: this.handleShowAndDuration,
+ },
+ });
}
@Watch('_type')
@@ -78,13 +134,34 @@ export class KolToast implements API {
}
public componentWillLoad(): void {
- this.validateLabel(this._label);
+ this.validateAlert(this._alert);
+ this.validateHasCloser(this._hasCloser);
+ this.validateLabel(this._label || this._heading);
+ this.validateLevel(this._level);
this.validateOn(this._on);
- this.validateStatus(this._status);
+ this.validateShow(this._show);
+ this.validateShowDuration(this._showDuration);
this.validateType(this._type);
}
+ private durationTimeout?: number;
+
+ private readonly handleShowAndDuration = () => {
+ if (this.state._show === true && typeof this.state._showDuration === 'number' && this.state._showDuration >= 0) {
+ clearTimeout(this.durationTimeout);
+ this.durationTimeout = setTimeout(() => {
+ this.close();
+ }, this.state._showDuration) as unknown as number;
+ }
+ };
+
private readonly close = () => {
+ this._show = false;
+ this.state = {
+ ...this.state,
+ _show: false,
+ };
+
if (this._on?.onClose !== undefined) {
this._on.onClose(new Event('Close'));
}
@@ -96,11 +173,24 @@ export class KolToast implements API {
public render(): JSX.Element {
return (
-
-
-
-
-
+
+ {this.state._show && (
+
+
+
+
+
+ )}
+
);
}
}
diff --git a/packages/components/src/components/toast/readme.md b/packages/components/src/components/toast/readme.md
index 989a6c18db..528eaa159d 100644
--- a/packages/components/src/components/toast/readme.md
+++ b/packages/components/src/components/toast/readme.md
@@ -1,27 +1,24 @@
# Toast
-Mit der **Toast**-Komponente geben Sie ein optisches Feedback an die Nutzer:innen. Sie wird am Kopf des Browserfenster
-angezeigt, bis sie geschlossen wird. Werden mehrere Toasts geöffnet, ohne das die bisherigen geschlossen wurden, so werden diese untereinander angezeigt.
+Mit der **Toast**-Komponente geben Sie ein optisches Feedback an die Nutzer:innen. Sie wird nur für einen kurzen Zeitraum am Kopf des Browserfenster angezeigt und verschwindet danach automatisch.
-## Konstruktion
+Ein **Toast** wird nach dem Laden der Webseite am oberen Rand des Browserfenster für zehn Sekunden angezeigt. Mit Ausblenden des **Toasts** wird dieser automatisch aus dem DOM entfernt. Wird er erneut benötigt, muss er z.B. über eine JavaScript-Funktion nachgeladen werden.
-Die Toast-Komponente wird nicht direkt verwendet, sondern immer über den ToasterService konstruiert.
+## Konstruktion
### Code
-```js
-import { ToasterService } from '@public-ui/components';
+```html
+Hier wird der Erfolg näher beschrieben.
+Hier wird der Erfolg näher beschrieben.
+```
-// Get the toaster instance for the current HTML document.
-const toaster = ToasterService.getInstance(document);
+### Beispiel
-// Enqueue a new toast to the toaster to display:
-toaster.enqueue({
- label: 'This is the title',
- description: 'Magna eu sit adipisicing cillum amet esse in aute quis in dolore.',
- type: 'info',
-});
-```
+
+ Hier wird der Erfolg näher beschrieben.
+ Hier wird der Erfolg näher beschrieben.
+
## Verwendung
@@ -29,9 +26,17 @@ toaster.enqueue({
Verwenden Sie das Attribut **`_label`**, um die Überschrift des Toasts zu bestimmen.
-### Inhalt
+### Größe der Überschrift
+
+Verwenden Sie das Attribut **`_level`**, um die Überschriftenebene zu setzen.
+
+### Anzeigen des Toasts
+
+Verwenden Sie das Attribut **`_show`**, um den Toast manuell anzuzeigen.
-Verwenden Sie das Attribut **`_description`**, um den Text-Inhalt des Toasts zu bestimmen.
+### Anzeigedauer des Toast
+
+Verwenden Sie das Attribut **`_showDuration`**, um die Anzeigedauer des Toasts festzulegen.
### Anzeigetyp des Toast
@@ -43,16 +48,29 @@ Verwenden Sie das Attribut **`_type`**, um den Typ des Toasts festzulegen. Mögl
- `success`
- `warning`
+
+
+## Barrierefreiheit
+
+> **[DEPRECATED]** - Use ToastService - see toaster
+
## Properties
-| Property | Attribute | Description | Type | Default |
-| ---------------------- | --------- | ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------- | ----------- |
-| `_label` _(required)_ | `_label` | Defines the visible or semantic label of the component (e.g. aria-label, label, headline, caption, summary, etc.). | `string` | `undefined` |
-| `_on` | -- | Defines the event callback functions for the component. | `undefined \| { onClose?: EventCallback \| undefined; }` | `undefined` |
-| `_status` _(required)_ | `_status` | Defines the current toast status. | `"adding" \| "removing" \| "settled"` | `undefined` |
-| `_type` | `_type` | Defines either the type of the component or of the components interactive element. | `"default" \| "error" \| "info" \| "success" \| "warning" \| undefined` | `'default'` |
+| Property | Attribute | Description | Type | Default |
+| --------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | ----------- |
+| `_alert` | `_alert` | Defines whether the screen-readers should read out the notification. | `boolean \| undefined` | `true` |
+| `_hasCloser` | `_has-closer` | Defines whether the element can be closed. | `boolean \| undefined` | `false` |
+| `_heading` | `_heading` | **[DEPRECATED]** Use \_label.
Deprecated: Gibt die Beschriftung der Komponente an. | `string \| undefined` | `''` |
+| `_label` | `_label` | Defines the visible or semantic label of the component (e.g. aria-label, label, headline, caption, summary, etc.). | `string \| undefined` | `undefined` |
+| `_level` | `_level` | Defines which H-level from 1-6 the heading has. 0 specifies no heading and is shown as bold text. | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| undefined` | `1` |
+| `_on` | -- | Gibt die EventCallback-Function für das Schließen des Toasts an. | `undefined \| { onClose?: EventCallback \| undefined; }` | `undefined` |
+| `_show` | `_show` | Makes the element show up. | `boolean \| undefined` | `true` |
+| `_showDuration` | `_show-duration` | Gibt an, wie viele Millisekunden der Toast eingeblendet werden soll. | `number \| undefined` | `10000` |
+| `_type` | `_type` | Defines either the type of the component or of the components interactive element. | `"default" \| "error" \| "info" \| "success" \| "warning" \| undefined` | `'default'` |
## Slots
@@ -62,10 +80,6 @@ Verwenden Sie das Attribut **`_type`**, um den Typ des Toasts festzulegen. Mögl
## Dependencies
-### Used by
-
-- [kol-toast-container](../toast-container)
-
### Depends on
- [kol-alert](../alert)
@@ -83,7 +97,6 @@ graph TD;
kol-button-wc --> kol-tooltip-wc
kol-span-wc --> kol-icon
kol-tooltip-wc --> kol-span-wc
- kol-toast-container --> kol-toast
style kol-toast fill:#f9f,stroke:#333,stroke-width:4px
```
diff --git a/packages/components/src/components/toast/style.css b/packages/components/src/components/toast/style.css
index 24668ade8d..f04c4a8c80 100644
--- a/packages/components/src/components/toast/style.css
+++ b/packages/components/src/components/toast/style.css
@@ -1 +1,24 @@
-/* Placeholder file to allow theme styles to be applied. */
+@import url(../style.css);
+@import url(../host-display-block.css);
+:host > div {
+ background-color: #fff;
+ /* needed for overlay situations */
+ height: 0;
+ left: 0;
+ position: fixed;
+ top: 0;
+ width: 100%;
+ z-index: 200;
+}
+:host > div > kol-alert {
+ display: block;
+ margin: auto;
+ max-width: 750px;
+}
+:host > div > kol-button-wc {
+ display: block;
+ margin: auto;
+ position: relative;
+ top: 0;
+ width: 1em;
+}
diff --git a/packages/components/src/components/toast/test/html.mock.ts b/packages/components/src/components/toast/test/html.mock.ts
index e489679086..e24eaef093 100644
--- a/packages/components/src/components/toast/test/html.mock.ts
+++ b/packages/components/src/components/toast/test/html.mock.ts
@@ -1,24 +1,37 @@
import { mixMembers } from 'stencil-awesome-test';
-import { Props, States } from '../types';
+import { getAlertHtml } from '../../alert/test/html.mock';
+import { Props } from '../types';
export const getToastHtml = (props: Props): string => {
- const state = mixMembers(
+ props = mixMembers(
{
- _label: '...',
- _status: 'adding',
- _type: 'default',
+ _alert: true,
+ _level: 1,
+ _show: true,
},
props
);
return `
-
-
-
-
-
-
-
-
+
+
+ ${
+ props._show === true
+ ? `
+ ${getAlertHtml(
+ {
+ _alert: props._alert,
+ _label: props._label,
+ _level: props._level,
+ _type: props._type,
+ _variant: 'card',
+ },
+ ''
+ // ' tabindex="0"'
+ )}
+
`
+ : ''
+ }
+
`;
};
diff --git a/packages/components/src/components/toast/test/snapshot.spec.tsx b/packages/components/src/components/toast/test/snapshot.spec.tsx
index 9ded42fbf9..bb5c70f0a7 100644
--- a/packages/components/src/components/toast/test/snapshot.spec.tsx
+++ b/packages/components/src/components/toast/test/snapshot.spec.tsx
@@ -3,7 +3,7 @@ import { executeTests } from 'stencil-awesome-test';
import { h } from '@stencil/core';
import { newSpecPage, SpecPage } from '@stencil/core/testing';
-import { KolToast } from '../component';
+import { COMPONENTS } from '../../component-list';
import { Props } from '../types';
import { getToastHtml } from './html.mock';
@@ -11,15 +11,17 @@ executeTests(
'Toast',
async (props): Promise => {
const page = await newSpecPage({
- components: [KolToast], // Use the stubbed version of child components to work around https://github.com/ionic-team/stencil/issues/3220
+ components: COMPONENTS,
template: () => ,
});
return page;
},
{
+ _alert: [false, true],
_label: ['Überschrift'],
+ _level: [1, 2, 3, 4, 5, 6],
+ _show: [false, true],
_type: ['default', 'error', 'info', 'success', 'warning'],
- _status: ['adding', 'settled', 'removing'],
},
getToastHtml,
{
diff --git a/packages/components/src/components/toast/toaster.tsx b/packages/components/src/components/toast/toaster.tsx
index f44c8d6f18..ed5e8ff78a 100644
--- a/packages/components/src/components/toast/toaster.tsx
+++ b/packages/components/src/components/toast/toaster.tsx
@@ -1,13 +1,48 @@
-import { Toast } from '../toast-container/types';
+import { LabelPropType } from '../../types/props/label';
+import { AlertType } from '../alert/types';
+type Toast = {
+ description: string;
+ /**
+ * @deprecated Use label.
+ */
+ heading?: string;
+ label?: LabelPropType;
+ type: AlertType;
+};
+
+/**
+ * @deprecated Use toast/toaster.tsx
+ */
export class ToasterService {
private static readonly instances: Map = new Map();
- private toastContainerElement?: HTMLKolToastContainerElement;
+ private toastElement?: HTMLKolToastElement;
+
+ private readonly queue: Set = new Set();
+
+ private isOpen = false;
private constructor(private readonly document: Document) {
- this.toastContainerElement = this.document.createElement('kol-toast-container');
- this.document.body.prepend(this.toastContainerElement);
+ this.toastElement = this.document.createElement('kol-toast');
+ this.toastElement.setAttribute('_level', '0');
+ this.toastElement.setAttribute('_show', 'false');
+ this.toastElement.setAttribute('_show-duration', '-1'); // @deprecated in v2
+ this.toastElement.setAttribute('_has-closer', 'true');
+ this.toastElement._on = {
+ onClose: () => {
+ const next = this.queue.values().next();
+ if (next.value) {
+ this.queue.delete(next.value as Toast);
+ setTimeout(() => {
+ this.showToast(next.value as Toast);
+ }, 200);
+ } else {
+ this.isOpen = false;
+ }
+ },
+ };
+ this.document.body.insertBefore(this.toastElement, this.document.body.firstChild);
}
/**
@@ -23,16 +58,48 @@ export class ToasterService {
}
public dispose() {
- const element = this.toastContainerElement;
+ const element = this.toastElement;
if (element) {
- this.toastContainerElement = undefined;
+ this.toastElement = undefined;
+ this.queue.clear();
element.remove();
+
+ const on = element._on;
+ if (on && on.onClose) {
+ on.onClose(new Event('dispose'));
+ }
} else {
console.warn('Toaster service is already disposed.');
}
}
- public async enqueue(toast: Toast) {
- await this.toastContainerElement?.enqueue(toast);
+ /**
+ * Reiht einen neuen Toast in die Warteschlange ein, um ihn anzuzeigen.
+ */
+ public enqueue(data: Toast): void {
+ if (this.isOpen) {
+ this.queue.add(data);
+ } else {
+ this.showToast(data);
+ }
+ }
+
+ private showToast(data: Toast): void {
+ const label = data.label || data.heading;
+
+ if (typeof label === 'undefined') {
+ // TODO v2: Make label required, remove this check.
+ throw new Error('Toast requires a label.');
+ }
+
+ if (!this.toastElement) {
+ console.warn('Tried to show a new toast at a disposed toaster service!');
+ return;
+ }
+ this.toastElement.setAttribute('_label', label);
+ this.toastElement.setAttribute('_show', 'true');
+ this.toastElement.setAttribute('_type', data.type);
+ this.toastElement.innerText = data.description;
+ this.isOpen = true;
}
}
diff --git a/packages/components/src/components/toast/types.ts b/packages/components/src/components/toast/types.ts
index 7fa687e9c8..cfd5064940 100644
--- a/packages/components/src/components/toast/types.ts
+++ b/packages/components/src/components/toast/types.ts
@@ -1,17 +1,26 @@
import { Generic } from '@a11y-ui/core';
+import { HeadingLevel } from '../../types/heading-level';
+import { PropHasCloser } from '../../types/props/has-closer';
import { PropLabel } from '../../types/props/label';
+import { PropShow } from '../../types/props/show';
import { KoliBriToastEventCallbacks } from '../../types/toast';
import { AlertType } from '../alert/types';
-import { ToastStatus } from '../toast-container/types';
-type RequiredProps = PropLabel & {
- status: ToastStatus;
-};
+type RequiredProps = NonNullable;
type OptionalProps = {
+ alert: boolean;
+ /**
+ * @deprecated Use label.
+ */
+ heading: string;
+ level: HeadingLevel;
on: KoliBriToastEventCallbacks;
+ showDuration: number;
type: AlertType;
-};
+} & PropHasCloser &
+ PropShow &
+ PropLabel;
export type Props = Generic.Element.Members;
type RequiredStates = RequiredProps;
diff --git a/packages/components/src/components/toaster/InternalToast.tsx b/packages/components/src/components/toaster/InternalToast.tsx
new file mode 100644
index 0000000000..f8c0cac474
--- /dev/null
+++ b/packages/components/src/components/toaster/InternalToast.tsx
@@ -0,0 +1,32 @@
+import { h } from '@stencil/core';
+import { ToastState } from './types';
+
+type Props = {
+ toastState: ToastState;
+ onClose: () => void;
+ key: string;
+};
+export const InternalToast = ({ toastState, onClose, key }: Props) => {
+ const handleRef = (element?: HTMLDivElement) => {
+ if (typeof toastState.toast.render === 'function' && element) {
+ toastState.toast.render(element, { close: () => onClose() });
+ }
+ };
+
+ return (
+
+
+ {typeof toastState.toast.description === 'string' ? toastState.toast.description : null}
+
+
+ );
+};
diff --git a/packages/components/src/components/toast-container/component.tsx b/packages/components/src/components/toaster/component.tsx
similarity index 73%
rename from packages/components/src/components/toast-container/component.tsx
rename to packages/components/src/components/toaster/component.tsx
index 9e823dc50a..6f7a673d5a 100644
--- a/packages/components/src/components/toast-container/component.tsx
+++ b/packages/components/src/components/toaster/component.tsx
@@ -3,6 +3,7 @@ import { Component, Fragment, h, JSX, Method, State } from '@stencil/core';
import { translate } from '../../i18n';
import { nonce } from '../../utils/dev.utils';
import { API, States, Toast, ToastState } from './types';
+import { InternalToast } from './InternalToast';
const TRANSITION_TIMEOUT = 300;
@@ -21,7 +22,7 @@ export class KolToastContainer implements API {
// Stencil requires async function:
// eslint-disable-next-line @typescript-eslint/require-await
@Method()
- async enqueue(toast: Toast) {
+ public async enqueue(toast: Toast) {
const newToastState: ToastState = {
toast,
status: 'adding',
@@ -36,7 +37,7 @@ export class KolToastContainer implements API {
this.state = {
...this.state,
_toastStates: this.state._toastStates.map((localToastState) =>
- localToastState === newToastState
+ localToastState.id === newToastState.id
? {
...localToastState,
status: 'settled',
@@ -45,13 +46,17 @@ export class KolToastContainer implements API {
),
};
}, TRANSITION_TIMEOUT);
+
+ return () => {
+ this.handleClose(newToastState);
+ };
}
private handleClose(toastState: ToastState) {
this.state = {
...this.state,
_toastStates: this.state._toastStates.map((localToastState) => {
- if (localToastState === toastState) {
+ if (localToastState.id === toastState.id) {
localToastState.status = 'removing';
}
return localToastState;
@@ -61,12 +66,14 @@ export class KolToastContainer implements API {
setTimeout(() => {
this.state = {
...this.state,
- _toastStates: this.state._toastStates.filter((localToastState) => localToastState !== toastState),
+ _toastStates: this.state._toastStates.filter((localToastState) => localToastState.id !== toastState.id),
};
}, TRANSITION_TIMEOUT);
}
- private handleCloseAllClick() {
+ // eslint-disable-next-line @typescript-eslint/require-await
+ @Method()
+ public async closeAll() {
this.state = {
...this.state,
_toastStates: this.state._toastStates.map((localToastState) => ({
@@ -87,18 +94,18 @@ export class KolToastContainer implements API {
return (
{this.state._toastStates.length > 1 && (
-
+ {
+ void this.closeAll();
+ },
+ }}
+ >
)}
{this.state._toastStates.map((toastState) => (
- this.handleClose(toastState) }}
- key={toastState.id}
- >
- {toastState.toast.description}
-
+ this.handleClose(toastState)} key={toastState.id} />
))}
);
diff --git a/packages/components/src/components/toaster/readme.md b/packages/components/src/components/toaster/readme.md
new file mode 100644
index 0000000000..d62cf3e6a4
--- /dev/null
+++ b/packages/components/src/components/toaster/readme.md
@@ -0,0 +1,109 @@
+# Toaster
+
+Mit dem **Toast**-Service geben Sie ein optisches Feedback an die Nutzer:innen. Sie wird am Kopf des Browserfenster
+angezeigt, bis sie geschlossen wird. Werden mehrere Toasts geöffnet, ohne das die bisherigen geschlossen wurden, so werden diese untereinander angezeigt.
+
+## Konstruktion
+
+Die Toast-Komponenten werden nicht direkt verwendet, sondern immer über den ToasterService konstruiert.
+
+### Code
+
+```js
+import { ToasterService } from '@public-ui/components';
+
+// Get the toaster instance for the current HTML document.
+const toaster = ToasterService.getInstance(document);
+
+// Enqueue a new toast to the toaster to display:
+toaster.enqueue({
+ label: 'This is the title',
+ description: 'Magna eu sit adipisicing cillum amet esse in aute quis in dolore.',
+ type: 'info',
+});
+```
+
+### Weitere Service-Methoden
+
+- `closeAll`: Schließt alle Toasts
+- `dispose`: Entfernt den Toast Container. Die Toaster-Instanz kann nicht weiter genutzt werden.
+
+## Verwendung
+
+### Überschrift
+
+Verwenden Sie das Attribut **`_label`**, um die Überschrift des Toasts zu bestimmen.
+
+### Inhalt
+
+Verwenden Sie das Attribut **`_description`**, um den Text-Inhalt des Toasts zu bestimmen.
+
+Alternativ zur statischen Description können Sie über das Attribut **`_render`** eine eigene Render-Funktion definieren. Diese wird mit einer Referenz zum
+HTMLElement der Toast-Komponente aufgerufen. Zudem wird auch ein Objekt übergeben, das eine `close`-Funktion zum Schließen des Toasts bereitstellt.
+
+```ts
+const closeToast = toaster.enqueue({
+ render: (element: HTMLElement, { close }) => {
+ element.textContent = 'Mein Inhalt';
+ const customCloseButton = document.createElement('button');
+ customCloseButton.textContent = 'Toast schließen';
+ element.appendChild(customCloseButton);
+ customCloseButton.addEventListener('click', close, { once: true });
+ },
+});
+
+/* Optional: Toast wieder schließen mit `closeToast()` */
+```
+
+### Anzeigetyp des Toast
+
+Verwenden Sie das Attribut **`_type`**, um den Typ des Toasts festzulegen. Mögliche Werte sind:
+
+- `default`
+- `error`
+- `info`
+- `success`
+- `warning`
+
+
+
+## Methods
+
+### `closeAll() => Promise`
+
+#### Returns
+
+Type: `Promise`
+
+### `enqueue(toast: Toast) => Promise<() => void>`
+
+#### Returns
+
+Type: `Promise<() => void>`
+
+## Dependencies
+
+### Depends on
+
+- [kol-button](../button)
+- [kol-alert](../alert)
+
+### Graph
+
+```mermaid
+graph TD;
+ kol-toast-container --> kol-button
+ kol-toast-container --> kol-alert
+ kol-button --> kol-button-wc
+ kol-button-wc --> kol-span-wc
+ kol-button-wc --> kol-tooltip-wc
+ kol-span-wc --> kol-icon
+ kol-tooltip-wc --> kol-span-wc
+ kol-alert --> kol-alert-wc
+ kol-alert-wc --> kol-heading-wc
+ kol-alert-wc --> kol-button-wc
+ kol-alert-wc --> kol-icon
+ style kol-toast-container fill:#f9f,stroke:#333,stroke-width:4px
+```
+
+---
diff --git a/packages/components/src/components/toaster/style.css b/packages/components/src/components/toaster/style.css
new file mode 100644
index 0000000000..1f285155bf
--- /dev/null
+++ b/packages/components/src/components/toaster/style.css
@@ -0,0 +1,12 @@
+@layer kol-component {
+ :host {
+ display: flex;
+ flex-direction: column;
+ position: fixed;
+ z-index: 200;
+ }
+
+ .close-all {
+ align-self: flex-end;
+ }
+}
diff --git a/packages/components/src/components/toaster/toaster.tsx b/packages/components/src/components/toaster/toaster.tsx
new file mode 100644
index 0000000000..4082d20a63
--- /dev/null
+++ b/packages/components/src/components/toaster/toaster.tsx
@@ -0,0 +1,50 @@
+import { Toast } from './types';
+
+export class ToasterService {
+ private static readonly instances: Map = new Map();
+
+ private toastContainerElement?: HTMLKolToastContainerElement;
+
+ private constructor(private readonly document: Document) {
+ this.toastContainerElement = this.document.createElement('kol-toast-container');
+ this.document.body.prepend(this.toastContainerElement);
+ }
+
+ /**
+ * Get a toaster for the specified document environment. Each environment has exactly one instance of the service.
+ */
+ public static getInstance(document: Document) {
+ let instance = this.instances.get(document);
+ if (!instance) {
+ instance = new ToasterService(document);
+ this.instances.set(document, instance);
+ }
+ return instance;
+ }
+
+ public dispose() {
+ const element = this.toastContainerElement;
+ if (element) {
+ this.toastContainerElement = undefined;
+ element.remove();
+ } else {
+ console.warn('Toaster service is already disposed.');
+ }
+ }
+
+ public enqueue(toast: Toast): Promise<() => void> | undefined {
+ /**
+ * We need this condition for SSR. The toast container is not rendered on the server,
+ * so we can't enqueue toasts.
+ */
+ if (this.toastContainerElement && typeof this.toastContainerElement.enqueue === 'function') {
+ return this.toastContainerElement.enqueue(toast);
+ }
+ }
+
+ public closeAll(): void {
+ if (this.toastContainerElement && typeof this.toastContainerElement.closeAll === 'function') {
+ void this.toastContainerElement.closeAll();
+ }
+ }
+}
diff --git a/packages/components/src/components/toast-container/types.ts b/packages/components/src/components/toaster/types.ts
similarity index 75%
rename from packages/components/src/components/toast-container/types.ts
rename to packages/components/src/components/toaster/types.ts
index f08d5035ca..55d01d822b 100644
--- a/packages/components/src/components/toast-container/types.ts
+++ b/packages/components/src/components/toaster/types.ts
@@ -3,11 +3,12 @@ import { Generic } from '@a11y-ui/core';
import { LabelPropType } from '../../types/props/label';
import { AlertType } from '../alert/types';
-export const toastStatusOptions = ['adding', 'settled', 'removing'] as const;
-export type ToastStatus = (typeof toastStatusOptions)[number];
+const toastStatusOptions = ['adding', 'settled', 'removing'] as const;
+type ToastStatus = (typeof toastStatusOptions)[number];
export type Toast = {
- description: string;
+ description?: string;
+ render?: (nodeRef: HTMLElement, options: { close: () => void }) => void;
label: LabelPropType;
type: AlertType;
};
diff --git a/packages/components/src/index.html b/packages/components/src/index.html
index 54c45451f1..339e4e5dc4 100644
--- a/packages/components/src/index.html
+++ b/packages/components/src/index.html
@@ -81,10 +81,10 @@
_variant="normal"
:_on="{ onClick: ()=> showLogin = !showLogin }"
>
-
+
Benutzername
Passwort
-
+
@@ -238,7 +238,7 @@
-
+
@@ -1534,18 +1534,18 @@