"use client"; import type { ToastManagerAddOptions } from "@base-ui/react/toast"; import type { ComponentPropsWithoutRef, ReactNode } from "react"; import type { ThreadToastData } from "./toast"; export type StackedThreadToastOptions = { type: "error" | "warning" | "success" | "info" | "loading"; title: ReactNode; description?: ReactNode; timeout?: number; priority?: "low" | "high"; actionProps?: ComponentPropsWithoutRef<"button">; /** Merged into `data`; `actionLayout` is always forced to `"stacked-end"` by the helper. */ actionVariant?: ThreadToastData["actionVariant"]; data?: Omit; }; /** * Defined `actionProps` that hide a previous toast CTA on `toastManager.update`. * Passing `actionProps: undefined` is a no-op because updates omit undefined keys. */ export const hiddenToastActionProps = { children: null, } as const satisfies Pick, "children">; /** * Thread toast using the stacked body + bottom action row (copy for errors, CTA on its own row). */ export function stackedThreadToast( options: StackedThreadToastOptions, ): ToastManagerAddOptions { const { type, title, description, timeout, priority, actionProps, actionVariant, data } = options; // Helper-owned `actionLayout` must win over any caller-provided `data`, so spread // the caller's data first and apply `actionLayout: "stacked-end"` last. const mergedData: ThreadToastData = { ...(data !== undefined ? data : {}), actionLayout: "stacked-end", }; if (actionVariant !== undefined) { mergedData.actionVariant = actionVariant; } const payload: ToastManagerAddOptions = { type, title, data: mergedData, }; if (description !== undefined) { payload.description = description; } if (timeout !== undefined) { payload.timeout = timeout; } if (priority !== undefined) { payload.priority = priority; } if (actionProps !== undefined) { payload.actionProps = actionProps; } return payload; }