@hanzogui/toast 3.0.2 → 7.3.0

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 (270) hide show
  1. package/dist/cjs/Toast.cjs +149 -141
  2. package/dist/cjs/Toast.native.js +149 -142
  3. package/dist/cjs/Toast.native.js.map +1 -1
  4. package/dist/cjs/ToastAnnounce.cjs +81 -75
  5. package/dist/cjs/ToastAnnounce.native.js +86 -80
  6. package/dist/cjs/ToastAnnounce.native.js.map +1 -1
  7. package/dist/cjs/ToastComposable.cjs +933 -0
  8. package/dist/cjs/ToastComposable.native.js +1025 -0
  9. package/dist/cjs/ToastComposable.native.js.map +1 -0
  10. package/dist/cjs/ToastImperative.cjs +99 -80
  11. package/dist/cjs/ToastImperative.native.js +104 -96
  12. package/dist/cjs/ToastImperative.native.js.map +1 -1
  13. package/dist/cjs/ToastImpl.cjs +303 -241
  14. package/dist/cjs/ToastImpl.native.js +311 -273
  15. package/dist/cjs/ToastImpl.native.js.map +1 -1
  16. package/dist/cjs/ToastItemFrame.cjs +143 -0
  17. package/dist/cjs/ToastItemFrame.native.js +148 -0
  18. package/dist/cjs/ToastItemFrame.native.js.map +1 -0
  19. package/dist/cjs/ToastPortal.cjs +23 -18
  20. package/dist/cjs/ToastPortal.native.js +27 -22
  21. package/dist/cjs/ToastPortal.native.js.map +1 -1
  22. package/dist/cjs/ToastProvider.cjs +103 -99
  23. package/dist/cjs/ToastProvider.native.js +108 -106
  24. package/dist/cjs/ToastProvider.native.js.map +1 -1
  25. package/dist/cjs/ToastState.cjs +311 -0
  26. package/dist/cjs/ToastState.native.js +324 -0
  27. package/dist/cjs/ToastState.native.js.map +1 -0
  28. package/dist/cjs/ToastViewport.cjs +276 -235
  29. package/dist/cjs/ToastViewport.native.js +302 -274
  30. package/dist/cjs/ToastViewport.native.js.map +1 -1
  31. package/dist/cjs/Toaster.cjs +87 -0
  32. package/dist/cjs/Toaster.native.js +91 -0
  33. package/dist/cjs/Toaster.native.js.map +1 -0
  34. package/dist/cjs/constants.cjs +14 -12
  35. package/dist/cjs/constants.native.js +14 -12
  36. package/dist/cjs/constants.native.js.map +1 -1
  37. package/dist/cjs/createNativeToast.cjs +43 -35
  38. package/dist/cjs/createNativeToast.native.js +42 -30
  39. package/dist/cjs/createNativeToast.native.js.map +1 -1
  40. package/dist/cjs/dispatchNativeToast.cjs +47 -0
  41. package/dist/cjs/dispatchNativeToast.native.js +52 -0
  42. package/dist/cjs/dispatchNativeToast.native.js.map +1 -0
  43. package/dist/cjs/index.cjs +7 -5
  44. package/dist/cjs/index.native.js +7 -5
  45. package/dist/cjs/index.native.js.map +1 -1
  46. package/dist/cjs/types.cjs +7 -5
  47. package/dist/cjs/types.native.js +7 -5
  48. package/dist/cjs/types.native.js.map +1 -1
  49. package/dist/cjs/useAnimatedDragGesture.cjs +214 -0
  50. package/dist/cjs/useAnimatedDragGesture.native.js +224 -0
  51. package/dist/cjs/useAnimatedDragGesture.native.js.map +1 -0
  52. package/dist/cjs/useReducedMotion.cjs +74 -0
  53. package/dist/cjs/useReducedMotion.native.js +83 -0
  54. package/dist/cjs/useReducedMotion.native.js.map +1 -0
  55. package/dist/cjs/useToastAnimations.cjs +294 -0
  56. package/dist/cjs/useToastAnimations.native.js +304 -0
  57. package/dist/cjs/useToastAnimations.native.js.map +1 -0
  58. package/dist/cjs/v2.cjs +36 -0
  59. package/dist/cjs/v2.native.js +39 -0
  60. package/dist/cjs/v2.native.js.map +1 -0
  61. package/dist/esm/Toast.mjs +112 -106
  62. package/dist/esm/Toast.mjs.map +1 -1
  63. package/dist/esm/Toast.native.js +112 -107
  64. package/dist/esm/Toast.native.js.map +1 -1
  65. package/dist/esm/ToastAnnounce.mjs +46 -42
  66. package/dist/esm/ToastAnnounce.mjs.map +1 -1
  67. package/dist/esm/ToastAnnounce.native.js +52 -48
  68. package/dist/esm/ToastAnnounce.native.js.map +1 -1
  69. package/dist/esm/ToastComposable.mjs +895 -0
  70. package/dist/esm/ToastComposable.mjs.map +1 -0
  71. package/dist/esm/ToastComposable.native.js +984 -0
  72. package/dist/esm/ToastComposable.native.js.map +1 -0
  73. package/dist/esm/ToastImperative.mjs +71 -54
  74. package/dist/esm/ToastImperative.mjs.map +1 -1
  75. package/dist/esm/ToastImperative.native.js +76 -70
  76. package/dist/esm/ToastImperative.native.js.map +1 -1
  77. package/dist/esm/ToastImpl.mjs +261 -201
  78. package/dist/esm/ToastImpl.mjs.map +1 -1
  79. package/dist/esm/ToastImpl.native.js +270 -234
  80. package/dist/esm/ToastImpl.native.js.map +1 -1
  81. package/dist/esm/ToastItemFrame.mjs +114 -0
  82. package/dist/esm/ToastItemFrame.mjs.map +1 -0
  83. package/dist/esm/ToastItemFrame.native.js +116 -0
  84. package/dist/esm/ToastItemFrame.native.js.map +1 -0
  85. package/dist/esm/ToastPortal.mjs +8 -5
  86. package/dist/esm/ToastPortal.mjs.map +1 -1
  87. package/dist/esm/ToastPortal.native.js +12 -9
  88. package/dist/esm/ToastPortal.native.js.map +1 -1
  89. package/dist/esm/ToastProvider.mjs +71 -69
  90. package/dist/esm/ToastProvider.mjs.map +1 -1
  91. package/dist/esm/ToastProvider.native.js +77 -77
  92. package/dist/esm/ToastProvider.native.js.map +1 -1
  93. package/dist/esm/ToastState.mjs +285 -0
  94. package/dist/esm/ToastState.mjs.map +1 -0
  95. package/dist/esm/ToastState.native.js +295 -0
  96. package/dist/esm/ToastState.native.js.map +1 -0
  97. package/dist/esm/ToastViewport.mjs +238 -199
  98. package/dist/esm/ToastViewport.mjs.map +1 -1
  99. package/dist/esm/ToastViewport.native.js +265 -239
  100. package/dist/esm/ToastViewport.native.js.map +1 -1
  101. package/dist/esm/Toaster.mjs +51 -0
  102. package/dist/esm/Toaster.mjs.map +1 -0
  103. package/dist/esm/Toaster.native.js +52 -0
  104. package/dist/esm/Toaster.native.js.map +1 -0
  105. package/dist/esm/constants.mjs +2 -2
  106. package/dist/esm/constants.mjs.map +1 -1
  107. package/dist/esm/constants.native.js +2 -2
  108. package/dist/esm/constants.native.js.map +1 -1
  109. package/dist/esm/createNativeToast.mjs +29 -24
  110. package/dist/esm/createNativeToast.mjs.map +1 -1
  111. package/dist/esm/createNativeToast.native.js +27 -18
  112. package/dist/esm/createNativeToast.native.js.map +1 -1
  113. package/dist/esm/dispatchNativeToast.mjs +22 -0
  114. package/dist/esm/dispatchNativeToast.mjs.map +1 -0
  115. package/dist/esm/dispatchNativeToast.native.js +24 -0
  116. package/dist/esm/dispatchNativeToast.native.js.map +1 -0
  117. package/dist/esm/useAnimatedDragGesture.mjs +178 -0
  118. package/dist/esm/useAnimatedDragGesture.mjs.map +1 -0
  119. package/dist/esm/useAnimatedDragGesture.native.js +185 -0
  120. package/dist/esm/useAnimatedDragGesture.native.js.map +1 -0
  121. package/dist/esm/useReducedMotion.mjs +38 -0
  122. package/dist/esm/useReducedMotion.mjs.map +1 -0
  123. package/dist/esm/useReducedMotion.native.js +44 -0
  124. package/dist/esm/useReducedMotion.native.js.map +1 -0
  125. package/dist/esm/useToastAnimations.mjs +258 -0
  126. package/dist/esm/useToastAnimations.mjs.map +1 -0
  127. package/dist/esm/useToastAnimations.native.js +265 -0
  128. package/dist/esm/useToastAnimations.native.js.map +1 -0
  129. package/dist/esm/v2.mjs +6 -0
  130. package/dist/esm/v2.mjs.map +1 -0
  131. package/dist/esm/v2.native.js +6 -0
  132. package/dist/esm/v2.native.js.map +1 -0
  133. package/dist/jsx/Toast.mjs +112 -106
  134. package/dist/jsx/Toast.mjs.map +1 -1
  135. package/dist/jsx/Toast.native.js +149 -142
  136. package/dist/jsx/Toast.native.js.map +1 -1
  137. package/dist/jsx/ToastAnnounce.mjs +46 -42
  138. package/dist/jsx/ToastAnnounce.mjs.map +1 -1
  139. package/dist/jsx/ToastAnnounce.native.js +86 -80
  140. package/dist/jsx/ToastAnnounce.native.js.map +1 -1
  141. package/dist/jsx/ToastComposable.mjs +895 -0
  142. package/dist/jsx/ToastComposable.mjs.map +1 -0
  143. package/dist/jsx/ToastComposable.native.js +1025 -0
  144. package/dist/jsx/ToastComposable.native.js.map +1 -0
  145. package/dist/jsx/ToastImperative.mjs +71 -54
  146. package/dist/jsx/ToastImperative.mjs.map +1 -1
  147. package/dist/jsx/ToastImperative.native.js +104 -96
  148. package/dist/jsx/ToastImperative.native.js.map +1 -1
  149. package/dist/jsx/ToastImpl.mjs +261 -201
  150. package/dist/jsx/ToastImpl.mjs.map +1 -1
  151. package/dist/jsx/ToastImpl.native.js +311 -273
  152. package/dist/jsx/ToastImpl.native.js.map +1 -1
  153. package/dist/jsx/ToastItemFrame.mjs +114 -0
  154. package/dist/jsx/ToastItemFrame.mjs.map +1 -0
  155. package/dist/jsx/ToastItemFrame.native.js +148 -0
  156. package/dist/jsx/ToastItemFrame.native.js.map +1 -0
  157. package/dist/jsx/ToastPortal.mjs +8 -5
  158. package/dist/jsx/ToastPortal.mjs.map +1 -1
  159. package/dist/jsx/ToastPortal.native.js +27 -22
  160. package/dist/jsx/ToastPortal.native.js.map +1 -1
  161. package/dist/jsx/ToastProvider.mjs +71 -69
  162. package/dist/jsx/ToastProvider.mjs.map +1 -1
  163. package/dist/jsx/ToastProvider.native.js +108 -106
  164. package/dist/jsx/ToastProvider.native.js.map +1 -1
  165. package/dist/jsx/ToastState.mjs +285 -0
  166. package/dist/jsx/ToastState.mjs.map +1 -0
  167. package/dist/jsx/ToastState.native.js +324 -0
  168. package/dist/jsx/ToastState.native.js.map +1 -0
  169. package/dist/jsx/ToastViewport.mjs +238 -199
  170. package/dist/jsx/ToastViewport.mjs.map +1 -1
  171. package/dist/jsx/ToastViewport.native.js +302 -274
  172. package/dist/jsx/ToastViewport.native.js.map +1 -1
  173. package/dist/jsx/Toaster.mjs +51 -0
  174. package/dist/jsx/Toaster.mjs.map +1 -0
  175. package/dist/jsx/Toaster.native.js +91 -0
  176. package/dist/jsx/Toaster.native.js.map +1 -0
  177. package/dist/jsx/constants.mjs +2 -2
  178. package/dist/jsx/constants.mjs.map +1 -1
  179. package/dist/jsx/constants.native.js +14 -12
  180. package/dist/jsx/constants.native.js.map +1 -1
  181. package/dist/jsx/createNativeToast.mjs +29 -24
  182. package/dist/jsx/createNativeToast.mjs.map +1 -1
  183. package/dist/jsx/createNativeToast.native.js +42 -30
  184. package/dist/jsx/createNativeToast.native.js.map +1 -1
  185. package/dist/jsx/dispatchNativeToast.mjs +22 -0
  186. package/dist/jsx/dispatchNativeToast.mjs.map +1 -0
  187. package/dist/jsx/dispatchNativeToast.native.js +52 -0
  188. package/dist/jsx/dispatchNativeToast.native.js.map +1 -0
  189. package/dist/jsx/index.native.js +7 -5
  190. package/dist/jsx/types.native.js +7 -5
  191. package/dist/jsx/useAnimatedDragGesture.mjs +178 -0
  192. package/dist/jsx/useAnimatedDragGesture.mjs.map +1 -0
  193. package/dist/jsx/useAnimatedDragGesture.native.js +224 -0
  194. package/dist/jsx/useAnimatedDragGesture.native.js.map +1 -0
  195. package/dist/jsx/useReducedMotion.mjs +38 -0
  196. package/dist/jsx/useReducedMotion.mjs.map +1 -0
  197. package/dist/jsx/useReducedMotion.native.js +83 -0
  198. package/dist/jsx/useReducedMotion.native.js.map +1 -0
  199. package/dist/jsx/useToastAnimations.mjs +258 -0
  200. package/dist/jsx/useToastAnimations.mjs.map +1 -0
  201. package/dist/jsx/useToastAnimations.native.js +304 -0
  202. package/dist/jsx/useToastAnimations.native.js.map +1 -0
  203. package/dist/jsx/v2.mjs +6 -0
  204. package/dist/jsx/v2.mjs.map +1 -0
  205. package/dist/jsx/v2.native.js +39 -0
  206. package/dist/jsx/v2.native.js.map +1 -0
  207. package/package.json +43 -24
  208. package/src/Toast.tsx +2 -2
  209. package/src/ToastComposable.tsx +1380 -0
  210. package/src/ToastImpl.tsx +15 -3
  211. package/src/ToastItemFrame.tsx +136 -0
  212. package/src/ToastPortal.tsx +2 -2
  213. package/src/ToastProvider.tsx +8 -1
  214. package/src/ToastState.ts +398 -0
  215. package/src/ToastViewport.tsx +6 -5
  216. package/src/Toaster.tsx +181 -0
  217. package/src/createNativeToast.native.tsx +4 -0
  218. package/src/createNativeToast.tsx +18 -24
  219. package/src/dispatchNativeToast.ts +43 -0
  220. package/src/useAnimatedDragGesture.native.ts +255 -0
  221. package/src/useAnimatedDragGesture.ts +319 -0
  222. package/src/useReducedMotion.ts +59 -0
  223. package/src/useToastAnimations.ts +377 -0
  224. package/src/v2.ts +31 -0
  225. package/types/Toast.d.ts +34 -34
  226. package/types/Toast.d.ts.map +1 -0
  227. package/types/ToastAnnounce.d.ts +4 -4
  228. package/types/ToastAnnounce.d.ts.map +1 -0
  229. package/types/ToastComposable.d.ts +199 -0
  230. package/types/ToastComposable.d.ts.map +1 -0
  231. package/types/ToastImperative.d.ts +3 -3
  232. package/types/ToastImperative.d.ts.map +1 -0
  233. package/types/ToastImpl.d.ts +11 -11
  234. package/types/ToastImpl.d.ts.map +1 -0
  235. package/types/ToastItemFrame.d.ts +25 -0
  236. package/types/ToastItemFrame.d.ts.map +1 -0
  237. package/types/ToastPortal.d.ts +1 -1
  238. package/types/ToastPortal.d.ts.map +1 -0
  239. package/types/ToastProvider.d.ts +4 -4
  240. package/types/ToastProvider.d.ts.map +1 -0
  241. package/types/ToastState.d.ts +179 -0
  242. package/types/ToastState.d.ts.map +1 -0
  243. package/types/ToastViewport.d.ts +10 -10
  244. package/types/ToastViewport.d.ts.map +1 -0
  245. package/types/Toaster.d.ts +112 -0
  246. package/types/Toaster.d.ts.map +1 -0
  247. package/types/constants.d.ts.map +1 -0
  248. package/types/createNativeToast.d.ts +5 -0
  249. package/types/createNativeToast.d.ts.map +1 -0
  250. package/types/createNativeToast.native.d.ts +1 -0
  251. package/types/createNativeToast.native.d.ts.map +1 -0
  252. package/types/dispatchNativeToast.d.ts +12 -0
  253. package/types/dispatchNativeToast.d.ts.map +1 -0
  254. package/types/index.d.ts.map +1 -0
  255. package/types/types.d.ts.map +1 -0
  256. package/types/useAnimatedDragGesture.d.ts +33 -0
  257. package/types/useAnimatedDragGesture.d.ts.map +1 -0
  258. package/types/useAnimatedDragGesture.native.d.ts +33 -0
  259. package/types/useAnimatedDragGesture.native.d.ts.map +1 -0
  260. package/types/useReducedMotion.d.ts +6 -0
  261. package/types/useReducedMotion.d.ts.map +1 -0
  262. package/types/useToastAnimations.d.ts +50 -0
  263. package/types/useToastAnimations.d.ts.map +1 -0
  264. package/types/v2.d.ts +10 -0
  265. package/types/v2.d.ts.map +1 -0
  266. package/v2/index.cjs +2 -0
  267. package/v2/index.js +2 -0
  268. package/v2/index.native.cjs +2 -0
  269. package/v2/index.native.js +2 -0
  270. package/LICENSE +0 -21
