|
|
@@ -0,0 +1,142 @@
|
|
|
+import { Toast as Kobalte, toaster } from "@kobalte/core/toast"
|
|
|
+import type { ToastRootProps, ToastCloseButtonProps, ToastTitleProps, ToastDescriptionProps } from "@kobalte/core/toast"
|
|
|
+import type { ComponentProps, JSX } from "solid-js"
|
|
|
+import { Show } from "solid-js"
|
|
|
+import { Portal } from "solid-js/web"
|
|
|
+import { Icon, type IconProps } from "./icon"
|
|
|
+import { IconButton } from "./icon-button"
|
|
|
+
|
|
|
+export interface ToastRegionProps extends ComponentProps<typeof Kobalte.Region> {}
|
|
|
+
|
|
|
+function ToastRegion(props: ToastRegionProps) {
|
|
|
+ return (
|
|
|
+ <Portal>
|
|
|
+ <Kobalte.Region data-component="toast-region" {...props}>
|
|
|
+ <Kobalte.List data-slot="toast-list" />
|
|
|
+ </Kobalte.Region>
|
|
|
+ </Portal>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+export interface ToastRootComponentProps extends ToastRootProps {
|
|
|
+ class?: string
|
|
|
+ classList?: ComponentProps<"li">["classList"]
|
|
|
+ children?: JSX.Element
|
|
|
+}
|
|
|
+
|
|
|
+function ToastRoot(props: ToastRootComponentProps) {
|
|
|
+ return (
|
|
|
+ <Kobalte
|
|
|
+ data-component="toast"
|
|
|
+ classList={{
|
|
|
+ ...(props.classList ?? {}),
|
|
|
+ [props.class ?? ""]: !!props.class,
|
|
|
+ }}
|
|
|
+ {...props}
|
|
|
+ />
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+function ToastIcon(props: { name: IconProps["name"] }) {
|
|
|
+ return (
|
|
|
+ <div data-slot="toast-icon">
|
|
|
+ <Icon name={props.name} />
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+function ToastContent(props: ComponentProps<"div">) {
|
|
|
+ return <div data-slot="toast-content" {...props} />
|
|
|
+}
|
|
|
+
|
|
|
+function ToastTitle(props: ToastTitleProps & ComponentProps<"div">) {
|
|
|
+ return <Kobalte.Title data-slot="toast-title" {...props} />
|
|
|
+}
|
|
|
+
|
|
|
+function ToastDescription(props: ToastDescriptionProps & ComponentProps<"div">) {
|
|
|
+ return <Kobalte.Description data-slot="toast-description" {...props} />
|
|
|
+}
|
|
|
+
|
|
|
+function ToastCloseButton(props: ToastCloseButtonProps & ComponentProps<"button">) {
|
|
|
+ return <Kobalte.CloseButton data-slot="toast-close-button" as={IconButton} icon="close" variant="ghost" {...props} />
|
|
|
+}
|
|
|
+
|
|
|
+function ToastProgressTrack(props: ComponentProps<typeof Kobalte.ProgressTrack>) {
|
|
|
+ return <Kobalte.ProgressTrack data-slot="toast-progress-track" {...props} />
|
|
|
+}
|
|
|
+
|
|
|
+function ToastProgressFill(props: ComponentProps<typeof Kobalte.ProgressFill>) {
|
|
|
+ return <Kobalte.ProgressFill data-slot="toast-progress-fill" {...props} />
|
|
|
+}
|
|
|
+
|
|
|
+export const Toast = Object.assign(ToastRoot, {
|
|
|
+ Region: ToastRegion,
|
|
|
+ Icon: ToastIcon,
|
|
|
+ Content: ToastContent,
|
|
|
+ Title: ToastTitle,
|
|
|
+ Description: ToastDescription,
|
|
|
+ CloseButton: ToastCloseButton,
|
|
|
+ ProgressTrack: ToastProgressTrack,
|
|
|
+ ProgressFill: ToastProgressFill,
|
|
|
+})
|
|
|
+
|
|
|
+export { toaster }
|
|
|
+
|
|
|
+export type ToastVariant = "default" | "success" | "error" | "loading"
|
|
|
+
|
|
|
+export interface ToastOptions {
|
|
|
+ title?: string
|
|
|
+ description?: string
|
|
|
+ icon?: IconProps["name"]
|
|
|
+ variant?: ToastVariant
|
|
|
+ duration?: number
|
|
|
+}
|
|
|
+
|
|
|
+export function showToast(options: ToastOptions | string) {
|
|
|
+ const opts = typeof options === "string" ? { description: options } : options
|
|
|
+ return toaster.show((props) => (
|
|
|
+ <Toast toastId={props.toastId} duration={opts.duration} data-variant={opts.variant ?? "default"}>
|
|
|
+ <div data-slot="toast-inner">
|
|
|
+ <Show when={opts.icon}>
|
|
|
+ <Toast.Icon name={opts.icon!} />
|
|
|
+ </Show>
|
|
|
+ <Toast.Content>
|
|
|
+ <Show when={opts.title}>
|
|
|
+ <Toast.Title>{opts.title}</Toast.Title>
|
|
|
+ </Show>
|
|
|
+ <Show when={opts.description}>
|
|
|
+ <Toast.Description>{opts.description}</Toast.Description>
|
|
|
+ </Show>
|
|
|
+ </Toast.Content>
|
|
|
+ </div>
|
|
|
+ <Toast.CloseButton />
|
|
|
+ </Toast>
|
|
|
+ ))
|
|
|
+}
|
|
|
+
|
|
|
+export interface ToastPromiseOptions<T, U = unknown> {
|
|
|
+ loading?: JSX.Element
|
|
|
+ success?: (data: T) => JSX.Element
|
|
|
+ error?: (error: U) => JSX.Element
|
|
|
+}
|
|
|
+
|
|
|
+export function showPromiseToast<T, U = unknown>(
|
|
|
+ promise: Promise<T> | (() => Promise<T>),
|
|
|
+ options: ToastPromiseOptions<T, U>,
|
|
|
+) {
|
|
|
+ return toaster.promise(promise, (props) => (
|
|
|
+ <Toast
|
|
|
+ toastId={props.toastId}
|
|
|
+ data-variant={props.state === "pending" ? "loading" : props.state === "fulfilled" ? "success" : "error"}
|
|
|
+ >
|
|
|
+ <Toast.Content>
|
|
|
+ <Toast.Description>
|
|
|
+ {props.state === "pending" && options.loading}
|
|
|
+ {props.state === "fulfilled" && options.success?.(props.data!)}
|
|
|
+ {props.state === "rejected" && options.error?.(props.error)}
|
|
|
+ </Toast.Description>
|
|
|
+ </Toast.Content>
|
|
|
+ <Toast.CloseButton />
|
|
|
+ </Toast>
|
|
|
+ ))
|
|
|
+}
|