@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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { NoticeProps } from './interface';
|
|
2
|
+
import type { NoticeProps, PlacementType } from './interface';
|
|
3
3
|
import Notice from './notice';
|
|
4
4
|
|
|
5
5
|
import './style/index.less';
|
|
@@ -10,14 +10,8 @@ const NoticeList = ({
|
|
|
10
10
|
onDestroy,
|
|
11
11
|
}: {
|
|
12
12
|
items: NoticeProps[];
|
|
13
|
-
placement?:
|
|
14
|
-
|
|
15
|
-
| 'topRight'
|
|
16
|
-
| 'bottomLeft'
|
|
17
|
-
| 'bottomRight'
|
|
18
|
-
| 'top'
|
|
19
|
-
| 'bottom';
|
|
20
|
-
onDestroy: (key: string | number) => void;
|
|
13
|
+
placement?: PlacementType;
|
|
14
|
+
onDestroy: (key: string | number, timerGeneration?: number) => void;
|
|
21
15
|
}) => {
|
|
22
16
|
if (items.length === 0) {
|
|
23
17
|
return null;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React, { createContext, useContext } from 'react';
|
|
2
|
+
import type { NoticeApi } from './interface';
|
|
2
3
|
import useNotice from './use-notice';
|
|
3
4
|
|
|
4
|
-
const NoticeContext = createContext<
|
|
5
|
+
const NoticeContext = createContext<NoticeApi | undefined>(undefined);
|
|
5
6
|
|
|
6
7
|
export function NoticeProvider({ children }: { children: React.ReactNode }) {
|
|
7
8
|
const { noticeApi, noticeHolders } = useNotice();
|
|
@@ -14,7 +15,7 @@ export function NoticeProvider({ children }: { children: React.ReactNode }) {
|
|
|
14
15
|
);
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
export function useGlobalNotice() {
|
|
18
|
+
export function useGlobalNotice(): NoticeApi {
|
|
18
19
|
const context = useContext(NoticeContext);
|
|
19
20
|
|
|
20
21
|
if (context === undefined) {
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
act,
|
|
5
|
+
cleanup,
|
|
6
|
+
fireEvent,
|
|
7
|
+
render,
|
|
8
|
+
renderHook,
|
|
9
|
+
screen,
|
|
10
|
+
} from '@testing-library/react';
|
|
11
|
+
import React from 'react';
|
|
12
|
+
import { renderToString } from 'react-dom/server';
|
|
13
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
14
|
+
import type { NoticeApi, NoticeClose } from './interface';
|
|
15
|
+
import { useGlobalNotice } from './notice-provider';
|
|
16
|
+
import useNotice from './use-notice';
|
|
17
|
+
import noticeWrapper, { notice } from './wrapper';
|
|
18
|
+
|
|
19
|
+
let currentApi: NoticeApi;
|
|
20
|
+
|
|
21
|
+
function NoticeHarness() {
|
|
22
|
+
const { noticeApi, noticeHolders } = useNotice();
|
|
23
|
+
currentApi = noticeApi;
|
|
24
|
+
return <>{noticeHolders}</>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
vi.unstubAllGlobals();
|
|
29
|
+
cleanup();
|
|
30
|
+
noticeWrapper.unmount();
|
|
31
|
+
vi.useRealTimers();
|
|
32
|
+
vi.restoreAllMocks();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('notice', () => {
|
|
36
|
+
it('renders safely without a document during SSR', () => {
|
|
37
|
+
vi.stubGlobal('document', undefined);
|
|
38
|
+
|
|
39
|
+
expect(() => renderToString(<NoticeHarness />)).not.toThrow();
|
|
40
|
+
expect(() => notice.info('server notice')).not.toThrow();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('keeps duration zero notices open', () => {
|
|
44
|
+
vi.useFakeTimers();
|
|
45
|
+
const onClose = vi.fn();
|
|
46
|
+
render(<NoticeHarness />);
|
|
47
|
+
|
|
48
|
+
act(() => {
|
|
49
|
+
currentApi.info({ content: 'persistent', duration: 0, onClose });
|
|
50
|
+
vi.advanceTimersByTime(10000);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
expect(screen.queryByText('persistent')).not.toBeNull();
|
|
54
|
+
expect(onClose).not.toHaveBeenCalled();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('keeps the hook API reference stable across updates', () => {
|
|
58
|
+
render(<NoticeHarness />);
|
|
59
|
+
const initialApi = currentApi;
|
|
60
|
+
|
|
61
|
+
act(() => {
|
|
62
|
+
currentApi.info({ content: 'stable', duration: 0 });
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
expect(currentApi).toBe(initialApi);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('keeps automatic keys independent from explicit numeric keys', () => {
|
|
69
|
+
render(<NoticeHarness />);
|
|
70
|
+
|
|
71
|
+
act(() => {
|
|
72
|
+
currentApi.info({
|
|
73
|
+
key: Number.POSITIVE_INFINITY,
|
|
74
|
+
content: 'explicit infinity',
|
|
75
|
+
duration: 0,
|
|
76
|
+
});
|
|
77
|
+
currentApi.info({ content: 'automatic one', duration: 0 });
|
|
78
|
+
currentApi.info({ content: 'automatic two', duration: 0 });
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
expect(screen.queryByText('explicit infinity')).not.toBeNull();
|
|
82
|
+
expect(screen.queryByText('automatic one')).not.toBeNull();
|
|
83
|
+
expect(screen.queryByText('automatic two')).not.toBeNull();
|
|
84
|
+
expect(document.querySelectorAll('.yee-notice')).toHaveLength(3);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('does not restart existing timers when another notice is added', () => {
|
|
88
|
+
vi.useFakeTimers();
|
|
89
|
+
const firstOnClose = vi.fn();
|
|
90
|
+
render(<NoticeHarness />);
|
|
91
|
+
|
|
92
|
+
act(() => {
|
|
93
|
+
currentApi.info({ content: 'first', duration: 300, onClose: firstOnClose });
|
|
94
|
+
});
|
|
95
|
+
act(() => vi.advanceTimersByTime(200));
|
|
96
|
+
act(() => {
|
|
97
|
+
currentApi.info({ content: 'second', duration: 300 });
|
|
98
|
+
});
|
|
99
|
+
act(() => vi.advanceTimersByTime(100));
|
|
100
|
+
|
|
101
|
+
expect(firstOnClose).toHaveBeenCalledTimes(1);
|
|
102
|
+
expect(screen.queryByText('first')).toBeNull();
|
|
103
|
+
expect(screen.queryByText('second')).not.toBeNull();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('restarts the timer when an existing key is updated', () => {
|
|
107
|
+
vi.useFakeTimers();
|
|
108
|
+
const onClose = vi.fn();
|
|
109
|
+
render(<NoticeHarness />);
|
|
110
|
+
|
|
111
|
+
act(() => {
|
|
112
|
+
currentApi.info({
|
|
113
|
+
key: 'timer-update',
|
|
114
|
+
content: 'working',
|
|
115
|
+
duration: 300,
|
|
116
|
+
onClose,
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
act(() => vi.advanceTimersByTime(200));
|
|
120
|
+
act(() => {
|
|
121
|
+
currentApi.success({
|
|
122
|
+
key: 'timer-update',
|
|
123
|
+
content: 'finished',
|
|
124
|
+
duration: 300,
|
|
125
|
+
onClose,
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
act(() => vi.advanceTimersByTime(100));
|
|
129
|
+
|
|
130
|
+
expect(screen.queryByText('finished')).not.toBeNull();
|
|
131
|
+
expect(onClose).not.toHaveBeenCalled();
|
|
132
|
+
|
|
133
|
+
act(() => vi.advanceTimersByTime(200));
|
|
134
|
+
expect(screen.queryByText('finished')).toBeNull();
|
|
135
|
+
expect(onClose).toHaveBeenCalledTimes(1);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('moves an existing key to its new placement', () => {
|
|
139
|
+
render(<NoticeHarness />);
|
|
140
|
+
|
|
141
|
+
act(() => {
|
|
142
|
+
currentApi.info({
|
|
143
|
+
key: 'moving',
|
|
144
|
+
content: 'before move',
|
|
145
|
+
placement: 'topLeft',
|
|
146
|
+
duration: 0,
|
|
147
|
+
});
|
|
148
|
+
currentApi.success({
|
|
149
|
+
key: 'moving',
|
|
150
|
+
content: 'after move',
|
|
151
|
+
placement: 'bottomRight',
|
|
152
|
+
duration: 0,
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
expect(screen.queryByText('before move')).toBeNull();
|
|
157
|
+
const moved = screen.getByText('after move').closest('.yee-notice');
|
|
158
|
+
expect(document.querySelectorAll('.yee-notice')).toHaveLength(1);
|
|
159
|
+
expect(moved?.getAttribute('data-placement')).toBe('bottomRight');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('keeps update close handles valid but invalidates them after key reuse', () => {
|
|
163
|
+
let staleClose!: NoticeClose;
|
|
164
|
+
render(<NoticeHarness />);
|
|
165
|
+
|
|
166
|
+
act(() => {
|
|
167
|
+
staleClose = currentApi.info({
|
|
168
|
+
key: 'reused',
|
|
169
|
+
content: 'first',
|
|
170
|
+
duration: 0,
|
|
171
|
+
});
|
|
172
|
+
currentApi.success({
|
|
173
|
+
key: 'reused',
|
|
174
|
+
content: 'updated',
|
|
175
|
+
duration: 0,
|
|
176
|
+
});
|
|
177
|
+
staleClose();
|
|
178
|
+
currentApi.info({
|
|
179
|
+
key: 'reused',
|
|
180
|
+
content: 'new generation',
|
|
181
|
+
duration: 0,
|
|
182
|
+
});
|
|
183
|
+
staleClose();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
expect(screen.queryByText('updated')).toBeNull();
|
|
187
|
+
expect(screen.queryByText('new generation')).not.toBeNull();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('calls onClose exactly once for clear and destroy', () => {
|
|
191
|
+
const firstOnClose = vi.fn();
|
|
192
|
+
const secondOnClose = vi.fn();
|
|
193
|
+
render(<NoticeHarness />);
|
|
194
|
+
|
|
195
|
+
act(() => {
|
|
196
|
+
currentApi.info({
|
|
197
|
+
key: 'first-close',
|
|
198
|
+
content: 'first',
|
|
199
|
+
duration: 0,
|
|
200
|
+
onClose: firstOnClose,
|
|
201
|
+
});
|
|
202
|
+
currentApi.info({
|
|
203
|
+
key: 'second-close',
|
|
204
|
+
content: 'second',
|
|
205
|
+
duration: 0,
|
|
206
|
+
onClose: secondOnClose,
|
|
207
|
+
});
|
|
208
|
+
currentApi.destroy('first-close');
|
|
209
|
+
currentApi.clear();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
expect(firstOnClose).toHaveBeenCalledTimes(1);
|
|
213
|
+
expect(secondOnClose).toHaveBeenCalledTimes(1);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('runs every hook clear callback even when one throws', () => {
|
|
217
|
+
const secondOnClose = vi.fn();
|
|
218
|
+
const reportError = vi.fn();
|
|
219
|
+
vi.stubGlobal('reportError', reportError);
|
|
220
|
+
render(<NoticeHarness />);
|
|
221
|
+
|
|
222
|
+
act(() => {
|
|
223
|
+
currentApi.info({
|
|
224
|
+
content: 'throwing hook callback',
|
|
225
|
+
duration: 0,
|
|
226
|
+
onClose: () => {
|
|
227
|
+
throw new Error('hook close failed');
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
currentApi.info({
|
|
231
|
+
content: 'later hook callback',
|
|
232
|
+
duration: 0,
|
|
233
|
+
onClose: secondOnClose,
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
act(() => currentApi.clear());
|
|
238
|
+
expect(secondOnClose).toHaveBeenCalledTimes(1);
|
|
239
|
+
expect(reportError).toHaveBeenCalledTimes(1);
|
|
240
|
+
expect(screen.queryByText('later hook callback')).toBeNull();
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('runs every global clear callback even when one throws', () => {
|
|
244
|
+
const secondOnClose = vi.fn();
|
|
245
|
+
const reportError = vi.fn();
|
|
246
|
+
vi.stubGlobal('reportError', reportError);
|
|
247
|
+
|
|
248
|
+
act(() => {
|
|
249
|
+
notice.info({
|
|
250
|
+
content: 'throwing global callback',
|
|
251
|
+
duration: 0,
|
|
252
|
+
onClose: () => {
|
|
253
|
+
throw new Error('global close failed');
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
notice.info({
|
|
257
|
+
content: 'later global callback',
|
|
258
|
+
duration: 0,
|
|
259
|
+
onClose: secondOnClose,
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
act(() => notice.clear());
|
|
264
|
+
expect(secondOnClose).toHaveBeenCalledTimes(1);
|
|
265
|
+
expect(reportError).toHaveBeenCalledTimes(1);
|
|
266
|
+
expect(screen.queryByText('later global callback')).toBeNull();
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('preserves hover pause while the notice is updated', () => {
|
|
270
|
+
vi.useFakeTimers();
|
|
271
|
+
const onClose = vi.fn();
|
|
272
|
+
render(<NoticeHarness />);
|
|
273
|
+
|
|
274
|
+
act(() => {
|
|
275
|
+
currentApi.info({
|
|
276
|
+
key: 'paused',
|
|
277
|
+
content: 'hover me',
|
|
278
|
+
duration: 300,
|
|
279
|
+
onClose,
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
act(() => vi.advanceTimersByTime(100));
|
|
283
|
+
fireEvent.mouseEnter(screen.getByText('hover me').closest('.yee-notice')!);
|
|
284
|
+
|
|
285
|
+
act(() => {
|
|
286
|
+
currentApi.info({
|
|
287
|
+
key: 'paused',
|
|
288
|
+
content: 'still paused',
|
|
289
|
+
duration: 300,
|
|
290
|
+
onClose,
|
|
291
|
+
});
|
|
292
|
+
vi.advanceTimersByTime(1000);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
expect(screen.queryByText('still paused')).not.toBeNull();
|
|
296
|
+
expect(onClose).not.toHaveBeenCalled();
|
|
297
|
+
|
|
298
|
+
fireEvent.mouseLeave(screen.getByText('still paused').closest('.yee-notice')!);
|
|
299
|
+
act(() => vi.advanceTimersByTime(300));
|
|
300
|
+
expect(onClose).toHaveBeenCalledTimes(1);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('resumes a paused countdown when pauseOnHover is disabled', () => {
|
|
304
|
+
vi.useFakeTimers();
|
|
305
|
+
const onClose = vi.fn();
|
|
306
|
+
render(<NoticeHarness />);
|
|
307
|
+
|
|
308
|
+
act(() => {
|
|
309
|
+
currentApi.info({
|
|
310
|
+
key: 'disable-pause',
|
|
311
|
+
content: 'pause enabled',
|
|
312
|
+
duration: 300,
|
|
313
|
+
pauseOnHover: true,
|
|
314
|
+
onClose,
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
fireEvent.mouseEnter(
|
|
318
|
+
screen.getByText('pause enabled').closest('.yee-notice')!,
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
act(() => {
|
|
322
|
+
currentApi.info({
|
|
323
|
+
key: 'disable-pause',
|
|
324
|
+
content: 'pause disabled',
|
|
325
|
+
duration: 300,
|
|
326
|
+
pauseOnHover: false,
|
|
327
|
+
onClose,
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
act(() => vi.advanceTimersByTime(300));
|
|
331
|
+
|
|
332
|
+
expect(screen.queryByText('pause disabled')).toBeNull();
|
|
333
|
+
expect(onClose).toHaveBeenCalledTimes(1);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it('pauses when pauseOnHover is enabled while already hovered', () => {
|
|
337
|
+
vi.useFakeTimers();
|
|
338
|
+
const onClose = vi.fn();
|
|
339
|
+
render(<NoticeHarness />);
|
|
340
|
+
|
|
341
|
+
act(() => {
|
|
342
|
+
currentApi.info({
|
|
343
|
+
key: 'enable-pause',
|
|
344
|
+
content: 'pause disabled',
|
|
345
|
+
duration: 300,
|
|
346
|
+
pauseOnHover: false,
|
|
347
|
+
onClose,
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
fireEvent.mouseEnter(
|
|
351
|
+
screen.getByText('pause disabled').closest('.yee-notice')!,
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
act(() => {
|
|
355
|
+
currentApi.info({
|
|
356
|
+
key: 'enable-pause',
|
|
357
|
+
content: 'pause enabled',
|
|
358
|
+
duration: 300,
|
|
359
|
+
pauseOnHover: true,
|
|
360
|
+
onClose,
|
|
361
|
+
});
|
|
362
|
+
vi.advanceTimersByTime(1000);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
expect(screen.queryByText('pause enabled')).not.toBeNull();
|
|
366
|
+
expect(onClose).not.toHaveBeenCalled();
|
|
367
|
+
|
|
368
|
+
fireEvent.mouseLeave(screen.getByText('pause enabled').closest('.yee-notice')!);
|
|
369
|
+
act(() => vi.advanceTimersByTime(300));
|
|
370
|
+
expect(onClose).toHaveBeenCalledTimes(1);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it('preserves falsy global keys and dispatches onClose on destroy', () => {
|
|
374
|
+
const onClose = vi.fn();
|
|
375
|
+
const config = { key: 0, content: 'zero', duration: 0, onClose };
|
|
376
|
+
|
|
377
|
+
act(() => {
|
|
378
|
+
notice.open(config);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
expect(config.key).toBe(0);
|
|
382
|
+
expect(screen.queryByText('zero')).not.toBeNull();
|
|
383
|
+
|
|
384
|
+
act(() => notice.destroy(0));
|
|
385
|
+
expect(screen.queryByText('zero')).toBeNull();
|
|
386
|
+
expect(onClose).toHaveBeenCalledTimes(1);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it('preserves generations when global clear callbacks reopen a key', () => {
|
|
390
|
+
let reopenedClose!: NoticeClose;
|
|
391
|
+
|
|
392
|
+
act(() => {
|
|
393
|
+
notice.info({
|
|
394
|
+
key: 'reopener',
|
|
395
|
+
content: 'reopener',
|
|
396
|
+
duration: 0,
|
|
397
|
+
onClose: () => {
|
|
398
|
+
reopenedClose = notice.info({
|
|
399
|
+
key: 'reopened-key',
|
|
400
|
+
content: 'reopened',
|
|
401
|
+
duration: 0,
|
|
402
|
+
});
|
|
403
|
+
},
|
|
404
|
+
});
|
|
405
|
+
notice.info({
|
|
406
|
+
key: 'reopened-key',
|
|
407
|
+
content: 'old generation',
|
|
408
|
+
duration: 0,
|
|
409
|
+
});
|
|
410
|
+
notice.clear();
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
expect(screen.queryByText('reopened')).not.toBeNull();
|
|
414
|
+
act(() => reopenedClose());
|
|
415
|
+
expect(screen.queryByText('reopened')).toBeNull();
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it('throws a clear error outside NoticeProvider', () => {
|
|
419
|
+
expect(() => renderHook(() => useGlobalNotice())).toThrow(
|
|
420
|
+
'useGlobalNotice must be used within NoticeProvider',
|
|
421
|
+
);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it('forwards data attributes and supports keyboard activation', () => {
|
|
425
|
+
const onClick = vi.fn();
|
|
426
|
+
render(<NoticeHarness />);
|
|
427
|
+
|
|
428
|
+
act(() => {
|
|
429
|
+
currentApi.error({
|
|
430
|
+
content: 'interactive',
|
|
431
|
+
duration: 0,
|
|
432
|
+
onClick,
|
|
433
|
+
'data-track-id': 'interactive-notice',
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
const element = screen.getByText('interactive').closest('.yee-notice')!;
|
|
438
|
+
expect(element.getAttribute('data-track-id')).toBe('interactive-notice');
|
|
439
|
+
expect(element.getAttribute('role')).toBe('button');
|
|
440
|
+
expect(element.getAttribute('aria-live')).toBe('assertive');
|
|
441
|
+
expect(element.getAttribute('tabindex')).toBe('0');
|
|
442
|
+
expect(element.querySelector('.yee-notice-icon')?.getAttribute('aria-hidden')).toBe(
|
|
443
|
+
'true',
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
fireEvent.keyDown(element, { key: 'Enter' });
|
|
447
|
+
fireEvent.keyDown(element, { key: ' ' });
|
|
448
|
+
expect(onClick).toHaveBeenCalledTimes(2);
|
|
449
|
+
});
|
|
450
|
+
});
|
package/src/notice/notice.tsx
CHANGED
|
@@ -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={
|
|
10
|
-
success: <CircleCheck size={
|
|
11
|
-
warning: <CircleAlert size={
|
|
12
|
-
error: <CircleX size={
|
|
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
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
<
|
|
104
|
+
<Button
|
|
105
|
+
variant='text'
|
|
106
|
+
size="small"
|
|
69
107
|
className={`${prefixCls}-close`}
|
|
70
108
|
onClick={handleClose}
|
|
71
|
-
|
|
72
|
-
aria-
|
|
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
|
-
{
|
|
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
|
-
|
|
91
|
-
|
|
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:
|
|
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;
|