feat(ui): require confirmation before delete actions
- add a reusable modal component and export it from the shared ui barrel - gate all delete and remove flows behind explicit confirmation dialogs - use a solid modal surface color token with fallback to avoid transparent body
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
.overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 2000;
|
||||
background: color-mix(in srgb, #000 45%, transparent);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-4);
|
||||
}
|
||||
|
||||
.dialog {
|
||||
width: min(520px, 100%);
|
||||
background-color: var(--color-bg-subtle, #ffffff);
|
||||
border: var(--border-width) solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: 0 20px 48px color-mix(in srgb, #000 28%, transparent);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: var(--space-4) var(--space-5) var(--space-2);
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
font-size: var(--font-size-md);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.body {
|
||||
padding: var(--space-2) var(--space-5) var(--space-4);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: var(--space-3) var(--space-5) var(--space-4);
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { useEffect } from 'react';
|
||||
import styles from './Modal.module.css';
|
||||
|
||||
export interface ModalProps {
|
||||
open: boolean;
|
||||
title: string;
|
||||
children?: React.ReactNode;
|
||||
onClose: () => void;
|
||||
footer?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function Modal({ open, title, children, onClose, footer }: ModalProps) {
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') onClose();
|
||||
};
|
||||
window.addEventListener('keydown', onKeyDown);
|
||||
return () => window.removeEventListener('keydown', onKeyDown);
|
||||
}, [open, onClose]);
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
return (
|
||||
<div className={styles.overlay} role="presentation" onClick={onClose}>
|
||||
<div
|
||||
className={styles.dialog}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label={title}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<div className={styles.header}>
|
||||
<h3 className={styles.title}>{title}</h3>
|
||||
</div>
|
||||
<div className={styles.body}>{children}</div>
|
||||
{footer && <div className={styles.footer}>{footer}</div>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -57,6 +57,9 @@ export type { DescriptionListProps, DescriptionListItem } from './DescriptionLis
|
||||
export { Notification } from './Notification/Notification';
|
||||
export type { NotificationProps, NotificationVariant } from './Notification/Notification';
|
||||
|
||||
export { Modal } from './Modal/Modal';
|
||||
export type { ModalProps } from './Modal/Modal';
|
||||
|
||||
export { Table } from './Table/Table';
|
||||
export type { TableProps, TableColumn } from './Table/Table';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user