Skip to content

Commit

Permalink
feat: support stack
Browse files Browse the repository at this point in the history
  • Loading branch information
MadCcc committed Aug 28, 2023
1 parent 5ea5394 commit b0dff7e
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 8 deletions.
69 changes: 67 additions & 2 deletions assets/index.less
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
display: flex;
max-height: 100vh;
padding: 10px;
overflow-y: auto;
align-items: flex-end;

// Position
Expand All @@ -27,14 +26,14 @@
position: relative;
display: block;
box-sizing: border-box;
width: auto;
margin: 12px 0;
line-height: 1.5;
background: #fff;
border: 1px solid #999;
border: 0px solid rgba(0, 0, 0, 0);
border-radius: 3px 3px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
width: 300px;

// Content
&-content {
Expand Down Expand Up @@ -77,6 +76,11 @@
transition: all 0.3s;
}

&-fade-appear-prepare {
pointer-events: none;
opacity: 0 !important;
}

&-fade-appear-start {
transform: translateX(100%);
opacity: 0;
Expand Down Expand Up @@ -133,4 +137,65 @@
// opacity: 0;
// }
// }

// ========================= Stack =========================
&-stack {
& > .@{notificationPrefixCls}-notice {
transition: all 0.3s;
position: absolute;
top: 12px;
opacity: 1;

&:not(:nth-last-child(-n + 3)) {
opacity: 0;
right: 34px;
width: 252px;
overflow: hidden;
color: transparent;
pointer-events: none;
}

&:nth-last-child(1) {
right: 10px;
}

&:nth-last-child(2) {
right: 18px;
width: 284px;
color: transparent;
overflow: hidden;
}

&:nth-last-child(3) {
right: 26px;
width: 268px;
color: transparent;
overflow: hidden;
}
}

&&-expanded {
& > .@{notificationPrefixCls}-notice {
&:not(:nth-last-child(-n + 1)) {
opacity: 1;
width: 300px;
right: 10px;
overflow: unset;
color: inherit;
pointer-events: auto;
}

&::after {
content: "";
position: absolute;
left: 0;
right: 0;
top: calc(100% + 1px);
width: 100%;
height: 16px;
background: transparent
}
}
}
}
}
22 changes: 20 additions & 2 deletions docs/examples/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useNotification } from '../../src';
import motion from './motion';

