react-native-toast-signal 0.1.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/LICENSE +20 -0
- package/README.md +353 -0
- package/lib/module/components/signal-item.js +482 -0
- package/lib/module/components/signal-item.js.map +1 -0
- package/lib/module/constants.js +62 -0
- package/lib/module/constants.js.map +1 -0
- package/lib/module/core/store.js +158 -0
- package/lib/module/core/store.js.map +1 -0
- package/lib/module/hooks/useSignal.js +71 -0
- package/lib/module/hooks/useSignal.js.map +1 -0
- package/lib/module/index.js +6 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/provider/signal-provider.js +72 -0
- package/lib/module/provider/signal-provider.js.map +1 -0
- package/lib/module/types.js +4 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/components/signal-item.d.ts +7 -0
- package/lib/typescript/src/components/signal-item.d.ts.map +1 -0
- package/lib/typescript/src/constants.d.ts +12 -0
- package/lib/typescript/src/constants.d.ts.map +1 -0
- package/lib/typescript/src/core/store.d.ts +48 -0
- package/lib/typescript/src/core/store.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useSignal.d.ts +46 -0
- package/lib/typescript/src/hooks/useSignal.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +4 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/provider/signal-provider.d.ts +7 -0
- package/lib/typescript/src/provider/signal-provider.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +73 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +174 -0
- package/src/components/signal-item.tsx +548 -0
- package/src/constants.ts +62 -0
- package/src/core/store.ts +171 -0
- package/src/hooks/useSignal.ts +116 -0
- package/src/index.tsx +3 -0
- package/src/provider/signal-provider.tsx +89 -0
- package/src/types.ts +88 -0
package/src/constants.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { SignalTheme } from './types';
|
|
2
|
+
|
|
3
|
+
export const MAX_VISIBLE = 3;
|
|
4
|
+
export const DEFAULT_DURATION = 3000;
|
|
5
|
+
export const STACK_OFFSET = 14;
|
|
6
|
+
export const SCALE_STEP = 0.05;
|
|
7
|
+
export const ENTRY_OFFSET_Y = 80;
|
|
8
|
+
export const SWIPE_THRESHOLD = 100;
|
|
9
|
+
export const TIMING_MS = 280;
|
|
10
|
+
|
|
11
|
+
export const DEFAULT_THEME: SignalTheme = {
|
|
12
|
+
success: {
|
|
13
|
+
background: '#0a0a0a',
|
|
14
|
+
border: '#22c55e40',
|
|
15
|
+
titleColor: '#ffffff',
|
|
16
|
+
descriptionColor: '#d1fae5',
|
|
17
|
+
iconColor: '#22c55e',
|
|
18
|
+
},
|
|
19
|
+
error: {
|
|
20
|
+
background: '#0a0a0a',
|
|
21
|
+
border: '#ef444440',
|
|
22
|
+
titleColor: '#ffffff',
|
|
23
|
+
descriptionColor: '#fee2e2',
|
|
24
|
+
iconColor: '#ef4444',
|
|
25
|
+
},
|
|
26
|
+
warning: {
|
|
27
|
+
background: '#0a0a0a',
|
|
28
|
+
border: '#f59e0b40',
|
|
29
|
+
titleColor: '#ffffff',
|
|
30
|
+
descriptionColor: '#fef3c7',
|
|
31
|
+
iconColor: '#f59e0b',
|
|
32
|
+
},
|
|
33
|
+
info: {
|
|
34
|
+
background: '#0a0a0a',
|
|
35
|
+
border: '#ffffff26',
|
|
36
|
+
titleColor: '#ffffff',
|
|
37
|
+
descriptionColor: '#ffffffcc',
|
|
38
|
+
iconColor: '#60a5fa',
|
|
39
|
+
},
|
|
40
|
+
loading: {
|
|
41
|
+
background: '#0a0a0a',
|
|
42
|
+
border: '#a78bfa40',
|
|
43
|
+
titleColor: '#ffffff',
|
|
44
|
+
descriptionColor: '#ede9fe',
|
|
45
|
+
iconColor: '#a78bfa',
|
|
46
|
+
},
|
|
47
|
+
custom: {
|
|
48
|
+
background: '#0a0a0a',
|
|
49
|
+
border: '#ffffff26',
|
|
50
|
+
titleColor: '#ffffff',
|
|
51
|
+
descriptionColor: '#ffffffcc',
|
|
52
|
+
iconColor: '#ffffff',
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/** Unicode icons for non-loading, non-custom types */
|
|
57
|
+
export const TYPE_ICONS: Partial<Record<string, string>> = {
|
|
58
|
+
success: '✓',
|
|
59
|
+
error: '✕',
|
|
60
|
+
warning: '⚠',
|
|
61
|
+
info: 'ℹ',
|
|
62
|
+
};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { MAX_VISIBLE, DEFAULT_DURATION } from '../constants';
|
|
2
|
+
import type {
|
|
3
|
+
SignalListener,
|
|
4
|
+
SignalOptions,
|
|
5
|
+
SignalPromiseMessages,
|
|
6
|
+
} from '../types';
|
|
7
|
+
|
|
8
|
+
class SignalStore {
|
|
9
|
+
private listener: SignalListener | null = null;
|
|
10
|
+
private signals: SignalOptions[] = [];
|
|
11
|
+
private maxVisible: number = MAX_VISIBLE;
|
|
12
|
+
private defaultDuration: number = DEFAULT_DURATION;
|
|
13
|
+
|
|
14
|
+
configure(options: { maxVisible?: number; defaultDuration?: number }) {
|
|
15
|
+
if (options.maxVisible !== undefined) this.maxVisible = options.maxVisible;
|
|
16
|
+
if (options.defaultDuration !== undefined)
|
|
17
|
+
this.defaultDuration = options.defaultDuration;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
getMaxVisible() {
|
|
21
|
+
return this.maxVisible;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
setListener(listener: SignalListener) {
|
|
25
|
+
this.listener = listener;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
removeListener() {
|
|
29
|
+
this.listener = null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
show(options: SignalOptions): string {
|
|
33
|
+
const now = Date.now();
|
|
34
|
+
const id =
|
|
35
|
+
options.id ?? `signal_${now}_${Math.random().toString(36).slice(2, 7)}`;
|
|
36
|
+
|
|
37
|
+
if (this.signals.some((s) => s.id === id)) return id;
|
|
38
|
+
|
|
39
|
+
const isLoading = options.type === 'loading';
|
|
40
|
+
|
|
41
|
+
const signal: SignalOptions = {
|
|
42
|
+
type: 'info',
|
|
43
|
+
position: 'top',
|
|
44
|
+
swipeToDismiss: !isLoading, // loading toasts shouldn't be accidentally swiped away
|
|
45
|
+
autoHide: !isLoading, // loading toasts stay until manually dismissed
|
|
46
|
+
duration: this.defaultDuration,
|
|
47
|
+
...options,
|
|
48
|
+
id,
|
|
49
|
+
createdAt: now,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
this.signals.unshift(signal);
|
|
53
|
+
this.emit();
|
|
54
|
+
return id;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Remove a toast by id */
|
|
58
|
+
hide(id: string) {
|
|
59
|
+
const len = this.signals.length;
|
|
60
|
+
this.signals = this.signals.filter((s) => s.id !== id);
|
|
61
|
+
if (this.signals.length !== len) this.emit();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Alias for hide() */
|
|
65
|
+
dismiss(id: string) {
|
|
66
|
+
this.hide(id);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Update any fields of an existing toast in-place */
|
|
70
|
+
update(
|
|
71
|
+
id: string,
|
|
72
|
+
options: Partial<Omit<SignalOptions, 'id' | 'createdAt'>>
|
|
73
|
+
) {
|
|
74
|
+
const idx = this.signals.findIndex((s) => s.id === id);
|
|
75
|
+
if (idx === -1) return;
|
|
76
|
+
this.signals[idx] = { ...this.signals[idx]!, ...options };
|
|
77
|
+
this.emit();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Remove all toasts */
|
|
81
|
+
clear() {
|
|
82
|
+
if (this.signals.length === 0) return;
|
|
83
|
+
this.signals = [];
|
|
84
|
+
this.emit();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ─── Convenience shorthands ───────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
success(description: string, options?: Partial<SignalOptions>) {
|
|
90
|
+
return this.show({ ...options, description, type: 'success' });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
error(description: string, options?: Partial<SignalOptions>) {
|
|
94
|
+
return this.show({ ...options, description, type: 'error' });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
warning(description: string, options?: Partial<SignalOptions>) {
|
|
98
|
+
return this.show({ ...options, description, type: 'warning' });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
info(description: string, options?: Partial<SignalOptions>) {
|
|
102
|
+
return this.show({ ...options, description, type: 'info' });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Show an infinite loading toast.
|
|
107
|
+
* Returns the id — call Signal.dismiss(id) when the operation completes.
|
|
108
|
+
*/
|
|
109
|
+
loading(description: string, options?: Partial<SignalOptions>) {
|
|
110
|
+
return this.show({ ...options, description, type: 'loading' });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Attach a Promise to a toast lifecycle.
|
|
115
|
+
* Automatically transitions loading → success or error.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* Signal.promise(uploadFile(), {
|
|
119
|
+
* loading: 'Uploading…',
|
|
120
|
+
* success: (data) => `Uploaded ${data.name}!`,
|
|
121
|
+
* error: (err) => err.message ?? 'Upload failed',
|
|
122
|
+
* });
|
|
123
|
+
*/
|
|
124
|
+
promise<T = unknown>(
|
|
125
|
+
promise: Promise<T>,
|
|
126
|
+
messages: SignalPromiseMessages<T>,
|
|
127
|
+
options?: Partial<
|
|
128
|
+
Omit<SignalOptions, 'type' | 'autoHide' | 'swipeToDismiss'>
|
|
129
|
+
>
|
|
130
|
+
): Promise<T> {
|
|
131
|
+
const id = this.loading(messages.loading, { ...options });
|
|
132
|
+
|
|
133
|
+
promise
|
|
134
|
+
.then((data) => {
|
|
135
|
+
const description =
|
|
136
|
+
typeof messages.success === 'function'
|
|
137
|
+
? messages.success(data)
|
|
138
|
+
: messages.success;
|
|
139
|
+
this.update(id, {
|
|
140
|
+
type: 'success',
|
|
141
|
+
description,
|
|
142
|
+
autoHide: true,
|
|
143
|
+
swipeToDismiss: true,
|
|
144
|
+
|
|
145
|
+
duration: options?.duration ?? this.defaultDuration,
|
|
146
|
+
});
|
|
147
|
+
})
|
|
148
|
+
.catch((err: unknown) => {
|
|
149
|
+
const description =
|
|
150
|
+
typeof messages.error === 'function'
|
|
151
|
+
? messages.error(err)
|
|
152
|
+
: messages.error;
|
|
153
|
+
this.update(id, {
|
|
154
|
+
type: 'error',
|
|
155
|
+
description,
|
|
156
|
+
autoHide: true,
|
|
157
|
+
swipeToDismiss: true,
|
|
158
|
+
|
|
159
|
+
duration: options?.duration ?? this.defaultDuration,
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return promise;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private emit() {
|
|
167
|
+
this.listener?.([...this.signals]);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export const Signal = new SignalStore();
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { Signal } from '../core/store';
|
|
3
|
+
import type { SignalOptions, SignalPromiseMessages } from '../types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Hook to imperatively control Signal toasts from any component.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* const signal = useSignal();
|
|
10
|
+
*
|
|
11
|
+
* // Simple
|
|
12
|
+
* signal.success('Saved!');
|
|
13
|
+
* signal.error('Failed', { title: 'Oops' });
|
|
14
|
+
*
|
|
15
|
+
* // Loading → dismiss manually
|
|
16
|
+
* const id = signal.loading('Uploading…');
|
|
17
|
+
* await upload();
|
|
18
|
+
* signal.dismiss(id);
|
|
19
|
+
* signal.success('Done!');
|
|
20
|
+
*
|
|
21
|
+
* // Promise shorthand (auto loading → success/error)
|
|
22
|
+
* signal.promise(uploadFile(), {
|
|
23
|
+
* loading: 'Uploading…',
|
|
24
|
+
* success: (data) => `Uploaded ${data.name}!`,
|
|
25
|
+
* error: (err) => err.message ?? 'Upload failed',
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* // With action button
|
|
29
|
+
* signal.error('Payment failed', {
|
|
30
|
+
* action: { label: 'Retry', onPress: (dismiss) => { retry(); dismiss(); } },
|
|
31
|
+
* });
|
|
32
|
+
*
|
|
33
|
+
* // Custom icon
|
|
34
|
+
* signal.show({ description: 'New follower!', icon: <Avatar uri={url} size={20} /> });
|
|
35
|
+
*/
|
|
36
|
+
export const useSignal = () => {
|
|
37
|
+
const show = useCallback(
|
|
38
|
+
(options: SignalOptions): string => Signal.show(options),
|
|
39
|
+
[]
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const success = useCallback(
|
|
43
|
+
(description: string, options?: Partial<SignalOptions>): string =>
|
|
44
|
+
Signal.success(description, options),
|
|
45
|
+
[]
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const error = useCallback(
|
|
49
|
+
(description: string, options?: Partial<SignalOptions>): string =>
|
|
50
|
+
Signal.error(description, options),
|
|
51
|
+
[]
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const warning = useCallback(
|
|
55
|
+
(description: string, options?: Partial<SignalOptions>): string =>
|
|
56
|
+
Signal.warning(description, options),
|
|
57
|
+
[]
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const info = useCallback(
|
|
61
|
+
(description: string, options?: Partial<SignalOptions>): string =>
|
|
62
|
+
Signal.info(description, options),
|
|
63
|
+
[]
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
/** Show an infinite loading toast. Returns id — call dismiss(id) when done. */
|
|
67
|
+
const loading = useCallback(
|
|
68
|
+
(description: string, options?: Partial<SignalOptions>): string =>
|
|
69
|
+
Signal.loading(description, options),
|
|
70
|
+
[]
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Attach a Promise to a toast lifecycle.
|
|
75
|
+
* Automatically transitions loading → success or error when the promise settles.
|
|
76
|
+
*/
|
|
77
|
+
const promise = useCallback(
|
|
78
|
+
<T = unknown>(
|
|
79
|
+
p: Promise<T>,
|
|
80
|
+
messages: SignalPromiseMessages<T>,
|
|
81
|
+
options?: Partial<
|
|
82
|
+
Omit<SignalOptions, 'type' | 'autoHide' | 'swipeToDismiss'>
|
|
83
|
+
>
|
|
84
|
+
): Promise<T> => Signal.promise(p, messages, options),
|
|
85
|
+
[]
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const hide = useCallback((id: string): void => Signal.hide(id), []);
|
|
89
|
+
|
|
90
|
+
/** Alias for hide() */
|
|
91
|
+
const dismiss = useCallback((id: string): void => Signal.dismiss(id), []);
|
|
92
|
+
|
|
93
|
+
const update = useCallback(
|
|
94
|
+
(
|
|
95
|
+
id: string,
|
|
96
|
+
options: Partial<Omit<SignalOptions, 'id' | 'createdAt'>>
|
|
97
|
+
): void => Signal.update(id, options),
|
|
98
|
+
[]
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const clear = useCallback((): void => Signal.clear(), []);
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
show,
|
|
105
|
+
success,
|
|
106
|
+
error,
|
|
107
|
+
warning,
|
|
108
|
+
info,
|
|
109
|
+
loading,
|
|
110
|
+
promise,
|
|
111
|
+
hide,
|
|
112
|
+
dismiss,
|
|
113
|
+
update,
|
|
114
|
+
clear,
|
|
115
|
+
};
|
|
116
|
+
};
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import { View, StyleSheet } from 'react-native';
|
|
3
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
4
|
+
import { Signal } from '../core/store';
|
|
5
|
+
import { SignalItem } from '../components/signal-item';
|
|
6
|
+
import { MAX_VISIBLE, DEFAULT_DURATION } from '../constants';
|
|
7
|
+
import type { SignalOptions, SignalProviderProps, SignalTheme } from '../types';
|
|
8
|
+
|
|
9
|
+
interface Props extends SignalProviderProps {
|
|
10
|
+
theme?: Partial<SignalTheme>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const SignalProvider = ({
|
|
14
|
+
children,
|
|
15
|
+
maxVisible = MAX_VISIBLE,
|
|
16
|
+
defaultDuration = DEFAULT_DURATION,
|
|
17
|
+
theme,
|
|
18
|
+
}: Props) => {
|
|
19
|
+
const insets = useSafeAreaInsets();
|
|
20
|
+
const [signals, setSignals] = useState<SignalOptions[]>([]);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
Signal.configure({ maxVisible, defaultDuration });
|
|
24
|
+
}, [maxVisible, defaultDuration]);
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
Signal.setListener(setSignals);
|
|
28
|
+
return () => Signal.removeListener();
|
|
29
|
+
}, []);
|
|
30
|
+
|
|
31
|
+
const handleHide = useCallback((id: string) => {
|
|
32
|
+
Signal.hide(id);
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
35
|
+
const topSignals = signals.filter((s) => (s.position ?? 'top') === 'top');
|
|
36
|
+
const bottomSignals = signals.filter((s) => s.position === 'bottom');
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<>
|
|
40
|
+
{children}
|
|
41
|
+
|
|
42
|
+
{/* Top stack */}
|
|
43
|
+
<View
|
|
44
|
+
pointerEvents="box-none"
|
|
45
|
+
style={[styles.wrapper, { top: insets.top + 8 }]}
|
|
46
|
+
>
|
|
47
|
+
{topSignals.map((signal, idx) => (
|
|
48
|
+
<SignalItem
|
|
49
|
+
key={signal.id}
|
|
50
|
+
signal={signal}
|
|
51
|
+
index={idx}
|
|
52
|
+
maxVisible={maxVisible}
|
|
53
|
+
theme={theme}
|
|
54
|
+
onHide={() => handleHide(signal.id!)}
|
|
55
|
+
/>
|
|
56
|
+
))}
|
|
57
|
+
</View>
|
|
58
|
+
|
|
59
|
+
{/* Bottom stack */}
|
|
60
|
+
<View
|
|
61
|
+
pointerEvents="box-none"
|
|
62
|
+
style={[styles.wrapper, { bottom: insets.bottom + 8 }]}
|
|
63
|
+
>
|
|
64
|
+
{bottomSignals.map((signal, idx) => (
|
|
65
|
+
<SignalItem
|
|
66
|
+
key={signal.id}
|
|
67
|
+
signal={signal}
|
|
68
|
+
index={idx}
|
|
69
|
+
maxVisible={maxVisible}
|
|
70
|
+
theme={theme}
|
|
71
|
+
onHide={() => handleHide(signal.id!)}
|
|
72
|
+
/>
|
|
73
|
+
))}
|
|
74
|
+
</View>
|
|
75
|
+
</>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const styles = StyleSheet.create({
|
|
80
|
+
wrapper: {
|
|
81
|
+
position: 'absolute',
|
|
82
|
+
left: 0,
|
|
83
|
+
right: 0,
|
|
84
|
+
// height: 0 so wrapper doesn't block touches outside the toasts
|
|
85
|
+
height: 0,
|
|
86
|
+
zIndex: 9999,
|
|
87
|
+
elevation: 9999, // Android needs elevation, not just zIndex
|
|
88
|
+
},
|
|
89
|
+
});
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export type SignalType =
|
|
4
|
+
| 'success'
|
|
5
|
+
| 'error'
|
|
6
|
+
| 'warning'
|
|
7
|
+
| 'info'
|
|
8
|
+
| 'loading'
|
|
9
|
+
| 'custom';
|
|
10
|
+
|
|
11
|
+
export type SignalPosition = 'top' | 'bottom';
|
|
12
|
+
|
|
13
|
+
export interface SignalAction {
|
|
14
|
+
/** Button label */
|
|
15
|
+
label: string;
|
|
16
|
+
/** Called when tapped. Receives a dismiss callback so you control whether to close. */
|
|
17
|
+
onPress: (dismiss: () => void) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface SignalOptions {
|
|
21
|
+
/** Unique ID — auto-generated if omitted */
|
|
22
|
+
id?: string;
|
|
23
|
+
/** Optional bold headline above the description */
|
|
24
|
+
title?: string;
|
|
25
|
+
/** Main body text (required) */
|
|
26
|
+
description: string;
|
|
27
|
+
/** Duration in ms before auto-dismiss. Default: 3000. Loading defaults to 0 (infinite). */
|
|
28
|
+
duration?: number;
|
|
29
|
+
type?: SignalType;
|
|
30
|
+
/** Default: 'top' */
|
|
31
|
+
position?: SignalPosition;
|
|
32
|
+
/** Set automatically */
|
|
33
|
+
createdAt?: number;
|
|
34
|
+
/** Allow swipe-to-dismiss. Default: true */
|
|
35
|
+
swipeToDismiss?: boolean;
|
|
36
|
+
/** Auto-dismiss after duration. Default: true. Loading defaults to false. */
|
|
37
|
+
autoHide?: boolean;
|
|
38
|
+
/** Called once entry animation finishes */
|
|
39
|
+
onShow?: () => void;
|
|
40
|
+
/** Called after toast fully exits */
|
|
41
|
+
onHide?: () => void;
|
|
42
|
+
/** Called when the toast body is tapped. Receives a dismiss callback. */
|
|
43
|
+
onPress?: (dismiss: () => void) => void;
|
|
44
|
+
/**
|
|
45
|
+
* Optional CTA button rendered below the description.
|
|
46
|
+
* @example action={{ label: 'Retry', onPress: (dismiss) => { retry(); dismiss(); } }}
|
|
47
|
+
*/
|
|
48
|
+
action?: SignalAction;
|
|
49
|
+
/**
|
|
50
|
+
* Replace the default type icon with any ReactNode.
|
|
51
|
+
* @example icon={<MyIcon size={16} />}
|
|
52
|
+
*/
|
|
53
|
+
icon?: ReactNode;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface SignalItemProps {
|
|
57
|
+
signal: SignalOptions;
|
|
58
|
+
onHide: () => void;
|
|
59
|
+
index: number;
|
|
60
|
+
maxVisible: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface SignalProviderProps {
|
|
64
|
+
children: React.ReactNode;
|
|
65
|
+
/** Maximum toasts visible at once. Default: 3 */
|
|
66
|
+
maxVisible?: number;
|
|
67
|
+
/** Default auto-dismiss duration in ms. Default: 3000 */
|
|
68
|
+
defaultDuration?: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type SignalListener = (signals: SignalOptions[]) => void;
|
|
72
|
+
|
|
73
|
+
export interface SignalTypeTheme {
|
|
74
|
+
background: string;
|
|
75
|
+
border: string;
|
|
76
|
+
titleColor: string;
|
|
77
|
+
descriptionColor: string;
|
|
78
|
+
iconColor: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Full theme map — all six types */
|
|
82
|
+
export type SignalTheme = Record<SignalType, SignalTypeTheme>;
|
|
83
|
+
|
|
84
|
+
export interface SignalPromiseMessages<T = unknown> {
|
|
85
|
+
loading: string;
|
|
86
|
+
success: string | ((data: T) => string);
|
|
87
|
+
error: string | ((err: unknown) => string);
|
|
88
|
+
}
|