@sudobility/components-rn 1.0.20 → 1.0.22
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/index.cjs.js +2898 -77
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +2900 -79
- package/dist/index.esm.js.map +1 -1
- package/dist/lib/utils.d.ts +7 -2
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/ui/Alert/Alert.d.ts +8 -0
- package/dist/ui/Alert/Alert.d.ts.map +1 -1
- package/dist/ui/Banner/Banner.d.ts +97 -0
- package/dist/ui/Banner/Banner.d.ts.map +1 -0
- package/dist/ui/Banner/index.d.ts +2 -0
- package/dist/ui/Banner/index.d.ts.map +1 -0
- package/dist/ui/Button/Button.d.ts +2 -2
- package/dist/ui/Button/Button.shared.d.ts +18 -3
- package/dist/ui/Button/Button.shared.d.ts.map +1 -1
- package/dist/ui/Card/Card.d.ts +32 -0
- package/dist/ui/Card/Card.d.ts.map +1 -1
- package/dist/ui/Spinner/Spinner.d.ts +8 -0
- package/dist/ui/Spinner/Spinner.d.ts.map +1 -1
- package/dist/ui/Toast/Toast.d.ts +11 -0
- package/dist/ui/Toast/Toast.d.ts.map +1 -1
- package/package.json +5 -3
- package/src/index.ts +1 -0
- package/src/lib/utils.ts +7 -2
- package/src/ui/Alert/Alert.tsx +8 -0
- package/src/ui/Banner/Banner.tsx +315 -0
- package/src/ui/Banner/index.ts +7 -0
- package/src/ui/Button/Button.shared.ts +18 -3
- package/src/ui/Card/Card.tsx +32 -0
- package/src/ui/Spinner/Spinner.tsx +8 -0
- package/src/ui/Toast/Toast.tsx +11 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
|
2
|
+
import { View, Text, Pressable, Animated } from 'react-native';
|
|
3
|
+
import { InfoType } from '@sudobility/types';
|
|
4
|
+
import { cn } from '../../lib/utils';
|
|
5
|
+
|
|
6
|
+
export interface BannerProps {
|
|
7
|
+
/** Whether the banner is visible */
|
|
8
|
+
isVisible: boolean;
|
|
9
|
+
/** Callback when banner is dismissed */
|
|
10
|
+
onDismiss: () => void;
|
|
11
|
+
/** Banner title */
|
|
12
|
+
title: string;
|
|
13
|
+
/** Banner description (optional) */
|
|
14
|
+
description?: string;
|
|
15
|
+
/** Visual variant */
|
|
16
|
+
variant?: InfoType;
|
|
17
|
+
/** Auto-dismiss duration in milliseconds (0 to disable, default: 5000) */
|
|
18
|
+
duration?: number;
|
|
19
|
+
/** Custom icon (overrides variant icon) */
|
|
20
|
+
icon?: React.ReactNode;
|
|
21
|
+
/** Additional NativeWind classes */
|
|
22
|
+
className?: string;
|
|
23
|
+
/** Accessible label for close button */
|
|
24
|
+
closeAccessibilityLabel?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const variantConfig: Record<
|
|
28
|
+
InfoType,
|
|
29
|
+
{
|
|
30
|
+
icon: string;
|
|
31
|
+
container: string;
|
|
32
|
+
iconColor: string;
|
|
33
|
+
titleColor: string;
|
|
34
|
+
descriptionColor: string;
|
|
35
|
+
}
|
|
36
|
+
> = {
|
|
37
|
+
[InfoType.INFO]: {
|
|
38
|
+
icon: '\u2139',
|
|
39
|
+
container:
|
|
40
|
+
'bg-blue-50 dark:bg-blue-950 border-blue-200 dark:border-blue-800',
|
|
41
|
+
iconColor: 'text-blue-600 dark:text-blue-400',
|
|
42
|
+
titleColor: 'text-blue-900 dark:text-blue-100',
|
|
43
|
+
descriptionColor: 'text-blue-700 dark:text-blue-300',
|
|
44
|
+
},
|
|
45
|
+
[InfoType.SUCCESS]: {
|
|
46
|
+
icon: '\u2713',
|
|
47
|
+
container:
|
|
48
|
+
'bg-green-50 dark:bg-green-950 border-green-200 dark:border-green-800',
|
|
49
|
+
iconColor: 'text-green-600 dark:text-green-400',
|
|
50
|
+
titleColor: 'text-green-900 dark:text-green-100',
|
|
51
|
+
descriptionColor: 'text-green-700 dark:text-green-300',
|
|
52
|
+
},
|
|
53
|
+
[InfoType.WARNING]: {
|
|
54
|
+
icon: '\u26A0',
|
|
55
|
+
container:
|
|
56
|
+
'bg-yellow-50 dark:bg-amber-950 border-yellow-200 dark:border-amber-800',
|
|
57
|
+
iconColor: 'text-yellow-600 dark:text-amber-400',
|
|
58
|
+
titleColor: 'text-yellow-900 dark:text-amber-100',
|
|
59
|
+
descriptionColor: 'text-yellow-700 dark:text-amber-300',
|
|
60
|
+
},
|
|
61
|
+
[InfoType.ERROR]: {
|
|
62
|
+
icon: '\u2717',
|
|
63
|
+
container: 'bg-red-50 dark:bg-red-950 border-red-200 dark:border-red-800',
|
|
64
|
+
iconColor: 'text-red-600 dark:text-red-400',
|
|
65
|
+
titleColor: 'text-red-900 dark:text-red-100',
|
|
66
|
+
descriptionColor: 'text-red-700 dark:text-red-300',
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Banner Component
|
|
72
|
+
*
|
|
73
|
+
* A temporary notification banner that slides down from the top of the screen.
|
|
74
|
+
* React Native port of the web Banner from @sudobility/components.
|
|
75
|
+
*
|
|
76
|
+
* Features:
|
|
77
|
+
* - Slides down from top with spring animation
|
|
78
|
+
* - Auto-dismisses after configurable duration (default 5 seconds)
|
|
79
|
+
* - Manual dismiss via close button
|
|
80
|
+
* - Multiple variants: info, success, warning, error
|
|
81
|
+
* - Dark/light theme support via NativeWind
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```tsx
|
|
85
|
+
* const [showBanner, setShowBanner] = useState(false);
|
|
86
|
+
*
|
|
87
|
+
* <Banner
|
|
88
|
+
* isVisible={showBanner}
|
|
89
|
+
* onDismiss={() => setShowBanner(false)}
|
|
90
|
+
* variant={InfoType.SUCCESS}
|
|
91
|
+
* title="Changes saved"
|
|
92
|
+
* description="Your settings have been updated successfully."
|
|
93
|
+
* />
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
export const Banner: React.FC<BannerProps> = ({
|
|
97
|
+
isVisible,
|
|
98
|
+
onDismiss,
|
|
99
|
+
title,
|
|
100
|
+
description,
|
|
101
|
+
variant = InfoType.INFO,
|
|
102
|
+
duration = 5000,
|
|
103
|
+
icon,
|
|
104
|
+
className,
|
|
105
|
+
closeAccessibilityLabel = 'Dismiss banner',
|
|
106
|
+
}) => {
|
|
107
|
+
const [mounted, setMounted] = useState(false);
|
|
108
|
+
const slideAnim = useRef(new Animated.Value(-100)).current;
|
|
109
|
+
const opacityAnim = useRef(new Animated.Value(0)).current;
|
|
110
|
+
const dismissTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
111
|
+
|
|
112
|
+
const config = variantConfig[variant];
|
|
113
|
+
|
|
114
|
+
const clearDismissTimeout = useCallback(() => {
|
|
115
|
+
if (dismissTimeoutRef.current) {
|
|
116
|
+
clearTimeout(dismissTimeoutRef.current);
|
|
117
|
+
dismissTimeoutRef.current = null;
|
|
118
|
+
}
|
|
119
|
+
}, []);
|
|
120
|
+
|
|
121
|
+
const handleDismiss = useCallback(() => {
|
|
122
|
+
clearDismissTimeout();
|
|
123
|
+
Animated.parallel([
|
|
124
|
+
Animated.timing(slideAnim, {
|
|
125
|
+
toValue: -100,
|
|
126
|
+
duration: 300,
|
|
127
|
+
useNativeDriver: true,
|
|
128
|
+
}),
|
|
129
|
+
Animated.timing(opacityAnim, {
|
|
130
|
+
toValue: 0,
|
|
131
|
+
duration: 300,
|
|
132
|
+
useNativeDriver: true,
|
|
133
|
+
}),
|
|
134
|
+
]).start(() => {
|
|
135
|
+
setMounted(false);
|
|
136
|
+
onDismiss();
|
|
137
|
+
});
|
|
138
|
+
}, [clearDismissTimeout, slideAnim, opacityAnim, onDismiss]);
|
|
139
|
+
|
|
140
|
+
// Handle visibility changes
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
if (isVisible) {
|
|
143
|
+
setMounted(true);
|
|
144
|
+
slideAnim.setValue(-100);
|
|
145
|
+
opacityAnim.setValue(0);
|
|
146
|
+
|
|
147
|
+
Animated.parallel([
|
|
148
|
+
Animated.spring(slideAnim, {
|
|
149
|
+
toValue: 0,
|
|
150
|
+
useNativeDriver: true,
|
|
151
|
+
friction: 8,
|
|
152
|
+
}),
|
|
153
|
+
Animated.timing(opacityAnim, {
|
|
154
|
+
toValue: 1,
|
|
155
|
+
duration: 300,
|
|
156
|
+
useNativeDriver: true,
|
|
157
|
+
}),
|
|
158
|
+
]).start(() => {
|
|
159
|
+
// Start auto-dismiss timer after animation completes
|
|
160
|
+
if (duration > 0) {
|
|
161
|
+
dismissTimeoutRef.current = setTimeout(() => {
|
|
162
|
+
handleDismiss();
|
|
163
|
+
}, duration);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
} else if (mounted) {
|
|
167
|
+
handleDismiss();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return () => clearDismissTimeout();
|
|
171
|
+
}, [isVisible]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
172
|
+
|
|
173
|
+
// Cleanup on unmount
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
return () => clearDismissTimeout();
|
|
176
|
+
}, [clearDismissTimeout]);
|
|
177
|
+
|
|
178
|
+
if (!mounted) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<Animated.View
|
|
184
|
+
style={{
|
|
185
|
+
transform: [{ translateY: slideAnim }],
|
|
186
|
+
opacity: opacityAnim,
|
|
187
|
+
}}
|
|
188
|
+
className={cn('absolute top-0 left-0 right-0 z-50', className)}
|
|
189
|
+
accessibilityRole='alert'
|
|
190
|
+
accessibilityLiveRegion='polite'
|
|
191
|
+
>
|
|
192
|
+
<View
|
|
193
|
+
className={cn(
|
|
194
|
+
'flex-row items-start gap-3 p-4 border-b',
|
|
195
|
+
config.container
|
|
196
|
+
)}
|
|
197
|
+
>
|
|
198
|
+
{/* Icon */}
|
|
199
|
+
<View className='flex-shrink-0 mt-0.5'>
|
|
200
|
+
{icon || (
|
|
201
|
+
<Text className={cn('text-lg', config.iconColor)}>
|
|
202
|
+
{config.icon}
|
|
203
|
+
</Text>
|
|
204
|
+
)}
|
|
205
|
+
</View>
|
|
206
|
+
|
|
207
|
+
{/* Content */}
|
|
208
|
+
<View className='flex-1 min-w-0'>
|
|
209
|
+
<Text className={cn('font-semibold', config.titleColor)}>
|
|
210
|
+
{title}
|
|
211
|
+
</Text>
|
|
212
|
+
{description ? (
|
|
213
|
+
<Text className={cn('text-sm mt-1', config.descriptionColor)}>
|
|
214
|
+
{description}
|
|
215
|
+
</Text>
|
|
216
|
+
) : null}
|
|
217
|
+
</View>
|
|
218
|
+
|
|
219
|
+
{/* Close button */}
|
|
220
|
+
<Pressable
|
|
221
|
+
onPress={handleDismiss}
|
|
222
|
+
className='flex-shrink-0 p-1 rounded-md'
|
|
223
|
+
accessibilityRole='button'
|
|
224
|
+
accessibilityLabel={closeAccessibilityLabel}
|
|
225
|
+
>
|
|
226
|
+
<Text className={cn('text-lg', config.iconColor)}>{'\u2717'}</Text>
|
|
227
|
+
</Pressable>
|
|
228
|
+
</View>
|
|
229
|
+
</Animated.View>
|
|
230
|
+
);
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Hook for managing banner state
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* ```tsx
|
|
238
|
+
* const { isVisible, bannerProps, showBanner, hideBanner } = useBanner();
|
|
239
|
+
*
|
|
240
|
+
* showBanner({
|
|
241
|
+
* title: 'Success!',
|
|
242
|
+
* description: 'Your changes have been saved.',
|
|
243
|
+
* variant: InfoType.SUCCESS,
|
|
244
|
+
* });
|
|
245
|
+
*
|
|
246
|
+
* {bannerProps && (
|
|
247
|
+
* <Banner
|
|
248
|
+
* isVisible={isVisible}
|
|
249
|
+
* onDismiss={hideBanner}
|
|
250
|
+
* {...bannerProps}
|
|
251
|
+
* />
|
|
252
|
+
* )}
|
|
253
|
+
* ```
|
|
254
|
+
*/
|
|
255
|
+
export interface UseBannerOptions {
|
|
256
|
+
/** Default duration for auto-dismiss (default: 5000) */
|
|
257
|
+
defaultDuration?: number;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export interface UseBannerReturn {
|
|
261
|
+
/** Whether the banner is currently visible */
|
|
262
|
+
isVisible: boolean;
|
|
263
|
+
/** Current banner props */
|
|
264
|
+
bannerProps: {
|
|
265
|
+
title: string;
|
|
266
|
+
description?: string;
|
|
267
|
+
variant: InfoType;
|
|
268
|
+
duration: number;
|
|
269
|
+
} | null;
|
|
270
|
+
/** Show a banner */
|
|
271
|
+
showBanner: (options: {
|
|
272
|
+
title: string;
|
|
273
|
+
description?: string;
|
|
274
|
+
variant?: InfoType;
|
|
275
|
+
duration?: number;
|
|
276
|
+
}) => void;
|
|
277
|
+
/** Hide the current banner */
|
|
278
|
+
hideBanner: () => void;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export const useBanner = (options: UseBannerOptions = {}): UseBannerReturn => {
|
|
282
|
+
const { defaultDuration = 5000 } = options;
|
|
283
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
284
|
+
const [bannerProps, setBannerProps] =
|
|
285
|
+
useState<UseBannerReturn['bannerProps']>(null);
|
|
286
|
+
|
|
287
|
+
const showBanner = useCallback(
|
|
288
|
+
({
|
|
289
|
+
title,
|
|
290
|
+
description,
|
|
291
|
+
variant = InfoType.INFO,
|
|
292
|
+
duration = defaultDuration,
|
|
293
|
+
}: {
|
|
294
|
+
title: string;
|
|
295
|
+
description?: string;
|
|
296
|
+
variant?: InfoType;
|
|
297
|
+
duration?: number;
|
|
298
|
+
}) => {
|
|
299
|
+
setBannerProps({ title, description, variant, duration });
|
|
300
|
+
setIsVisible(true);
|
|
301
|
+
},
|
|
302
|
+
[defaultDuration]
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
const hideBanner = useCallback(() => {
|
|
306
|
+
setIsVisible(false);
|
|
307
|
+
}, []);
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
isVisible,
|
|
311
|
+
bannerProps,
|
|
312
|
+
showBanner,
|
|
313
|
+
hideBanner,
|
|
314
|
+
};
|
|
315
|
+
};
|
|
@@ -38,7 +38,10 @@ export interface ButtonBaseProps {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
* Map size abbreviations to design system variant keys
|
|
41
|
+
* Map size abbreviations to design system variant keys.
|
|
42
|
+
*
|
|
43
|
+
* @param size - The abbreviated size key ('sm', 'lg', or 'default')
|
|
44
|
+
* @returns The full design system variant key ('small', 'large', or 'default')
|
|
42
45
|
*/
|
|
43
46
|
export const mapSizeToVariantKey = (size: string | undefined): string => {
|
|
44
47
|
if (!size) return 'default';
|
|
@@ -51,7 +54,15 @@ export const mapSizeToVariantKey = (size: string | undefined): string => {
|
|
|
51
54
|
};
|
|
52
55
|
|
|
53
56
|
/**
|
|
54
|
-
* Get button variant class from design system
|
|
57
|
+
* Get button variant class string from the design system.
|
|
58
|
+
*
|
|
59
|
+
* Handles gradient variants, web3 variants (wallet/connect/disconnect),
|
|
60
|
+
* and standard variants with size modifiers.
|
|
61
|
+
*
|
|
62
|
+
* @param variantName - The button variant name (e.g., 'primary', 'gradient', 'wallet')
|
|
63
|
+
* @param sizeName - Optional size abbreviation ('sm' or 'lg')
|
|
64
|
+
* @param v - The design system variants object
|
|
65
|
+
* @returns A Tailwind class string from the design system
|
|
55
66
|
*/
|
|
56
67
|
export const getButtonVariantClass = (
|
|
57
68
|
variantName: string,
|
|
@@ -72,7 +83,11 @@ export const getButtonVariantClass = (
|
|
|
72
83
|
};
|
|
73
84
|
|
|
74
85
|
/**
|
|
75
|
-
* Shared button state logic
|
|
86
|
+
* Shared button state logic for determining disabled and spinner visibility.
|
|
87
|
+
*
|
|
88
|
+
* @param loading - Whether the button is in a loading state
|
|
89
|
+
* @param disabled - Whether the button is explicitly disabled
|
|
90
|
+
* @returns An object with `isDisabled` and `showSpinner` booleans
|
|
76
91
|
*/
|
|
77
92
|
export const useButtonState = (
|
|
78
93
|
loading: boolean | undefined,
|
package/src/ui/Card/Card.tsx
CHANGED
|
@@ -3,6 +3,13 @@ import { View, Text, Pressable, type ViewProps } from 'react-native';
|
|
|
3
3
|
import { cn } from '../../lib/utils';
|
|
4
4
|
import { textVariants, getCardVariantColors } from '@sudobility/design';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Props for the Card component.
|
|
8
|
+
*
|
|
9
|
+
* Supports multiple visual variants (bordered, elevated, info, success, etc.),
|
|
10
|
+
* configurable padding, optional icon display, and a close button for
|
|
11
|
+
* info-type variants.
|
|
12
|
+
*/
|
|
6
13
|
export interface CardProps extends ViewProps {
|
|
7
14
|
variant?:
|
|
8
15
|
| 'default'
|
|
@@ -32,6 +39,22 @@ const paddingStyles = {
|
|
|
32
39
|
lg: 'p-8',
|
|
33
40
|
};
|
|
34
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Card component for React Native.
|
|
44
|
+
*
|
|
45
|
+
* A versatile container with variant-based styling from the design system.
|
|
46
|
+
* Info-type variants (info, success, warning, error) support an optional
|
|
47
|
+
* icon and close button.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```tsx
|
|
51
|
+
* <Card variant="elevated" padding="md">
|
|
52
|
+
* <CardHeader title="Title" description="Subtitle" />
|
|
53
|
+
* <CardContent><Text>Body</Text></CardContent>
|
|
54
|
+
* <CardFooter><Button>Action</Button></CardFooter>
|
|
55
|
+
* </Card>
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
35
58
|
export const Card: React.FC<CardProps> = ({
|
|
36
59
|
variant = 'elevated',
|
|
37
60
|
padding = 'md',
|
|
@@ -85,11 +108,18 @@ export const Card: React.FC<CardProps> = ({
|
|
|
85
108
|
);
|
|
86
109
|
};
|
|
87
110
|
|
|
111
|
+
/** Props for the CardHeader sub-component. */
|
|
88
112
|
interface CardHeaderProps extends ViewProps {
|
|
113
|
+
/** Card title rendered as a heading. */
|
|
89
114
|
title?: string;
|
|
115
|
+
/** Card description rendered below the title. */
|
|
90
116
|
description?: string;
|
|
91
117
|
}
|
|
92
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Card header sub-component with optional title and description.
|
|
121
|
+
* Uses design system text variants for consistent typography.
|
|
122
|
+
*/
|
|
93
123
|
export const CardHeader: React.FC<CardHeaderProps> = ({
|
|
94
124
|
title,
|
|
95
125
|
description,
|
|
@@ -108,6 +138,7 @@ export const CardHeader: React.FC<CardHeaderProps> = ({
|
|
|
108
138
|
);
|
|
109
139
|
};
|
|
110
140
|
|
|
141
|
+
/** Card body content area. */
|
|
111
142
|
export const CardContent: React.FC<ViewProps> = ({
|
|
112
143
|
className,
|
|
113
144
|
children,
|
|
@@ -120,6 +151,7 @@ export const CardContent: React.FC<ViewProps> = ({
|
|
|
120
151
|
);
|
|
121
152
|
};
|
|
122
153
|
|
|
154
|
+
/** Card footer area, rendered as a horizontal row with top padding. */
|
|
123
155
|
export const CardFooter: React.FC<ViewProps> = ({
|
|
124
156
|
className,
|
|
125
157
|
children,
|
|
@@ -2,11 +2,19 @@ import React from 'react';
|
|
|
2
2
|
import { View, ActivityIndicator, Text, type ViewProps } from 'react-native';
|
|
3
3
|
import { cn } from '../../lib/utils';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Props for the Spinner loading indicator component.
|
|
7
|
+
*/
|
|
5
8
|
export interface SpinnerProps extends ViewProps {
|
|
9
|
+
/** Size of the activity indicator. */
|
|
6
10
|
size?: 'small' | 'default' | 'large' | 'extraLarge';
|
|
11
|
+
/** Color variant for the spinner. */
|
|
7
12
|
variant?: 'default' | 'white' | 'success' | 'warning' | 'error';
|
|
13
|
+
/** Accessibility label for screen readers. */
|
|
8
14
|
accessibilityLabel?: string;
|
|
15
|
+
/** Text shown below the spinner when showText is true. */
|
|
9
16
|
loadingText?: string;
|
|
17
|
+
/** Whether to display loading text below the spinner. */
|
|
10
18
|
showText?: boolean;
|
|
11
19
|
}
|
|
12
20
|
|
package/src/ui/Toast/Toast.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import React, {
|
|
|
9
9
|
import { View, Text, Pressable, Animated, SafeAreaView } from 'react-native';
|
|
10
10
|
import { cn } from '../../lib/utils';
|
|
11
11
|
|
|
12
|
+
/** Data structure representing a single toast notification. */
|
|
12
13
|
export interface ToastMessage {
|
|
13
14
|
id: string;
|
|
14
15
|
title?: string;
|
|
@@ -28,6 +29,7 @@ export interface ToastProps {
|
|
|
28
29
|
onRemove: (id: string) => void;
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
/** Context value provided by ToastProvider for managing toast notifications. */
|
|
31
33
|
export interface ToastContextValue {
|
|
32
34
|
toasts: ToastMessage[];
|
|
33
35
|
addToast: (toast: Omit<ToastMessage, 'id'>) => void;
|
|
@@ -36,6 +38,15 @@ export interface ToastContextValue {
|
|
|
36
38
|
|
|
37
39
|
const ToastContext = createContext<ToastContextValue | undefined>(undefined);
|
|
38
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Hook to access the toast notification system.
|
|
43
|
+
*
|
|
44
|
+
* Must be used within a ToastProvider. Returns functions to add and remove
|
|
45
|
+
* toast notifications, plus the current list of active toasts.
|
|
46
|
+
*
|
|
47
|
+
* @returns The toast context value with addToast, removeToast, and toasts
|
|
48
|
+
* @throws Error if used outside of a ToastProvider
|
|
49
|
+
*/
|
|
39
50
|
export const useToast = () => {
|
|
40
51
|
const context = useContext(ToastContext);
|
|
41
52
|
if (!context) {
|