react-native-bread 0.1.0 → 0.1.1
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/README.md +101 -84
- package/lib/commonjs/toast-provider.js +17 -22
- package/lib/commonjs/toast-provider.js.map +1 -1
- package/lib/commonjs/toast.js +118 -82
- package/lib/commonjs/toast.js.map +1 -1
- package/lib/module/toast-provider.js +18 -23
- package/lib/module/toast-provider.js.map +1 -1
- package/lib/module/toast.js +119 -83
- package/lib/module/toast.js.map +1 -1
- package/lib/typescript/toast-provider.d.ts +13 -12
- package/lib/typescript/toast-provider.d.ts.map +1 -1
- package/lib/typescript/toast.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/toast-provider.tsx +18 -22
- package/src/toast.tsx +191 -142
package/README.md
CHANGED
|
@@ -1,55 +1,91 @@
|
|
|
1
|
-
# 🍞
|
|
1
|
+
# React Native Bread 🍞
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
```tsx
|
|
6
|
-
toast.success('Saved!'); // That's it. No hooks, no context.
|
|
7
|
-
```
|
|
3
|
+
An opinionated toast component for React Native. Inspired by @emilkowalski's Sonner, built for mobile with smooth 60fps animations and intuitive swipe gestures.
|
|
8
4
|
|
|
5
|
+
https://github.com/user-attachments/assets/8a862dba-422c-4573-9f12-0a36cf6efe49
|
|
9
6
|
|
|
7
|
+
## Features
|
|
10
8
|
|
|
11
|
-
https://
|
|
9
|
+
- Clean, imperative API inspired by [Sonner](https://sonner.emilkowal.ski/)
|
|
10
|
+
- Zero setup - add one component, start toasting. No hooks, no providers
|
|
11
|
+
- Built for mobile with smooth 60fps animations via Reanimated 3
|
|
12
|
+
- Natural swipe gestures that feel native to the platform
|
|
13
|
+
- Multiple toast types: `success`, `error`, `info`, and `promise`
|
|
14
|
+
- Promise handling with automatic loading → success/error states
|
|
15
|
+
- Toast stacking with configurable limits
|
|
16
|
+
- Position toasts at top or bottom of screen
|
|
17
|
+
- Completely customizable - colors, icons, styles, animations
|
|
18
|
+
- Full Expo compatibility
|
|
19
|
+
- Imperative API works anywhere - components, utilities, event handlers
|
|
12
20
|
|
|
13
21
|
|
|
14
22
|
|
|
15
23
|
## Installation
|
|
16
24
|
|
|
17
|
-
```
|
|
25
|
+
```sh
|
|
18
26
|
bun add react-native-bread
|
|
19
|
-
# or any package manager
|
|
20
27
|
```
|
|
21
28
|
|
|
22
|
-
|
|
29
|
+
#### Requirements
|
|
23
30
|
|
|
24
|
-
|
|
31
|
+
To use this package, **you also need to install its peer dependencies**. Check out their documentation for more information:
|
|
25
32
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
33
|
+
- [React Native Reanimated](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started)
|
|
34
|
+
- [React Native Gesture Handler](https://docs.swmansion.com/react-native-gesture-handler/docs/)
|
|
35
|
+
- [React Native Safe Area Context](https://docs.expo.dev/versions/latest/sdk/safe-area-context/)
|
|
36
|
+
- [React Native SVG](https://github.com/software-mansion/react-native-svg)
|
|
37
|
+
- [React Native Worklets](https://github.com/margelo/react-native-worklets-core)
|
|
29
38
|
|
|
30
39
|
|
|
31
|
-
##
|
|
40
|
+
## Usage
|
|
32
41
|
|
|
33
|
-
|
|
42
|
+
### In your App.tsx/entry point
|
|
34
43
|
|
|
35
44
|
```tsx
|
|
36
45
|
import { BreadLoaf } from 'react-native-bread';
|
|
37
46
|
|
|
38
|
-
|
|
47
|
+
function App() {
|
|
39
48
|
return (
|
|
40
|
-
<
|
|
41
|
-
<
|
|
42
|
-
|
|
49
|
+
<View>
|
|
50
|
+
<NavigationContainer>...</NavigationContainer>
|
|
51
|
+
<BreadLoaf />
|
|
52
|
+
</View>
|
|
43
53
|
);
|
|
44
54
|
}
|
|
45
55
|
```
|
|
46
56
|
|
|
47
|
-
|
|
57
|
+
### Expo Router
|
|
58
|
+
|
|
59
|
+
When using Expo Router, place the `BreadLoaf` component in your root layout file (`app/_layout.tsx`):
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
import { BreadLoaf } from 'react-native-bread';
|
|
63
|
+
import { Stack } from 'expo-router';
|
|
64
|
+
|
|
65
|
+
export default function RootLayout() {
|
|
66
|
+
return (
|
|
67
|
+
<>
|
|
68
|
+
<Stack>
|
|
69
|
+
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
|
70
|
+
<Stack.Screen name="+not-found" />
|
|
71
|
+
</Stack>
|
|
72
|
+
<BreadLoaf />
|
|
73
|
+
</>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
This ensures the toasts will be displayed across all screens in your app.
|
|
79
|
+
|
|
80
|
+
### Show a toast
|
|
48
81
|
|
|
49
82
|
```tsx
|
|
50
83
|
import { toast } from 'react-native-bread';
|
|
51
84
|
|
|
52
|
-
// Basic
|
|
85
|
+
// Basic usage
|
|
86
|
+
toast.success('Saved!');
|
|
87
|
+
|
|
88
|
+
// With description
|
|
53
89
|
toast.success('Saved!', 'Your changes have been saved');
|
|
54
90
|
toast.error('Error', 'Something went wrong');
|
|
55
91
|
toast.info('Tip', 'Swipe to dismiss');
|
|
@@ -62,87 +98,68 @@ toast.promise(fetchData(), {
|
|
|
62
98
|
});
|
|
63
99
|
```
|
|
64
100
|
|
|
65
|
-
##
|
|
66
|
-
|
|
67
|
-
### Toast Methods
|
|
68
|
-
|
|
69
|
-
| Method | Description |
|
|
70
|
-
|--------|-------------|
|
|
71
|
-
| `toast.success(title, description?)` | Green checkmark toast |
|
|
72
|
-
| `toast.error(title, description?)` | Red X toast |
|
|
73
|
-
| `toast.info(title, description?)` | Yellow info toast |
|
|
74
|
-
| `toast.promise(promise, messages)` | Loading → success/error toast |
|
|
75
|
-
| `toast.dismiss(id)` | Dismiss a specific toast |
|
|
76
|
-
| `toast.dismissAll()` | Dismiss all toasts |
|
|
101
|
+
## Customization
|
|
77
102
|
|
|
78
103
|
### Per-Toast Options
|
|
79
104
|
|
|
80
|
-
|
|
105
|
+
Pass an options object as the second argument to customize individual toasts:
|
|
81
106
|
|
|
82
107
|
```tsx
|
|
83
108
|
toast.success('Saved!', {
|
|
84
109
|
description: 'Your changes have been saved',
|
|
85
110
|
duration: 5000,
|
|
86
111
|
icon: <CustomIcon />,
|
|
112
|
+
style: { backgroundColor: '#fff' },
|
|
113
|
+
dismissible: true,
|
|
114
|
+
showCloseButton: true,
|
|
87
115
|
});
|
|
88
116
|
```
|
|
89
117
|
|
|
90
|
-
|
|
91
|
-
|--------|------|-------------|
|
|
92
|
-
| `description` | `string` | Toast description text |
|
|
93
|
-
| `duration` | `number` | Display time in ms |
|
|
94
|
-
| `icon` | `ReactNode \| (props) => ReactNode` | Custom icon component |
|
|
95
|
-
| `style` | `ViewStyle` | Toast container style overrides |
|
|
96
|
-
| `titleStyle` | `TextStyle` | Title text style overrides |
|
|
97
|
-
| `descriptionStyle` | `TextStyle` | Description text style overrides |
|
|
98
|
-
| `dismissible` | `boolean` | Enable/disable swipe to dismiss |
|
|
99
|
-
| `showCloseButton` | `boolean` | Show/hide the X button |
|
|
100
|
-
|
|
101
|
-
### BreadLoaf Config
|
|
118
|
+
### Global Configuration
|
|
102
119
|
|
|
103
|
-
Customize all toasts globally via the `config` prop
|
|
120
|
+
Customize all toasts globally via the `config` prop on `<BreadLoaf />`:
|
|
104
121
|
|
|
105
122
|
```tsx
|
|
106
|
-
<BreadLoaf
|
|
107
|
-
|
|
108
|
-
|
|
123
|
+
<BreadLoaf
|
|
124
|
+
config={{
|
|
125
|
+
position: 'bottom',
|
|
126
|
+
stacking: true,
|
|
127
|
+
maxStack: 3,
|
|
128
|
+
defaultDuration: 4000,
|
|
129
|
+
colors: {
|
|
130
|
+
success: { accent: '#22c55e', background: '#f0fdf4' },
|
|
131
|
+
error: { accent: '#ef4444', background: '#fef2f2' },
|
|
132
|
+
}
|
|
133
|
+
}}
|
|
134
|
+
/>
|
|
109
135
|
```
|
|
110
136
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
| `toastStyle` | `ViewStyle` | — | Global toast container styles |
|
|
123
|
-
| `titleStyle` | `TextStyle` | — | Global title text styles |
|
|
124
|
-
| `descriptionStyle` | `TextStyle` | — | Global description text styles |
|
|
125
|
-
|
|
126
|
-
#### Colors
|
|
127
|
-
|
|
128
|
-
Each toast type (`success`, `error`, `info`, `loading`) accepts:
|
|
129
|
-
|
|
130
|
-
| Property | Description |
|
|
131
|
-
|----------|-------------|
|
|
132
|
-
| `accent` | Icon and title color |
|
|
133
|
-
| `background` | Toast background color |
|
|
137
|
+
Available options include:
|
|
138
|
+
- **position**: `'top' | 'bottom'` - Where toasts appear
|
|
139
|
+
- **offset**: Extra spacing from screen edge
|
|
140
|
+
- **stacking**: Show multiple toasts stacked
|
|
141
|
+
- **maxStack**: Max visible toasts when stacking
|
|
142
|
+
- **dismissible**: Allow swipe to dismiss
|
|
143
|
+
- **showCloseButton**: Show X button
|
|
144
|
+
- **defaultDuration**: Default display time in ms
|
|
145
|
+
- **colors**: Custom colors per toast type
|
|
146
|
+
- **icons**: Custom icons per toast type
|
|
147
|
+
- **toastStyle**, **titleStyle**, **descriptionStyle**: Global style overrides
|
|
134
148
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
149
|
+
## API Reference
|
|
150
|
+
|
|
151
|
+
| Method | Description |
|
|
152
|
+
|--------|-------------|
|
|
153
|
+
| `toast.success(title, description?)` | Show success toast |
|
|
154
|
+
| `toast.error(title, description?)` | Show error toast |
|
|
155
|
+
| `toast.info(title, description?)` | Show info toast |
|
|
156
|
+
| `toast.promise(promise, messages)` | Show loading → success/error toast |
|
|
157
|
+
| `toast.dismiss(id)` | Dismiss a specific toast |
|
|
158
|
+
| `toast.dismissAll()` | Dismiss all toasts |
|
|
141
159
|
|
|
142
|
-
## Known
|
|
160
|
+
## Known Issues
|
|
143
161
|
|
|
144
|
-
|
|
162
|
+
**Modal Overlays**: Toasts may render behind React Native's `<Modal>` component since modals are mounted at the native layer.
|
|
145
163
|
|
|
146
|
-
|
|
164
|
+
**Solution**: Use absolute positioning within your component tree instead of `<Modal>` for better toast visibility.
|
|
147
165
|
|
|
148
|
-
**Workaround**: Use a "contained" modal approach — render your modal content inside your regular component tree with absolute positioning, rather than using React Native's `<Modal>` component. This way toasts will appear on top as expected.
|
|
@@ -10,17 +10,22 @@ var _toast = require("./toast.js");
|
|
|
10
10
|
var _toastStore = require("./toast-store.js");
|
|
11
11
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
12
12
|
/**
|
|
13
|
-
* Toast
|
|
14
|
-
*
|
|
13
|
+
* Toast component that enables toast notifications in your app.
|
|
14
|
+
* Add `<BreadLoaf />` to your root layout to start showing toasts.
|
|
15
15
|
*
|
|
16
16
|
* @example
|
|
17
17
|
* ```tsx
|
|
18
18
|
* import { BreadLoaf } from 'react-native-bread';
|
|
19
19
|
*
|
|
20
|
-
* // Basic usage
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
20
|
+
* // Basic usage - add to your root layout
|
|
21
|
+
* export default function RootLayout() {
|
|
22
|
+
* return (
|
|
23
|
+
* <>
|
|
24
|
+
* <Stack />
|
|
25
|
+
* <BreadLoaf />
|
|
26
|
+
* </>
|
|
27
|
+
* );
|
|
28
|
+
* }
|
|
24
29
|
*
|
|
25
30
|
* // With configuration
|
|
26
31
|
* <BreadLoaf
|
|
@@ -34,36 +39,26 @@ var _jsxRuntime = require("react/jsx-runtime");
|
|
|
34
39
|
* },
|
|
35
40
|
* toastStyle: { borderRadius: 12 },
|
|
36
41
|
* }}
|
|
37
|
-
*
|
|
38
|
-
* <App />
|
|
39
|
-
* </BreadLoaf>
|
|
42
|
+
* />
|
|
40
43
|
* ```
|
|
41
44
|
*/
|
|
42
45
|
function BreadLoaf({
|
|
43
|
-
children,
|
|
44
46
|
config
|
|
45
47
|
}) {
|
|
46
48
|
(0, _react.useEffect)(() => {
|
|
47
49
|
_toastStore.toastStore.setConfig(config);
|
|
48
50
|
return () => {
|
|
49
|
-
// Reset to defaults when this provider unmounts
|
|
50
51
|
_toastStore.toastStore.setConfig(undefined);
|
|
51
52
|
};
|
|
52
53
|
}, [config]);
|
|
53
|
-
return /*#__PURE__*/(0, _jsxRuntime.
|
|
54
|
-
style: styles.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
pointerEvents: "box-none",
|
|
58
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_toast.ToastContainer, {})
|
|
59
|
-
})]
|
|
54
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
55
|
+
style: styles.container,
|
|
56
|
+
pointerEvents: "box-none",
|
|
57
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_toast.ToastContainer, {})
|
|
60
58
|
});
|
|
61
59
|
}
|
|
62
60
|
const styles = _reactNative.StyleSheet.create({
|
|
63
|
-
|
|
64
|
-
flex: 1
|
|
65
|
-
},
|
|
66
|
-
portalContainer: {
|
|
61
|
+
container: {
|
|
67
62
|
..._reactNative.StyleSheet.absoluteFillObject,
|
|
68
63
|
zIndex: 9999
|
|
69
64
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_react","require","_reactNative","_toast","_toastStore","_jsxRuntime","BreadLoaf","
|
|
1
|
+
{"version":3,"names":["_react","require","_reactNative","_toast","_toastStore","_jsxRuntime","BreadLoaf","config","useEffect","toastStore","setConfig","undefined","jsx","View","style","styles","container","pointerEvents","children","ToastContainer","StyleSheet","create","absoluteFillObject","zIndex"],"sourceRoot":"../../src","sources":["toast-provider.tsx"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AACA,IAAAC,YAAA,GAAAD,OAAA;AACA,IAAAE,MAAA,GAAAF,OAAA;AACA,IAAAG,WAAA,GAAAH,OAAA;AAA2C,IAAAI,WAAA,GAAAJ,OAAA;AAoB3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASK,SAASA,CAAC;EAAEC;AAAuB,CAAC,EAAE;EACpD,IAAAC,gBAAS,EAAC,MAAM;IACdC,sBAAU,CAACC,SAAS,CAACH,MAAM,CAAC;IAC5B,OAAO,MAAM;MACXE,sBAAU,CAACC,SAAS,CAACC,SAAS,CAAC;IACjC,CAAC;EACH,CAAC,EAAE,CAACJ,MAAM,CAAC,CAAC;EAEZ,oBACE,IAAAF,WAAA,CAAAO,GAAA,EAACV,YAAA,CAAAW,IAAI;IAACC,KAAK,EAAEC,MAAM,CAACC,SAAU;IAACC,aAAa,EAAC,UAAU;IAAAC,QAAA,eACrD,IAAAb,WAAA,CAAAO,GAAA,EAACT,MAAA,CAAAgB,cAAc,IAAE;EAAC,CACd,CAAC;AAEX;AAEA,MAAMJ,MAAM,GAAGK,uBAAU,CAACC,MAAM,CAAC;EAC/BL,SAAS,EAAE;IACT,GAAGI,uBAAU,CAACE,kBAAkB;IAChCC,MAAM,EAAE;EACV;AACF,CAAC,CAAC","ignoreList":[]}
|
package/lib/commonjs/toast.js
CHANGED
|
@@ -16,22 +16,45 @@ var _jsxRuntime = require("react/jsx-runtime");
|
|
|
16
16
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
17
17
|
const ICON_SIZE = 28;
|
|
18
18
|
|
|
19
|
-
/**
|
|
20
|
-
const
|
|
19
|
+
/** Memoized default icons to prevent SVG re-renders */
|
|
20
|
+
const MemoizedGreenCheck = /*#__PURE__*/(0, _react.memo)(({
|
|
21
|
+
fill
|
|
22
|
+
}) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.GreenCheck, {
|
|
23
|
+
width: 36,
|
|
24
|
+
height: 36,
|
|
25
|
+
fill: fill
|
|
26
|
+
}));
|
|
27
|
+
const MemoizedRedX = /*#__PURE__*/(0, _react.memo)(({
|
|
28
|
+
fill
|
|
29
|
+
}) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.RedX, {
|
|
30
|
+
width: ICON_SIZE,
|
|
31
|
+
height: ICON_SIZE,
|
|
32
|
+
fill: fill
|
|
33
|
+
}));
|
|
34
|
+
const MemoizedInfoIcon = /*#__PURE__*/(0, _react.memo)(({
|
|
35
|
+
fill
|
|
36
|
+
}) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.InfoIcon, {
|
|
37
|
+
width: ICON_SIZE,
|
|
38
|
+
height: ICON_SIZE,
|
|
39
|
+
fill: fill
|
|
40
|
+
}));
|
|
41
|
+
const MemoizedCloseIcon = /*#__PURE__*/(0, _react.memo)(() => /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.CloseIcon, {
|
|
42
|
+
width: 20,
|
|
43
|
+
height: 20
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
/** Default icon for each toast type - memoized */
|
|
47
|
+
const DefaultIcon = /*#__PURE__*/(0, _react.memo)(({
|
|
21
48
|
type,
|
|
22
49
|
accentColor
|
|
23
50
|
}) => {
|
|
24
51
|
switch (type) {
|
|
25
52
|
case "success":
|
|
26
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsx)(
|
|
27
|
-
width: 36,
|
|
28
|
-
height: 36,
|
|
53
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(MemoizedGreenCheck, {
|
|
29
54
|
fill: accentColor
|
|
30
55
|
});
|
|
31
56
|
case "error":
|
|
32
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsx)(
|
|
33
|
-
width: ICON_SIZE,
|
|
34
|
-
height: ICON_SIZE,
|
|
57
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(MemoizedRedX, {
|
|
35
58
|
fill: accentColor
|
|
36
59
|
});
|
|
37
60
|
case "loading":
|
|
@@ -40,23 +63,17 @@ const DefaultIcon = ({
|
|
|
40
63
|
color: accentColor
|
|
41
64
|
});
|
|
42
65
|
case "info":
|
|
43
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsx)(
|
|
44
|
-
width: ICON_SIZE,
|
|
45
|
-
height: ICON_SIZE,
|
|
66
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(MemoizedInfoIcon, {
|
|
46
67
|
fill: accentColor
|
|
47
68
|
});
|
|
48
69
|
default:
|
|
49
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsx)(
|
|
50
|
-
width: 36,
|
|
51
|
-
height: 36,
|
|
70
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(MemoizedGreenCheck, {
|
|
52
71
|
fill: accentColor
|
|
53
72
|
});
|
|
54
73
|
}
|
|
55
|
-
};
|
|
56
|
-
|
|
74
|
+
});
|
|
57
75
|
/** Resolves the icon to render - checks per-toast, then config, then default */
|
|
58
76
|
const resolveIcon = (type, accentColor, customIcon, configIcon) => {
|
|
59
|
-
// Per-toast custom icon takes priority
|
|
60
77
|
if (customIcon) {
|
|
61
78
|
if (typeof customIcon === "function") {
|
|
62
79
|
return customIcon({
|
|
@@ -66,22 +83,20 @@ const resolveIcon = (type, accentColor, customIcon, configIcon) => {
|
|
|
66
83
|
}
|
|
67
84
|
return customIcon;
|
|
68
85
|
}
|
|
69
|
-
|
|
70
|
-
// Config-level custom icon
|
|
71
86
|
if (configIcon) {
|
|
72
87
|
return configIcon({
|
|
73
88
|
color: accentColor,
|
|
74
89
|
size: ICON_SIZE
|
|
75
90
|
});
|
|
76
91
|
}
|
|
77
|
-
|
|
78
|
-
// Default icon
|
|
79
92
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(DefaultIcon, {
|
|
80
93
|
type: type,
|
|
81
94
|
accentColor: accentColor
|
|
82
95
|
});
|
|
83
96
|
};
|
|
84
|
-
|
|
97
|
+
|
|
98
|
+
/** Animated icon wrapper with scale/fade animation */
|
|
99
|
+
const AnimatedIcon = /*#__PURE__*/(0, _react.memo)(({
|
|
85
100
|
type,
|
|
86
101
|
accentColor,
|
|
87
102
|
customIcon,
|
|
@@ -104,7 +119,53 @@ const AnimatedIcon = ({
|
|
|
104
119
|
style: style,
|
|
105
120
|
children: resolveIcon(type, accentColor, customIcon, configIcon)
|
|
106
121
|
});
|
|
107
|
-
};
|
|
122
|
+
});
|
|
123
|
+
/** Memoized toast content to prevent inline JSX recreation */
|
|
124
|
+
const ToastContent = /*#__PURE__*/(0, _react.memo)(({
|
|
125
|
+
type,
|
|
126
|
+
title,
|
|
127
|
+
description,
|
|
128
|
+
accentColor,
|
|
129
|
+
customIcon,
|
|
130
|
+
configIcon,
|
|
131
|
+
showCloseButton,
|
|
132
|
+
onDismiss,
|
|
133
|
+
titleStyle,
|
|
134
|
+
descriptionStyle,
|
|
135
|
+
optionsTitleStyle,
|
|
136
|
+
optionsDescriptionStyle
|
|
137
|
+
}) => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
138
|
+
style: styles.content,
|
|
139
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
140
|
+
style: styles.icon,
|
|
141
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(AnimatedIcon, {
|
|
142
|
+
type: type,
|
|
143
|
+
accentColor: accentColor,
|
|
144
|
+
customIcon: customIcon,
|
|
145
|
+
configIcon: configIcon
|
|
146
|
+
}, type)
|
|
147
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
148
|
+
style: styles.textContainer,
|
|
149
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
150
|
+
maxFontSizeMultiplier: 1.35,
|
|
151
|
+
allowFontScaling: false,
|
|
152
|
+
style: [styles.title, {
|
|
153
|
+
color: accentColor
|
|
154
|
+
}, titleStyle, optionsTitleStyle],
|
|
155
|
+
children: title
|
|
156
|
+
}), description && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
157
|
+
allowFontScaling: false,
|
|
158
|
+
maxFontSizeMultiplier: 1.35,
|
|
159
|
+
style: [styles.description, descriptionStyle, optionsDescriptionStyle],
|
|
160
|
+
children: description
|
|
161
|
+
})]
|
|
162
|
+
}), showCloseButton && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
|
|
163
|
+
style: styles.closeButton,
|
|
164
|
+
onPress: onDismiss,
|
|
165
|
+
hitSlop: 12,
|
|
166
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(MemoizedCloseIcon, {})
|
|
167
|
+
})]
|
|
168
|
+
}));
|
|
108
169
|
|
|
109
170
|
// singleton instance
|
|
110
171
|
const ToastContainer = () => {
|
|
@@ -185,17 +246,10 @@ const ToastItem = ({
|
|
|
185
246
|
// Stack position animation
|
|
186
247
|
const stackIndex = (0, _reactNativeReanimated.useSharedValue)(index);
|
|
187
248
|
|
|
188
|
-
// Title color animation on variant change
|
|
189
|
-
const colorProgress = (0, _reactNativeReanimated.useSharedValue)(1);
|
|
190
|
-
const fromColor = (0, _reactNativeReanimated.useSharedValue)(theme.colors[toast.type].accent);
|
|
191
|
-
const toColor = (0, _reactNativeReanimated.useSharedValue)(theme.colors[toast.type].accent);
|
|
192
|
-
|
|
193
249
|
// Refs for tracking previous values to avoid unnecessary animations
|
|
194
250
|
const lastHandledType = (0, _react.useRef)(toast.type);
|
|
195
251
|
const prevIndex = (0, _react.useRef)(index);
|
|
196
252
|
const hasEntered = (0, _react.useRef)(false);
|
|
197
|
-
|
|
198
|
-
// Combined animation effect for entry, exit, color transitions, and stack position
|
|
199
253
|
(0, _react.useEffect)(() => {
|
|
200
254
|
// Entry animation (only once on mount)
|
|
201
255
|
if (!hasEntered.current && !toast.isExiting) {
|
|
@@ -218,16 +272,9 @@ const ToastItem = ({
|
|
|
218
272
|
});
|
|
219
273
|
}
|
|
220
274
|
|
|
221
|
-
//
|
|
275
|
+
// Track type changes (for icon animation via key)
|
|
222
276
|
if (toast.type !== lastHandledType.current) {
|
|
223
|
-
fromColor.value = theme.colors[lastHandledType.current].accent;
|
|
224
|
-
toColor.value = theme.colors[toast.type].accent;
|
|
225
277
|
lastHandledType.current = toast.type;
|
|
226
|
-
colorProgress.value = 0;
|
|
227
|
-
colorProgress.value = (0, _reactNativeReanimated.withTiming)(1, {
|
|
228
|
-
duration: 300,
|
|
229
|
-
easing: EASING
|
|
230
|
-
});
|
|
231
278
|
}
|
|
232
279
|
|
|
233
280
|
// Stack position animation when index changes
|
|
@@ -238,14 +285,11 @@ const ToastItem = ({
|
|
|
238
285
|
});
|
|
239
286
|
prevIndex.current = index;
|
|
240
287
|
}
|
|
241
|
-
}, [toast.isExiting, toast.type, index, progress, translationY,
|
|
242
|
-
const titleColorStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => ({
|
|
243
|
-
color: (0, _reactNativeReanimated.interpolateColor)(colorProgress.value, [0, 1], [fromColor.value, toColor.value])
|
|
244
|
-
}));
|
|
288
|
+
}, [toast.isExiting, toast.type, index, progress, translationY, stackIndex, exitToY]);
|
|
245
289
|
const dismissToast = (0, _react.useCallback)(() => {
|
|
246
290
|
_toastStore.toastStore.hide(toast.id);
|
|
247
291
|
}, [toast.id]);
|
|
248
|
-
const panGesture = _reactNativeGestureHandler.Gesture.Pan().onStart(() => {
|
|
292
|
+
const panGesture = (0, _react.useMemo)(() => _reactNativeGestureHandler.Gesture.Pan().onStart(() => {
|
|
249
293
|
"worklet";
|
|
250
294
|
|
|
251
295
|
isBeingDragged.value = true;
|
|
@@ -292,7 +336,13 @@ const ToastItem = ({
|
|
|
292
336
|
easing: EASING
|
|
293
337
|
});
|
|
294
338
|
}
|
|
295
|
-
});
|
|
339
|
+
}), [isBottom, dismissToast, progress, translationY, shouldDismiss, isBeingDragged]);
|
|
340
|
+
|
|
341
|
+
// Memoize disabled gesture to avoid recreation on every render
|
|
342
|
+
const disabledGesture = (0, _react.useMemo)(() => _reactNativeGestureHandler.Gesture.Pan().enabled(false), []);
|
|
343
|
+
|
|
344
|
+
// Derive zIndex separately - it's not animatable and shouldn't trigger worklet re-runs
|
|
345
|
+
const zIndex = (0, _reactNativeReanimated.useDerivedValue)(() => 1000 - Math.round(stackIndex.value));
|
|
296
346
|
const animatedStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => {
|
|
297
347
|
const baseTranslateY = (0, _reactNativeReanimated.interpolate)(progress.value, [0, 1], [entryFromY, ToY]);
|
|
298
348
|
|
|
@@ -316,7 +366,7 @@ const ToastItem = ({
|
|
|
316
366
|
scale
|
|
317
367
|
}],
|
|
318
368
|
opacity,
|
|
319
|
-
zIndex:
|
|
369
|
+
zIndex: zIndex.value
|
|
320
370
|
};
|
|
321
371
|
});
|
|
322
372
|
const accentColor = theme.colors[toast.type].accent;
|
|
@@ -339,50 +389,36 @@ const ToastItem = ({
|
|
|
339
389
|
const shouldShowCloseButton = toast.type !== "loading" && (options?.showCloseButton ?? theme.showCloseButton);
|
|
340
390
|
|
|
341
391
|
// Enable/disable gesture based on dismissible setting
|
|
342
|
-
const gesture = isDismissible ? panGesture :
|
|
392
|
+
const gesture = isDismissible ? panGesture : disabledGesture;
|
|
393
|
+
const animStyle = [styles.toast, verticalAnchor, {
|
|
394
|
+
backgroundColor
|
|
395
|
+
}, theme.toastStyle, options?.style, animatedStyle];
|
|
343
396
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeGestureHandler.GestureDetector, {
|
|
344
397
|
gesture: gesture,
|
|
345
398
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeReanimated.default.View, {
|
|
346
|
-
style:
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
style: styles.textContainer,
|
|
361
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeReanimated.default.Text, {
|
|
362
|
-
maxFontSizeMultiplier: 1.35,
|
|
363
|
-
allowFontScaling: false,
|
|
364
|
-
style: [styles.title, theme.titleStyle, options?.titleStyle, titleColorStyle],
|
|
365
|
-
children: toast.title
|
|
366
|
-
}), toast.description && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
367
|
-
allowFontScaling: false,
|
|
368
|
-
maxFontSizeMultiplier: 1.35,
|
|
369
|
-
style: [styles.description, theme.descriptionStyle, options?.descriptionStyle],
|
|
370
|
-
children: toast.description
|
|
371
|
-
})]
|
|
372
|
-
}), shouldShowCloseButton && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
|
|
373
|
-
style: styles.closeButton,
|
|
374
|
-
onPress: dismissToast,
|
|
375
|
-
hitSlop: 12,
|
|
376
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.CloseIcon, {
|
|
377
|
-
width: 20,
|
|
378
|
-
height: 20
|
|
379
|
-
})
|
|
380
|
-
})]
|
|
399
|
+
style: animStyle,
|
|
400
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(ToastContent, {
|
|
401
|
+
type: toast.type,
|
|
402
|
+
title: toast.title,
|
|
403
|
+
description: toast.description,
|
|
404
|
+
accentColor: accentColor,
|
|
405
|
+
customIcon: customIcon,
|
|
406
|
+
configIcon: configIcon,
|
|
407
|
+
showCloseButton: shouldShowCloseButton,
|
|
408
|
+
onDismiss: dismissToast,
|
|
409
|
+
titleStyle: theme.titleStyle,
|
|
410
|
+
descriptionStyle: theme.descriptionStyle,
|
|
411
|
+
optionsTitleStyle: options?.titleStyle,
|
|
412
|
+
optionsDescriptionStyle: options?.descriptionStyle
|
|
381
413
|
})
|
|
382
414
|
})
|
|
383
415
|
});
|
|
384
416
|
};
|
|
385
|
-
|
|
417
|
+
|
|
418
|
+
// Custom comparison to prevent re-renders when toast object reference changes but content is same
|
|
419
|
+
const MemoizedToastItem = /*#__PURE__*/(0, _react.memo)(ToastItem, (prev, next) => {
|
|
420
|
+
return prev.toast.id === next.toast.id && prev.toast.type === next.toast.type && prev.toast.title === next.toast.title && prev.toast.description === next.toast.description && prev.toast.isExiting === next.toast.isExiting && prev.index === next.index && prev.position === next.position && prev.theme === next.theme;
|
|
421
|
+
});
|
|
386
422
|
const styles = _reactNative.StyleSheet.create({
|
|
387
423
|
container: {
|
|
388
424
|
position: "absolute",
|