Update API

This commit is contained in:
Eric Bailey
2025-07-30 11:59:33 -05:00
parent bda14b3f3c
commit 6e96d3a5f9
5 changed files with 107 additions and 45 deletions
+25 -19
View File
@@ -21,7 +21,7 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {atoms as a} from '#/alf' import {atoms as a} from '#/alf'
import {Toast} from '#/components/Toast/Toast' 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 TIMEOUT = 2e3
const TOAST_ANIMATION_DURATION = 300 const TOAST_ANIMATION_DURATION = 300
@@ -30,30 +30,36 @@ export function ToastContainer() {
return null return null
} }
export function show(message: string, type: ToastType = 'default'): void { export const toast: ToastApi = {
if (process.env.NODE_ENV === 'test') { show(props) {
return if (process.env.NODE_ENV === 'test') {
} return
}
AccessibilityInfo.announceForAccessibility(message) AccessibilityInfo.announceForAccessibility(props.a11yLabel)
const item = new RootSiblings(
( const item = new RootSiblings(
<AnimatedToast (
message={message} <AnimatedToast
type={type} type={props.type}
destroy={() => item.destroy()} content={props.content}
/> a11yLabel={props.a11yLabel}
), destroy={() => item.destroy()}
) />
),
)
},
} }
function AnimatedToast({ function AnimatedToast({
message,
type, type,
content,
a11yLabel,
destroy, destroy,
}: { }: {
message: string
type: ToastType type: ToastType
content: React.ReactNode
a11yLabel: string
destroy: () => void destroy: () => void
}) { }) {
const {top} = useSafeAreaInsets() const {top} = useSafeAreaInsets()
@@ -169,12 +175,12 @@ function AnimatedToast({
onLayout={evt => setCardHeight(evt.nativeEvent.layout.height)} onLayout={evt => setCardHeight(evt.nativeEvent.layout.height)}
accessibilityRole="alert" accessibilityRole="alert"
accessible={true} accessible={true}
accessibilityLabel={message} accessibilityLabel={a11yLabel}
accessibilityHint="" accessibilityHint=""
onAccessibilityEscape={hideAndDestroyImmediately} onAccessibilityEscape={hideAndDestroyImmediately}
style={[a.flex_1, animatedStyle]}> style={[a.flex_1, animatedStyle]}>
<GestureDetector gesture={panGesture}> <GestureDetector gesture={panGesture}>
<Toast content={message} type={type} /> <Toast content={content} type={type} />
</GestureDetector> </GestureDetector>
</Animated.View> </Animated.View>
)} )}
+23 -12
View File
@@ -3,13 +3,13 @@
*/ */
import {useEffect, useState} from 'react' import {useEffect, useState} from 'react'
import {Pressable, View} from 'react-native' import {AccessibilityInfo, Pressable, View} from 'react-native'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {atoms as a, web} from '#/alf' import {atoms as a, web} from '#/alf'
import {Toast} from '#/components/Toast/Toast' 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 DURATION = 3500
const TOAST_ANIMATION_STYLES = { const TOAST_ANIMATION_STYLES = {
@@ -22,8 +22,9 @@ const TOAST_ANIMATION_STYLES = {
} }
interface ActiveToast { interface ActiveToast {
text: string
type: ToastType type: ToastType
content: React.ReactNode
a11yLabel: string
} }
type GlobalSetActiveToast = (_activeToast: ActiveToast | undefined) => void type GlobalSetActiveToast = (_activeToast: ActiveToast | undefined) => void
let globalSetActiveToast: GlobalSetActiveToast | undefined let globalSetActiveToast: GlobalSetActiveToast | undefined
@@ -44,6 +45,9 @@ export const ToastContainer: React.FC<ToastContainerProps> = ({}) => {
setIsExiting(false) setIsExiting(false)
}, 200) }, 200)
} else { } else {
if (t) {
AccessibilityInfo.announceForAccessibility(t.a11yLabel)
}
setActiveToast(t) setActiveToast(t)
setIsExiting(false) setIsExiting(false)
} }
@@ -66,7 +70,7 @@ export const ToastContainer: React.FC<ToastContainerProps> = ({}) => {
: TOAST_ANIMATION_STYLES.entering), : TOAST_ANIMATION_STYLES.entering),
}, },
]}> ]}>
<Toast content={activeToast.text} type={activeToast.type} /> <Toast content={activeToast.content} type={activeToast.type} />
<Pressable <Pressable
style={[a.absolute, a.inset_0]} style={[a.absolute, a.inset_0]}
accessibilityLabel={_(msg`Dismiss toast`)} accessibilityLabel={_(msg`Dismiss toast`)}
@@ -79,13 +83,20 @@ export const ToastContainer: React.FC<ToastContainerProps> = ({}) => {
) )
} }
export function show(text: string, type: ToastType = 'default') { export const toast: ToastApi = {
if (toastTimeout) { show(props) {
clearTimeout(toastTimeout) if (toastTimeout) {
} clearTimeout(toastTimeout)
}
globalSetActiveToast?.({text, type}) globalSetActiveToast?.({
toastTimeout = setTimeout(() => { type: props.type,
globalSetActiveToast?.(undefined) content: props.content,
}, DURATION) a11yLabel: props.a11yLabel,
})
toastTimeout = setTimeout(() => {
globalSetActiveToast?.(undefined)
}, DURATION)
},
} }
+8
View File
@@ -1 +1,9 @@
export type ToastType = 'default' | 'success' | 'error' | 'warning' | 'info' export type ToastType = 'default' | 'success' | 'error' | 'warning' | 'info'
export type ToastApi = {
show: (props: {
type: ToastType
content: React.ReactNode
a11yLabel: string
}) => void
}
+8 -4
View File
@@ -1,8 +1,8 @@
import {show as baseShow} from '#/components/Toast' import {toast} from '#/components/Toast'
import {type ToastType} from '#/components/Toast/types' 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 = export type LegacyToastType =
| 'xmark' | 'xmark'
@@ -39,12 +39,16 @@ export const convertLegacyToastType = (
} }
/** /**
* @deprecated use {@link baseShow} instead * @deprecated use {@link toast} instead
*/ */
export function show( export function show(
message: string, message: string,
type: ToastType | LegacyToastType = 'default', type: ToastType | LegacyToastType = 'default',
): void { ): void {
const convertedType = convertLegacyToastType(type) const convertedType = convertLegacyToastType(type)
baseShow(message, convertedType) toast.show({
type: convertedType,
content: message,
a11yLabel: message,
})
} }
+43 -10
View File
@@ -3,7 +3,7 @@ import {Pressable, View} from 'react-native'
import {show as deprecatedShow} from '#/view/com/util/Toast' import {show as deprecatedShow} from '#/view/com/util/Toast'
import {atoms as a} from '#/alf' import {atoms as a} from '#/alf'
import {Button, ButtonText} from '#/components/Button' import {Button, ButtonText} from '#/components/Button'
import {show} from '#/components/Toast' import {toast} from '#/components/Toast'
import {Toast} from '#/components/Toast/Toast' import {Toast} from '#/components/Toast/Toast'
import {H1} from '#/components/Typography' import {H1} from '#/components/Typography'
@@ -15,16 +15,25 @@ export function Toasts() {
<View style={[a.gap_md]}> <View style={[a.gap_md]}>
<Pressable <Pressable
accessibilityRole="button" accessibilityRole="button"
onPress={() => show('Default toast', 'default')}> onPress={() =>
toast.show({
type: 'default',
content: 'Default toast',
a11yLabel: 'Default toast',
})
}>
<Toast content="Default toast" type="default" /> <Toast content="Default toast" type="default" />
</Pressable> </Pressable>
<Pressable <Pressable
accessibilityRole="button" accessibilityRole="button"
onPress={() => onPress={() =>
show( toast.show({
'This is a longer message to test how the toast handles multiple lines of text content.', type: 'default',
'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.',
})
}> }>
<Toast <Toast
content="This is a longer message to test how the toast handles multiple lines of text content." content="This is a longer message to test how the toast handles multiple lines of text content."
@@ -33,22 +42,46 @@ export function Toasts() {
</Pressable> </Pressable>
<Pressable <Pressable
accessibilityRole="button" accessibilityRole="button"
onPress={() => show('Success toast', 'success')}> onPress={() =>
toast.show({
type: 'success',
content: 'Success toast',
a11yLabel: 'Success toast',
})
}>
<Toast content="Success toast" type="success" /> <Toast content="Success toast" type="success" />
</Pressable> </Pressable>
<Pressable <Pressable
accessibilityRole="button" accessibilityRole="button"
onPress={() => show('Info toast', 'info')}> onPress={() =>
toast.show({
type: 'info',
content: 'Info toast',
a11yLabel: 'Info toast',
})
}>
<Toast content="Info" type="info" /> <Toast content="Info" type="info" />
</Pressable> </Pressable>
<Pressable <Pressable
accessibilityRole="button" accessibilityRole="button"
onPress={() => show('Warning toast', 'warning')}> onPress={() =>
toast.show({
type: 'warning',
content: 'Warning toast',
a11yLabel: 'Warning toast',
})
}>
<Toast content="Warning" type="warning" /> <Toast content="Warning" type="warning" />
</Pressable> </Pressable>
<Pressable <Pressable
accessibilityRole="button" accessibilityRole="button"
onPress={() => show('Error toast', 'error')}> onPress={() =>
toast.show({
type: 'error',
content: 'Error toast',
a11yLabel: 'Error toast',
})
}>
<Toast content="Error" type="error" /> <Toast content="Error" type="error" />
</Pressable> </Pressable>