Bluesky app fork with some witchin' additions 💫

Yeah toast (#8878)

* Split out into macro component

* Add Action component

* Add fallback

* add button to view post after sending

* Dismiss toast when clicking action button

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>

authored by Eric Bailey Samuel Newman and committed by GitHub 8ec20026 acd7211b

Changed files
+414 -89
src
components
view
com
composer
screens
Storybook
+1
.eslintrc.js
··· 34 34 'P', 35 35 'Admonition', 36 36 'Admonition.Admonition', 37 + 'Toast.Action', 37 38 'AgeAssuranceAdmonition', 38 39 'Span', 39 40 ],
+219 -50
src/components/Toast/Toast.tsx
··· 1 1 import {createContext, useContext, useMemo} from 'react' 2 - import {View} from 'react-native' 2 + import {type GestureResponderEvent, View} from 'react-native' 3 3 4 4 import {atoms as a, select, useAlf, useTheme} from '#/alf' 5 + import { 6 + Button, 7 + type ButtonProps, 8 + type UninheritableButtonProps, 9 + } from '#/components/Button' 10 + import {CircleCheck_Stroke2_Corner0_Rounded as CircleCheck} from '#/components/icons/CircleCheck' 5 11 import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' 6 12 import {CircleInfo_Stroke2_Corner0_Rounded as ErrorIcon} from '#/components/icons/CircleInfo' 13 + import {type Props as SVGIconProps} from '#/components/icons/common' 7 14 import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' 15 + import {dismiss} from '#/components/Toast/sonner' 8 16 import {type ToastType} from '#/components/Toast/types' 9 - import {Text} from '#/components/Typography' 10 - import {CircleCheck_Stroke2_Corner0_Rounded as CircleCheck} from '../icons/CircleCheck' 17 + import {Text as BaseText} from '#/components/Typography' 11 18 12 - type ContextType = { 19 + type ToastConfigContextType = { 20 + id: string 21 + } 22 + 23 + type ToastThemeContextType = { 13 24 type: ToastType 14 25 } 15 26 16 27 export type ToastComponentProps = { 17 28 type?: ToastType 18 - content: React.ReactNode 29 + content: string 19 30 } 20 31 21 32 export const ICONS = { ··· 26 37 info: CircleInfo, 27 38 } 28 39 29 - const Context = createContext<ContextType>({ 40 + const ToastConfigContext = createContext<ToastConfigContextType>({ 41 + id: '', 42 + }) 43 + ToastConfigContext.displayName = 'ToastConfigContext' 44 + 45 + export function ToastConfigProvider({ 46 + children, 47 + id, 48 + }: { 49 + children: React.ReactNode 50 + id: string 51 + }) { 52 + return ( 53 + <ToastConfigContext.Provider value={useMemo(() => ({id}), [id])}> 54 + {children} 55 + </ToastConfigContext.Provider> 56 + ) 57 + } 58 + 59 + const ToastThemeContext = createContext<ToastThemeContextType>({ 30 60 type: 'default', 31 61 }) 32 - Context.displayName = 'ToastContext' 62 + ToastThemeContext.displayName = 'ToastThemeContext' 33 63 34 - export function Toast({type = 'default', content}: ToastComponentProps) { 35 - const {fonts} = useAlf() 64 + export function Default({type = 'default', content}: ToastComponentProps) { 65 + return ( 66 + <Outer type={type}> 67 + <Icon /> 68 + <Text>{content}</Text> 69 + </Outer> 70 + ) 71 + } 72 + 73 + export function Outer({ 74 + children, 75 + type = 'default', 76 + }: { 77 + children: React.ReactNode 78 + type?: ToastType 79 + }) { 36 80 const t = useTheme() 37 81 const styles = useToastStyles({type}) 38 - const Icon = ICONS[type] 39 - /** 40 - * Vibes-based number, adjusts `top` of `View` that wraps the text to 41 - * compensate for different type sizes and keep the first line of text 42 - * aligned with the icon. - esb 43 - */ 44 - const fontScaleCompensation = useMemo( 45 - () => parseInt(fonts.scale) * -1 * 0.65, 46 - [fonts.scale], 47 - ) 48 82 49 83 return ( 50 - <Context.Provider value={useMemo(() => ({type}), [type])}> 84 + <ToastThemeContext.Provider value={useMemo(() => ({type}), [type])}> 51 85 <View 52 86 style={[ 53 87 a.flex_1, 54 - a.py_lg, 55 - a.pl_xl, 56 - a.pr_2xl, 88 + a.p_lg, 57 89 a.rounded_md, 58 90 a.border, 59 91 a.flex_row, 60 92 a.gap_sm, 61 93 t.atoms.shadow_sm, 62 94 { 95 + paddingVertical: 14, // 16 seems too big 63 96 backgroundColor: styles.backgroundColor, 64 97 borderColor: styles.borderColor, 65 98 }, 66 99 ]}> 67 - <Icon size="md" fill={styles.iconColor} /> 68 - 69 - <View 70 - style={[ 71 - a.flex_1, 72 - { 73 - top: fontScaleCompensation, 74 - }, 75 - ]}> 76 - {typeof content === 'string' ? ( 77 - <ToastText>{content}</ToastText> 78 - ) : ( 79 - content 80 - )} 81 - </View> 100 + {children} 82 101 </View> 83 - </Context.Provider> 102 + </ToastThemeContext.Provider> 84 103 ) 85 104 } 86 105 87 - export function ToastText({children}: {children: React.ReactNode}) { 88 - const {type} = useContext(Context) 106 + export function Icon({icon}: {icon?: React.ComponentType<SVGIconProps>}) { 107 + const {type} = useContext(ToastThemeContext) 108 + const styles = useToastStyles({type}) 109 + const IconComponent = icon || ICONS[type] 110 + return <IconComponent size="md" fill={styles.iconColor} /> 111 + } 112 + 113 + export function Text({children}: {children: React.ReactNode}) { 114 + const {type} = useContext(ToastThemeContext) 89 115 const {textColor} = useToastStyles({type}) 116 + const {fontScaleCompensation} = useToastFontScaleCompensation() 90 117 return ( 91 - <Text 92 - selectable={false} 118 + <View 93 119 style={[ 94 - a.text_md, 95 - a.font_medium, 96 - a.leading_snug, 97 - a.pointer_events_none, 120 + a.flex_1, 121 + a.pr_lg, 98 122 { 99 - color: textColor, 123 + top: fontScaleCompensation, 100 124 }, 101 125 ]}> 102 - {children} 103 - </Text> 126 + <BaseText 127 + selectable={false} 128 + style={[ 129 + a.text_md, 130 + a.font_medium, 131 + a.leading_snug, 132 + a.pointer_events_none, 133 + { 134 + color: textColor, 135 + }, 136 + ]}> 137 + {children} 138 + </BaseText> 139 + </View> 140 + ) 141 + } 142 + 143 + export function Action( 144 + props: Omit<ButtonProps, UninheritableButtonProps | 'children'> & { 145 + children: string 146 + }, 147 + ) { 148 + const t = useTheme() 149 + const {fontScaleCompensation} = useToastFontScaleCompensation() 150 + const {type} = useContext(ToastThemeContext) 151 + const {id} = useContext(ToastConfigContext) 152 + const styles = useMemo(() => { 153 + const base = { 154 + base: { 155 + textColor: t.palette.contrast_600, 156 + backgroundColor: t.atoms.bg_contrast_25.backgroundColor, 157 + }, 158 + interacted: { 159 + textColor: t.atoms.text.color, 160 + backgroundColor: t.atoms.bg_contrast_50.backgroundColor, 161 + }, 162 + } 163 + return { 164 + default: base, 165 + success: { 166 + base: { 167 + textColor: select(t.name, { 168 + light: t.palette.primary_800, 169 + dim: t.palette.primary_900, 170 + dark: t.palette.primary_900, 171 + }), 172 + backgroundColor: t.palette.primary_25, 173 + }, 174 + interacted: { 175 + textColor: select(t.name, { 176 + light: t.palette.primary_900, 177 + dim: t.palette.primary_975, 178 + dark: t.palette.primary_975, 179 + }), 180 + backgroundColor: t.palette.primary_50, 181 + }, 182 + }, 183 + error: { 184 + base: { 185 + textColor: select(t.name, { 186 + light: t.palette.negative_700, 187 + dim: t.palette.negative_900, 188 + dark: t.palette.negative_900, 189 + }), 190 + backgroundColor: t.palette.negative_25, 191 + }, 192 + interacted: { 193 + textColor: select(t.name, { 194 + light: t.palette.negative_900, 195 + dim: t.palette.negative_975, 196 + dark: t.palette.negative_975, 197 + }), 198 + backgroundColor: t.palette.negative_50, 199 + }, 200 + }, 201 + warning: base, 202 + info: base, 203 + }[type] 204 + }, [t, type]) 205 + 206 + const onPress = (e: GestureResponderEvent) => { 207 + console.log('Toast Action pressed, dismissing toast', id) 208 + dismiss(id) 209 + props.onPress?.(e) 210 + } 211 + 212 + return ( 213 + <View style={{top: fontScaleCompensation}}> 214 + <Button {...props} onPress={onPress}> 215 + {s => { 216 + const interacted = s.pressed || s.hovered || s.focused 217 + return ( 218 + <> 219 + <View 220 + style={[ 221 + a.absolute, 222 + a.curve_continuous, 223 + { 224 + // tiny button styles 225 + top: -5, 226 + bottom: -5, 227 + left: -9, 228 + right: -9, 229 + borderRadius: 6, 230 + backgroundColor: interacted 231 + ? styles.interacted.backgroundColor 232 + : styles.base.backgroundColor, 233 + }, 234 + ]} 235 + /> 236 + <BaseText 237 + style={[ 238 + a.text_md, 239 + a.font_medium, 240 + a.leading_snug, 241 + { 242 + color: interacted 243 + ? styles.interacted.textColor 244 + : styles.base.textColor, 245 + }, 246 + ]}> 247 + {props.children} 248 + </BaseText> 249 + </> 250 + ) 251 + }} 252 + </Button> 253 + </View> 254 + ) 255 + } 256 + 257 + /** 258 + * Vibes-based number, provides t `top` value to wrap the text to compensate 259 + * for different type sizes and keep the first line of text aligned with the 260 + * icon. - esb 261 + */ 262 + function useToastFontScaleCompensation() { 263 + const {fonts} = useAlf() 264 + const fontScaleCompensation = useMemo( 265 + () => parseInt(fonts.scale) * -1 * 0.65, 266 + [fonts.scale], 267 + ) 268 + return useMemo( 269 + () => ({ 270 + fontScaleCompensation, 271 + }), 272 + [fontScaleCompensation], 104 273 ) 105 274 } 106 275
+50 -7
src/components/Toast/index.tsx
··· 1 + import React from 'react' 1 2 import {View} from 'react-native' 3 + import {nanoid} from 'nanoid/non-secure' 2 4 import {toast as sonner, Toaster} from 'sonner-native' 3 5 4 6 import {atoms as a} from '#/alf' 5 7 import {DURATION} from '#/components/Toast/const' 6 8 import { 7 - Toast as BaseToast, 9 + Default as DefaultToast, 10 + Outer as BaseOuter, 8 11 type ToastComponentProps, 12 + ToastConfigProvider, 9 13 } from '#/components/Toast/Toast' 10 14 import {type BaseToastOptions} from '#/components/Toast/types' 11 15 12 16 export {DURATION} from '#/components/Toast/const' 17 + export {Action, Icon, Text} from '#/components/Toast/Toast' 18 + export {type ToastType} from '#/components/Toast/types' 13 19 14 20 /** 15 21 * Toasts are rendered in a global outlet, which is placed at the top of the ··· 22 28 /** 23 29 * The toast UI component 24 30 */ 25 - export function Toast({type, content}: ToastComponentProps) { 31 + export function Default({type, content}: ToastComponentProps) { 32 + return ( 33 + <View style={[a.px_xl, a.w_full]}> 34 + <DefaultToast content={content} type={type} /> 35 + </View> 36 + ) 37 + } 38 + 39 + export function Outer({ 40 + children, 41 + type = 'default', 42 + }: { 43 + children: React.ReactNode 44 + type?: ToastComponentProps['type'] 45 + }) { 26 46 return ( 27 47 <View style={[a.px_xl, a.w_full]}> 28 - <BaseToast content={content} type={type} /> 48 + <BaseOuter type={type}>{children}</BaseOuter> 29 49 </View> 30 50 ) 31 51 } ··· 42 62 content: React.ReactNode, 43 63 {type, ...options}: BaseToastOptions = {}, 44 64 ) { 45 - sonner.custom(<Toast content={content} type={type} />, { 46 - ...options, 47 - duration: options?.duration ?? DURATION, 48 - }) 65 + const id = nanoid() 66 + 67 + if (typeof content === 'string') { 68 + sonner.custom( 69 + <ToastConfigProvider id={id}> 70 + <DefaultToast content={content} type={type} /> 71 + </ToastConfigProvider>, 72 + { 73 + ...options, 74 + id, 75 + duration: options?.duration ?? DURATION, 76 + }, 77 + ) 78 + } else if (React.isValidElement(content)) { 79 + sonner.custom( 80 + <ToastConfigProvider id={id}>{content}</ToastConfigProvider>, 81 + { 82 + ...options, 83 + id, 84 + duration: options?.duration ?? DURATION, 85 + }, 86 + ) 87 + } else { 88 + throw new Error( 89 + `Toast can be a string or a React element, got ${typeof content}`, 90 + ) 91 + } 49 92 }
+36 -6
src/components/Toast/index.web.tsx
··· 1 + import React from 'react' 2 + import {nanoid} from 'nanoid/non-secure' 1 3 import {toast as sonner, Toaster} from 'sonner' 2 4 3 5 import {atoms as a} from '#/alf' 4 6 import {DURATION} from '#/components/Toast/const' 5 - import {Toast} from '#/components/Toast/Toast' 7 + import { 8 + Default as DefaultToast, 9 + ToastConfigProvider, 10 + } from '#/components/Toast/Toast' 6 11 import {type BaseToastOptions} from '#/components/Toast/types' 12 + 13 + export {DURATION} from '#/components/Toast/const' 14 + export * from '#/components/Toast/Toast' 15 + export {type ToastType} from '#/components/Toast/types' 7 16 8 17 /** 9 18 * Toasts are rendered in a global outlet, which is placed at the top of the ··· 32 41 content: React.ReactNode, 33 42 {type, ...options}: BaseToastOptions = {}, 34 43 ) { 35 - sonner(<Toast content={content} type={type} />, { 36 - unstyled: true, // required on web 37 - ...options, 38 - duration: options?.duration ?? DURATION, 39 - }) 44 + const id = nanoid() 45 + 46 + if (typeof content === 'string') { 47 + sonner( 48 + <ToastConfigProvider id={id}> 49 + <DefaultToast content={content} type={type} /> 50 + </ToastConfigProvider>, 51 + { 52 + ...options, 53 + unstyled: true, // required on web 54 + id, 55 + duration: options?.duration ?? DURATION, 56 + }, 57 + ) 58 + } else if (React.isValidElement(content)) { 59 + sonner(<ToastConfigProvider id={id}>{content}</ToastConfigProvider>, { 60 + ...options, 61 + unstyled: true, // required on web 62 + id, 63 + duration: options?.duration ?? DURATION, 64 + }) 65 + } else { 66 + throw new Error( 67 + `Toast can be a string or a React element, got ${typeof content}`, 68 + ) 69 + } 40 70 }
+3
src/components/Toast/sonner/index.ts
··· 1 + import {toast} from 'sonner-native' 2 + 3 + export const dismiss = toast.dismiss
+3
src/components/Toast/sonner/index.web.ts
··· 1 + import {toast} from 'sonner' 2 + 3 + export const dismiss = toast.dismiss
+30 -9
src/view/com/composer/Composer.tsx
··· 46 46 AppBskyFeedDefs, 47 47 type AppBskyFeedGetPostThread, 48 48 AppBskyUnspeccedDefs, 49 + AtUri, 49 50 type BskyAgent, 50 51 type RichText, 51 52 } from '@atproto/api' 52 53 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 53 54 import {msg, plural, Trans} from '@lingui/macro' 54 55 import {useLingui} from '@lingui/react' 56 + import {useNavigation} from '@react-navigation/native' 55 57 import {useQueryClient} from '@tanstack/react-query' 56 58 57 59 import * as apilib from '#/lib/api/index' ··· 70 72 import {usePalette} from '#/lib/hooks/usePalette' 71 73 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 72 74 import {mimeToExt} from '#/lib/media/video/util' 75 + import {type NavigationProp} from '#/lib/routes/types' 73 76 import {logEvent} from '#/lib/statsig/statsig' 74 77 import {cleanError} from '#/lib/strings/errors' 75 78 import {colors} from '#/lib/styles' ··· 123 126 import {UserAvatar} from '#/view/com/util/UserAvatar' 124 127 import {atoms as a, native, useTheme, web} from '#/alf' 125 128 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 129 + import {CircleCheck_Stroke2_Corner0_Rounded as CircleCheckIcon} from '#/components/icons/CircleCheck' 126 130 import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' 127 131 import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons/Emoji' 128 132 import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' 129 133 import {LazyQuoteEmbed} from '#/components/Post/Embed/LazyQuoteEmbed' 130 134 import * as Prompt from '#/components/Prompt' 131 - import * as toast from '#/components/Toast' 135 + import * as Toast from '#/components/Toast' 132 136 import {Text as NewText} from '#/components/Typography' 133 137 import {BottomSheetPortalProvider} from '../../../../modules/bottom-sheet' 134 138 import { ··· 188 192 const {closeAllDialogs} = useDialogStateControlContext() 189 193 const {closeAllModals} = useModalControls() 190 194 const {data: preferences} = usePreferencesQuery() 195 + const navigation = useNavigation<NavigationProp>() 191 196 192 197 const [isKeyboardVisible] = useIsKeyboardVisible({iosUseWillEvents: true}) 193 198 const [isPublishing, setIsPublishing] = useState(false) ··· 521 526 onPostSuccess?.(postSuccessData) 522 527 } 523 528 onClose() 524 - toast.show( 525 - thread.posts.length > 1 526 - ? _(msg`Your posts have been published`) 527 - : replyTo 528 - ? _(msg`Your reply has been published`) 529 - : _(msg`Your post has been published`), 529 + Toast.show( 530 + <Toast.Outer type="success"> 531 + <Toast.Icon icon={CircleCheckIcon} /> 532 + <Toast.Text> 533 + {thread.posts.length > 1 534 + ? _(msg`Your posts were sent`) 535 + : replyTo 536 + ? _(msg`Your reply was sent`) 537 + : _(msg`Your post was sent`)} 538 + </Toast.Text> 539 + {postUri && ( 540 + <Toast.Action 541 + label={_(msg`View post`)} 542 + onPress={() => { 543 + const {host: name, rkey} = new AtUri(postUri) 544 + navigation.navigate('PostThread', {name, rkey}) 545 + }}> 546 + View 547 + </Toast.Action> 548 + )} 549 + </Toast.Outer>, 530 550 {type: 'success'}, 531 551 ) 532 552 }, [ ··· 543 563 replyTo, 544 564 setLangPrefs, 545 565 queryClient, 566 + navigation, 546 567 ]) 547 568 548 569 // Preserves the referential identity passed to each post item. ··· 826 847 if (isNative) return // web only 827 848 const [mimeType] = uri.slice('data:'.length).split(';') 828 849 if (!SUPPORTED_MIME_TYPES.includes(mimeType as SupportedMimeTypes)) { 829 - toast.show(_(msg`Unsupported video type: ${mimeType}`), { 850 + Toast.show(_(msg`Unsupported video type: ${mimeType}`), { 830 851 type: 'error', 831 852 }) 832 853 return ··· 1362 1383 } 1363 1384 1364 1385 errors.map(error => { 1365 - toast.show(error, { 1386 + Toast.show(error, { 1366 1387 type: 'warning', 1367 1388 }) 1368 1389 })
+72 -17
src/view/screens/Storybook/Toasts.tsx
··· 2 2 3 3 import {show as deprecatedShow} from '#/view/com/util/Toast' 4 4 import {atoms as a} from '#/alf' 5 - import * as toast from '#/components/Toast' 6 - import {Toast} from '#/components/Toast/Toast' 5 + import {Globe_Stroke2_Corner0_Rounded as GlobeIcon} from '#/components/icons/Globe' 6 + import * as Toast from '#/components/Toast' 7 + import {Default} from '#/components/Toast/Toast' 7 8 import {H1} from '#/components/Typography' 8 9 10 + function ToastWithAction({type = 'default'}: {type?: Toast.ToastType}) { 11 + return ( 12 + <Toast.Outer type={type}> 13 + <Toast.Icon icon={GlobeIcon} /> 14 + <Toast.Text>This toast has an action button</Toast.Text> 15 + <Toast.Action 16 + label="Action" 17 + onPress={() => console.log('Action clicked!')}> 18 + Action 19 + </Toast.Action> 20 + </Toast.Outer> 21 + ) 22 + } 23 + 24 + function LongToastWithAction({type = 'default'}: {type?: Toast.ToastType}) { 25 + return ( 26 + <Toast.Outer type={type}> 27 + <Toast.Icon icon={GlobeIcon} /> 28 + <Toast.Text> 29 + This is a longer message to test how the toast handles multiple lines of 30 + text content. 31 + </Toast.Text> 32 + <Toast.Action 33 + label="Action" 34 + onPress={() => console.log('Action clicked!')}> 35 + Action 36 + </Toast.Action> 37 + </Toast.Outer> 38 + ) 39 + } 40 + 9 41 export function Toasts() { 10 42 return ( 11 43 <View style={[a.gap_md]}> 12 44 <H1>Toast Examples</H1> 13 45 14 46 <View style={[a.gap_md]}> 47 + <View style={[a.gap_md, {marginHorizontal: a.px_xl.paddingLeft * -1}]}> 48 + <Pressable 49 + accessibilityRole="button" 50 + onPress={() => Toast.show(<ToastWithAction />)}> 51 + <ToastWithAction /> 52 + </Pressable> 53 + <Pressable 54 + accessibilityRole="button" 55 + onPress={() => Toast.show(<LongToastWithAction />)}> 56 + <LongToastWithAction /> 57 + </Pressable> 58 + <Pressable 59 + accessibilityRole="button" 60 + onPress={() => Toast.show(<ToastWithAction type="success" />)}> 61 + <ToastWithAction type="success" /> 62 + </Pressable> 63 + <Pressable 64 + accessibilityRole="button" 65 + onPress={() => Toast.show(<ToastWithAction type="error" />)}> 66 + <ToastWithAction type="error" /> 67 + </Pressable> 68 + </View> 69 + 15 70 <Pressable 16 71 accessibilityRole="button" 17 - onPress={() => toast.show(`Hey I'm a toast!`)}> 18 - <Toast content="Hey I'm a toast!" /> 72 + onPress={() => Toast.show(`Hey I'm a toast!`)}> 73 + <Default content="Hey I'm a toast!" /> 19 74 </Pressable> 20 75 <Pressable 21 76 accessibilityRole="button" 22 77 onPress={() => 23 - toast.show(`This toast will disappear after 6 seconds`, { 78 + Toast.show(`This toast will disappear after 6 seconds`, { 24 79 duration: 6e3, 25 80 }) 26 81 }> 27 - <Toast content="This toast will disappear after 6 seconds" /> 82 + <Default content="This toast will disappear after 6 seconds" /> 28 83 </Pressable> 29 84 <Pressable 30 85 accessibilityRole="button" 31 86 onPress={() => 32 - toast.show( 87 + Toast.show( 33 88 `This is a longer message to test how the toast handles multiple lines of text content.`, 34 89 ) 35 90 }> 36 - <Toast content="This is a longer message to test how the toast handles multiple lines of text content." /> 91 + <Default content="This is a longer message to test how the toast handles multiple lines of text content." /> 37 92 </Pressable> 38 93 <Pressable 39 94 accessibilityRole="button" 40 95 onPress={() => 41 - toast.show(`Success! Yayyyyyyy :)`, { 96 + Toast.show(`Success! Yayyyyyyy :)`, { 42 97 type: 'success', 43 98 }) 44 99 }> 45 - <Toast content="Success! Yayyyyyyy :)" type="success" /> 100 + <Default content="Success! Yayyyyyyy :)" type="success" /> 46 101 </Pressable> 47 102 <Pressable 48 103 accessibilityRole="button" 49 104 onPress={() => 50 - toast.show(`I'm providing info!`, { 105 + Toast.show(`I'm providing info!`, { 51 106 type: 'info', 52 107 }) 53 108 }> 54 - <Toast content="I'm providing info!" type="info" /> 109 + <Default content="I'm providing info!" type="info" /> 55 110 </Pressable> 56 111 <Pressable 57 112 accessibilityRole="button" 58 113 onPress={() => 59 - toast.show(`This is a warning toast`, { 114 + Toast.show(`This is a warning toast`, { 60 115 type: 'warning', 61 116 }) 62 117 }> 63 - <Toast content="This is a warning toast" type="warning" /> 118 + <Default content="This is a warning toast" type="warning" /> 64 119 </Pressable> 65 120 <Pressable 66 121 accessibilityRole="button" 67 122 onPress={() => 68 - toast.show(`This is an error toast :(`, { 123 + Toast.show(`This is an error toast :(`, { 69 124 type: 'error', 70 125 }) 71 126 }> 72 - <Toast content="This is an error toast :(" type="error" /> 127 + <Default content="This is an error toast :(" type="error" /> 73 128 </Pressable> 74 129 75 130 <Pressable ··· 80 135 'exclamation-circle', 81 136 ) 82 137 }> 83 - <Toast 138 + <Default 84 139 content="This is a test of the deprecated API" 85 140 type="warning" 86 141 />