From e201679f59835348d58801ade302149f29c09e54 Mon Sep 17 00:00:00 2001 From: yewenwen <357595032@qq.com> Date: Fri, 25 Jun 2021 10:36:54 +0800 Subject: [PATCH] feat: toast test --- src/config.json | 13 ++- src/packages/toast/Notification.tsx | 146 ++++++++++++++++++++++++++++ src/packages/toast/_toast.tsx | 136 ++++++++++++++++++++++++++ src/packages/toast/demo.tsx | 24 +++++ src/packages/toast/doc.md | 33 +++++++ src/packages/toast/index.ts | 2 + src/packages/toast/test.tsx | 107 ++++++++++++++++++++ src/packages/toast/toast.scss | 91 +++++++++++++++++ src/packages/toast/toast.tsx | 142 +++++++++++++++++++++++++++ 9 files changed, 693 insertions(+), 1 deletion(-) create mode 100644 src/packages/toast/Notification.tsx create mode 100644 src/packages/toast/_toast.tsx create mode 100644 src/packages/toast/demo.tsx create mode 100644 src/packages/toast/doc.md create mode 100644 src/packages/toast/index.ts create mode 100644 src/packages/toast/test.tsx create mode 100644 src/packages/toast/toast.scss create mode 100644 src/packages/toast/toast.tsx diff --git a/src/config.json b/src/config.json index b006a84..f15bcf1 100644 --- a/src/config.json +++ b/src/config.json @@ -97,7 +97,18 @@ }, { "name": "操作反馈", - "packages": [] + "packages": [ + { + "version": "1.0.0", + "name": "Toast", + "type": "component", + "cName": "吐司", + "desc": "轻提示", + "sort": 1, + "show": true, + "author": "VickyYe" + } + ] }, { "name": "基础组件", diff --git a/src/packages/toast/Notification.tsx b/src/packages/toast/Notification.tsx new file mode 100644 index 0000000..12eaf7f --- /dev/null +++ b/src/packages/toast/Notification.tsx @@ -0,0 +1,146 @@ +import * as React from 'react' +import * as ReactDOM from 'react-dom' +// import { CSSTransition } from 'react-transition-group' +// import classNames from 'classnames' +// import Mask from '../mask' +import Icon from '../icon/index' +import './toast.scss' +export interface NotificationProps { + prefixCls?: string + style?: React.CSSProperties + icon?: string + message: string | React.ReactNode + bottom?: boolean + duration?: number + onClose: () => void + className?: string + mask?: boolean +} + +interface State { + show: boolean +} + +export default class Notification extends React.PureComponent { + static defaultProps = { + prefixCls: 'nut-toast', + style: {}, + //show:false, + bottom: false, + duration: 1.5, + mask: false, + } + private closeTimer: number | undefined + static newInstance: (properties: NotificationProps, callback: any) => void + constructor(props: NotificationProps) { + super(props) + this.close = this.close.bind(this) + this.startCloseTimer = this.startCloseTimer.bind(this) + this.clearCloseTimer = this.clearCloseTimer.bind(this) + this.close = this.close.bind(this) + this.state = { + show: true, + } + } + + close() { + this.setState({ + show: false, + }) + this.clearCloseTimer() + this.props.onClose() + } + + startCloseTimer() { + const { duration } = this.props + if (duration) { + this.closeTimer = window.setTimeout(() => { + this.close() + }, duration * 1000) + } + } + // hasIcon() { + // if (type !== 'text') { + // return true + // } else { + // return !!icon + // } + // } + // toastBodyClass() { + // return ` + // nut-toast + // ${center ? 'nut-toast-center' : ''} + // ${hasIcon() ? 'nut-toast-has-icon' : ''} + // ${cover ? 'nut-toast-cover' : ''} + // ${type === 'loading' ? 'nut-toast-loading' : ''} + // ${customClass ? customClass : ''} + // ${size ? 'nut-toast-' + size : ''} + // ` + // } + + clearCloseTimer() { + if (this.closeTimer) { + clearTimeout(this.closeTimer) + this.closeTimer = -1 + } + } + + componentDidMount() { + this.startCloseTimer() + } + + componentWillUnmount() { + this.clearCloseTimer() + } + + render() { + const { className, prefixCls, style, icon, message, bottom, mask } = this.props + const { show } = this.state + // const cls = classNames(prefixCls, className, `${prefixCls}-mask`, { + // [`${prefixCls}-bottom`]: bottom, + // }) + return ( + <> +
+
+ + + +
+ + {message} +
+
+ + ) + } +} + +Notification.newInstance = (properties, callback) => { + const element = document.createElement('div') + document.body.appendChild(element) + + let called = false + function ref(instance: any) { + if (called) { + return + } + called = true + + callback({ + component: instance, + destroy() { + ReactDOM.unmountComponentAtNode(element) + element && element.parentNode && element.parentNode.removeChild(element) + }, + }) + } + + ReactDOM.render(, element) +} diff --git a/src/packages/toast/_toast.tsx b/src/packages/toast/_toast.tsx new file mode 100644 index 0000000..5dbc0d0 --- /dev/null +++ b/src/packages/toast/_toast.tsx @@ -0,0 +1,136 @@ +import React, { ReactHTML } from 'react' +import ReactDOM from 'react-dom' +import { Toast } from './toast' +// import ToastContainer from './toastContainer' +let instance: any +type type = 'text' | 'success' | 'fail' | 'hide' | 'warn' | 'loading' +const noticeType: type[] = ['text', 'success', 'fail', 'hide', 'warn', 'loading'] +export interface IToastProps { + msg: string + id?: string + duration?: number + center?: boolean + type?: string + customClass?: string + bottom?: number + size?: string + icon?: string + textAlignCenter?: boolean + loadingRotate?: boolean + bgColor?: string + onClose?: () => void + unmount?: () => void + cover?: boolean + coverColor?: string + closeOnClickOverlay?: boolean +} +const defaultOptions = { + msg: '', + id: '', + duration: 2000, //显示时间(毫秒) + center: true, + type: 'text', + customClass: '', + bottom: 30, + size: 'base', + icon: '', + textAlignCenter: true, + loadingRotate: true, + bgColor: 'rgba(0, 0, 0, .8)', + onClose: () => {}, + unmount: () => {}, + cover: false, //透明遮罩层 + coverColor: 'rgba(0, 0, 0, 0)', + closeOnClickOverlay: false, +} + +export type toastProps = Partial +interface ConfigOptions { + duration?: number +} +type onClose = () => void +type open = (content: string | IToastProps, duration?: number, onClose?: onClose) => void + +interface toast { + id: number + config: (ConfigOptions: ConfigOptions) => void + open: (toastProps: IToastProps, duration?: number, onClose?: onClose) => void + success: open + fail: open + text: open + warn: open + loading: open + hide: open +} + +const toast: toast = { + id: 0, + config: (options: ConfigOptions) => { + if (typeof options.duration === 'number') { + Object.assign(defaultOptions, { duration: options.duration }) + } + }, + open(config: IToastProps) { + let messageProps: IToastProps = { ...defaultOptions, ...config }, + key: number | string = toast.id++ + if (!config.msg) { + throw new Error('content defined error') + } else { + key = config.id || key + } + let noticeInstance: any = null + + const noticePromise = new Promise((resolve, reject) => { + const { onClose, ...rest } = messageProps + console.log(messageProps) + + const message = + const div = React.createElement('div', null, message) + + let mesagecontent = { key, content: message } + console.log(mesagecontent) + }) + + const result = () => { + console.log(config) + if (noticeInstance) { + noticeInstance.close() + } + } + result.then = (filled: any, rejected: any) => noticePromise.then(filled, rejected) + + return result + }, + success: setType('success'), + text: setType('text'), + fail: setType('fail'), + warn: setType('warn'), + loading: setType('loading'), + hide: setType('hide'), +} +function setType(type: string): open { + return ( + config: IToastProps | string, + duration?: number, + onClose?: (key: string | number) => void + ) => { + if (typeof config === 'string') { + const option = { msg: config, type } + if (typeof duration === 'function') { + Object.assign(option, { onClose: duration }) + return toast.open(option) + } else if (typeof duration === 'number') { + Object.assign(option, { duration }) + } + if (onClose !== undefined) { + Object.assign(option, { onClose }) + } + return toast.open(option) + } else { + Object.assign(config, { type }) + return toast.open(config) + } + } +} + +export { toast } diff --git a/src/packages/toast/demo.tsx b/src/packages/toast/demo.tsx new file mode 100644 index 0000000..e29a61d --- /dev/null +++ b/src/packages/toast/demo.tsx @@ -0,0 +1,24 @@ +import React from 'react' +// import { Toast } from './toast' +import Toast from './index' + +const ToastDemo = () => { + return ( + <> +
+

