Skip to content

Commit e890844

Browse files
committed
support semantic notification slots
1 parent bd9b38f commit e890844

3 files changed

Lines changed: 169 additions & 5 deletions

File tree

src/Notification.tsx

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ export interface NotificationClassNames {
1111
root?: string;
1212
icon?: string;
1313
section?: string;
14+
title?: string;
15+
description?: string;
16+
actions?: string;
1417
close?: string;
1518
progress?: string;
1619
}
@@ -20,6 +23,9 @@ export interface NotificationStyles {
2023
root?: React.CSSProperties;
2124
icon?: React.CSSProperties;
2225
section?: React.CSSProperties;
26+
title?: React.CSSProperties;
27+
description?: React.CSSProperties;
28+
actions?: React.CSSProperties;
2329
close?: React.CSSProperties;
2430
progress?: React.CSSProperties;
2531
}
@@ -42,6 +48,7 @@ export interface NotificationProps {
4248
description?: React.ReactNode;
4349
icon?: React.ReactNode;
4450
actions?: React.ReactNode;
51+
role?: string;
4552
closable?: ClosableType;
4653
offset?: number;
4754
notificationIndex?: number;
@@ -78,6 +85,7 @@ const Notification = React.forwardRef<HTMLDivElement, NotificationProps>((props,
7885
description,
7986
icon,
8087
actions,
88+
role,
8189
closable,
8290
offset,
8391
notificationIndex,
@@ -167,12 +175,19 @@ const Notification = React.forwardRef<HTMLDivElement, NotificationProps>((props,
167175
// ======================== Content =========================
168176
const titleNode =
169177
title !== undefined && title !== null ? (
170-
<div className={`${noticePrefixCls}-title`}>{title}</div>
178+
<div className={clsx(`${noticePrefixCls}-title`, classNames?.title)} style={styles?.title}>
179+
{title}
180+
</div>
171181
) : null;
172182

173183
const descNode =
174184
description !== undefined && description !== null ? (
175-
<div className={`${noticePrefixCls}-description`}>{description}</div>
185+
<div
186+
className={clsx(`${noticePrefixCls}-description`, classNames?.description)}
187+
style={styles?.description}
188+
>
189+
{description}
190+
</div>
176191
) : null;
177192

178193
const hasTitle = titleNode !== null;
@@ -204,6 +219,15 @@ const Notification = React.forwardRef<HTMLDivElement, NotificationProps>((props,
204219
);
205220
}
206221

222+
const actionsNode = actions ? (
223+
<div
224+
className={clsx(`${noticePrefixCls}-actions`, classNames?.actions)}
225+
style={styles?.actions}
226+
>
227+
{actions}
228+
</div>
229+
) : null;
230+
207231
// ========================= Render =========================
208232
const mergedStyle: React.CSSProperties & {
209233
'--notification-index'?: number;
@@ -218,10 +242,13 @@ const Notification = React.forwardRef<HTMLDivElement, NotificationProps>((props,
218242
mergedStyle['--notification-y'] = `${mergedOffset}px`;
219243
}
220244

245+
const mergedRole = role ?? rootProps?.role ?? 'alert';
246+
221247
return (
222248
<div
223249
{...rootProps}
224250
ref={ref}
251+
role={mergedRole}
225252
data-notification-index={mergedNotificationIndex}
226253
// Styles
227254
className={clsx(noticePrefixCls, className, classNames?.root, {
@@ -235,6 +262,7 @@ const Notification = React.forwardRef<HTMLDivElement, NotificationProps>((props,
235262
onMouseLeave={onInternalMouseLeave}
236263
>
237264
{contentNode}
265+
{actionsNode}
238266

239267
{mergedClosable && (
240268
<button
@@ -248,8 +276,6 @@ const Notification = React.forwardRef<HTMLDivElement, NotificationProps>((props,
248276
</button>
249277
)}
250278

251-
{actions && <div className="actions">{actions}</div>}
252-
253279
{showProgress && typeof duration === 'number' && duration > 0 && (
254280
<Progress
255281
className={clsx(`${noticePrefixCls}-progress`, classNames?.progress)}

src/NotificationList/index.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,17 @@ export interface NotificationListProps {
4747
onAllRemoved?: (placement: Placement) => void;
4848
}
4949

50-
const noticeSlotKeys = ['wrapper', 'root', 'icon', 'section', 'close', 'progress'] as const;
50+
const noticeSlotKeys: (keyof NoticeClassNames)[] = [
51+
'wrapper',
52+
'root',
53+
'icon',
54+
'section',
55+
'title',
56+
'description',
57+
'actions',
58+
'close',
59+
'progress',
60+
];
5161

5262
function fillClassNames(
5363
classNamesList: (NotificationClassNames | undefined)[],

tests/index.test.tsx

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,46 @@ describe('Notification.Basic', () => {
584584
expect(notice[0].getAttribute('role')).toBe('alert');
585585
});
586586

587+
it('sets role attribute from notification config', () => {
588+
const { instance } = renderDemo();
589+
590+
act(() => {
591+
instance.open({
592+
title: 'bamboo',
593+
description: <span className="test-role-description">simple show</span>,
594+
icon: <span className="test-role-icon" />,
595+
actions: <button type="button">light</button>,
596+
role: 'status',
597+
closable: true,
598+
showProgress: true,
599+
duration: 3,
600+
});
601+
});
602+
603+
const notice = document.querySelector('.rc-notification-notice');
604+
605+
expect(notice).toHaveAttribute('role', 'status');
606+
expect(notice.querySelector('.rc-notification-notice-content')).toBeFalsy();
607+
expect(notice.querySelector('.rc-notification-notice-title')).toBeTruthy();
608+
expect(notice.querySelector('.rc-notification-notice-description')).toBeTruthy();
609+
expect(notice.querySelector('.rc-notification-notice-icon')).toBeTruthy();
610+
expect(notice.querySelector('.rc-notification-notice-actions')).toBeTruthy();
611+
expect(notice.querySelector('.rc-notification-notice-close')).toBeTruthy();
612+
expect(notice.querySelector('.rc-notification-notice-progress')).toBeTruthy();
613+
});
614+
615+
it('sets default role attribute', () => {
616+
const { instance } = renderDemo();
617+
618+
act(() => {
619+
instance.open({
620+
description: <span className="test-default-role">simple show</span>,
621+
});
622+
});
623+
624+
expect(document.querySelector('.rc-notification-notice')).toHaveAttribute('role', 'alert');
625+
});
626+
587627
it('should style work', () => {
588628
const { instance } = renderDemo({
589629
style: () => ({
@@ -694,6 +734,94 @@ describe('Notification.Basic', () => {
694734
expect(document.querySelector('.bamboo')).toHaveClass('bamboo');
695735
});
696736

737+
it('should support semantic content styles and classNames', () => {
738+
const { instance } = renderDemo({
739+
classNames: {
740+
title: 'global-title',
741+
description: 'global-description',
742+
actions: 'global-actions',
743+
icon: 'global-icon',
744+
},
745+
styles: {
746+
title: {
747+
content: 'global-title',
748+
},
749+
description: {
750+
content: 'global-description',
751+
},
752+
actions: {
753+
content: 'global-actions',
754+
},
755+
icon: {
756+
content: 'global-icon',
757+
},
758+
},
759+
});
760+
761+
act(() => {
762+
instance.open({
763+
title: 'bamboo',
764+
description: 'little',
765+
icon: <span />,
766+
actions: <button type="button">light</button>,
767+
classNames: {
768+
title: 'notice-title',
769+
description: 'notice-description',
770+
actions: 'notice-actions',
771+
icon: 'notice-icon',
772+
},
773+
styles: {
774+
title: {
775+
marginTop: 1,
776+
},
777+
description: {
778+
marginRight: 2,
779+
},
780+
actions: {
781+
marginBottom: 3,
782+
},
783+
icon: {
784+
marginLeft: 4,
785+
},
786+
},
787+
});
788+
});
789+
790+
expect(document.querySelector('.rc-notification-notice-title')).toHaveClass(
791+
'global-title',
792+
'notice-title',
793+
);
794+
expect(document.querySelector('.rc-notification-notice-title')).toHaveStyle({
795+
content: 'global-title',
796+
marginTop: '1px',
797+
});
798+
expect(document.querySelector('.rc-notification-notice-description')).toHaveClass(
799+
'global-description',
800+
'notice-description',
801+
);
802+
expect(document.querySelector('.rc-notification-notice-description')).toHaveStyle({
803+
content: 'global-description',
804+
marginRight: '2px',
805+
});
806+
expect(document.querySelector('.rc-notification-notice-actions')).toHaveClass(
807+
'global-actions',
808+
'notice-actions',
809+
);
810+
expect(document.querySelector('.rc-notification-notice-actions')).toHaveStyle({
811+
content: 'global-actions',
812+
marginBottom: '3px',
813+
});
814+
expect(document.querySelector('.actions')).toBeFalsy();
815+
expect(document.querySelector('.rc-notification-notice-icon')).toHaveClass(
816+
'global-icon',
817+
'notice-icon',
818+
);
819+
expect(document.querySelector('.rc-notification-notice-icon')).toHaveStyle({
820+
content: 'global-icon',
821+
marginLeft: '4px',
822+
});
823+
});
824+
697825
it('should className work', () => {
698826
const { instance } = renderDemo({
699827
className: (placement) => `bamboo-${placement}`,

0 commit comments

Comments
 (0)