feat(web): reusable Modal + ConfirmProvider, replace native confirm()

Modal component: portal, ESC to close, Tab focus-trap, focus-on-open
(prefers [data-modal-autofocus]), focus restore, overlay click, scroll lock.
ConfirmProvider exposes useConfirm(): async confirm({...}) as a drop-in for
window.confirm; Enter confirms, ESC cancels. Task/account deletes now use it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMHQTtnQtQqL8muAXHr9kd
This commit is contained in:
2026-07-02 09:36:08 +07:00
parent fcbe438f32
commit 29ccbc22e9
6 changed files with 254 additions and 3 deletions
+71
View File
@@ -254,6 +254,77 @@
cursor: not-allowed;
}
/* ---------- modal ---------- */
.modal-overlay {
position: fixed;
inset: 0;
z-index: 100;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
background: rgba(4, 7, 5, 0.72);
backdrop-filter: blur(2px);
animation: modal-fade 0.12s ease-out;
}
.modal-dialog {
width: 100%;
max-width: 440px;
background: var(--bg-panel-raised);
border: 1px solid var(--border-bright);
box-shadow: 0 18px 48px rgba(0, 0, 0, 0.55);
padding: 22px 24px;
outline: none;
animation: modal-rise 0.14s ease-out;
}
.modal-title {
font-family: var(--font-mono);
font-size: 12px;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--accent);
margin-bottom: 14px;
}
.confirm-message {
margin: 0 0 20px;
color: var(--fg);
font-size: 14px;
line-height: 1.5;
}
.modal-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
}
.btn-danger {
border-color: var(--fail-dim);
color: var(--fail);
}
.btn-danger:hover:not(:disabled) {
background: var(--fail-dim);
color: var(--fail);
}
@keyframes modal-fade {
from {
opacity: 0;
}
}
@keyframes modal-rise {
from {
opacity: 0;
transform: translateY(6px);
}
}
/* ---------- buttons ---------- */
.btn {