refactor(ui): unify notice + toast styling, polish inline notices
Notices used to be styled only inside the toast stack — every inline .notice on the login page, auth pages and admin edit screens fell back to browser defaults and looked unstyled. The Stripe-style toast redesign (icon pill + neutral text + soft card surface) now lives on the base .notice rule, and .app-toast-stack adds the slide-in animation, soft shadow, dismiss button and progress bar on top. Inline notices auto-render the variant icon via a ::before pseudo using the Bootstrap Icons codepoints, so all 30+ existing call sites get the new look without markup changes. Toasts opt out of ::before via :has(> .notice-icon) and keep their explicit icon span. The previous app-flash.phtml partial is folded into app-toast-stack.phtml (both create the same .app-toast-stack container — having both mounted made two stacks fight for the same fixed corner). default/login/page templates now mount the unified partial; the architecture contract is updated to match. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -46,6 +46,13 @@ const defaultTimeouts = {
|
||||
|
||||
const MAX_VISIBLE_TOASTS = 5;
|
||||
|
||||
const VARIANT_ICONS = {
|
||||
success: 'bi-check-circle-fill',
|
||||
error: 'bi-x-octagon-fill',
|
||||
warning: 'bi-exclamation-triangle-fill',
|
||||
info: 'bi-info-circle-fill',
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolve the server-rendered toast stack container.
|
||||
* Returns null if the partial is missing — callers must handle that case.
|
||||
@@ -111,14 +118,26 @@ export function showAsyncFlash(type, message, timeout) {
|
||||
notice.setAttribute('aria-live', isAssertive ? 'assertive' : 'polite');
|
||||
notice.setAttribute('aria-atomic', 'true');
|
||||
|
||||
const iconSpan = document.createElement('span');
|
||||
iconSpan.className = 'notice-icon';
|
||||
iconSpan.setAttribute('aria-hidden', 'true');
|
||||
const iconElement = document.createElement('i');
|
||||
iconElement.className = `bi ${VARIANT_ICONS[type] || VARIANT_ICONS.info}`;
|
||||
iconSpan.appendChild(iconElement);
|
||||
notice.appendChild(iconSpan);
|
||||
|
||||
const messageSpan = document.createElement('span');
|
||||
messageSpan.className = 'notice-content';
|
||||
messageSpan.textContent = message;
|
||||
notice.appendChild(messageSpan);
|
||||
|
||||
const closeButton = document.createElement('button');
|
||||
closeButton.type = 'button';
|
||||
closeButton.className = 'notice-dismiss';
|
||||
closeButton.setAttribute('aria-label', 'Dismiss');
|
||||
closeButton.innerHTML = '<i class="bi bi-x-lg"></i>';
|
||||
const closeIcon = document.createElement('i');
|
||||
closeIcon.className = 'bi bi-x-lg';
|
||||
closeButton.appendChild(closeIcon);
|
||||
closeButton.addEventListener('click', () => dismissToast(notice));
|
||||
notice.appendChild(closeButton);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user