@taicode/common-web 1.1.21 → 3.0.1
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/output/loading-button/index.d.ts +31 -13
- package/output/loading-button/index.d.ts.map +1 -1
- package/output/loading-button/index.js +50 -26
- package/output/loading-button/index.test.d.ts +2 -0
- package/output/loading-button/index.test.d.ts.map +1 -0
- package/output/loading-button/index.test.js +203 -0
- package/output/signin/index.d.ts.map +1 -1
- package/output/signin/index.js +27 -9
- package/output/signin/service.d.ts.map +1 -1
- package/output/signin/service.js +5 -2
- package/output/signin/service.test.js +6 -2
- package/output/toaster/index.d.ts +0 -1
- package/output/toaster/index.d.ts.map +1 -1
- package/output/toaster/index.js +6 -7
- package/package.json +20 -14
|
@@ -1,26 +1,44 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { type ButtonProps } from '../catalyst/button';
|
|
1
|
+
import React, { ComponentPropsWithoutRef, ElementType } from 'react';
|
|
3
2
|
/**
|
|
4
|
-
*
|
|
5
|
-
* 继承 Button 的所有属性,自动处理 onClick 的异步状态
|
|
3
|
+
* 多态组件的 Props 类型
|
|
6
4
|
*/
|
|
7
|
-
|
|
5
|
+
type PolymorphicProps<T extends ElementType> = {
|
|
6
|
+
as?: T;
|
|
7
|
+
loading?: boolean;
|
|
8
|
+
loadingText?: string;
|
|
9
|
+
loadingIcon?: React.ReactNode;
|
|
10
|
+
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void | Promise<void>;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
children?: React.ReactNode;
|
|
13
|
+
} & Omit<ComponentPropsWithoutRef<T>, 'as' | 'loading' | 'loadingText' | 'loadingIcon' | 'onClick'>;
|
|
8
14
|
/**
|
|
9
15
|
* LoadingButton 组件
|
|
10
16
|
*
|
|
11
|
-
*
|
|
17
|
+
* 支持任意组件作为基础按钮,在异步操作时自动显示 loading 状态
|
|
12
18
|
*
|
|
13
19
|
* @example
|
|
14
20
|
* ```tsx
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
+
* // 使用默认 button
|
|
22
|
+
* <LoadingButton onClick={async () => await save()}>
|
|
23
|
+
* 保存
|
|
24
|
+
* </LoadingButton>
|
|
25
|
+
*
|
|
26
|
+
* // 使用自定义组件
|
|
27
|
+
* <LoadingButton as={MyButton} onClick={async () => await save()}>
|
|
28
|
+
* 保存
|
|
29
|
+
* </LoadingButton>
|
|
30
|
+
*
|
|
31
|
+
* // 自定义 loading 图标
|
|
32
|
+
* <LoadingButton loadingIcon={<MySpinner />} onClick={async () => await save()}>
|
|
33
|
+
* 保存
|
|
34
|
+
* </LoadingButton>
|
|
35
|
+
*
|
|
36
|
+
* // 手动控制 loading 状态
|
|
37
|
+
* <LoadingButton loading={isLoading}>
|
|
21
38
|
* 保存
|
|
22
39
|
* </LoadingButton>
|
|
23
40
|
* ```
|
|
24
41
|
*/
|
|
25
|
-
export declare const LoadingButton: React.ForwardRefExoticComponent<
|
|
42
|
+
export declare const LoadingButton: React.ForwardRefExoticComponent<Omit<PolymorphicProps<React.ElementType>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
|
|
43
|
+
export {};
|
|
26
44
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../source/loading-button/index.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../source/loading-button/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAqC,wBAAwB,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAkCvG;;GAEG;AACH,KAAK,gBAAgB,CAAC,CAAC,SAAS,WAAW,IAAI;IAC7C,EAAE,CAAC,EAAE,CAAC,CAAA;IACN,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC7B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,iBAAiB,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9E,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC3B,GAAG,IAAI,CAAC,wBAAwB,CAAC,CAAC,CAAC,EAAE,IAAI,GAAG,SAAS,GAAG,aAAa,GAAG,aAAa,GAAG,SAAS,CAAC,CAAA;AAEnG;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,aAAa,4HAsEzB,CAAA"}
|
|
@@ -9,47 +9,71 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
9
9
|
}
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
|
-
import {
|
|
14
|
-
import { Button } from '../catalyst/button';
|
|
15
|
-
import { catchIt } from '@taicode/common-base';
|
|
12
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
13
|
+
import { useState, useCallback, forwardRef } from 'react';
|
|
16
14
|
function isPromiseLike(value) {
|
|
17
|
-
return value != null && typeof value.then === 'function';
|
|
15
|
+
return value != null && typeof value === 'object' && 'then' in value && typeof value.then === 'function';
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Loading 指示器组件
|
|
19
|
+
*/
|
|
20
|
+
function LoadingSpinner({ className = '' }) {
|
|
21
|
+
return (_jsxs("svg", { className: `animate-spin ${className}`, xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [_jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), _jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })] }));
|
|
18
22
|
}
|
|
19
23
|
/**
|
|
20
24
|
* LoadingButton 组件
|
|
21
25
|
*
|
|
22
|
-
*
|
|
26
|
+
* 支持任意组件作为基础按钮,在异步操作时自动显示 loading 状态
|
|
23
27
|
*
|
|
24
28
|
* @example
|
|
25
29
|
* ```tsx
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
30
|
+
* // 使用默认 button
|
|
31
|
+
* <LoadingButton onClick={async () => await save()}>
|
|
32
|
+
* 保存
|
|
33
|
+
* </LoadingButton>
|
|
34
|
+
*
|
|
35
|
+
* // 使用自定义组件
|
|
36
|
+
* <LoadingButton as={MyButton} onClick={async () => await save()}>
|
|
37
|
+
* 保存
|
|
38
|
+
* </LoadingButton>
|
|
39
|
+
*
|
|
40
|
+
* // 自定义 loading 图标
|
|
41
|
+
* <LoadingButton loadingIcon={<MySpinner />} onClick={async () => await save()}>
|
|
42
|
+
* 保存
|
|
43
|
+
* </LoadingButton>
|
|
44
|
+
*
|
|
45
|
+
* // 手动控制 loading 状态
|
|
46
|
+
* <LoadingButton loading={isLoading}>
|
|
32
47
|
* 保存
|
|
33
48
|
* </LoadingButton>
|
|
34
49
|
* ```
|
|
35
50
|
*/
|
|
36
51
|
export const LoadingButton = forwardRef(function LoadingButton(_a, ref) {
|
|
37
|
-
var {
|
|
38
|
-
const [
|
|
39
|
-
const
|
|
40
|
-
|
|
52
|
+
var { as, loading: externalLoading, loadingText, loadingIcon, onClick, disabled, children, className = '' } = _a, props = __rest(_a, ["as", "loading", "loadingText", "loadingIcon", "onClick", "disabled", "children", "className"]);
|
|
53
|
+
const [internalLoading, setInternalLoading] = useState(false);
|
|
54
|
+
const loading = externalLoading !== null && externalLoading !== void 0 ? externalLoading : internalLoading;
|
|
55
|
+
const handleClick = useCallback((event) => {
|
|
56
|
+
if (!onClick || loading || disabled)
|
|
41
57
|
return;
|
|
42
58
|
const result = onClick(event);
|
|
43
|
-
//
|
|
59
|
+
// 如果返回 Promise-like,自动管理 loading 状态,但不捕获异常
|
|
60
|
+
// 使用 then 而不是 finally,因为 PromiseLike 不保证有 finally 方法
|
|
61
|
+
// 在 rejected 回调中重新抛出,让异常传播到全局错误处理机制(如 Toaster)
|
|
44
62
|
if (isPromiseLike(result)) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
throw catchResult.error;
|
|
51
|
-
}
|
|
63
|
+
setInternalLoading(true);
|
|
64
|
+
result.then(() => setInternalLoading(false), (error) => {
|
|
65
|
+
setInternalLoading(false);
|
|
66
|
+
throw error;
|
|
67
|
+
});
|
|
52
68
|
}
|
|
53
|
-
};
|
|
54
|
-
|
|
69
|
+
}, [onClick, loading, disabled]);
|
|
70
|
+
// 使用 as prop 或默认 button
|
|
71
|
+
const Component = as || 'button';
|
|
72
|
+
// 默认 button 样式(仅当使用默认 button 时应用)
|
|
73
|
+
const defaultButtonClass = !as
|
|
74
|
+
? 'inline-flex items-center justify-center gap-2 rounded-[var(--radius)] bg-[var(--primary)] px-4 py-2 text-sm font-medium text-[var(--primary-foreground)] shadow-sm transition-opacity hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-[var(--ring)] focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed'
|
|
75
|
+
: '';
|
|
76
|
+
const finalClassName = `${defaultButtonClass} ${className}`.trim();
|
|
77
|
+
return (_jsx(Component, Object.assign({ ref: ref, onClick: handleClick, disabled: disabled || loading, className: finalClassName }, props, { children: loading ? (_jsxs(_Fragment, { children: [loadingIcon || _jsx(LoadingSpinner, { className: "size-4" }), loadingText || children] })) : (children) })));
|
|
55
78
|
});
|
|
79
|
+
LoadingButton.displayName = 'LoadingButton';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../source/loading-button/index.test.tsx"],"names":[],"mappings":"AAAA,OAAO,kCAAkC,CAAA"}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
13
|
+
import '@testing-library/jest-dom/vitest';
|
|
14
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
15
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
16
|
+
import { LoadingButton } from './index';
|
|
17
|
+
describe('LoadingButton', () => {
|
|
18
|
+
describe('基础功能', () => {
|
|
19
|
+
it('应该渲染按钮和子元素', () => {
|
|
20
|
+
render(_jsx(LoadingButton, { children: "\u70B9\u51FB\u6211" }));
|
|
21
|
+
expect(screen.getByRole('button')).toHaveTextContent('点击我');
|
|
22
|
+
});
|
|
23
|
+
it('应该支持 disabled 属性', () => {
|
|
24
|
+
render(_jsx(LoadingButton, { disabled: true, children: "\u70B9\u51FB\u6211" }));
|
|
25
|
+
expect(screen.getByRole('button')).toBeDisabled();
|
|
26
|
+
});
|
|
27
|
+
it('应该应用自定义 className', () => {
|
|
28
|
+
render(_jsx(LoadingButton, { className: "custom-class", children: "\u70B9\u51FB\u6211" }));
|
|
29
|
+
expect(screen.getByRole('button')).toHaveClass('custom-class');
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
describe('同步点击', () => {
|
|
33
|
+
it('应该处理同步点击事件', () => {
|
|
34
|
+
const handleClick = vi.fn();
|
|
35
|
+
render(_jsx(LoadingButton, { onClick: handleClick, children: "\u70B9\u51FB\u6211" }));
|
|
36
|
+
fireEvent.click(screen.getByRole('button'));
|
|
37
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
38
|
+
});
|
|
39
|
+
it('同步操作不应该显示 loading', () => {
|
|
40
|
+
const handleClick = vi.fn();
|
|
41
|
+
render(_jsx(LoadingButton, { onClick: handleClick, children: "\u70B9\u51FB\u6211" }));
|
|
42
|
+
fireEvent.click(screen.getByRole('button'));
|
|
43
|
+
expect(screen.queryByRole('button')).toHaveTextContent('点击我');
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
describe('异步点击', () => {
|
|
47
|
+
it('应该在异步操作期间显示 loading', async () => {
|
|
48
|
+
const handleClick = vi.fn(async () => {
|
|
49
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
50
|
+
});
|
|
51
|
+
render(_jsx(LoadingButton, { children: "\u4FDD\u5B58" }));
|
|
52
|
+
const button = screen.getByRole('button');
|
|
53
|
+
fireEvent.click(button);
|
|
54
|
+
// 应该立即显示 loading
|
|
55
|
+
await waitFor(() => {
|
|
56
|
+
expect(button).toBeDisabled();
|
|
57
|
+
});
|
|
58
|
+
// 等待异步操作完成
|
|
59
|
+
await waitFor(() => {
|
|
60
|
+
expect(button).not.toBeDisabled();
|
|
61
|
+
}, { timeout: 200 });
|
|
62
|
+
});
|
|
63
|
+
it('异步操作完成后应该恢复正常状态', async () => {
|
|
64
|
+
let resolve;
|
|
65
|
+
const promise = new Promise(r => { resolve = r; });
|
|
66
|
+
const handleClick = vi.fn(() => promise);
|
|
67
|
+
render(_jsx(LoadingButton, { onClick: handleClick, children: "\u4FDD\u5B58" }));
|
|
68
|
+
const button = screen.getByRole('button');
|
|
69
|
+
fireEvent.click(button);
|
|
70
|
+
// loading 状态
|
|
71
|
+
await waitFor(() => {
|
|
72
|
+
expect(button).toBeDisabled();
|
|
73
|
+
});
|
|
74
|
+
// 完成异步操作
|
|
75
|
+
resolve();
|
|
76
|
+
// 恢复正常
|
|
77
|
+
await waitFor(() => {
|
|
78
|
+
expect(button).not.toBeDisabled();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
it('异步操作失败后应该恢复正常状态', async () => {
|
|
82
|
+
const handleClick = vi.fn(async () => {
|
|
83
|
+
await new Promise((_, reject) => setTimeout(() => reject(new Error('失败')), 50));
|
|
84
|
+
});
|
|
85
|
+
render(_jsx(LoadingButton, { onClick: handleClick, children: "\u4FDD\u5B58" }));
|
|
86
|
+
const button = screen.getByRole('button');
|
|
87
|
+
fireEvent.click(button);
|
|
88
|
+
// loading 状态
|
|
89
|
+
await waitFor(() => {
|
|
90
|
+
expect(button).toBeDisabled();
|
|
91
|
+
});
|
|
92
|
+
// 等待失败并恢复
|
|
93
|
+
await waitFor(() => {
|
|
94
|
+
expect(button).not.toBeDisabled();
|
|
95
|
+
}, { timeout: 100 });
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
describe('手动控制 loading', () => {
|
|
99
|
+
it('应该支持外部控制 loading 状态', () => {
|
|
100
|
+
const { rerender } = render(_jsx(LoadingButton, { loading: false, children: "\u4FDD\u5B58" }));
|
|
101
|
+
expect(screen.getByRole('button')).not.toBeDisabled();
|
|
102
|
+
rerender(_jsx(LoadingButton, { loading: true, children: "\u4FDD\u5B58" }));
|
|
103
|
+
expect(screen.getByRole('button')).toBeDisabled();
|
|
104
|
+
});
|
|
105
|
+
it('外部 loading 状态应该优先于内部状态', async () => {
|
|
106
|
+
const handleClick = vi.fn(async () => {
|
|
107
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
108
|
+
});
|
|
109
|
+
render(_jsx(LoadingButton, { loading: true, onClick: handleClick, children: "\u4FDD\u5B58" }));
|
|
110
|
+
const button = screen.getByRole('button');
|
|
111
|
+
// 外部 loading 为 true,应该禁用
|
|
112
|
+
expect(button).toBeDisabled();
|
|
113
|
+
// 点击不应该触发
|
|
114
|
+
fireEvent.click(button);
|
|
115
|
+
expect(handleClick).not.toHaveBeenCalled();
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
describe('loading 显示定制', () => {
|
|
119
|
+
it('应该显示自定义 loadingText', async () => {
|
|
120
|
+
const handleClick = vi.fn(async () => {
|
|
121
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
122
|
+
});
|
|
123
|
+
render(_jsx(LoadingButton, { loadingText: "\u5904\u7406\u4E2D...", onClick: handleClick, children: "\u4FDD\u5B58" }));
|
|
124
|
+
fireEvent.click(screen.getByRole('button'));
|
|
125
|
+
await waitFor(() => {
|
|
126
|
+
expect(screen.getByText('处理中...')).toBeInTheDocument();
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
it('应该显示自定义 loadingIcon', async () => {
|
|
130
|
+
const handleClick = vi.fn(async () => {
|
|
131
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
132
|
+
});
|
|
133
|
+
render(_jsx(LoadingButton, { loadingIcon: _jsx("span", { "data-testid": "custom-icon", children: "\u23F3" }), onClick: handleClick, children: "\u4FDD\u5B58" }));
|
|
134
|
+
fireEvent.click(screen.getByRole('button'));
|
|
135
|
+
await waitFor(() => {
|
|
136
|
+
expect(screen.getByTestId('custom-icon')).toBeInTheDocument();
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
describe('多态组件 (as prop)', () => {
|
|
141
|
+
it('应该渲染为自定义组件', () => {
|
|
142
|
+
const CustomButton = (_a) => {
|
|
143
|
+
var { children } = _a, props = __rest(_a, ["children"]);
|
|
144
|
+
return (_jsx("button", Object.assign({ "data-testid": "custom-button" }, props, { children: children })));
|
|
145
|
+
};
|
|
146
|
+
render(_jsx(LoadingButton, { as: CustomButton, children: "\u70B9\u51FB\u6211" }));
|
|
147
|
+
expect(screen.getByTestId('custom-button')).toBeInTheDocument();
|
|
148
|
+
});
|
|
149
|
+
it('应该渲染为 anchor 标签', () => {
|
|
150
|
+
render(_jsx(LoadingButton, { as: "a", href: "#", children: "\u94FE\u63A5" }));
|
|
151
|
+
const link = screen.getByText('链接');
|
|
152
|
+
expect(link.tagName).toBe('A');
|
|
153
|
+
});
|
|
154
|
+
it('自定义组件应该保留其 props', () => {
|
|
155
|
+
render(_jsx(LoadingButton, { as: "a", href: "https://example.com", target: "_blank", children: "\u94FE\u63A5" }));
|
|
156
|
+
const link = screen.getByText('链接');
|
|
157
|
+
expect(link).toHaveAttribute('href', 'https://example.com');
|
|
158
|
+
expect(link).toHaveAttribute('target', '_blank');
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
describe('禁用状态', () => {
|
|
162
|
+
it('禁用时不应该触发点击', () => {
|
|
163
|
+
const handleClick = vi.fn();
|
|
164
|
+
render(_jsx(LoadingButton, { disabled: true, onClick: handleClick, children: "\u70B9\u51FB\u6211" }));
|
|
165
|
+
fireEvent.click(screen.getByRole('button'));
|
|
166
|
+
expect(handleClick).not.toHaveBeenCalled();
|
|
167
|
+
});
|
|
168
|
+
it('loading 时不应该触发点击', async () => {
|
|
169
|
+
const handleClick = vi.fn(async () => {
|
|
170
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
171
|
+
});
|
|
172
|
+
render(_jsx(LoadingButton, { onClick: handleClick, children: "\u4FDD\u5B58" }));
|
|
173
|
+
const button = screen.getByRole('button');
|
|
174
|
+
// 第一次点击
|
|
175
|
+
fireEvent.click(button);
|
|
176
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
177
|
+
// loading 期间再次点击
|
|
178
|
+
fireEvent.click(button);
|
|
179
|
+
fireEvent.click(button);
|
|
180
|
+
// 不应该再次触发
|
|
181
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
182
|
+
// 等待完成
|
|
183
|
+
await waitFor(() => {
|
|
184
|
+
expect(button).not.toBeDisabled();
|
|
185
|
+
}, { timeout: 150 });
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
describe('默认样式', () => {
|
|
189
|
+
it('使用默认 button 时应该应用默认样式', () => {
|
|
190
|
+
render(_jsx(LoadingButton, { children: "\u70B9\u51FB\u6211" }));
|
|
191
|
+
const button = screen.getByRole('button');
|
|
192
|
+
// 检查是否包含默认样式类
|
|
193
|
+
expect(button.className).toContain('inline-flex');
|
|
194
|
+
expect(button.className).toContain('bg-primary');
|
|
195
|
+
});
|
|
196
|
+
it('使用 as prop 时不应该应用默认样式', () => {
|
|
197
|
+
render(_jsx(LoadingButton, { as: "a", children: "\u94FE\u63A5" }));
|
|
198
|
+
const link = screen.getByText('链接');
|
|
199
|
+
// 不应该包含默认 button 样式
|
|
200
|
+
expect(link.className).not.toContain('bg-primary');
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../source/signin/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../source/signin/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAYzB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAIxC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAGrC,YAAY,EACV,SAAS,EACT,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,SAAS,CAAA;AAEhB;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,cAAc;IACd,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa;IACb,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa;IACb,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa;IACb,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,cAAc;IACd,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,cAAc;IACd,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iBAAiB;IACjB,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,mBAAmB;IACnB,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,aAAa;IACb,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,sBAAsB;IACtB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kBAAkB;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,eAAe;IACf,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B,cAAc;IACd,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,mBAAmB;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,eAAe;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,aAAa;IACb,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAwWD;;;GAGG;AACH,UAAU,yBAAyB;IACjC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,yBAAyB;IACzB,IAAI,EAAE,SAAS,CAAA;IACf,mBAAmB;IACnB,KAAK,CAAC,EAAE,iBAAiB,CAAA;CAC1B;AAED,eAAO,MAAM,oBAAoB,WAAoB,yBAAyB;;CAmC5E,CAAA"}
|
package/output/signin/index.js
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
1
12
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
13
|
import React from 'react';
|
|
3
14
|
import { observer } from 'mobx-react-lite';
|
|
4
15
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
16
|
+
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
5
17
|
import { CheckCircleIcon, XCircleIcon, ExclamationTriangleIcon, ClockIcon } from '@heroicons/react/24/outline';
|
|
6
|
-
import { Dialog, DialogBody } from '../catalyst/dialog';
|
|
7
|
-
import { Button } from '../catalyst/button';
|
|
8
18
|
import { SigninService } from './service';
|
|
9
19
|
import { SigninContext } from './context';
|
|
10
20
|
import { ServiceProvider } from '../service';
|
|
@@ -31,6 +41,14 @@ const defaultTexts = {
|
|
|
31
41
|
cancelButton: '取消',
|
|
32
42
|
closeButton: '关闭'
|
|
33
43
|
};
|
|
44
|
+
const Button = (_a) => {
|
|
45
|
+
var { variant = 'primary', className = '', children } = _a, props = __rest(_a, ["variant", "className", "children"]);
|
|
46
|
+
const baseStyles = 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-[var(--radius)] text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)] focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 h-10 px-4 py-2';
|
|
47
|
+
const variantStyles = variant === 'primary'
|
|
48
|
+
? 'bg-[var(--primary)] text-[var(--primary-foreground)] hover:opacity-90'
|
|
49
|
+
: 'border border-[var(--input)] bg-[var(--background)] hover:bg-[var(--accent)] hover:text-[var(--accent-foreground)]';
|
|
50
|
+
return (_jsx("button", Object.assign({ className: `${baseStyles} ${variantStyles} ${className}` }, props, { children: children })));
|
|
51
|
+
};
|
|
34
52
|
const SigninDialog = observer((props) => {
|
|
35
53
|
const { service, texts } = props;
|
|
36
54
|
// 禁止关闭弹窗,防止用户误操作
|
|
@@ -70,28 +88,28 @@ const SigninDialog = observer((props) => {
|
|
|
70
88
|
const OpeningState = () => (_jsxs(motion.div, { initial: { opacity: 0, scale: 0.9 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: 0.9 }, transition: { duration: 0.2 }, className: "flex flex-col items-center gap-4", children: [_jsxs("div", { className: "relative flex items-center justify-center mb-8", children: [_jsx(motion.div, { className: "absolute size-16 rounded-full border-4 border-transparent border-t-emerald-600 border-r-emerald-600", animate: { rotate: 360 }, transition: { duration: 1, repeat: Infinity, ease: 'linear' } }), _jsx(motion.div, { className: "absolute size-12 rounded-full border-4 border-transparent border-b-emerald-400 border-l-emerald-400", animate: { rotate: -360 }, transition: { duration: 1.5, repeat: Infinity, ease: 'linear' } }), _jsx(motion.div, { className: "size-3 rounded-full bg-emerald-600", animate: {
|
|
71
89
|
scale: [1, 1.2, 1],
|
|
72
90
|
opacity: [1, 0.7, 1]
|
|
73
|
-
}, transition: { duration: 1, repeat: Infinity } })] }), _jsx(motion.p, { initial: { opacity: 0 }, animate: { opacity: 1 }, transition: { delay: 0.1 }, className: "text-base font-semibold text-
|
|
91
|
+
}, transition: { duration: 1, repeat: Infinity } })] }), _jsx(motion.p, { initial: { opacity: 0 }, animate: { opacity: 1 }, transition: { delay: 0.1 }, className: "text-base font-semibold text-[var(--foreground)]", children: texts.openingDescription }), _jsxs(motion.div, { initial: { opacity: 0, y: 10 }, animate: { opacity: 1, y: 0 }, transition: { delay: 0.2 }, className: "mt-2 flex gap-2", children: [_jsx(Button, { variant: "primary", onClick: handleReopenWindow, children: texts.reopenButton }), _jsx(Button, { variant: "outline", onClick: handleCancel, children: texts.cancelButton })] })] }, "opening"));
|
|
74
92
|
/**
|
|
75
93
|
* Waiting 状态 - 等待用户登录
|
|
76
94
|
*/
|
|
77
|
-
const WaitingState = () => (_jsxs(motion.div, { initial: { opacity: 0, scale: 0.9 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: 0.9 }, transition: { duration: 0.2 }, className: "flex flex-col items-center gap-4", children: [_jsxs("div", { className: "relative mb-2", children: [_jsx(motion.div, { className: "size-16 rounded-full border-4 border-emerald-200 dark:border-emerald-900", animate: { scale: [1, 1.1, 1] }, transition: { duration: 2, repeat: Infinity } }), _jsx(motion.div, { className: "absolute inset-0 size-16 rounded-full border-4 border-emerald-400", animate: { scale: [1, 1.3, 1], opacity: [0.75, 0, 0.75] }, transition: { duration: 2, repeat: Infinity } }), _jsx(motion.svg, { className: "absolute inset-0 m-auto size-8 text-emerald-600", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", initial: { scale: 0 }, animate: { scale: 1 }, transition: { delay: 0.1 }, children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" }) })] }), _jsx("p", { className: "text-base font-semibold text-
|
|
95
|
+
const WaitingState = () => (_jsxs(motion.div, { initial: { opacity: 0, scale: 0.9 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: 0.9 }, transition: { duration: 0.2 }, className: "flex flex-col items-center gap-4", children: [_jsxs("div", { className: "relative mb-2", children: [_jsx(motion.div, { className: "size-16 rounded-full border-4 border-emerald-200 dark:border-emerald-900", animate: { scale: [1, 1.1, 1] }, transition: { duration: 2, repeat: Infinity } }), _jsx(motion.div, { className: "absolute inset-0 size-16 rounded-full border-4 border-emerald-400", animate: { scale: [1, 1.3, 1], opacity: [0.75, 0, 0.75] }, transition: { duration: 2, repeat: Infinity } }), _jsx(motion.svg, { className: "absolute inset-0 m-auto size-8 text-emerald-600", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", initial: { scale: 0 }, animate: { scale: 1 }, transition: { delay: 0.1 }, children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" }) })] }), _jsx("p", { className: "text-base font-semibold text-[var(--foreground)]", children: texts.waitingDescription }), _jsx("p", { className: "text-xs text-[var(--muted-foreground)]", children: texts.waitingHint }), _jsxs("div", { className: "mt-2 flex gap-2", children: [_jsx(Button, { variant: "primary", onClick: handleReopenWindow, children: texts.reopenButton }), _jsx(Button, { variant: "outline", onClick: handleCancel, children: texts.cancelButton })] })] }, "waiting"));
|
|
78
96
|
/**
|
|
79
97
|
* Success 状态 - 登录成功
|
|
80
98
|
*/
|
|
81
|
-
const SuccessState = () => (_jsxs(motion.div, { initial: { opacity: 0, scale: 0.5 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: 0.5 }, transition: { duration: 0.3, type: 'spring' }, className: "flex flex-col items-center gap-4", children: [_jsx(motion.div, { className: "mb-2", initial: { scale: 0 }, animate: { scale: 1 }, transition: { delay: 0.1, type: 'spring', stiffness: 200 }, children: _jsx(CheckCircleIcon, { className: "size-16 text-emerald-600 dark:text-emerald-400" }) }), _jsx(motion.p, { initial: { opacity: 0, y: 10 }, animate: { opacity: 1, y: 0 }, transition: { delay: 0.2 }, className: "text-base font-semibold text-
|
|
99
|
+
const SuccessState = () => (_jsxs(motion.div, { initial: { opacity: 0, scale: 0.5 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: 0.5 }, transition: { duration: 0.3, type: 'spring' }, className: "flex flex-col items-center gap-4", children: [_jsx(motion.div, { className: "mb-2", initial: { scale: 0 }, animate: { scale: 1 }, transition: { delay: 0.1, type: 'spring', stiffness: 200 }, children: _jsx(CheckCircleIcon, { className: "size-16 text-emerald-600 dark:text-emerald-400" }) }), _jsx(motion.p, { initial: { opacity: 0, y: 10 }, animate: { opacity: 1, y: 0 }, transition: { delay: 0.2 }, className: "text-base font-semibold text-[var(--primary)]", children: texts.successDescription })] }, "success"));
|
|
82
100
|
/**
|
|
83
101
|
* Error 状态 - 登录失败
|
|
84
102
|
*/
|
|
85
|
-
const ErrorState = () => (_jsxs(motion.div, { initial: { opacity: 0, scale: 0.9 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: 0.9 }, transition: { duration: 0.2 }, className: "flex flex-col items-center gap-4", children: [_jsx(motion.div, { className: "mb-2", initial: { scale: 0 }, animate: { scale: 1, rotate: [0, -10, 10, -10, 0] }, transition: { scale: { delay: 0.1 }, rotate: { delay: 0.2, duration: 0.5 } }, children: _jsx(XCircleIcon, { className: "size-16 text-red-600 dark:text-red-400" }) }), _jsx("p", { className: "text-base font-semibold text-
|
|
103
|
+
const ErrorState = () => (_jsxs(motion.div, { initial: { opacity: 0, scale: 0.9 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: 0.9 }, transition: { duration: 0.2 }, className: "flex flex-col items-center gap-4", children: [_jsx(motion.div, { className: "mb-2", initial: { scale: 0 }, animate: { scale: 1, rotate: [0, -10, 10, -10, 0] }, transition: { scale: { delay: 0.1 }, rotate: { delay: 0.2, duration: 0.5 } }, children: _jsx(XCircleIcon, { className: "size-16 text-red-600 dark:text-red-400" }) }), _jsx("p", { className: "text-base font-semibold text-[var(--destructive)]", children: service.errorMessage || texts.errorDescription }), _jsxs("div", { className: "mt-2 flex gap-2", children: [_jsx(Button, { variant: "primary", onClick: handleRetry, children: texts.retryButton }), _jsx(Button, { variant: "outline", onClick: handleClose, children: texts.closeButton })] })] }, "error"));
|
|
86
104
|
/**
|
|
87
105
|
* Cancelled 状态 - 用户取消登录
|
|
88
106
|
*/
|
|
89
|
-
const CancelledState = () => (_jsxs(motion.div, { initial: { opacity: 0, scale: 0.9 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: 0.9 }, transition: { duration: 0.2 }, className: "flex flex-col items-center gap-4", children: [_jsx(motion.div, { className: "mb-2", initial: { scale: 0 }, animate: { scale: 1 }, transition: { delay: 0.1, type: 'spring' }, children: _jsx(ExclamationTriangleIcon, { className: "size-16 text-amber-600 dark:text-amber-400" }) }), _jsx("p", { className: "text-base font-semibold text-
|
|
107
|
+
const CancelledState = () => (_jsxs(motion.div, { initial: { opacity: 0, scale: 0.9 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: 0.9 }, transition: { duration: 0.2 }, className: "flex flex-col items-center gap-4", children: [_jsx(motion.div, { className: "mb-2", initial: { scale: 0 }, animate: { scale: 1 }, transition: { delay: 0.1, type: 'spring' }, children: _jsx(ExclamationTriangleIcon, { className: "size-16 text-amber-600 dark:text-amber-400" }) }), _jsx("p", { className: "text-base font-semibold text-[var(--muted-foreground)]", children: texts.cancelledDescription }), _jsxs("div", { className: "mt-2 flex gap-2", children: [_jsx(Button, { variant: "primary", onClick: handleRetry, children: texts.retryButton }), _jsx(Button, { variant: "outline", onClick: handleClose, children: texts.closeButton })] })] }, "cancelled"));
|
|
90
108
|
/**
|
|
91
109
|
* Expired 状态 - 票据过期
|
|
92
110
|
*/
|
|
93
|
-
const ExpiredState = () => (_jsxs(motion.div, { initial: { opacity: 0, scale: 0.9 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: 0.9 }, transition: { duration: 0.2 }, className: "flex flex-col items-center gap-4", children: [_jsx(motion.div, { className: "mb-2", initial: { scale: 0 }, animate: { scale: 1 }, transition: { delay: 0.1, type: 'spring' }, children: _jsx(ClockIcon, { className: "size-16 text-orange-600 dark:text-orange-400" }) }), _jsx("p", { className: "text-base font-semibold text-
|
|
94
|
-
return (_jsx(
|
|
111
|
+
const ExpiredState = () => (_jsxs(motion.div, { initial: { opacity: 0, scale: 0.9 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: 0.9 }, transition: { duration: 0.2 }, className: "flex flex-col items-center gap-4", children: [_jsx(motion.div, { className: "mb-2", initial: { scale: 0 }, animate: { scale: 1 }, transition: { delay: 0.1, type: 'spring' }, children: _jsx(ClockIcon, { className: "size-16 text-orange-600 dark:text-orange-400" }) }), _jsx("p", { className: "text-base font-semibold text-[var(--muted-foreground)]", children: texts.expiredDescription }), _jsxs("div", { className: "mt-2 flex gap-2", children: [_jsx(Button, { variant: "primary", onClick: handleRetry, children: texts.retryButton }), _jsx(Button, { variant: "outline", onClick: handleClose, children: texts.closeButton })] })] }, "expired"));
|
|
112
|
+
return (_jsx(DialogPrimitive.Root, { open: service.isDialogOpen, onOpenChange: (open) => !open && preventClose(), children: _jsxs(DialogPrimitive.Portal, { children: [_jsx(DialogPrimitive.Overlay, { className: "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0" }), _jsx(DialogPrimitive.Content, { className: "fixed left-[50%] top-[50%] z-50 w-full max-w-md translate-x-[-50%] translate-y-[-50%] border border-[var(--border)] bg-[var(--background)] p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] rounded-[var(--radius)]", children: _jsx("div", { className: "flex flex-col items-center justify-center py-8", children: _jsxs(AnimatePresence, { mode: "wait", children: [service.dialogStatus === 'opening' && _jsx(OpeningState, {}), service.dialogStatus === 'waiting' && _jsx(WaitingState, {}), service.dialogStatus === 'success' && _jsx(SuccessState, {}), service.dialogStatus === 'error' && _jsx(ErrorState, {}), service.dialogStatus === 'cancelled' && _jsx(CancelledState, {}), service.dialogStatus === 'expired' && _jsx(ExpiredState, {})] }) }) })] }) }));
|
|
95
113
|
});
|
|
96
114
|
export const SigninDialogProvider = observer((props) => {
|
|
97
115
|
const { children, apis: apiConfig, texts: customTexts } = props;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../source/signin/service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAsB,MAAM,sBAAsB,CAAA;AAElE,OAAO,KAAK,EAAE,SAAS,EAAwC,YAAY,EAAE,MAAM,SAAS,CAAA;AAE5F,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,WAAW,GAAG,SAAS,CAAA;AAE/G;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,2BAA2B;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,6BAA6B;IAC7B,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,6BAA6B;IAC7B,sBAAsB,CAAC,EAAE,MAAM,CAAA;CAChC;AAoBD,qBAAa,aAAc,SAAQ,OAAO;IACxC,SACgB,MAAM,EAAE,OAAO,CAAQ;IAEvC,OAAO,CAAC,SAAS,CAAiB;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiB;IAC1C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAW;IACrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA+B;IACtD,OAAO,CAAC,YAAY,CAA6C;IAGjE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiC;IAGvD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiC;IAGxD,SACgB,YAAY,EAAE,kBAAkB,CAAS;IAEzD,SACgB,YAAY,EAAE,MAAM,CAAK;gBAE7B,SAAS,EAAE,SAAS,EAAE,MAAM,GAAE,mBAAwB;IAMlE,IACW,MAAM,IAAI,YAAY,GAAG,IAAI,CAIvC;IAED,IACW,SAAS,IAAI,MAAM,GAAG,IAAI,CAEpC;IAED,IACW,YAAY,IAAI,OAAO,CAEjC;IAED,IACW,eAAe,IAAI,OAAO,CAEpC;IAED;;OAEG;IACH,OAAO,CAAC,eAAe;IAKvB;;OAEG;IACH,OAAO,CAAC,SAAS;IAgBjB;;OAEG;IACH,OAAO,CAAC,YAAY;IAuCpB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAU3B;;OAEG;IACH,OAAO,CAAC,QAAQ;IAehB;;OAEG;IACH,OAAO,CAAC,WAAW;IAQnB;;OAEG;YACW,iBAAiB;IAyB/B;;OAEG;YACW,iBAAiB;IA4G/B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAQxB;;OAEG;IAEG,KAAK,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IA+CrC;;OAEG;IAEG,WAAW;IASjB;;OAEG;IAEG,MAAM;IAqBZ;;OAEG;IAEU,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../source/signin/service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAsB,MAAM,sBAAsB,CAAA;AAElE,OAAO,KAAK,EAAE,SAAS,EAAwC,YAAY,EAAE,MAAM,SAAS,CAAA;AAE5F,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,WAAW,GAAG,SAAS,CAAA;AAE/G;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,2BAA2B;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,6BAA6B;IAC7B,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,6BAA6B;IAC7B,sBAAsB,CAAC,EAAE,MAAM,CAAA;CAChC;AAoBD,qBAAa,aAAc,SAAQ,OAAO;IACxC,SACgB,MAAM,EAAE,OAAO,CAAQ;IAEvC,OAAO,CAAC,SAAS,CAAiB;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiB;IAC1C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAW;IACrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA+B;IACtD,OAAO,CAAC,YAAY,CAA6C;IAGjE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiC;IAGvD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiC;IAGxD,SACgB,YAAY,EAAE,kBAAkB,CAAS;IAEzD,SACgB,YAAY,EAAE,MAAM,CAAK;gBAE7B,SAAS,EAAE,SAAS,EAAE,MAAM,GAAE,mBAAwB;IAMlE,IACW,MAAM,IAAI,YAAY,GAAG,IAAI,CAIvC;IAED,IACW,SAAS,IAAI,MAAM,GAAG,IAAI,CAEpC;IAED,IACW,YAAY,IAAI,OAAO,CAEjC;IAED,IACW,eAAe,IAAI,OAAO,CAEpC;IAED;;OAEG;IACH,OAAO,CAAC,eAAe;IAKvB;;OAEG;IACH,OAAO,CAAC,SAAS;IAgBjB;;OAEG;IACH,OAAO,CAAC,YAAY;IAuCpB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAU3B;;OAEG;IACH,OAAO,CAAC,QAAQ;IAehB;;OAEG;IACH,OAAO,CAAC,WAAW;IAQnB;;OAEG;YACW,iBAAiB;IAyB/B;;OAEG;YACW,iBAAiB;IA4G/B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAQxB;;OAEG;IAEG,KAAK,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IA+CrC;;OAEG;IAEG,WAAW;IASjB;;OAEG;IAEG,MAAM;IAqBZ;;OAEG;IAEU,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC;IA6BrC;;OAEG;IACH,OAAO,CAAC,OAAO;IAWf;;OAEG;IACI,OAAO;CAIf"}
|
package/output/signin/service.js
CHANGED
|
@@ -445,10 +445,13 @@ let SigninService = (() => {
|
|
|
445
445
|
dialogStatus: this.dialogStatus,
|
|
446
446
|
errorMessage: this.errorMessage
|
|
447
447
|
}), () => {
|
|
448
|
-
//
|
|
449
|
-
if (this.ticket || this.dialogStatus
|
|
448
|
+
// 只在需要恢复的状态时保存(等待或打开中且有有效票据)
|
|
449
|
+
if (this.ticket && (this.dialogStatus === 'waiting' || this.dialogStatus === 'opening')) {
|
|
450
450
|
this.saveState();
|
|
451
451
|
}
|
|
452
|
+
else {
|
|
453
|
+
this.clearPersistedState();
|
|
454
|
+
}
|
|
452
455
|
}, { delay: 100 }));
|
|
453
456
|
// 尝试恢复持久化状态
|
|
454
457
|
const restored = this.restoreState();
|
|
@@ -150,8 +150,10 @@ describe('SigninService', () => {
|
|
|
150
150
|
await service.start();
|
|
151
151
|
// 等待状态保存
|
|
152
152
|
await new Promise(resolve => setTimeout(resolve, 150));
|
|
153
|
-
// 模拟页面刷新 -
|
|
153
|
+
// 模拟页面刷新 - 保存 localStorage(浏览器刷新时不会调用 dispose,但会保留 localStorage)
|
|
154
|
+
const savedData = localStorageMock.getItem('__signin_service_state__');
|
|
154
155
|
service.dispose();
|
|
156
|
+
localStorageMock.setItem('__signin_service_state__', savedData);
|
|
155
157
|
// 创建新服务实例(模拟页面重新加载)
|
|
156
158
|
const newService = new SigninService(mockApi, {
|
|
157
159
|
pollInterval: 100,
|
|
@@ -173,8 +175,10 @@ describe('SigninService', () => {
|
|
|
173
175
|
// 等待状态保存
|
|
174
176
|
await new Promise(resolve => setTimeout(resolve, 150));
|
|
175
177
|
expect(service.isDialogOpen).toBe(true);
|
|
176
|
-
// 模拟页面刷新
|
|
178
|
+
// 模拟页面刷新 - 保存 localStorage(浏览器刷新时不会调用 dispose,但会保留 localStorage)
|
|
179
|
+
const savedData = localStorageMock.getItem('__signin_service_state__');
|
|
177
180
|
service.dispose();
|
|
181
|
+
localStorageMock.setItem('__signin_service_state__', savedData);
|
|
178
182
|
// 创建新服务实例
|
|
179
183
|
const newService = new SigninService(mockApi);
|
|
180
184
|
await newService.init();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../source/toaster/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4B,MAAM,OAAO,CAAA;AAIhD,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../source/toaster/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4B,MAAM,OAAO,CAAA;AAIhD,UAAU,iBAAiB;IACzB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CAC1C;AAED,+BAA+B;AAC/B,wBAAgB,OAAO,CAAC,KAAK,EAAE,iBAAiB,2CAkE/C;yBAlEe,OAAO;wBA2EE,KAAK,CAAC,SAAS;yBAId,KAAK,CAAC,SAAS;2BAIb,KAAK,CAAC,SAAS;2BAIf,KAAK,CAAC,SAAS"}
|
package/output/toaster/index.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { toast, Toaster as SonnerToaster } from 'sonner';
|
|
4
4
|
import { SystemError, UserError } from '@taicode/common-base';
|
|
5
|
-
import 'react-toastify/dist/ReactToastify.css';
|
|
6
5
|
/** 自动捕获和处理异步错误,无法识别的错误会保持原样 */
|
|
7
6
|
export function Toaster(props) {
|
|
8
7
|
const addEventListened = useRef(false);
|
|
@@ -55,17 +54,17 @@ export function Toaster(props) {
|
|
|
55
54
|
handledErrors.current = new WeakSet();
|
|
56
55
|
};
|
|
57
56
|
}, []);
|
|
58
|
-
return (_jsxs(_Fragment, { children: [props.children, _jsx(
|
|
57
|
+
return (_jsxs(_Fragment, { children: [props.children, _jsx(SonnerToaster, { position: "top-center", closeButton: true })] }));
|
|
59
58
|
}
|
|
60
59
|
Toaster.info = (content) => {
|
|
61
|
-
|
|
60
|
+
toast.info(content);
|
|
62
61
|
};
|
|
63
62
|
Toaster.error = (content) => {
|
|
64
|
-
|
|
63
|
+
toast.error(content);
|
|
65
64
|
};
|
|
66
65
|
Toaster.success = (content) => {
|
|
67
|
-
|
|
66
|
+
toast.success(content);
|
|
68
67
|
};
|
|
69
68
|
Toaster.warning = (content) => {
|
|
70
|
-
|
|
69
|
+
toast.warning(content);
|
|
71
70
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taicode/common-web",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"author": "Alain",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"description": "",
|
|
@@ -28,31 +28,37 @@
|
|
|
28
28
|
"dev": "tsc -p tsconfig.json --watch"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
|
-
"@taicode/common-base": ">=1.1.0",
|
|
32
|
-
"@needle-di/core": ">=1.0.0",
|
|
33
31
|
"@heroicons/react": "^2.2.0",
|
|
34
|
-
"
|
|
32
|
+
"@needle-di/core": ">=1.0.0",
|
|
33
|
+
"@taicode/common-base": "^3.0.1",
|
|
35
34
|
"@types/react": ">=18",
|
|
36
35
|
"mobx": ">=6",
|
|
36
|
+
"mobx-react-lite": "^4.1.1",
|
|
37
37
|
"react": ">=18"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"react-toastify": "^11.0.5",
|
|
41
|
-
"@heroicons/react": "^2.2.0",
|
|
42
40
|
"@headlessui/react": "^2.2.4",
|
|
41
|
+
"@heroicons/react": "^2.2.0",
|
|
42
|
+
"@radix-ui/react-dialog": "^1.1.15",
|
|
43
|
+
"@radix-ui/react-slot": "^1.2.4",
|
|
44
|
+
"class-variance-authority": "^0.7.1",
|
|
45
|
+
"clsx": "^2.1.1",
|
|
43
46
|
"framer-motion": "^12.19.2",
|
|
44
|
-
"
|
|
47
|
+
"sonner": "^1.7.1",
|
|
48
|
+
"tailwind-merge": "^3.4.0"
|
|
45
49
|
},
|
|
46
50
|
"devDependencies": {
|
|
47
|
-
"@
|
|
51
|
+
"@needle-di/core": "^1.0.0",
|
|
48
52
|
"@testing-library/jest-dom": "^6.1.0",
|
|
49
|
-
"
|
|
50
|
-
"vitest": "^1.0.0",
|
|
51
|
-
"@vitest/ui": "^1.0.0",
|
|
53
|
+
"@testing-library/react": "^14.0.0",
|
|
52
54
|
"@types/react": "^18.0.0",
|
|
53
|
-
"@
|
|
54
|
-
"
|
|
55
|
+
"@vitest/ui": "^1.0.0",
|
|
56
|
+
"execa": "^8.0.1",
|
|
57
|
+
"jsdom": "^23.0.0",
|
|
55
58
|
"mobx": "^6.0.0",
|
|
56
|
-
"react": "^
|
|
59
|
+
"mobx-react-lite": "^4.1.1",
|
|
60
|
+
"react": "^18.0.0",
|
|
61
|
+
"type-detect": "^4.1.0",
|
|
62
|
+
"vitest": "^1.0.0"
|
|
57
63
|
}
|
|
58
64
|
}
|