export default () => {
const [notice, contextHolder] = useNotification({ motion, closable: true });
const [notice, contextHolder] = useNotification({ motion, closable: true, stack: true });

return (
<>
Expand All @@ -26,7 +26,25 @@ export default () => {
<button
onClick={() => {
notice.open({
content: `${new Date().toISOString()}`,
content: `${Array(Math.round(Math.random() * 5) + 1)
.fill(1)
.map(() => new Date().toISOString())
.join('\n')}`,
duration: null,
});
}}
>
Not Auto Close
</button>

{/* Not Close */}
<button
onClick={() => {
notice.open({
content: `${Array(5)
.fill(1)
.map(() => new Date().toISOString())
.join('\n')}`,
duration: null,
});
}}
Expand Down
6 changes: 4 additions & 2 deletions src/Notice.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,13 @@ const Notify = React.forwardRef<HTMLDivElement, NoticeProps & { times?: number }
[`${noticePrefixCls}-closable`]: closable,
})}
style={style}
onMouseEnter={() => {
onMouseEnter={(e) => {
setHovering(true);
divProps?.onMouseEnter?.(e);
}}
onMouseLeave={() => {
onMouseLeave={(e) => {
setHovering(false);
divProps?.onMouseLeave?.(e);
}}
onClick={onClick}
>
Expand Down
53 changes: 51 additions & 2 deletions src/Notifications.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { CSSMotionProps } from 'rc-motion';
import classNames from 'classnames';
import Notice from './Notice';
import type { NoticeConfig } from './Notice';
import { CSSProperties, useEffect, useRef, useState } from 'react';

export interface OpenConfig extends NoticeConfig {
key: React.Key;
Expand All @@ -21,6 +22,11 @@ export interface NotificationsProps {
className?: (placement: Placement) => string;
style?: (placement: Placement) => React.CSSProperties;
onAllRemoved?: VoidFunction;
stack?:
| boolean
| {
threshold?: number;
};
}

export type Placement = 'top' | 'topLeft' | 'topRight' | 'bottom' | 'bottomLeft' | 'bottomRight';
Expand All @@ -45,6 +51,7 @@ const Notifications = React.forwardRef<NotificationsRef, NotificationsProps>((pr
className,
style,
onAllRemoved,
stack,
} = props;
const [configList, setConfigList] = React.useState<OpenConfig[]>([]);

Expand Down Expand Up @@ -140,6 +147,9 @@ const Notifications = React.forwardRef<NotificationsRef, NotificationsProps>((pr
}
}, [placements]);

const listRef = useRef<HTMLDivElement[]>([]);
const [latestNotice, setLatestNotice] = useState<HTMLDivElement>(null);
const [hoverCount, setHoverCount] = useState(0);
// ======================== Render ========================
if (!container) {
return null;
Expand All @@ -156,38 +166,77 @@ const Notifications = React.forwardRef<NotificationsRef, NotificationsProps>((pr
key: config.key,
}));

const expanded =
!!stack &&
(hoverCount > 0 ||
keys.length <=
(typeof stack === 'object' && 'threshold' in stack ? stack.threshold : 3));

const placementMotion = typeof motion === 'function' ? motion(placement) : motion;

return (
<CSSMotionList
key={placement}
className={classNames(prefixCls, `${prefixCls}-${placement}`, className?.(placement))}
className={classNames(prefixCls, `${prefixCls}-${placement}`, className?.(placement), {
[`${prefixCls}-stack`]: !!stack,
[`${prefixCls}-stack-expanded`]: expanded,
})}
style={style?.(placement)}
keys={keys}
motionAppear
{...placementMotion}
onAllRemoved={() => {
onAllNoticeRemoved(placement);
}}
onAppearPrepare={async (element) => {
if (element.parentNode.lastElementChild === element) {
setLatestNotice(element as HTMLDivElement);
}
}}
>
{({ config, className: motionClassName, style: motionStyle }, nodeRef) => {
const { key, times } = config as InnerOpenConfig;
const { className: configClassName, style: configStyle } = config as NoticeConfig;

const index = keys.length - 1 - keys.findIndex((item) => item.key === key);
const stackStyle: CSSProperties = {};
if (stack) {
if (index > 0) {
stackStyle.height = expanded ? '' : latestNotice.offsetHeight;
stackStyle.transform = `translateY(${
index * 8 +
(expanded
? listRef.current.reduce(
(acc, item, refIndex) => acc + (refIndex < index ? item.offsetHeight : 0),
0,
)
: 0)
}px)`;
}
}

return (
<Notice
{...config}
ref={nodeRef}
ref={(node) => {
nodeRef(node);
listRef.current[index] = node;
}}
prefixCls={prefixCls}
className={classNames(motionClassName, configClassName)}
style={{
...motionStyle,
...configStyle,
...stackStyle,
}}
times={times}
key={key}
eventKey={key}
onNoticeClose={onNoticeClose}
props={{
onMouseEnter: () => setHoverCount((c) => c + 1),
onMouseLeave: () => setHoverCount((c) => c - 1),
}}
/>
);
}}
Expand Down
7 changes: 7 additions & 0 deletions src/useNotification.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ export interface NotificationConfig {
style?: (placement: Placement) => React.CSSProperties;
/** @private Trigger when all the notification closed. */
onAllRemoved?: VoidFunction;
stack?:
| boolean
| {
threshold?: number;
};
}

export interface NotificationAPI {
Expand Down Expand Up @@ -77,6 +82,7 @@ export default function useNotification(
className,
style,
onAllRemoved,
stack,
...shareConfig
} = rootConfig;

Expand All @@ -92,6 +98,7 @@ export default function useNotification(
className={className}
style={style}
onAllRemoved={onAllRemoved}
stack={stack}
/>
);

Expand Down

0 comments on commit b0dff7e

Please sign in to comment.