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/double-check-button.tsx

59 lines · 1.4 KB · TypeScript
/** @jsxRuntime automatic */
/** @jsxImportSource remix/ui */
import { clientEntry, on, type Handle, type RemixNode } from 'remix/ui'

/**
 * Two-step confirm for destructive form submits (progressive enhancement).
 * SSR / no-JS: `type="submit"` so the parent form POSTs normally.
 * With JS: first click arms (preventDefault); second click submits; blur resets.
 * Pass `icon` for a Lucide (or other) glyph beside the label — labeled buttons stay tip-free.
 */
export const DoubleCheckButton = clientEntry(
	'kody:app#DoubleCheckButton',
	function DoubleCheckButton(
		handle: Handle<{
			label?: string
			confirmLabel?: string
			className?: string
			icon?: RemixNode
		}>,
	) {
		let armed = false
		const label = handle.props.label ?? 'Delete'
		const confirmLabel = handle.props.confirmLabel ?? 'Are you sure?'

		return () => (
			<button
				type="submit"
				class={handle.props.className ?? 'pak-btn pak-btn-danger'}
				data-double-check
				data-armed={armed ? 'true' : 'false'}
				aria-pressed={armed ? 'true' : 'false'}
				mix={[
					on('click', (event) => {
						if (!armed) {
							event.preventDefault()
							armed = true
							handle.update()
						}
					}),
					on('blur', () => {
						if (armed) {
							armed = false
							handle.update()
						}
					}),
				]}
			>
				{armed ? (
					confirmLabel
				) : (
					<span class="pak-cluster pak-gap-2">
						{handle.props.icon}
						{label}
					</span>
				)}
			</button>
		)
	},
)