/ proof-of-concept / takes
Sign in
proof-of-concept / takes
takes / src / components / Modal.tsx
56 lines · 1.6 KBBlameHistoryRaw
1import { useEffect, type ReactNode } from 'react';
3export function Modal({ title, onClose, children, footer, wide }: { title: string; onClose: () => void; children: ReactNode; footer?: ReactNode; wide?: boolean }) {
4 useEffect(() => {
5 const onKey = (e: KeyboardEvent) => {
6 if (e.key === 'Escape') onClose();
7 };
8 window.addEventListener('keydown', onKey);
9 return () => window.removeEventListener('keydown', onKey);
10 }, [onClose]);
11 return (
12 <div className="modal-backdrop" onMouseDown={(e) => e.target === e.currentTarget && onClose()}>
13 <div className={`modal${wide ? ' wide' : ''}`} role="dialog" aria-modal="true" aria-label={title}>
14 <h2>{title}</h2>
15 <div className="modal-body">{children}</div>
16 {footer && <div className="modal-footer">{footer}</div>}
17 </div>
18 </div>
19 );
22export interface ConfirmRequest {
23 title: string;
24 message: ReactNode;
25 confirmLabel: string;
26 danger?: boolean;
27 onConfirm: () => void | Promise<void>;
30export function ConfirmDialog({ req, onClose }: { req: ConfirmRequest; onClose: () => void }) {
31 return (
32 <Modal
33 title={req.title}
34 onClose={onClose}
35 footer={
36 <>
37 <button className="btn" onClick={onClose}>
38 Cancel
39 </button>
40 <button
41 className={`btn ${req.danger ? 'danger' : 'primary'}`}
42 autoFocus
43 onClick={async () => {
44 onClose();
45 await req.onConfirm();
46 }}
47 >
48 {req.confirmLabel}
49 </button>
50 </>
51 }
52 >
53 <p>{req.message}</p>
54 </Modal>
55 );
↑↓ move↵openescclose