Files
FamilyPlanner/frontend/src/components/ToastProvider.js
philippe fdd72c1135 Initial commit: Family Planner application
Complete family planning application with:
- React frontend with TypeScript
- Node.js/Express backend with TypeScript
- Python ingestion service for document processing
- Planning ingestion service with LLM integration
- Shared UI components and type definitions
- OAuth integration for calendar synchronization
- Comprehensive documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-14 10:43:33 +02:00

70 lines
2.5 KiB
JavaScript

import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
import styled, { keyframes } from "styled-components";
const ToastContext = createContext(undefined);
const slideIn = keyframes `
from { transform: translateY(10px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
`;
const ToastContainer = styled.div `
position: fixed;
bottom: 20px;
right: 20px;
display: flex;
flex-direction: column;
gap: 8px;
z-index: 9999;
`;
const ToastItem = styled.div `
min-width: 260px;
max-width: 420px;
padding: 12px 14px;
border-radius: 12px;
background: ${({ $level }) => $level === "success"
? "rgba(76, 217, 100, 0.15)"
: $level === "error"
? "rgba(255, 59, 48, 0.18)"
: "rgba(85, 98, 255, 0.15)"};
border: 1px solid
${({ $level }) => $level === "success"
? "rgba(76, 217, 100, 0.35)"
: $level === "error"
? "rgba(255, 59, 48, 0.35)"
: "rgba(85, 98, 255, 0.35)"};
color: #e9ebff;
animation: ${slideIn} 160ms ease-out;
`;
export const ToastProvider = ({ children }) => {
const [toasts, setToasts] = useState([]);
const addToast = useCallback((message, opts) => {
const id = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
const t = { id, message, level: opts?.level ?? "info", timeoutMs: opts?.timeoutMs ?? 3500 };
setToasts((prev) => [t, ...prev].slice(0, 5));
window.setTimeout(() => {
setToasts((prev) => prev.filter((x) => x.id !== id));
}, t.timeoutMs);
}, []);
const value = useMemo(() => ({ addToast }), [addToast]);
return (_jsxs(ToastContext.Provider, { value: value, children: [children, _jsx(Bridge, { addToast: addToast }), _jsx(ToastContainer, { children: toasts.map((t) => (_jsx(ToastItem, { "$level": t.level ?? "info", children: t.message }, t.id))) })] }));
};
export const useToasts = () => {
const ctx = useContext(ToastContext);
if (!ctx)
throw new Error("useToasts must be used within ToastProvider");
return ctx;
};
const Bridge = ({ addToast }) => {
useEffect(() => {
window.__fp_toast = (success, message) => addToast(message, { level: success ? "success" : "error" });
return () => {
try {
delete window.__fp_toast;
}
catch {
// Ignore errors when cleaning up
}
};
}, [addToast]);
return null;
};