Add CI, a pre-commit hook, and an accessibility pass on the panel's own views
CI (.github/workflows/tests.yml) reuses docker-compose.test.yml verbatim for the Postgres job rather than re-describing that environment in YAML, so the Docker image stays the single source of truth for it. Fixes a real mismatch found while wiring PHP versions into the workflow: composer.json declared "php": "^8.3", but the locked Symfony packages require >=8.4.1 — installing on 8.3 would fail at `composer install`, exactly what the Dockerfile's own comment already warned about. Bumped the constraint and resynced composer.lock's content-hash (no package versions changed). The pre-commit hook (.githooks/pre-commit, activated via `git config core.hooksPath` in post-autoload-dump) runs `pint --dirty --test` and refuses to commit unformatted files rather than silently rewriting them. The accessibility pass only touches this app's own hand-written Blade views, not Filament's components: aria-hidden on icons that repeat adjacent text, an aria-label on the one icon that is the only signal of a health check's severity, rel="noopener" and a labelled announcement on preview links that open in a new tab. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,18 @@
|
||||
<x-filament-panels::page>
|
||||
{{-- The headline. Whether anything is actually wrong is the one thing
|
||||
somebody opening this page came to find out. --}}
|
||||
somebody opening this page came to find out.
|
||||
|
||||
Most of the icons below are `aria-hidden`: colour and shape repeating
|
||||
what the text beside them already says. The one on each check's own
|
||||
heading is the exception — severity there is never written out in
|
||||
words, only implied by an icon and a badge colour — so that one gets
|
||||
a spoken name instead of being hidden. --}}
|
||||
@if ($this->problems()->isEmpty())
|
||||
<div class="rounded-xl border border-success-300 bg-success-50 p-4 dark:border-success-700/50 dark:bg-success-500/10">
|
||||
<div class="flex items-center gap-3">
|
||||
<x-filament::icon
|
||||
icon="heroicon-o-check-circle"
|
||||
aria-hidden="true"
|
||||
class="h-5 w-5 shrink-0 text-success-600 dark:text-success-400"
|
||||
/>
|
||||
|
||||
@@ -25,6 +32,7 @@
|
||||
<div class="flex items-start gap-3">
|
||||
<x-filament::icon
|
||||
icon="heroicon-o-exclamation-triangle"
|
||||
aria-hidden="true"
|
||||
@class([
|
||||
'mt-0.5 h-5 w-5 shrink-0',
|
||||
'text-danger-600 dark:text-danger-400' => $tone === 'danger',
|
||||
@@ -60,6 +68,8 @@
|
||||
<span class="flex items-center gap-2">
|
||||
<x-filament::icon
|
||||
:icon="$finding->severity->getIcon()"
|
||||
:aria-label="$finding->severity->getLabel()"
|
||||
role="img"
|
||||
@class([
|
||||
'h-5 w-5 shrink-0',
|
||||
'text-danger-500' => $finding->severity->getColor() === 'danger',
|
||||
@@ -123,6 +133,7 @@
|
||||
<li class="flex items-start gap-2 text-sm">
|
||||
<x-filament::icon
|
||||
icon="heroicon-o-check-circle"
|
||||
aria-hidden="true"
|
||||
class="mt-0.5 h-4 w-4 shrink-0 text-success-500"
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
<x-filament-panels::page>
|
||||
{{-- Current state, stated plainly. Whether the public site is down is the
|
||||
one thing this page must never leave ambiguous. --}}
|
||||
one thing this page must never leave ambiguous.
|
||||
|
||||
The icons beside it are `aria-hidden`: what they say is already in
|
||||
the text next to them, so a screen reader announcing "warning
|
||||
triangle icon" would be noise repeating what it is about to read
|
||||
anyway. --}}
|
||||
@if ($this->isEnabled())
|
||||
<div class="rounded-xl border border-danger-300 bg-danger-50 p-4 dark:border-danger-700/50 dark:bg-danger-500/10">
|
||||
<div class="flex items-start gap-3">
|
||||
<x-filament::icon
|
||||
icon="heroicon-o-exclamation-triangle"
|
||||
aria-hidden="true"
|
||||
class="mt-0.5 h-5 w-5 shrink-0 text-danger-600 dark:text-danger-400"
|
||||
/>
|
||||
|
||||
@@ -35,6 +41,7 @@
|
||||
<div class="flex items-center gap-3">
|
||||
<x-filament::icon
|
||||
icon="heroicon-o-check-circle"
|
||||
aria-hidden="true"
|
||||
class="h-5 w-5 shrink-0 text-success-600 dark:text-success-400"
|
||||
/>
|
||||
|
||||
|
||||
@@ -6,16 +6,39 @@
|
||||
<p class="text-sm font-medium text-gray-700 dark:text-gray-200">{{ $language }}</p>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
{{-- Readonly and click/focus-to-select rather than a plain
|
||||
display of the URL: copying it is the entire point of
|
||||
this field, for a mouse and a keyboard alike. Its own
|
||||
`aria-label` names it explicitly — the language above is
|
||||
a separate element with no `for`/`id` link to this one,
|
||||
so a screen reader landing directly on the input would
|
||||
otherwise hear only "text, read only". --}}
|
||||
<input
|
||||
type="text"
|
||||
readonly
|
||||
value="{{ $url }}"
|
||||
onclick="this.select()"
|
||||
onfocus="this.select()"
|
||||
aria-label="{{ __('panel.preview.link_for', ['language' => $language]) }}"
|
||||
class="fi-input w-full rounded-lg border-gray-300 bg-gray-50 px-3 py-2 font-mono text-xs text-gray-700 dark:border-white/10 dark:bg-white/5 dark:text-gray-300"
|
||||
/>
|
||||
|
||||
<x-filament::button tag="a" href="{{ $url }}" target="_blank" size="sm" color="gray">
|
||||
{{-- `noopener`: the page it opens runs on the public site's
|
||||
origin, and a target="_blank" link without it hands that
|
||||
page a live reference back to this one (`window.opener`).
|
||||
The visually-hidden text is for whoever is not looking at
|
||||
the screen — a new tab opening is a context change
|
||||
nothing else here announces. --}}
|
||||
<x-filament::button
|
||||
tag="a"
|
||||
href="{{ $url }}"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
size="sm"
|
||||
color="gray"
|
||||
>
|
||||
{{ __('panel.preview.open') }}
|
||||
<span class="sr-only">{{ __('panel.preview.opens_new_tab') }}</span>
|
||||
</x-filament::button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user