Skip to content

Commit

Permalink
chore: add 'Skip to main content' button to the main page and documen…
Browse files Browse the repository at this point in the history
…t pages

Closes: INSTUI-4240
  • Loading branch information
ToMESSKa committed Jan 8, 2025
1 parent 56308de commit a7e2315
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 5 deletions.
39 changes: 39 additions & 0 deletions packages/__docs__/src/App/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ import type {
ParsedDocSummary
} from '../../buildScripts/DataTypes.mjs'
import { logError } from '@instructure/console'
import React from 'react'

type AppContextType = {
themeKey: keyof MainDocsData['themes']
Expand All @@ -106,6 +107,8 @@ class App extends Component<AppProps, AppState> {
_mediaQueryListener?: ReturnType<typeof addMediaQueryMatchListener>
_defaultDocumentTitle?: string
_controller?: AbortController
_heroRef: React.RefObject<Hero>
_documentRef: React.RefObject<Document>

constructor(props: AppProps) {
super(props)
Expand All @@ -127,6 +130,9 @@ class App extends Component<AppProps, AppState> {
versionsData: undefined,
iconsData: null
}

this._heroRef = React.createRef()
this._documentRef = React.createRef()
}

fetchDocumentData = async (docId: string) => {
Expand Down Expand Up @@ -453,6 +459,7 @@ class App extends Component<AppProps, AppState> {
themeVariables={themeVariables}
repository={repository}
layout={layout}
ref={this._documentRef}
/>
</Section>
</View>
Expand Down Expand Up @@ -487,6 +494,7 @@ class App extends Component<AppProps, AppState> {
repository={library.repository}
version={library.version}
layout={layout}
ref={this._heroRef}
/>
</InstUISettingsProvider>
)
Expand Down Expand Up @@ -691,6 +699,36 @@ class App extends Component<AppProps, AppState> {
) : null
}

focusMainContent = () => {
if (this._heroRef.current) {
this._heroRef.current.focusMainContent()
} else if (this._documentRef.current) {
this._documentRef.current.focusMainContent()
}
}

renderSkipToMainButton = () => {
const { key } = this.state
const doc = this.state.docsData!.docs[key!]
const theme = this.state.docsData!.themes[key!]
if (!key || key === 'index' || doc) {
return (
<View
as={'button'}
onClick={this.focusMainContent}
tabIndex={0}
css={this.props.styles?.skipToMainButton}
borderRadius="small"
display="inline-block"
padding="small"
background="primary"
>
Skip to main content
</View>
)
}
}

render() {
const key = this.state.key
const { showMenu, layout, docsData, iconsData } = this.state
Expand All @@ -717,6 +755,7 @@ class App extends Component<AppProps, AppState> {
aria-label={key || docsData.library.name}
ref={this.handleContentRef}
>
{this.renderSkipToMainButton()}
{!showMenu && (
<div css={this.props.styles?.hamburger}>
<InstUISettingsProvider>
Expand Down
1 change: 1 addition & 0 deletions packages/__docs__/src/App/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ type AppStyle = ComponentStyle<
| 'hamburger'
| 'inlineNavigation'
| 'globalStyles'
| 'skipToMainButton'
>

type AppTheme = {
Expand Down
15 changes: 15 additions & 0 deletions packages/__docs__/src/App/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,21 @@ const generateStyle = (componentTheme: AppTheme): AppStyle => {
borderInlineEndWidth: componentTheme.navBorderWidth,
borderInlineEndStyle: 'solid'
},
skipToMainButton: {
label: 'skipToMainButton',
position: 'absolute',
left: '-9999px',
zIndex: 999,
marginTop: '6px',
opacity: 0,
height: '60px',
fontSize: '150%',
'&:focus': {
left: '11.5rem',
transform: 'translateX(-50%)',
opacity: 1
}
},
globalStyles: {
html: {
height: '100%',
Expand Down
20 changes: 18 additions & 2 deletions packages/__docs__/src/Description/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@
* SOFTWARE.
*/

import React, { Component, ReactElement } from 'react'
import React, { Component, LegacyRef, ReactElement } from 'react'

import { View } from '@instructure/ui-view'
import { compileMarkdown } from '../compileMarkdown'

import { propTypes, allowedProps } from './props'
Expand All @@ -33,6 +34,7 @@ class Description extends Component<DescriptionProps> {
static propTypes = propTypes
static allowedProps = allowedProps
compiledMarkdown: ReactElement | null = null
_mainContent?: HTMLElement

constructor(props: DescriptionProps) {
super(props)
Expand All @@ -42,10 +44,24 @@ class Description extends Component<DescriptionProps> {
)
}

mainContentRef = (el: Element | null) => {
this._mainContent = el as HTMLElement
}

focusMainContent = () => {
if (this._mainContent) {
this._mainContent.focus()
}
}

render() {
const { id } = this.props

return <div id={id}>{this.compiledMarkdown}</div>
return (
<View as="div" id={id} elementRef={this.mainContentRef} tabIndex={0}>
{this.compiledMarkdown}
</View>
)
}
}

Expand Down
6 changes: 4 additions & 2 deletions packages/__docs__/src/Description/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type DescriptionOwnProps = {
id: string
content: string
title: string
tabIndex?: number
}

type PropKeys = keyof DescriptionOwnProps
Expand All @@ -38,10 +39,11 @@ type DescriptionProps = DescriptionOwnProps
const propTypes: PropValidators<PropKeys> = {
id: PropTypes.string.isRequired,
content: PropTypes.string.isRequired,
title: PropTypes.string.isRequired
title: PropTypes.string.isRequired,
tabIndex: PropTypes.number
}

const allowedProps: AllowedPropKeys = ['content', 'id', 'title']
const allowedProps: AllowedPropKeys = ['content', 'id', 'title', 'tabIndex']

export type { DescriptionProps }
export { propTypes, allowedProps }
11 changes: 11 additions & 0 deletions packages/__docs__/src/Document/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { Heading } from '../Heading'

import { propTypes, allowedProps } from './props'
import type { DocumentProps, DocumentState, DocDataType } from './props'
import React from 'react'

@withStyle(generateStyle)
class Document extends Component<DocumentProps, DocumentState> {
Expand All @@ -64,6 +65,8 @@ class Document extends Component<DocumentProps, DocumentState> {

ref: HTMLDivElement | null = null

_mainContent = React.createRef<Description>()

componentDidMount() {
this.props.makeStyles?.()
this.setState({ pageRef: this.ref })
Expand All @@ -73,6 +76,12 @@ class Document extends Component<DocumentProps, DocumentState> {
this.props.makeStyles?.()
}

focusMainContent = () => {
if (this._mainContent.current) {
this._mainContent.current.focusMainContent()
}
}

handleDetailsTabChange: TabsProps['onRequestTabChange'] = (
_event,
{ index }
Expand Down Expand Up @@ -176,6 +185,8 @@ class Document extends Component<DocumentProps, DocumentState> {
id={`${id}Description`}
content={filteredDescription}
title={title}
ref={this._mainContent}
tabIndex={0}
/>
) : null
}
Expand Down
20 changes: 19 additions & 1 deletion packages/__docs__/src/Hero/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import { Heading } from '../Heading'

import type { HeroProps } from './props'
import { propTypes, allowedProps } from './props'
import React from 'react'

@withStyle(generateStyle, generateComponentTheme)
class Hero extends Component<HeroProps> {
Expand All @@ -61,6 +62,8 @@ class Hero extends Component<HeroProps> {
docs: null
}

_mainContent?: HTMLElement

componentDidMount() {
this.props.makeStyles?.()
}
Expand All @@ -69,6 +72,16 @@ class Hero extends Component<HeroProps> {
this.props.makeStyles?.()
}

mainContentRef = (el: Element | null) => {
this._mainContent = el as HTMLElement
}

focusMainContent = () => {
if (this._mainContent) {
this._mainContent.focus()
}
}

render() {
const { version, layout, styles } = this.props

Expand Down Expand Up @@ -191,7 +204,12 @@ class Hero extends Component<HeroProps> {
const checkmark = <IconCheckMarkSolid inline={false} color="success" />

const heroBodyContent = (
<View as="div">
<View
as="div"
elementRef={this.mainContentRef}
tabIndex={0}
aria-label="main content"
>
<Heading as="h3" level="h2" margin="none none medium">
Components everyone can count on
</Heading>
Expand Down

0 comments on commit a7e2315

Please sign in to comment.