Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/easy-suits-mate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': patch
---

Replaces 'aria-live' usage and removes internal LiveRegion component
137 changes: 69 additions & 68 deletions packages/react/src/DataTable/Pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {ChevronLeftIcon, ChevronRightIcon} from '@primer/octicons-react'
import type React from 'react'
import {useCallback, useMemo, useState} from 'react'
import {Button} from '../internal/components/ButtonReset'
import {LiveRegion, LiveRegionOutlet, Message} from '../internal/components/LiveRegion'
import {AriaStatus} from '../live-region'
import {VisuallyHidden} from '../VisuallyHidden'
import {warning} from '../utils/warning'
import type {ResponsiveValue} from '../hooks/useResponsiveValue'
Expand Down Expand Up @@ -99,72 +99,69 @@ export function Pagination({
}, [pageCount, pageIndex, showPages])

return (
<LiveRegion>
<LiveRegionOutlet />
<nav aria-label={label} className={clsx('TablePagination', classes.TablePagination)} id={id}>
<Range pageStart={pageStart} pageEnd={pageEnd} totalCount={totalCount} />
<ol
className={clsx('TablePaginationSteps', classes.TablePaginationSteps)}
data-hidden-viewport-ranges={getViewportRangesToHidePages().join(' ')}
>
<Step>
<Button
className={clsx('TablePaginationAction', classes.TablePaginationAction)}
type="button"
data-has-page={hasPreviousPage ? true : undefined}
aria-disabled={!hasPreviousPage ? true : undefined}
onClick={() => {
if (!hasPreviousPage) {
return
}
selectPreviousPage()
}}
>
{hasPreviousPage ? <ChevronLeftIcon /> : null}
<span>Previous</span>
<VisuallyHidden>&nbsp;page</VisuallyHidden>
</Button>
</Step>
{model.map((page, i) => {
if (page.type === 'BREAK') {
return <TruncationStep key={`truncation-${i}`} />
} else if (page.type === 'NUM') {
return (
<Step key={i}>
<Page
active={!!page.selected}
onClick={() => {
selectPage(page.num - 1)
}}
>
{page.num}
{page.precedesBreak ? <VisuallyHidden>…</VisuallyHidden> : null}
</Page>
</Step>
)
}
})}
<Step>
<Button
className={clsx('TablePaginationAction', classes.TablePaginationAction)}
type="button"
data-has-page={hasNextPage ? true : undefined}
aria-disabled={!hasNextPage ? true : undefined}
onClick={() => {
if (!hasNextPage) {
return
}
selectNextPage()
}}
>
<span>Next</span>
<VisuallyHidden>&nbsp;page</VisuallyHidden>
{hasNextPage ? <ChevronRightIcon /> : null}
</Button>
</Step>
</ol>
</nav>
</LiveRegion>
<nav aria-label={label} className={clsx('TablePagination', classes.TablePagination)} id={id}>
<Range pageStart={pageStart} pageEnd={pageEnd} totalCount={totalCount} />
<ol
className={clsx('TablePaginationSteps', classes.TablePaginationSteps)}
data-hidden-viewport-ranges={getViewportRangesToHidePages().join(' ')}
>
<Step>
<Button
className={clsx('TablePaginationAction', classes.TablePaginationAction)}
type="button"
data-has-page={hasPreviousPage ? true : undefined}
aria-disabled={!hasPreviousPage ? true : undefined}
onClick={() => {
if (!hasPreviousPage) {
return
}
selectPreviousPage()
}}
>
{hasPreviousPage ? <ChevronLeftIcon /> : null}
<span>Previous</span>
<VisuallyHidden>&nbsp;page</VisuallyHidden>
</Button>
</Step>
{model.map((page, i) => {
if (page.type === 'BREAK') {
return <TruncationStep key={`truncation-${i}`} />
} else if (page.type === 'NUM') {
return (
<Step key={i}>
<Page
active={!!page.selected}
onClick={() => {
selectPage(page.num - 1)
}}
>
{page.num}
{page.precedesBreak ? <VisuallyHidden>…</VisuallyHidden> : null}
</Page>
</Step>
)
}
})}
<Step>
<Button
className={clsx('TablePaginationAction', classes.TablePaginationAction)}
type="button"
data-has-page={hasNextPage ? true : undefined}
aria-disabled={!hasNextPage ? true : undefined}
onClick={() => {
if (!hasNextPage) {
return
}
selectNextPage()
}}
>
<span>Next</span>
<VisuallyHidden>&nbsp;page</VisuallyHidden>
{hasNextPage ? <ChevronRightIcon /> : null}
</Button>
</Step>
</ol>
</nav>
)
}

