react-native-bread 0.6.1 → 0.7.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.
- package/README.md +3 -2
- package/lib/commonjs/toast-icons.js +2 -2
- package/lib/commonjs/toast.js +33 -33
- package/lib/commonjs/use-toast-state.js +11 -11
- package/lib/module/toast-icons.js +2 -2
- package/lib/module/toast.js +34 -34
- package/lib/module/use-toast-state.js +12 -12
- package/lib/typescript/types.d.ts +9 -10
- package/lib/typescript/use-toast-state.d.ts +3 -3
- package/package.json +46 -4
- package/src/constants.ts +23 -0
- package/src/icons/CloseIcon.tsx +10 -0
- package/src/icons/GreenCheck.tsx +16 -0
- package/src/icons/InfoIcon.tsx +12 -0
- package/src/icons/RedX.tsx +16 -0
- package/src/icons/index.ts +4 -0
- package/src/index.ts +28 -0
- package/src/pool.ts +57 -0
- package/src/toast-api.ts +247 -0
- package/src/toast-icons.tsx +55 -0
- package/src/toast-provider.tsx +127 -0
- package/src/toast-store.ts +254 -0
- package/src/toast.tsx +398 -0
- package/src/types.ts +166 -0
- package/src/use-toast-state.ts +78 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { TextStyle, ViewStyle } from "react-native";
|
|
3
|
+
import type { SharedValue } from "react-native-reanimated";
|
|
4
|
+
|
|
5
|
+
export type ToastType = "success" | "error" | "info" | "loading";
|
|
6
|
+
|
|
7
|
+
export type ToastPosition = "top" | "bottom";
|
|
8
|
+
|
|
9
|
+
export interface ToastTypeColors {
|
|
10
|
+
/** Accent color used for title text and icons */
|
|
11
|
+
accent: string;
|
|
12
|
+
/** Background color of the toast */
|
|
13
|
+
background: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Props passed to custom icon render functions */
|
|
17
|
+
export interface IconProps {
|
|
18
|
+
/** The accent color from the theme for this toast type */
|
|
19
|
+
color: string;
|
|
20
|
+
/** Default icon size */
|
|
21
|
+
size: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Props passed to custom content render functions */
|
|
25
|
+
export interface CustomContentProps {
|
|
26
|
+
/** Toast ID */
|
|
27
|
+
id: string;
|
|
28
|
+
/** Dismiss this toast */
|
|
29
|
+
dismiss: () => void;
|
|
30
|
+
/** Toast type */
|
|
31
|
+
type: ToastType;
|
|
32
|
+
/** Whether the toast is currently exiting */
|
|
33
|
+
isExiting: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Custom content render function for fully custom toasts */
|
|
37
|
+
export type CustomContentRenderFn = (props: CustomContentProps) => ReactNode;
|
|
38
|
+
|
|
39
|
+
/** Custom icon render function */
|
|
40
|
+
export type IconRenderFn = (props: IconProps) => ReactNode;
|
|
41
|
+
|
|
42
|
+
export interface ToastTheme {
|
|
43
|
+
/** Position of toasts on screen */
|
|
44
|
+
position: ToastPosition;
|
|
45
|
+
/** Extra offset from safe area edge (in addition to safe area insets) */
|
|
46
|
+
offset: number;
|
|
47
|
+
/**
|
|
48
|
+
* Enable right-to-left layout at the code level (reverses icon/text order and text alignment).
|
|
49
|
+
* Only needed when you handle RTL in JavaScript — native RTL (e.g. via `I18nManager.forceRTL`)
|
|
50
|
+
* already flips the entire layout automatically, so this option is unnecessary in that case.
|
|
51
|
+
*/
|
|
52
|
+
rtl: boolean;
|
|
53
|
+
/** Whether to show multiple toasts stacked (default: true). When false, only one toast shows at a time. */
|
|
54
|
+
stacking: boolean;
|
|
55
|
+
/** Maximum number of toasts visible at once when stacking is enabled (default: 3) */
|
|
56
|
+
maxStack: number;
|
|
57
|
+
/** Whether toasts can be dismissed via swipe gesture (default: true) */
|
|
58
|
+
dismissible: boolean;
|
|
59
|
+
/** Whether to show the close button on toasts (default: true). Loading toasts never show close button. */
|
|
60
|
+
showCloseButton: boolean;
|
|
61
|
+
/** Colors for each toast type */
|
|
62
|
+
colors: Record<ToastType, ToastTypeColors>;
|
|
63
|
+
/** Custom icons for each toast type */
|
|
64
|
+
icons: Partial<Record<ToastType, IconRenderFn>>;
|
|
65
|
+
/** Style overrides for the toast container */
|
|
66
|
+
toastStyle: ViewStyle;
|
|
67
|
+
/** Style overrides for the title text */
|
|
68
|
+
titleStyle: TextStyle;
|
|
69
|
+
/** Style overrides for the description text */
|
|
70
|
+
descriptionStyle: TextStyle;
|
|
71
|
+
/** Default duration in ms for toasts (default: 4000) */
|
|
72
|
+
defaultDuration: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Per-toast options for customizing individual toasts */
|
|
76
|
+
export interface ToastOptions {
|
|
77
|
+
/** Description text */
|
|
78
|
+
description?: string;
|
|
79
|
+
/** Duration in ms (overrides default) */
|
|
80
|
+
duration?: number;
|
|
81
|
+
/** Custom icon (ReactNode or render function) */
|
|
82
|
+
icon?: ReactNode | IconRenderFn;
|
|
83
|
+
/** Style overrides for this toast's container */
|
|
84
|
+
style?: ViewStyle;
|
|
85
|
+
/** Style overrides for this toast's title */
|
|
86
|
+
titleStyle?: TextStyle;
|
|
87
|
+
/** Style overrides for this toast's description */
|
|
88
|
+
descriptionStyle?: TextStyle;
|
|
89
|
+
/** Whether this toast can be dismissed via swipe (overrides config) */
|
|
90
|
+
dismissible?: boolean;
|
|
91
|
+
/** Whether to show the close button on this toast (overrides config) */
|
|
92
|
+
showCloseButton?: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Custom content that fully replaces the default toast layout.
|
|
95
|
+
* When provided, icon, title, description, and close button are not rendered.
|
|
96
|
+
* Receives props: { id, dismiss, type, isExiting }
|
|
97
|
+
*/
|
|
98
|
+
customContent?: ReactNode | CustomContentRenderFn;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Configuration options for customizing toast behavior and appearance. All properties are optional. */
|
|
102
|
+
export type ToastConfig = {
|
|
103
|
+
[K in keyof ToastTheme]?: K extends "colors"
|
|
104
|
+
? Partial<Record<ToastType, Partial<ToastTypeColors>>>
|
|
105
|
+
: K extends "icons"
|
|
106
|
+
? Partial<Record<ToastType, IconRenderFn>>
|
|
107
|
+
: ToastTheme[K];
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export interface Toast {
|
|
111
|
+
id: string;
|
|
112
|
+
title: string;
|
|
113
|
+
description?: string;
|
|
114
|
+
type: ToastType;
|
|
115
|
+
duration: number;
|
|
116
|
+
createdAt: number;
|
|
117
|
+
isExiting?: boolean;
|
|
118
|
+
/** Per-toast style/icon overrides */
|
|
119
|
+
options?: ToastOptions;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface ToastState {
|
|
123
|
+
/** Visible toasts (index 0 = front/newest) */
|
|
124
|
+
visibleToasts: Toast[];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export type MessageInput =
|
|
128
|
+
| string
|
|
129
|
+
| {
|
|
130
|
+
title: string;
|
|
131
|
+
description?: string;
|
|
132
|
+
/** Override duration (ms) after promise settles */
|
|
133
|
+
duration?: number;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export type ErrorMessageInput = MessageInput | ((error: Error) => MessageInput);
|
|
137
|
+
|
|
138
|
+
export interface PromiseMessages {
|
|
139
|
+
loading: MessageInput;
|
|
140
|
+
success: MessageInput;
|
|
141
|
+
error: ErrorMessageInput;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface PromiseResult<T> {
|
|
145
|
+
data?: T;
|
|
146
|
+
error?: Error;
|
|
147
|
+
success: boolean;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface TopToastRef {
|
|
151
|
+
slot: {
|
|
152
|
+
progress: SharedValue<number>;
|
|
153
|
+
translationY: SharedValue<number>;
|
|
154
|
+
stackIndex: SharedValue<number>;
|
|
155
|
+
};
|
|
156
|
+
dismiss: () => void;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface ToastItemProps {
|
|
160
|
+
toast: Toast;
|
|
161
|
+
index: number;
|
|
162
|
+
theme: ToastTheme;
|
|
163
|
+
position: ToastPosition;
|
|
164
|
+
isTopToast: boolean;
|
|
165
|
+
registerTopToast: (values: TopToastRef | null) => void;
|
|
166
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { makeMutable } from "react-native-reanimated";
|
|
3
|
+
import { type Toast, type ToastState, toastStore } from "./toast-store";
|
|
4
|
+
import type { ToastTheme, TopToastRef } from "./types";
|
|
5
|
+
|
|
6
|
+
export const useToastState = () => {
|
|
7
|
+
const [visibleToasts, setVisibleToasts] = useState<Toast[]>([]);
|
|
8
|
+
const [theme, setTheme] = useState<ToastTheme>(() => toastStore.getTheme());
|
|
9
|
+
|
|
10
|
+
const [topToastMutable] = useState(() => makeMutable<TopToastRef | null>(null));
|
|
11
|
+
const [isBottomMutable] = useState(() => makeMutable(theme.position === "bottom"));
|
|
12
|
+
const [isDismissibleMutable] = useState(() => makeMutable(true));
|
|
13
|
+
|
|
14
|
+
const isBottom = theme.position === "bottom";
|
|
15
|
+
const topToast = visibleToasts.find(t => !t.isExiting);
|
|
16
|
+
const isTopDismissible = topToast?.options?.dismissible ?? theme.dismissible;
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
const initialToasts = toastStore.getState().visibleToasts;
|
|
20
|
+
const initialTheme = toastStore.getTheme();
|
|
21
|
+
|
|
22
|
+
setVisibleToasts(initialToasts);
|
|
23
|
+
|
|
24
|
+
const initialTopToast = initialToasts.find(t => !t.isExiting);
|
|
25
|
+
isBottomMutable.set(initialTheme.position === "bottom");
|
|
26
|
+
isDismissibleMutable.set(initialTopToast?.options?.dismissible ?? initialTheme.dismissible);
|
|
27
|
+
|
|
28
|
+
let pendingToasts: Toast[] | null = null;
|
|
29
|
+
let rafId: number | null = null;
|
|
30
|
+
|
|
31
|
+
const unsubscribe = toastStore.subscribe((state: ToastState) => {
|
|
32
|
+
pendingToasts = state.visibleToasts;
|
|
33
|
+
if (rafId === null) {
|
|
34
|
+
rafId = requestAnimationFrame(() => {
|
|
35
|
+
const currentToasts = pendingToasts ?? toastStore.getState().visibleToasts;
|
|
36
|
+
const currentTheme = toastStore.getTheme();
|
|
37
|
+
|
|
38
|
+
if (pendingToasts) {
|
|
39
|
+
setVisibleToasts(pendingToasts);
|
|
40
|
+
pendingToasts = null;
|
|
41
|
+
}
|
|
42
|
+
rafId = null;
|
|
43
|
+
setTheme((prev: ToastTheme) => (prev === currentTheme ? prev : currentTheme));
|
|
44
|
+
|
|
45
|
+
const topToast = currentToasts.find(t => !t.isExiting);
|
|
46
|
+
isBottomMutable.set(currentTheme.position === "bottom");
|
|
47
|
+
isDismissibleMutable.set(topToast?.options?.dismissible ?? currentTheme.dismissible);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return unsubscribe;
|
|
53
|
+
}, [isBottomMutable, isDismissibleMutable]);
|
|
54
|
+
|
|
55
|
+
const toastsWithIndex = useMemo(() => {
|
|
56
|
+
const indices = new Map<string, number>();
|
|
57
|
+
let visualIndex = 0;
|
|
58
|
+
for (const t of visibleToasts) {
|
|
59
|
+
indices.set(t.id, t.isExiting ? -1 : visualIndex);
|
|
60
|
+
if (!t.isExiting) visualIndex++;
|
|
61
|
+
}
|
|
62
|
+
return [...visibleToasts].reverse().map(t => ({
|
|
63
|
+
toast: t,
|
|
64
|
+
index: indices.get(t.id) ?? 0,
|
|
65
|
+
}));
|
|
66
|
+
}, [visibleToasts]);
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
visibleToasts,
|
|
70
|
+
theme,
|
|
71
|
+
toastsWithIndex,
|
|
72
|
+
isBottom,
|
|
73
|
+
isTopDismissible,
|
|
74
|
+
topToastMutable,
|
|
75
|
+
isBottomMutable,
|
|
76
|
+
isDismissibleMutable,
|
|
77
|
+
};
|
|
78
|
+
};
|