From 6e96d3a5f941040dc3d989c02ca05b517d4e680f Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Wed, 30 Jul 2025 11:59:33 -0500 Subject: [PATCH] Update API --- src/components/Toast/index.tsx | 44 ++++++++++++---------- src/components/Toast/index.web.tsx | 35 ++++++++++++------ src/components/Toast/types.ts | 8 ++++ src/view/com/util/Toast.tsx | 12 ++++-- src/view/screens/Storybook/Toasts.tsx | 53 ++++++++++++++++++++++----- 5 files changed, 107 insertions(+), 45 deletions(-) diff --git a/src/components/Toast/index.tsx b/src/components/Toast/index.tsx index b6316bc5b5..31135ed69f 100644 --- a/src/components/Toast/index.tsx +++ b/src/components/Toast/index.tsx @@ -21,7 +21,7 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {atoms as a} from '#/alf' import {Toast} from '#/components/Toast/Toast' -import {type ToastType} from '#/components/Toast/types' +import {type ToastApi, type ToastType} from '#/components/Toast/types' const TIMEOUT = 2e3 const TOAST_ANIMATION_DURATION = 300 @@ -30,30 +30,36 @@ export function ToastContainer() { return null } -export function show(message: string, type: ToastType = 'default'): void { - if (process.env.NODE_ENV === 'test') { - return - } +export const toast: ToastApi = { + show(props) { + if (process.env.NODE_ENV === 'test') { + return + } - AccessibilityInfo.announceForAccessibility(message) - const item = new RootSiblings( - ( - item.destroy()} - /> - ), - ) + AccessibilityInfo.announceForAccessibility(props.a11yLabel) + + const item = new RootSiblings( + ( + item.destroy()} + /> + ), + ) + }, } function AnimatedToast({ - message, type, + content, + a11yLabel, destroy, }: { - message: string type: ToastType + content: React.ReactNode + a11yLabel: string destroy: () => void }) { const {top} = useSafeAreaInsets() @@ -169,12 +175,12 @@ function AnimatedToast({ onLayout={evt => setCardHeight(evt.nativeEvent.layout.height)} accessibilityRole="alert" accessible={true} - accessibilityLabel={message} + accessibilityLabel={a11yLabel} accessibilityHint="" onAccessibilityEscape={hideAndDestroyImmediately} style={[a.flex_1, animatedStyle]}> - + )} diff --git a/src/components/Toast/index.web.tsx b/src/components/Toast/index.web.tsx index 7371b87d55..82c38f8d1c 100644 --- a/src/components/Toast/index.web.tsx +++ b/src/components/Toast/index.web.tsx @@ -3,13 +3,13 @@ */ import {useEffect, useState} from 'react' -import {Pressable, View} from 'react-native' +import {AccessibilityInfo, Pressable, View} from 'react-native' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {atoms as a, web} from '#/alf' import {Toast} from '#/components/Toast/Toast' -import {type ToastType} from '#/components/Toast/types' +import {type ToastApi, type ToastType} from '#/components/Toast/types' const DURATION = 3500 const TOAST_ANIMATION_STYLES = { @@ -22,8 +22,9 @@ const TOAST_ANIMATION_STYLES = { } interface ActiveToast { - text: string type: ToastType + content: React.ReactNode + a11yLabel: string } type GlobalSetActiveToast = (_activeToast: ActiveToast | undefined) => void let globalSetActiveToast: GlobalSetActiveToast | undefined @@ -44,6 +45,9 @@ export const ToastContainer: React.FC = ({}) => { setIsExiting(false) }, 200) } else { + if (t) { + AccessibilityInfo.announceForAccessibility(t.a11yLabel) + } setActiveToast(t) setIsExiting(false) } @@ -66,7 +70,7 @@ export const ToastContainer: React.FC = ({}) => { : TOAST_ANIMATION_STYLES.entering), }, ]}> - + = ({}) => { ) } -export function show(text: string, type: ToastType = 'default') { - if (toastTimeout) { - clearTimeout(toastTimeout) - } +export const toast: ToastApi = { + show(props) { + if (toastTimeout) { + clearTimeout(toastTimeout) + } - globalSetActiveToast?.({text, type}) - toastTimeout = setTimeout(() => { - globalSetActiveToast?.(undefined) - }, DURATION) + globalSetActiveToast?.({ + type: props.type, + content: props.content, + a11yLabel: props.a11yLabel, + }) + + toastTimeout = setTimeout(() => { + globalSetActiveToast?.(undefined) + }, DURATION) + }, } diff --git a/src/components/Toast/types.ts b/src/components/Toast/types.ts index 8969f4a01f..5d03b05938 100644 --- a/src/components/Toast/types.ts +++ b/src/components/Toast/types.ts @@ -1 +1,9 @@ export type ToastType = 'default' | 'success' | 'error' | 'warning' | 'info' + +export type ToastApi = { + show: (props: { + type: ToastType + content: React.ReactNode + a11yLabel: string + }) => void +} diff --git a/src/view/com/util/Toast.tsx b/src/view/com/util/Toast.tsx index 536ce4c1e4..37ec6acb53 100644 --- a/src/view/com/util/Toast.tsx +++ b/src/view/com/util/Toast.tsx @@ -1,8 +1,8 @@ -import {show as baseShow} from '#/components/Toast' +import {toast} from '#/components/Toast' import {type ToastType} from '#/components/Toast/types' /** - * @deprecated use {@link ToastType} and {@link baseShow} instead + * @deprecated use {@link ToastType} and {@link toast} instead */ export type LegacyToastType = | 'xmark' @@ -39,12 +39,16 @@ export const convertLegacyToastType = ( } /** - * @deprecated use {@link baseShow} instead + * @deprecated use {@link toast} instead */ export function show( message: string, type: ToastType | LegacyToastType = 'default', ): void { const convertedType = convertLegacyToastType(type) - baseShow(message, convertedType) + toast.show({ + type: convertedType, + content: message, + a11yLabel: message, + }) } diff --git a/src/view/screens/Storybook/Toasts.tsx b/src/view/screens/Storybook/Toasts.tsx index 59be40ef80..7c92b8348c 100644 --- a/src/view/screens/Storybook/Toasts.tsx +++ b/src/view/screens/Storybook/Toasts.tsx @@ -3,7 +3,7 @@ import {Pressable, View} from 'react-native' import {show as deprecatedShow} from '#/view/com/util/Toast' import {atoms as a} from '#/alf' import {Button, ButtonText} from '#/components/Button' -import {show} from '#/components/Toast' +import {toast} from '#/components/Toast' import {Toast} from '#/components/Toast/Toast' import {H1} from '#/components/Typography' @@ -15,16 +15,25 @@ export function Toasts() { show('Default toast', 'default')}> + onPress={() => + toast.show({ + type: 'default', + content: 'Default toast', + a11yLabel: 'Default toast', + }) + }> - show( - 'This is a longer message to test how the toast handles multiple lines of text content.', - 'default', - ) + toast.show({ + type: 'default', + content: + 'This is a longer message to test how the toast handles multiple lines of text content.', + a11yLabel: + 'This is a longer message to test how the toast handles multiple lines of text content.', + }) }> show('Success toast', 'success')}> + onPress={() => + toast.show({ + type: 'success', + content: 'Success toast', + a11yLabel: 'Success toast', + }) + }> show('Info toast', 'info')}> + onPress={() => + toast.show({ + type: 'info', + content: 'Info toast', + a11yLabel: 'Info toast', + }) + }> show('Warning toast', 'warning')}> + onPress={() => + toast.show({ + type: 'warning', + content: 'Warning toast', + a11yLabel: 'Warning toast', + }) + }> show('Error toast', 'error')}> + onPress={() => + toast.show({ + type: 'error', + content: 'Error toast', + a11yLabel: 'Error toast', + }) + }>