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
7 changes: 7 additions & 0 deletions .changeset/purple-seahorses-sin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@heroui/toast": patch
"@heroui/theme": patch
---

Making toast compatible with RA upgrade.
Changing the type of description prop to ReactNode(#5033).
9 changes: 2 additions & 7 deletions apps/docs/content/components/toast/custom-styles.raw.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,9 @@ const CustomToastComponent = () => {
classNames: {
base: cn([
"bg-default-50 dark:bg-background shadow-sm",
"border-1",
"relative before:content-[''] before:absolute before:z-10",
"before:left-0 before:top-[-1px] before:bottom-[-1px] before:w-1",
"rounded-l-none border-l-0",
"min-w-[350px]",
"rounded-md",
"border border-l-8 rounded-md rounded-l-none",
"flex flex-col items-start",
"before:bg-primary border-primary-200 dark:border-primary-100",
"border-primary-200 dark:border-primary-100 border-l-primary",
]),
icon: "w-6 h-6 fill-current",
},
Expand Down
12 changes: 6 additions & 6 deletions apps/docs/content/docs/components/toast.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,12 @@ Toast has the following slots:
description: "Whether to indicate the timeout progress or not",
default: "false",
},
{
attribute: "severity",
type: "default | primary | secondary | success | warning | danger",
description: "The severity of the toast. This changes the icon of the toast without having to change the color.",
default: "default"
},
{
attribute: "classNames",
type: "Partial<Record<\"base\" | \"content\" | \"wrapper\" | \"title\" | \"description\" | \"icon\" | \"loadingIcon\" | \"progressTrack\" | \"progressIndicator\ | \"motionDiv\" | \"closeButton\" | \"closeIcon\", string>>",
Expand All @@ -301,12 +307,6 @@ Toast has the following slots:
description: "The placement of the toast.",
default: "bottom-right"
},
{
attribute: "severity",
type: "default | primary | secondary | success | warning | danger",
description: "The severity of the toast. This changes the icon of the toast without having to change the color.",
default: "default"
},
{
attribute: "disableAnimation",
type: "boolean",
Expand Down
6 changes: 2 additions & 4 deletions packages/components/toast/__tests__/toast.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,14 +114,12 @@ describe("Toast", () => {
await user.click(button);

const initialCloseButtons = wrapper.getAllByRole("button");
const initialButtonLength = initialCloseButtons.length;

await user.click(initialCloseButtons[0]);

const finalCloseButtons = wrapper.getAllByRole("button");
const finalButtonLength = finalCloseButtons.length;
const toast = wrapper.getAllByRole("alertdialog")[0]! as HTMLElement;

expect(initialButtonLength).toEqual(finalButtonLength + 1);
expect(toast).toHaveAttribute("data-toast-exiting", "true");
});

it("should work with placement", async () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/components/toast/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
},
"peerDependencies": {
"@heroui/system": ">=2.4.10",
"@heroui/theme": ">=2.4.9",
"@heroui/theme": ">=2.4.12",
"react": ">=18 || >=19.0.0-rc.0",
"react-dom": ">=18 || >=19.0.0-rc.0",
"framer-motion": ">=11.5.6 || >=12.0.0-alpha.1"
Expand Down
2 changes: 1 addition & 1 deletion packages/components/toast/src/toast-region.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export function ToastRegion<T extends ToastProps>({
data-placement={placement}
onTouchStart={handleTouchStart}
>
{toastQueue.visibleToasts.map((toast: QueuedToast<ToastProps>, index) => {
{[...toastQueue.visibleToasts].reverse().map((toast: QueuedToast<ToastProps>, index) => {
if (disableAnimation && total - index > maxVisibleToasts) {
return null;
}
Expand Down
83 changes: 61 additions & 22 deletions packages/components/toast/src/use-toast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export interface ToastProps extends ToastVariantProps {
/**
* description of the toast
*/
description?: string;
description?: ReactNode;
/**
* Promise based on which the notification will be styled.
*/
Expand Down Expand Up @@ -265,7 +265,7 @@ export function useToast<T extends ToastProps>(originalProps: UseToastProps<T>)

const domRef = useDOMRef(ref);
const baseStyles = clsx(className, classNames?.base);
const {toastProps, contentProps, titleProps, descriptionProps, closeButtonProps} = useToastAria(
const {toastProps, contentProps, titleProps, descriptionProps} = useToastAria(
props,
state,
domRef,
Expand All @@ -278,10 +278,11 @@ export function useToast<T extends ToastProps>(originalProps: UseToastProps<T>)
}, []);

const [initialHeight, setInitialHeight] = useState<number>(0);
const [isToastExiting, setIsToastExiting] = useState(false);

// Following was inspired from sonner ❤️
useLayoutEffect(() => {
if (!domRef.current || !mounted) {
if (!domRef.current || !mounted || isToastExiting) {
return;
}
const toastNode = domRef.current;
Expand All @@ -304,7 +305,7 @@ export function useToast<T extends ToastProps>(originalProps: UseToastProps<T>)
updatedHeights.push(newHeight);
}
setHeights(updatedHeights);
}, [mounted, total, setHeights, index]);
}, [mounted, total, setHeights, index, isToastExiting]);

let liftHeight = 4;

Expand Down Expand Up @@ -388,21 +389,57 @@ export function useToast<T extends ToastProps>(originalProps: UseToastProps<T>)
}

const getToastProps: PropGetter = useCallback(
(props = {}) => ({
ref: domRef,
className: slots.base({class: clsx(baseStyles, classNames?.base)}),
"data-has-title": dataAttr(!isEmpty(title)),
"data-has-description": dataAttr(!isEmpty(description)),
"data-placement": placement,
"data-drag-value": dragValue,
"data-toast": true,
"aria-label": "toast",
style: {
opacity: opacityValue,
},
...mergeProps(props, otherProps, toastProps, hoverProps),
}),
[slots, classNames, toastProps, hoverProps, toast, toast.key, opacityValue],
(props = {}) => {
const aboveToastHeight = index + 1 < total ? heights[index + 1] : 0;
const belowToastHeight = index - 1 >= 0 ? heights[index - 1] : 0;

const topExtension = aboveToastHeight ? Math.ceil(aboveToastHeight / 2) + 8 : 16;
const bottomExtension = belowToastHeight ? Math.ceil(belowToastHeight / 2) + 8 : 16;

const pseudoElementStyles = {
"--top-extension": `${topExtension}px`,
"--bottom-extension": `${bottomExtension}px`,
};

return {
ref: domRef,
className: slots.base({class: clsx(baseStyles, classNames?.base)}),
"data-has-title": dataAttr(!isEmpty(title)),
"data-has-description": dataAttr(!isEmpty(description)),
"data-placement": placement,
"data-drag-value": dragValue,
"data-toast": true,
"aria-label": "toast",
"data-toast-exiting": dataAttr(isToastExiting),
onTransitionEnd: () => {
if (isToastExiting) {
const updatedHeights = heights;

updatedHeights.splice(index, 1);
setHeights([...updatedHeights]);

state.close(toast.key);
}
},
style: {
opacity: opacityValue,
...pseudoElementStyles,
},
...mergeProps(props, otherProps, toastProps, hoverProps),
};
},
[
slots,
classNames,
toastProps,
hoverProps,
toast,
toast.key,
opacityValue,
isToastExiting,
state,
toast.key,
],
);

const getWrapperProps: PropGetter = useCallback(
Expand Down Expand Up @@ -459,13 +496,15 @@ export function useToast<T extends ToastProps>(originalProps: UseToastProps<T>)
className: slots.closeButton({class: classNames?.closeButton}),
"aria-label": "closeButton",
"data-hidden": dataAttr(hideCloseButton),
...mergeProps(props, closeButtonProps, {
...mergeProps(props, {
onPress: chain(() => {
setIsToastExiting(true);

setTimeout(() => document.body.focus(), 0);
}, onClose),
}),
}),
[closeButtonProps, onClose],
[setIsToastExiting, onClose, state, toast],
);

const getCloseIconProps: PropGetter = useCallback(
Expand Down Expand Up @@ -578,11 +617,11 @@ export function useToast<T extends ToastProps>(originalProps: UseToastProps<T>)
};
},
[
closeButtonProps,
total,
index,
placement,
isRegionExpanded,
isToastExiting,
liftHeight,
multiplier,
initialHeight,
Expand Down
16 changes: 6 additions & 10 deletions packages/components/toast/stories/toast.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -291,11 +291,11 @@ const WithToastFromOverlayTemplate = (args) => {
const CustomToastComponent = (args) => {
const color = args.color;
const colorMap = {
primary: "before:bg-primary border-primary-200 dark:border-primary-100",
secondary: "before:bg-secondary border-secondary-200 dark:border-secondary-100",
success: "before:bg-success border-success-200 dark:border-success-100",
warning: "before:bg-warning border-warning-200 dark:border-warning-100",
danger: "before:bg-danger border-danger-200 dark:border-danger-100",
primary: "border-primary-200 dark:border-primary-100 border-l-primary",
secondary: "border-secondary-200 dark:border-secondary-100 border-l-secondary",
success: "border-success-200 dark:border-success-100 border-l-success",
warning: "border-warning-200 dark:border-warning-100 border-l-warning",
danger: "border-danger-200 dark:border-danger-100 border-l-danger",
};

return (
Expand All @@ -310,11 +310,7 @@ const CustomToastComponent = (args) => {
classNames: {
base: cn([
"bg-default-50 dark:bg-background shadow-sm",
"border-1",
"relative before:content-[''] before:absolute before:z-10",
"before:left-0 before:top-[-1px] before:bottom-[-1px] before:w-1",
"rounded-l-none border-l-0",
"rounded-md",
"border border-l-8 rounded-md rounded-l-none",
"flex flex-col items-start",
colorMap[color],
]),
Expand Down
56 changes: 43 additions & 13 deletions packages/core/theme/src/components/toast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,29 @@ const toast = tv({
"my-1",
"w-full sm:w-[356px]",
"min-h-4",

"before:content-['']",
"before:absolute",
"before:left-0",
"before:right-0",
"before:h-[var(--top-extension,16px)]",
"before:top-[calc(-1*var(--top-extension,16px))]",
"before:z-[-1]",
"before:pointer-events-auto",
"before:bg-transparent",

"after:content-['']",
"after:absolute",
"after:left-0",
"after:right-0",
"after:h-[var(--bottom-extension,16px)]",
"after:bottom-[calc(-1*var(--bottom-extension,16px))]",
"after:z-[-1]",
"after:pointer-events-auto",
"after:bg-transparent",
"transform-gpu",
"will-change-transform",
"backface-visibility-hidden",
],
wrapper: ["flex flex-col gap-y-0"],
title: ["text-sm", "me-4", "font-medium", "text-foreground"],
Expand All @@ -63,7 +86,11 @@ const toast = tv({
"data-[placement=top-center]:top-0 data-[placement=top-center]:left-0 data-[placement=top-center]:right-0 w-full sm:data-[placement=top-center]:w-max sm:data-[placement=top-center]:mx-auto",
],
closeButton: [
"opacity-0 pointer-events-none group-hover:pointer-events-auto p-0 group-hover:opacity-100 w-6 h-6 min-w-4 absolute -right-2 -top-2 items-center justify-center bg-transparent text-default-400 hover:text-default-600 border border-3 border-transparent",
"opacity-0 group-hover:opacity-100",
"transform-gpu",
"transition-all duration-200 ease-out",
"will-change-opacity will-change-transform",
"p-0 group-hover:pointer-events-auto w-6 h-6 min-w-4 absolute -right-2 -top-2 items-center justify-center bg-transparent text-default-400 hover:text-default-600 border border-3 border-transparent",
"data-[hidden=true]:hidden",
],
closeIcon: ["rounded-full w-full h-full p-0.5 border border-default-400 bg-default-100"],
Expand Down Expand Up @@ -129,21 +156,24 @@ const toast = tv({
disableAnimation: {
true: {
closeButton: "transition-none",
base: "data-[animation=exiting]:opacity-0",
base: "data-[animation=exiting]:opacity-0 transition-none",
},
false: {
closeButton: "transition-opacity ease-in duration-300",
closeButton: "transition-all ease-out duration-200",
base: [
"data-[animation=exiting]:transform",
"data-[animation=exiting]:delay-100",
"data-[animation=exiting]:data-[placement=bottom-right]:translate-x-28",
"data-[animation=exiting]:data-[placement=bottom-left]:-translate-x-28",
"data-[animation=exiting]:data-[placement=bottom-center]:translate-y-28",
"data-[animation=exiting]:data-[placement=top-right]:translate-x-28",
"data-[animation=exiting]:data-[placement=top-left]:-translate-x-28",
"data-[animation=exiting]:data-[placement=top-center]:-translate-y-28",
"data-[animation=exiting]:opacity-0",
"data-[animation=exiting]:duration-200",
"data-[toast-exiting=true]:transform-gpu",
"data-[toast-exiting=true]:will-change-transform",
"data-[toast-exiting=true]:transition-all",
"data-[toast-exiting=true]:ease-out",
"data-[toast-exiting=true]:data-[placement=bottom-right]:translate-x-full",
"data-[toast-exiting=true]:data-[placement=bottom-left]:-translate-x-full",
"data-[toast-exiting=true]:data-[placement=bottom-center]:translate-y-full",
"data-[toast-exiting=true]:data-[placement=top-right]:translate-x-full",
"data-[toast-exiting=true]:data-[placement=top-left]:-translate-x-full",
"data-[toast-exiting=true]:data-[placement=top-center]:-translate-y-full",
"data-[toast-exiting=true]:opacity-0",
"data-[toast-exiting=true]:duration-300",
"data-[toast-exiting=true]:ease-out",
],
},
},
Expand Down
Loading