6c7e76b551
This adapts the new client hooks of `usePathname`, `useSearchParams`, and `useRouter` to work within the `pages/` directory to aid users attempting to migrate shared components over to the `app/` directory. > **Exception:** > When the pages router is not ready, `useSearchParams` will return an empty `URLSearchParams`. This mirrors the behavior seen in the `pages/` directory today in that `router.query` is not available until the client hydrates. This also adds a new option for `useRouter` to bring it line with the correct typings with the app directory. By default, calling `useRouter()` will return the type `NextRouter | null` to represent what you get when you call it from a component originating from the app directory. If you want to instead force it to return `NextRouter` as it does today, you can pass a boolean into the `useRouter` call as such: ```ts const router = useRouter() // typeof router === NextRouter | null const router = useRouter(true) // typeof router === NextRouter ``` This change is designed to ease the incremental adoption of app.
64 lines
1.9 KiB
TypeScript
64 lines
1.9 KiB
TypeScript
import React from 'react'
|
||
import { useRouter } from './router'
|
||
|
||
const nextjsRouteAnnouncerStyles: React.CSSProperties = {
|
||
border: 0,
|
||
clip: 'rect(0 0 0 0)',
|
||
height: '1px',
|
||
margin: '-1px',
|
||
overflow: 'hidden',
|
||
padding: 0,
|
||
position: 'absolute',
|
||
width: '1px',
|
||
|
||
// https://medium.com/@jessebeach/beware-smushed-off-screen-accessible-text-5952a4c2cbfe
|
||
whiteSpace: 'nowrap',
|
||
wordWrap: 'normal',
|
||
}
|
||
|
||
export const RouteAnnouncer = () => {
|
||
const { asPath } = useRouter(true)
|
||
const [routeAnnouncement, setRouteAnnouncement] = React.useState('')
|
||
|
||
// Only announce the path change, but not for the first load because screen
|
||
// reader will do that automatically.
|
||
const previouslyLoadedPath = React.useRef(asPath)
|
||
|
||
// Every time the path changes, announce the new page’s title following this
|
||
// priority: first the document title (from head), otherwise the first h1, or
|
||
// if none of these exist, then the pathname from the URL. This methodology is
|
||
// inspired by Marcy Sutton’s accessible client routing user testing. More
|
||
// information can be found here:
|
||
// https://www.gatsbyjs.com/blog/2019-07-11-user-testing-accessible-client-routing/
|
||
React.useEffect(
|
||
() => {
|
||
// If the path hasn't change, we do nothing.
|
||
if (previouslyLoadedPath.current === asPath) return
|
||
previouslyLoadedPath.current = asPath
|
||
|
||
if (document.title) {
|
||
setRouteAnnouncement(document.title)
|
||
} else {
|
||
const pageHeader = document.querySelector('h1')
|
||
const content = pageHeader?.innerText ?? pageHeader?.textContent
|
||
|
||
setRouteAnnouncement(content || asPath)
|
||
}
|
||
},
|
||
// TODO: switch to pathname + query object of dynamic route requirements
|
||
[asPath]
|
||
)
|
||
|
||
return (
|
||
<p
|
||
aria-live="assertive" // Make the announcement immediately.
|
||
id="__next-route-announcer__"
|
||
role="alert"
|
||
style={nextjsRouteAnnouncerStyles}
|
||
>
|
||
{routeAnnouncement}
|
||
</p>
|
||
)
|
||
}
|
||
|
||
export default RouteAnnouncer
|