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:
2026-04-10 23:46:20 +03:00
parent 7223371fae
commit d8ea2d0126
14 changed files with 458 additions and 41 deletions
+41
View File
@@ -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);
}
+41
View File
@@ -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>
);
}
+3
View File
@@ -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';