Files
livewire-material/resources/views/components/toast.blade.php
T
Andreas Reinhold / reiniandClaude Opus 5 247c596c3a Cut duplicated and speculative code across the package
An over-engineering audit of the whole tree, applied in five reviewed
batches. Behaviour stays the same except where UPGRADE.md says otherwise.

PHP: the showcase and error-page stylesheets are prebuilt into
resources/dist by bin/stylesheets.mjs, through Vite's own postcss-import
(first occurrence kept, the order an application's build gives), instead
of Stylesheets::bundle() inlining imports on every request; only the
import walk DesignGuard needs stays. SchemeStylesheet::withProfiles()
replaces three copies of the scheme-plus-profiles loop, material:scheme
leaves spec and contrast checks to the node script that already made
them, and the error page's scheme cache, the hashed view namespace, the
translations path with no lang/ folder and DesignGuard's 1.x-name hints
are gone.

JS: the androidx shape port progress.js and both bin scripts each carried
lives once in resources/js/shapes.js (the generated SVGs are unchanged);
util.js holds ringIndex(), ms(), reopenGuard() and remember(), which
were written out several times; listeners are released through
AbortController; tooltip.js's hoverPopover() serves the rich tooltip too.

CSS: every rule for an element inside the navigation rail queries
`--md-navigation-rail-value` instead of repeating the seven collapsed
conditions under five media branches; badge, alert, progress, slider and
button read one non-inheriting colour-role table (components/color.css);
the dialog chrome, the submenu's popover chrome, the chip's state layer
and touch target, and the visually-hidden inputs use the shared rules
they copied; foundation/tokens.css is folded into foundation.css.

Views: Support\Field and Support\Link replace the error-key, bound-value
and link-attribute blocks copied into the fields and link components;
the timepicker period group, the menu filter and the showcase head are
partials; the datepicker's steppers and entry fields are loops; component
docblocks no longer restate SKILL.md.

Tests and tooling: one dataset-driven ComponentStylesheetsTest replaces
four per-group files, DesignGuardTest and the layout-component tests use
datasets, browser tests share one ready() helper, CSS parsing lives in
ComponentStylesheet alone. docs/audits and the finding IDs citing it are
removed, as are pestphp/pest-plugin-laravel, the unused composer scripts
and check:font; the lint job runs in the feature job, which now installs
node packages so the prebuilt-stylesheet staleness test runs in CI.

Feature suite 1177 passed, Chrome browser suite 299 passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 19:29:21 +02:00

64 lines
3.0 KiB
PHP

{{-- The snackbar host: shows every toast, one at a time, near the end of `<body>`. `materialToast()`,
`type`, `action`, `sticky`, `description`, the keyboard and the `data-md-toast-*` hooks are
documented in SKILL.md.
The live region is the host itself, not the snackbar: a region must be in the page before its
contents change for a screen reader to announce them reliably, and the snackbar comes and
goes. It is `aria-live="polite" aria-atomic="true"`, as M3 asks for a snackbar
(never assertive); `type` picks the region's `role` — `alert` for an error or a warning,
`status` otherwise — and the explicit `aria-live` keeps even those polite.
`@persist` keeps the host across `wire:navigate`, so a toast dispatched with `redirectTo` is
still on screen when the next page arrives.
`position`: `bottom` (centred, the default) or `bottom-start`. It lifts above a bottom bar
through `--material-bottom-bar`, and publishes its own height as `--material-snackbar-height`
so a FAB can lift clear of it (M3: a snackbar appears above a FAB, never in front of or
behind one). Drawn by resources/css/components/toast.css. --}}
@props(['position' => 'bottom'])
@php
$position = in_array($position, ['bottom', 'bottom-start'], true) ? $position : 'bottom';
@endphp
@persist('material-toast')
<div
x-data="materialSnackbar"
x-bind:role="current && (current.type === 'error' || current.type === 'warning') ? 'alert' : 'status'"
role="status"
aria-live="polite"
aria-atomic="true"
data-md-toast
data-md-position="{{ $position }}"
{{ $attributes }}
>
<template x-if="current">
<div
x-bind:key="current.id"
data-md-toast-snackbar
x-bind:data-md-two-line="current.description ? '' : null"
x-on:mouseenter="pause()"
x-on:mouseleave="resume()"
x-on:focusin="pause()"
x-on:focusout="resume()"
>
<div data-md-toast-content x-bind:data-md-toast-wrap="current.description && current.action ? '' : null">
<p data-md-toast-title x-text="current.title"></p>
<p data-md-toast-description x-show="current.description" x-text="current.description"></p>
</div>
<template x-if="current.action">
<button type="button" class="md-state-layer md-focus-ring md-touch-target" data-md-toast-action x-text="current.action.label" x-on:click="act()"></button>
</template>
<template x-if="current.action || ! current.timeout">
<button type="button" class="md-state-layer md-focus-ring md-touch-target" data-md-toast-dismiss aria-label="{{ __('Dismiss') }}" x-on:click="dismiss()">
<x-livewire-material::icon name="close" size="20" />
</button>
</template>
</div>
</template>
</div>
@endpersist