Task: Modal / Dialog Component
Type: Component
Milestone: M0.5 — Shared Component Library
Estimate: M
Component Type
Props Interface
interface ModalProps {
isOpen: boolean;
onClose: () => void;
title?: string;
description?: string;
children?: ReactNode;
footer?: ReactNode; // action buttons slot
size?: 'sm' | 'md' | 'lg';
isDismissable?: boolean; // if false, clicking backdrop does nothing
className?: string;
testId?: string;
}
Variants / States
| State |
Description |
| Closed |
Renders nothing — no DOM node present |
| Open |
Backdrop + centered dialog, entrance animation |
| With footer |
Action buttons (e.g. Confirm + Cancel) in bottom slot |
| Non-dismissable |
Backdrop click and ESC do nothing — for critical confirmations |
sm / md / lg |
Width variants: 360px / 480px / 640px |
Acceptance Criteria
Notes
ConfirmModal (used for Ban / Remove / Dismiss actions in M4) is built on top of this — validate the footer slot handles two side-by-side buttons before signing off
- Used also in M2 UserPanel drawer variant — keep
isDismissable default as true
Task: Modal / Dialog Component
Type: Component
Milestone: M0.5 — Shared Component Library
Estimate: M
Component Type
Props Interface
Variants / States
sm / md / lgAcceptance Criteria
createPortalintodocument.body— never trapped inside a positioned ancestorESCkey callsonClosewhenisDismissableis trueonClosewhenisDismissableis truebodyscroll locked while modal is openfooterslot renders below a divider, right-aligned by defaultNotes
ConfirmModal(used for Ban / Remove / Dismiss actions in M4) is built on top of this — validate the footer slot handles two side-by-side buttons before signing offisDismissabledefault astrue