package/src/ToastImpl.tsx CHANGED
@@ -21,8 +21,17 @@ import type {
21
21
  Animated,
22
22
  GestureResponderEvent,
23
23
  PanResponderGestureState,
24
+ PanResponder as PanResponderType,
24
25
  } from 'react-native'
25
- import { PanResponder } from 'react-native'
26
+
27
+ // Lazy load PanResponder only on native to avoid SSR issues
28
+ const getPanResponder = (): typeof PanResponderType | null => {
29
+ if (process.env.GUI_TARGET === 'native') {
30
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
31
+ return require('react-native').PanResponder
32
+ }
33
+ return null
34
+ }
26
35
  import { TOAST_CONTEXT, TOAST_NAME } from './constants'
27
36
  import { ToastAnnounce } from './ToastAnnounce'
28
37
  import type { ScopedProps, SwipeDirection } from './ToastProvider'
@@ -53,7 +62,7 @@ const ToastImplFrame = styled(YStack, {
53
62
  } as const,
54
63
 
55
64
  defaultVariants: {
56
- unstyled: process.env.HANZO_GUI_HEADLESS === '1',
65
+ unstyled: process.env.GUI_HEADLESS === '1',
57
66
  },
58
67
  })
59
68
 
@@ -281,6 +290,8 @@ const ToastImpl = React.forwardRef<GuiElement, ToastImplProps>(
281
290
  })
