feat: adding a skip to search anchor (#850)
Co-authored-by: Luke Karrys <luke@lukekarrys.com>
Ivan Demchenko committed
Dec 4, 2023 at 23:29 UTC
e388aee29541fd6a2a6af02cc29e740e20582172
4 files changed
+51
-34
gatsby-browser.js
+8
@@ -1,3 +1,5 @@
1
+import {SKIP_TO_SEARCH_ID} from './src/constants'
2
+
3
export {default as wrapPageElement} from './src/page'
4
export {default as wrapRootElement} from './src/root'
5
@@ -5,3 +7,9 @@ export const shouldUpdateScroll = ({routerProps}) => {
7
const {scrollUpdate = true} = routerProps.location.state ?? {}
8
return scrollUpdate
9
}
10
+
11
+export const onRouteUpdate = ({location, prevLocation}) => {
12
+ if (location.hash === `#${SKIP_TO_SEARCH_ID}` && prevLocation?.hash !== `#${SKIP_TO_SEARCH_ID}`) {
13
+ document.getElementById(SKIP_TO_SEARCH_ID)?.focus()
14
+ }
15
+}
src/components/skip-nav.js
+33
-32
@@ -1,49 +1,50 @@
1
import React from 'react'
2
-import {Box} from '@primer/react'
2
+import {Box, themeGet} from '@primer/react'
3
import styled from 'styled-components'
4
import Link from './link'
5
-import {SCROLL_MARGIN_TOP} from '../constants'
5
+import {SCROLL_MARGIN_TOP, SKIP_TO_CONTENT_ID} from '../constants'
6
7
-const ID = 'skip-nav'
8
-
9
-const SkipLinkBase = props => (
10
- <Link
11
- {...props}
12
- href={`#${ID}`}
13
- sx={{
14
- p: 3,
15
- color: 'fg.onEmphasis',
16
- backgroundColor: 'accent.emphasis',
17
- fontSize: 1,
18
- }}
19
- >
20
- Skip to content
21
- </Link>
22
-)
7
+export const SkipLink = styled(Link)`
8
+ color: ${themeGet('colors.accent.emphasis')};
9
+ padding: ${themeGet('space.1')};
10
+ &:focus {
11
+ text-decoration: underline;
12
+ }
13
+`
14
15
// The following rules are to ensure that the element is visually hidden, unless
16
// it has focus. This is the recommended way to hide content from:
17
// https://webaim.org/techniques/css/invisiblecontent/#techniques
27
-export const SkipLink = styled(SkipLinkBase)`
18
+export const SkipBox = styled.div`
19
+ display: inline-flex;
20
z-index: 20;
29
- width: auto;
30
- height: auto;
31
- clip: auto;
32
- position: absolute;
33
- overflow: hidden;
21
left: 10px;
22
+ gap: 3px;
23
+ position: absolute;
24
+ transform: translateY(-100%);
25
+ transition: transform 0.3s;
26
+ padding: ${themeGet('space.2')};
27
+ background-color: ${themeGet('colors.canvas.default')};
28
+ border: 1px solid ${themeGet('colors.accent.emphasis')};
29
+ border-top: 0;
30
+ font-size: ${themeGet('fontSizes.1')};
31
+ border-radius: 0 0 ${themeGet('radii.2')} ${themeGet('radii.2')};
32
+
33
+
34
+ &:focus-within {
35
+ transform: translateY(0%);
36
+ }
37
+
38
+ & > * {
39
+ margin-right: ${themeGet('space.1')};
40
+ }
41
36
- &:not(:focus) {
37
- clip: rect(1px, 1px, 1px, 1px);
38
- clip-path: inset(50%);
39
- height: 1px;
40
- width: 1px;
41
- margin: -1px;
42
- padding: 0;
42
+ & > *:last-child {
43
+ margin-right: 0;
44
}
45
`
46
46
-const SkipNavBase = props => <Box id={ID} {...props} />
47
+const SkipNavBase = props => <Box id={SKIP_TO_CONTENT_ID} {...props} />
48
49
export const SkipNav = styled(SkipNavBase)`
50
scroll-margin-top: ${SCROLL_MARGIN_TOP}px;
src/constants.js
+4
@@ -6,4 +6,8 @@ export const FULL_HEADER_HEIGHT = HEADER_HEIGHT + HEADER_BAR
6
7
export const SCROLL_MARGIN_TOP = FULL_HEADER_HEIGHT + 24
8
9
+export const SKIP_TO_CONTENT_ID = 'skip-to-content'
10
+
11
+export const SKIP_TO_SEARCH_ID = 'search-box-input'
12
+
13
export const CLI_PATH = '/cli'
src/page.js
+6
-2
@@ -4,7 +4,8 @@ import {createGlobalStyle} from 'styled-components'
4
import Slugger from 'github-slugger'
5
import Header from './components/header'
6
import Sidebar from './components/sidebar'
7
-import {SkipLink} from './components/skip-nav'
7
+import {SkipBox, SkipLink} from './components/skip-nav'
8
+import {SKIP_TO_CONTENT_ID, SKIP_TO_SEARCH_ID} from './constants'
9
10
import {PageProvider} from './hooks/use-page'
11
import Layout from './layout'
@@ -27,7 +28,10 @@ const PageElement = ({element, props}) => {
28
return (
29
<BaseStyles>
30
<GlobalStyles />
30
- <SkipLink />
31
+ <SkipBox>
32
+ <SkipLink href={`#${SKIP_TO_SEARCH_ID}`}>Skip to search</SkipLink>
33
+ <SkipLink href={`#${SKIP_TO_CONTENT_ID}`}>Skip to content</SkipLink>
34
+ </SkipBox>
35
<PageProvider value={page}>
36
<Box sx={{display: 'flex', flexDirection: 'column', minHeight: '100vh'}}>
37
<Header />