@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.
- package/dist/cjs/Toast.cjs +170 -0
- package/dist/cjs/Toast.js +119 -0
- package/dist/cjs/Toast.js.map +6 -0
- package/dist/cjs/Toast.native.js +174 -0
- package/dist/cjs/Toast.native.js.map +1 -0
- package/dist/cjs/ToastAnnounce.cjs +97 -0
- package/dist/cjs/ToastAnnounce.js +72 -0
- package/dist/cjs/ToastAnnounce.js.map +6 -0
- package/dist/cjs/ToastAnnounce.native.js +105 -0
- package/dist/cjs/ToastAnnounce.native.js.map +1 -0
- package/dist/cjs/ToastImperative.cjs +100 -0
- package/dist/cjs/ToastImperative.js +71 -0
- package/dist/cjs/ToastImperative.js.map +6 -0
- package/dist/cjs/ToastImperative.native.js +122 -0
- package/dist/cjs/ToastImperative.native.js.map +1 -0
- package/dist/cjs/ToastImpl.cjs +292 -0
- package/dist/cjs/ToastImpl.js +227 -0
- package/dist/cjs/ToastImpl.js.map +6 -0
- package/dist/cjs/ToastImpl.native.js +327 -0
- package/dist/cjs/ToastImpl.native.js.map +1 -0
- package/dist/cjs/ToastItem.cjs +466 -0
- package/dist/cjs/ToastItem.js +356 -0
- package/dist/cjs/ToastItem.js.map +6 -0
- package/dist/cjs/ToastItem.native.js +547 -0
- package/dist/cjs/ToastItem.native.js.map +1 -0
- package/dist/cjs/ToastPortal.cjs +44 -0
- package/dist/cjs/ToastPortal.js +26 -0
- package/dist/cjs/ToastPortal.js.map +6 -0
- package/dist/cjs/ToastPortal.native.js +47 -0
- package/dist/cjs/ToastPortal.native.js.map +1 -0
- package/dist/cjs/ToastProvider.cjs +146 -0
- package/dist/cjs/ToastProvider.js +105 -0
- package/dist/cjs/ToastProvider.js.map +6 -0
- package/dist/cjs/ToastProvider.native.js +159 -0
- package/dist/cjs/ToastProvider.native.js.map +1 -0
- package/dist/cjs/ToastState.cjs +248 -0
- package/dist/cjs/ToastState.js +160 -0
- package/dist/cjs/ToastState.js.map +6 -0
- package/dist/cjs/ToastState.native.js +257 -0
- package/dist/cjs/ToastState.native.js.map +1 -0
- package/dist/cjs/ToastViewport.cjs +278 -0
- package/dist/cjs/ToastViewport.js +263 -0
- package/dist/cjs/ToastViewport.js.map +6 -0
- package/dist/cjs/ToastViewport.native.js +316 -0
- package/dist/cjs/ToastViewport.native.js.map +1 -0
- package/dist/cjs/Toaster.cjs +219 -0
- package/dist/cjs/Toaster.js +177 -0
- package/dist/cjs/Toaster.js.map +6 -0
- package/dist/cjs/Toaster.native.js +279 -0
- package/dist/cjs/Toaster.native.js.map +1 -0
- package/dist/cjs/constants.cjs +28 -0
- package/dist/cjs/constants.js +22 -0
- package/dist/cjs/constants.js.map +6 -0
- package/dist/cjs/constants.native.js +31 -0
- package/dist/cjs/constants.native.js.map +1 -0
- package/dist/cjs/createNativeToast.cjs +51 -0
- package/dist/cjs/createNativeToast.js +44 -0
- package/dist/cjs/createNativeToast.js.map +6 -0
- package/dist/cjs/createNativeToast.native.js +47 -0
- package/dist/cjs/createNativeToast.native.js.map +1 -0
- package/dist/cjs/index.cjs +28 -0
- package/dist/cjs/index.js +22 -0
- package/dist/cjs/index.js.map +6 -0
- package/dist/cjs/index.native.js +31 -0
- package/dist/cjs/index.native.js.map +1 -0
- package/dist/cjs/types.cjs +16 -0
- package/dist/cjs/types.js +14 -0
- package/dist/cjs/types.js.map +6 -0
- package/dist/cjs/types.native.js +19 -0
- package/dist/cjs/types.native.js.map +1 -0
- package/dist/cjs/useDragGesture.cjs +129 -0
- package/dist/cjs/useDragGesture.js +100 -0
- package/dist/cjs/useDragGesture.js.map +6 -0
- package/dist/cjs/useDragGesture.native.js +146 -0
- package/dist/cjs/useDragGesture.native.js.map +1 -0
- package/dist/esm/Toast.js +107 -0
- package/dist/esm/Toast.js.map +6 -0
- package/dist/esm/Toast.mjs +131 -0
- package/dist/esm/Toast.mjs.map +1 -0
- package/dist/esm/Toast.native.js +132 -0
- package/dist/esm/Toast.native.js.map +1 -0
- package/dist/esm/ToastAnnounce.js +55 -0
- package/dist/esm/ToastAnnounce.js.map +6 -0
- package/dist/esm/ToastAnnounce.mjs +62 -0
- package/dist/esm/ToastAnnounce.mjs.map +1 -0
- package/dist/esm/ToastAnnounce.native.js +67 -0
- package/dist/esm/ToastAnnounce.native.js.map +1 -0
- package/dist/esm/ToastImperative.js +50 -0
- package/dist/esm/ToastImperative.js.map +6 -0
- package/dist/esm/ToastImperative.mjs +63 -0
- package/dist/esm/ToastImperative.mjs.map +1 -0
- package/dist/esm/ToastImperative.native.js +82 -0
- package/dist/esm/ToastImperative.native.js.map +1 -0
- package/dist/esm/ToastImpl.js +225 -0
- package/dist/esm/ToastImpl.js.map +6 -0
- package/dist/esm/ToastImpl.mjs +256 -0
- package/dist/esm/ToastImpl.mjs.map +1 -0
- package/dist/esm/ToastImpl.native.js +288 -0
- package/dist/esm/ToastImpl.native.js.map +1 -0
- package/dist/esm/ToastItem.js +339 -0
- package/dist/esm/ToastItem.js.map +6 -0
- package/dist/esm/ToastItem.mjs +432 -0
- package/dist/esm/ToastItem.mjs.map +1 -0
- package/dist/esm/ToastItem.native.js +510 -0
- package/dist/esm/ToastItem.native.js.map +1 -0
- package/dist/esm/ToastPortal.js +13 -0
- package/dist/esm/ToastPortal.js.map +6 -0
- package/dist/esm/ToastPortal.mjs +21 -0
- package/dist/esm/ToastPortal.mjs.map +1 -0
- package/dist/esm/ToastPortal.native.js +21 -0
- package/dist/esm/ToastPortal.native.js.map +1 -0
- package/dist/esm/ToastProvider.js +87 -0
- package/dist/esm/ToastProvider.js.map +6 -0
- package/dist/esm/ToastProvider.mjs +108 -0
- package/dist/esm/ToastProvider.mjs.map +1 -0
- package/dist/esm/ToastProvider.native.js +118 -0
- package/dist/esm/ToastProvider.native.js.map +1 -0
- package/dist/esm/ToastState.js +144 -0
- package/dist/esm/ToastState.js.map +6 -0
- package/dist/esm/ToastState.mjs +224 -0
- package/dist/esm/ToastState.mjs.map +1 -0
- package/dist/esm/ToastState.native.js +230 -0
- package/dist/esm/ToastState.native.js.map +1 -0
- package/dist/esm/ToastViewport.js +250 -0
- package/dist/esm/ToastViewport.js.map +6 -0
- package/dist/esm/ToastViewport.mjs +241 -0
- package/dist/esm/ToastViewport.mjs.map +1 -0
- package/dist/esm/ToastViewport.native.js +276 -0
- package/dist/esm/ToastViewport.native.js.map +1 -0
- package/dist/esm/Toaster.js +160 -0
- package/dist/esm/Toaster.js.map +6 -0
- package/dist/esm/Toaster.mjs +185 -0
- package/dist/esm/Toaster.mjs.map +1 -0
- package/dist/esm/Toaster.native.js +242 -0
- package/dist/esm/Toaster.native.js.map +1 -0
- package/dist/esm/constants.js +6 -0
- package/dist/esm/constants.js.map +6 -0
- package/dist/esm/constants.mjs +4 -0
- package/dist/esm/constants.mjs.map +1 -0
- package/dist/esm/constants.native.js +4 -0
- package/dist/esm/constants.native.js.map +1 -0
- package/dist/esm/createNativeToast.js +28 -0
- package/dist/esm/createNativeToast.js.map +6 -0
- package/dist/esm/createNativeToast.mjs +27 -0
- package/dist/esm/createNativeToast.mjs.map +1 -0
- package/dist/esm/createNativeToast.native.js +20 -0
- package/dist/esm/createNativeToast.native.js.map +1 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +6 -0
- package/dist/esm/index.mjs +4 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/index.native.js +4 -0
- package/dist/esm/index.native.js.map +1 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/types.js.map +6 -0
- package/dist/esm/types.mjs +2 -0
- package/dist/esm/types.mjs.map +1 -0
- package/dist/esm/types.native.js +2 -0
- package/dist/esm/types.native.js.map +1 -0
- package/dist/esm/useDragGesture.js +76 -0
- package/dist/esm/useDragGesture.js.map +6 -0
- package/dist/esm/useDragGesture.mjs +95 -0
- package/dist/esm/useDragGesture.mjs.map +1 -0
- package/dist/esm/useDragGesture.native.js +109 -0
- package/dist/esm/useDragGesture.native.js.map +1 -0
- package/dist/jsx/Toast.js +107 -0
- package/dist/jsx/Toast.js.map +6 -0
- package/dist/jsx/Toast.mjs +131 -0
- package/dist/jsx/Toast.mjs.map +1 -0
- package/dist/jsx/Toast.native.js +174 -0
- package/dist/jsx/Toast.native.js.map +1 -0
- package/dist/jsx/ToastAnnounce.js +55 -0
- package/dist/jsx/ToastAnnounce.js.map +6 -0
- package/dist/jsx/ToastAnnounce.mjs +62 -0
- package/dist/jsx/ToastAnnounce.mjs.map +1 -0
- package/dist/jsx/ToastAnnounce.native.js +105 -0
- package/dist/jsx/ToastAnnounce.native.js.map +1 -0
- package/dist/jsx/ToastImperative.js +50 -0
- package/dist/jsx/ToastImperative.js.map +6 -0
- package/dist/jsx/ToastImperative.mjs +63 -0
- package/dist/jsx/ToastImperative.mjs.map +1 -0
- package/dist/jsx/ToastImperative.native.js +122 -0
- package/dist/jsx/ToastImperative.native.js.map +1 -0
- package/dist/jsx/ToastImpl.js +225 -0
- package/dist/jsx/ToastImpl.js.map +6 -0
- package/dist/jsx/ToastImpl.mjs +256 -0
- package/dist/jsx/ToastImpl.mjs.map +1 -0
- package/dist/jsx/ToastImpl.native.js +327 -0
- package/dist/jsx/ToastImpl.native.js.map +1 -0
- package/dist/jsx/ToastItem.js +339 -0
- package/dist/jsx/ToastItem.js.map +6 -0
- package/dist/jsx/ToastItem.mjs +432 -0
- package/dist/jsx/ToastItem.mjs.map +1 -0
- package/dist/jsx/ToastItem.native.js +547 -0
- package/dist/jsx/ToastItem.native.js.map +1 -0
- package/dist/jsx/ToastPortal.js +13 -0
- package/dist/jsx/ToastPortal.js.map +6 -0
- package/dist/jsx/ToastPortal.mjs +21 -0
- package/dist/jsx/ToastPortal.mjs.map +1 -0
- package/dist/jsx/ToastPortal.native.js +47 -0
- package/dist/jsx/ToastPortal.native.js.map +1 -0
- package/dist/jsx/ToastProvider.js +87 -0
- package/dist/jsx/ToastProvider.js.map +6 -0
- package/dist/jsx/ToastProvider.mjs +108 -0
- package/dist/jsx/ToastProvider.mjs.map +1 -0
- package/dist/jsx/ToastProvider.native.js +159 -0
- package/dist/jsx/ToastProvider.native.js.map +1 -0
- package/dist/jsx/ToastState.js +144 -0
- package/dist/jsx/ToastState.js.map +6 -0
- package/dist/jsx/ToastState.mjs +224 -0
- package/dist/jsx/ToastState.mjs.map +1 -0
- package/dist/jsx/ToastState.native.js +257 -0
- package/dist/jsx/ToastState.native.js.map +1 -0
- package/dist/jsx/ToastViewport.js +250 -0
- package/dist/jsx/ToastViewport.js.map +6 -0
- package/dist/jsx/ToastViewport.mjs +241 -0
- package/dist/jsx/ToastViewport.mjs.map +1 -0
- package/dist/jsx/ToastViewport.native.js +316 -0
- package/dist/jsx/ToastViewport.native.js.map +1 -0
- package/dist/jsx/Toaster.js +160 -0
- package/dist/jsx/Toaster.js.map +6 -0
- package/dist/jsx/Toaster.mjs +185 -0
- package/dist/jsx/Toaster.mjs.map +1 -0
- package/dist/jsx/Toaster.native.js +279 -0
- package/dist/jsx/Toaster.native.js.map +1 -0
- package/dist/jsx/constants.js +6 -0
- package/dist/jsx/constants.js.map +6 -0
- package/dist/jsx/constants.mjs +4 -0
- package/dist/jsx/constants.mjs.map +1 -0
- package/dist/jsx/constants.native.js +31 -0
- package/dist/jsx/constants.native.js.map +1 -0
- package/dist/jsx/createNativeToast.js +28 -0
- package/dist/jsx/createNativeToast.js.map +6 -0
- package/dist/jsx/createNativeToast.mjs +27 -0
- package/dist/jsx/createNativeToast.mjs.map +1 -0
- package/dist/jsx/createNativeToast.native.js +47 -0
- package/dist/jsx/createNativeToast.native.js.map +1 -0
- package/dist/jsx/index.js +7 -0
- package/dist/jsx/index.js.map +6 -0
- package/dist/jsx/index.mjs +4 -0
- package/dist/jsx/index.mjs.map +1 -0
- package/dist/jsx/index.native.js +31 -0
- package/dist/jsx/index.native.js.map +1 -0
- package/dist/jsx/types.js +1 -0
- package/dist/jsx/types.js.map +6 -0
- package/dist/jsx/types.mjs +2 -0
- package/dist/jsx/types.mjs.map +1 -0
- package/dist/jsx/types.native.js +19 -0
- package/dist/jsx/types.native.js.map +1 -0
- package/dist/jsx/useDragGesture.js +76 -0
- package/dist/jsx/useDragGesture.js.map +6 -0
- package/dist/jsx/useDragGesture.mjs +95 -0
- package/dist/jsx/useDragGesture.mjs.map +1 -0
- package/dist/jsx/useDragGesture.native.js +146 -0
- package/dist/jsx/useDragGesture.native.js.map +1 -0
- package/package.json +77 -0
- package/src/Toast.tsx +219 -0
- package/src/ToastAnnounce.tsx +102 -0
- package/src/ToastImperative.tsx +190 -0
- package/src/ToastImpl.tsx +503 -0
- package/src/ToastItem.tsx +694 -0
- package/src/ToastPortal.tsx +19 -0
- package/src/ToastProvider.tsx +197 -0
- package/src/ToastState.ts +397 -0
- package/src/ToastViewport.tsx +430 -0
- package/src/Toaster.tsx +445 -0
- package/src/constants.ts +2 -0
- package/src/createNativeToast.native.tsx +22 -0
- package/src/createNativeToast.tsx +48 -0
- package/src/index.ts +17 -0
- package/src/types.ts +71 -0
- package/src/useDragGesture.native.ts +199 -0
- package/src/useDragGesture.ts +218 -0
- package/types/Toast.d.ts +84 -0
- package/types/Toast.d.ts.map +1 -0
- package/types/ToastAnnounce.d.ts +18 -0
- package/types/ToastAnnounce.d.ts.map +1 -0
- package/types/ToastImperative.d.ts +95 -0
- package/types/ToastImperative.d.ts.map +1 -0
- package/types/ToastImpl.d.ts +109 -0
- package/types/ToastImpl.d.ts.map +1 -0
- package/types/ToastItem.d.ts +34 -0
- package/types/ToastItem.d.ts.map +1 -0
- package/types/ToastPortal.d.ts +8 -0
- package/types/ToastPortal.d.ts.map +1 -0
- package/types/ToastProvider.d.ts +92 -0
- package/types/ToastProvider.d.ts.map +1 -0
- package/types/ToastState.d.ts +177 -0
- package/types/ToastState.d.ts.map +1 -0
- package/types/ToastViewport.d.ts +75 -0
- package/types/ToastViewport.d.ts.map +1 -0
- package/types/Toaster.d.ts +120 -0
- package/types/Toaster.d.ts.map +1 -0
- package/types/constants.d.ts +3 -0
- package/types/constants.d.ts.map +1 -0
- package/types/createNativeToast.d.ts +4 -0
- package/types/createNativeToast.d.ts.map +1 -0
- package/types/createNativeToast.native.d.ts +4 -0
- package/types/createNativeToast.native.d.ts.map +1 -0
- package/types/index.d.ts +7 -0
- package/types/index.d.ts.map +1 -0
- package/types/types.d.ts +61 -0
- package/types/types.d.ts.map +1 -0
- package/types/useDragGesture.d.ts +32 -0
- package/types/useDragGesture.d.ts.map +1 -0
- package/types/useDragGesture.native.d.ts +26 -0
- package/types/useDragGesture.native.d.ts.map +1 -0
package/src/Toaster.tsx
ADDED
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
import { AnimatePresence } from '@tamagui/animate-presence'
|
|
2
|
+
import { isWeb } from '@tamagui/constants'
|
|
3
|
+
import type { TamaguiElement } from '@tamagui/core'
|
|
4
|
+
import { Theme, View, styled, useThemeName } from '@tamagui/core'
|
|
5
|
+
import { Portal } from '@tamagui/portal'
|
|
6
|
+
import * as React from 'react'
|
|
7
|
+
import { ToastItem } from './ToastItem'
|
|
8
|
+
import type { SwipeDirection } from './ToastProvider'
|
|
9
|
+
import type { ExternalToast, ToastT, ToastToDismiss } from './ToastState'
|
|
10
|
+
import { ToastState } from './ToastState'
|
|
11
|
+
import type { BurntToastOptions } from './types'
|
|
12
|
+
|
|
13
|
+
// defaults
|
|
14
|
+
const VISIBLE_TOASTS_AMOUNT = 4
|
|
15
|
+
const VIEWPORT_OFFSET = 24
|
|
16
|
+
const TOAST_GAP = 14
|
|
17
|
+
const TOAST_LIFETIME = 4000
|
|
18
|
+
|
|
19
|
+
export type ToasterPosition =
|
|
20
|
+
| 'top-left'
|
|
21
|
+
| 'top-center'
|
|
22
|
+
| 'top-right'
|
|
23
|
+
| 'bottom-left'
|
|
24
|
+
| 'bottom-center'
|
|
25
|
+
| 'bottom-right'
|
|
26
|
+
|
|
27
|
+
export interface HeightT {
|
|
28
|
+
toastId: string | number
|
|
29
|
+
height: number
|
|
30
|
+
position?: ToasterPosition
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const TOAST_WIDTH = 356
|
|
34
|
+
|
|
35
|
+
const ToasterFrame = styled(View, {
|
|
36
|
+
name: 'Toaster',
|
|
37
|
+
|
|
38
|
+
variants: {
|
|
39
|
+
unstyled: {
|
|
40
|
+
false: {
|
|
41
|
+
position: 'fixed',
|
|
42
|
+
zIndex: 100000,
|
|
43
|
+
pointerEvents: 'box-none',
|
|
44
|
+
maxWidth: '100%',
|
|
45
|
+
// need min-height to contain absolutely positioned toasts
|
|
46
|
+
// toasts will overflow upward/downward from their anchor position
|
|
47
|
+
minHeight: 1,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
} as const,
|
|
51
|
+
|
|
52
|
+
defaultVariants: {
|
|
53
|
+
unstyled: process.env.TAMAGUI_HEADLESS === '1',
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
export interface ToasterProps {
|
|
58
|
+
/**
|
|
59
|
+
* Position of the toasts on screen
|
|
60
|
+
* @default 'bottom-right'
|
|
61
|
+
*/
|
|
62
|
+
position?: ToasterPosition
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Width of toast container in pixels
|
|
66
|
+
* @default 356
|
|
67
|
+
*/
|
|
68
|
+
width?: number
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Expand toasts on hover to show all
|
|
72
|
+
* @default false
|
|
73
|
+
*/
|
|
74
|
+
expand?: boolean
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Number of toasts visible at once
|
|
78
|
+
* @default 4
|
|
79
|
+
*/
|
|
80
|
+
visibleToasts?: number
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Gap between toasts in pixels
|
|
84
|
+
* @default 14
|
|
85
|
+
*/
|
|
86
|
+
gap?: number
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Default duration for toasts in ms
|
|
90
|
+
* @default 4000
|
|
91
|
+
*/
|
|
92
|
+
duration?: number
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Offset from screen edge in pixels
|
|
96
|
+
* @default 24
|
|
97
|
+
*/
|
|
98
|
+
offset?: number | { top?: number; right?: number; bottom?: number; left?: number }
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Hotkey to focus toast viewport
|
|
102
|
+
* @default ['altKey', 'KeyT']
|
|
103
|
+
*/
|
|
104
|
+
hotkey?: string[]
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Direction(s) toasts can be swiped to dismiss
|
|
108
|
+
* @default 'right'
|
|
109
|
+
*/
|
|
110
|
+
swipeDirection?: SwipeDirection
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Distance in pixels swipe must pass to dismiss
|
|
114
|
+
* @default 50
|
|
115
|
+
*/
|
|
116
|
+
swipeThreshold?: number
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Show close button on toasts
|
|
120
|
+
* @default false
|
|
121
|
+
*/
|
|
122
|
+
closeButton?: boolean
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Theme for toasts (auto-detected if not set)
|
|
126
|
+
*/
|
|
127
|
+
theme?: 'light' | 'dark' | 'system'
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Custom icons for toast types
|
|
131
|
+
*/
|
|
132
|
+
icons?: {
|
|
133
|
+
success?: React.ReactNode
|
|
134
|
+
error?: React.ReactNode
|
|
135
|
+
warning?: React.ReactNode
|
|
136
|
+
info?: React.ReactNode
|
|
137
|
+
loading?: React.ReactNode
|
|
138
|
+
close?: React.ReactNode
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Default toast options
|
|
143
|
+
*/
|
|
144
|
+
toastOptions?: ExternalToast
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Container aria label for screen readers
|
|
148
|
+
* @default 'Notifications'
|
|
149
|
+
*/
|
|
150
|
+
containerAriaLabel?: string
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Disable native toast on mobile (uses burnt package)
|
|
154
|
+
* @default false
|
|
155
|
+
*/
|
|
156
|
+
disableNative?: boolean
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Options for burnt native toasts on mobile
|
|
160
|
+
*/
|
|
161
|
+
burntOptions?: Omit<BurntToastOptions, 'title' | 'message' | 'duration'>
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Options for web Notification API
|
|
165
|
+
*/
|
|
166
|
+
notificationOptions?: NotificationOptions
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Custom className for the container
|
|
170
|
+
*/
|
|
171
|
+
className?: string
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Custom style for the container
|
|
175
|
+
*/
|
|
176
|
+
style?: React.CSSProperties
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export const Toaster = React.forwardRef<TamaguiElement, ToasterProps>(
|
|
180
|
+
function Toaster(props, _ref) {
|
|
181
|
+
const {
|
|
182
|
+
position = 'bottom-right',
|
|
183
|
+
width = TOAST_WIDTH,
|
|
184
|
+
expand = false,
|
|
185
|
+
visibleToasts = VISIBLE_TOASTS_AMOUNT,
|
|
186
|
+
gap = TOAST_GAP,
|
|
187
|
+
duration = TOAST_LIFETIME,
|
|
188
|
+
offset = VIEWPORT_OFFSET,
|
|
189
|
+
hotkey = ['altKey', 'KeyT'],
|
|
190
|
+
swipeDirection = 'right',
|
|
191
|
+
swipeThreshold = 50,
|
|
192
|
+
closeButton = false,
|
|
193
|
+
theme: themeProp,
|
|
194
|
+
icons,
|
|
195
|
+
toastOptions,
|
|
196
|
+
containerAriaLabel = 'Notifications',
|
|
197
|
+
disableNative = false,
|
|
198
|
+
burntOptions,
|
|
199
|
+
notificationOptions,
|
|
200
|
+
className,
|
|
201
|
+
style,
|
|
202
|
+
} = props
|
|
203
|
+
|
|
204
|
+
const [toasts, setToasts] = React.useState<ToastT[]>([])
|
|
205
|
+
const [heights, setHeights] = React.useState<HeightT[]>([])
|
|
206
|
+
const [expanded, setExpanded] = React.useState(false)
|
|
207
|
+
const [interacting, setInteracting] = React.useState(false)
|
|
208
|
+
|
|
209
|
+
const listRef = React.useRef<TamaguiElement>(null)
|
|
210
|
+
const lastFocusedElementRef = React.useRef<HTMLElement | null>(null)
|
|
211
|
+
const isFocusWithinRef = React.useRef(false)
|
|
212
|
+
|
|
213
|
+
// subscribe to toast state changes
|
|
214
|
+
React.useEffect(() => {
|
|
215
|
+
return ToastState.subscribe((toast) => {
|
|
216
|
+
if ((toast as ToastToDismiss).dismiss) {
|
|
217
|
+
// mark toast for deletion animation
|
|
218
|
+
setToasts((toasts) =>
|
|
219
|
+
toasts.map((t) => (t.id === toast.id ? { ...t, delete: true } : t))
|
|
220
|
+
)
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// add or update toast
|
|
225
|
+
setToasts((toasts) => {
|
|
226
|
+
const indexOfExistingToast = toasts.findIndex((t) => t.id === toast.id)
|
|
227
|
+
|
|
228
|
+
if (indexOfExistingToast !== -1) {
|
|
229
|
+
// update existing
|
|
230
|
+
return [
|
|
231
|
+
...toasts.slice(0, indexOfExistingToast),
|
|
232
|
+
{ ...toasts[indexOfExistingToast], ...toast },
|
|
233
|
+
...toasts.slice(indexOfExistingToast + 1),
|
|
234
|
+
]
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// add new toast at the beginning
|
|
238
|
+
return [toast as ToastT, ...toasts]
|
|
239
|
+
})
|
|
240
|
+
})
|
|
241
|
+
}, [])
|
|
242
|
+
|
|
243
|
+
// collapse expanded view when only 1 toast remains
|
|
244
|
+
React.useEffect(() => {
|
|
245
|
+
if (toasts.length <= 1) {
|
|
246
|
+
setExpanded(false)
|
|
247
|
+
}
|
|
248
|
+
}, [toasts.length])
|
|
249
|
+
|
|
250
|
+
// keyboard hotkey handler
|
|
251
|
+
React.useEffect(() => {
|
|
252
|
+
if (!isWeb) return
|
|
253
|
+
|
|
254
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
255
|
+
const isHotkeyPressed =
|
|
256
|
+
hotkey.length > 0 &&
|
|
257
|
+
hotkey.every((key) => (event as any)[key] || event.code === key)
|
|
258
|
+
|
|
259
|
+
if (isHotkeyPressed) {
|
|
260
|
+
setExpanded(true)
|
|
261
|
+
;(listRef.current as HTMLElement)?.focus()
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (
|
|
265
|
+
event.code === 'Escape' &&
|
|
266
|
+
(document.activeElement === listRef.current ||
|
|
267
|
+
(listRef.current as HTMLElement)?.contains(document.activeElement))
|
|
268
|
+
) {
|
|
269
|
+
setExpanded(false)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
document.addEventListener('keydown', handleKeyDown)
|
|
274
|
+
return () => document.removeEventListener('keydown', handleKeyDown)
|
|
275
|
+
}, [hotkey])
|
|
276
|
+
|
|
277
|
+
// restore focus when toaster unmounts
|
|
278
|
+
React.useEffect(() => {
|
|
279
|
+
if (!isWeb || !listRef.current) return
|
|
280
|
+
|
|
281
|
+
return () => {
|
|
282
|
+
if (lastFocusedElementRef.current) {
|
|
283
|
+
lastFocusedElementRef.current.focus({ preventScroll: true })
|
|
284
|
+
lastFocusedElementRef.current = null
|
|
285
|
+
isFocusWithinRef.current = false
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}, [])
|
|
289
|
+
|
|
290
|
+
const removeToast = React.useCallback((toastToRemove: ToastT) => {
|
|
291
|
+
setToasts((toasts) => {
|
|
292
|
+
if (!toasts.find((toast) => toast.id === toastToRemove.id)?.delete) {
|
|
293
|
+
ToastState.dismiss(toastToRemove.id)
|
|
294
|
+
}
|
|
295
|
+
return toasts.filter(({ id }) => id !== toastToRemove.id)
|
|
296
|
+
})
|
|
297
|
+
}, [])
|
|
298
|
+
|
|
299
|
+
// parse position
|
|
300
|
+
const [yPosition, xPosition] = position.split('-') as [
|
|
301
|
+
'top' | 'bottom',
|
|
302
|
+
'left' | 'center' | 'right',
|
|
303
|
+
]
|
|
304
|
+
|
|
305
|
+
// calculate offset styles
|
|
306
|
+
const offsetStyles = React.useMemo(() => {
|
|
307
|
+
const styles: React.CSSProperties = {}
|
|
308
|
+
|
|
309
|
+
const defaultOffset = typeof offset === 'number' ? offset : VIEWPORT_OFFSET
|
|
310
|
+
const offsetObj =
|
|
311
|
+
typeof offset === 'object'
|
|
312
|
+
? offset
|
|
313
|
+
: {
|
|
314
|
+
top: defaultOffset,
|
|
315
|
+
right: defaultOffset,
|
|
316
|
+
bottom: defaultOffset,
|
|
317
|
+
left: defaultOffset,
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (yPosition === 'top') {
|
|
321
|
+
styles.top = offsetObj.top ?? defaultOffset
|
|
322
|
+
} else {
|
|
323
|
+
styles.bottom = offsetObj.bottom ?? defaultOffset
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (xPosition === 'left') {
|
|
327
|
+
styles.left = offsetObj.left ?? defaultOffset
|
|
328
|
+
} else if (xPosition === 'right') {
|
|
329
|
+
styles.right = offsetObj.right ?? defaultOffset
|
|
330
|
+
} else {
|
|
331
|
+
// center
|
|
332
|
+
styles.left = '50%'
|
|
333
|
+
styles.transform = 'translateX(-50%)'
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return styles
|
|
337
|
+
}, [offset, yPosition, xPosition])
|
|
338
|
+
|
|
339
|
+
// get current theme
|
|
340
|
+
const currentTheme = useThemeName()
|
|
341
|
+
const resolvedTheme =
|
|
342
|
+
themeProp === 'system' || !themeProp
|
|
343
|
+
? currentTheme?.includes('dark')
|
|
344
|
+
? 'dark'
|
|
345
|
+
: 'light'
|
|
346
|
+
: themeProp
|
|
347
|
+
|
|
348
|
+
const hotkeyLabel = hotkey.join('+').replace(/Key/g, '').replace(/Digit/g, '')
|
|
349
|
+
|
|
350
|
+
if (toasts.length === 0) {
|
|
351
|
+
return null
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const content = (
|
|
355
|
+
<ToasterFrame
|
|
356
|
+
ref={listRef}
|
|
357
|
+
width={width}
|
|
358
|
+
aria-label={`${containerAriaLabel} ${hotkeyLabel}`}
|
|
359
|
+
tabIndex={-1}
|
|
360
|
+
aria-live="polite"
|
|
361
|
+
aria-relevant="additions text"
|
|
362
|
+
aria-atomic={false}
|
|
363
|
+
style={{ ...offsetStyles, ...style }}
|
|
364
|
+
className={className}
|
|
365
|
+
data-y-position={yPosition}
|
|
366
|
+
data-x-position={xPosition}
|
|
367
|
+
onMouseEnter={() => setExpanded(true)}
|
|
368
|
+
onMouseMove={() => setExpanded(true)}
|
|
369
|
+
onMouseLeave={() => {
|
|
370
|
+
if (!interacting) {
|
|
371
|
+
setExpanded(false)
|
|
372
|
+
}
|
|
373
|
+
}}
|
|
374
|
+
onPointerDown={() => setInteracting(true)}
|
|
375
|
+
onPointerUp={() => setInteracting(false)}
|
|
376
|
+
{...(isWeb && {
|
|
377
|
+
onBlur: (event: React.FocusEvent) => {
|
|
378
|
+
if (
|
|
379
|
+
isFocusWithinRef.current &&
|
|
380
|
+
!(event.currentTarget as HTMLElement).contains(
|
|
381
|
+
event.relatedTarget as HTMLElement
|
|
382
|
+
)
|
|
383
|
+
) {
|
|
384
|
+
isFocusWithinRef.current = false
|
|
385
|
+
if (lastFocusedElementRef.current) {
|
|
386
|
+
lastFocusedElementRef.current.focus({ preventScroll: true })
|
|
387
|
+
lastFocusedElementRef.current = null
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
onFocus: (event: React.FocusEvent) => {
|
|
392
|
+
if (!isFocusWithinRef.current) {
|
|
393
|
+
isFocusWithinRef.current = true
|
|
394
|
+
lastFocusedElementRef.current = event.relatedTarget as HTMLElement
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
})}
|
|
398
|
+
>
|
|
399
|
+
<AnimatePresence>
|
|
400
|
+
{toasts.map((toast, index) => {
|
|
401
|
+
const isVisible = index < visibleToasts
|
|
402
|
+
const isFront = index === 0
|
|
403
|
+
|
|
404
|
+
return (
|
|
405
|
+
<ToastItem
|
|
406
|
+
key={toast.id}
|
|
407
|
+
toast={toast}
|
|
408
|
+
index={index}
|
|
409
|
+
expanded={expanded || expand}
|
|
410
|
+
interacting={interacting}
|
|
411
|
+
position={position}
|
|
412
|
+
visibleToasts={visibleToasts}
|
|
413
|
+
removeToast={removeToast}
|
|
414
|
+
heights={heights}
|
|
415
|
+
setHeights={setHeights}
|
|
416
|
+
duration={toast.duration ?? toastOptions?.duration ?? duration}
|
|
417
|
+
gap={gap}
|
|
418
|
+
swipeDirection={swipeDirection}
|
|
419
|
+
swipeThreshold={swipeThreshold}
|
|
420
|
+
closeButton={toast.closeButton ?? closeButton}
|
|
421
|
+
icons={icons}
|
|
422
|
+
disableNative={disableNative}
|
|
423
|
+
burntOptions={burntOptions}
|
|
424
|
+
notificationOptions={notificationOptions}
|
|
425
|
+
/>
|
|
426
|
+
)
|
|
427
|
+
})}
|
|
428
|
+
</AnimatePresence>
|
|
429
|
+
</ToasterFrame>
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
// on web, render in a portal
|
|
433
|
+
if (isWeb) {
|
|
434
|
+
return (
|
|
435
|
+
<Portal>
|
|
436
|
+
<Theme name={resolvedTheme as any}>{content}</Theme>
|
|
437
|
+
</Portal>
|
|
438
|
+
)
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return <Theme name={resolvedTheme as any}>{content}</Theme>
|
|
442
|
+
}
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
Toaster.displayName = 'Toaster'
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CreateNativeToastsFn, HideNativeToastsFn } from './types'
|
|
2
|
+
|
|
3
|
+
export const createNativeToast: CreateNativeToastsFn = (
|
|
4
|
+
title,
|
|
5
|
+
{ message, duration, burntOptions }
|
|
6
|
+
) => {
|
|
7
|
+
// import inline to allow lazy usage of native dependecy:
|
|
8
|
+
const Burnt = require('burnt') as typeof import('burnt')
|
|
9
|
+
|
|
10
|
+
Burnt.toast({
|
|
11
|
+
title,
|
|
12
|
+
message,
|
|
13
|
+
duration: duration ? duration / 1000 : undefined,
|
|
14
|
+
...burntOptions,
|
|
15
|
+
})
|
|
16
|
+
return true
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const hideNativeToast: HideNativeToastsFn = () => {
|
|
20
|
+
const Burnt = require('burnt') as typeof import('burnt')
|
|
21
|
+
Burnt.dismissAllAlerts()
|
|
22
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { CreateNativeToastsFn, HideNativeToastsFn } from './types'
|
|
2
|
+
|
|
3
|
+
export const createNativeToast: CreateNativeToastsFn = (
|
|
4
|
+
title,
|
|
5
|
+
{ message, notificationOptions }
|
|
6
|
+
) => {
|
|
7
|
+
if (!('Notification' in window)) {
|
|
8
|
+
console.error('This browser does not support notifications')
|
|
9
|
+
return false
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (Notification.permission === 'denied') return false
|
|
13
|
+
const showNotification = () => {
|
|
14
|
+
const notification = new Notification(title, {
|
|
15
|
+
body: message,
|
|
16
|
+
...notificationOptions,
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
return notification
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (Notification.permission === 'granted') {
|
|
23
|
+
const notification = showNotification()
|
|
24
|
+
return {
|
|
25
|
+
nativeToastRef: notification,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
Notification.requestPermission().then((permission) => {
|
|
29
|
+
if (permission === 'granted') {
|
|
30
|
+
const notification = showNotification()
|
|
31
|
+
return {
|
|
32
|
+
nativeToastRef: notification,
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
return true
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const hideNativeToast: HideNativeToastsFn = (ref) => {
|
|
40
|
+
if (!('Notification' in window)) {
|
|
41
|
+
console.error('This browser does not support notifications')
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (ref) {
|
|
46
|
+
ref.close()
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Toast v2 API
|
|
2
|
+
export { toast } from './ToastState'
|
|
3
|
+
export type {
|
|
4
|
+
ToastT,
|
|
5
|
+
ToastType,
|
|
6
|
+
ToastToDismiss,
|
|
7
|
+
ExternalToast,
|
|
8
|
+
PromiseT,
|
|
9
|
+
PromiseData,
|
|
10
|
+
ToastAction,
|
|
11
|
+
} from './ToastState'
|
|
12
|
+
|
|
13
|
+
export { Toaster } from './Toaster'
|
|
14
|
+
export type { ToasterProps, ToasterPosition, HeightT } from './Toaster'
|
|
15
|
+
|
|
16
|
+
export type { SwipeDirection } from './ToastProvider'
|
|
17
|
+
export type { BurntToastOptions, CreateNativeToastOptions, NativeToastRef } from './types'
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// from `burnt`
|
|
2
|
+
type BurntLayout = {
|
|
3
|
+
iconSize?: {
|
|
4
|
+
width: number
|
|
5
|
+
height: number
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// from `burnt`
|
|
10
|
+
export type BurntToastOptions = {
|
|
11
|
+
title: string
|
|
12
|
+
message?: string
|
|
13
|
+
/**
|
|
14
|
+
* Defaults to `done`.
|
|
15
|
+
*/
|
|
16
|
+
preset?: 'done' | 'error' | 'none'
|
|
17
|
+
/**
|
|
18
|
+
* Duration in seconds.
|
|
19
|
+
*/
|
|
20
|
+
duration?: number
|
|
21
|
+
haptic?: 'success' | 'warning' | 'error' | 'none'
|
|
22
|
+
/**
|
|
23
|
+
* Defaults to `true`.
|
|
24
|
+
*/
|
|
25
|
+
shouldDismissByDrag?: boolean
|
|
26
|
+
/**
|
|
27
|
+
* Change the presentation side.
|
|
28
|
+
* @platform ios
|
|
29
|
+
*/
|
|
30
|
+
from?: 'top' | 'bottom'
|
|
31
|
+
layout?: BurntLayout
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface CreateNativeToastOptions {
|
|
35
|
+
/**
|
|
36
|
+
* Body of the toast
|
|
37
|
+
*/
|
|
38
|
+
message?: BurntToastOptions['message']
|
|
39
|
+
/**
|
|
40
|
+
* Duration of toast in ms
|
|
41
|
+
*
|
|
42
|
+
* @example 1000
|
|
43
|
+
*/
|
|
44
|
+
duration?: BurntToastOptions['duration']
|
|
45
|
+
/**
|
|
46
|
+
* Options for the burnt package if you're using native toasts on mobile
|
|
47
|
+
*/
|
|
48
|
+
burntOptions?: Omit<BurntToastOptions, 'title' | 'message' | 'duration'>
|
|
49
|
+
/**
|
|
50
|
+
* Options for the notification API if you're using native toasts on web
|
|
51
|
+
*/
|
|
52
|
+
notificationOptions?: NotificationOptions
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface NativeToastRef {
|
|
56
|
+
/**
|
|
57
|
+
* Used to close web notifications
|
|
58
|
+
*/
|
|
59
|
+
close: () => void
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type CreateNativeToastsFn = (
|
|
63
|
+
title: string,
|
|
64
|
+
options: CreateNativeToastOptions
|
|
65
|
+
) =>
|
|
66
|
+
| {
|
|
67
|
+
nativeToastRef?: NativeToastRef
|
|
68
|
+
}
|
|
69
|
+
| boolean
|
|
70
|
+
|
|
71
|
+
export type HideNativeToastsFn = (ref?: NativeToastRef) => void
|