BLAST UI

Alert Dialog

Alert Dialog is a modal dialog that interrupts the user with important information and requires a response.

Code

import { motion, AnimatePresence } from "motion/react";
import React from "react";
import { createPortal } from "react-dom";
import { Portal } from "./portal";

export interface AlertDialogContext {
	isOpen: boolean;
	onClose: () => void;
	onOpen: () => void;
	onConfirm: () => void;
}

const AlertDialogContext = React.createContext<AlertDialogContext>({
	isOpen: false,
	onClose: () => {},
	onOpen: () => {},
	onConfirm: () => {},
});

export function useAlertDialog() {
	return React.useContext(AlertDialogContext);
}

export interface AlertDialogProps {
	children: React.ReactNode;
	isOpen?: boolean;
	defaultOpen?: boolean;
	onClose?: () => void;
	onOpen?: () => void;
	onConfirm?: () => void;
}

export function AlertDialog({
	children,
	isOpen,
	defaultOpen,
	onClose,
	onOpen,
	onConfirm,
}: AlertDialogProps) {
	const [open, setOpen] = React.useState(defaultOpen ?? false);

	function handleClose() {
		setOpen(false);
		onClose?.();
	}

	function handleOpen() {
		setOpen(true);
		onOpen?.();
	}

	function handleConfirm() {
		handleClose();
		onConfirm?.();
	}

	const state = isOpen ?? open;

	return (
		<AlertDialogContext.Provider
			value={{
				isOpen: state,
				onClose: handleClose,
				onOpen: handleOpen,
				onConfirm: handleConfirm,
			}}
		>
			{children}
		</AlertDialogContext.Provider>
	);
}

export interface AlertDialogTriggerProps
	extends React.ComponentProps<"button"> {}

export function AlertDialogTrigger(props: AlertDialogTriggerProps) {
	const { onOpen } = useAlertDialog();
	return (
		<button
			{...props}
			onClick={e => {
				onOpen();
				props.onClick?.(e);
			}}
		/>
	);
}

export interface AlertDialogContentProps
	extends React.ComponentProps<typeof motion.div> {
	children: React.ReactNode;
}

export function AlertDialogContent({
	children,
	...props
}: AlertDialogContentProps) {
	const { isOpen } = useAlertDialog();

	return (
		<AnimatePresence>
			{isOpen && (
				<Portal>
					<motion.div {...props}>{children}</motion.div>
				</Portal>
			)}
		</AnimatePresence>
	);
}

export interface AlertDialogBackdropProps
	extends React.ComponentProps<typeof motion.div> {}

export function AlertDialogBackdrop(props: AlertDialogBackdropProps) {
	const { isOpen } = useAlertDialog();

	return (
		<AnimatePresence>
			{isOpen && (
				<Portal>
					<motion.div {...props}></motion.div>
				</Portal>
			)}
		</AnimatePresence>
	);
}

export interface AlertDialogClose extends React.ComponentProps<"button"> {}

export function AlertDialogClose(props: AlertDialogClose) {
	const { onClose } = useAlertDialog();
	return (
		<button
			{...props}
			onClick={e => {
				onClose();
				props.onClick?.(e);
			}}
		/>
	);
}

export interface AlertDialogConfirm extends React.ComponentProps<"button"> {}

export function AlertDialogConfirm(props: AlertDialogConfirm) {
	const { onConfirm } = useAlertDialog();
	return (
		<button
			{...props}
			onClick={e => {
				onConfirm();
				props.onClick?.(e);
			}}
		/>
	);
}

Example