@rainbow-oh/yee-c 0.8.2 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/dist/cjs/Form/form-store.js +3 -0
  2. package/dist/cjs/Input/style/index.less +1 -0
  3. package/dist/cjs/Select/interface.d.ts +1 -1
  4. package/dist/cjs/Selector/style/index.less +1 -0
  5. package/dist/cjs/Table/icon/arrow-icon.js +1 -1
  6. package/dist/cjs/Table/theader/cell.js +6 -3
  7. package/dist/cjs/TextArea/style/index.less +1 -0
  8. package/dist/cjs/VerificationCode/style/index.less +1 -0
  9. package/dist/cjs/hooks/useCountdown.d.ts +2 -1
  10. package/dist/cjs/hooks/useCountdown.js +46 -23
  11. package/dist/cjs/locale/interface.d.ts +3 -0
  12. package/dist/cjs/locale/lang/en_US.js +4 -1
  13. package/dist/cjs/locale/lang/ja_JP.js +4 -1
  14. package/dist/cjs/locale/lang/zh_CN.js +4 -1
  15. package/dist/cjs/locale/lang/zh_TW.js +4 -1
  16. package/dist/cjs/message/demo/base.js +1 -1
  17. package/dist/cjs/message/index.d.ts +1 -1
  18. package/dist/cjs/message/interface.d.ts +16 -7
  19. package/dist/cjs/message/message-provider.d.ts +2 -1
  20. package/dist/cjs/message/message-provider.js +1 -1
  21. package/dist/cjs/message/message.js +14 -9
  22. package/dist/cjs/message/style/index.less +3 -1
  23. package/dist/cjs/message/use-message.d.ts +3 -12
  24. package/dist/cjs/message/use-message.js +103 -45
  25. package/dist/cjs/message/wrapper.d.ts +21 -27
  26. package/dist/cjs/message/wrapper.js +49 -19
  27. package/dist/cjs/notice/index.d.ts +1 -1
  28. package/dist/cjs/notice/interface.d.ts +17 -7
  29. package/dist/cjs/notice/notice-list.d.ts +3 -3
  30. package/dist/cjs/notice/notice-provider.d.ts +2 -1
  31. package/dist/cjs/notice/notice-provider.js +1 -1
  32. package/dist/cjs/notice/notice.js +63 -20
  33. package/dist/cjs/notice/style/index.less +13 -1
  34. package/dist/cjs/notice/use-notice.d.ts +2 -11
  35. package/dist/cjs/notice/use-notice.js +156 -71
  36. package/dist/cjs/notice/wrapper.d.ts +19 -15
  37. package/dist/cjs/notice/wrapper.js +122 -52
  38. package/dist/cjs/style/size.css +3 -3
  39. package/dist/cjs/style/themes.css +1 -1
  40. package/dist/cjs/style/variables.css +4 -4
  41. package/dist/cjs/version.d.ts +1 -1
  42. package/dist/cjs/version.js +1 -1
  43. package/dist/esm/Form/form-store.js +25 -17
  44. package/dist/esm/Input/style/index.less +1 -0
  45. package/dist/esm/Select/interface.d.ts +1 -1
  46. package/dist/esm/Selector/style/index.less +1 -0
  47. package/dist/esm/Table/icon/arrow-icon.js +1 -1
  48. package/dist/esm/Table/theader/cell.js +7 -3
  49. package/dist/esm/TextArea/style/index.less +1 -0
  50. package/dist/esm/VerificationCode/style/index.less +1 -0
  51. package/dist/esm/hooks/useCountdown.d.ts +2 -1
  52. package/dist/esm/hooks/useCountdown.js +44 -14
  53. package/dist/esm/locale/interface.d.ts +3 -0
  54. package/dist/esm/locale/lang/en_US.js +4 -1
  55. package/dist/esm/locale/lang/ja_JP.js +4 -1
  56. package/dist/esm/locale/lang/zh_CN.js +4 -1
  57. package/dist/esm/locale/lang/zh_TW.js +4 -1
  58. package/dist/esm/message/demo/base.js +4 -1
  59. package/dist/esm/message/index.d.ts +1 -1
  60. package/dist/esm/message/interface.d.ts +16 -7
  61. package/dist/esm/message/message-provider.d.ts +2 -1
  62. package/dist/esm/message/message-provider.js +1 -1
  63. package/dist/esm/message/message.js +22 -9
  64. package/dist/esm/message/style/index.less +3 -1
  65. package/dist/esm/message/use-message.d.ts +3 -12
  66. package/dist/esm/message/use-message.js +129 -79
  67. package/dist/esm/message/wrapper.d.ts +21 -27
  68. package/dist/esm/message/wrapper.js +60 -18
  69. package/dist/esm/notice/index.d.ts +1 -1
  70. package/dist/esm/notice/interface.d.ts +17 -7
  71. package/dist/esm/notice/notice-list.d.ts +3 -3
  72. package/dist/esm/notice/notice-provider.d.ts +2 -1
  73. package/dist/esm/notice/notice-provider.js +1 -1
  74. package/dist/esm/notice/notice.js +73 -21
  75. package/dist/esm/notice/style/index.less +13 -1
  76. package/dist/esm/notice/use-notice.d.ts +2 -11
  77. package/dist/esm/notice/use-notice.js +180 -100
  78. package/dist/esm/notice/wrapper.d.ts +19 -15
  79. package/dist/esm/notice/wrapper.js +144 -62
  80. package/dist/esm/style/size.css +3 -3
  81. package/dist/esm/style/themes.css +1 -1
  82. package/dist/esm/style/variables.css +4 -4
  83. package/dist/esm/version.d.ts +1 -1
  84. package/dist/esm/version.js +1 -1
  85. package/dist/umd/yee-c.min.css +2 -2
  86. package/dist/umd/yee-c.min.js +1 -1
  87. package/package.json +1 -1
  88. package/src/Form/form-store.ts +5 -0
  89. package/src/Input/style/index.less +1 -0
  90. package/src/Select/interface.ts +1 -1
  91. package/src/Selector/style/index.less +1 -0
  92. package/src/Table/icon/arrow-icon.tsx +1 -1
  93. package/src/Table/theader/cell.tsx +6 -3
  94. package/src/TextArea/style/index.less +1 -0
  95. package/src/VerificationCode/style/index.less +1 -0
  96. package/src/hooks/useCountdown.ts +52 -21
  97. package/src/locale/interface.ts +3 -0
  98. package/src/locale/lang/en_US.ts +3 -0
  99. package/src/locale/lang/ja_JP.ts +3 -0
  100. package/src/locale/lang/zh_CN.ts +3 -0
  101. package/src/locale/lang/zh_TW.ts +3 -0
  102. package/src/message/demo/base.tsx +1 -1
  103. package/src/message/index.ts +1 -1
  104. package/src/message/interface.ts +22 -7
  105. package/src/message/message-provider.tsx +3 -2
  106. package/src/message/message.test.tsx +283 -0
  107. package/src/message/message.tsx +14 -9
  108. package/src/message/style/index.less +3 -1
  109. package/src/message/use-message.tsx +144 -67
  110. package/src/message/wrapper.tsx +62 -20
  111. package/src/notice/index.en-US.md +76 -75
  112. package/src/notice/index.ts +6 -1
  113. package/src/notice/index.zh-CN.md +3 -2
  114. package/src/notice/interface.ts +31 -14
  115. package/src/notice/notice-list.tsx +3 -9
  116. package/src/notice/notice-provider.tsx +3 -2
  117. package/src/notice/notice.test.tsx +450 -0
  118. package/src/notice/notice.tsx +69 -21
  119. package/src/notice/style/index.less +13 -1
  120. package/src/notice/use-notice.tsx +210 -94
  121. package/src/notice/wrapper.tsx +159 -71
  122. package/src/style/size.css +3 -3
  123. package/src/version.ts +1 -1
