Skip to content

Commit

Permalink
Merge pull request #444 from department-of-veterans-affairs/feature/4…
Browse files Browse the repository at this point in the history
…40-narin-snackbar-screenreader-auto-dismiss

[Feature] Snackbar – Add auto-dismiss logic for screen readers when no action provided
  • Loading branch information
narin authored Aug 20, 2024
2 parents e1a0d7e + e3fd493 commit a47db2a
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 16 deletions.
2 changes: 1 addition & 1 deletion packages/components/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@department-of-veterans-affairs/mobile-component-library",
"version": "0.21.1-alpha.19",
"version": "0.21.1-alpha.21",
"description": "VA Design System Mobile Component Library",
"main": "src/index.tsx",
"scripts": {
Expand Down
18 changes: 16 additions & 2 deletions packages/components/src/components/Snackbar/Snackbar.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,27 @@ const mobileComponentRenderer = (props: storyArgs) => {
return <Button label="Press for Snackbar" onPress={onPressSnackbar} />
}

export const _Snackbar: Story = {
render: Platform.OS !== 'web' ? mobileComponentRenderer : undefined, // Render Snackbar flat in web
// Render Snackbar flat in web
const render = Platform.OS !== 'web' ? mobileComponentRenderer : undefined

export const _Default: Story = {
render,
args: {
message: 'Message moved to Test Folder',
data: {
isError: false,
messageA11y: 'Message moved to Test Folder with accessibility override',
},
},
}

export const _WithAction: Story = {
render,
args: {
message: 'Message sent',
data: {
isError: false,
messageA11y: 'Message sent with accessibility override',
onActionPressed: () => console.log('Action pressed'),
},
},
Expand Down
12 changes: 8 additions & 4 deletions packages/components/src/components/Snackbar/SnackbarProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ type SnackbarContextType = {
setOffset: (newOffset: number) => void
}

export const SNACKBAR_DURATIONS = {
DEFAULT: 1000000000000, // Essentially indefinite until dismissed
SCREEN_READER: 5000,
}

export const SnackbarContext = createContext<SnackbarContextType | undefined>(
undefined,
)
Expand All @@ -19,17 +24,16 @@ export const SnackbarProvider: React.FC<{ children: ReactNode }> = ({
children,
}) => {
// TODO: Check global default setting
const defaultOffset = useSnackbarDefaultOffset()
const [offset, setOffset] = useState(defaultOffset)
const [offset, setOffset] = useState(useSnackbarDefaultOffset())

return (
<SnackbarContext.Provider value={{ offset, setOffset }}>
<ToastProvider
animationDuration={100}
duration={1000000000000} // Essentially indefinite until dismissed
duration={SNACKBAR_DURATIONS.DEFAULT}
offset={offset}
placement="bottom"
renderToast={(toast) => <Snackbar {...toast as SnackbarProps} />}
renderToast={(toast) => <Snackbar {...(toast as SnackbarProps)} />}
swipeEnabled={false}>
{children}
</ToastProvider>
Expand Down
27 changes: 18 additions & 9 deletions packages/components/src/components/Snackbar/useSnackbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { useContext } from 'react'

import * as Toast from 'react-native-toast-notifications'

import { SnackbarContext } from './SnackbarProvider'
import { SNACKBAR_DURATIONS, SnackbarContext } from './SnackbarProvider'
import { SnackbarOptions } from './Snackbar'
import { useIsScreenReaderEnabled } from '../../utils'
import { useSnackbarDefaultOffset } from './useSnackbarDefaultOffset'

/**
Expand All @@ -15,23 +16,31 @@ export function useSnackbar() {
const toast = Toast.useToast()
const context = useContext(SnackbarContext)
const defaultOffset = useSnackbarDefaultOffset()
const screenReaderEnabled = useIsScreenReaderEnabled()

if (!context) {
throw new Error('useSnackbar must be used within a SnackbarProvider')
}

const { offset, setOffset } = context

const show = (message: string, snackbarOptions?: SnackbarOptions) => {
toast.hideAll()
const { offset, setOffset } = context

// Adjust offset if provided or reset to default
const newOffset = snackbarOptions?.offset || defaultOffset

if (snackbarOptions?.offset) {
setOffset(snackbarOptions.offset)
} else if (offset !== defaultOffset) {
setOffset(defaultOffset)
// Only call setOffset if different from current to avoid re-render
if (newOffset !== offset) {
setOffset(newOffset)
}

return toast.show(message, { data: snackbarOptions })
// Auto-dismiss if screen reader is on and there is no action button
const duration =
screenReaderEnabled && !snackbarOptions?.onActionPressed
? SNACKBAR_DURATIONS.SCREEN_READER
: SNACKBAR_DURATIONS.DEFAULT

toast.hideAll()
toast.show(message, { data: snackbarOptions, duration })
}

return {
Expand Down

0 comments on commit a47db2a

Please sign in to comment.