{"version":3,"file":"ToastProvider.cjs","names":[],"sources":["../../../src/components/Toast/ToastProvider.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines — the provider is the queue: enqueue, dedupe, auto-\n * dismiss timers, pause on hover and the portal that renders them. Splitting the\n * queue from the renderer would put a timer in one file and the element it dismisses\n * in another.\n */\nimport {\n    createContext,\n    useCallback,\n    useContext,\n    useEffect,\n    useMemo,\n    useRef,\n    useState,\n} from \"react\";\nimport type { ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Toast.module.css\";\nimport { usePortalHost } from \"../Portal/portal-host\";\n\nexport type ToastVariant = \"success\" | \"warning\" | \"error\" | \"info\";\n\nexport interface ToastOptions {\n    id?: string;\n    title?: string;\n    description?: string;\n    variant?: ToastVariant;\n    /** Auto-dismiss timeout in ms. Pass `0` to keep the toast until dismissed manually. */\n    duration?: number;\n}\n\ninterface ToastEntry extends Required<Omit<ToastOptions, \"id\" | \"title\" | \"description\">> {\n    id: string;\n    title?: string;\n    description?: string;\n}\n\nexport interface ToastApi {\n    show: (options: ToastOptions) => string;\n    dismiss: (id: string) => void;\n    success: (\n        description: string,\n        options?: Omit<ToastOptions, \"variant\" | \"description\">,\n    ) => string;\n    error: (description: string, options?: Omit<ToastOptions, \"variant\" | \"description\">) => string;\n    warning: (\n        description: string,\n        options?: Omit<ToastOptions, \"variant\" | \"description\">,\n    ) => string;\n    info: (description: string, options?: Omit<ToastOptions, \"variant\" | \"description\">) => string;\n}\n\nconst ToastContext = createContext<ToastApi | null>(null);\n\n/**\n * Access the toast API. Must be used inside a {@link ToastProvider}.\n *\n * @returns Methods to show and dismiss toasts.\n */\nexport function useToast(): ToastApi {\n    const ctx = useContext(ToastContext);\n    if (!ctx) throw new Error(\"useToast must be used inside a <ToastProvider>\");\n    return ctx;\n}\n\nexport type ToastPosition =\n    \"top-right\" | \"top-left\" | \"top-center\" | \"bottom-right\" | \"bottom-left\" | \"bottom-center\";\n\nexport interface ToastProviderProps {\n    children: ReactNode;\n    /** Default auto-dismiss duration (ms). Default 4000. */\n    defaultDuration?: number;\n    /** Stack position on screen. Default `\"top-right\"`. */\n    position?: ToastPosition;\n}\n\n/**\n * Renders a portalled toast container and exposes the imperative {@link useToast} API.\n */\nexport function ToastProvider({\n    children,\n    defaultDuration = 4000,\n    position = \"top-right\",\n}: ToastProviderProps) {\n    const [toasts, setToasts] = useState<ToastEntry[]>([]);\n    const counter = useRef<number>(0);\n\n    const dismiss = useCallback((id: string): void => {\n        setToasts((current) => current.filter((toast) => toast.id !== id));\n    }, []);\n\n    const show = useCallback(\n        (options: ToastOptions): string => {\n            const id = options.id ?? `toast-${++counter.current}`;\n            const entry: ToastEntry = {\n                id,\n                title: options.title,\n                description: options.description,\n                variant: options.variant ?? \"info\",\n                duration: options.duration ?? defaultDuration,\n            };\n            setToasts((current) => [...current, entry]);\n            return id;\n        },\n        [defaultDuration],\n    );\n\n    const api = useMemo<ToastApi>(\n        () => ({\n            show,\n            dismiss,\n            success: (description, options) =>\n                show({ ...options, description, variant: \"success\" }),\n            error: (description, options) => show({ ...options, description, variant: \"error\" }),\n            warning: (description, options) =>\n                show({ ...options, description, variant: \"warning\" }),\n            info: (description, options) => show({ ...options, description, variant: \"info\" }),\n        }),\n        [show, dismiss],\n    );\n\n    return (\n        <ToastContext.Provider value={api}>\n            {children}\n            <ToastContainer toasts={toasts} onDismiss={dismiss} position={position} />\n        </ToastContext.Provider>\n    );\n}\n\ninterface ContainerProps {\n    toasts: ToastEntry[];\n    onDismiss: (id: string) => void;\n    position: ToastPosition;\n}\n\nfunction positionClass(position: ToastPosition): string {\n    switch (position) {\n        case \"top-left\":\n            return styles.positionTopLeft;\n        case \"top-center\":\n            return styles.positionTopCenter;\n        case \"bottom-right\":\n            return styles.positionBottomRight;\n        case \"bottom-left\":\n            return styles.positionBottomLeft;\n        case \"bottom-center\":\n            return styles.positionBottomCenter;\n        case \"top-right\":\n        default:\n            return styles.positionTopRight;\n    }\n}\n\n/**\n * The portalled stack, and the page's live region for toasts.\n *\n * `aria-atomic=\"false\"` matters: with `\"true\"` a screen reader re-reads the\n * **entire** stack on every change, so the third toast of a batch is announced as\n * all three, and dismissing one re-announces the survivors. `\"false\"` announces only\n * the node that was added, which is what the user needs.\n *\n * The stack stays the live region rather than delegating to `useAnnounce`, because\n * routing it through the shared announcer would put the same text in the document\n * twice — once visible, once hidden — and every `getByText(\"Salvo\")` in a consuming\n * app's test suite would start matching two nodes. The announcer is for messages\n * that have no on-screen home; a toast has one.\n */\nfunction ToastContainer({ toasts, onDismiss, position }: ContainerProps) {\n    const portalHost = usePortalHost();\n\n    if (!portalHost) return null;\n    return createPortal(\n        <div\n            className={cn(styles.container, positionClass(position))}\n            aria-live=\"polite\"\n            aria-atomic=\"false\"\n        >\n            {toasts.map((toast) => (\n                <ToastItem key={toast.id} toast={toast} onDismiss={onDismiss} />\n            ))}\n        </div>,\n        portalHost,\n    );\n}\n\ninterface ItemProps {\n    toast: ToastEntry;\n    onDismiss: (id: string) => void;\n}\n\n/**\n * One toast.\n *\n * No `role=\"status\"` of its own: it already lives inside the container's live\n * region, and a live region nested in a live region is announced twice by some\n * screen readers.\n */\nfunction ToastItem({ toast, onDismiss }: ItemProps) {\n    useEffect(() => {\n        if (!toast.duration) return;\n        const timer = setTimeout(() => onDismiss(toast.id), toast.duration);\n        return () => clearTimeout(timer);\n    }, [toast.id, toast.duration, onDismiss]);\n\n    return (\n        <div className={cn(styles.toast, styles[toast.variant])}>\n            <div>\n                {toast.title && <p className={styles.title}>{toast.title}</p>}\n                {toast.description && <p className={styles.description}>{toast.description}</p>}\n            </div>\n            <button\n                type=\"button\"\n                className={styles.close}\n                aria-label=\"Fechar notificação\"\n                onClick={() => onDismiss(toast.id)}\n            >\n                <CloseIcon />\n            </button>\n        </div>\n    );\n}\n\nfunction CloseIcon() {\n    return (\n        <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\">\n            <path\n                d=\"M6 6l12 12M6 18L18 6\"\n                stroke=\"currentColor\"\n                strokeWidth=\"2\"\n                strokeLinecap=\"round\"\n            />\n        </svg>\n    );\n}\n"],"mappings":"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"}