Expand All @@ -179,7 +176,11 @@ function Range({pageStart, pageEnd, totalCount}: RangeProps) {
const end = pageEnd
return (
<>
<Message value={`Showing ${start} through ${end} of ${totalCount}`} />
<VisuallyHidden>
<AriaStatus>
Showing {start} through {end} of {totalCount}
</AriaStatus>
</VisuallyHidden>
<p className={clsx('TablePaginationRange', classes.TablePaginationRange)}>
{start}
<VisuallyHidden>&nbsp;through&nbsp;</VisuallyHidden>
Expand Down
9 changes: 7 additions & 2 deletions packages/react/src/Skeleton/Skeleton.examples.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {SkeletonAvatar} from '../SkeletonAvatar'
import {VisuallyHidden} from '../VisuallyHidden'
import {KebabHorizontalIcon} from '@primer/octicons-react'
import classes from './Skeleton.examples.stories.module.css'
import {AriaStatus} from '../experimental'

export default {
title: 'Components/Skeleton/Examples',
Expand Down Expand Up @@ -42,7 +43,9 @@ export const CommentsLoading = () => {
{/** read by screen readers in place of the comments in a skeleton loading state */}
{loading ? <VisuallyHidden>Comments are loading</VisuallyHidden> : null}
{/** when loading is completed, it should be announced by the screen-reader */}
<VisuallyHidden aria-live="polite">{loadingFinished ? 'Comments are loaded' : null}</VisuallyHidden>
<VisuallyHidden>
<AriaStatus>{loadingFinished ? 'Comments are loaded' : null}</AriaStatus>
</VisuallyHidden>
<div className={classes.CommentsSpacing}>
<Button onClick={toggleLoadingState}>{loading ? 'Stop loading' : 'Start loading'}</Button>
{Array.from({length: COMMENT_LIST_LENGTH}, (_, index) => (
Expand Down Expand Up @@ -101,7 +104,9 @@ export const CommentsLoadingWithSuspense = () => {
{/** read by screen readers in place of the comments in a skeleton loading state */}
{loadingStatus === 'pending' ? <VisuallyHidden>Comments are loading</VisuallyHidden> : null}
{/** when loading is completed, it should be announced by the screen-reader */}
<VisuallyHidden aria-live="polite">{loadingStatus === 'fulfilled' ? 'Comments are loaded' : null}</VisuallyHidden>
<VisuallyHidden>
<AriaStatus>{loadingStatus === 'fulfilled' ? 'Comments are loaded' : null}</AriaStatus>
</VisuallyHidden>

{/* aria-busy is passed so the screenreader doesn't announce the skeleton state */}
<div className={classes.CommentsSpacing} aria-busy={loadingStatus === 'pending'}>
Expand Down
33 changes: 23 additions & 10 deletions packages/react/src/TreeView/TreeView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import React from 'react'
import type {SubTreeState} from './TreeView'
import {TreeView} from './TreeView'
import {GearIcon} from '@primer/octicons-react'
import {getLiveRegion} from '../live-region/__tests__/test-helpers'

// TODO: Move this function into a shared location
function renderWithTheme(
Expand Down Expand Up @@ -1391,7 +1392,14 @@ describe('State', () => {
})

describe('Asynchronous loading', () => {
it('updates aria live region when loading is done', () => {
afterEach(() => {
const liveRegion = document.querySelector('live-region')
if (liveRegion) {
document.body.removeChild(liveRegion)
}
})

it('updates aria live region when loading is done', async () => {
function TestTree() {
const [state, setState] = React.useState<SubTreeState>('initial')

Expand Down Expand Up @@ -1423,29 +1431,33 @@ describe('Asynchronous loading', () => {
</div>
)
}
const user = userEvent.setup()
const {getByRole} = renderWithTheme(<TestTree />)

const doneButton = getByRole('button', {name: 'Load'})
const liveRegion = getByRole('status')
const liveRegion = getLiveRegion()

// Live region should be empty
expect(liveRegion).toHaveTextContent('')
expect(liveRegion.getMessage('polite')).toBe('')

// Click load button to mimic async loading
fireEvent.click(doneButton)
await act(async () => {
await user.click(doneButton)
})

expect(liveRegion).toHaveTextContent('Parent content loading')
expect(liveRegion.getMessage('polite')).toBe('Parent content loading')

// Click done button to mimic the completion of async loading
fireEvent.click(doneButton)
await act(async () => {
await user.click(doneButton)
})

act(() => {
vi.runAllTimers()
})

// Live region should be updated
expect(liveRegion).not.toHaveTextContent('Child 2 is empty')
expect(liveRegion).toHaveTextContent('Parent content loaded')
expect(liveRegion.getMessage('polite')).toBe('Parent content loaded')
})

it('moves focus from loading item to first child', async () => {
Expand Down Expand Up @@ -1810,7 +1822,8 @@ describe('CSS Module Migration', () => {
</TreeView>
)

// Testing on the second child element because the first child element is visually hidden
expect(render(<TreeViewTestComponent />).container.children[1]).toHaveClass('test-class-name')
// Find the TreeView ul element (which should have the className)
const treeElement = render(<TreeViewTestComponent />).getByRole('tree')
expect(treeElement).toHaveClass('test-class-name')
})
})
5 changes: 3 additions & 2 deletions packages/react/src/TreeView/TreeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {useIsMacOS} from '../hooks'
import {Tooltip} from '../TooltipV2'
import {isSlot} from '../utils/is-slot'
import type {FCWithSlotMarker} from '../utils/types'
import {AriaStatus} from '../live-region'

// ----------------------------------------------------------------------------
// Context
Expand Down Expand Up @@ -144,8 +145,8 @@ const Root: React.FC<TreeViewProps> = ({
}}
>
<>
<VisuallyHidden role="status" aria-live="polite" aria-atomic="true">
{ariaLiveMessage}
<VisuallyHidden>
<AriaStatus announceOnShow>{ariaLiveMessage}</AriaStatus>
</VisuallyHidden>
<ul
ref={containerRef}
Expand Down
14 changes: 6 additions & 8 deletions packages/react/src/experimental/SelectPanel2/SelectPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -595,9 +595,11 @@ const SelectPanelMessage: React.FC<SelectPanelMessageProps> = ({
title,
children,
}) => {
const MessageWrapper = variant === 'empty' ? 'div' : AriaStatus

if (size === 'full') {
return (
<div aria-live={variant === 'empty' ? undefined : 'polite'} className={classes.MessageFull}>
<MessageWrapper className={classes.MessageFull}>
{variant !== 'empty' ? (
<Octicon
icon={AlertIcon}
Expand All @@ -610,18 +612,14 @@ const SelectPanelMessage: React.FC<SelectPanelMessageProps> = ({
) : null}
<span className={classes.MessageTitle}>{title}</span>
<span className={classes.MessageContent}>{children}</span>
</div>
</MessageWrapper>
)
} else {
return (
<div
aria-live={variant === 'empty' ? undefined : 'polite'}
className={classes.MessageInline}
data-variant={variant}
>
<MessageWrapper className={classes.MessageInline} data-variant={variant}>
<AlertIcon size={16} />
<div>{children}</div>
</div>
</MessageWrapper>
)
}
}
Expand Down
71 changes: 0 additions & 71 deletions packages/react/src/internal/components/LiveRegion.tsx

This file was deleted.

Loading