diff --git a/src/components/Toast/Toast.tsx b/src/components/Toast/Toast.tsx
index ac5bc4889a..1cbeaa9b3a 100644
--- a/src/components/Toast/Toast.tsx
+++ b/src/components/Toast/Toast.tsx
@@ -12,7 +12,7 @@ import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/ico
import {CircleInfo_Stroke2_Corner0_Rounded as ErrorIcon} from '#/components/icons/CircleInfo'
import {type Props as SVGIconProps} from '#/components/icons/common'
import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
-import {dismiss} from '#/components/Toast/sonner'
+import {dismiss} from '#/components/Toast'
import {type ToastType} from '#/components/Toast/types'
import {Text as BaseText} from '#/components/Typography'
@@ -177,7 +177,6 @@ export function Action(
}, [t, type])
const onPress = (e: GestureResponderEvent) => {
- console.log('Toast Action pressed, dismissing toast', id)
dismiss(id)
props.onPress?.(e)
}
diff --git a/src/components/Toast/index.ios.tsx b/src/components/Toast/index.ios.tsx
new file mode 100644
index 0000000000..041b088820
--- /dev/null
+++ b/src/components/Toast/index.ios.tsx
@@ -0,0 +1,149 @@
+import React, {useSyncExternalStore} from 'react'
+import {View} from 'react-native'
+import {useSafeAreaInsets} from 'react-native-safe-area-context'
+import {nanoid} from 'nanoid/non-secure'
+import {toast as sonner} from 'sonner-native'
+
+import {atoms as a} from '#/alf'
+import {DURATION} from '#/components/Toast/const'
+import {
+ Icon as ToastIcon,
+ Outer as BaseOuter,
+ Text as ToastText,
+ ToastConfigProvider,
+} from '#/components/Toast/Toast'
+import {type BaseToastOptions} from '#/components/Toast/types'
+
+export {DURATION} from '#/components/Toast/const'
+export {Action, Icon, Text, ToastConfigProvider} from '#/components/Toast/Toast'
+export {type ToastType} from '#/components/Toast/types'
+
+type ToastConfig = {
+ id: string
+ options: BaseToastOptions
+ component: React.ReactNode
+}
+
+class ToastsManager {
+ private toasts: ToastConfig[] = []
+ private listeners = new Set<() => void>()
+
+ getToasts = (): ToastConfig[] => {
+ return this.toasts
+ }
+
+ subscribe = (listener: () => void) => {
+ this.listeners.add(listener)
+ return () => {
+ this.listeners.delete(listener)
+ }
+ }
+
+ sync = () => {
+ this.listeners.forEach(listener => listener())
+ }
+
+ add = (toast: ToastConfig) => {
+ this.toasts = [...this.toasts, toast]
+ this.sync()
+
+ setTimeout(() => {
+ this.remove(toast.id)
+ }, toast.options.duration || DURATION)
+ }
+
+ remove = (id: string) => {
+ this.toasts = this.toasts.filter(t => t.id !== id)
+ this.sync()
+ }
+}
+
+const manager = new ToastsManager()
+
+export const dismiss = (id: string) => {
+ manager.remove(id)
+}
+
+/**
+ * Toasts are rendered in a global outlet, which is placed at the top of the
+ * component tree.
+ */
+export function ToastOutlet() {
+ const toasts = useSyncExternalStore(manager.subscribe, manager.getToasts)
+ const insets = useSafeAreaInsets()
+ return (
+
+ {toasts.map(({id, component}) => (
+ {component}
+ ))}
+
+ )
+}
+
+export function Outer({children}: {children: React.ReactNode}) {
+ return (
+
+ {children}
+
+ )
+}
+
+/**
+ * Access the full Sonner API
+ * @deprecated TBD
+ */
+export const api = sonner
+
+/**
+ * Our base toast API, using the `Toast` export of this file.
+ */
+export function show(
+ content: React.ReactNode,
+ {type = 'default', ...options}: BaseToastOptions = {},
+) {
+ const id = nanoid()
+
+ if (typeof content === 'string') {
+ manager.add({
+ id,
+ options: {
+ ...options,
+ duration: options?.duration ?? DURATION,
+ },
+ component: (
+
+
+
+ {content}
+
+
+ ),
+ })
+ } else if (React.isValidElement(content)) {
+ manager.add({
+ id,
+ options: {
+ ...options,
+ duration: options?.duration ?? DURATION,
+ },
+ component: (
+
+ {content}
+
+ ),
+ })
+ } else {
+ throw new Error(
+ `Toast can be a string or a React element, got ${typeof content}`,
+ )
+ }
+}
diff --git a/src/components/Toast/index.tsx b/src/components/Toast/index.tsx
index d70a8ad164..54afeafb11 100644
--- a/src/components/Toast/index.tsx
+++ b/src/components/Toast/index.tsx
@@ -14,6 +14,7 @@ import {
import {type BaseToastOptions} from '#/components/Toast/types'
export {DURATION} from '#/components/Toast/const'
+export {dismiss} from '#/components/Toast/sonner'
export {Action, Icon, Text, ToastConfigProvider} from '#/components/Toast/Toast'
export {type ToastType} from '#/components/Toast/types'
diff --git a/src/components/Toast/index.web.tsx b/src/components/Toast/index.web.tsx
index 8b2028db98..95a4ff996a 100644
--- a/src/components/Toast/index.web.tsx
+++ b/src/components/Toast/index.web.tsx
@@ -13,6 +13,7 @@ import {
import {type BaseToastOptions} from '#/components/Toast/types'
export {DURATION} from '#/components/Toast/const'
+export {dismiss} from '#/components/Toast/sonner'
export * from '#/components/Toast/Toast'
export {type ToastType} from '#/components/Toast/types'