Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(live-region): update live region helpers to match ADR #4673

Merged
merged 11 commits into from
Jun 28, 2024
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stupid question, are there styles attached to <Status> and is that different than <AriaStatus>

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jonrohan no styles, just behavior for now 👀 They're both setup as a Box though internally 😅

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.

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
Loading