feat(ui, dashboard): Toast rework (#7076)

**What**
- Re-works how toasts work in Medusa UI. API is now built on top of `sonner` instead of `@radix-ui/react-toast`. This is a breaking change, and we will need to update the documentation once this has been merged and released (cc: @shahednasser).
- Adds an example of usage in the products list table in the new admin dashboard. As part of the coming weeks cleanup we will add toasts everywhere that they are currently missing.

CLOSES CORE-1977
This commit is contained in:
Kasper Fabricius Kristensen
2024-04-16 15:14:58 +02:00
committed by GitHub
parent 379ff7a36d
commit c3260a2c5a
15 changed files with 730 additions and 516 deletions

View File

@@ -0,0 +1,214 @@
import { Toast } from "@/components/toast"
import { ToastAction, ToastVariant, ToasterPosition } from "@/types"
import * as React from "react"
import { toast as toastFn } from "sonner"
interface BaseToastProps {
id?: string | number
position?: ToasterPosition
dismissable?: boolean
dismissLabel?: string
duration?: number
}
interface ToastProps extends BaseToastProps {
description?: string
action?: ToastAction
}
function create(variant: ToastVariant, title: string, props: ToastProps = {}) {
return toastFn.custom(
(t) => {
return (
<Toast
id={props.id || t}
title={title}
description={props.description}
onDismiss={
props.dismissable ? () => toastFn.dismiss(props.id || t) : undefined
}
dismissLabel={props.dismissLabel}
variant={variant}
action={props.action}
/>
)
},
{
id: props.id,
position: props.position,
dismissible: props.dismissable,
}
)
}
function message(
/**
* The title of the toast.
*/
title: string,
/**
* The props of the toast.
*/
props: ToastProps = {}
) {
return create("message", title, props)
}
function info(
/**
* The title of the toast.
*/ title: string,
/**
* The props of the toast.
*/
props: ToastProps = {}
) {
return create("info", title, props)
}
function error(
/**
* The title of the toast.
*/ title: string,
/**
* The props of the toast.
*/
props: ToastProps = {}
) {
return create("error", title, props)
}
function success(
/**
* The title of the toast.
*/ title: string,
/**
* The props of the toast.
*/
props: ToastProps = {}
) {
return create("success", title, props)
}
function warning(
/**
* The title of the toast.
*/ title: string,
/**
* The props of the toast.
*/
props: ToastProps = {}
) {
return create("warning", title, props)
}
type LoadingToastProps = Omit<ToastProps, "dismissable" | "dismissLabel">
function loading
/**
* The title of the toast.
*/(
title: string,
/**
* The props of the toast.
*/
props: ToastProps = {}
) {
return create("loading", title, { ...props, dismissable: false })
}
type PromiseStateProps =
| string
| {
title: string
description?: string
}
interface PromiseToastProps extends BaseToastProps {
loading: PromiseStateProps
success: PromiseStateProps
error: PromiseStateProps
}
function createUniqueID() {
return Math.random().toString(36).slice(2, 8)
}
async function promise<TData>(
/**
* The promise to be resolved.
*/
promise: Promise<TData> | (() => Promise<TData>),
/**
* The props of the toast.
*/
props: PromiseToastProps
) {
let id: string | number | undefined = props.id || createUniqueID()
let shouldDismiss = id !== undefined
id = create(
"loading",
typeof props.loading === "string" ? props.loading : props.loading.title,
{
id: id,
position: props.position,
description:
typeof props.loading === "string"
? undefined
: props.loading.description,
duration: Infinity,
}
)
const p = promise instanceof Promise ? promise : promise()
p.then(() => {
shouldDismiss = false
create(
"success",
typeof props.success === "string" ? props.success : props.success.title,
{
id: id,
position: props.position,
description:
typeof props.success === "string"
? undefined
: props.success.description,
}
)
})
.catch(() => {
shouldDismiss = false
create(
"error",
typeof props.error === "string" ? props.error : props.error.title,
{
id: id,
position: props.position,
description:
typeof props.error === "string"
? undefined
: props.error.description,
}
)
})
.finally(() => {
if (shouldDismiss) {
toastFn.dismiss(id)
id = undefined
}
})
return id
}
export const toast = Object.assign(message, {
info,
error,
warning,
success,
promise,
loading,
dismiss: toastFn.dismiss,
})