diff --git a/CHANGELOG.md b/CHANGELOG.md index 282ce166..27a4d7e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to ## [Unreleased] +### Changed + +- ♿️(frontend) Focus main container after navigation #1854 + ### Fixed 🐛(frontend) fix broadcast store sync #1846 diff --git a/src/frontend/apps/e2e/__tests__/app-impress/left-panel.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/left-panel.spec.ts index cf5ee4c3..c0cdfedc 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/left-panel.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/left-panel.spec.ts @@ -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, diff --git a/src/frontend/apps/impress/src/core/AppProvider.tsx b/src/frontend/apps/impress/src/core/AppProvider.tsx index 79059491..3ecff227 100644 --- a/src/frontend/apps/impress/src/core/AppProvider.tsx +++ b/src/frontend/apps/impress/src/core/AppProvider.tsx @@ -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, diff --git a/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGrid.tsx b/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGrid.tsx index d9df77b3..bee4eb4c 100644 --- a/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGrid.tsx +++ b/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGrid.tsx @@ -237,7 +237,7 @@ const DocGridTitleBar = ({ > {icon} - + {title} diff --git a/src/frontend/apps/impress/src/features/left-panel/components/LefPanelTargetFilters.tsx b/src/frontend/apps/impress/src/features/left-panel/components/LefPanelTargetFilters.tsx index 26dded8a..5ab79b6a 100644 --- a/src/frontend/apps/impress/src/features/left-panel/components/LefPanelTargetFilters.tsx +++ b/src/frontend/apps/impress/src/features/left-panel/components/LefPanelTargetFilters.tsx @@ -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; diff --git a/src/frontend/apps/impress/src/hooks/useRouteChangeCompleteFocus.tsx b/src/frontend/apps/impress/src/hooks/useRouteChangeCompleteFocus.tsx new file mode 100644 index 00000000..929b76c1 --- /dev/null +++ b/src/frontend/apps/impress/src/hooks/useRouteChangeCompleteFocus.tsx @@ -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]); +};