@tamagui/v2-toast 2.0.0-1769464493958

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (307) hide show
  1. package/dist/cjs/Toast.cjs +170 -0
  2. package/dist/cjs/Toast.js +119 -0
  3. package/dist/cjs/Toast.js.map +6 -0
  4. package/dist/cjs/Toast.native.js +174 -0
  5. package/dist/cjs/Toast.native.js.map +1 -0
  6. package/dist/cjs/ToastAnnounce.cjs +97 -0
  7. package/dist/cjs/ToastAnnounce.js +72 -0
  8. package/dist/cjs/ToastAnnounce.js.map +6 -0
  9. package/dist/cjs/ToastAnnounce.native.js +105 -0
  10. package/dist/cjs/ToastAnnounce.native.js.map +1 -0
  11. package/dist/cjs/ToastImperative.cjs +100 -0
  12. package/dist/cjs/ToastImperative.js +71 -0
  13. package/dist/cjs/ToastImperative.js.map +6 -0
  14. package/dist/cjs/ToastImperative.native.js +122 -0
  15. package/dist/cjs/ToastImperative.native.js.map +1 -0
  16. package/dist/cjs/ToastImpl.cjs +292 -0
  17. package/dist/cjs/ToastImpl.js +227 -0
  18. package/dist/cjs/ToastImpl.js.map +6 -0
  19. package/dist/cjs/ToastImpl.native.js +327 -0
  20. package/dist/cjs/ToastImpl.native.js.map +1 -0
  21. package/dist/cjs/ToastItem.cjs +466 -0
  22. package/dist/cjs/ToastItem.js +356 -0
  23. package/dist/cjs/ToastItem.js.map +6 -0
  24. package/dist/cjs/ToastItem.native.js +547 -0
  25. package/dist/cjs/ToastItem.native.js.map +1 -0
  26. package/dist/cjs/ToastPortal.cjs +44 -0
  27. package/dist/cjs/ToastPortal.js +26 -0
  28. package/dist/cjs/ToastPortal.js.map +6 -0
  29. package/dist/cjs/ToastPortal.native.js +47 -0
  30. package/dist/cjs/ToastPortal.native.js.map +1 -0
  31. package/dist/cjs/ToastProvider.cjs +146 -0
  32. package/dist/cjs/ToastProvider.js +105 -0
  33. package/dist/cjs/ToastProvider.js.map +6 -0
  34. package/dist/cjs/ToastProvider.native.js +159 -0
  35. package/dist/cjs/ToastProvider.native.js.map +1 -0
  36. package/dist/cjs/ToastState.cjs +248 -0
  37. package/dist/cjs/ToastState.js +160 -0
  38. package/dist/cjs/ToastState.js.map +6 -0
  39. package/dist/cjs/ToastState.native.js +257 -0
  40. package/dist/cjs/ToastState.native.js.map +1 -0
  41. package/dist/cjs/ToastViewport.cjs +278 -0
  42. package/dist/cjs/ToastViewport.js +263 -0
  43. package/dist/cjs/ToastViewport.js.map +6 -0
  44. package/dist/cjs/ToastViewport.native.js +316 -0
  45. package/dist/cjs/ToastViewport.native.js.map +1 -0
  46. package/dist/cjs/Toaster.cjs +219 -0
  47. package/dist/cjs/Toaster.js +177 -0
  48. package/dist/cjs/Toaster.js.map +6 -0
  49. package/dist/cjs/Toaster.native.js +279 -0
  50. package/dist/cjs/Toaster.native.js.map +1 -0
  51. package/dist/cjs/constants.cjs +28 -0
  52. package/dist/cjs/constants.js +22 -0
  53. package/dist/cjs/constants.js.map +6 -0
  54. package/dist/cjs/constants.native.js +31 -0
  55. package/dist/cjs/constants.native.js.map +1 -0
  56. package/dist/cjs/createNativeToast.cjs +51 -0
  57. package/dist/cjs/createNativeToast.js +44 -0
  58. package/dist/cjs/createNativeToast.js.map +6 -0
  59. package/dist/cjs/createNativeToast.native.js +47 -0
  60. package/dist/cjs/createNativeToast.native.js.map +1 -0
  61. package/dist/cjs/index.cjs +28 -0
  62. package/dist/cjs/index.js +22 -0
  63. package/dist/cjs/index.js.map +6 -0
  64. package/dist/cjs/index.native.js +31 -0
  65. package/dist/cjs/index.native.js.map +1 -0
  66. package/dist/cjs/types.cjs +16 -0
  67. package/dist/cjs/types.js +14 -0
  68. package/dist/cjs/types.js.map +6 -0
  69. package/dist/cjs/types.native.js +19 -0
  70. package/dist/cjs/types.native.js.map +1 -0
  71. package/dist/cjs/useDragGesture.cjs +129 -0
  72. package/dist/cjs/useDragGesture.js +100 -0
  73. package/dist/cjs/useDragGesture.js.map +6 -0
  74. package/dist/cjs/useDragGesture.native.js +146 -0
  75. package/dist/cjs/useDragGesture.native.js.map +1 -0
  76. package/dist/esm/Toast.js +107 -0
  77. package/dist/esm/Toast.js.map +6 -0
  78. package/dist/esm/Toast.mjs +131 -0
  79. package/dist/esm/Toast.mjs.map +1 -0
  80. package/dist/esm/Toast.native.js +132 -0
  81. package/dist/esm/Toast.native.js.map +1 -0
  82. package/dist/esm/ToastAnnounce.js +55 -0
  83. package/dist/esm/ToastAnnounce.js.map +6 -0
  84. package/dist/esm/ToastAnnounce.mjs +62 -0
  85. package/dist/esm/ToastAnnounce.mjs.map +1 -0
  86. package/dist/esm/ToastAnnounce.native.js +67 -0
  87. package/dist/esm/ToastAnnounce.native.js.map +1 -0
  88. package/dist/esm/ToastImperative.js +50 -0
  89. package/dist/esm/ToastImperative.js.map +6 -0
  90. package/dist/esm/ToastImperative.mjs +63 -0
  91. package/dist/esm/ToastImperative.mjs.map +1 -0
  92. package/dist/esm/ToastImperative.native.js +82 -0
  93. package/dist/esm/ToastImperative.native.js.map +1 -0
  94. package/dist/esm/ToastImpl.js +225 -0
  95. package/dist/esm/ToastImpl.js.map +6 -0
  96. package/dist/esm/ToastImpl.mjs +256 -0
  97. package/dist/esm/ToastImpl.mjs.map +1 -0
  98. package/dist/esm/ToastImpl.native.js +288 -0
  99. package/dist/esm/ToastImpl.native.js.map +1 -0
  100. package/dist/esm/ToastItem.js +339 -0
  101. package/dist/esm/ToastItem.js.map +6 -0
  102. package/dist/esm/ToastItem.mjs +432 -0
  103. package/dist/esm/ToastItem.mjs.map +1 -0
  104. package/dist/esm/ToastItem.native.js +510 -0
  105. package/dist/esm/ToastItem.native.js.map +1 -0
  106. package/dist/esm/ToastPortal.js +13 -0
  107. package/dist/esm/ToastPortal.js.map +6 -0
  108. package/dist/esm/ToastPortal.mjs +21 -0
  109. package/dist/esm/ToastPortal.mjs.map +1 -0
  110. package/dist/esm/ToastPortal.native.js +21 -0
  111. package/dist/esm/ToastPortal.native.js.map +1 -0
  112. package/dist/esm/ToastProvider.js +87 -0
  113. package/dist/esm/ToastProvider.js.map +6 -0
  114. package/dist/esm/ToastProvider.mjs +108 -0
  115. package/dist/esm/ToastProvider.mjs.map +1 -0
  116. package/dist/esm/ToastProvider.native.js +118 -0
  117. package/dist/esm/ToastProvider.native.js.map +1 -0
  118. package/dist/esm/ToastState.js +144 -0
  119. package/dist/esm/ToastState.js.map +6 -0
  120. package/dist/esm/ToastState.mjs +224 -0
  121. package/dist/esm/ToastState.mjs.map +1 -0
  122. package/dist/esm/ToastState.native.js +230 -0
  123. package/dist/esm/ToastState.native.js.map +1 -0
  124. package/dist/esm/ToastViewport.js +250 -0
  125. package/dist/esm/ToastViewport.js.map +6 -0
  126. package/dist/esm/ToastViewport.mjs +241 -0
  127. package/dist/esm/ToastViewport.mjs.map +1 -0
  128. package/dist/esm/ToastViewport.native.js +276 -0
  129. package/dist/esm/ToastViewport.native.js.map +1 -0
  130. package/dist/esm/Toaster.js +160 -0
  131. package/dist/esm/Toaster.js.map +6 -0
  132. package/dist/esm/Toaster.mjs +185 -0
  133. package/dist/esm/Toaster.mjs.map +1 -0
  134. package/dist/esm/Toaster.native.js +242 -0
  135. package/dist/esm/Toaster.native.js.map +1 -0
  136. package/dist/esm/constants.js +6 -0
  137. package/dist/esm/constants.js.map +6 -0
  138. package/dist/esm/constants.mjs +4 -0
  139. package/dist/esm/constants.mjs.map +1 -0
  140. package/dist/esm/constants.native.js +4 -0
  141. package/dist/esm/constants.native.js.map +1 -0
  142. package/dist/esm/createNativeToast.js +28 -0
  143. package/dist/esm/createNativeToast.js.map +6 -0
  144. package/dist/esm/createNativeToast.mjs +27 -0
  145. package/dist/esm/createNativeToast.mjs.map +1 -0
  146. package/dist/esm/createNativeToast.native.js +20 -0
  147. package/dist/esm/createNativeToast.native.js.map +1 -0
  148. package/dist/esm/index.js +7 -0
  149. package/dist/esm/index.js.map +6 -0
  150. package/dist/esm/index.mjs +4 -0
  151. package/dist/esm/index.mjs.map +1 -0
  152. package/dist/esm/index.native.js +4 -0
  153. package/dist/esm/index.native.js.map +1 -0
  154. package/dist/esm/types.js +1 -0
  155. package/dist/esm/types.js.map +6 -0
  156. package/dist/esm/types.mjs +2 -0
  157. package/dist/esm/types.mjs.map +1 -0
  158. package/dist/esm/types.native.js +2 -0
  159. package/dist/esm/types.native.js.map +1 -0
  160. package/dist/esm/useDragGesture.js +76 -0
  161. package/dist/esm/useDragGesture.js.map +6 -0
  162. package/dist/esm/useDragGesture.mjs +95 -0
  163. package/dist/esm/useDragGesture.mjs.map +1 -0
  164. package/dist/esm/useDragGesture.native.js +109 -0
  165. package/dist/esm/useDragGesture.native.js.map +1 -0
  166. package/dist/jsx/Toast.js +107 -0
  167. package/dist/jsx/Toast.js.map +6 -0
  168. package/dist/jsx/Toast.mjs +131 -0
  169. package/dist/jsx/Toast.mjs.map +1 -0
  170. package/dist/jsx/Toast.native.js +174 -0
  171. package/dist/jsx/Toast.native.js.map +1 -0
  172. package/dist/jsx/ToastAnnounce.js +55 -0
  173. package/dist/jsx/ToastAnnounce.js.map +6 -0
  174. package/dist/jsx/ToastAnnounce.mjs +62 -0
  175. package/dist/jsx/ToastAnnounce.mjs.map +1 -0
  176. package/dist/jsx/ToastAnnounce.native.js +105 -0
  177. package/dist/jsx/ToastAnnounce.native.js.map +1 -0
  178. package/dist/jsx/ToastImperative.js +50 -0
  179. package/dist/jsx/ToastImperative.js.map +6 -0
  180. package/dist/jsx/ToastImperative.mjs +63 -0
  181. package/dist/jsx/ToastImperative.mjs.map +1 -0
  182. package/dist/jsx/ToastImperative.native.js +122 -0
  183. package/dist/jsx/ToastImperative.native.js.map +1 -0
  184. package/dist/jsx/ToastImpl.js +225 -0
  185. package/dist/jsx/ToastImpl.js.map +6 -0
  186. package/dist/jsx/ToastImpl.mjs +256 -0
  187. package/dist/jsx/ToastImpl.mjs.map +1 -0
  188. package/dist/jsx/ToastImpl.native.js +327 -0
  189. package/dist/jsx/ToastImpl.native.js.map +1 -0
  190. package/dist/jsx/ToastItem.js +339 -0
  191. package/dist/jsx/ToastItem.js.map +6 -0
  192. package/dist/jsx/ToastItem.mjs +432 -0
  193. package/dist/jsx/ToastItem.mjs.map +1 -0
  194. package/dist/jsx/ToastItem.native.js +547 -0
  195. package/dist/jsx/ToastItem.native.js.map +1 -0
  196. package/dist/jsx/ToastPortal.js +13 -0
  197. package/dist/jsx/ToastPortal.js.map +6 -0
  198. package/dist/jsx/ToastPortal.mjs +21 -0
  199. package/dist/jsx/ToastPortal.mjs.map +1 -0
  200. package/dist/jsx/ToastPortal.native.js +47 -0
  201. package/dist/jsx/ToastPortal.native.js.map +1 -0
  202. package/dist/jsx/ToastProvider.js +87 -0
  203. package/dist/jsx/ToastProvider.js.map +6 -0
  204. package/dist/jsx/ToastProvider.mjs +108 -0
  205. package/dist/jsx/ToastProvider.mjs.map +1 -0
  206. package/dist/jsx/ToastProvider.native.js +159 -0
  207. package/dist/jsx/ToastProvider.native.js.map +1 -0
  208. package/dist/jsx/ToastState.js +144 -0
  209. package/dist/jsx/ToastState.js.map +6 -0
  210. package/dist/jsx/ToastState.mjs +224 -0
  211. package/dist/jsx/ToastState.mjs.map +1 -0
  212. package/dist/jsx/ToastState.native.js +257 -0
  213. package/dist/jsx/ToastState.native.js.map +1 -0
  214. package/dist/jsx/ToastViewport.js +250 -0
  215. package/dist/jsx/ToastViewport.js.map +6 -0
  216. package/dist/jsx/ToastViewport.mjs +241 -0
  217. package/dist/jsx/ToastViewport.mjs.map +1 -0
  218. package/dist/jsx/ToastViewport.native.js +316 -0
  219. package/dist/jsx/ToastViewport.native.js.map +1 -0
  220. package/dist/jsx/Toaster.js +160 -0
  221. package/dist/jsx/Toaster.js.map +6 -0
  222. package/dist/jsx/Toaster.mjs +185 -0
  223. package/dist/jsx/Toaster.mjs.map +1 -0
  224. package/dist/jsx/Toaster.native.js +279 -0
  225. package/dist/jsx/Toaster.native.js.map +1 -0
  226. package/dist/jsx/constants.js +6 -0
  227. package/dist/jsx/constants.js.map +6 -0
  228. package/dist/jsx/constants.mjs +4 -0
  229. package/dist/jsx/constants.mjs.map +1 -0
  230. package/dist/jsx/constants.native.js +31 -0
  231. package/dist/jsx/constants.native.js.map +1 -0
  232. package/dist/jsx/createNativeToast.js +28 -0
  233. package/dist/jsx/createNativeToast.js.map +6 -0
  234. package/dist/jsx/createNativeToast.mjs +27 -0
  235. package/dist/jsx/createNativeToast.mjs.map +1 -0
  236. package/dist/jsx/createNativeToast.native.js +47 -0
  237. package/dist/jsx/createNativeToast.native.js.map +1 -0
  238. package/dist/jsx/index.js +7 -0
  239. package/dist/jsx/index.js.map +6 -0
  240. package/dist/jsx/index.mjs +4 -0
  241. package/dist/jsx/index.mjs.map +1 -0
  242. package/dist/jsx/index.native.js +31 -0
  243. package/dist/jsx/index.native.js.map +1 -0
  244. package/dist/jsx/types.js +1 -0
  245. package/dist/jsx/types.js.map +6 -0
  246. package/dist/jsx/types.mjs +2 -0
  247. package/dist/jsx/types.mjs.map +1 -0
  248. package/dist/jsx/types.native.js +19 -0
  249. package/dist/jsx/types.native.js.map +1 -0
  250. package/dist/jsx/useDragGesture.js +76 -0
  251. package/dist/jsx/useDragGesture.js.map +6 -0
  252. package/dist/jsx/useDragGesture.mjs +95 -0
  253. package/dist/jsx/useDragGesture.mjs.map +1 -0
  254. package/dist/jsx/useDragGesture.native.js +146 -0
  255. package/dist/jsx/useDragGesture.native.js.map +1 -0
  256. package/package.json +77 -0
  257. package/src/Toast.tsx +219 -0
  258. package/src/ToastAnnounce.tsx +102 -0
  259. package/src/ToastImperative.tsx +190 -0
  260. package/src/ToastImpl.tsx +503 -0
  261. package/src/ToastItem.tsx +694 -0
  262. package/src/ToastPortal.tsx +19 -0
  263. package/src/ToastProvider.tsx +197 -0
  264. package/src/ToastState.ts +397 -0
  265. package/src/ToastViewport.tsx +430 -0
  266. package/src/Toaster.tsx +445 -0
  267. package/src/constants.ts +2 -0
  268. package/src/createNativeToast.native.tsx +22 -0
  269. package/src/createNativeToast.tsx +48 -0
  270. package/src/index.ts +17 -0
  271. package/src/types.ts +71 -0
  272. package/src/useDragGesture.native.ts +199 -0
  273. package/src/useDragGesture.ts +218 -0
  274. package/types/Toast.d.ts +84 -0
  275. package/types/Toast.d.ts.map +1 -0
  276. package/types/ToastAnnounce.d.ts +18 -0
  277. package/types/ToastAnnounce.d.ts.map +1 -0
  278. package/types/ToastImperative.d.ts +95 -0
  279. package/types/ToastImperative.d.ts.map +1 -0
  280. package/types/ToastImpl.d.ts +109 -0
  281. package/types/ToastImpl.d.ts.map +1 -0
  282. package/types/ToastItem.d.ts +34 -0
  283. package/types/ToastItem.d.ts.map +1 -0
  284. package/types/ToastPortal.d.ts +8 -0
  285. package/types/ToastPortal.d.ts.map +1 -0
  286. package/types/ToastProvider.d.ts +92 -0
  287. package/types/ToastProvider.d.ts.map +1 -0
  288. package/types/ToastState.d.ts +177 -0
  289. package/types/ToastState.d.ts.map +1 -0
  290. package/types/ToastViewport.d.ts +75 -0
  291. package/types/ToastViewport.d.ts.map +1 -0
  292. package/types/Toaster.d.ts +120 -0
  293. package/types/Toaster.d.ts.map +1 -0
  294. package/types/constants.d.ts +3 -0
  295. package/types/constants.d.ts.map +1 -0
  296. package/types/createNativeToast.d.ts +4 -0
  297. package/types/createNativeToast.d.ts.map +1 -0
  298. package/types/createNativeToast.native.d.ts +4 -0
  299. package/types/createNativeToast.native.d.ts.map +1 -0
  300. package/types/index.d.ts +7 -0
  301. package/types/index.d.ts.map +1 -0
  302. package/types/types.d.ts +61 -0
  303. package/types/types.d.ts.map +1 -0
  304. package/types/useDragGesture.d.ts +32 -0
  305. package/types/useDragGesture.d.ts.map +1 -0
  306. package/types/useDragGesture.native.d.ts +26 -0
  307. package/types/useDragGesture.native.d.ts.map +1 -0
