Skip to content

Commit 45455fb

Browse files
committed
fix(a11y): keep summary above scrolled violations
1 parent e3af508 commit 45455fb

2 files changed

Lines changed: 27 additions & 1 deletion

File tree

plugins/a11y/app/components/SummaryBar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const ACTION = 'inline-flex items-center gap-1.5 text-xs color-muted bg-secondar
3838
export function SummaryBar(props: SummaryBarProps) {
3939
const plural = (n: number, one: string) => `${n} ${n === 1 ? one : `${one}s`}`
4040
return (
41-
<div class="flex flex-col gap-2 pt-3 pb-2.5 sticky top-0 z-[2] bg-base">
41+
<div class="flex flex-col gap-2 pt-3 pb-2.5 sticky top-0 z-nav bg-base">
4242
<Summary counts={props.counts} active={props.filter} onToggle={props.onToggleFilter} onHover={props.onHoverImpact} />
4343

4444
<div class="flex items-center gap-2 flex-wrap">

tests/e2e/a11y-messages-hub-static.spec.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,32 @@ test.describe('a11y-messages playground (static hub build)', () => {
3434
await expect(page.locator('[data-df-a11y-overlay]')).toContainText('image-alt')
3535
})
3636

37+
test('keeps the a11y summary controls above scrolled violations', async ({ page }) => {
38+
await page.setViewportSize({ width: 1_000, height: 360 })
39+
const panel = page.frameLocator('iframe[title="A11y Inspector"]')
40+
41+
await expect(panel.getByRole('checkbox', { name: /image-alt/ })).toBeVisible()
42+
const scrollArea = panel.locator('#a11y-scroll')
43+
const summary = scrollArea.locator(':scope > div').first()
44+
await scrollArea.evaluate(element => element.scrollTo({ top: element.scrollHeight }))
45+
46+
expect(await scrollArea.evaluate(element => element.scrollTop)).toBeGreaterThan(0)
47+
expect(await summary.locator('button').evaluateAll(buttons => buttons.flatMap((button) => {
48+
const bounds = button.getBoundingClientRect()
49+
const hitTarget = document.elementFromPoint(
50+
bounds.left + bounds.width / 2,
51+
bounds.top + bounds.height / 2,
52+
)
53+
return hitTarget != null && button.contains(hitTarget)
54+
? []
55+
: [button.getAttribute('aria-label') ?? button.title ?? button.textContent?.trim()]
56+
}))).toEqual([])
57+
58+
const critical = panel.getByRole('button', { name: /Critical issues/ })
59+
await critical.click()
60+
await expect(critical).toHaveAttribute('aria-pressed', 'true')
61+
})
62+
3763
test('messages panel renders the baked feed; its activate action switches docks over the BroadcastChannel', async ({ page }) => {
3864
await page.click('button[data-dock-id="devframes_plugin_messages"]')
3965
const panel = page.frameLocator('iframe[title="Messages"]')

0 commit comments

Comments
 (0)