fix(ux): replace native confirm() dialogs, add undo-toast, fix prefers-reduced-motion

- Replace all 13 native confirm() calls with confirmModal() across 7 page modules
- Add confirmModal() to modal.js (Promise-based, danger variant, focus management)
- Fix double-confirm bug in contacts.js and budget.js (modal + deleteContact/deleteEntry)
- Extend showToast() with onUndo callback and max-3-toast limit
- Implement optimistic undo-toast (4s window) for shopping item and bulk-checked delete
- Add prefers-reduced-motion guard to btnSuccess() and btnError() in modal.js
- Add btn--error-static CSS class as motion-reduced fallback for btnError()
- Add toast__undo button styles to layout.css
- Add common.confirm and common.undo i18n keys (de, en, it, sv)
- Add shopping.itemDeletedToast i18n key (de, en, it, sv)
This commit is contained in:
Ulas
2026-04-05 12:31:16 +02:00
parent 3a7d6d0e0a
commit 44e5a879b9
15 changed files with 245 additions and 56 deletions
+18 -2
View File
@@ -397,10 +397,14 @@ const TOAST_ICONS = {
warning: '<svg class="toast__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>',
};
function showToast(message, type = 'default', duration = 3000) {
function showToast(message, type = 'default', duration = 3000, onUndo = null) {
const container = document.getElementById('toast-container');
if (!container) return;
// Max. 3 gleichzeitige Toasts: ältesten entfernen falls Limit erreicht
const existing = container.querySelectorAll('.toast');
if (existing.length >= 3) existing[0].remove();
const toast = document.createElement('div');
toast.className = `toast ${type !== 'default' ? `toast--${type}` : ''}`;
toast.setAttribute('role', 'alert');
@@ -412,8 +416,20 @@ function showToast(message, type = 'default', duration = 3000) {
toast.innerHTML = icon; // eslint-disable-line no-unsanitized/property -- static SVG only
toast.appendChild(span);
if (typeof onUndo === 'function') {
const undoBtn = document.createElement('button');
undoBtn.className = 'toast__undo';
undoBtn.textContent = t('common.undo');
undoBtn.addEventListener('click', () => {
clearTimeout(dismissTimer);
toast.remove();
onUndo();
});
toast.appendChild(undoBtn);
}
container.appendChild(toast);
setTimeout(() => {
const dismissTimer = setTimeout(() => {
toast.classList.add('toast--out');
toast.addEventListener('animationend', () => toast.remove(), { once: true });
}, duration);