Files
session-notify/src/client/ui/ToastLayer.js
T

63 lines
2.2 KiB
JavaScript

/**
* The light popup stack: the frame-wide overlay entry this package owns.
*
* @module @dsh-plugin/session-notify/client/ui/ToastLayer
*/
import * as React from 'react'
import { KindIcon, CloseIcon } from './icons.js'
import { seatTranslator, useOwnStore } from './hooks.js'
import { CSS } from './styles.js'
const h = React.createElement
export function ToastLayer(props) {
const snapshot = useOwnStore(props.store)
const toasts = snapshot.toasts
const tr = seatTranslator(props, props.fallbackT)
React.useEffect(() => {
if (toasts.length === 0) return undefined
const onKeyDown = (event) => {
if (event.key !== 'Escape' || event.defaultPrevented) return
const top = toasts[0]
if (top === undefined) return
event.preventDefault()
props.dismiss(top.id)
}
document.addEventListener('keydown', onKeyDown)
return () => {
document.removeEventListener('keydown', onKeyDown)
}
}, [toasts, props])
if (toasts.length === 0) return null
return h(React.Fragment, null,
h('style', null, CSS),
h('div', { className: 'dsn-stack', role: 'region', 'aria-live': 'polite' },
toasts.map((toast) => h('div', {
key: toast.id,
className: 'dsn-toast',
role: toast.kind === 'completion' ? 'status' : 'alert',
onMouseEnter: () => props.hold(toast.id),
onMouseLeave: () => props.arm(toast.id),
},
h('span', { className: `dsn-toast-icon is-${toast.kind}` }, h(KindIcon, { kind: toast.kind, size: 16 })),
h('div', { className: 'dsn-toast-text' },
h('div', { className: 'dsn-toast-title' }, toast.title),
h('div', { className: 'dsn-toast-desc' }, toast.body)),
h('div', { className: 'dsn-toast-actions' },
h('button', {
type: 'button',
className: 'dsn-toast-action',
onClick: () => {
props.dismiss(toast.id)
props.open(toast.sessionId)
},
}, tr('toast.view')),
h('button', {
type: 'button',
className: 'dsn-toast-close',
'aria-label': tr('toast.dismiss'),
onClick: () => props.dismiss(toast.id),
}, h(CloseIcon, { size: 14 })))))))
}