Skip to content

Modal and Dialogs

Modal and dialog components provide overlays for focused user interactions like confirmations, forms, and bottom sheet selections.

Generic modal wrapper with backdrop.

interface ModalProps {
open: boolean;
onClose: () => void;
title?: string;
children: ReactNode;
maxWidth?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
fullScreen?: boolean;
}

Pre-built confirmation dialog.

interface ConfirmationModalProps {
open: boolean;
onClose: () => void;
onConfirm: () => void;
title: string;
message: string;
confirmText?: string;
cancelText?: string;
severity?: 'info' | 'warning' | 'error' | 'success';
}

Mobile-friendly bottom sheet.

interface CustomBottomSheetProps {
open: boolean;
onClose: () => void;
title?: string;
children: ReactNode;
snapPoints?: number[];
}
import { ConfirmationModal } from 'src/components/confirmation-modal';
function DeleteGroupButton({ groupId }: Props) {
const [open, setOpen] = useState(false);
const handleConfirm = async () => {
await groupsHandlers.deleteGroup(groupId);
toast.success('Group deleted');
setOpen(false);
};
return (
<>
<Button onClick={() => setOpen(true)} color="error">
Delete Group
</Button>
<ConfirmationModal
open={open}
onClose={() => setOpen(false)}
onConfirm={handleConfirm}
title="Delete Group?"
message="This action cannot be undone. All members will be removed."
confirmText="Delete"
severity="error"
/>
</>
);
}
import { CustomBottomSheet } from 'src/components/custom-bottom-sheet';
function SelectWishlistSheet({ open, onClose, onSelect }: Props) {
const wishlists = useSelector((state) => state.wishlists.userWishlists);
return (
<CustomBottomSheet
open={open}
onClose={onClose}
title="Select Wishlist"
>
<List>
{wishlists.map((wishlist) => (
<ListItem
key={wishlist.id}
button
onClick={() => {
onSelect(wishlist);
onClose();
}}
>
<ListItemText primary={wishlist.name} />
</ListItem>
))}
</List>
</CustomBottomSheet>
);
}
import { Modal } from 'src/components/modal';
function EditGroupModal({ open, onClose, group }: Props) {
return (
<Modal
open={open}
onClose={onClose}
title="Edit Group"
maxWidth="md"
>
<EditGroupForm
group={group}
onSubmit={(data) => {
// Handle save
onClose();
}}
/>
</Modal>
);
}
  • Backdrop click: Closes modal by default (configurable)
  • Escape key: Closes modal
  • Focus trap: Focus stays within modal when open
  • Scroll lock: Body scroll disabled when modal open
  • Responsive: Bottom sheets on mobile, modals on desktop
it('should call onConfirm when confirmed', async () => {
const onConfirm = jest.fn();
render(
<ConfirmationModal
open
onClose={() => {}}
onConfirm={onConfirm}
title="Confirm"
message="Are you sure?"
/>
);
await userEvent.click(screen.getByText('Confirm'));
expect(onConfirm).toHaveBeenCalled();
});