feat(ux): microinteraction polish — undo tap feedback, strikethrough transition, modal loading state
- toast__undo: add :active scale + tap-highlight-color for reliable tap feedback - task titles: animate text-decoration-color instead of snapping for smoother done-state - modal forms: auto-add btn--loading on submit; rAF guard removes it on validation fail; MutationObserver removes it on error re-enable; btnSuccess clears it before checkmark Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -294,6 +294,22 @@ export function openModal({ title, content, onSave, onDelete, size = 'md' } = {}
|
||||
// Callback für Aufrufer (Form-Events binden etc.)
|
||||
if (typeof onSave === 'function') onSave(panel);
|
||||
|
||||
// Loading-State: btn--loading auf Submit-Button während async-Save.
|
||||
// rAF-Check: Validierung schlägt fehl → btn bleibt enabled → Loading sofort entfernen.
|
||||
// MutationObserver: Error-Pfad → btn wird re-enabled → Loading entfernen.
|
||||
panel.addEventListener('submit', (e) => {
|
||||
const btn = e.target.querySelector('[type="submit"], .btn--primary');
|
||||
if (!btn || btn.disabled) return;
|
||||
btn.classList.add('btn--loading');
|
||||
requestAnimationFrame(() => {
|
||||
if (!btn.disabled) { btn.classList.remove('btn--loading'); return; }
|
||||
const mo = new MutationObserver(() => {
|
||||
if (!btn.disabled) { btn.classList.remove('btn--loading'); mo.disconnect(); }
|
||||
});
|
||||
mo.observe(btn, { attributes: true, attributeFilter: ['disabled'] });
|
||||
});
|
||||
}, { capture: true });
|
||||
|
||||
// Standalone: Statusbar abdunkeln (Overlay-Effekt)
|
||||
if (window.oikos?.setThemeColor) {
|
||||
window.oikos.setThemeColor(OVERLAY_THEME_COLOR, OVERLAY_THEME_COLOR);
|
||||
@@ -620,6 +636,7 @@ export function validateAll(formContainer) {
|
||||
* @param {string} [originalLabel]
|
||||
*/
|
||||
export function btnSuccess(btn, originalLabel) {
|
||||
btn.classList.remove('btn--loading');
|
||||
const label = originalLabel ?? btn.textContent;
|
||||
btn.classList.add('btn--success');
|
||||
const reducedMotion = matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
|
||||
@@ -1647,10 +1647,20 @@
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.toast__undo {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
transition: opacity var(--transition-fast), transform 0.08s ease;
|
||||
}
|
||||
|
||||
.toast__undo:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.toast__undo:active {
|
||||
transform: scale(0.94);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.toast--success { background-color: var(--color-success); color: var(--toast-success-text); }
|
||||
.toast--danger { background-color: var(--color-danger); color: var(--toast-danger-text); }
|
||||
.toast--warning { background-color: var(--color-warning); color: var(--toast-warning-text); }
|
||||
|
||||
@@ -406,11 +406,16 @@
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--space-1);
|
||||
cursor: pointer;
|
||||
text-decoration: line-through;
|
||||
text-decoration-color: transparent;
|
||||
transition:
|
||||
color var(--transition-fast),
|
||||
text-decoration-color var(--transition-base);
|
||||
}
|
||||
|
||||
.task-card--done .task-card__title {
|
||||
text-decoration: line-through;
|
||||
color: var(--color-text-secondary);
|
||||
text-decoration-color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.task-card__meta {
|
||||
|
||||
Reference in New Issue
Block a user