import { html, css, TemplateResult, nothing } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { OmniElement } from '../core/OmniElement.js';
import { type ShowToastInit, ToastStack } from './ToastStack.js';
import '../icons/Close.icon.js';
/**
* Component to visually notify a user of a message.
*
* @import
* ```js
* import '@capitec/omni-components/toast';
* ```
*
* @example
* ```html
*
*
* ```
*
* @element omni-toast
*
* Registry of all properties defined by the component.
*
* @slot prefix - Content to render before toast message area.
* @slot - Content to render inside the component message area.
* @slot close - Content to render as the close button when `closeable`.
*
* @fires close-click - Dispatched when the close button is clicked when `closeable`.
*
* @cssprop --omni-toast-min-width - Min Width.
* @cssprop --omni-toast-max-width - Max Width.
* @cssprop --omni-toast-width - Width.
* @cssprop --omni-toast-z-index - The z-index.
* @cssprop --omni-toast-border-width - Border width.
* @cssprop --omni-toast-border-radius - Border radius.
* @cssprop --omni-toast-box-shadow - Box shadow.
* @cssprop --omni-toast-padding - Container padding.
* @cssprop --omni-toast-horizontal-gap - Horizontal spacing between icon from `type` and content.
* @cssprop --omni-toast-icon-size - Symmetrical size of icon from `type`.
*
* @cssprop --omni-toast-header-font-family - Font family for header.
* @cssprop --omni-toast-header-font-size - Font size for header.
* @cssprop --omni-toast-header-font-weight - Font weight for header.
* @cssprop --omni-toast-header-line-height - Line height for header.
*
* @cssprop --omni-toast-detail-font-family - Font family for detail.
* @cssprop --omni-toast-detail-font-size - Font size for detail.
* @cssprop --omni-toast-detail-font-weight - Font weight for detail.
* @cssprop --omni-toast-detail-line-height - Line height for detail.
* @cssprop --omni-toast-vertical-gap - Vertical space between detail and header.
*
* @cssprop --omni-toast-background - The default background applied when no `type` is set.
* @cssprop --omni-toast-default-font-color - The default font color applied when no `type` is set.
* @cssprop --omni-toast-border-color - Border color. *
*
* @cssprop --omni-toast-success-background - The background applied when `type` is set to `success`.
* @cssprop --omni-toast-success-font-color - The font color applied when `type` is set to `success`.
* @cssprop --omni-toast-success-border-color - The border color applied when `type` is set to `success`.
* @cssprop --omni-toast-success-icon-color - The icon color applied when `type` is set to `success`.
*
* @cssprop --omni-toast-warning-background - The background applied when `type` is set to `warning`.
* @cssprop --omni-toast-warning-font-color - The font color applied when `type` is set to `warning`.
* @cssprop --omni-toast-warning-border-color - The border color applied when `type` is set to `warning`.
* @cssprop --omni-toast-warning-icon-color - The icon color applied when `type` is set to `warning`.
*
* @cssprop --omni-toast-error-background - The background applied when `type` is set to `error`.
* @cssprop --omni-toast-error-font-color - The font color applied when `type` is set to `error`.
* @cssprop --omni-toast-error-border-color - The border color applied when `type` is set to `error`.
* @cssprop --omni-toast-error-icon-color - The icon color applied when `type` is set to `error`.
*
* @cssprop --omni-toast-info-background - The background applied when `type` is set to `info`.
* @cssprop --omni-toast-info-font-color - The font color applied when `type` is set to `info`.
* @cssprop --omni-toast-info-border-color - The border color applied when `type` is set to `info`.
* @cssprop --omni-toast-info-icon-color - The icon color applied when `type` is set to `info`.
*
* @cssprop --omni-toast-close-padding - Padding applied to close button when `closeable`.
* @cssprop --omni-toast-close-size - Symmetrical size applied to close button when `closeable`.
*
*/
@customElement('omni-toast')
export class Toast extends OmniElement {
/**
* The type of toast to display.
* @attr
*/
@property({ type: String, reflect: true }) type: 'success' | 'warning' | 'error' | 'info' | 'none' = 'none';
/**
* The toast title.
* @attr
*/
@property({ type: String, reflect: true }) header?: string;
/**
* The toast detail.
* @attr
*/
@property({ type: String, reflect: true }) detail?: string;
/**
* If true, will display a close button that fires a `close-click` event when clicked.
* @attr
*/
@property({ type: Boolean, reflect: true }) closeable?: boolean;
private static stack?: ToastStack & {
/**
* When true, will append toast to the toast stack. Otherwise when false will replace any toast(s). Defaults to true.
*/
stack?: boolean;
/**
* If true, will display a close button that fires a `close-click` event when clicked and removes the toast from the stack.
*/
closeable?: boolean;
/**
* If provided will be the time in milliseconds the toast is displayed before being automatically removed from the stack.
* Defaults to 3000ms when not provided.
* When set to 0 will amount to no timeout.
*/
duration?: number;
};
/**
* Global singleton {@link ToastStack} used for showing a toast, either by adding to, or replacing.
* Use `Toast.show` function to add or replace toasts to this instance.
*/
public static get current() {
if (!Toast.stack) {
Toast.stack = ToastStack.create({});
if (Toast.stack) {
Toast.stack.closeable = false;
Toast.stack.duration = 3000;
Toast.stack.stack = true;
}
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return Toast.stack!;
}
/**
* Configure the global singleton {@link ToastStack} used for showing a toast, either by adding to, or replacing.
* Use `Toast.show` function to add or replace toasts to this instance.
* @returns The global singleton {@link ToastStack} instance. It can also be accessed via `Toast.current` static property.
*/
public static configure(options: {
/**
* The position to stack toasts
*/
position?: 'top' | 'bottom' | 'left' | 'right' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
/**
* Reverse the order of toast with newest toasts showed on top of the stack. By default newest toasts are showed at the bottom of the stack.
*/
reverse?: boolean;
/**
* When true, will append toast to the toast stack. Otherwise when false will replace any toast(s). Defaults to true.
*/
stack?: boolean;
/**
* If true, will display a close button that fires a `close-click` event when clicked and removes the toast from the stack.
*/
closeable?: boolean;
/**
* If provided will be the time in millisecond the toast is displayed before being automatically removed from the stack.
* Defaults to 3000ms when not provided.
* When set to 0 it will never be auto removed.
*/
duration?: number;
}) {
const current = Toast.current;
if (options) {
if (options.position) {
current.position = options.position;
}
if (typeof options.reverse !== 'undefined') {
current.reverse = options.reverse;
}
if (typeof options.stack !== 'undefined') {
current.stack = options.stack;
}
if (typeof options.closeable !== 'undefined') {
current.closeable = options.closeable;
}
if (typeof options.duration !== 'undefined') {
current.duration = options.duration === 0 ? undefined : options.duration;
}
}
return current;
}
/**
* Show a toast message.
* @returns The {@link Toast} instance that was created.
*/
public static show(options: ShowToastInit) {
const current = Toast.current;
if (!current.stack) {
current.innerHTML = '';
}
// Apply default config if not specified
if (typeof options.closeable === 'undefined') {
options.closeable = current.closeable;
}
if (typeof options.duration === 'undefined') {
options.duration = current.duration;
}
return current.showToast(options);
}
private _raiseCloseClick(event: MouseEvent) {
// Notify any subscribers that the close button was clicked.
this.dispatchEvent(
new CustomEvent(`close-click`, {
detail: {}
})
);
// Prevent the event from bubbling up.
event.stopPropagation();
}
static override get styles() {
return [
super.styles,
css`
:host {
display: flex;
flex-direction: row;
box-sizing: border-box;
min-width: var(--omni-toast-min-width);
max-width: var(--omni-toast-max-width);
width: var(--omni-toast-width, 100%);
z-index: var(--omni-toast-z-index, 10000);
background: var(--omni-toast-background, var(--omni-background-color));
border-width: var(--omni-toast-border-width, 1px);
border-style: var(--omni-toast-border-style, solid);
border-color: var(--omni-toast-border-color, black);
border-radius: var(--omni-toast-border-radius, 10px);
box-shadow: var(--omni-toast-box-shadow, 0 6px 10px 0 rgba(0,0,0,0.25));
color: var(--omni-toast-default-font-color, --omni-font-color);
}
.container {
display: flex;
flex-direction: row;
justify-content: stretch;
align-items: center;
grid-template-columns: auto 1fr;
grid-template-rows: auto auto;
box-sizing: border-box;
min-width: var(--omni-toast-min-width);
max-width: var(--omni-toast-max-width);
width: var(--omni-toast-width, 100%);
padding: var(--omni-toast-padding, 10px);
}
/* Toast Box */
.type-icon {
grid-column: 1;
grid-row: 1/3;
margin-right: var(--omni-toast-horizontal-gap, 10px);
width: var(--omni-toast-icon-size, 24px);
height: var(--omni-toast-icon-size, 24px);
min-width: var(--omni-toast-icon-size, 24px);
min-height: var(--omni-toast-icon-size, 24px);
max-width: var(--omni-toast-icon-size, 24px);
max-height: var(--omni-toast-icon-size, 24px);
}
.content {
flex: 1 1 auto;
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
}
.content .header {
grid-column: 2;
grid-row: 1;
font-family: var(--omni-toast-header-font-family, var(--omni-font-family));
font-size: var(--omni-toast-header-font-size, 16px);
font-weight: var(--omni-toast-header-font-weight, bold);
line-height: var(--omni-toast-header-line-height, 1.2);
}
::slotted(*) {
font-family: var(--omni-toast-detail-font-family, var(--omni-font-family));
font-size: var(--omni-toast-detail-font-size, 16px);
font-weight: var(--omni-toast-detail-font-weight, normal);
line-height: var(--omni-toast-detail-line-height, 1.2);
}
.content .detail {
grid-column: 2;
grid-row: 2;
font-family: var(--omni-toast-detail-font-family, var(--omni-font-family));
font-size: var(--omni-toast-detail-font-size, 16px);
font-weight: var(--omni-toast-detail-font-weight, normal);
line-height: var(--omni-toast-detail-line-height, 1.2);
margin-top: var(--omni-toast-vertical-gap);
}
/* Info Status */
:host([type="info"]) {
background: var(--omni-toast-info-background, lightcyan);
border-color: var(--omni-toast-info-border-color, cyan);
}
:host([type="info"]) .header {
color: var(--omni-toast-info-font-color, var(--omni-toast-default-font-color, --omni-font-color));
}
:host([type="info"]) ::slotted(*) {
color: var(--omni-toast-info-font-color, var(--omni-toast-default-font-color, --omni-font-color));
}
:host([type="info"]) .detail {
color: var(--omni-toast-info-font-color, var(--omni-toast-default-font-color, --omni-font-color));
}
:host([type="info"]) .type-icon #shape {
stroke: var(--omni-toast-info-icon-color,var(--omni-toast-info-border-color, cyan));
}
:host([type="info"]) .type-icon #icon {
fill: var(--omni-toast-info-icon-color,var(--omni-toast-info-border-color, cyan));
}
/* Success Status */
:host([type="success"]) {
background: var(--omni-toast-success-background, lightgreen);
border-color: var(--omni-toast-success-border-color, darkgreen);
}
:host([type="success"]) .header {
color: var(--omni-toast-success-font-color, var(--omni-toast-default-font-color, --omni-font-color));
}
:host([type="success"]) ::slotted(*) {
color: var(--omni-toast-success-font-color, var(--omni-toast-default-font-color, --omni-font-color));
}
:host([type="success"]) .detail {
color: var(--omni-toast-success-font-color, var(--omni-toast-default-font-color, --omni-font-color));
}
:host([type="success"]) .type-icon #shape {
stroke: var(--omni-toast-success-icon-color,var(--omni-toast-success-border-color, darkgreen));
}
:host([type="success"]) .type-icon #icon {
fill: var(--omni-toast-success-icon-color,var(--omni-toast-success-border-color, darkgreen));
}
/* Error Status */
:host([type="error"]) {
background: var(--omni-toast-error-background, lightcoral);
border-color: var(--omni-toast-error-border-color, darkred);
}
:host([type="error"]) .header {
color: var(--omni-toast-error-font-color, var(--omni-toast-default-font-color, --omni-font-color));
}
:host([type="error"]) ::slotted(*) {
color: var(--omni-toast-error-font-color, var(--omni-toast-default-font-color, --omni-font-color));
}
:host([type="error"]) .detail{
color: var(--omni-toast-error-font-color, var(--omni-toast-default-font-color, --omni-font-color));
}
:host([type="error"]) .type-icon #shape {
stroke: var(--omni-toast-error-icon-color,var(--omni-toast-error-border-color, darkred));
}
:host([type="error"]) .type-icon #icon {
fill: var(--omni-toast-error-icon-color,var(--omni-toast-error-border-color, darkred));
}
/* Warning Status */
:host([type="warning"]) {
background: var(--omni-toast-warning-background, lightyellow);
border-color: var(--omni-toast-warning-border-color, orange);
}
:host([type="warning"]) .header {
color: var(--omni-toast-warning-font-color, var(--omni-toast-default-font-color, --omni-font-color));
}
:host([type="warning"]) ::slotted(*) {
color: var(--omni-toast-warning-font-color, var(--omni-toast-default-font-color, --omni-font-color));
}
:host([type="warning"]) .detail {
color: var(--omni-toast-warning-font-color, var(--omni-toast-default-font-color, --omni-font-color));
}
:host([type="warning"]) .type-icon #shape {
stroke: var(--omni-toast-warning-icon-color,var(--omni-toast-warning-border-color, orange));
}
:host([type="warning"]) .type-icon #icon {
fill: var(--omni-toast-warning-icon-color,var(--omni-toast-warning-border-color, orange));
}
.closer {
display: flex;
padding: var(--omni-toast-close-padding,4px);
}
.close-btn {
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: 0px;
margin: 0px;
width: var(--omni-toast-close-size,24px);
height: var(--omni-toast-close-size,24px);
cursor: pointer;
}
`
];
}
override render(): TemplateResult {
return html`