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
34 changes: 30 additions & 4 deletions src/Notification.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ export interface NotificationClassNames {
root?: string;
icon?: string;
section?: string;
title?: string;
description?: string;
actions?: string;
close?: string;
progress?: string;
}
Expand All @@ -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;
}
Expand All @@ -42,6 +48,7 @@ export interface NotificationProps {
description?: React.ReactNode;
icon?: React.ReactNode;
actions?: React.ReactNode;
role?: string;
closable?: ClosableType;
offset?: number;
notificationIndex?: number;
Expand Down Expand Up @@ -78,6 +85,7 @@ const Notification = React.forwardRef<HTMLDivElement, NotificationProps>((props,
description,
icon,
actions,
role,
closable,
offset,
notificationIndex,
Expand Down Expand Up @@ -167,12 +175,19 @@ const Notification = React.forwardRef<HTMLDivElement, NotificationProps>((props,
// ======================== Content =========================
const titleNode =
title !== undefined && title !== null ? (
<div className={`${noticePrefixCls}-title`}>{title}</div>
<div className={clsx(`${noticePrefixCls}-title`, classNames?.title)} style={styles?.title}>
{title}
</div>
) : null;

const descNode =
description !== undefined && description !== null ? (
<div className={`${noticePrefixCls}-description`}>{description}</div>
<div
className={clsx(`${noticePrefixCls}-description`, classNames?.description)}
style={styles?.description}
>
{description}
</div>
) : null;

const hasTitle = titleNode !== null;
Expand Down Expand Up @@ -204,6 +219,15 @@ const Notification = React.forwardRef<HTMLDivElement, NotificationProps>((props,
);
}

const actionsNode = actions ? (
<div
className={clsx(`${noticePrefixCls}-actions`, classNames?.actions)}
style={styles?.actions}
>
{actions}
</div>
) : null;

// ========================= Render =========================
const mergedStyle: React.CSSProperties & {
'--notification-index'?: number;
Expand All @@ -218,10 +242,13 @@ const Notification = React.forwardRef<HTMLDivElement, NotificationProps>((props,
mergedStyle['--notification-y'] = `${mergedOffset}px`;
}

const mergedRole = role ?? rootProps?.role ?? 'alert';

return (
<div
{...rootProps}
ref={ref}
role={mergedRole}
data-notification-index={mergedNotificationIndex}
// Styles
className={clsx(noticePrefixCls, className, classNames?.root, {
Expand All @@ -235,6 +262,7 @@ const Notification = React.forwardRef<HTMLDivElement, NotificationProps>((props,
onMouseLeave={onInternalMouseLeave}
>
{contentNode}
{actionsNode}

{mergedClosable && (
<button
Expand All @@ -248,8 +276,6 @@ const Notification = React.forwardRef<HTMLDivElement, NotificationProps>((props,
</button>
)}

{actions && <div className="actions">{actions}</div>}

{showProgress && typeof duration === 'number' && duration > 0 && (
<Progress
className={clsx(`${noticePrefixCls}-progress`, classNames?.progress)}
Expand Down
12 changes: 11 additions & 1 deletion src/NotificationList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,17 @@ export interface NotificationListProps {
onAllRemoved?: (placement: Placement) => 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)[],
Expand Down
128 changes: 128 additions & 0 deletions tests/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: <span className="test-role-description">simple show</span>,
icon: <span className="test-role-icon" />,
actions: <button type="button">light</button>,
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: <span className="test-default-role">simple show</span>,
});
});

expect(document.querySelector('.rc-notification-notice')).toHaveAttribute('role', 'alert');
});

it('should style work', () => {
const { instance } = renderDemo({
style: () => ({
Expand Down Expand Up @@ -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: <span />,
actions: <button type="button">light</button>,
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}`,
Expand Down
Loading