From eb43cef8728c17d06a494299460c2ff2ebae9e12 Mon Sep 17 00:00:00 2001 From: FragginWagon Date: Fri, 11 Sep 2026 09:17:18 -0400 Subject: [PATCH] feat: add admin navigation skip link --- apps/web/src/assets/styles/global.scss | 2 ++ apps/web/src/pages/AdminShell.stories.js | 10 ++++++++++ apps/web/src/pages/AdminShell.vue | 3 ++- docs/IMPLEMENTATION-STATUS.md | 1 + 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/web/src/assets/styles/global.scss b/apps/web/src/assets/styles/global.scss index 6f3761d..6115311 100644 --- a/apps/web/src/assets/styles/global.scss +++ b/apps/web/src/assets/styles/global.scss @@ -43,6 +43,8 @@ h1 { max-width: 760px; margin: var(--space-3) 0 var(--space-4); font-size: var(- .auth-page { display: grid; place-items: center; min-height: 100vh; padding: var(--space-8); background: var(--color-background); } .auth-panel { display: grid; gap: var(--space-3); width: min(440px, 100%); border: var(--border-thin) solid var(--color-border); border-radius: var(--radius-xl); padding: var(--space-8); background: var(--color-surface); box-shadow: var(--shadow-md); } .auth-panel h1 { font-size: var(--text-2xl); } +.skip-link { position: absolute; left: var(--space-4); top: var(--space-2); z-index: 10; transform: translateY(-200%); background: var(--color-surface); color: var(--color-interactive-text); padding: var(--space-2) var(--space-4); border-radius: var(--radius-sm); } +.skip-link:focus { transform: translateY(0); } .admin-shell { display: grid; grid-template-columns: 260px 1fr; min-height: 100vh; background: var(--color-background); } .admin-sidebar { display: grid; align-content: start; gap: var(--space-6); border-right: var(--border-thin) solid var(--color-border); padding: var(--space-8); background: var(--color-surface); } .admin-sidebar h1 { font-size: var(--text-xl); } diff --git a/apps/web/src/pages/AdminShell.stories.js b/apps/web/src/pages/AdminShell.stories.js index b463f31..7a521bf 100644 --- a/apps/web/src/pages/AdminShell.stories.js +++ b/apps/web/src/pages/AdminShell.stories.js @@ -1,4 +1,5 @@ import AdminShell from './AdminShell.vue'; +import { expect, userEvent, within } from '@storybook/test'; export default { title: 'Pages/AdminShell', @@ -6,3 +7,12 @@ export default { }; export const Navigation = { args: { routed: false } }; + +export const KeyboardSkipLink = { + args: { routed: false }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.tab(); + await expect(canvas.getByRole('link', { name: 'Skip to main content' })).toHaveFocus(); + }, +}; diff --git a/apps/web/src/pages/AdminShell.vue b/apps/web/src/pages/AdminShell.vue index 88ff84a..7f01dc3 100644 --- a/apps/web/src/pages/AdminShell.vue +++ b/apps/web/src/pages/AdminShell.vue @@ -14,6 +14,7 @@ async function logout() { diff --git a/docs/IMPLEMENTATION-STATUS.md b/docs/IMPLEMENTATION-STATUS.md index bb162b9..5e720cd 100644 --- a/docs/IMPLEMENTATION-STATUS.md +++ b/docs/IMPLEMENTATION-STATUS.md @@ -267,6 +267,7 @@ Remaining roadmap areas are approved external provider credentials/adapters, bro - The public catalog now has its own Storybook coverage, including an interaction test proving the mobile filter disclosure is keyboard-operable and correctly hides/reveals filter controls via `aria-expanded`. This review also found and fixed a real color-contrast failure in the catalog error-state text. - `ListingDetailPage`, `ProductDetailPage`, and `AdminImagePage` now have Storybook coverage. Adding it surfaced and fixed a color-contrast bug shared by all three: the "Back to catalog" link, pagination controls, and the selected image-gallery thumbnail were using the non-text-safe `--color-interactive` token instead of `--color-interactive-text`. - Per-item image limits are now enforced atomically for seller image creation and uploads, using `inventory.maxImagesPerItem` (default 10); concurrent inserts are serialized on the physical inventory item and return `IMAGE_COUNT_LIMIT_REACHED` at the configured cap. +- Admin navigation now includes a keyboard-visible skip link to the main content region, with Storybook interaction coverage. ## Local Commands