import { createContext, useContext, type ParentProps, Show } from "solid-js" import { createStore } from "solid-js/store" import { useTheme } from "../context/ThemeContext" import { useTerminalDimensions } from "@opentui/solid" import { TextAttributes } from "@opentui/core" import { emit } from "../utils/event-bus" export type ToastVariant = "info" | "success" | "warning" | "error" export type ToastOptions = { title?: string message: string variant: ToastVariant duration?: number } const DEFAULT_DURATION = 5000 /** * Toast component that displays at the top-right of the screen. * NOTE: This component must be rendered INSIDE ThemeProvider since it uses useTheme(). * The ToastProvider itself can be placed outside ThemeProvider if needed. */ export function Toast() { const toast = useToast() const { theme } = useTheme() const dimensions = useTerminalDimensions() const getVariantColor = (variant: ToastVariant) => { switch (variant) { case "success": return theme.success case "warning": return theme.warning case "error": return theme.error case "info": default: return theme.info } } return ( {(current) => ( {current().title} {current().message} )} ) } function init() { const [store, setStore] = createStore({ currentToast: null as ToastOptions | null, }) let timeoutHandle: NodeJS.Timeout | null = null const toast = { show(options: ToastOptions) { const duration = options.duration ?? DEFAULT_DURATION setStore("currentToast", { title: options.title, message: options.message, variant: options.variant, }) // Emit event for other listeners emit("toast.show", options) if (timeoutHandle) clearTimeout(timeoutHandle) timeoutHandle = setTimeout(() => { setStore("currentToast", null) }, duration) }, error: (err: unknown) => { if (err instanceof Error) { return toast.show({ variant: "error", message: err.message, }) } toast.show({ variant: "error", message: "An unknown error has occurred", }) }, info: (message: string, title?: string) => { toast.show({ variant: "info", message, title }) }, success: (message: string, title?: string) => { toast.show({ variant: "success", message, title }) }, warning: (message: string, title?: string) => { toast.show({ variant: "warning", message, title }) }, clear: () => { if (timeoutHandle) clearTimeout(timeoutHandle) setStore("currentToast", null) }, get currentToast(): ToastOptions | null { return store.currentToast }, } return toast } export type ToastContext = ReturnType const ctx = createContext() /** * ToastProvider provides toast functionality. * NOTE: The Toast UI component is NOT rendered here - you must render * separately inside your component tree, after ThemeProvider. */ export function ToastProvider(props: ParentProps) { const value = init() return {props.children} } export function useToast() { const value = useContext(ctx) if (!value) { throw new Error("useToast must be used within a ToastProvider") } return value }