|
1 | | -import React, { useEffect, useState, useCallback } from "react"; |
| 1 | +import React, { |
| 2 | + createContext, |
| 3 | + useContext, |
| 4 | + useEffect, |
| 5 | + useState, |
| 6 | + useCallback, |
| 7 | +} from "react"; |
2 | 8 | import { |
3 | 9 | View, |
4 | 10 | Text, |
@@ -199,6 +205,78 @@ export const ToastManager: React.FC = () => { |
199 | 205 | ); |
200 | 206 | }; |
201 | 207 |
|
| 208 | +// ── Context-based toast API (Issue #712) ────────────────────────────────────── |
| 209 | + |
| 210 | +type ToastContextValue = { |
| 211 | + showToast: (message: string, type: ToastType) => void; |
| 212 | +}; |
| 213 | + |
| 214 | +const ToastContext = createContext<ToastContextValue | undefined>(undefined); |
| 215 | + |
| 216 | +export function useToast(): ToastContextValue { |
| 217 | + const context = useContext(ToastContext); |
| 218 | + if (!context) { |
| 219 | + throw new Error("useToast must be used within ToastProvider"); |
| 220 | + } |
| 221 | + return context; |
| 222 | +} |
| 223 | + |
| 224 | +export const ToastProvider: React.FC<{ children: React.ReactNode }> = ({ |
| 225 | + children, |
| 226 | +}) => { |
| 227 | + const [toasts, setToasts] = useState<{ id: string; props: ToastProps }[]>([]); |
| 228 | + |
| 229 | + const removeToast = useCallback((id: string) => { |
| 230 | + setToasts((prev) => prev.filter((t) => t.id !== id)); |
| 231 | + }, []); |
| 232 | + |
| 233 | + const showToast = useCallback((message: string, type: ToastType) => { |
| 234 | + const id = Date.now().toString(); |
| 235 | + setToasts((prev) => [...prev, { id, props: { message, type } }]); |
| 236 | + }, []); |
| 237 | + |
| 238 | + // Bridge to global.toast for existing consumers |
| 239 | + useEffect(() => { |
| 240 | + (global as any).toast = { |
| 241 | + show: (props: Omit<ToastProps, "onHide">) => { |
| 242 | + const id = Date.now().toString(); |
| 243 | + setToasts((prev) => [...prev, { id, props }]); |
| 244 | + }, |
| 245 | + success: (message: string, action?: ToastProps["action"]) => { |
| 246 | + const id = Date.now().toString(); |
| 247 | + setToasts((prev) => [...prev, { id, props: { message, type: "success", action } }]); |
| 248 | + }, |
| 249 | + error: (message: string, action?: ToastProps["action"]) => { |
| 250 | + const id = Date.now().toString(); |
| 251 | + setToasts((prev) => [...prev, { id, props: { message, type: "error", action } }]); |
| 252 | + }, |
| 253 | + warning: (message: string, action?: ToastProps["action"]) => { |
| 254 | + const id = Date.now().toString(); |
| 255 | + setToasts((prev) => [...prev, { id, props: { message, type: "warning", action } }]); |
| 256 | + }, |
| 257 | + info: (message: string, action?: ToastProps["action"]) => { |
| 258 | + const id = Date.now().toString(); |
| 259 | + setToasts((prev) => [...prev, { id, props: { message, type: "info", action } }]); |
| 260 | + }, |
| 261 | + }; |
| 262 | + |
| 263 | + return () => { |
| 264 | + delete (global as any).toast; |
| 265 | + }; |
| 266 | + }, []); |
| 267 | + |
| 268 | + return ( |
| 269 | + <ToastContext.Provider value={{ showToast }}> |
| 270 | + {children} |
| 271 | + <View style={styles.toastContainer}> |
| 272 | + {toasts.map(({ id, props }) => ( |
| 273 | + <Toast key={id} {...props} onHide={() => removeToast(id)} /> |
| 274 | + ))} |
| 275 | + </View> |
| 276 | + </ToastContext.Provider> |
| 277 | + ); |
| 278 | +}; |
| 279 | + |
202 | 280 | const styles = StyleSheet.create({ |
203 | 281 | container: { |
204 | 282 | marginHorizontal: 16, |
|
0 commit comments