Skip to content

Commit

Permalink
fix: adding junior's suggestions
Browse files Browse the repository at this point in the history
  • Loading branch information
macci001 committed Jan 6, 2025
1 parent 404dddf commit 012d422
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 92 deletions.
2 changes: 1 addition & 1 deletion packages/components/toast/__tests__/toast.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ describe("Toast", () => {
const initialCloseButtons = wrapper.getAllByRole("button");
const initialButtonLength = initialCloseButtons.length;

await user.click(initialCloseButtons[initialButtonLength - 1]);
await user.click(initialCloseButtons[0]);

const finalCloseButtons = wrapper.getAllByRole("button");
const finalButtonLength = finalCloseButtons.length;
Expand Down
7 changes: 3 additions & 4 deletions packages/components/toast/src/toast-region.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {useEffect, useRef, useState} from "react";
import {useToastRegion, AriaToastRegionProps} from "@react-aria/toast";
import {QueuedToast, ToastState} from "@react-stately/toast";
import {createPortal} from "react-dom";
import {useHover} from "@react-aria/interactions";
import {mergeProps} from "@react-aria/utils";
import {clsx} from "@nextui-org/shared-utils";
Expand Down Expand Up @@ -70,11 +69,12 @@ export function ToastRegion<T extends ToastProps>({
setIsTouched(true);
};

return createPortal(
return (
<div
{...mergeProps(regionProps, hoverProps)}
ref={ref}
className={clsx(disableAnimation ? positionStyle : "")}
data-position={position}
onTouchStart={handleTouchStart}
>
{toastQueue.visibleToasts.map((toast: QueuedToast<ToastProps>, index) => {
Expand Down Expand Up @@ -102,7 +102,6 @@ export function ToastRegion<T extends ToastProps>({

return null;
})}
</div>,
document.body,
</div>
);
}
91 changes: 6 additions & 85 deletions packages/components/toast/src/toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import {
LoadingIcon,
} from "@nextui-org/shared-icons";
import {motion, AnimatePresence} from "framer-motion";
import {cloneElement, isValidElement, useState} from "react";
import {clsx} from "@nextui-org/shared-utils";
import {cloneElement, isValidElement} from "react";

import {UseToastProps, useToast} from "./use-toast";

Expand All @@ -32,76 +31,29 @@ const Toast = forwardRef<"div", ToastProps>((props, ref) => {
endContent,
color,
hideIcon,
position,
toast,
state,
disableAnimation,
progressBarRef,
classNames,
slots,
isProgressBarVisible,
total,
index,
isRegionExpanded,
getToastProps,
getContentProps,
getTitleProps,
getDescriptionProps,
getCloseButtonProps,
getIconProps,
liftHeight,
initialHeight,
frontHeight,
getMotionDivProps,
getCloseIconProps,
isLoading,
} = useToast({
...props,
ref,
});

const toastVariants = position.includes("bottom")
? {
hidden: {opacity: 0, y: 50},
visible: {opacity: 1, y: 0},
exit: {opacity: 0, y: 50},
}
: {
hidden: {opacity: 0, y: -50},
visible: {opacity: 1, y: 0},
exit: {opacity: 0, y: -50},
};

const customIcon = icon && isValidElement(icon) ? cloneElement(icon, getIconProps()) : null;
const IconComponent = iconMap[color] || iconMap.primary;
const loadingIcon = isLoading ? <LoadingIcon /> : null;

const handleDragEnd = (offsetX: number, offsetY: number) => {
const isRight = position.includes("right");
const isLeft = position.includes("left");
const isTop = position === "center-top";
const isBottom = position === "center-bottom";

if (
(isRight && offsetX >= 50) ||
(isLeft && offsetX <= -50) ||
(isTop && offsetY <= -50) ||
(isBottom && offsetY >= 50)
) {
state.close(toast.key);
}
};

const positionStyles: Record<string, string> = {
"right-bottom": "bottom-0 right-0 mx-auto w-max mr-2",
"left-bottom": "bottom-0 left-0 mx-auto w-max ml-2",
"center-bottom": "bottom-0 left-0 right-0 w-max mx-auto",
"right-top": "top-0 right-0 mx-auto w-max mr-2",
"left-top": "top-0 left-0 mx-auto w-max ml-2",
"center-top": "top-0 left-0 right-0 w-max mx-auto",
};
const positionStyle = position ? positionStyles[position] : positionStyles["right-bottom"];
const multiplier = position.includes("top") ? 1 : -1;
const [drag, setDrag] = useState(false);

const toastContent = (
<Component ref={domRef} {...getToastProps()}>
<main {...getContentProps()}>
Expand All @@ -121,8 +73,8 @@ const Toast = forwardRef<"div", ToastProps>((props, ref) => {
)}
</div>
</main>
<Button {...(getCloseButtonProps() as ButtonProps)} isIconOnly variant="bordered">
<CloseIcon />
<Button isIconOnly {...(getCloseButtonProps() as ButtonProps)}>
<CloseIcon {...getCloseIconProps()} />
</Button>
{endContent}
</Component>
Expand All @@ -134,38 +86,7 @@ const Toast = forwardRef<"div", ToastProps>((props, ref) => {
toastContent
) : (
<AnimatePresence>
<motion.div
animate={{
opacity: total - index - 1 <= 2 ? 1 : 0,
pointerEvents: total - index - 1 <= 2 ? "all" : "none",
y: isRegionExpanded ? liftHeight * multiplier : (total - 1 - index) * 8 * multiplier,
scaleX: isRegionExpanded ? 1 : 1 - (total - 1 - index) * 0.1,
height: isRegionExpanded ? initialHeight : frontHeight,
}}
className={clsx(
"fixed",
positionStyle,
drag
? "before:content-[''] before:absolute before:left-0 before:right-0 before:h-full before:-z-10"
: "",
)}
drag={position.includes("center") ? "y" : "x"}
dragConstraints={{left: 0, right: 0, top: 0, bottom: 0}}
exit={{opacity: 0, y: 100}}
initial={{opacity: 0, y: -40 * multiplier, scale: 1}}
transition={{duration: 0.3, ease: "easeOut"}}
variants={toastVariants}
onDragEnd={(_, info) => {
setDrag(false);
const offsetX = info.offset.x;
const offsetY = info.offset.y;

handleDragEnd(offsetX, offsetY);
}}
onDragStart={() => {
setDrag(true);
}}
>
<motion.div {...getMotionDivProps()}>
<motion.div
animate={{opacity: 1}}
exit={{opacity: 0}}
Expand Down
102 changes: 102 additions & 0 deletions packages/components/toast/src/use-toast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {ReactNode, useCallback, useEffect, useLayoutEffect, useMemo, useRef, use
import {useToast as useToastAria, AriaToastProps} from "@react-aria/toast";
import {mergeProps} from "@react-aria/utils";
import {QueuedToast, ToastState} from "@react-stately/toast";
import {MotionProps} from "framer-motion";

export interface ToastProps extends ToastVariantProps {
/**
Expand Down Expand Up @@ -244,6 +245,35 @@ export function useToast<T extends ToastProps>(originalProps: UseToastProps<T>)
[objectToDeps(variantProps)],
);

const multiplier = position.includes("top") ? 1 : -1;
const toastVariants = position.includes("bottom")
? {
hidden: {opacity: 0, y: 50},
visible: {opacity: 1, y: 0},
exit: {opacity: 0, y: 50},
}
: {
hidden: {opacity: 0, y: -50},
visible: {opacity: 1, y: 0},
exit: {opacity: 0, y: -50},
};
const [drag, setDrag] = useState(false);
const handleDragEnd = (offsetX: number, offsetY: number) => {
const isRight = position.includes("right");
const isLeft = position.includes("left");
const isTop = position === "center-top";
const isBottom = position === "center-bottom";

if (
(isRight && offsetX >= 50) ||
(isLeft && offsetX <= -50) ||
(isTop && offsetY <= -50) ||
(isBottom && offsetY >= 50)
) {
state.close(toast.key);
}
};

const getToastProps: PropGetter = useCallback(
(props = {}) => ({
ref: domRef,
Expand Down Expand Up @@ -311,6 +341,76 @@ export function useToast<T extends ToastProps>(originalProps: UseToastProps<T>)
[closeButtonProps],
);

const getCloseIconProps: PropGetter = useCallback(
(props = {}) => ({
className: slots.closeIcon({class: classNames?.closeIcon}),
"aria-label": "close-icon",
...props,
}),
[],
);

const getMotionDivProps = useCallback(
(
props = {},
): MotionProps & {
"data-drag": string | boolean;
"data-position": string;
className: string;
} => {
const isCloseToEnd = total - index - 1 <= 2;
const dragDirection = position.includes("center") ? "y" : "x";
const dragConstraints = {left: 0, right: 0, top: 0, bottom: 0};

return {
animate: {
opacity: isCloseToEnd ? 1 : 0,
pointerEvents: isCloseToEnd ? "all" : "none",
y: isRegionExpanded ? liftHeight * multiplier : (total - 1 - index) * 8 * multiplier,
scaleX: isRegionExpanded ? 1 : 1 - (total - 1 - index) * 0.1,
height: isRegionExpanded ? initialHeight : frontHeight,
},
drag: dragDirection,
dragConstraints: dragConstraints,
exit: {opacity: 0, y: 100},
initial: {opacity: 0, y: -40 * multiplier, scale: 1},
transition: {duration: 0.3, ease: "easeOut"},
variants: toastVariants,
onDragEnd: (_, info) => {
setDrag(false);
const {x: offsetX, y: offsetY} = info.offset;

handleDragEnd(offsetX, offsetY);
},
onDragStart: () => {
setDrag(true);
},
"data-drag": dataAttr(drag),
"data-position": position,
className: slots.motionDiv({class: classNames?.motionDiv}),
...props,
};
},
[
closeButtonProps,
total,
index,
position,
isRegionExpanded,
liftHeight,
multiplier,
initialHeight,
frontHeight,
toastVariants,
classNames,
drag,
dataAttr,
setDrag,
handleDragEnd,
slots,
],
);

return {
Component,
title,
Expand All @@ -333,6 +433,8 @@ export function useToast<T extends ToastProps>(originalProps: UseToastProps<T>)
getDescriptionProps,
getCloseButtonProps,
getIconProps,
getMotionDivProps,
getCloseIconProps,
progressBarRef,
endContent,
slots,
Expand Down
2 changes: 1 addition & 1 deletion packages/components/toast/stories/toast.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default {
},
color: {
control: {type: "select"},
options: ["default", "primary", "secondary", "success", "warning", "danger"],
options: ["default", "foreground", "primary", "secondary", "success", "warning", "danger"],
},
radius: {
control: {type: "select"},
Expand Down
Loading

0 comments on commit 012d422

Please sign in to comment.