Skip to content

Commit

Permalink
feat: adding the promises support
Browse files Browse the repository at this point in the history
  • Loading branch information
macci001 committed Jan 1, 2025
1 parent 983d94f commit c38a181
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 30 deletions.
3 changes: 2 additions & 1 deletion packages/components/toast/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
"@react-aria/toast": "3.0.0-beta.15",
"@react-aria/utils": "3.24.1",
"@react-aria/interactions": "3.22.5",
"@react-stately/toast": "3.0.0-beta.5"
"@react-stately/toast": "3.0.0-beta.5",
"@react-stately/utils": "3.10.5"
},
"devDependencies": {
"@nextui-org/system": "workspace:*",
Expand Down
4 changes: 4 additions & 0 deletions packages/components/toast/src/toast-region.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ export function ToastRegion<T extends ToastProps>({
onTouchStart={handleTouchStart}
>
{toastQueue.visibleToasts.map((toast: QueuedToast<ToastProps>, index) => {
if (disableAnimation && total - index > maxVisibleToasts) {
return null;
}

if (total - index <= 4 || (isHovered && total - index <= maxVisibleToasts + 1)) {
return (
<Toast
Expand Down
31 changes: 18 additions & 13 deletions packages/components/toast/src/toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
InfoFilledIcon,
SuccessIcon,
WarningIcon,
LoadingIcon,
} from "@nextui-org/shared-icons";
import {motion, AnimatePresence} from "framer-motion";
import {cloneElement, isValidElement, useState} from "react";
Expand Down Expand Up @@ -51,6 +52,7 @@ const Toast = forwardRef<"div", ToastProps>((props, ref) => {
liftHeight,
initialHeight,
frontHeight,
isLoading,
} = useToast({
...props,
ref,
Expand All @@ -70,6 +72,7 @@ const Toast = forwardRef<"div", ToastProps>((props, ref) => {

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");
Expand All @@ -87,10 +90,24 @@ const Toast = forwardRef<"div", ToastProps>((props, ref) => {
}
};

const positionStyles: Record<string, string> = {
"right-bottom": "bottom-0 right-0 mx-auto w-max",
"left-bottom": "bottom-0 left-0 mx-auto w-max",
"center-bottom": "bottom-0 left-0 right-0 w-max mx-auto",
"right-top": "top-0 right-0 mx-auto w-max",
"left-top": "top-0 left-0 mx-auto w-max",
"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()}>
{hideIcon ? null : customIcon || <IconComponent {...getIconProps()} />}
{hideIcon && !isLoading
? null
: loadingIcon || customIcon || <IconComponent {...getIconProps()} />}
<div>
<div {...getTitleProps()}>{props.toast.content.title}</div>
<div {...getDescriptionProps()}>{props.toast.content.description}</div>
Expand All @@ -111,18 +128,6 @@ const Toast = forwardRef<"div", ToastProps>((props, ref) => {
</Component>
);

const positionStyles: Record<string, string> = {
"right-bottom": "bottom-0 right-0 mx-auto w-max",
"left-bottom": "bottom-0 left-0 mx-auto w-max",
"center-bottom": "bottom-0 left-0 right-0 w-max mx-auto",
"right-top": "top-0 right-0 mx-auto w-max",
"left-top": "top-0 left-0 mx-auto w-max",
"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);

return (
<>
{disableAnimation ? (
Expand Down
15 changes: 15 additions & 0 deletions packages/components/toast/src/use-toast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ export interface ToastProps extends ToastVariantProps {
* description of the toast
*/
description?: string;
/**
* Promise based on which the notification will be sent.
*/
promise?: Promise<any>;
/**
* Classname or List of classes to change the classNames of the element.
* if `className` is passed, it will be added to the base slot.
Expand Down Expand Up @@ -164,10 +168,20 @@ export function useToast<T extends ToastProps>(originalProps: UseToastProps<T>)
total = 1,
index = 0,
heights,
promise: promiseProp,
setHeights,
...otherProps
} = props;

const [isLoading, setIsLoading] = useState<boolean>(!!promiseProp);

useEffect(() => {
if (!promiseProp) return;
promiseProp.finally(() => {
setIsLoading(false);
});
}, [promiseProp]);

const Component = as || "div";
const icon: ReactNode = props.icon;

Expand Down Expand Up @@ -326,6 +340,7 @@ export function useToast<T extends ToastProps>(originalProps: UseToastProps<T>)
liftHeight,
frontHeight,
initialHeight,
isLoading,
};
}

Expand Down
76 changes: 64 additions & 12 deletions packages/components/toast/stories/toast.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,6 @@ export default {
type: "boolean",
},
},
disableAnimation: {
control: {
type: "boolean",
},
},
position: {
control: {type: "select"},
options: [
Expand Down Expand Up @@ -77,7 +72,7 @@ const defaultProps = {
const Template = (args: ToastProps) => {
return (
<>
<ToastProvider />
<ToastProvider position={args.position} />
<div>
<Button
onPress={() => {
Expand All @@ -98,7 +93,7 @@ const Template = (args: ToastProps) => {
const TimeoutTemplate = (args: ToastProps) => {
return (
<>
<ToastProvider />
<ToastProvider position={args.position} />
<Button
onPress={() => {
addToast({
Expand All @@ -118,7 +113,7 @@ const TimeoutTemplate = (args: ToastProps) => {
const WithEndContentTemplate = (args) => {
return (
<>
<ToastProvider />
<ToastProvider position={args.position} />
<Button
onPress={() => {
addToast({
Expand Down Expand Up @@ -162,6 +157,49 @@ const PositionTemplate = (args: ToastProps) => {
);
};

const DisableAnimationTemplate = (args: ToastProps) => {
return (
<>
<ToastProvider disableAnimation={true} position={args.position} />
<div>
<Button
onPress={() => {
addToast({
title: "Toast Title",
description: "Toast Displayed Successfully",
...args,
});
}}
>
Show toast
</Button>
</div>
</>
);
};

const PromiseToastTemplate = (args: ToastProps) => {
return (
<>
<ToastProvider position={args.position} />
<div>
<Button
onPress={() => {
addToast({
title: "Toast Title",
description: "Toast Displayed Successfully",
promise: new Promise((resolve) => setTimeout(resolve, 4000)),
...args,
});
}}
>
Show toast
</Button>
</div>
</>
);
};

const CustomToastComponent = (args) => {
const color = args.color;
const colorMap = {
Expand Down Expand Up @@ -214,12 +252,12 @@ const CustomToastComponent = (args) => {
);
};

const CustomToastTemplate = () => {
const CustomToastTemplate = (args) => {
const colors = ["primary", "secondary", "warning", "danger", "success"];

return (
<>
<ToastProvider />
<ToastProvider position={args.position} />
<div className="flex gap-2">
{colors.map((color, idx) => (
<CustomToastComponent key={idx} color={color} />
Expand Down Expand Up @@ -270,6 +308,20 @@ export const iconHidden = {
},
};

export const DisableAnimation = {
render: DisableAnimationTemplate,
args: {
...defaultProps,
},
};

export const PromiseToast = {
render: PromiseToastTemplate,
args: {
...defaultProps,
},
};

export const WithTimeout = {
render: TimeoutTemplate,
args: {
Expand All @@ -292,8 +344,8 @@ export const WithEndContent = {
};

export const CustomStyles = {
render: CustomToastTemplate,
args: {
disableAnimation: true,
...defaultProps,
},
render: CustomToastTemplate,
};
7 changes: 3 additions & 4 deletions packages/core/theme/src/components/toast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@ const toast = tv({
"my-1",
"w-[210px] sm:w-[270px] md:w-[300px]",
"min-h-4",
"text-white",
"shadow-inner",
"drop-shadow",
],
title: ["font-medium", "text-sm", "me-4"],
description: ["text-sm", "me-4"],
title: ["font-medium", "text-sm", "me-4", "text-foreground"],
description: ["text-sm", "me-4", "text-default-600"],
icon: ["w-6 h-6 fill-current"],
content: ["flex flex-grow flex-row gap-x-4 items-center relative"],
progressTrack: ["absolute h-[2px] left-0 -bottom-2 w-full bg-default-200"],
Expand Down
1 change: 1 addition & 0 deletions packages/utilities/shared-icons/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export * from "./info-circle";
export * from "./warning";
export * from "./danger";
export * from "./success";
export * from "./loading";

// sets
export * from "./bulk";
Expand Down
31 changes: 31 additions & 0 deletions packages/utilities/shared-icons/src/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {IconSvgProps} from "./types";

export const LoadingIcon = (
props: IconSvgProps & {
className?: string;
},
) => {
return (
<svg height="24" viewBox="0 0 100 100" width="24" xmlns="http://www.w3.org/2000/svg" {...props}>
<circle
cx="50"
cy="50"
fill="none"
r="32"
stroke="currentColor"
strokeDasharray="50.26548245743669 50.26548245743669"
strokeLinecap="round"
strokeWidth="8"
>
<animateTransform
attributeName="transform"
dur="1s"
keyTimes="0;1"
repeatCount="indefinite"
type="rotate"
values="0 50 50;360 50 50"
/>
</circle>
</svg>
);
};
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit c38a181

Please sign in to comment.