282
291
 
283
292
  const panResponder = React.useMemo(() => {
293
+ const PanResponder = getPanResponder()
294
+ if (!PanResponder) return null
284
295
  return PanResponder.create({
285
296
  onMoveShouldSetPanResponder: (e, gesture) => {
286
297
  const shouldMove = shouldGrantGestureMove(context.swipeDirection, gesture)
@@ -330,7 +341,8 @@ const ToastImpl = React.forwardRef<GuiElement, ToastImplProps>(
330
341
  {announceTextContent && (
331
342
  <ToastAnnounce
332
343
  scope={scope}
333
- // toasts are always role=status to avoid stuttering issues with role=alert in SRs
344
+ // Toasts are always role=status to avoid stuttering issues with role=alert in SRs.
345
+ // biome-ignore lint/a11y/useSemanticElements: <explanation>
334
346
  role="status"
335
347
  aria-live={type === 'foreground' ? 'assertive' : 'polite'}
336
348
  aria-atomic
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Shared visual styled components for toast items.
3
+ * Shared across web and native — imported by ToastComposable.
4
+ */
5
+
6
+ import { styled } from '@hanzogui/core'
7
+ import { XStack, YStack } from '@hanzogui/stacks'
8
+ import { SizableText } from '@hanzogui/text'
9
+
10
+ /* -------------------------------------------------------------------------------------------------
11
+ * ToastPositionWrapper - handles absolute positioning and stacking animations
12
+ * On web: uses Gui transition/enterStyle/exitStyle
13
+ * On native: replaced by Animated.View with useAnimatedStyle
14
+ * -----------------------------------------------------------------------------------------------*/
15
+
16
+ export const ToastPositionWrapper = styled(YStack, {
17
+ name: 'ToastPositionWrapper',
18
+ pointerEvents: 'auto',
19
+ position: 'absolute',
20
+ left: 0,
21
+ right: 0,
22
+ opacity: 1,
23
+ scale: 1,
24
+ y: 0,
25
+ x: 0,
26
+ })
27
+
28
+ /* -------------------------------------------------------------------------------------------------
29
+ * ToastItemFrame - visual styling for the toast
30
+ * Shared across web and native — the visual appearance of the toast card.
31
+ * -----------------------------------------------------------------------------------------------*/
32
+
33
+ export const ToastItemFrame = styled(YStack, {
34
+ name: 'ToastItem',
35
+ userSelect: 'none',
36
+ cursor: 'default',
37
+ focusable: true,
38
+
39
+ variants: {
40
+ unstyled: {
41
+ false: {
42
+ backgroundColor: '$background',
43
+ borderRadius: '$6',
44
+ paddingHorizontal: '$4',
45
+ paddingVertical: '$3',
46
+ borderWidth: 1,
47
+ borderColor: '$borderColor',
48
+ shadowColor: 'rgba(0, 0, 0, 0.15)',
49
+ shadowOffset: { width: 0, height: 4 },
50
+ shadowRadius: 12,
51
+ focusVisibleStyle: {
52
+ outlineWidth: 2,
53
+ outlineColor: '$color8',
54
+ outlineStyle: 'solid',
55
+ },
56
+ },
57
+ },
58
+ } as const,
59
+
60
+ defaultVariants: {
61
+ unstyled: process.env.GUI_HEADLESS === '1',
62
+ },
63
+ })
64
+
65
+ /* -------------------------------------------------------------------------------------------------
66
+ * ToastCloseFrame
67
+ * -----------------------------------------------------------------------------------------------*/
68
+
69
+ export const ToastCloseFrame = styled(XStack, {
70
+ name: 'ToastClose',
71
+ render: 'button',
72
+ alignItems: 'center',
73
+ justifyContent: 'center',
74
+ cursor: 'pointer',
75
+
76
+ variants: {
77
+ unstyled: {
78
+ false: {
79
+ width: 18,
80
+ height: 18,
81
+ borderRadius: '$10',
82
+ backgroundColor: '$background',
83
+ borderWidth: 1,
84
+ borderColor: '$borderColor',
85
+ shadowColor: 'rgba(0, 0, 0, 0.08)',
86
+ shadowOffset: { width: 0, height: 1 },
87
+ shadowRadius: 3,
88
+ hoverStyle: { backgroundColor: '$color3' },
89
+ pressStyle: { backgroundColor: '$color4' },
90
+ },
91
+ },
92
+ } as const,
93
+
94
+ defaultVariants: {
95
+ unstyled: process.env.GUI_HEADLESS === '1',
96
+ },
97
+ })
98
+
99
+ /* -------------------------------------------------------------------------------------------------
100
+ * ToastActionFrame - for action/cancel buttons with text
101
+ * -----------------------------------------------------------------------------------------------*/
102
+
103
+ export const ToastActionFrame = styled(XStack, {
104
+ name: 'ToastAction',
105
+ render: 'button',
106
+ alignItems: 'center',
107
+ justifyContent: 'center',
108
+ cursor: 'pointer',
109
+
110
+ variants: {
111
+ unstyled: {
112
+ false: {
113
+ borderRadius: '$2',
114
+ paddingHorizontal: '$2',
115
+ height: 24,
116
+ backgroundColor: '$color5',
117
+ hoverStyle: { backgroundColor: '$color6' },
118
+ pressStyle: { backgroundColor: '$color7' },
119
+ },
120
+ },
121
+ } as const,
122
+
123
+ defaultVariants: {
124
+ unstyled: process.env.GUI_HEADLESS === '1',
125
+ },
126
+ })
127
+
128
+ /* -------------------------------------------------------------------------------------------------
129
+ * DefaultCloseIcon
130
+ * -----------------------------------------------------------------------------------------------*/
131
+
132
+ export const DefaultCloseIcon = () => (
133
+ <SizableText size="$1" color="$color11">
134
+ ✕
135
+ </SizableText>
136
+ )
@@ -1,6 +1,6 @@
1
+ import { isWeb } from '@hanzogui/constants'
1
2
  import { Portal } from '@hanzogui/portal'
2
3
  import type { ReactNode } from 'react'
3
- import { Platform } from 'react-native'
4
4
  import { ReprogapateToastProvider, type ToastProviderContextValue } from './ToastProvider'
5
5
 
6
6
  export function ToastPortal(props: {
@@ -10,7 +10,7 @@ export function ToastPortal(props: {
10
10
  }) {
11
11
  const { context, children, zIndex } = props
12
12
  let content = children
13
- if (Platform.OS === 'android' || Platform.OS === 'ios') {
13
+ if (!isWeb) {
14
14
  content = (
15
15
  <ReprogapateToastProvider context={context}>{children}</ReprogapateToastProvider>
16
16
  )
@@ -17,7 +17,14 @@ const PROVIDER_NAME = 'ToastProvider'
17
17
 
18
18
  const [Collection, useCollection] = createCollection<GuiElement>('Toast')
19
19
 
20
- export type SwipeDirection = 'vertical' | 'up' | 'down' | 'horizontal' | 'left' | 'right'
20
+ export type SwipeDirection =
21
+ | 'vertical'
22
+ | 'up'
23
+ | 'down'
24
+ | 'horizontal'
25
+ | 'left'
26
+ | 'right'
27
+ | 'auto'
21
28
 
22
29
  export type ToastProviderContextValue = {
23
30
  id: string
@@ -0,0 +1,398 @@
1
+ import type React from 'react'
2
+ import type { CreateNativeToastOptions, NativeToastRef } from './types'
3
+
4
+ // counter for generating unique toast ids
5
+ let toastsCounter = 1
6
+
7
+ export type ToastType = 'default' | 'success' | 'error' | 'warning' | 'info' | 'loading'
8
+
9
+ export interface ToastT {
10
+ id: string | number
11
+ title: React.ReactNode | (() => React.ReactNode)
12
+ description?: React.ReactNode | (() => React.ReactNode)
13
+ type?: ToastType
14
+ icon?: React.ReactNode
15
+ jsx?: React.ReactElement
16
+ dismissible?: boolean
17
+ duration?: number
18
+ promise?: PromiseT
19
+ action?: ToastAction
20
+ cancel?: ToastAction
21
+ closeButton?: boolean
22
+ onDismiss?: (toast: ToastT) => void
23
+ onAutoClose?: (toast: ToastT) => void
24
+ // internal
25
+ delete?: boolean
26
+ // style overrides
27
+ style?: React.CSSProperties
28
+ className?: string
29
+ // native options
30
+ burntOptions?: CreateNativeToastOptions['burntOptions']
31
+ notificationOptions?: CreateNativeToastOptions['notificationOptions']
32
+ /** Custom user data */
33
+ data?: Record<string, unknown>
34
+ }
35
+
36
+ export interface ToastAction {
37
+ label: string
38
+ onClick?: (event: React.MouseEvent) => void
39
+ }
40
+
41
+ export interface ToastToDismiss {
42
+ id: string | number
43
+ dismiss: true
44
+ }
45
+
46
+ export type ExternalToast = Omit<
47
+ ToastT,
48
+ 'id' | 'title' | 'type' | 'delete' | 'promise'
49
+ > & {
50
+ id?: string | number
51
+ }
52
+
53
+ export type PromiseT<Data = any> = Promise<Data> | (() => Promise<Data>)
54
+
55
+ export interface PromiseData<Data = any> {
56
+ loading?: React.ReactNode | (() => React.ReactNode)
57
+ success?: React.ReactNode | ((data: Data) => React.ReactNode)
58
+ error?: React.ReactNode | ((error: any) => React.ReactNode)
59
+ description?: React.ReactNode | ((data: any) => React.ReactNode)
60
+ finally?: () => void
61
+ }
62
+
63
+ type TitleT = React.ReactNode | (() => React.ReactNode)
64
+
65
+ /**
66
+ * Observer class that manages toast state globally.
67
+ * Follows the pattern from Sonner for a clean, decoupled architecture.
68
+ */
69
+ class Observer {
70
+ subscribers: Array<(toast: ToastT | ToastToDismiss) => void> = []
71
+ toasts: ToastT[] = []
72
+ dismissedToasts: Set<string | number> = new Set()
73
+
74
+ /**
75
+ * Subscribe to toast state changes.
76
+ * Returns an unsubscribe function.
77
+ */
78
+ subscribe = (subscriber: (toast: ToastT | ToastToDismiss) => void) => {
79
+ this.subscribers.push(subscriber)
80
+
81
+ return () => {
82
+ const index = this.subscribers.indexOf(subscriber)
83
+ if (index > -1) {
84
+ this.subscribers.splice(index, 1)
85
+ }
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Publish a toast to all subscribers
91
+ */
92
+ publish = (data: ToastT) => {
93
+ this.subscribers.forEach((subscriber) => subscriber(data))
94
+ }
95
+
96
+ /**
97
+ * Add a new toast to the internal array and publish to subscribers
98
+ */
99
+ addToast = (data: ToastT) => {
100
+ this.publish(data)
101
+ this.toasts = [...this.toasts, data]
102
+ }
103
+
104
+ /**
105
+ * Create or update a toast
106
+ */
107
+ create = (
108
+ data: ExternalToast & {
109
+ title?: TitleT
110
+ type?: ToastType
111
+ promise?: PromiseT
112
+ jsx?: React.ReactElement
113
+ }
114
+ ) => {
115
+ const { title, ...rest } = data
116
+ const id =
117
+ typeof data?.id === 'number' || (typeof data?.id === 'string' && data.id.length > 0)
118
+ ? data.id
119
+ : toastsCounter++
120
+
121
+ const alreadyExists = this.toasts.find((toast) => toast.id === id)
122
+ const dismissible = data.dismissible ?? true
123
+
124
+ // if this toast was previously dismissed, clear that
125
+ if (this.dismissedToasts.has(id)) {
126
+ this.dismissedToasts.delete(id)
127
+ }
128
+
129
+ if (alreadyExists) {
130
+ // update existing toast
131
+ this.toasts = this.toasts.map((toast) => {
132
+ if (toast.id === id) {
133
+ this.publish({ ...toast, ...data, id, title, dismissible })
134
+ return { ...toast, ...data, id, title, dismissible }
135
+ }
136
+ return toast
137
+ })
138
+ } else {
139
+ this.addToast({ title, ...rest, dismissible, id } as ToastT)
140
+ }
141
+
142
+ return id
143
+ }
144
+
145
+ /**
146
+ * Dismiss a toast by id, or all toasts if no id provided
147
+ */
148
+ dismiss = (id?: string | number) => {
149
+ if (id !== undefined) {
150
+ this.dismissedToasts.add(id)
151
+ // use requestAnimationFrame to batch updates
152
+ requestAnimationFrame(() => {
153
+ this.subscribers.forEach((subscriber) => subscriber({ id, dismiss: true }))
154
+ })
155
+ } else {
156
+ // dismiss all
157
+ this.toasts.forEach((toast) => {
158
+ this.subscribers.forEach((subscriber) =>
159
+ subscriber({ id: toast.id, dismiss: true })
160
+ )
161
+ })
162
+ }
163
+
164
+ return id
165
+ }
166
+
167
+ /**
168
+ * Show a basic toast message
169
+ */
170
+ message = (title: TitleT, data?: ExternalToast) => {
171
+ return this.create({ ...data, title, type: 'default' })
172
+ }
173
+
174
+ /**
175
+ * Show a success toast
176
+ */
177
+ success = (title: TitleT, data?: ExternalToast) => {
178
+ return this.create({ ...data, title, type: 'success' })
179
+ }
180
+
181
+ /**
182
+ * Show an error toast
183
+ */
184
+ error = (title: TitleT, data?: ExternalToast) => {
185
+ return this.create({ ...data, title, type: 'error' })
186
+ }
187
+
188
+ /**
189
+ * Show a warning toast
190
+ */
191
+ warning = (title: TitleT, data?: ExternalToast) => {
192
+ return this.create({ ...data, title, type: 'warning' })
193
+ }
194
+
195
+ /**
196
+ * Show an info toast
197
+ */
198
+ info = (title: TitleT, data?: ExternalToast) => {
199
+ return this.create({ ...data, title, type: 'info' })
200
+ }
201
+
202
+ /**
203
+ * Show a loading toast
204
+ */
205
+ loading = (title: TitleT, data?: ExternalToast) => {
206
+ return this.create({ ...data, title, type: 'loading' })
207
+ }
208
+
209
+ /**
210
+ * Show a toast for a promise, automatically transitioning through
211
+ * loading -> success/error states
212
+ */
213
+ promise = <ToastData>(promise: PromiseT<ToastData>, data?: PromiseData<ToastData>) => {
214
+ if (!data) {
215
+ return
216
+ }
217
+
218
+ let id: string | number | undefined = undefined
219
+
220
+ // show loading state if provided
221
+ if (data.loading !== undefined) {
222
+ id = this.create({
223
+ promise,
224
+ type: 'loading',
225
+ title: data.loading,
226
+ description:
227
+ typeof data.description !== 'function' ? data.description : undefined,
228
+ // loading toasts shouldn't auto-dismiss
229
+ duration: Number.POSITIVE_INFINITY,
230
+ })
231
+ }
232
+
233
+ const p = Promise.resolve(promise instanceof Function ? promise() : promise)
234
+
235
+ let shouldDismiss = id !== undefined
236
+ let result: ['resolve', ToastData] | ['reject', unknown]
237
+
238
+ const originalPromise = p
239
+ .then(async (response) => {
240
+ result = ['resolve', response]
241
+
242
+ // check if response is an HTTP error
243
+ if (isHttpResponse(response) && !response.ok) {
244
+ shouldDismiss = false
245
+ const errorMsg =
246
+ typeof data.error === 'function'
247
+ ? await data.error(`HTTP error! status: ${response.status}`)
248
+ : data.error
249
+ const description =
250
+ typeof data.description === 'function'
251
+ ? await data.description(`HTTP error! status: ${response.status}`)
252
+ : data.description
253
+
254
+ this.create({ id, type: 'error', title: errorMsg, description })
255
+ } else if (data.success !== undefined) {
256
+ shouldDismiss = false
257
+ const successMsg =
258
+ typeof data.success === 'function'
259
+ ? await data.success(response)
260
+ : data.success
261
+ const description =
262
+ typeof data.description === 'function'
263
+ ? await data.description(response)
264
+ : data.description
265
+
266
+ this.create({ id, type: 'success', title: successMsg, description })
267
+ }
268
+ })
269
+ .catch(async (error) => {
270
+ result = ['reject', error]
271
+
272
+ if (data.error !== undefined) {
273
+ shouldDismiss = false
274
+ const errorMsg =
275
+ typeof data.error === 'function' ? await data.error(error) : data.error
276
+ const description =
277
+ typeof data.description === 'function'
278
+ ? await data.description(error)
279
+ : data.description
280
+
281
+ this.create({ id, type: 'error', title: errorMsg, description })
282
+ }
283
+ })
284
+ .finally(() => {
285
+ if (shouldDismiss) {
286
+ // toast is still in load state, dismiss it
287
+ this.dismiss(id)
288
+ id = undefined
289
+ }
290
+ data.finally?.()
291
+ })
292
+
293
+ // return a promise that can be unwrapped
294
+ const unwrap = () =>
295
+ new Promise<ToastData>((resolve, reject) =>
296
+ originalPromise
297
+ .then(() => (result[0] === 'reject' ? reject(result[1]) : resolve(result[1])))
298
+ .catch(reject)
299
+ )
300
+
301
+ if (typeof id !== 'string' && typeof id !== 'number') {
302
+ return { unwrap }
303
+ } else {
304
+ return Object.assign(id, { unwrap })
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Show a custom JSX toast
310
+ */
311
+ custom = (jsx: (id: string | number) => React.ReactElement, data?: ExternalToast) => {
312
+ const id = data?.id ?? toastsCounter++
313
+ this.create({ jsx: jsx(id), ...data, id })
314
+ return id
315
+ }
316
+
317
+ /**
318
+ * Get all active (non-dismissed) toasts
319
+ */
320
+ getActiveToasts = () => {
321
+ return this.toasts.filter((toast) => !this.dismissedToasts.has(toast.id))
322
+ }
323
+
324
+ /**
325
+ * Get full toast history
326
+ */
327
+ getHistory = () => {
328
+ return this.toasts
329
+ }
330
+ }
331
+
332
+ function isHttpResponse(data: any): data is Response {
333
+ return (
334
+ data &&
335
+ typeof data === 'object' &&
336
+ 'ok' in data &&
337
+ typeof data.ok === 'boolean' &&
338
+ 'status' in data &&
339
+ typeof data.status === 'number'
340
+ )
341
+ }
342
+
343
+ // singleton instance
344
+ export const ToastState = new Observer()
345
+
346
+ // basic toast function
347
+ const toastFunction = (title: TitleT, data?: ExternalToast) => {
348
+ return ToastState.create({ ...data, title })
349
+ }
350
+
351
+ // getters
352
+ const getHistory = () => ToastState.getHistory()
353
+ const getToasts = () => ToastState.getActiveToasts()
354
+
355
+ /**
356
+ * Main toast API - call directly or use methods like toast.success()
357
+ *
358
+ * @example
359
+ * // basic usage
360
+ * toast('Hello world')
361
+ *
362
+ * // with type
363
+ * toast.success('Saved!')
364
+ * toast.error('Something went wrong')
365
+ *
366
+ * // with options
367
+ * toast('Hello', { duration: 5000 })
368
+ *
369
+ * // promise toast
370
+ * toast.promise(fetch('/api'), {
371
+ * loading: 'Loading...',
372
+ * success: 'Done!',
373
+ * error: 'Failed'
374
+ * })
375
+ *
376
+ * // custom JSX
377
+ * toast.custom((id) => <MyToast id={id} />)
378
+ *
379
+ * // dismiss
380
+ * const id = toast('Hello')
381
+ * toast.dismiss(id)
382
+ * toast.dismiss() // dismiss all
383
+ */
384
+ export const toast = Object.assign(toastFunction, {
385
+ success: ToastState.success,
386
+ error: ToastState.error,
387
+ warning: ToastState.warning,
388
+ info: ToastState.info,
389
+ loading: ToastState.loading,
390
+ promise: ToastState.promise,
391
+ custom: ToastState.custom,
392
+ dismiss: ToastState.dismiss,
393
+ message: ToastState.message,
394
+ getHistory,
395
+ getToasts,
396
+ })
397
+
398
+ export type { NativeToastRef }
@@ -28,7 +28,7 @@ const ToastViewportWrapperFrame = styled(YStack, {
28
28
  bottom: 0,
29
29
  left: 0,
30
30
  right: 0,
31
- position: 'fixed',
31
+ position: isWeb ? ('fixed' as any) : 'absolute',
32
32
  maxWidth: '100%',
33
33
  tabIndex: 0,
34
34
  zIndex: 100000,
@@ -37,7 +37,7 @@ const ToastViewportWrapperFrame = styled(YStack, {
37
37
  } as const,
38
38
 
39
39
  defaultVariants: {
40
- unstyled: process.env.HANZO_GUI_HEADLESS === '1',
40
+ unstyled: process.env.GUI_HEADLESS === '1',
41
41
  },
42
42
  })
43
43
 
@@ -48,14 +48,14 @@ const ToastViewportFrame = styled(YStack, {
48
48
  unstyled: {
49
49
  false: {
50
50
  pointerEvents: 'box-none',
51
- position: 'fixed',
51
+ position: isWeb ? ('fixed' as any) : 'absolute',
52
52
  maxWidth: '100%',
53
53
  },
54
54
  },
55
55
  } as const,
56
56
 
57
57
  defaultVariants: {
58
- unstyled: process.env.HANZO_GUI_HEADLESS === '1',
58
+ unstyled: process.env.GUI_HEADLESS === '1',
59
59
  },
60
60
  })
61
61
 
@@ -262,6 +262,7 @@ const ToastViewport = React.memo(
262
262
  const contents = (
263
263
  <ToastViewportWrapperFrame
264
264
  ref={wrapperRef as any}
265
+ // biome-ignore lint/a11y/useSemanticElements: <explanation>
265
266
  role="region"
266
267
  aria-label={label.replace('{hotkey}', hotkeyLabel)}
267
268
  // // Ensure virtual cursor from landmarks menus triggers focus/blur for pause/resume
@@ -363,7 +364,7 @@ const FocusProxy = React.forwardRef<FocusProxyElement, ScopedProps<FocusProxyPro
363
364
  {...proxyProps}
364
365
  ref={forwardedRef}
365
366
  // Avoid page scrolling when focus is on the focus proxy
366
- position="fixed"
367
+ position={isWeb ? ('fixed' as any) : 'absolute'}
367
368
  onFocus={(event) => {
368
369
  if (!isWeb) return
369
370
  const prevFocusedElement = event.relatedTarget as HTMLElement | null