基础用法

+
{ + console.log(111, Toast) + console.log(111, Toast.success('这是一个特别长的提示!!!')) + }} + > + click me +
+ {/* */} +
+ + ) +} + +export default ToastDemo diff --git a/src/packages/toast/doc.md b/src/packages/toast/doc.md new file mode 100644 index 0000000..7ea68cf --- /dev/null +++ b/src/packages/toast/doc.md @@ -0,0 +1,33 @@ +# Toast组件 + +### 介绍 + +基于 xxxxxxx + +### 安装 + + + +## 代码演示 + +### 基础用法1 + + + +## API + +### Props + +| 参数 | 说明 | 类型 | 默认值 | +|--------------|----------------------------------|--------|------------------| +| name | 图标名称或图片链接 | String | - | +| color | 图标颜色 | String | - | +| size | 图标大小,如 '20px' '2em' '2rem' | String | - | +| class-prefix | 类名前缀,用于使用自定义图标 | String | 'nutui-iconfont' | +| tag | HTML 标签 | String | 'i' | + +### Events + +| 事件名 | 说明 | 回调参数 | +|--------|----------------|--------------| +| click | 点击图标时触发 | event: Event | diff --git a/src/packages/toast/index.ts b/src/packages/toast/index.ts new file mode 100644 index 0000000..8ee157f --- /dev/null +++ b/src/packages/toast/index.ts @@ -0,0 +1,2 @@ +import Toast from './test' +export default Toast diff --git a/src/packages/toast/test.tsx b/src/packages/toast/test.tsx new file mode 100644 index 0000000..3a5ad7f --- /dev/null +++ b/src/packages/toast/test.tsx @@ -0,0 +1,107 @@ +import * as React from 'react' +// import CheckCircleOutlined from '@jdcfe/icons-react/CheckCircleOutlined' +// import TipOutlined from '@jdcfe/icons-react/TipOutlined' +// import Icon from '../icon' +import Icon from '../icon/index' +import Notification, { NotificationProps } from './Notification' +// const { JiaZai } = Icon +let messageInstance: any = null +interface IToastOptions { + duration: number + mask: boolean +} +const SHORT = 3 +const options: IToastOptions = { + duration: SHORT, + mask: false, +} + +function getInstance(props: NotificationProps, callback: (notification: any) => void) { + if (messageInstance) { + messageInstance.destroy() + messageInstance = null + } + + Notification.newInstance(props, (notification: any) => { + return callback && callback(notification) + }) +} + +function notice( + message: string | React.ReactNode, + icon: any, + duration = options.duration, + onClose: (() => void) | undefined | null, + mask = options.mask +) { + function close() { + if (messageInstance) { + messageInstance.destroy() + messageInstance = null + } + if (onClose) { + onClose() + } + } + + getInstance( + { + message, + icon, + duration, + onClose: close, + mask, + }, + (notification: any) => { + messageInstance = notification + } + ) +} + +export default { + SHORT, + LONG: 8, + text(message: string | React.ReactNode, duration?: number, onClose?: () => void, mask?: boolean) { + return notice(message, null, duration, onClose, mask) + }, + success( + message: string | React.ReactNode, + duration?: number, + icon?: string, + onClose?: () => void, + mask?: boolean + ) { + return notice(message, 'JD', duration, onClose, mask) + }, + fail( + message: string | React.ReactNode, + icon?: string, + duration?: number, + onClose?: () => void, + mask?: boolean + ) { + return notice(message, icon, duration, onClose, mask) + }, + loading( + message: string | React.ReactNode, + duration?: number, + icon?: string, + onClose?: () => void, + mask?: boolean + ) { + return notice(message, icon, duration, onClose, mask) + }, + hide() { + if (messageInstance) { + messageInstance.destroy() + messageInstance = null + } + }, + config(option: Partial = {}) { + const { duration = SHORT, mask } = option + options.duration = duration + if (mask === true) { + options.mask = true + } + }, +} diff --git a/src/packages/toast/toast.scss b/src/packages/toast/toast.scss new file mode 100644 index 0000000..6dd6bfd --- /dev/null +++ b/src/packages/toast/toast.scss @@ -0,0 +1,91 @@ +@keyframes rotation { + 0% { + -webkit-transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + } +} + +.nut-toast { + position: fixed; + left: 0; + bottom: 150px; + width: 100%; + text-align: center; + pointer-events: none; + z-index: 9999; + font-family: $font-family; + &-small { + .nut-toast-inner { + font-size: $font-size-small; + } + } + &-large { + .nut-toast-inner { + font-size: $font-size-large; + } + } + &-cover { + display: flex; + align-items: center; + justify-content: center; + pointer-events: auto; + height: 100%; + } + &-inner { + display: inline-block; + font-size: $font-size-base; + min-width: 40%; + max-width: 65%; + text-align: center; + padding: 24px 30px; + word-break: break-all; + background: rgba(0, 0, 0, 0); + border-radius: 12px; + color: $white; + } + &-text { + font-size: 14px; + &:empty { + margin-bottom: -8px; + } + } + &-has-icon { + .nut-toast-icon-wrapper { + width: 100%; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 8px; + } + } + &-center { + top: 50%; + transform: translateY(-50%); + } + &-loading { + .nut-toast-inner { + display: inline-flex; + flex-direction: column; + justify-content: center; + align-items: center; + } + .nut-toast-icon-wrapper { + animation: rotation 2s linear infinite; + } + } +} + +.toast-fade-enter-active { + transition: opacity 0.3s; +} + +.toast-fade-leave-active { + transition: opacity 0.3s; +} + +.toast-fade-enter-from, +.toast-fade-leave-to { + opacity: 0; +} diff --git a/src/packages/toast/toast.tsx b/src/packages/toast/toast.tsx new file mode 100644 index 0000000..bf5c68a --- /dev/null +++ b/src/packages/toast/toast.tsx @@ -0,0 +1,142 @@ +import React, { FunctionComponent, useState, useLayoutEffect } from 'react' +import bem from '@/utils/bem' +import './toast.scss' +import Icon from '../icon/index' +export interface ToastProps { + msg: string + id?: string + duration?: number + center?: boolean + type?: string + customClass?: string + bottom?: number + size?: string + icon?: string + textAlignCenter?: boolean + loadingRotate?: boolean + bgColor?: string + onClose?: (params?: string | number) => void + unmount?: () => void + cover?: boolean + coverColor?: string + closeOnClickOverlay?: boolean + noticeKey: number | string +} +const defaultProps = { + msg: '', + id: '', + duration: 2000, //显示时间(毫秒) + center: true, + type: 'text', + customClass: '', + bottom: 30, + size: 'base', + icon: '', + textAlignCenter: true, + loadingRotate: true, + bgColor: 'rgba(0, 0, 0, .8)', + onClose: (params?: string | number) => {}, + unmount: () => {}, + cover: false, //透明遮罩层 + coverColor: 'rgba(0, 0, 0, 0)', + closeOnClickOverlay: false, +} as ToastProps + +export const Toast: FunctionComponent> = (props) => { + const { + msg, + id, + duration, + center, + type, + customClass, + bottom, + size, + icon, + textAlignCenter, + loadingRotate, + bgColor, + onClose, + cover, + coverColor, + closeOnClickOverlay, + noticeKey, + } = { ...defaultProps, ...props } + const [mounted, SetMAounted] = useState(false) + const [isRef, SetRef] = useState(false) + const [value, SetValue] = useState(false) + let timer: any + + useLayoutEffect(() => { + if (duration) { + show() + } + }, []) + const hasIcon = () => { + if (type !== 'text') { + return true + } else { + return !!icon + } + } + const toastBodyClass = () => { + return ` + nut-toast + ${center ? 'nut-toast-center' : ''} + ${hasIcon() ? 'nut-toast-has-icon' : ''} + ${cover ? 'nut-toast-cover' : ''} + ${type === 'loading' ? 'nut-toast-loading' : ''} + ${customClass ? customClass : ''} + ${size ? 'nut-toast-' + size : ''} + ` + } + const clickCover = () => { + if (closeOnClickOverlay) { + hide() + } + } + const hide = () => { + SetMAounted(false) + } + const show = () => { + SetMAounted(true) + clearTimer() + if (duration) { + timer = setTimeout(() => { + hide() + }, duration) + } + } + const clearTimer = () => { + if (timer) { + clearTimeout(timer) + timer = null + } + } + + const alignStyle = () => { + return center ? 'auto' : bottom + 'px' + } + + return mounted ? ( +
+
+ {hasIcon() ? ( + + + + ) : null} + {msg} +
+
+ ) : null +} + +Toast.defaultProps = defaultProps +Toast.displayName = 'NutToast' -- GitLab