1+ import { ComponentPreview } from "@/components/docs/component-preview" ;
2+
3+ export default function ToastPage ( ) {
4+ return (
5+ < ComponentPreview
6+ name = "Toast"
7+ description = "Display temporary notification messages to users."
8+ examples = { [
9+ {
10+ title : "Default" ,
11+ value : "default" ,
12+ content :
13+ 'import { Button } from "@nativeui/ui";\nimport { ToastProvider, useToast } from "@nativeui/ui";\nimport { Text } from "react-native";\n\nfunction ToastDemo() {\n const { show } = useToast();\n \n return (\n <Button onPress={() => show("Your changes have been saved")}>\n <Text>Show Toast</Text>\n </Button>\n );\n}\n\nexport default function App() {\n return (\n <ToastProvider>\n <ToastDemo />\n </ToastProvider>\n );\n}' ,
14+ language : "tsx" ,
15+ } ,
16+ {
17+ title : "Variants" ,
18+ value : "variants" ,
19+ content :
20+ 'import { Button } from "@nativeui/ui";\nimport { ToastProvider, useToast } from "@nativeui/ui";\nimport { View, Text } from "react-native";\n\nfunction ToastVariants() {\n const { show } = useToast();\n \n return (\n <View className="flex flex-col gap-4">\n <Button onPress={() => show("Default notification")}>\n <Text>Default</Text>\n </Button>\n <Button onPress={() => show("Success!", "success")}>\n <Text>Success</Text>\n </Button>\n <Button onPress={() => show("Error occurred", "error")}>\n <Text>Error</Text>\n </Button>\n <Button onPress={() => show("Warning message", "warning")}>\n <Text>Warning</Text>\n </Button>\n <Button onPress={() => show("Info message", "info")}>\n <Text>Info</Text>\n </Button>\n </View>\n );\n}\n\nexport default function App() {\n return (\n <ToastProvider>\n <ToastVariants />\n </ToastProvider>\n );\n}' ,
21+ language : "tsx" ,
22+ } ,
23+ {
24+ title : "Custom Duration & Position" ,
25+ value : "custom" ,
26+ content :
27+ 'import { Button } from "@nativeui/ui";\nimport { ToastProvider, useToast } from "@nativeui/ui";\nimport { View, Text } from "react-native";\n\nfunction ToastCustom() {\n const { show } = useToast();\n \n return (\n <View className="flex flex-col gap-4">\n <Button onPress={() => show("This toast appears at the top")}>\n <Text>Top Position</Text>\n </Button>\n </View>\n );\n}\n\nexport default function App() {\n return (\n <ToastProvider duration={5000} position="top">\n <ToastCustom />\n </ToastProvider>\n );\n}' ,
28+ language : "tsx" ,
29+ } ,
30+ ] }
31+ componentCode = { `import { cn } from "@/lib/utils";
32+ import { cva, type VariantProps } from "class-variance-authority";
33+ import * as React from "react";
34+ import { Animated, StyleSheet, Text, View } from "react-native";
35+
36+ type ToastType = "default" | "success" | "error" | "warning" | "info";
37+
38+ interface ToastContextValue {
39+ show: (message: string, type?: ToastType) => void;
40+ }
41+
42+ const ToastContext = React.createContext<ToastContextValue | undefined>(
43+ undefined,
44+ );
45+
46+ export const useToast = () => {
47+ const context = React.useContext(ToastContext);
48+ if (!context) {
49+ throw new Error("useToast must be used within a ToastProvider");
50+ }
51+ return context;
52+ };
53+
54+ const toastVariants = cva("px-4 py-3 rounded-lg shadow-lg border", {
55+ variants: {
56+ variant: {
57+ default: "bg-background border-border",
58+ success:
59+ "bg-green-50 border-green-200 dark:bg-green-950 dark:border-green-800",
60+ error:
61+ "bg-red-50 border-red-200 dark:bg-red-950 dark:border-red-800",
62+ warning:
63+ "bg-yellow-50 border-yellow-200 dark:bg-yellow-950 dark:border-yellow-800",
64+ info: "bg-blue-50 border-blue-200 dark:bg-blue-950 dark:border-blue-800",
65+ },
66+ },
67+ defaultVariants: {
68+ variant: "default",
69+ },
70+ });
71+
72+ const toastTextVariants = cva("text-sm font-medium", {
73+ variants: {
74+ variant: {
75+ default: "text-foreground",
76+ success: "text-green-900 dark:text-green-100",
77+ error: "text-red-900 dark:text-red-100",
78+ warning: "text-yellow-900 dark:text-yellow-100",
79+ info: "text-blue-900 dark:text-blue-100",
80+ },
81+ },
82+ defaultVariants: {
83+ variant: "default",
84+ },
85+ });
86+
87+ interface ToastProviderProps {
88+ children: React.ReactNode;
89+ duration?: number;
90+ position?: "top" | "bottom";
91+ }
92+
93+ export function ToastProvider({
94+ children,
95+ duration = 3000,
96+ position = "bottom",
97+ }: ToastProviderProps) {
98+ const [toast, setToast] = React.useState<{
99+ message: string;
100+ type: ToastType;
101+ } | null>(null);
102+ const fadeAnim = React.useRef(new Animated.Value(0)).current;
103+ const timeoutRef = React.useRef<NodeJS.Timeout | undefined>(undefined);
104+
105+ const show = React.useCallback(
106+ (message: string, type: ToastType = "default") => {
107+ if (timeoutRef.current) {
108+ clearTimeout(timeoutRef.current);
109+ }
110+
111+ setToast({ message, type });
112+
113+ Animated.timing(fadeAnim, {
114+ toValue: 1,
115+ duration: 200,
116+ useNativeDriver: true,
117+ }).start(() => {
118+ timeoutRef.current = setTimeout(() => {
119+ Animated.timing(fadeAnim, {
120+ toValue: 0,
121+ duration: 200,
122+ useNativeDriver: true,
123+ }).start(() => setToast(null));
124+ }, duration);
125+ });
126+ },
127+ [fadeAnim, duration],
128+ );
129+
130+ React.useEffect(() => {
131+ return () => {
132+ if (timeoutRef.current) {
133+ clearTimeout(timeoutRef.current);
134+ }
135+ };
136+ }, []);
137+
138+ const positionStyle = position === "top" ? { top: 50 } : { bottom: 50 };
139+
140+ return (
141+ <ToastContext.Provider value={{ show }}>
142+ {children}
143+ {toast && (
144+ <Animated.View
145+ style={[
146+ styles.container,
147+ positionStyle,
148+ {
149+ opacity: fadeAnim,
150+ transform: [
151+ {
152+ translateY: fadeAnim.interpolate({
153+ inputRange: [0, 1],
154+ outputRange: position === "top" ? [-40, 0] : [40, 0],
155+ }),
156+ },
157+ ],
158+ },
159+ ]}
160+ >
161+ <View className={cn(toastVariants({ variant: toast.type }))}>
162+ <Text className={cn(toastTextVariants({ variant: toast.type }))}>
163+ {toast.message}
164+ </Text>
165+ </View>
166+ </Animated.View>
167+ )}
168+ </ToastContext.Provider>
169+ );
170+ }
171+
172+ const styles = StyleSheet.create({
173+ container: {
174+ position: "absolute",
175+ alignSelf: "center",
176+ left: 0,
177+ right: 0,
178+ alignItems: "center",
179+ paddingHorizontal: 16,
180+ zIndex: 9999,
181+ },
182+ });
183+
184+ export { toastVariants, toastTextVariants };
185+ export type { ToastType, ToastProviderProps };
186+ ` }
187+ previewCode = { `import { Button } from "@/components/ui/button";
188+ import { ToastProvider, useToast } from "@/components/ui/toast";
189+ import { Feather } from "@expo/vector-icons";
190+ import { useColorScheme } from "nativewind";
191+ import React from "react";
192+ import { ScrollView, Text, View } from "react-native";
193+ import { SafeAreaView } from "react-native-safe-area-context";
194+
195+ function ToastDemo() {
196+ const { show } = useToast();
197+ const { colorScheme } = useColorScheme();
198+ const isDark = colorScheme === "dark";
199+
200+ return (
201+ <SafeAreaView className="flex-1 bg-background" edges={["bottom"]}>
202+ <ScrollView className="px-5 py-5">
203+ <View className="mb-6">
204+ <Text className="text-2xl font-bold mb-2 text-foreground">Toast</Text>
205+ <Text className="text-base mb-4 text-muted-foreground">
206+ Display temporary notification messages
207+ </Text>
208+ <Text className="text-base mb-4 text-foreground">
209+ Current mode: {isDark ? "dark" : "light"}
210+ </Text>
211+ </View>
212+
213+ <View className="mb-6">
214+ <Text className="text-xl font-semibold mb-4 text-foreground">
215+ Toast Variants
216+ </Text>
217+
218+ <View className="gap-4">
219+ <View className="flex-row gap-3 flex-wrap">
220+ <Button onPress={() => show("This is a default toast")}>
221+ <Text className="text-primary-foreground dark:text-primary-foreground">
222+ Default
223+ </Text>
224+ </Button>
225+
226+ <Button
227+ variant="outline"
228+ onPress={() => show("Operation successful!", "success")}
229+ >
230+ <Feather
231+ name="check-circle"
232+ size={16}
233+ color={isDark ? "white" : "#111827"}
234+ />
235+ <Text className="text-foreground dark:text-foreground ml-2">
236+ Success
237+ </Text>
238+ </Button>
239+
240+ <Button
241+ variant="destructive"
242+ onPress={() => show("Something went wrong", "error")}
243+ >
244+ <Feather
245+ name="alert-circle"
246+ size={16}
247+ color={isDark ? "#111827" : "white"}
248+ />
249+ <Text className="text-destructive-foreground dark:text-destructive-foreground ml-2">
250+ Error
251+ </Text>
252+ </Button>
253+ </View>
254+
255+ <View className="flex-row gap-3 flex-wrap">
256+ <Button
257+ variant="secondary"
258+ onPress={() => show("Please review your changes", "warning")}
259+ >
260+ <Feather
261+ name="alert-triangle"
262+ size={16}
263+ color={isDark ? "white" : "#111827"}
264+ />
265+ <Text className="text-secondary-foreground dark:text-secondary-foreground ml-2">
266+ Warning
267+ </Text>
268+ </Button>
269+
270+ <Button
271+ variant="ghost"
272+ onPress={() => show("Here's some information", "info")}
273+ >
274+ <Feather
275+ name="info"
276+ size={16}
277+ color={isDark ? "white" : "#111827"}
278+ />
279+ <Text className="text-foreground dark:text-foreground ml-2">
280+ Info
281+ </Text>
282+ </Button>
283+ </View>
284+ </View>
285+ </View>
286+
287+ <View className="mb-6">
288+ <Text className="text-xl font-semibold mb-4 text-foreground">
289+ Toast with Actions
290+ </Text>
291+
292+ <View className="gap-4">
293+ <View className="flex-row gap-3 flex-wrap">
294+ <Button
295+ onPress={async () => {
296+ show("Processing...", "info");
297+ await new Promise((resolve) => setTimeout(resolve, 1500));
298+ show("Changes saved successfully!", "success");
299+ }}
300+ >
301+ <Feather
302+ name="save"
303+ size={16}
304+ color={isDark ? "#111827" : "white"}
305+ />
306+ <Text className="text-primary-foreground dark:text-primary-foreground ml-2">
307+ Save Changes
308+ </Text>
309+ </Button>
310+
311+ <Button
312+ variant="outline"
313+ onPress={async () => {
314+ show("Uploading file...", "info");
315+ await new Promise((resolve) => setTimeout(resolve, 2000));
316+ show("File uploaded!", "success");
317+ }}
318+ >
319+ <Feather
320+ name="upload"
321+ size={16}
322+ color={isDark ? "white" : "#111827"}
323+ />
324+ <Text className="text-foreground dark:text-foreground ml-2">
325+ Upload File
326+ </Text>
327+ </Button>
328+ </View>
329+ </View>
330+ </View>
331+ </ScrollView>
332+ </SafeAreaView>
333+ );
334+ }
335+
336+ export default function ToastPreview() {
337+ return (
338+ <ToastProvider duration={3000} position="bottom">
339+ <ToastDemo />
340+ </ToastProvider>
341+ );
342+ }
343+ ` }
344+ registryName = "toast"
345+ packageName = "@nativeui/ui"
346+ dependencies = { [ "react-native" , "class-variance-authority" ] }
347+ changelog = { [ ] }
348+ />
349+ ) ;
350+ }
0 commit comments