@rainbow-oh/yee-c 0.8.1 → 0.8.3
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.
- package/dist/cjs/Cascader/cascader.js +38 -30
- package/dist/cjs/Cascader/column.js +17 -9
- package/dist/cjs/Cascader/search-list.js +30 -22
- package/dist/cjs/Cascader/utils/index.js +1 -5
- package/dist/cjs/Checkbox/checkbox-group.js +23 -14
- package/dist/cjs/Dialog/dialog.js +1 -1
- package/dist/cjs/Dialog/interface.d.ts +1 -1
- package/dist/cjs/Dialog/style/index.less +2 -1
- package/dist/cjs/Drawer/drawer.js +9 -1
- package/dist/cjs/Form/Group.js +1 -1
- package/dist/cjs/Form/demo/tooltip.js +1 -3
- package/dist/cjs/Input/style/index.less +1 -0
- package/dist/cjs/List/hooks/useListKeyboard.js +3 -1
- package/dist/cjs/Menu/menu.js +18 -2
- package/dist/cjs/Pagination/items.js +54 -26
- package/dist/cjs/Pagination/pagination.js +25 -9
- package/dist/cjs/PickerPanel/panels/DateTimePanel/index.d.ts +1 -1
- package/dist/cjs/PickerPanel/panels/MonthPanel/MonthHeader.js +7 -1
- package/dist/cjs/Rate/star.js +27 -3
- package/dist/cjs/Segmented/demo/variant.js +15 -1
- package/dist/cjs/Segmented/index.d.ts +1 -1
- package/dist/cjs/Segmented/segmented.js +12 -15
- package/dist/cjs/Select/option.js +8 -1
- package/dist/cjs/Select/options.js +9 -1
- package/dist/cjs/Select/select.js +3 -1
- package/dist/cjs/Selector/style/index.less +1 -0
- package/dist/cjs/Steps/step.js +14 -2
- package/dist/cjs/Steps/steps.js +11 -1
- package/dist/cjs/Table/demo/components-override.js +31 -2
- package/dist/cjs/Table/demo/grouping.js +7 -1
- package/dist/cjs/Table/hooks/useColumns.d.ts +3 -3
- package/dist/cjs/Table/hooks/useColumns.js +11 -14
- package/dist/cjs/Table/icon/arrow-icon.js +1 -1
- package/dist/cjs/Table/index.d.ts +1 -1
- package/dist/cjs/Table/style/index.less +2 -4
- package/dist/cjs/Table/tbody/row.js +1 -4
- package/dist/cjs/Table/theader/cell.js +6 -3
- package/dist/cjs/Table/theader/index.js +12 -4
- package/dist/cjs/TextArea/style/index.less +1 -0
- package/dist/cjs/Tour/index.d.ts +1 -1
- package/dist/cjs/Tree/style/index.less +1 -5
- package/dist/cjs/Tree/tree.js +38 -35
- package/dist/cjs/VerificationCode/style/index.less +1 -0
- package/dist/cjs/hooks/useCountdown.d.ts +2 -1
- package/dist/cjs/hooks/useCountdown.js +46 -23
- package/dist/cjs/hooks/useEvent.js +1 -4
- package/dist/cjs/locale/interface.d.ts +3 -0
- package/dist/cjs/locale/lang/en_US.js +4 -1
- package/dist/cjs/locale/lang/ja_JP.js +4 -1
- package/dist/cjs/locale/lang/zh_CN.js +4 -1
- package/dist/cjs/locale/lang/zh_TW.js +4 -1
- package/dist/cjs/message/demo/base.js +1 -1
- package/dist/cjs/message/index.d.ts +1 -1
- package/dist/cjs/message/interface.d.ts +16 -7
- package/dist/cjs/message/message-provider.d.ts +2 -1
- package/dist/cjs/message/message-provider.js +1 -1
- package/dist/cjs/message/message.js +14 -9
- package/dist/cjs/message/style/index.less +3 -1
- package/dist/cjs/message/use-message.d.ts +3 -12
- package/dist/cjs/message/use-message.js +103 -45
- package/dist/cjs/message/wrapper.d.ts +21 -27
- package/dist/cjs/message/wrapper.js +49 -19
- package/dist/cjs/notice/index.d.ts +1 -1
- package/dist/cjs/notice/interface.d.ts +17 -7
- package/dist/cjs/notice/notice-list.d.ts +3 -3
- package/dist/cjs/notice/notice-provider.d.ts +2 -1
- package/dist/cjs/notice/notice-provider.js +1 -1
- package/dist/cjs/notice/notice.js +63 -20
- package/dist/cjs/notice/style/index.less +13 -1
- package/dist/cjs/notice/use-notice.d.ts +2 -11
- package/dist/cjs/notice/use-notice.js +156 -71
- package/dist/cjs/notice/wrapper.d.ts +19 -15
- package/dist/cjs/notice/wrapper.js +122 -52
- package/dist/cjs/style/size.css +5 -3
- package/dist/cjs/style/themes.css +1 -1
- package/dist/cjs/style/variables.css +6 -4
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/Dialog/dialog.js +2 -2
- package/dist/esm/Dialog/interface.d.ts +1 -1
- package/dist/esm/Dialog/style/index.less +2 -1
- package/dist/esm/Form/Group.js +1 -1
- package/dist/esm/Input/style/index.less +1 -0
- package/dist/esm/PickerPanel/panels/DateTimePanel/index.d.ts +1 -1
- package/dist/esm/Segmented/index.d.ts +1 -1
- package/dist/esm/Segmented/segmented.js +1 -1
- package/dist/esm/Selector/style/index.less +1 -0
- package/dist/esm/Table/demo/components-override.js +1 -0
- package/dist/esm/Table/hooks/useColumns.d.ts +3 -3
- package/dist/esm/Table/hooks/useColumns.js +13 -16
- package/dist/esm/Table/icon/arrow-icon.js +1 -1
- package/dist/esm/Table/index.d.ts +1 -1
- package/dist/esm/Table/style/index.less +2 -4
- package/dist/esm/Table/theader/cell.js +7 -3
- package/dist/esm/TextArea/style/index.less +1 -0
- package/dist/esm/Tour/index.d.ts +1 -1
- package/dist/esm/Tree/style/index.less +1 -5
- package/dist/esm/Tree/tree.js +1 -1
- package/dist/esm/VerificationCode/style/index.less +1 -0
- package/dist/esm/hooks/useCountdown.d.ts +2 -1
- package/dist/esm/hooks/useCountdown.js +44 -14
- package/dist/esm/locale/interface.d.ts +3 -0
- package/dist/esm/locale/lang/en_US.js +4 -1
- package/dist/esm/locale/lang/ja_JP.js +4 -1
- package/dist/esm/locale/lang/zh_CN.js +4 -1
- package/dist/esm/locale/lang/zh_TW.js +4 -1
- package/dist/esm/message/demo/base.js +4 -1
- package/dist/esm/message/index.d.ts +1 -1
- package/dist/esm/message/interface.d.ts +16 -7
- package/dist/esm/message/message-provider.d.ts +2 -1
- package/dist/esm/message/message-provider.js +1 -1
- package/dist/esm/message/message.js +22 -9
- package/dist/esm/message/style/index.less +3 -1
- package/dist/esm/message/use-message.d.ts +3 -12
- package/dist/esm/message/use-message.js +129 -79
- package/dist/esm/message/wrapper.d.ts +21 -27
- package/dist/esm/message/wrapper.js +60 -18
- package/dist/esm/notice/index.d.ts +1 -1
- package/dist/esm/notice/interface.d.ts +17 -7
- package/dist/esm/notice/notice-list.d.ts +3 -3
- package/dist/esm/notice/notice-provider.d.ts +2 -1
- package/dist/esm/notice/notice-provider.js +1 -1
- package/dist/esm/notice/notice.js +73 -21
- package/dist/esm/notice/style/index.less +13 -1
- package/dist/esm/notice/use-notice.d.ts +2 -11
- package/dist/esm/notice/use-notice.js +180 -100
- package/dist/esm/notice/wrapper.d.ts +19 -15
- package/dist/esm/notice/wrapper.js +144 -62
- package/dist/esm/style/size.css +5 -3
- package/dist/esm/style/themes.css +1 -1
- package/dist/esm/style/variables.css +6 -4
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/umd/yee-c.min.css +2 -2
- package/dist/umd/yee-c.min.js +1 -1
- package/package.json +1 -1
- package/src/Anchor/anchor.tsx +2 -2
- package/src/Carousel/carousel.tsx +1 -2
- package/src/Cascader/cascader.tsx +5 -1
- package/src/Cascader/column.tsx +5 -1
- package/src/Cascader/search-list.tsx +10 -3
- package/src/Cascader/utils/index.ts +3 -6
- package/src/Checkbox/checkbox-group.tsx +6 -1
- package/src/Dialog/dialog.tsx +7 -3
- package/src/Dialog/interface.ts +1 -1
- package/src/Dialog/style/index.less +2 -1
- package/src/Drawer/drawer.tsx +7 -1
- package/src/Form/Group.tsx +2 -4
- package/src/Form/demo/tooltip.tsx +2 -11
- package/src/Form/utils/validate.ts +1 -4
- package/src/Input/style/index.less +1 -0
- package/src/List/hooks/useListKeyboard.ts +3 -3
- package/src/List/list.tsx +2 -1
- package/src/Menu/menu.tsx +14 -3
- package/src/Pagination/items.tsx +16 -4
- package/src/Pagination/pagination.tsx +18 -4
- package/src/PickerPanel/panels/DatePanel/DateBody.tsx +1 -1
- package/src/PickerPanel/panels/DateTimePanel/index.tsx +1 -1
- package/src/PickerPanel/panels/MonthPanel/MonthHeader.tsx +7 -2
- package/src/PickerPanel/panels/PanelBody.tsx +1 -1
- package/src/PickerPanel/panels/QuarterPanel/QuarterHeader.tsx +1 -1
- package/src/PickerPanel/panels/RangePanel/index.tsx +1 -1
- package/src/PickerPanel/panels/TimePanel/TimeBody.tsx +1 -1
- package/src/PickerPanel/panels/YearPanel/YearHeader.tsx +1 -1
- package/src/PickerPanel/utils/pickerUtils.ts +2 -1
- package/src/RangePicker/range-picker-mobile.tsx +1 -3
- package/src/Rate/star.tsx +16 -4
- package/src/Segmented/demo/variant.tsx +10 -2
- package/src/Segmented/index.ts +2 -2
- package/src/Segmented/segmented.tsx +4 -11
- package/src/Select/interface.ts +0 -1
- package/src/Select/option.tsx +8 -2
- package/src/Select/options.tsx +11 -4
- package/src/Select/select.tsx +4 -4
- package/src/Selector/style/index.less +1 -0
- package/src/Steps/step.tsx +11 -3
- package/src/Steps/steps.tsx +8 -1
- package/src/Table/demo/components-override.tsx +34 -7
- package/src/Table/demo/grouping.tsx +8 -4
- package/src/Table/hooks/useColumns.ts +14 -18
- package/src/Table/icon/arrow-icon.tsx +1 -1
- package/src/Table/index.ts +3 -3
- package/src/Table/style/index.less +2 -4
- package/src/Table/table.tsx +2 -4
- package/src/Table/tbody/row.tsx +1 -3
- package/src/Table/theader/cell.tsx +6 -3
- package/src/Table/theader/index.tsx +9 -10
- package/src/TextArea/style/index.less +1 -0
- package/src/Tour/demo/placement.tsx +2 -1
- package/src/Tour/index.ts +1 -1
- package/src/Tree/style/index.less +1 -5
- package/src/Tree/tree.tsx +16 -11
- package/src/Tree/utils/tree-tools.ts +4 -1
- package/src/VerificationCode/style/index.less +1 -0
- package/src/hooks/useCountdown.ts +52 -21
- package/src/hooks/useEvent.ts +1 -3
- package/src/locale/context.tsx +5 -1
- package/src/locale/interface.ts +6 -1
- package/src/locale/lang/en_US.ts +3 -0
- package/src/locale/lang/ja_JP.ts +3 -0
- package/src/locale/lang/zh_CN.ts +3 -0
- package/src/locale/lang/zh_TW.ts +3 -0
- package/src/message/demo/base.tsx +1 -1
- package/src/message/index.ts +1 -1
- package/src/message/interface.ts +22 -7
- package/src/message/message-provider.tsx +3 -2
- package/src/message/message.test.tsx +283 -0
- package/src/message/message.tsx +14 -9
- package/src/message/style/index.less +3 -1
- package/src/message/use-message.tsx +144 -67
- package/src/message/wrapper.tsx +62 -20
- package/src/notice/index.en-US.md +76 -75
- package/src/notice/index.ts +6 -1
- package/src/notice/index.zh-CN.md +3 -2
- package/src/notice/interface.ts +31 -14
- package/src/notice/notice-list.tsx +3 -9
- package/src/notice/notice-provider.tsx +3 -2
- package/src/notice/notice.test.tsx +450 -0
- package/src/notice/notice.tsx +69 -21
- package/src/notice/style/index.less +13 -1
- package/src/notice/use-notice.tsx +210 -94
- package/src/notice/wrapper.tsx +159 -71
- package/src/style/size.css +5 -3
- package/src/utils/debounce.ts +1 -3
- package/src/version.ts +1 -1
|
@@ -4,19 +4,33 @@ export type CountdownOptions = {
|
|
|
4
4
|
duration: number;
|
|
5
5
|
interval?: number;
|
|
6
6
|
onComplete?: () => void;
|
|
7
|
+
resetKey?: string | number;
|
|
7
8
|
};
|
|
8
9
|
|
|
9
10
|
export default function useCountdown({
|
|
10
11
|
duration,
|
|
11
12
|
interval = 100,
|
|
12
13
|
onComplete,
|
|
14
|
+
resetKey,
|
|
13
15
|
}: CountdownOptions) {
|
|
14
|
-
const
|
|
16
|
+
const initialRemaining = Math.max(0, duration);
|
|
17
|
+
const [remaining, setRemaining] = useState(initialRemaining);
|
|
15
18
|
const [isPaused, setIsPaused] = useState(false);
|
|
16
19
|
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
17
|
-
const remainingRef = useRef(
|
|
20
|
+
const remainingRef = useRef(initialRemaining);
|
|
21
|
+
const isPausedRef = useRef(false);
|
|
18
22
|
const onCompleteRef = useRef(onComplete);
|
|
23
|
+
const identityRef = useRef({ duration, resetKey });
|
|
24
|
+
const epochRef = useRef(0);
|
|
25
|
+
|
|
19
26
|
onCompleteRef.current = onComplete;
|
|
27
|
+
if (
|
|
28
|
+
identityRef.current.duration !== duration ||
|
|
29
|
+
identityRef.current.resetKey !== resetKey
|
|
30
|
+
) {
|
|
31
|
+
identityRef.current = { duration, resetKey };
|
|
32
|
+
epochRef.current += 1;
|
|
33
|
+
}
|
|
20
34
|
|
|
21
35
|
const clear = useCallback(() => {
|
|
22
36
|
if (timerRef.current !== null) {
|
|
@@ -25,41 +39,58 @@ export default function useCountdown({
|
|
|
25
39
|
}
|
|
26
40
|
}, []);
|
|
27
41
|
|
|
28
|
-
const tick = useCallback(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
42
|
+
const tick = useCallback(
|
|
43
|
+
(epoch: number) => {
|
|
44
|
+
if (epoch !== epochRef.current) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
32
47
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
48
|
+
const next = Math.max(0, remainingRef.current - interval);
|
|
49
|
+
remainingRef.current = next;
|
|
50
|
+
setRemaining(next);
|
|
51
|
+
|
|
52
|
+
if (next <= 0) {
|
|
53
|
+
onCompleteRef.current?.();
|
|
54
|
+
} else {
|
|
55
|
+
timerRef.current = setTimeout(() => tick(epoch), interval);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
[interval],
|
|
59
|
+
);
|
|
39
60
|
|
|
40
61
|
const start = useCallback(() => {
|
|
41
62
|
clear();
|
|
42
|
-
|
|
63
|
+
if (remainingRef.current > 0 && !isPausedRef.current) {
|
|
64
|
+
const epoch = epochRef.current;
|
|
65
|
+
timerRef.current = setTimeout(() => tick(epoch), interval);
|
|
66
|
+
}
|
|
43
67
|
}, [clear, interval, tick]);
|
|
44
68
|
|
|
45
69
|
useEffect(() => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
70
|
+
clear();
|
|
71
|
+
const nextRemaining = Math.max(0, duration);
|
|
72
|
+
remainingRef.current = nextRemaining;
|
|
73
|
+
setRemaining(nextRemaining);
|
|
74
|
+
|
|
75
|
+
if (nextRemaining > 0 && !isPausedRef.current) {
|
|
76
|
+
start();
|
|
77
|
+
}
|
|
78
|
+
|
|
50
79
|
return clear;
|
|
51
|
-
|
|
52
|
-
}, [duration]);
|
|
80
|
+
}, [clear, duration, resetKey, start]);
|
|
53
81
|
|
|
54
82
|
const onPause = useCallback(() => {
|
|
83
|
+
isPausedRef.current = true;
|
|
55
84
|
clear();
|
|
56
85
|
setIsPaused(true);
|
|
57
86
|
}, [clear]);
|
|
58
87
|
|
|
59
88
|
const onResume = useCallback(() => {
|
|
60
|
-
|
|
89
|
+
isPausedRef.current = false;
|
|
61
90
|
setIsPaused(false);
|
|
62
|
-
|
|
91
|
+
if (remainingRef.current > 0) {
|
|
92
|
+
start();
|
|
93
|
+
}
|
|
63
94
|
}, [start]);
|
|
64
95
|
|
|
65
96
|
return { remaining, isPaused, onPause, onResume };
|
package/src/hooks/useEvent.ts
CHANGED
|
@@ -7,9 +7,7 @@ export default function useEvent<T extends Function>(callback?: T): T {
|
|
|
7
7
|
|
|
8
8
|
const memoFn = React.useCallback(
|
|
9
9
|
(...args: unknown[]) =>
|
|
10
|
-
(ref.current as ((...args: unknown[]) => unknown) | undefined)?.(
|
|
11
|
-
...args,
|
|
12
|
-
),
|
|
10
|
+
(ref.current as ((...args: unknown[]) => unknown) | undefined)?.(...args),
|
|
13
11
|
[],
|
|
14
12
|
) as unknown as T;
|
|
15
13
|
|
package/src/locale/context.tsx
CHANGED
|
@@ -7,7 +7,11 @@ import React, {
|
|
|
7
7
|
useMemo,
|
|
8
8
|
useState,
|
|
9
9
|
} from 'react';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
DEFAULT_LOCALE,
|
|
12
|
+
DEFAULT_LOCALE_CONFIG,
|
|
13
|
+
LOCALE_CONFIGS,
|
|
14
|
+
} from './constants';
|
|
11
15
|
import type { Locale, LocaleConfigInput, TFunction } from './interface';
|
|
12
16
|
import defaultLocaleData from './lang/en_US';
|
|
13
17
|
import { deepMerge, isValidLocale, translate } from './util';
|
package/src/locale/interface.ts
CHANGED
|
@@ -76,6 +76,9 @@ export interface LocaleTable {
|
|
|
76
76
|
selectionAll?: string; // "Select all on current page"
|
|
77
77
|
selectionInvert?: string; // "Invert selection on current page"
|
|
78
78
|
selectionNone?: string; // "Clear selection on current page"
|
|
79
|
+
sortAscending?: string; // "Click to sort ascending"
|
|
80
|
+
sortDescending?: string; // "Click to sort descending"
|
|
81
|
+
sortCancel?: string; // "Click to cancel sorting"
|
|
79
82
|
}
|
|
80
83
|
|
|
81
84
|
// Pagination component
|
|
@@ -220,4 +223,6 @@ export type DeepPartial<T> = T extends (...args: any[]) => any
|
|
|
220
223
|
* so the provider knows which built-in pack to merge the overrides onto;
|
|
221
224
|
* only the keys you specify are overridden, everything else is inherited.
|
|
222
225
|
*/
|
|
223
|
-
export type LocaleConfigInput =
|
|
226
|
+
export type LocaleConfigInput =
|
|
227
|
+
| string
|
|
228
|
+
| (DeepPartial<Locale> & { locale: string });
|
package/src/locale/lang/en_US.ts
CHANGED
|
@@ -102,6 +102,9 @@ const locale: Locale = {
|
|
|
102
102
|
selectionAll: 'Select All on Page',
|
|
103
103
|
selectionInvert: 'Invert Selection on Page',
|
|
104
104
|
selectionNone: 'Clear Selection on Page',
|
|
105
|
+
sortAscending: 'Click to sort ascending',
|
|
106
|
+
sortDescending: 'Click to sort descending',
|
|
107
|
+
sortCancel: 'Click to cancel sorting',
|
|
105
108
|
},
|
|
106
109
|
|
|
107
110
|
pagination: {
|
package/src/locale/lang/ja_JP.ts
CHANGED
|
@@ -102,6 +102,9 @@ const locale: Locale = {
|
|
|
102
102
|
selectionAll: 'ページ内すべて選択',
|
|
103
103
|
selectionInvert: 'ページ内選択を反転',
|
|
104
104
|
selectionNone: 'ページ内選択をクリア',
|
|
105
|
+
sortAscending: 'クリックで昇順に並べ替え',
|
|
106
|
+
sortDescending: 'クリックで降順に並べ替え',
|
|
107
|
+
sortCancel: 'クリックで並べ替えを解除',
|
|
105
108
|
},
|
|
106
109
|
|
|
107
110
|
pagination: {
|
package/src/locale/lang/zh_CN.ts
CHANGED
package/src/locale/lang/zh_TW.ts
CHANGED
|
@@ -6,7 +6,7 @@ const BaseDemo = () => {
|
|
|
6
6
|
const showMessage = (type: string) => {
|
|
7
7
|
switch (type) {
|
|
8
8
|
case 'info':
|
|
9
|
-
messageApi.open('This is an info message');
|
|
9
|
+
messageApi.open({content: 'This is an info message', duration: 0});
|
|
10
10
|
break;
|
|
11
11
|
case 'success':
|
|
12
12
|
message.success('This is a success message');
|
package/src/message/index.ts
CHANGED
package/src/message/interface.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export type MessageConfig = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
[dataAttribute: `data-${string}`]:
|
|
3
|
+
| string
|
|
4
|
+
| number
|
|
5
|
+
| boolean
|
|
6
|
+
| undefined;
|
|
6
7
|
/**
|
|
7
8
|
* Custom prefix class name
|
|
8
9
|
*/
|
|
@@ -20,8 +21,8 @@ export type MessageConfig = {
|
|
|
20
21
|
*/
|
|
21
22
|
content: React.ReactNode;
|
|
22
23
|
/**
|
|
23
|
-
* Auto close time (
|
|
24
|
-
* @default
|
|
24
|
+
* Auto close time (seconds), 0 means no auto close
|
|
25
|
+
* @default 3
|
|
25
26
|
*/
|
|
26
27
|
duration?: number;
|
|
27
28
|
/**
|
|
@@ -42,6 +43,19 @@ export type MessageConfig = {
|
|
|
42
43
|
onClose?: () => void;
|
|
43
44
|
};
|
|
44
45
|
|
|
46
|
+
export type MessageClose = () => void;
|
|
47
|
+
|
|
48
|
+
export interface MessageApi {
|
|
49
|
+
open: (config: string | MessageConfig) => MessageClose;
|
|
50
|
+
info: (config: string | MessageConfig) => MessageClose;
|
|
51
|
+
success: (config: string | MessageConfig) => MessageClose;
|
|
52
|
+
warning: (config: string | MessageConfig) => MessageClose;
|
|
53
|
+
error: (config: string | MessageConfig) => MessageClose;
|
|
54
|
+
loading: (config: string | MessageConfig) => MessageClose;
|
|
55
|
+
destroy: (key: string | number) => void;
|
|
56
|
+
clear: () => void;
|
|
57
|
+
}
|
|
58
|
+
|
|
45
59
|
// With status
|
|
46
60
|
export type WrapperedMessageConfig = MessageConfig & {
|
|
47
61
|
status?: 'info' | 'success' | 'warning' | 'error' | 'loading';
|
|
@@ -50,11 +64,12 @@ export type WrapperedMessageConfig = MessageConfig & {
|
|
|
50
64
|
// With key
|
|
51
65
|
export type MessageType = WrapperedMessageConfig & {
|
|
52
66
|
key: string | number;
|
|
67
|
+
timerGeneration: number;
|
|
53
68
|
};
|
|
54
69
|
|
|
55
70
|
export interface MessageProps extends MessageType {
|
|
56
71
|
/**
|
|
57
72
|
* Destroy component callback
|
|
58
73
|
*/
|
|
59
|
-
onDestroy?: (key: string | number) => void;
|
|
74
|
+
onDestroy?: (key: string | number, timerGeneration?: number) => void;
|
|
60
75
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React, { createContext, useContext } from 'react';
|
|
2
|
+
import type { MessageApi } from './interface';
|
|
2
3
|
import useMessage from './use-message';
|
|
3
4
|
|
|
4
|
-
const MessageContext = createContext<
|
|
5
|
+
const MessageContext = createContext<MessageApi | undefined>(undefined);
|
|
5
6
|
|
|
6
7
|
export function MessageProvider({ children }: { children: React.ReactNode }) {
|
|
7
8
|
const { messageApi, messageHolder } = useMessage();
|
|
@@ -14,7 +15,7 @@ export function MessageProvider({ children }: { children: React.ReactNode }) {
|
|
|
14
15
|
);
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
export function useGlobalMessage() {
|
|
18
|
+
export function useGlobalMessage(): MessageApi {
|
|
18
19
|
const context = useContext(MessageContext);
|
|
19
20
|
|
|
20
21
|
if (context === undefined) {
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { act, cleanup, render, renderHook, screen } from '@testing-library/react';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { renderToString } from 'react-dom/server';
|
|
6
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
7
|
+
import type { MessageApi, MessageClose } from './interface';
|
|
8
|
+
import { useGlobalMessage } from './message-provider';
|
|
9
|
+
import useMessage from './use-message';
|
|
10
|
+
import messageWrapper, { message } from './wrapper';
|
|
11
|
+
|
|
12
|
+
let currentApi: MessageApi;
|
|
13
|
+
|
|
14
|
+
function MessageHarness() {
|
|
15
|
+
const { messageApi, messageHolder } = useMessage();
|
|
16
|
+
currentApi = messageApi;
|
|
17
|
+
return <>{messageHolder}</>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
cleanup();
|
|
22
|
+
messageWrapper.unmount();
|
|
23
|
+
vi.useRealTimers();
|
|
24
|
+
vi.unstubAllGlobals();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('message', () => {
|
|
28
|
+
it('renders safely without a document during SSR', () => {
|
|
29
|
+
vi.stubGlobal('document', undefined);
|
|
30
|
+
|
|
31
|
+
expect(() => renderToString(<MessageHarness />)).not.toThrow();
|
|
32
|
+
expect(() => message.info('server message')).not.toThrow();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('keeps the hook API reference stable across message updates', () => {
|
|
36
|
+
render(<MessageHarness />);
|
|
37
|
+
const initialApi = currentApi;
|
|
38
|
+
|
|
39
|
+
act(() => {
|
|
40
|
+
currentApi.info({ content: 'saved', duration: 0 });
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
expect(currentApi).toBe(initialApi);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('does not restart existing timers when another message is added', () => {
|
|
47
|
+
vi.useFakeTimers();
|
|
48
|
+
const firstOnClose = vi.fn();
|
|
49
|
+
render(<MessageHarness />);
|
|
50
|
+
|
|
51
|
+
act(() => {
|
|
52
|
+
currentApi.info({
|
|
53
|
+
content: 'first',
|
|
54
|
+
duration: 3,
|
|
55
|
+
onClose: firstOnClose,
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
act(() => {
|
|
59
|
+
vi.advanceTimersByTime(2000);
|
|
60
|
+
});
|
|
61
|
+
act(() => {
|
|
62
|
+
currentApi.info({ content: 'second', duration: 3 });
|
|
63
|
+
});
|
|
64
|
+
act(() => {
|
|
65
|
+
vi.advanceTimersByTime(1000);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
expect(firstOnClose).toHaveBeenCalledTimes(1);
|
|
69
|
+
expect(screen.queryByText('first')).toBeNull();
|
|
70
|
+
expect(screen.queryByText('second')).not.toBeNull();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('does not let an old timer close a reused key in the same batch', () => {
|
|
74
|
+
vi.useFakeTimers();
|
|
75
|
+
const secondOnClose = vi.fn();
|
|
76
|
+
render(<MessageHarness />);
|
|
77
|
+
|
|
78
|
+
act(() => {
|
|
79
|
+
currentApi.info({ key: 'timer', content: 'first', duration: 3 });
|
|
80
|
+
});
|
|
81
|
+
act(() => {
|
|
82
|
+
vi.advanceTimersByTime(2000);
|
|
83
|
+
});
|
|
84
|
+
act(() => {
|
|
85
|
+
currentApi.destroy('timer');
|
|
86
|
+
currentApi.info({
|
|
87
|
+
key: 'timer',
|
|
88
|
+
content: 'second',
|
|
89
|
+
duration: 3,
|
|
90
|
+
onClose: secondOnClose,
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
act(() => {
|
|
94
|
+
vi.advanceTimersByTime(1000);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
expect(screen.queryByText('second')).not.toBeNull();
|
|
98
|
+
expect(secondOnClose).not.toHaveBeenCalled();
|
|
99
|
+
|
|
100
|
+
act(() => {
|
|
101
|
+
vi.advanceTimersByTime(2000);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
expect(screen.queryByText('second')).toBeNull();
|
|
105
|
+
expect(secondOnClose).toHaveBeenCalledTimes(1);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('restarts the timer when an existing key is updated', () => {
|
|
109
|
+
vi.useFakeTimers();
|
|
110
|
+
const onClose = vi.fn();
|
|
111
|
+
render(<MessageHarness />);
|
|
112
|
+
|
|
113
|
+
act(() => {
|
|
114
|
+
currentApi.info({
|
|
115
|
+
key: 'updating-timer',
|
|
116
|
+
content: 'working',
|
|
117
|
+
duration: 3,
|
|
118
|
+
onClose,
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
act(() => {
|
|
122
|
+
vi.advanceTimersByTime(2000);
|
|
123
|
+
});
|
|
124
|
+
act(() => {
|
|
125
|
+
currentApi.info({
|
|
126
|
+
key: 'updating-timer',
|
|
127
|
+
content: 'finished',
|
|
128
|
+
duration: 3,
|
|
129
|
+
onClose,
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
act(() => {
|
|
133
|
+
vi.advanceTimersByTime(1000);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
expect(screen.queryByText('finished')).not.toBeNull();
|
|
137
|
+
expect(onClose).not.toHaveBeenCalled();
|
|
138
|
+
|
|
139
|
+
act(() => {
|
|
140
|
+
vi.advanceTimersByTime(2000);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
expect(screen.queryByText('finished')).toBeNull();
|
|
144
|
+
expect(onClose).toHaveBeenCalledTimes(1);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('updates duplicate keys and closes the current message with its handle', () => {
|
|
148
|
+
const firstOnClose = vi.fn();
|
|
149
|
+
const updatedOnClose = vi.fn();
|
|
150
|
+
let close!: MessageClose;
|
|
151
|
+
render(<MessageHarness />);
|
|
152
|
+
|
|
153
|
+
act(() => {
|
|
154
|
+
close = currentApi.info({
|
|
155
|
+
key: 'job',
|
|
156
|
+
content: 'working',
|
|
157
|
+
duration: 0,
|
|
158
|
+
onClose: firstOnClose,
|
|
159
|
+
});
|
|
160
|
+
currentApi.success({
|
|
161
|
+
key: 'job',
|
|
162
|
+
content: 'finished',
|
|
163
|
+
duration: 0,
|
|
164
|
+
onClose: updatedOnClose,
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
expect(screen.queryByText('working')).toBeNull();
|
|
169
|
+
expect(screen.queryByText('finished')).not.toBeNull();
|
|
170
|
+
expect(document.querySelectorAll('.yee-message')).toHaveLength(1);
|
|
171
|
+
|
|
172
|
+
act(() => close());
|
|
173
|
+
|
|
174
|
+
expect(screen.queryByText('finished')).toBeNull();
|
|
175
|
+
expect(firstOnClose).not.toHaveBeenCalled();
|
|
176
|
+
expect(updatedOnClose).toHaveBeenCalledTimes(1);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('does not let stale hook close handles close a reused key', () => {
|
|
180
|
+
let staleClose!: MessageClose;
|
|
181
|
+
render(<MessageHarness />);
|
|
182
|
+
|
|
183
|
+
act(() => {
|
|
184
|
+
staleClose = currentApi.info({
|
|
185
|
+
key: 'reused',
|
|
186
|
+
content: 'first generation',
|
|
187
|
+
duration: 0,
|
|
188
|
+
});
|
|
189
|
+
staleClose();
|
|
190
|
+
currentApi.info({
|
|
191
|
+
key: 'reused',
|
|
192
|
+
content: 'second generation',
|
|
193
|
+
duration: 0,
|
|
194
|
+
});
|
|
195
|
+
staleClose();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
expect(screen.queryByText('second generation')).not.toBeNull();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('does not let stale global close handles close a reused key', () => {
|
|
202
|
+
let staleClose!: MessageClose;
|
|
203
|
+
|
|
204
|
+
act(() => {
|
|
205
|
+
staleClose = message.info({
|
|
206
|
+
key: 'global-reused',
|
|
207
|
+
content: 'first generation',
|
|
208
|
+
duration: 0,
|
|
209
|
+
});
|
|
210
|
+
staleClose();
|
|
211
|
+
message.info({
|
|
212
|
+
key: 'global-reused',
|
|
213
|
+
content: 'second generation',
|
|
214
|
+
duration: 0,
|
|
215
|
+
});
|
|
216
|
+
staleClose();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
expect(screen.queryByText('second generation')).not.toBeNull();
|
|
220
|
+
act(() => message.destroy('global-reused'));
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('calls onClose when clearing hook messages', () => {
|
|
224
|
+
const firstOnClose = vi.fn();
|
|
225
|
+
const secondOnClose = vi.fn();
|
|
226
|
+
render(<MessageHarness />);
|
|
227
|
+
|
|
228
|
+
act(() => {
|
|
229
|
+
currentApi.info({ content: 'first', duration: 0, onClose: firstOnClose });
|
|
230
|
+
currentApi.info({ content: 'second', duration: 0, onClose: secondOnClose });
|
|
231
|
+
currentApi.clear();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
expect(firstOnClose).toHaveBeenCalledTimes(1);
|
|
235
|
+
expect(secondOnClose).toHaveBeenCalledTimes(1);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('preserves a falsy global key without mutating the config', () => {
|
|
239
|
+
const onClose = vi.fn();
|
|
240
|
+
const config = { key: 0, content: 'zero', duration: 0, onClose };
|
|
241
|
+
|
|
242
|
+
act(() => {
|
|
243
|
+
message.open(config);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
expect(config.key).toBe(0);
|
|
247
|
+
expect(screen.queryByText('zero')).not.toBeNull();
|
|
248
|
+
|
|
249
|
+
act(() => {
|
|
250
|
+
message.destroy(0);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
expect(screen.queryByText('zero')).toBeNull();
|
|
254
|
+
expect(onClose).toHaveBeenCalledTimes(1);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('throws a clear error outside MessageProvider', () => {
|
|
258
|
+
expect(() => renderHook(() => useGlobalMessage())).toThrow(
|
|
259
|
+
'useGlobalMessage must be used within MessageProvider',
|
|
260
|
+
);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('forwards data attributes and exposes live-region semantics', () => {
|
|
264
|
+
render(<MessageHarness />);
|
|
265
|
+
|
|
266
|
+
act(() => {
|
|
267
|
+
currentApi.error({
|
|
268
|
+
content: <div>failed</div>,
|
|
269
|
+
duration: 0,
|
|
270
|
+
'data-track-id': 'failure-message',
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
const element = screen.getByText('failed').closest('.yee-message');
|
|
275
|
+
expect(element?.getAttribute('data-track-id')).toBe('failure-message');
|
|
276
|
+
expect(element?.getAttribute('role')).toBe('alert');
|
|
277
|
+
expect(element?.getAttribute('aria-live')).toBe('assertive');
|
|
278
|
+
expect(element?.querySelector('.yee-message-content')?.tagName).toBe('DIV');
|
|
279
|
+
expect(element?.querySelector('.yee-message-icon')?.getAttribute('aria-hidden')).toBe(
|
|
280
|
+
'true',
|
|
281
|
+
);
|
|
282
|
+
});
|
|
283
|
+
});
|
package/src/message/message.tsx
CHANGED
|
@@ -5,10 +5,10 @@ import Spin from '../Spin';
|
|
|
5
5
|
import type { MessageProps } from './interface';
|
|
6
6
|
|
|
7
7
|
const icons = {
|
|
8
|
-
info: <Info size={
|
|
9
|
-
success: <CircleCheck size={
|
|
10
|
-
warning: <CircleAlert size={
|
|
11
|
-
error: <CircleX size={
|
|
8
|
+
info: <Info size={22} fill='currentColor' stroke="#FFF" strokeWidth={2} />,
|
|
9
|
+
success: <CircleCheck size={22} fill='currentColor' stroke="#FFF" strokeWidth={2} />,
|
|
10
|
+
warning: <CircleAlert size={22} fill='currentColor' stroke="#FFF" strokeWidth={2} />,
|
|
11
|
+
error: <CircleX size={22} fill='currentColor' stroke="#FFF" strokeWidth={2} />,
|
|
12
12
|
loading: <Spin className="loading" type="spin" size="small" height="auto" />,
|
|
13
13
|
};
|
|
14
14
|
|
|
@@ -16,6 +16,7 @@ const Message: React.FC<MessageProps & { id: string | number }> = (props) => {
|
|
|
16
16
|
const {
|
|
17
17
|
prefixCls = 'yee-message',
|
|
18
18
|
id: key,
|
|
19
|
+
timerGeneration,
|
|
19
20
|
status,
|
|
20
21
|
content,
|
|
21
22
|
style,
|
|
@@ -32,15 +33,14 @@ const Message: React.FC<MessageProps & { id: string | number }> = (props) => {
|
|
|
32
33
|
useEffect(() => {
|
|
33
34
|
if (duration && duration > 0) {
|
|
34
35
|
const timer = setTimeout(() => {
|
|
35
|
-
onDestroy?.(key);
|
|
36
|
-
onClose?.();
|
|
36
|
+
onDestroy?.(key, timerGeneration);
|
|
37
37
|
}, duration * 1000);
|
|
38
38
|
|
|
39
39
|
return () => {
|
|
40
40
|
clearTimeout(timer);
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
|
-
}, [duration, key, onDestroy, onClose]);
|
|
43
|
+
}, [duration, key, onDestroy, onClose, timerGeneration]);
|
|
44
44
|
|
|
45
45
|
const renderIcon = () => {
|
|
46
46
|
if (!icon && !status) {
|
|
@@ -48,22 +48,27 @@ const Message: React.FC<MessageProps & { id: string | number }> = (props) => {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
return (
|
|
51
|
-
<span className={`${prefixCls}-icon`}>
|
|
51
|
+
<span aria-hidden="true" className={`${prefixCls}-icon`}>
|
|
52
52
|
{icon ? icon : status ? icons[status] : null}
|
|
53
53
|
</span>
|
|
54
54
|
);
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
+
const isAssertive = status === 'error' || status === 'warning';
|
|
58
|
+
|
|
57
59
|
return (
|
|
58
60
|
<div
|
|
59
61
|
{...rest}
|
|
62
|
+
aria-atomic="true"
|
|
63
|
+
aria-live={isAssertive ? 'assertive' : 'polite'}
|
|
64
|
+
role={isAssertive ? 'alert' : 'status'}
|
|
60
65
|
data-testid={dataTestId}
|
|
61
66
|
className={clsx(prefixCls, [`${prefixCls}-${status}`], className)}
|
|
62
67
|
style={style}
|
|
63
68
|
onClick={onClick}
|
|
64
69
|
>
|
|
65
70
|
{renderIcon()}
|
|
66
|
-
<
|
|
71
|
+
<div className={`${prefixCls}-content`}>{content}</div>
|
|
67
72
|
</div>
|
|
68
73
|
);
|
|
69
74
|
};
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
align-items: center;
|
|
7
7
|
width: fit-content;
|
|
8
8
|
padding: var(--yee-message-padding);
|
|
9
|
+
pointer-events: auto;
|
|
9
10
|
background-color: var(--yee-message-bg-color);
|
|
10
11
|
border-radius: var(--yee-message-border-radius);
|
|
11
12
|
box-shadow: var(--yee-message-box-shadow);
|
|
@@ -30,12 +31,13 @@
|
|
|
30
31
|
align-items: center;
|
|
31
32
|
justify-content: center;
|
|
32
33
|
width: 100vw;
|
|
34
|
+
pointer-events: none;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
// status
|
|
36
38
|
&-success {
|
|
37
39
|
@{pls}-icon {
|
|
38
|
-
color: var(--yee-
|
|
40
|
+
color: var(--yee-success-color);
|
|
39
41
|
}
|
|
40
42
|
}
|
|
41
43
|
|