Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/fast-ways-hang.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@heroui/shared-icons": patch
"@heroui/toast": patch
---

support render icons by function in Toast
21 changes: 16 additions & 5 deletions packages/components/toast/src/toast.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type {ReactElement} from "react";

import {forwardRef} from "@heroui/system";
import {Button, ButtonProps} from "@heroui/button";
import {
Expand Down Expand Up @@ -56,12 +58,19 @@ const Toast = forwardRef<"div", ToastProps>((props, ref) => {
ref,
});

const customIcon = icon && isValidElement(icon) ? cloneElement(icon, getIconProps()) : null;
const customIcon =
typeof icon === "function"
? icon(getIconProps())
: isValidElement(icon) && cloneElement(icon as ReactElement, getIconProps());

const IconComponent = severity ? iconMap[severity] : iconMap[color] || iconMap.default;

const customLoadingIcon =
loadingIcon && isValidElement(loadingIcon)
? cloneElement(loadingIcon, getLoadingIconProps())
: null;
typeof loadingIcon === "function"
? loadingIcon(getLoadingIconProps())
: isValidElement(loadingIcon) &&
cloneElement(loadingIcon as ReactElement, getLoadingIconProps());

const loadingIconComponent = isLoading
? customLoadingIcon || (
<Spinner
Expand All @@ -73,7 +82,9 @@ const Toast = forwardRef<"div", ToastProps>((props, ref) => {
: null;

const customCloseIcon =
closeIcon && isValidElement(closeIcon) ? cloneElement(closeIcon, {}) : null;
typeof closeIcon === "function"
? closeIcon({})
: isValidElement(closeIcon) && cloneElement(closeIcon as ReactElement, {});

const toastContent = (
<Component ref={domRef} {...getToastProps()}>
Expand Down
9 changes: 5 additions & 4 deletions packages/components/toast/src/use-toast.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {SlotsToClasses, ToastSlots, ToastVariantProps} from "@heroui/theme";
import type {DOMAttributes} from "react";

import {HTMLHeroUIProps, PropGetter, mapPropsVariants, useProviderContext} from "@heroui/system";
import {toast as toastTheme} from "@heroui/theme";
Expand Down Expand Up @@ -67,15 +68,15 @@ export interface ToastProps extends ToastVariantProps {
/**
* Icon to be displayed in the toast - overrides the default icon
*/
icon?: ReactNode;
icon?: ReactNode | ((props: DOMAttributes<HTMLElement>) => ReactNode);
/**
* Icon to be displayed in the close button - overrides the default close icon
*/
closeIcon?: ReactNode | ((props: any) => ReactNode);
closeIcon?: ReactNode | ((props: DOMAttributes<HTMLElement>) => ReactNode);
/**
* Icon to be displayed in the loading toast - overrides the loading icon
*/
loadingIcon?: ReactNode;
loadingIcon?: ReactNode | ((props: DOMAttributes<HTMLElement>) => ReactNode);
/**
* Whether the toast-icon should be hidden.
* @default false
Expand Down Expand Up @@ -157,6 +158,7 @@ export function useToast<T extends ToastProps>(originalProps: UseToastProps<T>)
timeout = 6000,
shouldShowTimeoutProgress = false,
icon,
loadingIcon,
onClose,
severity,
maxVisibleToasts,
Expand Down Expand Up @@ -261,7 +263,6 @@ export function useToast<T extends ToastProps>(originalProps: UseToastProps<T>)
]);

const Component = as || "div";
const loadingIcon: ReactNode = icon;

const domRef = useDOMRef(ref);
const baseStyles = clsx(className, classNames?.base);
Expand Down
73 changes: 26 additions & 47 deletions packages/components/toast/stories/toast.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
useDisclosure,
} from "@heroui/modal";
import {Drawer, DrawerContent} from "@heroui/drawer";
import {LoadingIcon, AvatarIcon, CloseIcon} from "@heroui/shared-icons";

import {Toast, ToastProps, ToastProvider, addToast, closeAll} from "../src";

Expand Down Expand Up @@ -352,7 +353,7 @@ const CustomToastTemplate = (args) => {
);
};

const CustomCloseButtonTemplate = (args) => {
const CustomCloseIconTemplate = (args) => {
return (
<>
<ToastProvider
Expand All @@ -367,23 +368,9 @@ const CustomCloseButtonTemplate = (args) => {
<Button
onPress={() =>
addToast({
title: "Toast Title",
title: "Custom Close Icon",
description: "Toast Description",
closeIcon: (
<svg
fill="none"
height="32"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="32"
>
<path d="M18 6 6 18" />
<path d="m6 6 12 12" />
</svg>
),
closeIcon: CloseIcon,
})
}
>
Expand All @@ -408,33 +395,14 @@ export const WithDescription = {
},
};

export const WithCustomIcon = {
render: Template,
export const WithEndContent = {
render: WithEndContentTemplate,
args: {
...defaultProps,
title: "Custom Icon",
icon: (
<svg height={24} viewBox="0 0 24 24" width={24}>
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeMiterlimit={10}
strokeWidth={1.5}
>
<path
d="M11.845 21.662C8.153 21.662 5 21.088 5 18.787s3.133-4.425 6.845-4.425c3.692 0 6.845 2.1 6.845 4.4s-3.134 2.9-6.845 2.9z"
data-name="Stroke 1"
/>
<path d="M11.837 11.174a4.372 4.372 0 10-.031 0z" data-name="Stroke 3" />
</g>
</svg>
),
},
};

export const iconHidden = {
export const IconHidden = {
render: Template,
args: {
...defaultProps,
Expand Down Expand Up @@ -470,29 +438,40 @@ export const Placement = {
},
};

export const WithEndContent = {
render: WithEndContentTemplate,
export const ToastFromOverlay = {
render: WithToastFromOverlayTemplate,
args: {
...defaultProps,
},
};

export const ToastFromOverlay = {
render: WithToastFromOverlayTemplate,
export const CustomStyles = {
render: CustomToastTemplate,
args: {
...defaultProps,
},
};

export const CustomStyles = {
render: CustomToastTemplate,
export const CustomIcon = {
render: Template,
args: {
...defaultProps,
title: "Custom Icon",
icon: AvatarIcon,
},
};

export const CustomLoadingIcon = {
render: PromiseToastTemplate,
args: {
...defaultProps,
title: "Custom Loading Icon",
loadingIcon: LoadingIcon,
},
};

export const CustomCloseButton = {
render: CustomCloseButtonTemplate,
export const CustomCloseIcon = {
render: CustomCloseIconTemplate,
args: {
...defaultProps,
},
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 @@ -28,6 +28,7 @@ export * from "./eye-filled";
export * from "./eye-slash-filled";
export * from "./search";
export * from "./lock-filled";
export * from "./loading";
export * from "./edit";
export * from "./delete";
export * from "./eye";
Expand Down
39 changes: 39 additions & 0 deletions packages/utilities/shared-icons/src/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type {IconSvgProps} from "./types";

export const LoadingIcon = (props: IconSvgProps) => (
<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg" {...props}>
<circle cx="40" cy="65" fill="#3871FF" r="15" stroke="#3871FF" strokeWidth="15">
<animate
attributeName="cy"
begin="-.4"
calcMode="spline"
dur="2"
keySplines=".5 0 .5 1;.5 0 .5 1"
repeatCount="indefinite"
values="65;135;65;"
/>
</circle>
<circle cx="100" cy="65" fill="#3871FF" r="15" stroke="#3871FF" strokeWidth="15">
<animate
attributeName="cy"
begin="-.2"
calcMode="spline"
dur="2"
keySplines=".5 0 .5 1;.5 0 .5 1"
repeatCount="indefinite"
values="65;135;65;"
/>
</circle>
<circle cx="160" cy="65" fill="#3871FF" r="15" stroke="#3871FF" strokeWidth="15">
<animate
attributeName="cy"
begin="0"
calcMode="spline"
dur="2"
keySplines=".5 0 .5 1;.5 0 .5 1"
repeatCount="indefinite"
values="65;135;65;"
/>
</circle>
</svg>
);