rsnext/packages/next/client/route-announcer.tsx
Wyatt Johnson 6c7e76b551
Hybrid App Hooks Support (#41767)
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.
2022-10-31 20:13:27 -07:00

64 lines
1.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 pages 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 Suttons 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