From 1ba6829a1c2e413122aa21c6810e331ba870be5f Mon Sep 17 00:00:00 2001 From: fs Date: Thu, 19 Mar 2026 19:58:15 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20toast=20visibility=20=E2=80=94=20opaque?= =?UTF-8?q?=20backgrounds=20and=20flash=20scope=20matching=20with=20query?= =?UTF-8?q?=20strings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bugs in the toast redesign: 1. Variant backgrounds (success/warning/info/error) used rgba with 12-20% opacity, making toasts semi-transparent over page content. Fixed by layering the tint over a solid background via linear-gradient compositing. 2. Flash messages with scoped paths including query strings (e.g. edit pages with ?return=...) were not matched by Flash::peek(Request::path()) since path() strips the query. Added fallback to pathWithQuery() matching. Also moved flash partial before JS init scripts to ensure DOM is present when the component runtime mounts. Co-Authored-By: Claude Opus 4.6 (1M context) --- templates/default.phtml | 4 +- templates/login.phtml | 4 +- templates/partials/app-flash.phtml | 2 +- web/css/components/app-flash.css | 86 +++++++++------------ web/js/components/app-flash-auto-dismiss.js | 2 +- 5 files changed, 44 insertions(+), 54 deletions(-) diff --git a/templates/default.phtml b/templates/default.phtml index 4ec0779..b934dce 100644 --- a/templates/default.phtml +++ b/templates/default.phtml @@ -145,7 +145,7 @@ $componentPageConfig = [ 'components' => [ 'confirmActions' => [], 'flashAutoDismiss' => [ - 'selector' => '.app-toast-stack .notice[data-flash-timeout]', + 'selector' => '.notice[data-flash-timeout]', ], 'passwordHints' => [], 'copyBadge' => [], @@ -358,8 +358,8 @@ $componentPageConfig['moduleRuntimeComponents'] = $moduleRuntimeComponents; ?> - + diff --git a/templates/login.phtml b/templates/login.phtml index 56d9bad..ecfa723 100644 --- a/templates/login.phtml +++ b/templates/login.phtml @@ -15,7 +15,7 @@ if ($bufferTitle !== '') { $loginComponentConfig = [ 'components' => [ 'flashAutoDismiss' => [ - 'selector' => '.app-toast-stack .notice[data-flash-timeout]', + 'selector' => '.notice[data-flash-timeout]', ], 'passwordHints' => [ 'selector' => '[data-password-hints]', @@ -65,8 +65,8 @@ $loginComponentConfig = [
- + diff --git a/templates/partials/app-flash.phtml b/templates/partials/app-flash.phtml index 806cffb..e14d042 100644 --- a/templates/partials/app-flash.phtml +++ b/templates/partials/app-flash.phtml @@ -4,7 +4,7 @@ use MintyPHP\Support\Flash; use MintyPHP\Http\Request; use MintyPHP\Session; -$messages = Flash::peek(Request::path()); +$messages = Flash::peek(Request::path()) ?: Flash::peek(Request::pathWithQuery()); if (!$messages) { return; } diff --git a/web/css/components/app-flash.css b/web/css/components/app-flash.css index b1fe02e..d2341b8 100644 --- a/web/css/components/app-flash.css +++ b/web/css/components/app-flash.css @@ -14,6 +14,10 @@ pointer-events: none; } + .app-toast-stack:empty { + display: none; + } + /* In admin layout: offset below topbar */ .app-container ~ .app-toast-stack, body:has(.app-container) .app-toast-stack { @@ -37,14 +41,16 @@ /* ── Individual toast notice ─────────────────────────────────────────── */ - .notice { + .app-toast-stack .notice { --app-notice-border-color: var(--app-muted-border-color); - --app-notice-background-color: var(--app-card-background-color); + --app-notice-background-color: var(--app-background-color); --app-notice-accent-color: var(--app-color); - background: var(--app-notice-background-color); + --_toast-tint: transparent; + background: linear-gradient(var(--_toast-tint), var(--_toast-tint)), var(--app-background-color); + border: 1px solid color-mix(in srgb, var(--app-notice-border-color) 40%, transparent); border-left: 3px solid var(--app-notice-border-color); border-radius: var(--app-border-radius); - box-shadow: var(--app-card-box-shadow, 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.06)); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15), 0 1px 3px rgba(0, 0, 0, 0.08); padding: calc(var(--app-spacing) * 0.625) calc(var(--app-spacing) * 0.875); padding-right: calc(var(--app-spacing) * 0.5); display: flex; @@ -57,80 +63,64 @@ font-size: 0.875rem; line-height: 1.4; pointer-events: auto; - } - - /* ── Slide-in animation ──────────────────────────────────────────────── */ - - .notice { + margin: 0; animation: toast-slide-in 250ms ease-out; } - .notice.toast-dismissing { + .app-toast-stack .notice.toast-dismissing { animation: toast-slide-out 200ms ease-in forwards; } @keyframes toast-slide-in { - from { - opacity: 0; - transform: translateX(20px); - } - to { - opacity: 1; - transform: translateX(0); - } + from { opacity: 0; transform: translateX(20px); } + to { opacity: 1; transform: translateX(0); } } @keyframes toast-slide-out { - from { - opacity: 1; - transform: translateX(0); - } - to { - opacity: 0; - transform: translateX(20px); - } + from { opacity: 1; transform: translateX(0); } + to { opacity: 0; transform: translateX(20px); } } @media (prefers-reduced-motion: reduce) { - .notice, - .notice.toast-dismissing { + .app-toast-stack .notice, + .app-toast-stack .notice.toast-dismissing { animation: none; } } /* ── Variant colors ──────────────────────────────────────────────────── */ - .notice strong { + .app-toast-stack .notice strong { color: var(--app-notice-accent-color); } - .notice[data-variant=success] { + .app-toast-stack .notice[data-variant=success] { --app-notice-border-color: var(--app-notice-success-border-color); - --app-notice-background-color: var(--app-notice-success-background-color); + --_toast-tint: var(--app-notice-success-background-color); --app-notice-accent-color: var(--app-notice-success-color); } - .notice[data-variant=warning] { + .app-toast-stack .notice[data-variant=warning] { --app-notice-border-color: var(--app-notice-warning-border-color); - --app-notice-background-color: var(--app-notice-warning-background-color); + --_toast-tint: var(--app-notice-warning-background-color); --app-notice-accent-color: var(--app-notice-warning-color); } - .notice[data-variant=info] { + .app-toast-stack .notice[data-variant=info] { --app-notice-border-color: var(--app-notice-info-border-color); - --app-notice-background-color: var(--app-notice-info-background-color); + --_toast-tint: var(--app-notice-info-background-color); --app-notice-accent-color: var(--app-notice-info-color); } - .notice[data-variant=error] { + .app-toast-stack .notice[data-variant=error] { --app-notice-border-color: var(--app-notice-error-border-color); - --app-notice-background-color: var(--app-notice-error-background-color); + --_toast-tint: var(--app-notice-error-background-color); --app-notice-accent-color: var(--app-notice-error-color); } /* ── Progress bar timer ──────────────────────────────────────────────── */ - .notice.flash-timed::after { + .app-toast-stack .notice.flash-timed::after { content: ""; position: absolute; left: 0; @@ -144,7 +134,7 @@ opacity: 0.7; } - .notice.flash-timed:hover::after { + .app-toast-stack .notice.flash-timed:hover::after { animation-play-state: paused; } @@ -154,42 +144,42 @@ } @media (prefers-reduced-motion: reduce) { - .notice.flash-timed::after { + .app-toast-stack .notice.flash-timed::after { animation: none; } } /* ── Dismiss button ──────────────────────────────────────────────────── */ - .notice button[type=submit], - .notice button[type=button] { + .app-toast-stack .notice button[type=submit], + .app-toast-stack .notice button[type=button] { border: none !important; background: transparent; color: var(--app-notice-border-color); - opacity: 0.7; + opacity: 0.6; cursor: pointer; padding: 2px 4px; line-height: 1; flex-shrink: 0; } - .notice button[type=submit]:hover, - .notice button[type=button]:hover { + .app-toast-stack .notice button[type=submit]:hover, + .app-toast-stack .notice button[type=button]:hover { opacity: 1; } /* ── List formatting inside notices ──────────────────────────────────── */ - .notice ul { + .app-toast-stack .notice ul { margin: 0; padding-left: 0; } - .notice li { + .app-toast-stack .notice li { list-style: none; } - .notice li:last-child { + .app-toast-stack .notice li:last-child { margin: 0; } } diff --git a/web/js/components/app-flash-auto-dismiss.js b/web/js/components/app-flash-auto-dismiss.js index ba3e063..2e5af85 100644 --- a/web/js/components/app-flash-auto-dismiss.js +++ b/web/js/components/app-flash-auto-dismiss.js @@ -6,7 +6,7 @@ import { resolveHost } from '../core/app-dom.js'; export function initFlashAutoDismiss(root = document, options = {}) { const { - selector = '.app-toast-stack .notice[data-flash-timeout]', + selector = '.notice[data-flash-timeout]', defaultTimeout = 0, } = options; const host = resolveHost(root);