78 lines
2.4 KiB
JavaScript
78 lines
2.4 KiB
JavaScript
/**
|
||
* Hoover — components/toast.js
|
||
*
|
||
* ToastContainer component that renders queued toast notifications.
|
||
* Uses the toast/dismissToast state from api.js.
|
||
*
|
||
* Long messages (>200 chars or containing newlines) render compact —
|
||
* first line with an ellipsis — plus a "Details" button that opens a
|
||
* modal with the full text. Dismissal is only via the × button;
|
||
* hovering the toast pauses its auto-dismiss timer.
|
||
*/
|
||
|
||
import { h } from '../vdom.js';
|
||
import { _toasts, dismissToast } from '../api.js';
|
||
import { openModal } from './modal.js';
|
||
|
||
/** Messages longer than this (or containing newlines) render compact. */
|
||
const _LONG_MESSAGE_CHARS = 200;
|
||
|
||
function _isLong(message) {
|
||
return message.length > _LONG_MESSAGE_CHARS || message.includes('\n');
|
||
}
|
||
|
||
function _firstLine(message) {
|
||
return message.split('\n')[0].trim();
|
||
}
|
||
|
||
function _showDetails(t) {
|
||
openModal(
|
||
h('div', null,
|
||
h('h2', { class: 'modal-title' }, 'Details'),
|
||
h('div', { class: 'modal-body' },
|
||
h('pre', { class: 'toast-details-msg' }, t.message),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Render all pending toast notifications.
|
||
*
|
||
* @returns {VNode}
|
||
*/
|
||
export function ToastContainer() {
|
||
if (!_toasts.length) return h('#text', '');
|
||
|
||
const clsMap = {
|
||
info: 'toast-info',
|
||
success: 'toast-success',
|
||
error: 'toast-error',
|
||
warning: 'toast-warning',
|
||
};
|
||
|
||
return h('div', { class: 'toast' },
|
||
..._toasts.map(t => {
|
||
const long = _isLong(t.message);
|
||
return h('div', {
|
||
class: `toast-message ${clsMap[t.type] || clsMap.info}`,
|
||
'on:mouseover': () => { t.hovered = true; },
|
||
'on:mouseout': () => { t.hovered = false; },
|
||
},
|
||
h('span', { class: 'toast-text' + (long ? ' toast-text-long' : '') },
|
||
long ? _firstLine(t.message) : t.message),
|
||
h('div', { class: 'toast-actions' },
|
||
long ? h('button', {
|
||
class: 'toast-btn toast-details',
|
||
'on:click': (e) => { e.stopPropagation(); _showDetails(t); },
|
||
}, 'Details') : null,
|
||
h('button', {
|
||
class: 'toast-btn toast-close',
|
||
'on:click': (e) => { e.stopPropagation(); dismissToast(t.id); },
|
||
}, '\u00d7'),
|
||
),
|
||
);
|
||
}),
|
||
);
|
||
}
|