@mehul214/react-native-flash-toast 0.0.0-stage → 1.0.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/LICENSE +22 -0
- package/README.md +251 -2
- package/dist/index.d.mts +240 -0
- package/dist/index.d.ts +242 -0
- package/dist/index.js +749 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +730 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +87 -4
- package/src/FlashMessage.tsx +436 -0
- package/src/FlashMessageManager.ts +71 -0
- package/src/FlashMessageProvider.tsx +48 -0
- package/src/hooks/useFlashMessage.ts +2 -0
- package/src/icons.tsx +154 -0
- package/src/index.ts +31 -0
- package/src/styles.ts +145 -0
- package/src/types.ts +101 -0
- package/src/utils/safeArea.ts +45 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { FlashMessageOptions, FlashMessageRef } from './types';
|
|
2
|
+
|
|
3
|
+
class FlashMessageManager {
|
|
4
|
+
private defaultInstance: FlashMessageRef | null = null;
|
|
5
|
+
private instances: Map<string, FlashMessageRef> = new Map();
|
|
6
|
+
private pendingQueue: FlashMessageOptions[] = [];
|
|
7
|
+
|
|
8
|
+
public register(ref: FlashMessageRef, id: string = 'default'): void {
|
|
9
|
+
this.instances.set(id, ref);
|
|
10
|
+
if (!this.defaultInstance || id === 'default') {
|
|
11
|
+
this.defaultInstance = ref;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Flush any pending messages that arrived before mount
|
|
15
|
+
if (this.pendingQueue.length > 0) {
|
|
16
|
+
const next = this.pendingQueue.shift();
|
|
17
|
+
if (next) {
|
|
18
|
+
ref.showMessage(next);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public unregister(id: string = 'default'): void {
|
|
24
|
+
this.instances.delete(id);
|
|
25
|
+
if (this.defaultInstance === this.instances.get(id)) {
|
|
26
|
+
this.defaultInstance = this.instances.values().next().value || null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public showMessage(options: FlashMessageOptions, targetId?: string): void {
|
|
31
|
+
const target = targetId ? this.instances.get(targetId) : this.defaultInstance;
|
|
32
|
+
|
|
33
|
+
if (target) {
|
|
34
|
+
target.showMessage(options);
|
|
35
|
+
} else {
|
|
36
|
+
// Buffer if component hasn't mounted yet
|
|
37
|
+
this.pendingQueue.push(options);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public hideMessage(targetId?: string): void {
|
|
42
|
+
const target = targetId ? this.instances.get(targetId) : this.defaultInstance;
|
|
43
|
+
if (target) {
|
|
44
|
+
target.hideMessage();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const flashManager = new FlashMessageManager();
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Imperative helper to trigger a flash message from anywhere in the app
|
|
53
|
+
* (services, Zustand, Redux, API handlers, navigation screens).
|
|
54
|
+
*/
|
|
55
|
+
export function showMessage(options: FlashMessageOptions): void {
|
|
56
|
+
flashManager.showMessage(options);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Imperative helper to dismiss the currently displayed flash message.
|
|
61
|
+
*/
|
|
62
|
+
export function hideMessage(): void {
|
|
63
|
+
flashManager.hideMessage();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Modern alias for showMessage
|
|
68
|
+
*/
|
|
69
|
+
export const flash = showMessage;
|
|
70
|
+
export const dismissFlash = hideMessage;
|
|
71
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React, { createContext, useContext, useMemo } from 'react';
|
|
2
|
+
import { FlashMessage } from './FlashMessage';
|
|
3
|
+
import { flashManager } from './FlashMessageManager';
|
|
4
|
+
import type { FlashMessageContainerProps, FlashMessageOptions } from './types';
|
|
5
|
+
|
|
6
|
+
interface FlashMessageContextValue {
|
|
7
|
+
show: (options: FlashMessageOptions) => void;
|
|
8
|
+
hide: () => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const FlashMessageContext = createContext<FlashMessageContextValue | null>(null);
|
|
12
|
+
|
|
13
|
+
export interface FlashMessageProviderProps extends FlashMessageContainerProps {
|
|
14
|
+
children: React.ReactNode;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const FlashMessageProvider: React.FC<FlashMessageProviderProps> = ({
|
|
18
|
+
children,
|
|
19
|
+
...containerProps
|
|
20
|
+
}) => {
|
|
21
|
+
const contextValue = useMemo<FlashMessageContextValue>(
|
|
22
|
+
() => ({
|
|
23
|
+
show: (options: FlashMessageOptions) => flashManager.showMessage(options),
|
|
24
|
+
hide: () => flashManager.hideMessage(),
|
|
25
|
+
}),
|
|
26
|
+
[]
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<FlashMessageContext.Provider value={contextValue}>
|
|
31
|
+
{children}
|
|
32
|
+
<FlashMessage {...containerProps} />
|
|
33
|
+
</FlashMessageContext.Provider>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export function useFlashMessage(): FlashMessageContextValue {
|
|
38
|
+
const context = useContext(FlashMessageContext);
|
|
39
|
+
if (!context) {
|
|
40
|
+
// Graceful fallback to global manager even if Provider is omitted
|
|
41
|
+
return {
|
|
42
|
+
show: (options: FlashMessageOptions) => flashManager.showMessage(options),
|
|
43
|
+
hide: () => flashManager.hideMessage(),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return context;
|
|
47
|
+
}
|
|
48
|
+
|
package/src/icons.tsx
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, StyleSheet } from 'react-native';
|
|
3
|
+
import type { FlashMessageType } from './types';
|
|
4
|
+
|
|
5
|
+
interface BuiltInIconProps {
|
|
6
|
+
type: FlashMessageType;
|
|
7
|
+
size?: number;
|
|
8
|
+
color?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const BuiltInIcon: React.FC<BuiltInIconProps> = ({
|
|
12
|
+
type,
|
|
13
|
+
size = 18,
|
|
14
|
+
color = '#FFFFFF',
|
|
15
|
+
}) => {
|
|
16
|
+
switch (type) {
|
|
17
|
+
case 'success': {
|
|
18
|
+
// Crisp pixel-perfect checkmark
|
|
19
|
+
const checkWidth = size * 0.36;
|
|
20
|
+
const checkHeight = size * 0.65;
|
|
21
|
+
const stroke = Math.max(2, size * 0.12);
|
|
22
|
+
return (
|
|
23
|
+
<View style={[styles.center, { width: size, height: size }]}>
|
|
24
|
+
<View
|
|
25
|
+
style={{
|
|
26
|
+
width: checkWidth,
|
|
27
|
+
height: checkHeight,
|
|
28
|
+
borderColor: color,
|
|
29
|
+
borderBottomWidth: stroke,
|
|
30
|
+
borderRightWidth: stroke,
|
|
31
|
+
borderRadius: 1,
|
|
32
|
+
transform: [{ rotate: '45deg' }, { translateY: -size * 0.08 }],
|
|
33
|
+
}}
|
|
34
|
+
/>
|
|
35
|
+
</View>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
case 'danger': {
|
|
40
|
+
// Crisp pixel-perfect cross
|
|
41
|
+
const crossThickness = Math.max(2, size * 0.12);
|
|
42
|
+
const crossLength = size * 0.72;
|
|
43
|
+
return (
|
|
44
|
+
<View style={[styles.center, { width: size, height: size }]}>
|
|
45
|
+
<View
|
|
46
|
+
style={[
|
|
47
|
+
styles.crossBar,
|
|
48
|
+
{
|
|
49
|
+
width: crossLength,
|
|
50
|
+
height: crossThickness,
|
|
51
|
+
backgroundColor: color,
|
|
52
|
+
borderRadius: crossThickness / 2,
|
|
53
|
+
transform: [{ rotate: '45deg' }],
|
|
54
|
+
},
|
|
55
|
+
]}
|
|
56
|
+
/>
|
|
57
|
+
<View
|
|
58
|
+
style={[
|
|
59
|
+
styles.crossBar,
|
|
60
|
+
{
|
|
61
|
+
width: crossLength,
|
|
62
|
+
height: crossThickness,
|
|
63
|
+
backgroundColor: color,
|
|
64
|
+
borderRadius: crossThickness / 2,
|
|
65
|
+
transform: [{ rotate: '-45deg' }],
|
|
66
|
+
},
|
|
67
|
+
]}
|
|
68
|
+
/>
|
|
69
|
+
</View>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
case 'warning': {
|
|
74
|
+
// Modern clean exclamation mark
|
|
75
|
+
const warnThickness = Math.max(2, size * 0.13);
|
|
76
|
+
const dotSize = warnThickness * 1.1;
|
|
77
|
+
return (
|
|
78
|
+
<View style={[styles.center, { width: size, height: size }]}>
|
|
79
|
+
<View
|
|
80
|
+
style={{
|
|
81
|
+
width: warnThickness,
|
|
82
|
+
height: size * 0.46,
|
|
83
|
+
backgroundColor: color,
|
|
84
|
+
borderRadius: warnThickness / 2,
|
|
85
|
+
marginBottom: size * 0.14,
|
|
86
|
+
}}
|
|
87
|
+
/>
|
|
88
|
+
<View
|
|
89
|
+
style={{
|
|
90
|
+
width: dotSize,
|
|
91
|
+
height: dotSize,
|
|
92
|
+
borderRadius: dotSize / 2,
|
|
93
|
+
backgroundColor: color,
|
|
94
|
+
}}
|
|
95
|
+
/>
|
|
96
|
+
</View>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
case 'info': {
|
|
101
|
+
// Modern clean info icon: top dot, bottom stem (no double circle!)
|
|
102
|
+
const infoThickness = Math.max(2, size * 0.13);
|
|
103
|
+
const infoDotSize = infoThickness * 1.1;
|
|
104
|
+
return (
|
|
105
|
+
<View style={[styles.center, { width: size, height: size }]}>
|
|
106
|
+
<View
|
|
107
|
+
style={{
|
|
108
|
+
width: infoDotSize,
|
|
109
|
+
height: infoDotSize,
|
|
110
|
+
borderRadius: infoDotSize / 2,
|
|
111
|
+
backgroundColor: color,
|
|
112
|
+
marginBottom: size * 0.14,
|
|
113
|
+
}}
|
|
114
|
+
/>
|
|
115
|
+
<View
|
|
116
|
+
style={{
|
|
117
|
+
width: infoThickness,
|
|
118
|
+
height: size * 0.46,
|
|
119
|
+
backgroundColor: color,
|
|
120
|
+
borderRadius: infoThickness / 2,
|
|
121
|
+
}}
|
|
122
|
+
/>
|
|
123
|
+
</View>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
case 'default':
|
|
128
|
+
default: {
|
|
129
|
+
const defaultDot = size * 0.44;
|
|
130
|
+
return (
|
|
131
|
+
<View style={[styles.center, { width: size, height: size }]}>
|
|
132
|
+
<View
|
|
133
|
+
style={{
|
|
134
|
+
width: defaultDot,
|
|
135
|
+
height: defaultDot,
|
|
136
|
+
borderRadius: defaultDot / 2,
|
|
137
|
+
backgroundColor: color,
|
|
138
|
+
}}
|
|
139
|
+
/>
|
|
140
|
+
</View>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const styles = StyleSheet.create({
|
|
147
|
+
center: {
|
|
148
|
+
justifyContent: 'center',
|
|
149
|
+
alignItems: 'center',
|
|
150
|
+
},
|
|
151
|
+
crossBar: {
|
|
152
|
+
position: 'absolute',
|
|
153
|
+
},
|
|
154
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { FlashMessage } from './FlashMessage';
|
|
2
|
+
|
|
3
|
+
export { FlashMessage };
|
|
4
|
+
export default FlashMessage;
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
flashManager,
|
|
8
|
+
showMessage,
|
|
9
|
+
hideMessage,
|
|
10
|
+
flash,
|
|
11
|
+
dismissFlash,
|
|
12
|
+
} from './FlashMessageManager';
|
|
13
|
+
|
|
14
|
+
export {
|
|
15
|
+
FlashMessageProvider,
|
|
16
|
+
useFlashMessage,
|
|
17
|
+
} from './FlashMessageProvider';
|
|
18
|
+
|
|
19
|
+
export { BuiltInIcon } from './icons';
|
|
20
|
+
export { COLORS, styles } from './styles';
|
|
21
|
+
|
|
22
|
+
export type {
|
|
23
|
+
FlashMessageAction,
|
|
24
|
+
FlashMessageContainerProps,
|
|
25
|
+
FlashMessageIcon,
|
|
26
|
+
FlashMessageOptions,
|
|
27
|
+
FlashMessagePosition,
|
|
28
|
+
FlashMessageRef,
|
|
29
|
+
FlashMessageTransition,
|
|
30
|
+
FlashMessageType,
|
|
31
|
+
} from './types';
|
package/src/styles.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { StyleSheet, Platform } from 'react-native';
|
|
2
|
+
import type { FlashMessageType } from './types';
|
|
3
|
+
|
|
4
|
+
export const COLORS: Record<FlashMessageType, { background: string; text: string; icon: string }> = {
|
|
5
|
+
default: {
|
|
6
|
+
background: '#1F2937', // Slate 800
|
|
7
|
+
text: '#FFFFFF',
|
|
8
|
+
icon: '#9CA3AF',
|
|
9
|
+
},
|
|
10
|
+
success: {
|
|
11
|
+
background: '#059669', // Emerald 600
|
|
12
|
+
text: '#FFFFFF',
|
|
13
|
+
icon: '#D1FAE5',
|
|
14
|
+
},
|
|
15
|
+
danger: {
|
|
16
|
+
background: '#DC2626', // Red 600
|
|
17
|
+
text: '#FFFFFF',
|
|
18
|
+
icon: '#FEE2E2',
|
|
19
|
+
},
|
|
20
|
+
warning: {
|
|
21
|
+
background: '#D97706', // Amber 600
|
|
22
|
+
text: '#FFFFFF',
|
|
23
|
+
icon: '#FEF3C7',
|
|
24
|
+
},
|
|
25
|
+
info: {
|
|
26
|
+
background: '#2563EB', // Blue 600
|
|
27
|
+
text: '#FFFFFF',
|
|
28
|
+
icon: '#DBEAFE',
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const styles = StyleSheet.create({
|
|
33
|
+
rootOverlay: {
|
|
34
|
+
position: 'absolute',
|
|
35
|
+
left: 0,
|
|
36
|
+
right: 0,
|
|
37
|
+
zIndex: 99999,
|
|
38
|
+
elevation: 99999,
|
|
39
|
+
},
|
|
40
|
+
rootTop: {
|
|
41
|
+
top: 0,
|
|
42
|
+
},
|
|
43
|
+
rootBottom: {
|
|
44
|
+
bottom: 0,
|
|
45
|
+
},
|
|
46
|
+
rootCenter: {
|
|
47
|
+
top: 0,
|
|
48
|
+
bottom: 0,
|
|
49
|
+
justifyContent: 'center',
|
|
50
|
+
alignItems: 'center',
|
|
51
|
+
},
|
|
52
|
+
toastContainer: {
|
|
53
|
+
flexDirection: 'row',
|
|
54
|
+
alignItems: 'center',
|
|
55
|
+
overflow: 'hidden',
|
|
56
|
+
...Platform.select({
|
|
57
|
+
ios: {
|
|
58
|
+
shadowColor: '#000000',
|
|
59
|
+
shadowOffset: { width: 0, height: 6 },
|
|
60
|
+
shadowOpacity: 0.2,
|
|
61
|
+
shadowRadius: 14,
|
|
62
|
+
},
|
|
63
|
+
android: {
|
|
64
|
+
elevation: 8,
|
|
65
|
+
},
|
|
66
|
+
web: {
|
|
67
|
+
boxShadow: '0 10px 25px -5px rgba(0, 0, 0, 0.25), 0 8px 10px -6px rgba(0, 0, 0, 0.15)',
|
|
68
|
+
} as any,
|
|
69
|
+
}),
|
|
70
|
+
},
|
|
71
|
+
floatingContainer: {
|
|
72
|
+
marginHorizontal: 16,
|
|
73
|
+
borderRadius: 16,
|
|
74
|
+
borderWidth: 1,
|
|
75
|
+
borderColor: 'rgba(255, 255, 255, 0.16)',
|
|
76
|
+
},
|
|
77
|
+
edgeToEdgeContainer: {
|
|
78
|
+
marginHorizontal: 0,
|
|
79
|
+
borderRadius: 0,
|
|
80
|
+
},
|
|
81
|
+
contentWrapper: {
|
|
82
|
+
flex: 1,
|
|
83
|
+
flexDirection: 'row',
|
|
84
|
+
alignItems: 'center',
|
|
85
|
+
paddingVertical: 13,
|
|
86
|
+
paddingHorizontal: 16,
|
|
87
|
+
},
|
|
88
|
+
iconContainer: {
|
|
89
|
+
marginRight: 12,
|
|
90
|
+
justifyContent: 'center',
|
|
91
|
+
alignItems: 'center',
|
|
92
|
+
},
|
|
93
|
+
iconBadge: {
|
|
94
|
+
width: 32,
|
|
95
|
+
height: 32,
|
|
96
|
+
borderRadius: 16,
|
|
97
|
+
backgroundColor: 'rgba(255, 255, 255, 0.22)',
|
|
98
|
+
justifyContent: 'center',
|
|
99
|
+
alignItems: 'center',
|
|
100
|
+
},
|
|
101
|
+
textContainer: {
|
|
102
|
+
flex: 1,
|
|
103
|
+
justifyContent: 'center',
|
|
104
|
+
},
|
|
105
|
+
titleText: {
|
|
106
|
+
fontSize: 15,
|
|
107
|
+
fontWeight: '600',
|
|
108
|
+
letterSpacing: -0.2,
|
|
109
|
+
lineHeight: 20,
|
|
110
|
+
color: '#FFFFFF',
|
|
111
|
+
},
|
|
112
|
+
descriptionText: {
|
|
113
|
+
fontSize: 13,
|
|
114
|
+
fontWeight: '400',
|
|
115
|
+
color: 'rgba(255, 255, 255, 0.9)',
|
|
116
|
+
marginTop: 2,
|
|
117
|
+
lineHeight: 18,
|
|
118
|
+
},
|
|
119
|
+
actionButton: {
|
|
120
|
+
marginLeft: 12,
|
|
121
|
+
paddingHorizontal: 12,
|
|
122
|
+
paddingVertical: 6,
|
|
123
|
+
borderRadius: 8,
|
|
124
|
+
backgroundColor: 'rgba(255, 255, 255, 0.22)',
|
|
125
|
+
borderWidth: 1,
|
|
126
|
+
borderColor: 'rgba(255, 255, 255, 0.2)',
|
|
127
|
+
},
|
|
128
|
+
actionButtonText: {
|
|
129
|
+
fontSize: 13,
|
|
130
|
+
fontWeight: '600',
|
|
131
|
+
color: '#FFFFFF',
|
|
132
|
+
},
|
|
133
|
+
progressBarContainer: {
|
|
134
|
+
position: 'absolute',
|
|
135
|
+
bottom: 0,
|
|
136
|
+
left: 0,
|
|
137
|
+
right: 0,
|
|
138
|
+
height: 3,
|
|
139
|
+
backgroundColor: 'rgba(255, 255, 255, 0.2)',
|
|
140
|
+
},
|
|
141
|
+
progressBarFill: {
|
|
142
|
+
height: '100%',
|
|
143
|
+
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
|
144
|
+
},
|
|
145
|
+
});
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { StyleProp, ViewStyle, TextStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
export type FlashMessageType = 'default' | 'success' | 'danger' | 'warning' | 'info';
|
|
5
|
+
|
|
6
|
+
export type FlashMessagePosition = 'top' | 'bottom' | 'center';
|
|
7
|
+
|
|
8
|
+
export type FlashMessageTransition = 'slide' | 'fade' | 'bounce';
|
|
9
|
+
|
|
10
|
+
export type FlashMessageIcon =
|
|
11
|
+
| FlashMessageType
|
|
12
|
+
| 'none'
|
|
13
|
+
| ReactNode
|
|
14
|
+
| ((props: { size: number; color: string }) => ReactNode);
|
|
15
|
+
|
|
16
|
+
export interface FlashMessageAction {
|
|
17
|
+
label: string;
|
|
18
|
+
onPress: () => void;
|
|
19
|
+
style?: StyleProp<ViewStyle>;
|
|
20
|
+
textStyle?: StyleProp<TextStyle>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface FlashMessageOptions {
|
|
24
|
+
/** Unique ID for the message. Auto-generated if not provided */
|
|
25
|
+
id?: string;
|
|
26
|
+
/** Primary title or content */
|
|
27
|
+
message: string | ReactNode;
|
|
28
|
+
/** Secondary subtitle or body content */
|
|
29
|
+
description?: string | ReactNode;
|
|
30
|
+
/** Preset semantic style */
|
|
31
|
+
type?: FlashMessageType;
|
|
32
|
+
/** Screen position: 'top', 'bottom', or 'center'. Default: 'top' */
|
|
33
|
+
position?: FlashMessagePosition;
|
|
34
|
+
/** Display duration in milliseconds before auto-hiding. Default: 3000ms. Set to 0 to disable */
|
|
35
|
+
duration?: number;
|
|
36
|
+
/** Whether the message auto-dismisses after duration. Default: true */
|
|
37
|
+
autoHide?: boolean;
|
|
38
|
+
/** Floating pill with rounded corners vs edge-to-edge bar. Default: true */
|
|
39
|
+
floating?: boolean;
|
|
40
|
+
/** Preset or custom icon */
|
|
41
|
+
icon?: FlashMessageIcon;
|
|
42
|
+
/** Interactive action button (e.g. Undo, Retry) */
|
|
43
|
+
action?: FlashMessageAction;
|
|
44
|
+
/** Callback fired when the flash message card is tapped */
|
|
45
|
+
onPress?: () => void;
|
|
46
|
+
/** Callback fired when the flash message is long-pressed */
|
|
47
|
+
onLongPress?: () => void;
|
|
48
|
+
/** Callback fired after the message appears */
|
|
49
|
+
onShow?: () => void;
|
|
50
|
+
/** Callback fired after the message has fully hidden */
|
|
51
|
+
onHide?: () => void;
|
|
52
|
+
/** Custom container style */
|
|
53
|
+
style?: StyleProp<ViewStyle>;
|
|
54
|
+
/** Custom title text style */
|
|
55
|
+
titleStyle?: StyleProp<TextStyle>;
|
|
56
|
+
/** Custom description text style */
|
|
57
|
+
textStyle?: StyleProp<TextStyle>;
|
|
58
|
+
/** Override background color */
|
|
59
|
+
backgroundColor?: string;
|
|
60
|
+
/** Override text and icon color */
|
|
61
|
+
color?: string;
|
|
62
|
+
/** Dismiss when tapped. Default: true */
|
|
63
|
+
hideOnPress?: boolean;
|
|
64
|
+
/** Enable swipe up/down gesture to dismiss. Default: true */
|
|
65
|
+
swipeToDismiss?: boolean;
|
|
66
|
+
/** Temporarily pause the autoHide timer while finger touches the banner. Default: true */
|
|
67
|
+
pauseOnPress?: boolean;
|
|
68
|
+
/** Show an animated countdown progress bar at the bottom of the card. Default: false */
|
|
69
|
+
showProgressBar?: boolean;
|
|
70
|
+
/** Transition animation style: 'slide' | 'fade' | 'bounce'. Default: 'slide' */
|
|
71
|
+
transition?: FlashMessageTransition;
|
|
72
|
+
/** Override manual top inset / status bar height if needed */
|
|
73
|
+
statusBarHeight?: number;
|
|
74
|
+
/** Custom render function to completely override inner layout */
|
|
75
|
+
customComponent?: (options: FlashMessageOptions, hide: () => void) => ReactNode;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface FlashMessageRef {
|
|
79
|
+
showMessage: (options: FlashMessageOptions) => void;
|
|
80
|
+
hideMessage: () => void;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface FlashMessageContainerProps {
|
|
84
|
+
/** Default position for all messages if not specified in options */
|
|
85
|
+
position?: FlashMessagePosition;
|
|
86
|
+
/** Default duration in ms. Default: 3000 */
|
|
87
|
+
duration?: number;
|
|
88
|
+
/** Default floating style. Default: true */
|
|
89
|
+
floating?: boolean;
|
|
90
|
+
/** Default transition. Default: 'slide' */
|
|
91
|
+
transition?: FlashMessageTransition;
|
|
92
|
+
/** Optional top status bar offset */
|
|
93
|
+
statusBarOffset?: number;
|
|
94
|
+
/** Optional bottom navigation bar offset */
|
|
95
|
+
bottomOffset?: number;
|
|
96
|
+
/** Extra container style */
|
|
97
|
+
style?: StyleProp<ViewStyle>;
|
|
98
|
+
/** Can message replace current one immediately or queue. Default: 'replace' */
|
|
99
|
+
mode?: 'replace' | 'queue';
|
|
100
|
+
}
|
|
101
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Platform, StatusBar, Dimensions } from 'react-native';
|
|
2
|
+
|
|
3
|
+
export interface EdgeInsets {
|
|
4
|
+
top: number;
|
|
5
|
+
bottom: number;
|
|
6
|
+
left: number;
|
|
7
|
+
right: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Returns safe default insets when react-native-safe-area-context is not present.
|
|
12
|
+
* If the user's app wraps FlashMessage in SafeAreaProvider, safe-area context takes precedence.
|
|
13
|
+
*/
|
|
14
|
+
export function getDefaultSafeAreaInsets(): EdgeInsets {
|
|
15
|
+
const { width, height } = Dimensions.get('window');
|
|
16
|
+
const isLandscape = width > height;
|
|
17
|
+
|
|
18
|
+
if (Platform.OS === 'android') {
|
|
19
|
+
return {
|
|
20
|
+
top: StatusBar.currentHeight ?? 24,
|
|
21
|
+
bottom: 16,
|
|
22
|
+
left: 0,
|
|
23
|
+
right: 0,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (Platform.OS === 'ios') {
|
|
28
|
+
// Dynamic island & modern notch iPhones typically have 47-59pt top inset
|
|
29
|
+
const isModernIphone = !Platform.isPad && !Platform.isTV && (height >= 812 || width >= 812);
|
|
30
|
+
return {
|
|
31
|
+
top: isModernIphone ? (isLandscape ? 0 : 50) : 20,
|
|
32
|
+
bottom: isModernIphone ? 34 : 0,
|
|
33
|
+
left: 0,
|
|
34
|
+
right: 0,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
top: 16,
|
|
40
|
+
bottom: 16,
|
|
41
|
+
left: 0,
|
|
42
|
+
right: 0,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|