import { useEffect, useId, useLayoutEffect, useRef, type ReactNode } from 'react' import { useBlocker } from 'react-router' import { Button } from '../ui/Button.tsx' import { Sheet } from '../ui/Sheet.tsx' import styles from './LeaveGuard.module.css' // LeaveGuard asks before leaving the screen with unsaved changes. export function LeaveGuard({ dirty }: { dirty: boolean }) { const blocker = useBlocker( ({ currentLocation, nextLocation }) => dirty && currentLocation.pathname !== nextLocation.pathname, ) useEffect(() => { if (!dirty) return const onBeforeUnload = (event: BeforeUnloadEvent) => event.preventDefault() window.addEventListener('beforeunload', onBeforeUnload) return () => window.removeEventListener('beforeunload', onBeforeUnload) }, [dirty]) return ( blocker.reset?.()} onConfirm={() => blocker.proceed?.()} > Your changes on this screen have not been saved. ) } type ConfirmProps = { open: boolean title: string children: ReactNode confirmLabel: string cancelLabel?: string busy?: boolean // When false, the cancel button, Escape and the scrim do nothing, so the // sheet stays until the action settles. dismissible?: boolean error?: string | null onCancel: () => void onConfirm: () => void } // Confirm is a sheet asking the household to confirm one action. export function Confirm({ open, title, children, confirmLabel, cancelLabel = 'Cancel', busy = false, dismissible = true, error = null, onCancel, onConfirm, }: ConfirmProps) { const titleId = useId() const sheetRef = useRef(null) // A focused button that becomes disabled either keeps focus while disabled or // loses it to the page, depending on the browser; the dialog takes it instead. useLayoutEffect(() => { const dialog = sheetRef.current?.closest('[role="dialog"]') if (!dialog) return const active = document.activeElement const lost = active === null || active === document.body || (dialog.contains(active) && active.matches(':disabled')) if (lost) dialog.focus() }, [busy, dismissible]) function dismiss() { if (dismissible) onCancel() } return ( {title} {children} {error !== null && ( {error} )} {cancelLabel} {confirmLabel} ) }
{children}
{error}