Make scrollable code boxes accessible by keyboards (#296)

* Make scrollable code boxes accessible by keyboards * Fixed the issue on MacOS+Chrome (triple announce) * Resize the scroll handle * Add aria-label to the scroll handle

Nurbol Alpysbayev committed Nov 18, 2022 at 18:37 UTC c2cfe938df7cb5bd3d7ab13a92194f374ddfcacb
1 file changed +26 -1
theme/src/components/code.js
+26 -1
@@ -2,12 +2,19 @@ import {Absolute, BorderBox, Relative, Text} from '@primer/components'
2 import Highlight, {defaultProps} from 'prism-react-renderer'
3 import githubTheme from 'prism-react-renderer/themes/github'
4 import React from 'react'
5 +import {useState, useEffect} from 'react'
6 import ClipboardCopy from './clipboard-copy'
7 import LiveCode from './live-code'
8
9 function Code({className, children, live, noinline}) {
10 + const [scrollHandleStyle, setScrollHandleStyle] = useState({position: 'absolute'})
11 const language = className ? className.replace(/language-/, '') : ''
12 const code = children.trim()
13 + const scrollHandleRef = React.createRef()
14 +
15 + useEffect(() => {
16 + resizeScrollHandle()
17 + })
18
19 if (live) {
20 return <LiveCode code={code} language={language} noinline={noinline} />
@@ -15,7 +22,7 @@ function Code({className, children, live, noinline}) {
22
23 return (
24 <Relative>
18 - <Absolute top={0} right={0} p={2}>
25 + <Absolute top={0} right={0} p={2} zIndex={1}>
26 <ClipboardCopy value={code} />
27 </Absolute>
28 <Highlight
@@ -34,6 +41,8 @@ function Code({className, children, live, noinline}) {
41 border={0}
42 style={{...style, overflow: 'auto'}}
43 >
44 + {/* This is the scroll handle, it is supposed to be focused with keyboard and scroll a wide codebox horizontally */}
45 + <div aria-hidden="true" tabIndex={0} style={scrollHandleStyle} ref={scrollHandleRef} aria-label={code}>&nbsp;</div>
46 {tokens.map((line, i) => (
47 <div key={i} {...getLineProps({line, key: i})}>
48 {line.map((token, key) => (
@@ -51,6 +60,22 @@ function Code({className, children, live, noinline}) {
60 </Highlight>
61 </Relative>
62 )
63 +
64 + /**
65 + * Resize the scroll handle to the size of the code contents, since the former has to be positioned absolutely.
66 + */
67 + function resizeScrollHandle() {
68 + // Skip if already resized.
69 + if (typeof scrollHandleStyle.width !== 'undefined')
70 + return
71 +
72 + const node = scrollHandleRef.current
73 + node.parentElement.style.position = 'relative'
74 + const computedStyle = getComputedStyle(node.parentElement)
75 + const height = node.parentElement.clientHeight - parseInt(computedStyle.paddingTop, 10) - parseInt(computedStyle.paddingBottom, 10)
76 + const width = node.parentElement.scrollWidth - parseInt(computedStyle.paddingLeft, 10) - parseInt(computedStyle.paddingRight, 10)
77 + setScrollHandleStyle({...scrollHandleStyle, height, width})
78 + }
79 }
80
81 export default Code