fix(toast): enable proper exit animations (#5121)

* fix(toast): enable proper exit animations
	•	Use LazyMotion and AnimatePresence in ToastProvider to support exit animations.
	•	Simplify Toast by removing redundant LazyMotion wrapper.
	•	Add motionProps to stories for easier animation customization.

* chore(changeset): add changeset for ToastProvider exit animations

* chore(toast): clean up stories by removing motionProps argument

* chore(docs): revert `CONTRIBUTING.md` and `toast.stories.tsx`to initial state
This commit is contained in:
Alexander Gavrilenko 2025-03-28 12:40:57 +01:00 committed by GitHub
parent 0cc127b1c7
commit ca5babcbb9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 35 additions and 31 deletions

View File

@ -0,0 +1,5 @@
---
"@heroui/toast": patch
---
- Use LazyMotion and AnimatePresence in ToastProvider to support exit animations.

View File

@ -1,9 +1,12 @@
import {ToastOptions, ToastQueue, useToastQueue} from "@react-stately/toast"; import {ToastOptions, ToastQueue, useToastQueue} from "@react-stately/toast";
import {useProviderContext} from "@heroui/system"; import {useProviderContext} from "@heroui/system";
import {AnimatePresence, LazyMotion} from "framer-motion";
import {RegionProps, ToastRegion} from "./toast-region"; import {RegionProps, ToastRegion} from "./toast-region";
import {ToastProps, ToastPlacement} from "./use-toast"; import {ToastProps, ToastPlacement} from "./use-toast";
const loadFeatures = () => import("framer-motion").then((res) => res.domMax);
let globalToastQueue: ToastQueue<ToastProps> | null = null; let globalToastQueue: ToastQueue<ToastProps> | null = null;
interface ToastProviderProps { interface ToastProviderProps {
@ -37,11 +40,10 @@ export const ToastProvider = ({
const globalContext = useProviderContext(); const globalContext = useProviderContext();
const disableAnimation = disableAnimationProp ?? globalContext?.disableAnimation ?? false; const disableAnimation = disableAnimationProp ?? globalContext?.disableAnimation ?? false;
if (toastQueue.visibleToasts.length == 0) {
return null;
}
return ( return (
<LazyMotion features={loadFeatures}>
<AnimatePresence>
{toastQueue.visibleToasts.length > 0 ? (
<ToastRegion <ToastRegion
disableAnimation={disableAnimation} disableAnimation={disableAnimation}
maxVisibleToasts={maxVisibleToasts} maxVisibleToasts={maxVisibleToasts}
@ -51,6 +53,9 @@ export const ToastProvider = ({
toastQueue={toastQueue} toastQueue={toastQueue}
{...regionProps} {...regionProps}
/> />
) : null}
</AnimatePresence>
</LazyMotion>
); );
}; };

View File

@ -7,14 +7,12 @@ import {
SuccessIcon, SuccessIcon,
WarningIcon, WarningIcon,
} from "@heroui/shared-icons"; } from "@heroui/shared-icons";
import {AnimatePresence, m, LazyMotion} from "framer-motion"; import {m} from "framer-motion";
import {cloneElement, isValidElement} from "react"; import {cloneElement, isValidElement} from "react";
import {Spinner} from "@heroui/spinner"; import {Spinner} from "@heroui/spinner";
import {UseToastProps, useToast} from "./use-toast"; import {UseToastProps, useToast} from "./use-toast";
const loadFeatures = () => import("framer-motion").then((res) => res.domMax);
export interface ToastProps extends UseToastProps {} export interface ToastProps extends UseToastProps {}
const iconMap = { const iconMap = {
@ -108,8 +106,6 @@ const Toast = forwardRef<"div", ToastProps>((props, ref) => {
{disableAnimation ? ( {disableAnimation ? (
toastContent toastContent
) : ( ) : (
<LazyMotion features={loadFeatures}>
<AnimatePresence>
<m.div {...getMotionDivProps()}> <m.div {...getMotionDivProps()}>
<m.div <m.div
key={"inner-div"} key={"inner-div"}
@ -121,8 +117,6 @@ const Toast = forwardRef<"div", ToastProps>((props, ref) => {
{toastContent} {toastContent}
</m.div> </m.div>
</m.div> </m.div>
</AnimatePresence>
</LazyMotion>
)} )}
</> </>
); );