Skip to content

Kody is live

Watch the launch video — what Kody is, and why it exists.

← Public packages

@kentcdodds/package-app-kit

Design tokens, PWA install/update, About/version, cache helpers, and optional realtime notes sync for Kody package apps.

src/client/toast.ts

61 lines · 1.9 KB · TypeScript
/** Browser toaster — exposes window.pakToast from [data-toast-host]. */

export function bootToast() {
	const host = document.querySelector('[data-toast-host]')
	if (!host) return
	const timers = {}

	function dismiss(id) {
		const node = host.querySelector(`[data-toast-id="${id}"]`)
		if (node) node.remove()
		if (timers[id]) {
			clearTimeout(timers[id])
			delete timers[id]
		}
	}

	function show(input) {
		if (!input?.title) return null
		const id = String(input.id || `t${Date.now()}${Math.random().toString(16).slice(2)}`)
		dismiss(id)
		const tone = input.tone || 'default'
		const node = document.createElement('div')
		node.className = 'pak-toast'
		node.setAttribute('data-toast-id', id)
		node.setAttribute('data-tone', tone)
		const body = document.createElement('div')
		const title = document.createElement('strong')
		title.textContent = input.title
		body.appendChild(title)
		if (input.description) {
			const p = document.createElement('p')
			p.textContent = input.description
			body.appendChild(p)
		}
		node.appendChild(body)
		const actions = document.createElement('div')
		actions.className = 'pak-toast-actions'
		if (input.action?.label && typeof input.action.onClick === 'function') {
			const act = document.createElement('button')
			act.type = 'button'
			act.textContent = input.action.label
			act.addEventListener('click', () => input.action.onClick())
			actions.appendChild(act)
		}
		const close = document.createElement('button')
		close.type = 'button'
		close.setAttribute('data-toast-dismiss', '')
		close.setAttribute('aria-label', 'Dismiss')
		close.textContent = '×'
		close.addEventListener('click', () => dismiss(id))
		actions.appendChild(close)
		node.appendChild(actions)
		host.appendChild(node)
		let duration = input.durationMs
		if (duration == null) duration = input.action ? 0 : 3400
		if (duration > 0) timers[id] = setTimeout(() => dismiss(id), duration)
		return id
	}

	window.pakToast = { show, dismiss }
}