From e8908447154c1b0be727c0166030ed54eb846136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Mon, 27 Apr 2026 16:44:51 +0800 Subject: [PATCH] support semantic notification slots --- src/Notification.tsx | 34 +++++++-- src/NotificationList/index.tsx | 12 +++- tests/index.test.tsx | 128 +++++++++++++++++++++++++++++++++ 3 files changed, 169 insertions(+), 5 deletions(-) diff --git a/src/Notification.tsx b/src/Notification.tsx index 86bb50f..2d47fe2 100644 --- a/src/Notification.tsx +++ b/src/Notification.tsx @@ -11,6 +11,9 @@ export interface NotificationClassNames { root?: string; icon?: string; section?: string; + title?: string; + description?: string; + actions?: string; close?: string; progress?: string; } @@ -20,6 +23,9 @@ export interface NotificationStyles { root?: React.CSSProperties; icon?: React.CSSProperties; section?: React.CSSProperties; + title?: React.CSSProperties; + description?: React.CSSProperties; + actions?: React.CSSProperties; close?: React.CSSProperties; progress?: React.CSSProperties; } @@ -42,6 +48,7 @@ export interface NotificationProps { description?: React.ReactNode; icon?: React.ReactNode; actions?: React.ReactNode; + role?: string; closable?: ClosableType; offset?: number; notificationIndex?: number; @@ -78,6 +85,7 @@ const Notification = React.forwardRef((props, description, icon, actions, + role, closable, offset, notificationIndex, @@ -167,12 +175,19 @@ const Notification = React.forwardRef((props, // ======================== Content ========================= const titleNode = title !== undefined && title !== null ? ( -
{title}
+
+ {title} +
) : null; const descNode = description !== undefined && description !== null ? ( -
{description}
+
+ {description} +
) : null; const hasTitle = titleNode !== null; @@ -204,6 +219,15 @@ const Notification = React.forwardRef((props, ); } + const actionsNode = actions ? ( +
+ {actions} +
+ ) : null; + // ========================= Render ========================= const mergedStyle: React.CSSProperties & { '--notification-index'?: number; @@ -218,10 +242,13 @@ const Notification = React.forwardRef((props, mergedStyle['--notification-y'] = `${mergedOffset}px`; } + const mergedRole = role ?? rootProps?.role ?? 'alert'; + return (
((props, onMouseLeave={onInternalMouseLeave} > {contentNode} + {actionsNode} {mergedClosable && ( )} - {actions &&
{actions}
} - {showProgress && typeof duration === 'number' && duration > 0 && ( void; } -const noticeSlotKeys = ['wrapper', 'root', 'icon', 'section', 'close', 'progress'] as const; +const noticeSlotKeys: (keyof NoticeClassNames)[] = [ + 'wrapper', + 'root', + 'icon', + 'section', + 'title', + 'description', + 'actions', + 'close', + 'progress', +]; function fillClassNames( classNamesList: (NotificationClassNames | undefined)[], diff --git a/tests/index.test.tsx b/tests/index.test.tsx index 1a48e64..d79deae 100644 --- a/tests/index.test.tsx +++ b/tests/index.test.tsx @@ -584,6 +584,46 @@ describe('Notification.Basic', () => { expect(notice[0].getAttribute('role')).toBe('alert'); }); + it('sets role attribute from notification config', () => { + const { instance } = renderDemo(); + + act(() => { + instance.open({ + title: 'bamboo', + description: simple show, + icon: , + actions: , + role: 'status', + closable: true, + showProgress: true, + duration: 3, + }); + }); + + const notice = document.querySelector('.rc-notification-notice'); + + expect(notice).toHaveAttribute('role', 'status'); + expect(notice.querySelector('.rc-notification-notice-content')).toBeFalsy(); + expect(notice.querySelector('.rc-notification-notice-title')).toBeTruthy(); + expect(notice.querySelector('.rc-notification-notice-description')).toBeTruthy(); + expect(notice.querySelector('.rc-notification-notice-icon')).toBeTruthy(); + expect(notice.querySelector('.rc-notification-notice-actions')).toBeTruthy(); + expect(notice.querySelector('.rc-notification-notice-close')).toBeTruthy(); + expect(notice.querySelector('.rc-notification-notice-progress')).toBeTruthy(); + }); + + it('sets default role attribute', () => { + const { instance } = renderDemo(); + + act(() => { + instance.open({ + description: simple show, + }); + }); + + expect(document.querySelector('.rc-notification-notice')).toHaveAttribute('role', 'alert'); + }); + it('should style work', () => { const { instance } = renderDemo({ style: () => ({ @@ -694,6 +734,94 @@ describe('Notification.Basic', () => { expect(document.querySelector('.bamboo')).toHaveClass('bamboo'); }); + it('should support semantic content styles and classNames', () => { + const { instance } = renderDemo({ + classNames: { + title: 'global-title', + description: 'global-description', + actions: 'global-actions', + icon: 'global-icon', + }, + styles: { + title: { + content: 'global-title', + }, + description: { + content: 'global-description', + }, + actions: { + content: 'global-actions', + }, + icon: { + content: 'global-icon', + }, + }, + }); + + act(() => { + instance.open({ + title: 'bamboo', + description: 'little', + icon: , + actions: , + classNames: { + title: 'notice-title', + description: 'notice-description', + actions: 'notice-actions', + icon: 'notice-icon', + }, + styles: { + title: { + marginTop: 1, + }, + description: { + marginRight: 2, + }, + actions: { + marginBottom: 3, + }, + icon: { + marginLeft: 4, + }, + }, + }); + }); + + expect(document.querySelector('.rc-notification-notice-title')).toHaveClass( + 'global-title', + 'notice-title', + ); + expect(document.querySelector('.rc-notification-notice-title')).toHaveStyle({ + content: 'global-title', + marginTop: '1px', + }); + expect(document.querySelector('.rc-notification-notice-description')).toHaveClass( + 'global-description', + 'notice-description', + ); + expect(document.querySelector('.rc-notification-notice-description')).toHaveStyle({ + content: 'global-description', + marginRight: '2px', + }); + expect(document.querySelector('.rc-notification-notice-actions')).toHaveClass( + 'global-actions', + 'notice-actions', + ); + expect(document.querySelector('.rc-notification-notice-actions')).toHaveStyle({ + content: 'global-actions', + marginBottom: '3px', + }); + expect(document.querySelector('.actions')).toBeFalsy(); + expect(document.querySelector('.rc-notification-notice-icon')).toHaveClass( + 'global-icon', + 'notice-icon', + ); + expect(document.querySelector('.rc-notification-notice-icon')).toHaveStyle({ + content: 'global-icon', + marginLeft: '4px', + }); + }); + it('should className work', () => { const { instance } = renderDemo({ className: (placement) => `bamboo-${placement}`,