From bc0112f3b4d8804fd8596e041fca63df9e2e9eee Mon Sep 17 00:00:00 2001 From: Junior Garcia Date: Fri, 14 Feb 2025 19:26:18 -0300 Subject: [PATCH] feat: toast api improved --- .changeset/brave-masks-rescue.md | 6 +++ apps/docs/content/docs/components/toast.mdx | 10 ++++- .../components/toast/src/toast-provider.tsx | 12 ++---- .../components/toast/src/toast-region.tsx | 10 +---- packages/components/toast/src/toast.tsx | 4 +- packages/components/toast/src/use-toast.ts | 41 +++++++++++-------- .../toast/stories/toast.stories.tsx | 16 +++++--- packages/core/theme/src/components/toast.ts | 36 ++++++++-------- 8 files changed, 75 insertions(+), 60 deletions(-) create mode 100644 .changeset/brave-masks-rescue.md diff --git a/.changeset/brave-masks-rescue.md b/.changeset/brave-masks-rescue.md new file mode 100644 index 000000000..4bd94f41a --- /dev/null +++ b/.changeset/brave-masks-rescue.md @@ -0,0 +1,6 @@ +--- +"@heroui/toast": patch +"@heroui/theme": patch +--- + +Toast API changes, pleacements changed and severity property added diff --git a/apps/docs/content/docs/components/toast.mdx b/apps/docs/content/docs/components/toast.mdx index d7a1c27fe..ba27a8221 100644 --- a/apps/docs/content/docs/components/toast.mdx +++ b/apps/docs/content/docs/components/toast.mdx @@ -303,9 +303,15 @@ Toast has the following slots: }, { attribute: "placement", - type: "right-bottom" | "left-bottom" | "center-bottom" | "right-top" | "left-top" | "center-top", + type: "bottom-right" | "bottom-left" | "bottom-center" | "top-right" | "top-left" | "top-center", description: "The placement of the toast.", - default: "right-bottom" + default: "bottom-right" + }, + { + attribute: "severity", + type: "default | primary | secondary | success | warning | danger", + description: "The severity of the toast. This changes the icon of the toast without having to change the color.", + default: "default" }, { attribute: "disableAnimation", diff --git a/packages/components/toast/src/toast-provider.tsx b/packages/components/toast/src/toast-provider.tsx index c871da2e8..4a80a6872 100644 --- a/packages/components/toast/src/toast-provider.tsx +++ b/packages/components/toast/src/toast-provider.tsx @@ -2,19 +2,13 @@ import {ToastOptions, ToastQueue, useToastQueue} from "@react-stately/toast"; import {useProviderContext} from "@heroui/system"; import {ToastRegion} from "./toast-region"; -import {ToastProps} from "./use-toast"; +import {ToastProps, ToastPlacement} from "./use-toast"; let globalToastQueue: ToastQueue | null = null; interface ToastProviderProps { maxVisibleToasts?: number; - placement?: - | "right-bottom" - | "left-bottom" - | "center-bottom" - | "right-top" - | "left-top" - | "center-top"; + placement?: ToastPlacement; disableAnimation?: boolean; toastProps?: ToastProps; toastOffset?: number; @@ -32,7 +26,7 @@ export const getToastQueue = () => { }; export const ToastProvider = ({ - placement = "right-bottom", + placement = "bottom-right", disableAnimation: disableAnimationProp = false, maxVisibleToasts = 3, toastOffset = 0, diff --git a/packages/components/toast/src/toast-region.tsx b/packages/components/toast/src/toast-region.tsx index 05b36e657..41a212ece 100644 --- a/packages/components/toast/src/toast-region.tsx +++ b/packages/components/toast/src/toast-region.tsx @@ -6,17 +6,11 @@ import {mergeProps} from "@react-aria/utils"; import {toastRegion, ToastRegionVariantProps} from "@heroui/theme"; import Toast from "./toast"; -import {ToastProps} from "./use-toast"; +import {ToastProps, ToastPlacement} from "./use-toast"; interface ToastRegionProps extends AriaToastRegionProps, ToastRegionVariantProps { toastQueue: ToastState; - placement?: - | "right-bottom" - | "left-bottom" - | "center-bottom" - | "right-top" - | "left-top" - | "center-top"; + placement?: ToastPlacement; maxVisibleToasts: number; toastOffset?: number; toastProps?: ToastProps; diff --git a/packages/components/toast/src/toast.tsx b/packages/components/toast/src/toast.tsx index 11d4e820c..5d8738948 100644 --- a/packages/components/toast/src/toast.tsx +++ b/packages/components/toast/src/toast.tsx @@ -18,6 +18,7 @@ const loadFeatures = () => import("framer-motion").then((res) => res.domMax); export interface ToastProps extends UseToastProps {} const iconMap = { + default: InfoFilledIcon, primary: InfoFilledIcon, secondary: InfoFilledIcon, success: SuccessIcon, @@ -27,6 +28,7 @@ const iconMap = { const Toast = forwardRef<"div", ToastProps>((props, ref) => { const { + severity, Component, icon, loadingIcon, @@ -56,7 +58,7 @@ const Toast = forwardRef<"div", ToastProps>((props, ref) => { }); const customIcon = icon && isValidElement(icon) ? cloneElement(icon, getIconProps()) : null; - const IconComponent = iconMap[color] || iconMap.primary; + const IconComponent = severity ? iconMap[severity] : iconMap[color] || iconMap.default; const customLoadingIcon = loadingIcon && isValidElement(loadingIcon) ? cloneElement(loadingIcon, getLoadingIconProps()) diff --git a/packages/components/toast/src/use-toast.ts b/packages/components/toast/src/use-toast.ts index 7dd8a59b6..b6661f075 100644 --- a/packages/components/toast/src/use-toast.ts +++ b/packages/components/toast/src/use-toast.ts @@ -12,6 +12,14 @@ import {MotionProps} from "framer-motion"; import {useHover} from "@react-aria/interactions"; import {useIsMobile} from "@heroui/use-is-mobile"; +export type ToastPlacement = + | "bottom-right" + | "bottom-left" + | "bottom-center" + | "top-right" + | "top-left" + | "top-center"; + export interface ToastProps extends ToastVariantProps { /** * Ref to the DOM node. @@ -93,6 +101,11 @@ export interface ToastProps extends ToastVariantProps { * should apply styles to indicate timeout progress */ shouldShowTimeoutProgess?: boolean; + /** + * The severity of the toast. This changes the icon without having to change the color. + * @default "default" + */ + severity?: "default" | "primary" | "secondary" | "success" | "warning" | "danger"; } interface Props extends Omit, "title">, ToastProps { @@ -104,13 +117,7 @@ interface Props extends Omit, "title">, ToastProps { setHeights: (val: number[]) => void; disableAnimation?: boolean; isRegionExpanded: boolean; - placement?: - | "right-bottom" - | "left-bottom" - | "center-bottom" - | "right-top" - | "left-top" - | "center-top"; + placement?: ToastPlacement; toastOffset?: number; } @@ -135,7 +142,7 @@ export function useToast(originalProps: UseToastProps) endContent, closeIcon, hideIcon = false, - placement: placementProp = "right-bottom", + placement: placementProp = "bottom-right", isRegionExpanded, hideCloseButton = false, state, @@ -150,6 +157,7 @@ export function useToast(originalProps: UseToastProps) shouldShowTimeoutProgess = false, icon, onClose, + severity, ...otherProps } = props; @@ -166,9 +174,9 @@ export function useToast(originalProps: UseToastProps) if (isMobile) { if (placementProp.includes("top")) { - placement = "center-top"; + placement = "top-center"; } else { - placement = "center-bottom"; + placement = "bottom-center"; } } @@ -317,8 +325,8 @@ export function useToast(originalProps: UseToastProps) const shouldCloseToast = (offsetX: number, offsetY: number) => { const isRight = placement.includes("right"); const isLeft = placement.includes("left"); - const isCenterTop = placement === "center-top"; - const isCenterBottom = placement === "center-bottom"; + const isCenterTop = placement === "top-center"; + const isCenterBottom = placement === "bottom-center"; if ( (isRight && offsetX >= SWIPE_THRESHOLD_X) || @@ -362,7 +370,7 @@ export function useToast(originalProps: UseToastProps) let opacityValue: undefined | number = undefined; - if ((drag && placement === "center-bottom") || placement === "center-top") { + if ((drag && placement === "bottom-center") || placement === "top-center") { opacityValue = Math.max(0, 1 - dragValue / (SWIPE_THRESHOLD_Y + 5)); } else if (drag) { opacityValue = Math.max(0, 1 - dragValue / (SWIPE_THRESHOLD_X + 20)); @@ -471,7 +479,7 @@ export function useToast(originalProps: UseToastProps) className: string; } => { const isCloseToEnd = total - index - 1 <= 2; - const dragDirection = placement === "center-bottom" || placement === "center-top" ? "y" : "x"; + const dragDirection = placement === "bottom-center" || placement === "top-center" ? "y" : "x"; const dragConstraints = {left: 0, right: 0, top: 0, bottom: 0}; const dragElastic = getDragElasticConstraints(placement); @@ -534,9 +542,9 @@ export function useToast(originalProps: UseToastProps) onDrag: (_, info) => { let updatedDragValue = 0; - if (placement === "center-top") { + if (placement === "top-center") { updatedDragValue = -info.offset.y; - } else if (placement === "center-bottom") { + } else if (placement === "bottom-center") { updatedDragValue = info.offset.y; } else if (placement.includes("right")) { updatedDragValue = info.offset.x; @@ -587,6 +595,7 @@ export function useToast(originalProps: UseToastProps) icon, loadingIcon, domRef, + severity, closeIcon, classNames, color: variantProps["color"], diff --git a/packages/components/toast/stories/toast.stories.tsx b/packages/components/toast/stories/toast.stories.tsx index 8358f2b58..67faf55ad 100644 --- a/packages/components/toast/stories/toast.stories.tsx +++ b/packages/components/toast/stories/toast.stories.tsx @@ -13,6 +13,10 @@ export default { control: {type: "select"}, options: ["flat", "bordered", "solid"], }, + severity: { + control: {type: "select"}, + options: ["default", "primary", "secondary", "success", "warning", "danger"], + }, color: { control: {type: "select"}, options: ["default", "foreground", "primary", "secondary", "success", "warning", "danger"], @@ -37,12 +41,12 @@ export default { placement: { control: {type: "select"}, options: [ - "right-bottom", - "left-bottom", - "center-bottom", - "right-top", - "left-top", - "center-top", + "bottom-right", + "bottom-left", + "bottom-center", + "top-right", + "top-left", + "top-center", ], }, hideCloseButton: { diff --git a/packages/core/theme/src/components/toast.ts b/packages/core/theme/src/components/toast.ts index a668019e2..ee0fd8667 100644 --- a/packages/core/theme/src/components/toast.ts +++ b/packages/core/theme/src/components/toast.ts @@ -14,12 +14,12 @@ const toastRegion = tv({ }, true: { base: [ - "data-[placement=right-bottom]:bottom-0 data-[placement=right-bottom]:right-0 w-full px-2 sm:w-auto sm:px-0 data-[placement=right-bottom]:fixed data-[placement=right-bottom]:flex data-[placement=right-bottom]:flex-col", - "data-[placement=left-bottom]:bottom-0 data-[placement=left-bottom]:left-0 w-full px-2 sm:w-auto sm:px-0 data-[placement=left-bottom]:fixed data-[placement=left-bottom]:flex data-[placement=left-bottom]:flex-col", - "data-[placement=center-bottom]:bottom-0 data-[placement=center-bottom]:fixed w-full px-2 sm:w-auto sm:px-0 data-[placement=center-bottom]:flex data-[placement=center-bottom]:flex-col data-[placement=center-bottom]:left-1/2 data-[placement=center-bottom]:-translate-x-1/2", - "data-[placement=right-top]:top-0 data-[placement=right-top]:right-0 w-full px-2 sm:w-auto sm:px-0 data-[placement=right-top]:fixed data-[placement=right-top]:flex data-[placement=right-top]:flex-col", - "data-[placement=left-top]:top-0 data-[placement=left-top]:left-0 w-full px-2 sm:w-auto sm:px-0 data-[placement=left-top]:fixed data-[placement=left-top]:flex data-[placement=left-top]:flex-col", - "data-[placement=center-top]:top-0 data-[placement=center-top]:fixed w-full px-2 sm:w-auto sm:px-0 data-[placement=center-top]:flex data-[placement=center-top]:flex-col data-[placement=center-top]:left-1/2 data-[placement=center-top]:-translate-x-1/2", + "data-[placement=bottom-right]:bottom-0 data-[placement=bottom-right]:right-0 w-full px-2 sm:w-auto sm:px-0 data-[placement=bottom-right]:fixed data-[placement=bottom-right]:flex data-[placement=bottom-right]:flex-col", + "data-[placement=bottom-left]:bottom-0 data-[placement=bottom-left]:left-0 w-full px-2 sm:w-auto sm:px-0 data-[placement=bottom-left]:fixed data-[placement=bottom-left]:flex data-[placement=bottom-left]:flex-col", + "data-[placement=bottom-center]:bottom-0 data-[placement=bottom-center]:fixed w-full px-2 sm:w-auto sm:px-0 data-[placement=bottom-center]:flex data-[placement=bottom-center]:flex-col data-[placement=bottom-center]:left-1/2 data-[placement=bottom-center]:-translate-x-1/2", + "data-[placement=top-right]:top-0 data-[placement=top-right]:right-0 w-full px-2 sm:w-auto sm:px-0 data-[placement=top-right]:fixed data-[placement=top-right]:flex data-[placement=top-right]:flex-col", + "data-[placement=top-left]:top-0 data-[placement=top-left]:left-0 w-full px-2 sm:w-auto sm:px-0 data-[placement=top-left]:fixed data-[placement=top-left]:flex data-[placement=top-left]:flex-col", + "data-[placement=top-center]:top-0 data-[placement=top-center]:fixed w-full px-2 sm:w-auto sm:px-0 data-[placement=top-center]:flex data-[placement=top-center]:flex-col data-[placement=top-center]:left-1/2 data-[placement=top-center]:-translate-x-1/2", ], }, }, @@ -54,12 +54,12 @@ const toast = tv({ motionDiv: [ "fixed", "px-4 sm:px-0", - "data-[placement=right-bottom]:bottom-0 data-[placement=right-bottom]:right-0 data-[placement=right-bottom]:mx-auto w-full sm:data-[placement=right-bottom]:w-max mb-1 sm:data-[placement=right-bottom]:mr-2", - "data-[placement=left-bottom]:bottom-0 data-[placement=left-bottom]:left-0 data-[placement=left-bottom]:mx-auto w-full sm:data-[placement=left-bottom]:w-max mb-1 sm:data-[placement=left-bottom]:ml-2", - "data-[placement=center-bottom]:bottom-0 data-[placement=center-bottom]:left-0 data-[placement=center-bottom]:right-0 w-full sm:data-[placement=center-bottom]:w-max sm:data-[placement=center-bottom]:mx-auto", - "data-[placement=right-top]:top-0 data-[placement=right-top]:right-0 data-[placement=right-top]:mx-auto w-full sm:data-[placement=right-top]:w-max sm:data-[placement=right-top]:mr-2", - "data-[placement=left-top]:top-0 data-[placement=left-top]:left-0 data-[placement=left-top]:mx-auto w-full sm:data-[placement=left-top]:w-max sm:data-[placement=left-top]:ml-2", - "data-[placement=center-top]:top-0 data-[placement=center-top]:left-0 data-[placement=center-top]:right-0 w-full sm:data-[placement=center-top]:w-max sm:data-[placement=center-top]:mx-auto", + "data-[placement=bottom-right]:bottom-0 data-[placement=bottom-right]:right-0 data-[placement=bottom-right]:mx-auto w-full sm:data-[placement=bottom-right]:w-max mb-1 sm:data-[placement=bottom-right]:mr-2", + "data-[placement=bottom-left]:bottom-0 data-[placement=bottom-left]:left-0 data-[placement=bottom-left]:mx-auto w-full sm:data-[placement=bottom-left]:w-max mb-1 sm:data-[placement=bottom-left]:ml-2", + "data-[placement=bottom-center]:bottom-0 data-[placement=bottom-center]:left-0 data-[placement=bottom-center]:right-0 w-full sm:data-[placement=bottom-center]:w-max sm:data-[placement=bottom-center]:mx-auto", + "data-[placement=top-right]:top-0 data-[placement=top-right]:right-0 data-[placement=top-right]:mx-auto w-full sm:data-[placement=top-right]:w-max sm:data-[placement=top-right]:mr-2", + "data-[placement=top-left]:top-0 data-[placement=top-left]:left-0 data-[placement=top-left]:mx-auto w-full sm:data-[placement=top-left]:w-max sm:data-[placement=top-left]:ml-2", + "data-[placement=top-center]:top-0 data-[placement=top-center]:left-0 data-[placement=top-center]:right-0 w-full sm:data-[placement=top-center]:w-max sm:data-[placement=top-center]:mx-auto", ], closeButton: [ "opacity-0 pointer-events-none group-hover:pointer-events-auto p-0 group-hover:opacity-100 w-6 h-6 min-w-4 absolute -right-2 -top-2 items-center justify-center bg-transparent text-default-400 hover:text-default-600 border border-3 border-transparent", @@ -143,12 +143,12 @@ const toast = tv({ base: [ "data-[animation=exiting]:transform", "data-[animation=exiting]:delay-100", - "data-[animation=exiting]:data-[placement=right-bottom]:translate-x-28", - "data-[animation=exiting]:data-[placement=left-bottom]:-translate-x-28", - "data-[animation=exiting]:data-[placement=center-bottom]:translate-y-28", - "data-[animation=exiting]:data-[placement=right-top]:translate-x-28", - "data-[animation=exiting]:data-[placement=left-top]:-translate-x-28", - "data-[animation=exiting]:data-[placement=center-top]:-translate-y-28", + "data-[animation=exiting]:data-[placement=bottom-right]:translate-x-28", + "data-[animation=exiting]:data-[placement=bottom-left]:-translate-x-28", + "data-[animation=exiting]:data-[placement=bottom-center]:translate-y-28", + "data-[animation=exiting]:data-[placement=top-right]:translate-x-28", + "data-[animation=exiting]:data-[placement=top-left]:-translate-x-28", + "data-[animation=exiting]:data-[placement=top-center]:-translate-y-28", "data-[animation=exiting]:opacity-0", "data-[animation=exiting]:duration-200", ],