({ ...options });
+ const [visible, setVisible] = useState(false);
+
+ const destroy = useCallback(() => {
+ setVisible(false);
+ if (props.onClose) props.onClose();
+ }, []);
+
+ const internalOnClosed = useCallback(() => {
+ const unmountResult = unmount(container);
+ if (unmountResult && container.parentNode) {
+ container.parentNode.removeChild(container);
+ }
+ }, [container]);
+
+ update.clear = internalOnClosed;
+
+ update.config = useCallback(
+ nextState => {
+ setState(prev =>
+ typeof nextState === 'function'
+ ? { ...prev, ...nextState(prev) }
+ : { ...prev, ...nextState }
+ );
+ },
+ [setState]
+ );
+
+ useEffect(() => {
+ setVisible(true);
+ syncClear();
+ toastArray.push(internalOnClosed);
+
+ if (state.duration && +state.duration > 0) {
+ timer = window.setTimeout(destroy, state.duration);
+ }
+
+ return () => {
+ if (timer !== 0) {
+ window.clearTimeout(timer);
+ }
+ };
+ }, []);
+
+ return (
+
+
+
+ );
+ };
+
+ render(, container);
+
+ return update;
+};
+
+const defaultOptions: ToastOptions = {
+ message: '',
+ duration: 3000,
+ direction: 'vertical',
+ loadingType: 'gap',
+ position: 'center',
+};
+
+['info', 'loading', 'success', 'fail'].forEach(method => {
+ currentOptions[method] = defaultOptions;
+});
+
+const setDefaultOptions = (type: ToastType, options: ToastOptions) => {
+ currentOptions[type] = Object.assign(currentOptions[type], options);
+};
+
+// 重置配置
+const resetDefaultOptions = (type: ToastType) => {
+ currentOptions[type] = { ...defaultOptions };
+};
+
+const clear = nextTickClear;
+
+const ToastDefault = (options: ToastProps | string) => {
+ let type: ToastType = 'info';
+ if (typeof options !== 'string') {
+ type = options.type || 'info';
+ }
+ return show({
+ type: type,
+ ...currentOptions[type],
+ ...parseOptions(options),
+ });
+};
+
+const info = (options: ToastOptions | string) =>
+ show({
+ ...currentOptions['info'],
+ ...parseOptions(options),
+ type: 'info',
+ });
+
+const fail = (options: ToastOptions | string) =>
+ show({
+ ...currentOptions['fail'],
+ ...parseOptions(options),
+ type: 'fail',
+ });
+
+const success = (options: ToastOptions | string) =>
+ show({
+ ...currentOptions['success'],
+ ...parseOptions(options),
+ type: 'success',
+ });
+
+const loading = (options: ToastOptions | string) =>
+ show({
+ ...currentOptions['loading'],
+ ...parseOptions(options),
+ type: 'loading',
+ });
+
+const ToastController: ToastInstance = Object.assign(ToastDefault, {
+ setDefaultOptions,
+ resetDefaultOptions,
+ clear,
+ info,
+ fail,
+ loading,
+ success,
+});
+
+export default ToastController;
diff --git a/src/components/toast/demos/demo-base.tsx b/src/components/toast/demos/demo-base.tsx
new file mode 100644
index 0000000..b2d260f
--- /dev/null
+++ b/src/components/toast/demos/demo-base.tsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import { Space, Button, Toast } from 'aunt';
+
+export default () => {
+ return (
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/toast/demos/demo-direction.tsx b/src/components/toast/demos/demo-direction.tsx
new file mode 100644
index 0000000..235176b
--- /dev/null
+++ b/src/components/toast/demos/demo-direction.tsx
@@ -0,0 +1,45 @@
+import React from 'react';
+import { Space, Button, Toast } from 'aunt';
+
+export default () => {
+ return (
+
+
+
+
+
+ );
+};
diff --git a/src/components/toast/demos/demo-icon.tsx b/src/components/toast/demos/demo-icon.tsx
new file mode 100644
index 0000000..dce3415
--- /dev/null
+++ b/src/components/toast/demos/demo-icon.tsx
@@ -0,0 +1,48 @@
+import React from 'react';
+import { Space, Button, Toast, AuntIconCheckCircle, AuntIconXCircle } from 'aunt';
+
+export default () => {
+ return (
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/toast/demos/demo-position.tsx b/src/components/toast/demos/demo-position.tsx
new file mode 100644
index 0000000..2da6851
--- /dev/null
+++ b/src/components/toast/demos/demo-position.tsx
@@ -0,0 +1,43 @@
+import React from 'react';
+import { Space, Button, Toast } from 'aunt';
+
+export default () => {
+ return (
+
+
+
+
+
+ );
+};
diff --git a/src/components/toast/demos/demo.tsx b/src/components/toast/demos/demo.tsx
new file mode 100644
index 0000000..dbb53e6
--- /dev/null
+++ b/src/components/toast/demos/demo.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+import { DemoBlock } from 'demos';
+import DemoBase from './demo-base';
+import DemoIcon from './demo-icon';
+import DemoDirection from './demo-direction';
+import DemoPosition from './demo-position';
+import './index.less';
+
+function Demo() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default Demo;
diff --git a/src/components/toast/demos/index.less b/src/components/toast/demos/index.less
new file mode 100644
index 0000000..a099ca2
--- /dev/null
+++ b/src/components/toast/demos/index.less
@@ -0,0 +1,3 @@
+.demo {
+
+}
diff --git a/src/components/toast/index.ts b/src/components/toast/index.ts
new file mode 100644
index 0000000..f9442af
--- /dev/null
+++ b/src/components/toast/index.ts
@@ -0,0 +1,7 @@
+import './styles/index.less';
+import Toast from './controller';
+
+export type { ToastProps, ToastOptions, ToastPosition, ToastType, ToastInstance } from './types';
+
+export { Toast };
+export default Toast;
diff --git a/src/components/toast/styles/index.less b/src/components/toast/styles/index.less
new file mode 100644
index 0000000..08cc192
--- /dev/null
+++ b/src/components/toast/styles/index.less
@@ -0,0 +1,72 @@
+@class-prefix: ~'aunt';
+
+:root{
+ --aunt-toast-z-index: var(--aunt-z-index-full-screen);
+ --aunt-toast-content-background-color: rgba(0,0,0,.8);
+ --aunt-toast-content-padding: var(--aunt-padding-base) var(--aunt-padding-s);
+ --aunt-toast-content-border-radius: var(--aunt-border-radius-md);
+ --aunt-toast-content-color: var(--aunt-white-color);
+ --aunt-toast-content-top: 20%;
+ --aunt-toast-content-bottom: 20%;
+ --aunt-toast-content-text-size: var(--aunt-font-size-sm);
+ --aunt-toast-content-text-margin-left: var(--aunt-padding-base);
+}
+
+.@{class-prefix}-toast {
+ z-index: var(--aunt-toast-z-index);
+ position: fixed;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ top: 0;
+ box-sizing: border-box;
+ &--pointer{
+ pointer-events: none;
+ }
+
+ &__content{
+ position: absolute;
+ display: inline-flex;
+ background-color: var(--aunt-toast-content-background-color);
+ padding: var(--aunt-toast-content-padding);
+ border-radius: var(--aunt-toast-content-border-radius);
+ align-items: center;
+ justify-content: center;
+ color: var(--aunt-toast-content-color);
+
+ &--vertical{
+ flex-direction: column;
+ }
+ &--horizontal{
+ flex-direction: row;
+ }
+
+ &--center{
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%,-50%);
+ }
+ &--top{
+ top: var(--aunt-toast-content-top);
+ left: 50%;
+ transform: translate(-50%,-50%);
+ }
+ &--bottom{
+ bottom: var(--aunt-toast-content-bottom);
+ left: 50%;
+ transform: translate(-50%,-50%);
+ }
+ &--spacing{
+ .@{class-prefix}-toast__content--text{
+ margin-left: var(--aunt-toast-content-text-margin-left);
+ }
+ }
+ &--text{
+ white-space: pre-wrap;
+ text-align: center;
+ word-wrap: break-word;
+ text-align: center;
+ font-size: var(--aunt-toast-content-text-size);
+ }
+ }
+}
diff --git a/src/components/toast/tests/index.test.tsx b/src/components/toast/tests/index.test.tsx
new file mode 100644
index 0000000..59dafcc
--- /dev/null
+++ b/src/components/toast/tests/index.test.tsx
@@ -0,0 +1,5 @@
+import '@testing-library/jest-dom';
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+
+describe('< />', () => {});
diff --git a/src/components/toast/toast.tsx b/src/components/toast/toast.tsx
new file mode 100644
index 0000000..9f06187
--- /dev/null
+++ b/src/components/toast/toast.tsx
@@ -0,0 +1,70 @@
+import React, { useMemo, FunctionComponent, useContext } from 'react';
+import ConfigProviderContext from '../config-provider/config-provider-context';
+import { AuntIconCheck, AuntIconX } from '../icon/icons';
+import Loading from '../loading';
+import { useNamespace } from '../../hooks';
+import { joinTrim, isFunction } from '../../utils';
+import type { ToastProps, ToastDirection, ToastIconSizeFunction } from './types';
+
+export const Toast: FunctionComponent = props => {
+ const {
+ type = 'info',
+ direction = 'vertical',
+ iconSize = (direction: ToastDirection) => {
+ if (direction === 'horizontal') return 20;
+ return 34;
+ },
+ icon,
+ loadingType = 'gap',
+ message,
+ position = 'center',
+ } = props;
+
+ const { prefix } = useContext(ConfigProviderContext);
+ const ns = useNamespace('toast', prefix);
+
+ const varClasses = useMemo(() => {
+ return joinTrim([ns.b(), !props.forbidClick ? ns.m('pointer') : '', props.className]);
+ }, [props.forbidClick, props.className]);
+
+ const renderIcon = () => {
+ const size =
+ isFunction(iconSize) && typeof iconSize === 'function'
+ ? iconSize(direction)
+ : (iconSize as number | string);
+ if (React.isValidElement(icon))
+ return React.cloneElement(icon, {
+ size: size,
+ ...icon.props,
+ });
+ if (type === 'loading') {
+ return ;
+ }
+ if (type === 'success') {
+ return ;
+ }
+ if (type === 'fail') {
+ return ;
+ }
+ return null;
+ };
+
+ const renderContent = () => {
+ return (
+
+ {renderIcon()}
+ {message}
+
+ );
+ };
+
+ return {renderContent()}
;
+};
diff --git a/src/components/toast/types.ts b/src/components/toast/types.ts
new file mode 100644
index 0000000..3a31885
--- /dev/null
+++ b/src/components/toast/types.ts
@@ -0,0 +1,69 @@
+import React from 'react';
+import type { BaseTypeProps } from '../../utils';
+import type { LoadingType } from '../loading';
+
+export type ToastType = 'loading' | 'success' | 'fail' | 'info';
+
+export type ToastPosition = 'top' | 'center' | 'bottom';
+
+export type ToastDirection = 'vertical' | 'horizontal';
+
+export type ToastIconSizeFunction = (direction: ToastDirection) => number | string;
+
+export interface ToastProps extends BaseTypeProps {
+ /** 提示类型 */
+ type?: ToastType;
+ /** 文本内容,支持通过\n换行 */
+ message?: number | string;
+ /** 展示时长(ms),值为 0 时,toast 不会消失 */
+ duration?: number;
+ /** 自定义图标 */
+ icon?: React.ReactNode;
+ /** 图标大小,如 20px 2em,默认单位为 px */
+ iconSize?: number | string | ToastIconSizeFunction;
+ /** 加载图标类型, 可选值为 spinner */
+ loadingType?: LoadingType;
+ /** 图标和文字的排列方式 */
+ direction?: ToastDirection;
+ /** 是否禁止背景点击 */
+ forbidClick?: boolean;
+ /** 位置,可选值为 top bottom */
+ position?: ToastPosition;
+ /** 轻提示弹出时的的父容器 */
+ teleport?: HTMLElement | (() => HTMLElement);
+ /** 关闭时的回调函数 */
+ onClose?: () => void;
+ /** 完全展示后的回调函数 */
+ onOpened?: () => void;
+}
+
+export type ToastOptions = Omit;
+
+export type ToastReturnType = {
+ /** 动态更新方法 */
+ config: React.Dispatch>;
+ /** 清除单例toast */
+ clear: () => void;
+};
+
+export interface ToastInstance {
+ (opts: ToastProps | string): ToastReturnType;
+ /** 文本提示 */
+ info(opts: ToastOptions | string): ToastReturnType;
+ /** 展示加载提示 */
+ loading(opts: ToastOptions | string): ToastReturnType;
+ /** 展示成功提示 */
+ success(opts: ToastOptions | string): ToastReturnType;
+ /** 展示失败提示 */
+ fail(opts: ToastOptions | string): ToastReturnType;
+ /**
+ * 修改默认配置,对所有 Toast 生效。
+ */
+ setDefaultOptions(type: ToastType, options: ToastProps): void;
+ /**
+ * 重置默认配置,对所有 Toast 生效。
+ */
+ resetDefaultOptions(type: ToastType): void;
+ /** 关闭提示 */
+ clear(): void;
+}
diff --git a/src/index.ts b/src/index.ts
index 0f89a27..678bd8e 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -36,3 +36,4 @@ export * from './components/steps';
export * from './components/tabbar';
export * from './components/back-top';
export * from './components/notify';
+export * from './components/toast';
diff --git a/src/utils/index.ts b/src/utils/index.ts
index 55dcc44..eaaad9c 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -2,6 +2,7 @@ export * from './format/unit';
export * from './format/string';
export * from './validate/number';
export * from './validate/string';
+export * from './validate/function';
export * from './base';
export * from './interface';
export * from './constant';
diff --git a/src/utils/validate/function.ts b/src/utils/validate/function.ts
new file mode 100644
index 0000000..f81bd93
--- /dev/null
+++ b/src/utils/validate/function.ts
@@ -0,0 +1,3 @@
+export function isFunction(fn: any) {
+ return Object.prototype.toString.call(fn) === '[object Function]';
+}