mirror of
https://github.com/mudler/LocalAI.git
synced 2026-06-18 13:49:09 -04:00
fix(ui): single focus ring (no double-ring) + neutralize stagger delay under reduced motion
Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Assisted-by: Claude:claude-opus-4-8 [Claude Code]
This commit is contained in:
@@ -26,3 +26,19 @@ test.describe('Editorial design system', () => {
|
||||
expect(name).toBe('pageReveal')
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('reduced motion', () => {
|
||||
test('stagger animation-delay is neutralized under reduced motion', async ({ page }) => {
|
||||
// Emulate prefers-reduced-motion explicitly. (The fixture-option form
|
||||
// test.use({ reducedMotion }) does not propagate through our extended
|
||||
// coverage `page` fixture, so set it on the page directly.)
|
||||
await page.emulateMedia({ reducedMotion: 'reduce' })
|
||||
await page.goto('/app') // Home renders .reveal-stagger children
|
||||
// .home-status-line is staggerStyle(1) -> 60ms delay without the fix.
|
||||
const child = page.locator('.home-status-line').first()
|
||||
await expect(child).toBeVisible({ timeout: 15_000 })
|
||||
const delay = await child.evaluate(el => getComputedStyle(el).animationDelay)
|
||||
// Under reduced motion the per-child delay must be ~0 (not 60ms+).
|
||||
expect(parseFloat(delay)).toBeLessThan(0.05)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1668,20 +1668,17 @@
|
||||
box-shadow: 0 0 0 3px var(--color-focus-ring);
|
||||
}
|
||||
|
||||
/* Global focus ring — any interactive that isn't a .btn */
|
||||
/* Global focus ring - any interactive that isn't a .btn. This box-shadow ring
|
||||
is the single focus technique app-wide (it covers .btn plus the :where(...)
|
||||
list below). The strengthened --color-focus-ring keeps it WCAG-AA visible
|
||||
(>=3:1), so no separate solid-outline rule is needed; a bare :focus-visible
|
||||
outline here would double-ring every element. */
|
||||
:where(a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])):focus-visible {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px var(--color-focus-ring);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
/* Solid outline fallback so the focus ring stays AA-visible (>=3:1) on every
|
||||
interactive element, including those that clip or override box-shadow. */
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--color-focus-ring);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--color-primary);
|
||||
color: var(--color-primary-text);
|
||||
|
||||
@@ -93,6 +93,9 @@ a:hover {
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
/* Neutralize per-child stagger delays (e.g. .reveal-stagger) so content
|
||||
does not sit at the hidden "from" keyframe before snapping in. */
|
||||
animation-delay: 0s !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
scroll-behavior: auto !important;
|
||||
|
||||
Reference in New Issue
Block a user