♿️(frontend) focus docs list title after filter navigation
Explain focus shift to match skip-to-content behavior. hook useRouteChangeCompleteFocus Positionne the focus on the first target or main element after a route change.
This commit is contained in:
@@ -18,6 +18,20 @@ test.describe('Left panel desktop', () => {
|
||||
await expect(page.getByTestId('home-button')).toBeVisible();
|
||||
});
|
||||
|
||||
test('focuses main content after switching the docs filter', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto('/');
|
||||
|
||||
const myDocsLink = page.getByRole('link', { name: 'My docs' });
|
||||
await myDocsLink.focus();
|
||||
await page.keyboard.press('Enter');
|
||||
await expect(page).toHaveURL(/target=my_docs/);
|
||||
|
||||
const mainContent = page.locator('main#mainContent');
|
||||
await expect(mainContent).toBeFocused();
|
||||
});
|
||||
|
||||
test('checks resize handle is present and functional on document page', async ({
|
||||
page,
|
||||
browserName,
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useEffect } from 'react';
|
||||
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { Auth, KEY_AUTH, setAuthUrl } from '@/features/auth';
|
||||
import { useRouteChangeCompleteFocus } from '@/hooks/useRouteChangeCompleteFocus';
|
||||
import { useResponsiveStore } from '@/stores/';
|
||||
|
||||
import { ConfigProvider } from './config/';
|
||||
@@ -51,6 +52,7 @@ const queryClient = new QueryClient({
|
||||
export function AppProvider({ children }: { children: React.ReactNode }) {
|
||||
const { theme } = useCunninghamTheme();
|
||||
const { replace } = useRouter();
|
||||
useRouteChangeCompleteFocus();
|
||||
|
||||
const initializeResizeListener = useResponsiveStore(
|
||||
(state) => state.initializeResizeListener,
|
||||
|
||||
@@ -237,7 +237,7 @@ const DocGridTitleBar = ({
|
||||
>
|
||||
<Box $direction="row" $gap="xs" $align="center">
|
||||
{icon}
|
||||
<Text as="h2" $size="h4" $margin="none">
|
||||
<Text as="h2" $size="h4" $margin="none" tabIndex={-1}>
|
||||
{title}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
@@ -70,7 +70,9 @@ export const LeftPanelTargetFilters = () => {
|
||||
href={href}
|
||||
aria-label={query.label}
|
||||
aria-current={isActive ? 'page' : undefined}
|
||||
onClick={handleClick}
|
||||
onClick={() => {
|
||||
handleClick();
|
||||
}}
|
||||
$css={css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { MAIN_LAYOUT_ID } from '@/layouts/conf';
|
||||
|
||||
export const useRouteChangeCompleteFocus = () => {
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChangeComplete = () => {
|
||||
requestAnimationFrame(() => {
|
||||
const mainContent =
|
||||
document.getElementById(MAIN_LAYOUT_ID) ??
|
||||
document.getElementsByTagName('main')[0];
|
||||
|
||||
if (!mainContent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstHeading = mainContent.querySelector('h1, h2, h3');
|
||||
const prefersReducedMotion = window.matchMedia(
|
||||
'(prefers-reduced-motion: reduce)',
|
||||
).matches;
|
||||
|
||||
(mainContent as HTMLElement | null)?.focus({ preventScroll: true });
|
||||
(firstHeading ?? mainContent)?.scrollIntoView({
|
||||
behavior: prefersReducedMotion ? 'auto' : 'smooth',
|
||||
block: 'start',
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
router.events.on('routeChangeComplete', handleRouteChangeComplete);
|
||||
return () => {
|
||||
router.events.off('routeChangeComplete', handleRouteChangeComplete);
|
||||
};
|
||||
}, [router.events]);
|
||||
};
|
||||
Reference in New Issue
Block a user