Skip to content

Commit

Permalink
refactor(live-region): update live region helpers to match ADR (#4673)
Browse files Browse the repository at this point in the history
* refactor(live-region): update live region helpers to match ADR

* fix: update file paths from moving to live-region folder

* feat(live-region): add support for delayMs to announce

* feat: add support for delayMs to Announce, AriaStatus, AriaAlert

* chore: add changeset

* docs: update stories

* chore: fix eslint violations

* docs: update jsdoc for hook

* fix: update signature to include support for "as"

* chore: add support for sx to components

---------

Co-authored-by: Josh Black <joshblack@users.noreply.github.com>
  • Loading branch information
joshblack and joshblack committed Jun 28, 2024
1 parent 93f2ac0 commit eedc6b1
Show file tree
Hide file tree
Showing 25 changed files with 615 additions and 188 deletions.
5 changes: 5 additions & 0 deletions .changeset/five-humans-retire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': minor
---

Add experimental support for the AriaStatus, AriaAlert, and Announce components
7 changes: 3 additions & 4 deletions packages/react/src/Banner/Banner.examples.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import {Banner} from '../Banner'
import {action} from '@storybook/addon-actions'
import Link from '../Link'
import type {Meta} from '@storybook/react'
import {Status} from '../internal/components/Status'
import {Alert} from '../internal/components/Alert'
import {AriaAlert, AriaStatus} from '../live-region'
import FormControl from '../FormControl'
import RadioGroup from '../RadioGroup'
import Radio from '../Radio'
Expand All @@ -30,7 +29,7 @@ export const WithUserAction = () => {
<Banner
ref={bannerRef}
title="Error"
description={<Alert>Something went wrong. Please try again later.</Alert>}
description={<AriaAlert>Something went wrong. Please try again later.</AriaAlert>}
variant="critical"
/>
) : null}
Expand Down Expand Up @@ -60,7 +59,7 @@ export const WithDynamicContent = () => {
<>
<Banner
title="Info"
description={<Status>{messages.get(selected)}</Status>}
description={<AriaStatus>{messages.get(selected)}</AriaStatus>}
onDismiss={action('onDismiss')}
primaryAction={<Banner.PrimaryAction>Button</Banner.PrimaryAction>}
secondaryAction={<Banner.SecondaryAction>Button</Banner.SecondaryAction>}
Expand Down
8 changes: 4 additions & 4 deletions packages/react/src/Spinner/Spinner.examples.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type {Meta} from '@storybook/react'
import Spinner from './Spinner'
import {Box, Button} from '..'
import {VisuallyHidden} from '../internal/components/VisuallyHidden'
import {Status} from '../internal/components/Status'
import {AriaStatus} from '../live-region'

export default {
title: 'Components/Spinner/Examples',
Expand Down Expand Up @@ -47,7 +47,7 @@ export const FullLifecycle = () => {
{state === 'loading' && <Spinner />}
<p>{loadedContent}</p>
<VisuallyHidden>
<Status>{state === 'done' && 'Content finished loading'}</Status>
<AriaStatus>{state === 'done' && 'Content finished loading'}</AriaStatus>
</VisuallyHidden>
</>
)
Expand Down Expand Up @@ -84,12 +84,12 @@ export const FullLifecycleVisibleLoadingText = () => {
{state !== 'done' && (
<Box sx={{alignItems: 'center', display: 'flex', gap: '0.25rem'}}>
{state === 'loading' && <Spinner size="small" srText={null} />}
<Status>{state === 'loading' ? 'Content is loading...' : ''}</Status>
<AriaStatus>{state === 'loading' ? 'Content is loading...' : ''}</AriaStatus>
</Box>
)}
<p>{loadedContent}</p>
<VisuallyHidden>
<Status>{state === 'done' && 'Content finished loading'}</Status>
<AriaStatus>{state === 'done' && 'Content finished loading'}</AriaStatus>
</VisuallyHidden>
</Box>
)
Expand Down
4 changes: 2 additions & 2 deletions packages/react/src/Spinner/Spinner.features.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react'
import type {Meta} from '@storybook/react'
import Spinner from './Spinner'
import {Box} from '..'
import {Status} from '../internal/components/Status'
import {AriaStatus} from '../live-region'

export default {
title: 'Components/Spinner/Features',
Expand All @@ -16,6 +16,6 @@ export const Large = () => <Spinner size="large" />
export const SuppressScreenReaderText = () => (
<Box sx={{alignItems: 'center', display: 'flex', gap: '0.25rem'}}>
<Spinner size="small" srText={null} />
<Status>Loading...</Status>
<AriaStatus>Loading...</AriaStatus>
</Box>
)
12 changes: 12 additions & 0 deletions packages/react/src/__tests__/__snapshots__/exports.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,12 @@ exports[`@primer/react/drafts should not update exports without a semver change
[
"ActionBar",
"type ActionBarProps",
"Announce",
"type AnnounceProps",
"AriaAlert",
"type AriaAlertProps",
"AriaStatus",
"type AriaStatusProps",
"Banner",
"type BannerProps",
"Blankslate",
Expand Down Expand Up @@ -347,6 +353,12 @@ exports[`@primer/react/experimental should not update exports without a semver c
[
"ActionBar",
"type ActionBarProps",
"Announce",
"type AnnounceProps",
"AriaAlert",
"type AriaAlertProps",
"AriaStatus",
"type AriaStatusProps",
"Banner",
"type BannerProps",
"Blankslate",
Expand Down
7 changes: 4 additions & 3 deletions packages/react/src/drafts/SelectPanel2/SelectPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import type {OverlayProps} from '../../Overlay/Overlay'
import {StyledOverlay, heightMap} from '../../Overlay/Overlay'
import InputLabel from '../../internal/components/InputLabel'
import {invariant} from '../../utils/invariant'
import {Status} from '../../internal/components/Status'
import {AriaStatus} from '../../live-region'
import {useResponsiveValue} from '../../hooks/useResponsiveValue'
import type {ResponsiveValue} from '../../hooks/useResponsiveValue'

Expand Down Expand Up @@ -604,7 +604,8 @@ const SelectPanelSecondaryAction: React.FC<SelectPanelSecondaryActionProps> = ({

const SelectPanelLoading = ({children = 'Fetching items...'}: React.PropsWithChildren) => {
return (
<Status
<AriaStatus
announceOnShow
sx={{
display: 'flex',
flexDirection: 'column',
Expand All @@ -618,7 +619,7 @@ const SelectPanelLoading = ({children = 'Fetching items...'}: React.PropsWithChi
>
<Spinner size="medium" srText={null} />
<Text sx={{fontSize: 1, color: 'fg.muted'}}>{children}</Text>
</Status>
</AriaStatus>
)
}

Expand Down
3 changes: 3 additions & 0 deletions packages/react/src/drafts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ export * from '../ActionBar'
export {Stack} from '../Stack'
export type {StackProps, StackItemProps} from '../Stack'

export {Announce, AriaStatus, AriaAlert} from '../live-region'
export type {AnnounceProps, AriaStatusProps, AriaAlertProps} from '../live-region'

export * from './UnderlinePanels'

export {SkeletonBox, SkeletonText, SkeletonAvatar} from './Skeleton'
12 changes: 0 additions & 12 deletions packages/react/src/internal/components/Alert.tsx

This file was deleted.

71 changes: 0 additions & 71 deletions packages/react/src/internal/components/Announce.tsx

This file was deleted.

12 changes: 0 additions & 12 deletions packages/react/src/internal/components/Status.tsx

This file was deleted.

52 changes: 0 additions & 52 deletions packages/react/src/internal/components/__tests__/Status.test.tsx

This file was deleted.

20 changes: 20 additions & 0 deletions packages/react/src/internal/hooks/useEffectCallback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {useCallback, useEffect, useRef} from 'react'

/**
* Create a callback that can be used within an effect without re-running the
* effect when the values used change. The callback passed to this hook will
* always see the latest snapshot of values that it uses and does not need to
* use a dependency array.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function useEffectCallback<T extends (...args: any) => any>(callback: T) {
const savedCallback = useRef<T>(callback)

useEffect(() => {
savedCallback.current = callback
}, [callback])

return useCallback((...args: Parameters<T>): ReturnType<T> => {
return savedCallback.current(...args)
}, [])
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,11 @@
import type {StoryObj} from '@storybook/react'
import React, {useEffect, useState} from 'react'
import {Status} from './Status'
import {VisuallyHidden} from './VisuallyHidden'
import {Announce} from './Announce'
import {VisuallyHidden} from '../internal/components/VisuallyHidden'

export default {
title: 'Private/Components/Status',
component: Status,
}

export const Default = () => {
const [message, setMessage] = useState('Default message')

useEffect(() => {
const interval = setInterval(() => {
setMessage(`Last updated at ${new Date().toLocaleTimeString()}`)
}, 5000)
return () => {
clearInterval(interval)
}
}, [])

return <Status>{message}</Status>
title: 'Drafts/Components/Announce/Features',
component: Announce,
}

export const VisuallyHiddenStory: StoryObj = {
Expand All @@ -30,9 +15,24 @@ export const VisuallyHiddenStory: StoryObj = {
<>
<p>This is an example</p>
<VisuallyHidden>
<Status>A visually hidden message</Status>
<Announce>A visually hidden message</Announce>
</VisuallyHidden>
</>
)
},
}

export const WithDelay = () => {
const [message, setMessage] = useState('Default message')

useEffect(() => {
const interval = setInterval(() => {
setMessage(`Last updated at ${new Date().toLocaleTimeString()}`)
}, 5000)
return () => {
clearInterval(interval)
}
}, [])

return <Announce delayMs={1000}>{message}</Announce>
}
Loading

0 comments on commit eedc6b1

Please sign in to comment.