@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.
@@ -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
- * LoadingButton 组件属性类型
5
- * 继承 Button 的所有属性,自动处理 onClick 的异步状态
3
+ * 多态组件的 Props 类型
6
4
  */
7
- export type LoadingButtonProps = ButtonProps;
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
- * 自动处理 onClick 异步操作的加载状态,在按钮末尾显示 loading 图标
17
+ * 支持任意组件作为基础按钮,在异步操作时自动显示 loading 状态
12
18
  *
13
19
  * @example
14
20
  * ```tsx
15
- * <LoadingButton
16
- * color="blue"
17
- * onClick={async () => {
18
- * await saveData()
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<ButtonProps & React.RefAttributes<HTMLElement>>;
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,KAA+B,MAAM,OAAO,CAAA;AACnD,OAAO,EAAU,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAG7D;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,WAAW,CAAA;AAM5C;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,aAAa,iFAkDxB,CAAA"}
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 { forwardRef, useState } from 'react';
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
- * 自动处理 onClick 异步操作的加载状态,在按钮末尾显示 loading 图标
26
+ * 支持任意组件作为基础按钮,在异步操作时自动显示 loading 状态
23
27
  *
24
28
  * @example
25
29
  * ```tsx
26
- * <LoadingButton
27
- * color="blue"
28
- * onClick={async () => {
29
- * await saveData()
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 { children, onClick } = _a, props = __rest(_a, ["children", "onClick"]);
38
- const [isLoading, setIsLoading] = useState(false);
39
- const handleClick = async (event) => {
40
- if (!onClick || isLoading)
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
- // 检查是否是 Promise
59
+ // 如果返回 Promise-like,自动管理 loading 状态,但不捕获异常
60
+ // 使用 then 而不是 finally,因为 PromiseLike 不保证有 finally 方法
61
+ // 在 rejected 回调中重新抛出,让异常传播到全局错误处理机制(如 Toaster)
44
62
  if (isPromiseLike(result)) {
45
- setIsLoading(true);
46
- const catchResult = await catchIt(result);
47
- setIsLoading(false);
48
- // 错误由调用方处理
49
- if (catchResult.isError()) {
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
- return (_jsxs(Button, Object.assign({}, props, { ref: ref, onClick: handleClick, disabled: isLoading, children: [children, isLoading && (_jsxs("svg", { "data-slot": "icon", className: "animate-spin", viewBox: "0 0 24 24", fill: "none", 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" })] }))] })));
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,2 @@
1
+ import '@testing-library/jest-dom/vitest';
2
+ //# sourceMappingURL=index.test.d.ts.map
@@ -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;AAczB,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;AA+UD;;;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"}
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"}
@@ -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-slate-900 dark:text-slate-100", 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, { color: "emerald", onClick: handleReopenWindow, children: texts.reopenButton }), _jsx(Button, { outline: true, onClick: handleCancel, children: texts.cancelButton })] })] }, "opening"));
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-slate-900 dark:text-slate-100", children: texts.waitingDescription }), _jsx("p", { className: "text-xs text-slate-500 dark:text-slate-500", children: texts.waitingHint }), _jsxs("div", { className: "mt-2 flex gap-2", children: [_jsx(Button, { color: "emerald", onClick: handleReopenWindow, children: texts.reopenButton }), _jsx(Button, { outline: true, onClick: handleCancel, children: texts.cancelButton })] })] }, "waiting"));
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-emerald-600 dark:text-emerald-400", children: texts.successDescription })] }, "success"));
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-red-600 dark:text-red-400", children: service.errorMessage || texts.errorDescription }), _jsxs("div", { className: "mt-2 flex gap-2", children: [_jsx(Button, { color: "emerald", onClick: handleRetry, children: texts.retryButton }), _jsx(Button, { outline: true, onClick: handleClose, children: texts.closeButton })] })] }, "error"));
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-amber-600 dark:text-amber-400", children: texts.cancelledDescription }), _jsxs("div", { className: "mt-2 flex gap-2", children: [_jsx(Button, { color: "emerald", onClick: handleRetry, children: texts.retryButton }), _jsx(Button, { outline: true, onClick: handleClose, children: texts.closeButton })] })] }, "cancelled"));
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-orange-600 dark:text-orange-400", children: texts.expiredDescription }), _jsxs("div", { className: "mt-2 flex gap-2", children: [_jsx(Button, { color: "emerald", onClick: handleRetry, children: texts.retryButton }), _jsx(Button, { outline: true, onClick: handleClose, children: texts.closeButton })] })] }, "expired"));
94
- return (_jsx(Dialog, { open: service.isDialogOpen, onClose: preventClose, children: _jsx(DialogBody, { 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, {})] }) }) }) }));
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;IA2BrC;;OAEG;IACH,OAAO,CAAC,OAAO;IAWf;;OAEG;IACI,OAAO;CAIf"}
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"}
@@ -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 !== 'idle') {
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,5 +1,4 @@
1
1
  import React from 'react';
2
- import 'react-toastify/dist/ReactToastify.css';
3
2
  interface ToastCatcherProps {
4
3
  defaultMessage?: string;
5
4
  children: React.ReactNode;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../source/toaster/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4B,MAAM,OAAO,CAAA;AAIhD,OAAO,uCAAuC,CAAA;AAE9C,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,2CAuE/C;yBAvEe,OAAO;wBAgFE,KAAK,CAAC,SAAS;yBAId,KAAK,CAAC,SAAS;2BAIb,KAAK,CAAC,SAAS;2BAIf,KAAK,CAAC,SAAS"}
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"}
@@ -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 { ToastContainer, toast as baseToast } from 'react-toastify';
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(ToastContainer, { newestOnTop: true, hideProgressBar: true, closeButton: false, position: "top-center" })] }));
57
+ return (_jsxs(_Fragment, { children: [props.children, _jsx(SonnerToaster, { position: "top-center", closeButton: true })] }));
59
58
  }
60
59
  Toaster.info = (content) => {
61
- baseToast.info(content);
60
+ toast.info(content);
62
61
  };
63
62
  Toaster.error = (content) => {
64
- baseToast.error(content);
63
+ toast.error(content);
65
64
  };
66
65
  Toaster.success = (content) => {
67
- baseToast.success(content);
66
+ toast.success(content);
68
67
  };
69
68
  Toaster.warning = (content) => {
70
- baseToast.warning(content);
69
+ toast.warning(content);
71
70
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taicode/common-web",
3
- "version": "1.1.21",
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
- "mobx-react-lite": "^4.1.1",
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
- "clsx": "^2.1.1"
47
+ "sonner": "^1.7.1",
48
+ "tailwind-merge": "^3.4.0"
45
49
  },
46
50
  "devDependencies": {
47
- "@testing-library/react": "^14.0.0",
51
+ "@needle-di/core": "^1.0.0",
48
52
  "@testing-library/jest-dom": "^6.1.0",
49
- "jsdom": "^23.0.0",
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
- "@needle-di/core": "^1.0.0",
54
- "mobx-react-lite": "^4.1.1",
55
+ "@vitest/ui": "^1.0.0",
56
+ "execa": "^8.0.1",
57
+ "jsdom": "^23.0.0",
55
58
  "mobx": "^6.0.0",
56
- "react": "^18.0.0"
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
  }