fix(toast): icons (#5246)

* feat(shared-icons): add loading icon

* fix(toast): icons

* chore(toast): revise types for icons

* chore(changeset): add changeset
This commit is contained in:
WK 2025-06-02 00:50:43 +08:00 committed by GitHub
parent ee4d2ebe25
commit 8df9716dfa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 94 additions and 57 deletions

View File

@ -0,0 +1,6 @@
---
"@heroui/shared-icons": patch
"@heroui/toast": patch
---
support render icons by function in Toast

View File

@ -1,3 +1,5 @@
import type {ReactElement} from "react";
import {forwardRef} from "@heroui/system";
import {Button, ButtonProps} from "@heroui/button";
import {
@ -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
@ -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()}>

View File

@ -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";
@ -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
@ -157,6 +158,7 @@ export function useToast<T extends ToastProps>(originalProps: UseToastProps<T>)
timeout = 6000,
shouldShowTimeoutProgress = false,
icon,
loadingIcon,
onClose,
severity,
maxVisibleToasts,
@ -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);

View File

@ -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";
@ -352,7 +353,7 @@ const CustomToastTemplate = (args) => {
);
};
const CustomCloseButtonTemplate = (args) => {
const CustomCloseIconTemplate = (args) => {
return (
<>
<ToastProvider
@ -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,
})
}
>
@ -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,
@ -470,13 +438,6 @@ export const Placement = {
},
};
export const WithEndContent = {
render: WithEndContentTemplate,
args: {
...defaultProps,
},
};
export const ToastFromOverlay = {
render: WithToastFromOverlayTemplate,
args: {
@ -491,8 +452,26 @@ export const CustomStyles = {
},
};
export const CustomCloseButton = {
render: CustomCloseButtonTemplate,
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 CustomCloseIcon = {
render: CustomCloseIconTemplate,
args: {
...defaultProps,
},

View File

@ -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";

View File

@ -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>
);