@@ -4,18 +4,21 @@ import React from 'react';
4
4
  import Progress from '../Progress';
5
5
  import useCountdown from '../hooks/useCountdown';
6
6
  import { NoticeProps } from './interface';
7
+ import Button from '../Button';
7
8
 
8
9
  const icons = {
9
- info: <Info size={20} strokeWidth={2} />,
10
- success: <CircleCheck size={20} strokeWidth={2} />,
11
- warning: <CircleAlert size={20} strokeWidth={2} />,
12
- error: <CircleX size={20} strokeWidth={2} />,
10
+ info: <Info size={22} fill='currentColor' stroke="#FFF" strokeWidth={2} />,
11
+ success: <CircleCheck size={22} fill='currentColor' stroke="#FFF" strokeWidth={2} />,
12
+ warning: <CircleAlert size={22} fill='currentColor' stroke="#FFF" strokeWidth={2} />,
13
+ error: <CircleX size={22} fill='currentColor' stroke="#FFF" strokeWidth={2} />,
13
14
  };
14
15
 
15
16
  const Notice: React.FC<NoticeProps & { id: string | number }> = (props) => {
16
17
  const {
17
18
  prefixCls = 'yee-notice',
18
19
  id: key,
20
+ timerGeneration,
21
+ placement,
19
22
  status,
20
23
  title,
21
24
  content,
@@ -33,18 +36,51 @@ const Notice: React.FC<NoticeProps & { id: string | number }> = (props) => {
33
36
  ...rest
34
37
  } = props;
35
38
 
36
- const { remaining, onPause, onResume } = useCountdown({
39
+ // The state owner dispatches onClose after removing the notice.
40
+ void onClose;
41
+
42
+ const isHoveredRef = React.useRef(false);
43
+ const { remaining, isPaused, onPause, onResume } = useCountdown({
37
44
  duration,
45
+ resetKey: timerGeneration,
38
46
  onComplete: () => {
39
- onDestroy?.(key);
40
- onClose?.();
47
+ onDestroy?.(key, timerGeneration);
41
48
  },
42
49
  });
43
50
 
44
- const handleClose = (e: React.MouseEvent) => {
45
- e.stopPropagation();
46
- onDestroy?.(key);
47
- onClose?.();
51
+ React.useEffect(() => {
52
+ if (pauseOnHover && isHoveredRef.current && !isPaused) {
53
+ onPause();
54
+ } else if ((!pauseOnHover || !isHoveredRef.current) && isPaused) {
55
+ onResume();
56
+ }
57
+ }, [isPaused, onPause, onResume, pauseOnHover]);
58
+
59
+ const handleMouseEnter = () => {
60
+ isHoveredRef.current = true;
61
+ if (pauseOnHover) {
62
+ onPause();
63
+ }
64
+ };
65
+
66
+ const handleMouseLeave = () => {
67
+ isHoveredRef.current = false;
68
+ if (isPaused) {
69
+ onResume();
70
+ }
71
+ };
72
+
73
+ const handleClose = (event: React.MouseEvent) => {
74
+ event.stopPropagation();
75
+ onDestroy?.(key, timerGeneration);
76
+ };
77
+
78
+ const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
79
+ if (!onClick || (event.key !== 'Enter' && event.key !== ' ')) {
80
+ return;
81
+ }
82
+ event.preventDefault();
83
+ onClick();
48
84
  };
49
85
 
50
86
  const renderIcon = () => {
@@ -53,7 +89,7 @@ const Notice: React.FC<NoticeProps & { id: string | number }> = (props) => {
53
89
  }
54
90
 
55
91
  return (
56
- <span className={`${prefixCls}-icon`}>
92
+ <span aria-hidden="true" className={`${prefixCls}-icon`}>
57
93
  {icon ? icon : status ? icons[status] : null}
58
94
  </span>
59
95
  );
@@ -65,30 +101,42 @@ const Notice: React.FC<NoticeProps & { id: string | number }> = (props) => {
65
101
  }
66
102
 
67
103
  return (
68
- <button
104
+ <Button
105
+ variant='text'
106
+ size="small"
69
107
  className={`${prefixCls}-close`}
70
108
  onClick={handleClose}
71
- type="button"
72
- aria-label="Close"
73
- >
74
- <X size={14} strokeWidth={2} />
75
- </button>
109
+ aria-label="Close notice"
110
+ icon={<X aria-hidden="true" size={14} strokeWidth={2} />}
111
+ />
76
112
  );
77
113
  };
78
114
 
115
+ const isAssertive = status === 'error' || status === 'warning';
116
+
79
117
  return (
80
118
  <div
81
119
  {...rest}
120
+ aria-atomic="true"
121
+ aria-live={isAssertive ? 'assertive' : 'polite'}
122
+ role={onClick ? 'button' : isAssertive ? 'alert' : 'status'}
123
+ tabIndex={onClick ? 0 : undefined}
124
+ data-placement={placement}
82
125
  data-testid={dataTestId}
83
126
  className={clsx(
84
127
  prefixCls,
85
- { [`${prefixCls}-${status}`]: status },
128
+ {
129
+ [`${prefixCls}-${status}`]: status,
130
+ [`${prefixCls}-clickable`]: onClick,
131
+ [`${prefixCls}-closable`]: closable,
132
+ },
86
133
  className,
87
134
  )}
88
135
  style={style}
89
136
  onClick={onClick}
90
- onMouseEnter={pauseOnHover ? onPause : undefined}
91
- onMouseLeave={pauseOnHover ? onResume : undefined}
137
+ onKeyDown={handleKeyDown}
138
+ onMouseEnter={handleMouseEnter}
139
+ onMouseLeave={handleMouseLeave}
92
140
  >
93
141
  <div className={`${prefixCls}-content-wrapper`}>
94
142
  {renderIcon()}
@@ -12,7 +12,7 @@
12
12
  background-color: var(--yee-notice-bg-color);
13
13
  border-radius: var(--yee-notice-border-radius);
14
14
  box-shadow: var(--yee-notice-box-shadow);
15
- cursor: pointer;
15
+ cursor: default;
16
16
  transition: all 0.3s ease;
17
17
  border: var(--yee-notice-border-width) solid transparent;
18
18
  overflow: hidden;
@@ -21,6 +21,18 @@
21
21
  box-shadow: var(--yee-notice-box-shadow-hover);
22
22
  }
23
23
 
24
+ &-clickable {
25
+ cursor: pointer;
26
+ }
27
+
28
+ &-closable {
29
+ @{pls}-content-wrapper {
30
+ padding-right: calc(
31
+ var(--yee-notice-close-size) + var(--yee-notice-close-right)
32
+ );
33
+ }
34
+ }
35
+
24
36
  &-content-wrapper {
25
37
  display: flex;
26
38
  align-items: flex-start;
@@ -1,117 +1,233 @@
1
- import React, { useCallback, useRef, useState } from 'react';
1
+ import React, {
2
+ useCallback,
3
+ useEffect,
4
+ useMemo,
5
+ useRef,
6
+ useState,
7
+ } from 'react';
2
8
  import { createPortal } from 'react-dom';
3
- import { NoticeConfig, NoticeType, WrapperedNoticeConfig } from './interface';
9
+ import {
10
+ NoticeApi,
11
+ NoticeType,
12
+ PlacementType,
13
+ WrapperedNoticeConfig,
14
+ } from './interface';
4
15
  import NoticeList from './notice-list';
5
16
 
6
- type PlacementType =
7
- | 'topLeft'
8
- | 'topRight'
9
- | 'bottomLeft'
10
- | 'bottomRight'
11
- | 'top'
12
- | 'bottom';
17
+ const invokeOnClose = (notices: NoticeType[]) => {
18
+ const errors: unknown[] = [];
19
+
20
+ notices.forEach((notice) => {
21
+ try {
22
+ notice.onClose?.();
23
+ } catch (error) {
24
+ errors.push(error);
25
+ }
26
+ });
27
+
28
+ errors.forEach((error) => {
29
+ const reportError = (
30
+ globalThis as typeof globalThis & {
31
+ reportError?: (reportedError: unknown) => void;
32
+ }
33
+ ).reportError;
34
+ if (reportError) {
35
+ reportError(error);
36
+ } else {
37
+ console.error(error);
38
+ }
39
+ });
40
+ };
13
41
 
14
42
  const useNotice = () => {
15
43
  const [noticesByPlacement, setNoticesByPlacement] = useState<
16
44
  Map<PlacementType, NoticeType[]>
17
45
  >(new Map());
18
- const count = useRef(0);
19
-
20
- const add = useCallback((props: WrapperedNoticeConfig) => {
21
- const placement = props.placement || 'topRight';
22
- const id = count.current++;
46
+ const [portalContainer, setPortalContainer] = useState<HTMLElement | null>(
47
+ null,
48
+ );
49
+ const noticesRef = useRef(new Map<PlacementType, NoticeType[]>());
50
+ const generationByKey = useRef(new Map<string | number, number>());
51
+ const generation = useRef(0);
52
+ const timerGeneration = useRef(0);
53
+ const autoKey = useRef(0);
23
54
 
24
- setNoticesByPlacement((prev) => {
25
- const newMap = new Map(prev);
26
- const notices = newMap.get(placement) || [];
27
- newMap.set(placement, [...notices, { key: id, ...props }]);
28
- return newMap;
29
- });
55
+ useEffect(() => {
56
+ setPortalContainer(document.body);
30
57
  }, []);
31
58
 
32
- const onDestroy = (key: string | number, placement: PlacementType) => {
33
- setNoticesByPlacement((prev) => {
34
- const newMap = new Map(prev);
35
- const notices = newMap.get(placement) || [];
36
- const filtered = notices.filter((notice) => notice.key !== key);
59
+ const updateNotices = useCallback(
60
+ (
61
+ updater: (
62
+ current: Map<PlacementType, NoticeType[]>,
63
+ ) => Map<PlacementType, NoticeType[]>,
64
+ ) => {
65
+ const nextNotices = updater(noticesRef.current);
66
+ noticesRef.current = nextNotices;
67
+ setNoticesByPlacement(nextNotices);
68
+ },
69
+ [],
70
+ );
71
+
72
+ const onDestroy = useCallback(
73
+ (key: string | number, expectedTimerGeneration?: number) => {
74
+ let target: NoticeType | undefined;
75
+ noticesRef.current.forEach((items) => {
76
+ target ??= items.find((item) => item.key === key);
77
+ });
37
78
 
38
- if (filtered.length === 0) {
39
- newMap.delete(placement);
40
- } else {
41
- newMap.set(placement, filtered);
79
+ if (
80
+ !target ||
81
+ (expectedTimerGeneration !== undefined &&
82
+ target.timerGeneration !== expectedTimerGeneration)
83
+ ) {
84
+ return;
42
85
  }
43
86
 
44
- return newMap;
45
- });
46
- };
47
-
48
- const noticeApi = {
49
- open: (props: string | NoticeConfig) => {
50
- add(
51
- typeof props === 'string'
52
- ? { content: props, status: 'info' }
53
- : { status: 'info', ...props },
54
- );
55
- },
56
- success: (props: string | NoticeConfig) => {
57
- add(
58
- typeof props === 'string'
59
- ? { content: props, status: 'success' }
60
- : { ...props, status: 'success' },
61
- );
62
- },
63
- error: (props: string | NoticeConfig) => {
64
- add(
65
- typeof props === 'string'
66
- ? { content: props, status: 'error' }
67
- : { ...props, status: 'error' },
68
- );
69
- },
70
- warning: (props: string | NoticeConfig) => {
71
- add(
72
- typeof props === 'string'
73
- ? { content: props, status: 'warning' }
74
- : { ...props, status: 'warning' },
75
- );
76
- },
77
- info: (props: string | NoticeConfig) => {
78
- add(
79
- typeof props === 'string'
80
- ? { content: props, status: 'info' }
81
- : { ...props, status: 'info' },
82
- );
87
+ generationByKey.current.delete(key);
88
+ updateNotices((current) => {
89
+ const next = new Map(current);
90
+ next.forEach((items, placement) => {
91
+ const filtered = items.filter((item) => item.key !== key);
92
+ if (filtered.length === 0) {
93
+ next.delete(placement);
94
+ } else if (filtered.length !== items.length) {
95
+ next.set(placement, filtered);
96
+ }
97
+ });
98
+ return next;
99
+ });
100
+ target.onClose?.();
83
101
  },
84
- destroy: (key: string | number) => {
85
- setNoticesByPlacement((prev) => {
86
- const newMap = new Map(prev);
102
+ [updateNotices],
103
+ );
87
104
 
88
- // Find and remove in all placements
89
- newMap.forEach((notices, placement) => {
90
- const filtered = notices.filter((notice) => notice.key !== key);
91
- if (filtered.length === 0) {
92
- newMap.delete(placement);
93
- } else if (filtered.length !== notices.length) {
94
- newMap.set(placement, filtered);
105
+ const add = useCallback(
106
+ (props: WrapperedNoticeConfig) => {
107
+ const placement = props.placement ?? 'topRight';
108
+ let key = props.key;
109
+ if (key === undefined || key === null) {
110
+ do {
111
+ key = `__yee_notice_auto_${autoKey.current++}`;
112
+ } while (generationByKey.current.has(key));
113
+ }
114
+
115
+ const keyGeneration =
116
+ generationByKey.current.get(key) ?? generation.current++;
117
+ generationByKey.current.set(key, keyGeneration);
118
+ const nextNotice = {
119
+ ...props,
120
+ key,
121
+ placement,
122
+ timerGeneration: timerGeneration.current++,
123
+ } as NoticeType;
124
+
125
+ updateNotices((current) => {
126
+ const next = new Map(current);
127
+ let existingIndex = -1;
128
+
129
+ next.forEach((items, currentPlacement) => {
130
+ const index = items.findIndex((item) => item.key === key);
131
+ if (index < 0) {
132
+ return;
133
+ }
134
+
135
+ if (currentPlacement === placement) {
136
+ existingIndex = index;
137
+ } else {
138
+ const filtered = items.filter((item) => item.key !== key);
139
+ if (filtered.length === 0) {
140
+ next.delete(currentPlacement);
141
+ } else {
142
+ next.set(currentPlacement, filtered);
143
+ }
95
144
  }
96
145
  });
97
146
 
98
- return newMap;
147
+ const targetNotices = (next.get(placement) || []).filter(
148
+ (item) => item.key !== key,
149
+ );
150
+ if (existingIndex >= 0) {
151
+ targetNotices.splice(existingIndex, 0, nextNotice);
152
+ } else {
153
+ targetNotices.push(nextNotice);
154
+ }
155
+ next.set(placement, targetNotices);
156
+ return next;
99
157
  });
158
+
159
+ return () => {
160
+ if (generationByKey.current.get(key) === keyGeneration) {
161
+ onDestroy(key);
162
+ }
163
+ };
100
164
  },
101
- clear: (placement?: PlacementType) => {
102
- if (placement) {
103
- setNoticesByPlacement((prev) => {
104
- const newMap = new Map(prev);
105
- newMap.delete(placement);
106
- return newMap;
107
- });
108
- } else {
109
- setNoticesByPlacement(new Map());
110
- }
165
+ [onDestroy, updateNotices],
166
+ );
167
+
168
+ const clear = useCallback(
169
+ (placement?: PlacementType) => {
170
+ const removed = placement
171
+ ? noticesRef.current.get(placement) || []
172
+ : Array.from(noticesRef.current.values()).flat();
173
+
174
+ removed.forEach((item) => generationByKey.current.delete(item.key));
175
+ updateNotices((current) => {
176
+ if (!placement) {
177
+ return new Map();
178
+ }
179
+ const next = new Map(current);
180
+ next.delete(placement);
181
+ return next;
182
+ });
183
+ invokeOnClose(removed);
111
184
  },
112
- };
185
+ [updateNotices],
186
+ );
187
+
188
+ const noticeApi = useMemo<NoticeApi>(
189
+ () => ({
190
+ open: (props) =>
191
+ add(
192
+ typeof props === 'string'
193
+ ? { content: props, status: 'info' }
194
+ : { status: 'info', ...props },
195
+ ),
196
+ success: (props) =>
197
+ add(
198
+ typeof props === 'string'
199
+ ? { content: props, status: 'success' }
200
+ : { ...props, status: 'success' },
201
+ ),
202
+ error: (props) =>
203
+ add(
204
+ typeof props === 'string'
205
+ ? { content: props, status: 'error' }
206
+ : { ...props, status: 'error' },
207
+ ),
208
+ warning: (props) =>
209
+ add(
210
+ typeof props === 'string'
211
+ ? { content: props, status: 'warning' }
212
+ : { ...props, status: 'warning' },
213
+ ),
214
+ info: (props) =>
215
+ add(
216
+ typeof props === 'string'
217
+ ? { content: props, status: 'info' }
218
+ : { ...props, status: 'info' },
219
+ ),
220
+ destroy: onDestroy,
221
+ clear,
222
+ }),
223
+ [add, clear, onDestroy],
224
+ );
225
+
226
+ const noticeHolders = useMemo(() => {
227
+ if (!portalContainer) {
228
+ return [];
229
+ }
113
230
 
114
- const noticeHolders = React.useMemo(() => {
115
231
  const holders: React.ReactPortal[] = [];
116
232
  noticesByPlacement.forEach((notices, placement) => {
117
233
  holders.push(
@@ -119,15 +235,15 @@ const useNotice = () => {
119
235
  <NoticeList
120
236
  items={notices}
121
237
  placement={placement}
122
- onDestroy={(key) => onDestroy(key, placement)}
238
+ onDestroy={onDestroy}
123
239
  />,
124
- document.body,
240
+ portalContainer,
125
241
  `notice-${placement}`,
126
242
  ),
127
243
  );
128
244
  });
129
245
  return holders;
130
- }, [noticesByPlacement]);
246
+ }, [noticesByPlacement, onDestroy, portalContainer]);
131
247
 
132
248
  return { noticeApi, noticeHolders };
133
249
  };