Skip to content

Commit 26bf99c

Browse files
committed
feat: 新增Toast 轻提示组件
1 parent 94d9470 commit 26bf99c

17 files changed

+682
-0
lines changed

.umirc.ts

+1
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ export default defineConfig({
151151
'components/overlay',
152152
'components/back-top',
153153
'components/notify',
154+
'components/toast',
154155
],
155156
},
156157
{

src/components/toast/README.md

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Toast 轻提示
2+
3+
<code hidden="hidden" src="./demos/demo.tsx"></code>
4+
5+
## 介绍
6+
对操作结果的轻量级反馈,适用于页面内容的变化不能直接反应操作结果时使用。
7+
8+
## 使用
9+
10+
```tsx
11+
import { Toast } from 'aunt';
12+
```
13+
14+
### 基本用法
15+
通过调用`Toast` 方法进行展示提示。
16+
<code src="./demos/demo-base.tsx"></code>
17+
18+
### 自定义图标
19+
可通过`icon` 传入相关内容在自定义图标,可传入图片。
20+
<code src="./demos/demo-icon.tsx"></code>
21+
22+
### 横向排布
23+
通过`direction`属性设置展示的布局,可选`horizontal`进行横向排布。
24+
<code src="./demos/demo-direction.tsx"></code>
25+
26+
### 自定义位置
27+
通过`position`属性设置展示的位置。
28+
<code src="./demos/demo-position.tsx"></code>
29+
30+
## 参数
31+
| 参数 | 说明 | 类型 | 默认值 |
32+
| --------- | -------------- | ---------- | --------- |
33+
| type | 提示类型 | `'loading' \| 'success' \| 'fail' \| 'info'` | `info` |
34+
| message | 文本内容,支持通过\n换行 | `number \| string` | `-` |
35+
| duration | 展示时长(ms),值为 0 时,toast 不会消失 | `number` | `3000` |
36+
| icon | 自定义图标 | `React.ReactNode` | `-` |
37+
| iconSize | 展示时长(ms),值为 0 时,toast 不会消失 | `number \| string \| ((direction: ToastDirection) => number \| string)` | `-` |
38+
| loadingType | 展示时长(ms),值为 0 时,toast 不会消失 | `LoadingType` | `gap` |
39+
| direction | 图标和文字的排列方式 | `'vertical' \| 'horizontal'` | `vertical` |
40+
| forbidClick | 是否禁止背景点击 | `boolean` | `false` |
41+
| position | 位置,可选值为 top bottom | `'top' \| 'center' \| 'bottom'` | `center` |
42+
| teleport | 轻提示弹出时的的父容器 | `HTMLElement \| (() => HTMLElement)` | `body` |
43+
| onClose | 关闭时的回调函数 | `() => void` | `-` |
44+
| onOpened | 完全展示后的回调函数 | `() => void` | `-` |
45+
46+
```tsx
47+
const iconSize = (direction: ToastDirection)=>{
48+
if(direction === 'horizontal') return 20;
49+
return 34;
50+
}
51+
```
52+
53+
## 方法
54+
| 方法名 | 说明 | 参数 | 返回值 |
55+
| --- | --- | --- | --- |
56+
| Toast | 展示提示 | `options \| message` | toast 实例 |
57+
| Toast.info | 展示文字提示 | `options \| message` | toast 实例 |
58+
| Toast.loading | 展示加载提示 | `options \| message` | toast 实例 |
59+
| Toast.success | 展示成功提示 | `options \| message` | toast 实例 |
60+
| Toast.fail | 展示失败提示 | `options \| message` | toast 实例 |
61+
| Toast.clear | 关闭提示 | `-` | `void` |
62+
| Toast.allowMultiple | 允许同时存在多个 Toast | `-` | `void` |
63+
| Toast.setDefaultOptions | 修改默认配置,对所有 Toast 生效。<br>传入 type 可以修改指定类型的默认配置 | `options` | `void` |
64+
| Toast.resetDefaultOptions | 重置默认配置,对所有 Toast 生效。<br>传入 type 可以重置指定类型的默认配置 | `-` | `void` |
65+
66+
```tsx
67+
// toast 实例
68+
export type ToastReturnType = {
69+
/** 动态更新方法 */
70+
config: React.Dispatch<React.SetStateAction<ToastProps>>
71+
/** 清除单例toast */
72+
clear: () => void
73+
}
74+
```
75+
76+
## 样式变量
77+
| 属性名 | 说明 | 默认值 |
78+
| ---------------- | -------- | --------- |
79+
| --aunt-toast-z-index | 显示层级 | `var(--aunt-z-index-full-screen);` |
80+
| --aunt-toast-content-background-color | 背景颜色| `rgba(0,0,0,.8);` |
81+
| --aunt-toast-content-padding | 默认内边距| `var(--aunt-padding-base) var(--aunt-padding-s);` |
82+
| --aunt-toast-content-border-radius | 默认圆角| `var(--aunt-border-radius-md);` |
83+
| --aunt-toast-content-color | 默认字体颜色| `var(--aunt-white-color);` |
84+
| --aunt-toast-content-top | 默认Top高度| `20%;` |
85+
| --aunt-toast-content-bottom | 默认Bottom高度| `20%;` |
86+
| --aunt-toast-content-text-size | 文字大小| `var(--aunt-font-size-sm);` |
87+
| --aunt-toast-content-text-margin-left | 横行排布的文字左外边距 | `var(--aunt-padding-base);` |

src/components/toast/controller.tsx

+178
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import React, { useEffect, useState, useCallback } from 'react';
2+
import type { ToastType, ToastOptions, ToastProps, ToastInstance, ToastReturnType } from './types';
3+
import Transition from '../transition';
4+
import { Toast as BaseToast } from './toast';
5+
import { isObject } from '../../utils';
6+
import { render, unmount } from '../../utils/dom/render';
7+
import { resolveContainer } from '../../utils/dom/getContainer';
8+
9+
const currentOptions: { [key: string]: ToastOptions } = {};
10+
11+
function parseOptions(message: string | ToastOptions): ToastOptions {
12+
return isObject(message) ? message : { message };
13+
}
14+
15+
const toastArray: (() => void)[] = [];
16+
17+
// 同步的销毁
18+
function syncClear() {
19+
let fn = toastArray.pop();
20+
while (fn) {
21+
fn();
22+
fn = toastArray.pop();
23+
}
24+
}
25+
26+
// 针对 toast 还没弹出来就立刻销毁的情况,将销毁放到下一个 event loop 中,避免销毁失败。
27+
function nextTickClear() {
28+
setTimeout(syncClear);
29+
}
30+
31+
const show = (p: ToastProps | string) => {
32+
const props = parseOptions(p);
33+
const update: ToastReturnType = {
34+
config: () => {},
35+
clear: () => null,
36+
};
37+
// 创建父亲节点
38+
const userContainer = resolveContainer(props.teleport);
39+
const container = document.createElement('div');
40+
userContainer.appendChild(container);
41+
42+
// 定时器
43+
let timer = 0;
44+
45+
const TempNotify = () => {
46+
const options = {
47+
...props,
48+
};
49+
const [state, setState] = useState<ToastProps>({ ...options });
50+
const [visible, setVisible] = useState(false);
51+
52+
const destroy = useCallback(() => {
53+
setVisible(false);
54+
if (props.onClose) props.onClose();
55+
}, []);
56+
57+
const internalOnClosed = useCallback(() => {
58+
const unmountResult = unmount(container);
59+
if (unmountResult && container.parentNode) {
60+
container.parentNode.removeChild(container);
61+
}
62+
}, [container]);
63+
64+
update.clear = internalOnClosed;
65+
66+
update.config = useCallback(
67+
nextState => {
68+
setState(prev =>
69+
typeof nextState === 'function'
70+
? { ...prev, ...nextState(prev) }
71+
: { ...prev, ...nextState }
72+
);
73+
},
74+
[setState]
75+
);
76+
77+
useEffect(() => {
78+
setVisible(true);
79+
syncClear();
80+
toastArray.push(internalOnClosed);
81+
82+
if (state.duration && +state.duration > 0) {
83+
timer = window.setTimeout(destroy, state.duration);
84+
}
85+
86+
return () => {
87+
if (timer !== 0) {
88+
window.clearTimeout(timer);
89+
}
90+
};
91+
}, []);
92+
93+
return (
94+
<Transition in={visible} onEntered={props.onOpened} onExited={props.onClose}>
95+
<BaseToast {...state} />
96+
</Transition>
97+
);
98+
};
99+
100+
render(<TempNotify />, container);
101+
102+
return update;
103+
};
104+
105+
const defaultOptions: ToastOptions = {
106+
message: '',
107+
duration: 3000,
108+
direction: 'vertical',
109+
loadingType: 'gap',
110+
position: 'center',
111+
};
112+
113+
['info', 'loading', 'success', 'fail'].forEach(method => {
114+
currentOptions[method] = defaultOptions;
115+
});
116+
117+
const setDefaultOptions = (type: ToastType, options: ToastOptions) => {
118+
currentOptions[type] = Object.assign(currentOptions[type], options);
119+
};
120+
121+
// 重置配置
122+
const resetDefaultOptions = (type: ToastType) => {
123+
currentOptions[type] = { ...defaultOptions };
124+
};
125+
126+
const clear = nextTickClear;
127+
128+
const ToastDefault = (options: ToastProps | string) => {
129+
let type: ToastType = 'info';
130+
if (typeof options !== 'string') {
131+
type = options.type || 'info';
132+
}
133+
return show({
134+
type: type,
135+
...currentOptions[type],
136+
...parseOptions(options),
137+
});
138+
};
139+
140+
const info = (options: ToastOptions | string) =>
141+
show({
142+
...currentOptions['info'],
143+
...parseOptions(options),
144+
type: 'info',
145+
});
146+
147+
const fail = (options: ToastOptions | string) =>
148+
show({
149+
...currentOptions['fail'],
150+
...parseOptions(options),
151+
type: 'fail',
152+
});
153+
154+
const success = (options: ToastOptions | string) =>
155+
show({
156+
...currentOptions['success'],
157+
...parseOptions(options),
158+
type: 'success',
159+
});
160+
161+
const loading = (options: ToastOptions | string) =>
162+
show({
163+
...currentOptions['loading'],
164+
...parseOptions(options),
165+
type: 'loading',
166+
});
167+
168+
const ToastController: ToastInstance = Object.assign(ToastDefault, {
169+
setDefaultOptions,
170+
resetDefaultOptions,
171+
clear,
172+
info,
173+
fail,
174+
loading,
175+
success,
176+
});
177+
178+
export default ToastController;
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React from 'react';
2+
import { Space, Button, Toast } from 'aunt';
3+
4+
export default () => {
5+
return (
6+
<Space wrap direction='vertical' style={{ width: '100%' }}>
7+
<Button type='primary' block onClick={() => Toast('文字提示')}>
8+
文字提示
9+
</Button>
10+
<Button type='success' block onClick={() => Toast.success('成功提示')}>
11+
成功提示
12+
</Button>
13+
<Button type='danger' block onClick={() => Toast.fail('失败提示')}>
14+
失败提示
15+
</Button>
16+
<Button type='warning' block onClick={() => Toast.loading('加载提示')}>
17+
加载提示
18+
</Button>
19+
</Space>
20+
);
21+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import React from 'react';
2+
import { Space, Button, Toast } from 'aunt';
3+
4+
export default () => {
5+
return (
6+
<Space wrap direction='vertical' style={{ width: '100%' }}>
7+
<Button
8+
type='success'
9+
block
10+
onClick={() =>
11+
Toast.success({
12+
message: '成功提示',
13+
direction: 'horizontal',
14+
})
15+
}
16+
>
17+
成功提示
18+
</Button>
19+
<Button
20+
type='danger'
21+
block
22+
onClick={() =>
23+
Toast.fail({
24+
message: '失败提示',
25+
direction: 'horizontal',
26+
})
27+
}
28+
>
29+
失败提示
30+
</Button>
31+
<Button
32+
type='warning'
33+
block
34+
onClick={() =>
35+
Toast.loading({
36+
message: '加载提示',
37+
direction: 'horizontal',
38+
})
39+
}
40+
>
41+
加载提示
42+
</Button>
43+
</Space>
44+
);
45+
};
+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React from 'react';
2+
import { Space, Button, Toast, AuntIconCheckCircle, AuntIconXCircle } from 'aunt';
3+
4+
export default () => {
5+
return (
6+
<Space wrap direction='vertical' style={{ width: '100%' }}>
7+
<Button
8+
type='success'
9+
block
10+
onClick={() =>
11+
Toast.success({
12+
message: '成功提示',
13+
icon: <AuntIconCheckCircle />,
14+
})
15+
}
16+
>
17+
成功提示
18+
</Button>
19+
<Button
20+
type='danger'
21+
block
22+
onClick={() =>
23+
Toast.fail({
24+
message: '失败提示',
25+
icon: <AuntIconXCircle />,
26+
})
27+
}
28+
>
29+
失败提示
30+
</Button>
31+
<Button
32+
type='warning'
33+
block
34+
onClick={() =>
35+
Toast.loading({
36+
message: '加载提示',
37+
loadingType: 'bars',
38+
})
39+
}
40+
>
41+
加载提示
42+
</Button>
43+
<Button type='primary' block onClick={() => Toast.loading({})}>
44+
单图标模式
45+
</Button>
46+
</Space>
47+
);
48+
};

0 commit comments

Comments
 (0)