Skip to content
Open
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
11 changes: 11 additions & 0 deletions .changeset/fix-mui-breadcrumb-styling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@refinedev/mui": patch
---

fix(mui): use MuiLink in Breadcrumb LinkRouter to respect styling props

The `LinkRouter` helper in the Breadcrumb component was spreading MUI `LinkProps` (sx, underline, color, variant) onto a plain `<span>` element, which silently ignored all styling. Replaced with `<MuiLink component="span">` so props are properly processed by MUI's styling system.

This was a regression from the v5 migration (commit 5d63ada, #6945).

Fixes #7462
12 changes: 12 additions & 0 deletions .changeset/fix-notification-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@refinedev/antd": patch
"@refinedev/mantine": patch
---

fix(antd,mantine): respect notification type in notification providers

**@refinedev/antd**: Use `notification.success()` and `notification.error()` shortcut methods instead of `notification.open({ type })`, which does not render type-specific icons or colors in Ant Design.

**@refinedev/mantine**: Properly map notification types to distinct colors and icons — `success` renders with green/check, `error` renders with red/x — instead of treating all non-success types as error.

Fixes #7477
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const UndoableNotification: React.FC<UndoableNotificationProps> = ({
onClick={cancelMutation}
disabled={undoableTimeout === 0}
icon={<UndoOutlined />}
aria-label="undo"
/>
</div>
);
56 changes: 30 additions & 26 deletions packages/antd/src/providers/notificationProvider/index.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,35 +22,36 @@ describe("Antd useNotificationProvider", () => {
});

const notificationOpenSpy = vi.spyOn(notification, "open");
const notificationSuccessSpy = vi.spyOn(notification, "success");
const notificationErrorSpy = vi.spyOn(notification, "error");
const notificationCloseSpy = vi.spyOn(notification, "destroy");

it("should render notification type succes notification", async () => {
it("should render notification type success notification using success method", async () => {
const { result } = renderHook(() => useNotificationProvider(), {});

result.current.open?.(mockNotification);

expect(notificationOpenSpy).toHaveBeenCalledTimes(1);
expect(notificationOpenSpy).toHaveBeenCalledWith({
...mockNotification,
expect(notificationSuccessSpy).toHaveBeenCalledTimes(1);
expect(notificationSuccessSpy).toHaveBeenCalledWith({
key: mockNotification.key,
message: null,
description: mockNotification.message,
});
});

it("should render notification type error notification", async () => {
it("should render notification type error notification using error method", async () => {
const { result } = renderHook(() => useNotificationProvider(), {});

result.current.open?.({
...mockNotification,
type: "error",
});

expect(notificationOpenSpy).toHaveBeenCalledTimes(1);
expect(notificationOpenSpy).toHaveBeenCalledWith({
...mockNotification,
expect(notificationErrorSpy).toHaveBeenCalledTimes(1);
expect(notificationErrorSpy).toHaveBeenCalledWith({
key: mockNotification.key,
message: null,
description: mockNotification.message,
type: "error",
});
});

Expand All @@ -62,15 +63,15 @@ describe("Antd useNotificationProvider", () => {
description: "Notification Description",
});

expect(notificationOpenSpy).toHaveBeenCalledTimes(1);
expect(notificationOpenSpy).toHaveBeenCalledWith({
...mockNotification,
expect(notificationSuccessSpy).toHaveBeenCalledTimes(1);
expect(notificationSuccessSpy).toHaveBeenCalledWith({
key: mockNotification.key,
message: "Notification Description",
description: "Test Notification Message",
});
});

it("should render notification type error notification", async () => {
it("should render progress notification using open method", async () => {
const { result } = renderHook(() => useNotificationProvider(), {});

result.current.open?.({
Expand Down Expand Up @@ -109,12 +110,16 @@ describe("Antd useNotificationProvider", () => {

describe("using with Ant design's App component", () => {
const openFn = vi.fn();
const successFn = vi.fn();
const errorFn = vi.fn();
const destroyFn = vi.fn();

beforeAll(() => {
vi.spyOn(App, "useApp").mockReturnValue({
notification: {
open: openFn,
success: successFn,
error: errorFn,
destroy: destroyFn,
},
} as unknown as ReturnType<typeof App.useApp>);
Expand All @@ -124,24 +129,24 @@ describe("Antd useNotificationProvider", () => {
vi.clearAllMocks();
});

it("should render notification type succes notification", async () => {
it("should render notification type success notification using success method", async () => {
const { result } = renderHook(() => useNotificationProvider());

act(() => {
result.current.open?.(mockNotification);
});

await waitFor(() => {
expect(openFn).toHaveBeenCalledTimes(1);
expect(openFn).toHaveBeenCalledWith({
...mockNotification,
expect(successFn).toHaveBeenCalledTimes(1);
expect(successFn).toHaveBeenCalledWith({
key: mockNotification.key,
message: null,
description: mockNotification.message,
});
});
});

it("should render notification type error notification", async () => {
it("should render notification type error notification using error method", async () => {
const { result } = renderHook(() => useNotificationProvider());

act(() => {
Expand All @@ -152,12 +157,11 @@ describe("Antd useNotificationProvider", () => {
});

await waitFor(() => {
expect(openFn).toHaveBeenCalledTimes(1);
expect(openFn).toHaveBeenCalledWith({
...mockNotification,
expect(errorFn).toHaveBeenCalledTimes(1);
expect(errorFn).toHaveBeenCalledWith({
key: mockNotification.key,
message: null,
description: mockNotification.message,
type: "error",
});
});
});
Expand All @@ -173,16 +177,16 @@ describe("Antd useNotificationProvider", () => {
});

await waitFor(() => {
expect(openFn).toHaveBeenCalledTimes(1);
expect(openFn).toHaveBeenCalledWith({
...mockNotification,
expect(successFn).toHaveBeenCalledTimes(1);
expect(successFn).toHaveBeenCalledWith({
key: mockNotification.key,
message: "Notification Description",
description: "Test Notification Message",
});
});
});

it("should render notification type error notification", async () => {
it("should render progress notification using open method", async () => {
const { result } = renderHook(() => useNotificationProvider());

act(() => {
Expand Down
8 changes: 6 additions & 2 deletions packages/antd/src/providers/notificationProvider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,15 @@ export const useNotificationProvider = (): NotificationProvider => {
closeIcon: <></>,
});
} else {
notification.open({
const notificationMethod =
type && type in notification
? notification[type as "success" | "error"]
: notification.open;

notificationMethod({
key,
description: message,
message: description ?? null,
type,
});
}
},
Expand Down
31 changes: 17 additions & 14 deletions packages/mantine/src/providers/notificationProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export const useNotificationProvider = (): NotificationProvider => {
</Group>
<ActionIcon
variant="default"
aria-label="undo"
onClick={() => {
cancelMutation?.();
if (key) {
Expand Down Expand Up @@ -97,6 +98,7 @@ export const useNotificationProvider = (): NotificationProvider => {
</Group>
<ActionIcon
variant="default"
aria-label="undo"
onClick={() => {
cancelMutation?.();
if (key) {
Expand All @@ -123,16 +125,22 @@ export const useNotificationProvider = (): NotificationProvider => {
});
}
} else {
const notificationColor =
type === "success" ? "primary" : type === "error" ? "red" : "primary";
const notificationIcon =
type === "success" ? (
<IconCheck size={18} />
) : type === "error" ? (
<IconX size={18} />
) : (
<IconCheck size={18} />
);

if (isNotificationActive(key)) {
updateNotification({
id: key!,
color: type === "success" ? "primary" : "red",
icon:
type === "success" ? (
<IconCheck size={18} />
) : (
<IconX size={18} />
),
color: notificationColor,
icon: notificationIcon,
message,
title: description,
autoClose: 5000,
Expand All @@ -141,13 +149,8 @@ export const useNotificationProvider = (): NotificationProvider => {
addNotification(key);
showNotification({
id: key!,
color: type === "success" ? "primary" : "red",
icon:
type === "success" ? (
<IconCheck size={18} />
) : (
<IconX size={18} />
),
color: notificationColor,
icon: notificationIcon,
message,
title: description,
onClose: () => {
Expand Down
4 changes: 3 additions & 1 deletion packages/mui/src/components/breadcrumb/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ export const Breadcrumb: React.FC<BreadcrumbProps> = ({
const { to, children, ...restProps } = props;
return (
<Link to={to || ""}>
<span {...restProps}>{children}</span>
<MuiLink component="span" {...restProps}>
{children}
</MuiLink>
</Link>
);
};
Expand Down
1 change: 1 addition & 0 deletions packages/mui/src/components/crud/create/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export const Create: React.FC<CreateProps> = ({
goBackFromProps
) : (
<IconButton
aria-label="go back"
onClick={
action !== "list" || typeof action !== "undefined"
? back
Expand Down
1 change: 1 addition & 0 deletions packages/mui/src/components/crud/edit/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ export const Edit: React.FC<EditProps> = ({
goBackFromProps
) : (
<IconButton
aria-label="go back"
onClick={
action !== "list" && typeof action !== "undefined"
? back
Expand Down
1 change: 1 addition & 0 deletions packages/mui/src/components/crud/show/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ export const Show: React.FC<ShowProps> = ({
goBackFromProps
) : (
<IconButton
aria-label="go back"
onClick={
action !== "list" && typeof action !== "undefined"
? back
Expand Down
6 changes: 5 additions & 1 deletion packages/mui/src/components/themedLayout/sider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,11 @@ export const ThemedSider: React.FC<RefineThemedLayoutSiderProps> = ({
>
<RenderToTitle collapsed={siderCollapsed} />
{!siderCollapsed && (
<IconButton size="small" onClick={() => setSiderCollapsed(true)}>
<IconButton
size="small"
aria-label="collapse sidebar"
onClick={() => setSiderCollapsed(true)}
>
{<ChevronLeft />}
</IconButton>
)}
Expand Down
1 change: 1 addition & 0 deletions packages/mui/src/providers/notificationProvider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const useNotificationProvider = (): NotificationProvider => {
closeSnackbar(key);
}}
color="inherit"
aria-label="undo"
>
<UndoOutlined />
</IconButton>
Expand Down