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.

app/ui/busy-demo.tsx

62 lines · 1.5 KB · TypeScript
/** @jsxRuntime automatic */
/** @jsxImportSource remix/ui */
import { clientEntry, on, type Handle } from 'remix/ui'
import { createBusyGate } from './busy.ts'

/**
 * Home showcase: fake async job with spin-delay busy gate.
 * Immediate press affordance (pending); "Loading…" only after ~200ms;
 * once shown, stays ≥400ms.
 */
export const BusyDemo = clientEntry(
	'kody:app#BusyDemo',
	function BusyDemo(handle: Handle<{ label?: string }>) {
		const gate = createBusyGate({ delayMs: 200, minDurationMs: 400 })
		let doneFlash = false
		let flashTimer: ReturnType<typeof setTimeout> | null = null

		async function runJob() {
			if (gate.isActive) return
			doneFlash = false
			if (flashTimer) {
				clearTimeout(flashTimer)
				flashTimer = null
			}
			gate.start(() => handle.update())
			handle.update()
			try {
				await new Promise((r) => setTimeout(r, 800))
			} finally {
				await gate.stop(() => handle.update())
				doneFlash = true
				handle.update()
				flashTimer = setTimeout(() => {
					doneFlash = false
					handle.update()
				}, 1200)
			}
		}

		return () => {
			const active = gate.isActive
			const label = gate.showBusy
				? 'Loading…'
				: doneFlash
					? 'Done'
					: handle.props.label || 'Run demo job'

			return (
				<button
					type="button"
					class="pak-btn pak-btn-accent"
					data-pending={active ? 'true' : undefined}
					aria-busy={active ? 'true' : undefined}
					disabled={active}
					mix={on('click', () => void runJob())}
				>
					{label}
				</button>
			)
		}
	},
)