@@ -0,0 +1,694 @@
1
+ import { isWeb } from '@tamagui/constants'
2
+ import type { TamaguiElement } from '@tamagui/core'
3
+ import { styled, useEvent, View } from '@tamagui/core'
4
+ import { XStack, YStack } from '@tamagui/stacks'
5
+ import { SizableText } from '@tamagui/text'
6
+ import * as React from 'react'
7
+ import type { LayoutChangeEvent } from 'react-native'
8
+
9
+ import type { HeightT, ToasterPosition } from './Toaster'
10
+ import type { ToastT, ToastType } from './ToastState'
11
+ import { useDragGesture } from './useDragGesture'
12
+ import type { SwipeDirection } from './ToastProvider'
13
+ import type { BurntToastOptions } from './types'
14
+ import { createNativeToast } from './createNativeToast'
15
+
16
+ // time before unmount after deletion
17
+ const TIME_BEFORE_UNMOUNT = 200
18
+
19
+ /* -------------------------------------------------------------------------------------------------
20
+ * ToastItemFrame
21
+ * -----------------------------------------------------------------------------------------------*/
22
+
23
+ const ToastItemFrame = styled(YStack, {
24
+ name: 'ToastItem',
25
+ focusable: true,
26
+ pointerEvents: 'auto',
27
+ position: 'absolute',
28
+ left: 0,
29
+ right: 0,
30
+ // for stacking animation - default visible state
31
+ opacity: 1,
32
+ scale: 1,
33
+ y: 0,
34
+ x: 0,
35
+
36
+ variants: {
37
+ unstyled: {
38
+ false: {
39
+ backgroundColor: '$background',
40
+ borderRadius: '$4',
41
+ paddingHorizontal: '$4',
42
+ paddingVertical: '$3',
43
+ // shadow using elevation for cross-platform
44
+ elevation: '$4',
45
+ shadowColor: '$shadowColor',
46
+ shadowOffset: { width: 0, height: 4 },
47
+ shadowOpacity: 0.15,
48
+ shadowRadius: 12,
49
+ borderWidth: 1,
50
+ borderColor: '$borderColor',
51
+
52
+ // only show focus outline on keyboard navigation, not on click/tap
53
+ focusVisibleStyle: {
54
+ outlineStyle: 'solid',
55
+ outlineWidth: 2,
56
+ outlineColor: '$outlineColor',
57
+ },
58
+ },
59
+ },
60
+ } as const,
61
+
62
+ defaultVariants: {
63
+ unstyled: process.env.TAMAGUI_HEADLESS === '1',
64
+ },
65
+ })
66
+
67
+ /* -------------------------------------------------------------------------------------------------
68
+ * ToastTitle
69
+ * -----------------------------------------------------------------------------------------------*/
70
+
71
+ const ToastItemTitle = styled(SizableText, {
72
+ name: 'ToastItemTitle',
73
+
74
+ variants: {
75
+ unstyled: {
76
+ false: {
77
+ fontWeight: '600',
78
+ color: '$color',
79
+ size: '$4',
80
+ },
81
+ },
82
+ } as const,
83
+
84
+ defaultVariants: {
85
+ unstyled: process.env.TAMAGUI_HEADLESS === '1',
86
+ },
87
+ })
88
+
89
+ /* -------------------------------------------------------------------------------------------------
90
+ * ToastDescription
91
+ * -----------------------------------------------------------------------------------------------*/
92
+
93
+ const ToastItemDescription = styled(SizableText, {
94
+ name: 'ToastItemDescription',
95
+
96
+ variants: {
97
+ unstyled: {
98
+ false: {
99
+ color: '$color11',
100
+ size: '$2',
101
+ marginTop: '$1',
102
+ },
103
+ },
104
+ } as const,
105
+
106
+ defaultVariants: {
107
+ unstyled: process.env.TAMAGUI_HEADLESS === '1',
108
+ },
109
+ })
110
+
111
+ /* -------------------------------------------------------------------------------------------------
112
+ * ToastCloseButton
113
+ * -----------------------------------------------------------------------------------------------*/
114
+
115
+ const ToastCloseButton = styled(XStack, {
116
+ name: 'ToastCloseButton',
117
+ render: 'button',
118
+ alignItems: 'center',
119
+ justifyContent: 'center',
120
+ cursor: 'pointer',
121
+
122
+ variants: {
123
+ unstyled: {
124
+ false: {
125
+ width: 20,
126
+ height: 20,
127
+ borderRadius: '$10',
128
+ backgroundColor: '$color5',
129
+ hoverStyle: {
130
+ backgroundColor: '$color6',
131
+ },
132
+ pressStyle: {
133
+ backgroundColor: '$color7',
134
+ },
135
+ },
136
+ },
137
+ } as const,
138
+
139
+ defaultVariants: {
140
+ unstyled: process.env.TAMAGUI_HEADLESS === '1',
141
+ },
142
+ })
143
+
144
+ /* -------------------------------------------------------------------------------------------------
145
+ * ToastActionButton - for action/cancel buttons with text
146
+ * -----------------------------------------------------------------------------------------------*/
147
+
148
+ const ToastActionButton = styled(XStack, {
149
+ name: 'ToastActionButton',
150
+ render: 'button',
151
+ alignItems: 'center',
152
+ justifyContent: 'center',
153
+ cursor: 'pointer',
154
+
155
+ variants: {
156
+ unstyled: {
157
+ false: {
158
+ borderRadius: '$2',
159
+ paddingHorizontal: '$2',
160
+ height: 24,
161
+ backgroundColor: '$color5',
162
+ hoverStyle: {
163
+ backgroundColor: '$color6',
164
+ },
165
+ pressStyle: {
166
+ backgroundColor: '$color7',
167
+ },
168
+ },
169
+ },
170
+
171
+ // primary action button style
172
+ primary: {
173
+ true: {
174
+ backgroundColor: '$color12',
175
+ hoverStyle: {
176
+ backgroundColor: '$color11',
177
+ },
178
+ pressStyle: {
179
+ backgroundColor: '$color10',
180
+ },
181
+ },
182
+ },
183
+ } as const,
184
+
185
+ defaultVariants: {
186
+ unstyled: process.env.TAMAGUI_HEADLESS === '1',
187
+ },
188
+ })
189
+
190
+ /* -------------------------------------------------------------------------------------------------
191
+ * Icons
192
+ * -----------------------------------------------------------------------------------------------*/
193
+
194
+ const DefaultCloseIcon = () => (
195
+ <SizableText size="$1" color="$color11">
196
+
197
+ </SizableText>
198
+ )
199
+
200
+ const DefaultSuccessIcon = () => (
201
+ <SizableText size="$5" color="$green10">
202
+
203
+ </SizableText>
204
+ )
205
+
206
+ const DefaultErrorIcon = () => (
207
+ <SizableText size="$5" color="$red10">
208
+
209
+ </SizableText>
210
+ )
211
+
212
+ const DefaultWarningIcon = () => (
213
+ <SizableText size="$5" color="$yellow10">
214
+
215
+ </SizableText>
216
+ )
217
+
218
+ const DefaultInfoIcon = () => (
219
+ <SizableText size="$5" color="$blue10">
220
+
221
+ </SizableText>
222
+ )
223
+
224
+ const DefaultLoadingIcon = () => (
225
+ <SizableText size="$5" color="$color11">
226
+
227
+ </SizableText>
228
+ )
229
+
230
+ /* -------------------------------------------------------------------------------------------------
231
+ * ToastItem
232
+ * -----------------------------------------------------------------------------------------------*/
233
+
234
+ export interface ToastItemProps {
235
+ toast: ToastT
236
+ index: number
237
+ expanded: boolean
238
+ interacting: boolean
239
+ position: ToasterPosition
240
+ visibleToasts: number
241
+ removeToast: (toast: ToastT) => void
242
+ heights: HeightT[]
243
+ setHeights: React.Dispatch<React.SetStateAction<HeightT[]>>
244
+ duration: number
245
+ gap: number
246
+ swipeDirection: SwipeDirection
247
+ swipeThreshold: number
248
+ closeButton?: boolean
249
+ icons?: {
250
+ success?: React.ReactNode
251
+ error?: React.ReactNode
252
+ warning?: React.ReactNode
253
+ info?: React.ReactNode
254
+ loading?: React.ReactNode
255
+ close?: React.ReactNode
256
+ }
257
+ disableNative?: boolean
258
+ burntOptions?: Omit<BurntToastOptions, 'title' | 'message' | 'duration'>
259
+ notificationOptions?: NotificationOptions
260
+ }
261
+
262
+ export const ToastItem = React.memo(function ToastItem(props: ToastItemProps) {
263
+ const {
264
+ toast,
265
+ index,
266
+ expanded,
267
+ interacting,
268
+ position,
269
+ visibleToasts,
270
+ removeToast,
271
+ heights,
272
+ setHeights,
273
+ duration,
274
+ gap,
275
+ swipeDirection,
276
+ swipeThreshold,
277
+ closeButton,
278
+ icons,
279
+ disableNative,
280
+ burntOptions,
281
+ notificationOptions,
282
+ } = props
283
+
284
+ const [mounted, setMounted] = React.useState(false)
285
+ const [removed, setRemoved] = React.useState(false)
286
+ const [swipeOut, setSwipeOut] = React.useState(false)
287
+
288
+ const toastRef = React.useRef<TamaguiElement>(null)
289
+ const closeTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)
290
+ const closeTimerStartRef = React.useRef(0)
291
+ const lastPauseTimeRef = React.useRef(0) // tracks when pause was called to prevent double-counting
292
+ const remainingTimeRef = React.useRef(duration)
293
+
294
+ const isFront = index === 0
295
+ const isVisible = index < visibleToasts
296
+ const toastType = toast.type ?? 'default'
297
+ const dismissible = toast.dismissible !== false
298
+
299
+ // calculate offset based on heights of previous toasts
300
+ const heightIndex = React.useMemo(
301
+ () => heights.findIndex((h) => h.toastId === toast.id) || 0,
302
+ [heights, toast.id]
303
+ )
304
+
305
+ const toastsHeightBefore = React.useMemo(() => {
306
+ return heights.reduce((prev, curr, reducerIndex) => {
307
+ if (reducerIndex >= heightIndex) return prev
308
+ return prev + curr.height
309
+ }, 0)
310
+ }, [heights, heightIndex])
311
+
312
+ const offset = heightIndex * gap + toastsHeightBefore
313
+
314
+ // parse position for animation direction
315
+ const [yPosition] = position.split('-') as ['top' | 'bottom', string]
316
+ const isTop = yPosition === 'top'
317
+
318
+ // handle native toast on mobile
319
+ React.useEffect(() => {
320
+ if (!disableNative && !isWeb) {
321
+ const titleText = typeof toast.title === 'function' ? toast.title() : toast.title
322
+ const descText =
323
+ typeof toast.description === 'function' ? toast.description() : toast.description
324
+
325
+ if (typeof titleText === 'string') {
326
+ createNativeToast(titleText, {
327
+ message: typeof descText === 'string' ? descText : undefined,
328
+ duration,
329
+ burntOptions,
330
+ notificationOptions,
331
+ })
332
+ }
333
+ }
334
+ }, [])
335
+
336
+ // trigger mount animation
337
+ React.useEffect(() => {
338
+ setMounted(true)
339
+ }, [])
340
+
341
+ // handle deletion
342
+ React.useEffect(() => {
343
+ if (toast.delete) {
344
+ setRemoved(true)
345
+ setTimeout(() => {
346
+ removeToast(toast)
347
+ }, TIME_BEFORE_UNMOUNT)
348
+ }
349
+ }, [toast.delete, toast, removeToast])
350
+
351
+ // auto-close timer
352
+ const startTimer = React.useCallback(() => {
353
+ if (duration === Number.POSITIVE_INFINITY || toastType === 'loading') return
354
+
355
+ closeTimerStartRef.current = Date.now()
356
+ closeTimerRef.current = setTimeout(() => {
357
+ toast.onAutoClose?.(toast)
358
+ setRemoved(true)
359
+ setTimeout(() => {
360
+ removeToast(toast)
361
+ }, TIME_BEFORE_UNMOUNT)
362
+ }, remainingTimeRef.current)
363
+ }, [duration, toastType, toast, removeToast])
364
+
365
+ const pauseTimer = useEvent(() => {
366
+ if (closeTimerRef.current) {
367
+ clearTimeout(closeTimerRef.current)
368
+ }
369
+ // only calculate elapsed time if timer was started after last pause
370
+ // this prevents double-counting when pause is called multiple times (Sonner pattern)
371
+ if (lastPauseTimeRef.current < closeTimerStartRef.current) {
372
+ const elapsed = Date.now() - closeTimerStartRef.current
373
+ remainingTimeRef.current = Math.max(0, remainingTimeRef.current - elapsed)
374
+ }
375
+ lastPauseTimeRef.current = Date.now()
376
+ })
377
+
378
+ const resumeTimer = useEvent(() => {
379
+ startTimer()
380
+ })
381
+
382
+ // start/pause timer based on expanded/interacting state
383
+ React.useEffect(() => {
384
+ if (expanded || interacting) {
385
+ pauseTimer()
386
+ } else {
387
+ startTimer()
388
+ }
389
+
390
+ return () => {
391
+ if (closeTimerRef.current) {
392
+ clearTimeout(closeTimerRef.current)
393
+ }
394
+ }
395
+ }, [expanded, interacting, startTimer])
396
+
397
+ // reset remaining time when duration changes
398
+ React.useEffect(() => {
399
+ remainingTimeRef.current = duration
400
+ }, [duration])
401
+
402
+ // drag gesture for swipe-to-dismiss
403
+ const { dragState, gestureHandlers } = useDragGesture({
404
+ direction: swipeDirection,
405
+ threshold: swipeThreshold,
406
+ disabled: !dismissible || toastType === 'loading',
407
+ onDragStart: pauseTimer,
408
+ onDragEnd: (dismissed) => {
409
+ if (dismissed) {
410
+ setSwipeOut(true)
411
+ toast.onDismiss?.(toast)
412
+ setRemoved(true)
413
+ setTimeout(() => {
414
+ removeToast(toast)
415
+ }, TIME_BEFORE_UNMOUNT)
416
+ }
417
+ },
418
+ onDragCancel: resumeTimer,
419
+ })
420
+
421
+ // measure height
422
+ const handleLayout = React.useCallback(
423
+ (event: LayoutChangeEvent) => {
424
+ const { height } = event.nativeEvent.layout
425
+
426
+ setHeights((prev) => {
427
+ const exists = prev.find((h) => h.toastId === toast.id)
428
+ if (exists) {
429
+ return prev.map((h) => (h.toastId === toast.id ? { ...h, height } : h))
430
+ }
431
+ return [{ toastId: toast.id, height, position }, ...prev]
432
+ })
433
+ },
434
+ [toast.id, position, setHeights]
435
+ )
436
+
437
+ // cleanup height on unmount
438
+ React.useEffect(() => {
439
+ return () => {
440
+ setHeights((prev) => prev.filter((h) => h.toastId !== toast.id))
441
+ }
442
+ }, [toast.id, setHeights])
443
+
444
+ const handleClose = React.useCallback(() => {
445
+ if (!dismissible) return
446
+ toast.onDismiss?.(toast)
447
+ setRemoved(true)
448
+ setTimeout(() => {
449
+ removeToast(toast)
450
+ }, TIME_BEFORE_UNMOUNT)
451
+ }, [dismissible, toast, removeToast])
452
+
453
+ // get icon - just use what's passed on the toast, or type-based defaults
454
+ const getIcon = () => {
455
+ if (toast.icon !== undefined) return toast.icon
456
+
457
+ const typeIcons: Record<ToastType, React.ReactNode> = {
458
+ default: null,
459
+ success: icons?.success ?? <DefaultSuccessIcon />,
460
+ error: icons?.error ?? <DefaultErrorIcon />,
461
+ warning: icons?.warning ?? <DefaultWarningIcon />,
462
+ info: icons?.info ?? <DefaultInfoIcon />,
463
+ loading: icons?.loading ?? <DefaultLoadingIcon />,
464
+ }
465
+
466
+ return typeIcons[toastType]
467
+ }
468
+
469
+ const icon = getIcon()
470
+
471
+ // calculate values for stacking effect using Tamagui animation props
472
+ const isHorizontalSwipe =
473
+ swipeDirection === 'left' ||
474
+ swipeDirection === 'right' ||
475
+ swipeDirection === 'horizontal'
476
+ const isVerticalSwipe =
477
+ swipeDirection === 'up' || swipeDirection === 'down' || swipeDirection === 'vertical'
478
+
479
+ // drag offset based on swipe direction
480
+ const dragOffsetX = isHorizontalSwipe ? dragState.offsetX : 0
481
+ const dragOffsetY = isVerticalSwipe ? dragState.offsetY : 0
482
+
483
+ // scale non-front toasts when not expanded (sonner-style stacking)
484
+ // each toast behind front is scaled down by 5% - this creates visual depth
485
+ const stackScale = !expanded && !isFront ? 1 - index * 0.05 : 1
486
+
487
+ // get the height of the front toast for collapsed positioning
488
+ const frontToastHeight = heights.length > 0 ? (heights[0]?.height ?? 55) : 55
489
+
490
+ // y position: expanded shows full offset, collapsed stacks visually
491
+ // sonner uses gap (14px) as the lift amount per toast in collapsed mode
492
+ const baseOffset = isTop ? offset : -offset
493
+
494
+ // in collapsed mode, create visual stack where each toast peeks behind the one in front
495
+ // for bottom position with transformOrigin: bottom, back toasts need to move UP
496
+ // enough that their TOP edge peeks ABOVE the front toast's TOP edge
497
+ // since scale shrinks toward bottom, we need: lift > frontHeight * (1 - scale)
498
+ // but we only want a small peek (8-12px visible), so lift = frontHeight - peekAmount
499
+ // simplified: use a fixed peek amount that creates nice visual stacking
500
+ // for bottom position: back toasts need to peek ABOVE front toast
501
+ // with bottom:0 anchor and transformOrigin:bottom, we need lift > toast height to peek
502
+ // use front toast height minus desired overlap as lift
503
+ const peekVisible = 10 // how many pixels of back toast border should peek
504
+ const liftPerToast = peekVisible // lift this much for each toast in stack
505
+ const stackY = expanded
506
+ ? baseOffset
507
+ : isFront
508
+ ? 0
509
+ : isTop
510
+ ? liftPerToast * index // for top position, toasts stack downward
511
+ : -liftPerToast * index // for bottom position, toasts stack upward
512
+
513
+ // final computed values including drag
514
+ const computedY = stackY + dragOffsetY
515
+ const computedX = dragOffsetX
516
+ const computedScale = stackScale
517
+
518
+ // opacity: toasts beyond visibleToasts are always hidden (like Sonner)
519
+ // this applies in BOTH collapsed and expanded states
520
+ // in collapsed mode, the last visible toast also fades slightly
521
+ let computedOpacity = 1
522
+ if (index >= visibleToasts) {
523
+ computedOpacity = 0 // completely hidden beyond limit (both states)
524
+ } else if (!expanded && index === visibleToasts - 1) {
525
+ computedOpacity = 0.5 // last visible toast fades in collapsed mode only
526
+ }
527
+
528
+ // z-index: front toast should be on top, back toasts below
529
+ // higher z-index = more in front
530
+ const computedZIndex = visibleToasts - index
531
+ // in collapsed mode, set back toasts to front toast height (sonner pattern)
532
+ // this makes the stack work by having all toasts same height
533
+ const computedHeight = !expanded && !isFront ? frontToastHeight : undefined
534
+ // hidden toasts should not intercept pointer events (like Sonner)
535
+ const computedPointerEvents = index >= visibleToasts ? 'none' : 'auto'
536
+
537
+ // render custom JSX if provided
538
+ if (toast.jsx) {
539
+ return toast.jsx
540
+ }
541
+
542
+ const title = typeof toast.title === 'function' ? toast.title() : toast.title
543
+ const description =
544
+ typeof toast.description === 'function' ? toast.description() : toast.description
545
+
546
+ // data attributes for testing/styling - use dataSet for RN Web compatibility
547
+ const dataSet = {
548
+ mounted: mounted ? 'true' : 'false',
549
+ removed: removed ? 'true' : 'false',
550
+ swipeOut: swipeOut ? 'true' : 'false',
551
+ visible: isVisible ? 'true' : 'false',
552
+ front: isFront ? 'true' : 'false',
553
+ index: String(index),
554
+ type: toastType,
555
+ expanded: expanded ? 'true' : 'false',
556
+ }
557
+
558
+ // gap filler height - extends hit area to prevent flicker when moving between toasts
559
+ // only needed when expanded (toasts have visual gaps between them)
560
+ const gapFillerHeight = expanded ? gap + 1 : 0
561
+
562
+ return (
563
+ <ToastItemFrame
564
+ ref={toastRef}
565
+ // biome-ignore lint/a11y/useSemanticElements: we can't use <output> element as this is a styled Tamagui component
566
+ role="status"
567
+ aria-live="polite"
568
+ aria-atomic
569
+ tabIndex={0}
570
+ // @ts-expect-error dataSet is a valid prop for RN Web compatibility
571
+ dataSet={dataSet}
572
+ data-expanded={expanded ? 'true' : 'false'}
573
+ onLayout={handleLayout}
574
+ // use Tamagui animation system - disable animation while dragging
575
+ transition={dragState.isDragging ? undefined : 'quick'}
576
+ // animation props
577
+ y={computedY}
578
+ x={computedX}
579
+ scale={computedScale}
580
+ opacity={computedOpacity}
581
+ zIndex={computedZIndex}
582
+ height={computedHeight}
583
+ overflow={computedHeight ? 'hidden' : undefined}
584
+ pointerEvents={computedPointerEvents as any}
585
+ // anchor position: top positions anchor at top, bottom positions anchor at bottom
586
+ top={isTop ? 0 : undefined}
587
+ bottom={isTop ? undefined : 0}
588
+ // transform-origin: scale from bottom for bottom position, top for top position
589
+ // this ensures the stack peek is visible in the correct direction
590
+ {...(isWeb &&
591
+ !isFront &&
592
+ !expanded && {
593
+ style: { transformOrigin: isTop ? 'top center' : 'bottom center' },
594
+ })}
595
+ // enter/exit styles for AnimatePresence
596
+ // subtle animations - small y shift + opacity fade
597
+ enterStyle={{
598
+ opacity: 0,
599
+ y: isTop ? -10 : 10,
600
+ scale: 0.95,
601
+ }}
602
+ exitStyle={{
603
+ opacity: 0,
604
+ // for swipe dismissal, continue in swipe direction with subtle movement
605
+ x: isHorizontalSwipe && swipeOut ? (swipeDirection === 'left' ? -30 : 30) : 0,
606
+ y:
607
+ isVerticalSwipe && swipeOut
608
+ ? swipeDirection === 'up'
609
+ ? -30
610
+ : 30
611
+ : isTop
612
+ ? -10
613
+ : 10,
614
+ scale: 0.95,
615
+ }}
616
+ {...gestureHandlers}
617
+ {...(isWeb && {
618
+ onKeyDown: (event: React.KeyboardEvent) => {
619
+ if (event.key === 'Escape' && dismissible) {
620
+ handleClose()
621
+ }
622
+ },
623
+ })}
624
+ >
625
+ {/* invisible hit area that fills the gap above/below toast when expanded
626
+ this prevents hover state flickering when mouse moves between toasts */}
627
+ {expanded && gapFillerHeight > 0 && (
628
+ <View
629
+ position="absolute"
630
+ left={0}
631
+ right={0}
632
+ height={gapFillerHeight}
633
+ pointerEvents="auto"
634
+ {
635
+ ...(isTop
636
+ ? { top: '100%' } // for top position, extend downward
637
+ : { bottom: '100%' }) // for bottom position, extend upward
638
+ }
639
+ />
640
+ )}
641
+ <XStack alignItems="flex-start" gap="$3">
642
+ {icon && (
643
+ <View flexShrink={0} marginTop="$0.5">
644
+ {icon}
645
+ </View>
646
+ )}
647
+
648
+ <YStack flex={1} gap="$1">
649
+ {title && <ToastItemTitle>{title}</ToastItemTitle>}
650
+ {description && <ToastItemDescription>{description}</ToastItemDescription>}
651
+ </YStack>
652
+
653
+ {closeButton && dismissible && (
654
+ <ToastCloseButton onPress={handleClose} aria-label="Close toast">
655
+ {icons?.close ?? <DefaultCloseIcon />}
656
+ </ToastCloseButton>
657
+ )}
658
+ </XStack>
659
+
660
+ {/* Action buttons */}
661
+ {(toast.action || toast.cancel) && (
662
+ <XStack marginTop="$3" gap="$2" justifyContent="flex-end">
663
+ {toast.cancel && (
664
+ <ToastActionButton
665
+ onPress={(event) => {
666
+ toast.cancel?.onClick?.(event as any)
667
+ handleClose()
668
+ }}
669
+ >
670
+ <SizableText size="$2">{toast.cancel.label}</SizableText>
671
+ </ToastActionButton>
672
+ )}
673
+ {toast.action && (
674
+ <ToastActionButton
675
+ primary
676
+ onPress={(event) => {
677
+ toast.action?.onClick?.(event as any)
678
+ if (!(event as any).defaultPrevented) {
679
+ handleClose()
680
+ }
681
+ }}
682
+ >
683
+ <SizableText size="$2" fontWeight="600" color="$background">
684
+ {toast.action.label}
685
+ </SizableText>
686
+ </ToastActionButton>
687
+ )}
688
+ </XStack>
689
+ )}
690
+ </ToastItemFrame>
691
+ )
692
+ })
693
+
694
+ ToastItem.displayName = 'ToastItem'
@@ -0,0 +1,19 @@
1
+ import { Portal } from '@tamagui/portal'
2
+ import type { ReactNode } from 'react'
3
+ import { Platform } from 'react-native'
4
+ import { ReprogapateToastProvider, type ToastProviderContextValue } from './ToastProvider'
5
+
6
+ export function ToastPortal(props: {
7
+ children: ReactNode
8
+ zIndex?: number
9
+ context: ToastProviderContextValue
10
+ }) {
11
+ const { context, children, zIndex } = props
12
+ let content = children
13
+ if (Platform.OS === 'android' || Platform.OS === 'ios') {
14
+ content = (
15
+ <ReprogapateToastProvider context={context}>{children}</ReprogapateToastProvider>
16
+ )
17
+ }
18
+ return <Portal zIndex={zIndex || Number.MAX_SAFE_INTEGER}>{content}</Portal>
19
+ }