Migration: Message & Modal to hooks component

This commit is contained in:
Dreamacro 2019-07-02 23:22:19 +08:00
parent 659f41ec5a
commit 724cd44d69
3 changed files with 103 additions and 129 deletions

View File

@ -1,10 +1,10 @@
import * as React from 'react' import React, { useState, useLayoutEffect } from 'react'
import { Icon } from '@components'
import classnames from 'classnames' import classnames from 'classnames'
import { unmountComponentAtNode, render } from 'react-dom' import { unmountComponentAtNode, render } from 'react-dom'
import { Icon } from '@components'
import { noop } from '@lib/helper'
import './style.scss' import './style.scss'
const noop = () => {}
const TYPE_ICON_MAP = { const TYPE_ICON_MAP = {
info: 'info', info: 'info',
success: 'check', success: 'check',
@ -30,74 +30,63 @@ interface MessageProps {
removeComponent: typeof noop removeComponent: typeof noop
} }
export class Message extends React.Component <MessageProps, {}> { export function Message (props: MessageProps) {
const {
removeComponent = noop,
onClose = noop,
icon = <Icon type="info" size={16} />,
content = '',
type = 'info',
duration = 1500
} = props
/** const [visible, setVisible] = useState(false)
* static function to call Message directly
*/
static info = (
content: string,
duration?: number,
onClose?: typeof noop
) => showMessage({ type: 'info', content, duration, onClose })
static success = ( useLayoutEffect(() => {
content: string, window.setTimeout(() => setVisible(true), 0)
duration?: number,
onClose?: typeof noop
) => showMessage({ type: 'success', content, duration, onClose })
static warning = ( const id = window.setTimeout(() => {
content: string, setVisible(false)
duration?: number, onClose()
onClose?: typeof noop }, duration)
) => showMessage({ type: 'warning', content, duration, onClose }) return () => window.clearTimeout(id)
}, [])
static error = (
content: string,
duration?: number,
onClose?: typeof noop
) => showMessage({ type: 'error', content, duration, onClose })
static defaultProps: MessageProps = {
content: '',
type: 'info',
icon: <Icon type="info" size={16} />,
duration: 1500,
onClose: noop,
removeComponent: noop
}
state = {
visible: false
}
componentDidMount () {
// TODO: optimize animation
// fix do not show animation when element mounted
setTimeout(() => this.setState({ visible: true }), 0)
setTimeout(() => {
this.setState({ visible: false })
this.props.onClose()
}, this.props.duration)
}
render () {
const { removeComponent, icon, content, type } = this.props
return ( return (
<div <div
className={classnames('message', `message-${type}`, { 'message-show': this.state.visible })} className={classnames('message', `message-${type}`, { 'message-show': visible })}
onTransitionEnd={() => !this.state.visible && removeComponent()} onTransitionEnd={() => !visible && removeComponent()}
> >
<span className="message-icon">{icon}</span> <span className="message-icon">{icon}</span>
<span className="message-content">{content}</span> <span className="message-content">{content}</span>
</div> </div>
) )
}
} }
export const info = (
content: string,
duration?: number,
onClose?: typeof noop
) => showMessage({ type: 'info', content, duration, onClose })
export const success = (
content: string,
duration?: number,
onClose?: typeof noop
) => showMessage({ type: 'success', content, duration, onClose })
export const warning = (
content: string,
duration?: number,
onClose?: typeof noop
) => showMessage({ type: 'warning', content, duration, onClose })
export const error = (
content: string,
duration?: number,
onClose?: typeof noop
) => showMessage({ type: 'error', content, duration, onClose })
export function showMessage (args: ArgsProps) { export function showMessage (args: ArgsProps) {
// create container element // create container element
const container = document.createElement('div') const container = document.createElement('div')

View File

@ -1,12 +1,11 @@
import * as React from 'react' import React, { useRef, useLayoutEffect } from 'react'
import classnames from 'classnames' import classnames from 'classnames'
import { createPortal } from 'react-dom' import { createPortal } from 'react-dom'
import { BaseComponentProps } from '@models' import { BaseComponentProps } from '@models'
import { Button } from '@components' import { Button } from '@components'
import { noop } from '@lib/helper'
import './style.scss' import './style.scss'
const noop = () => {}
interface ModalProps extends BaseComponentProps { interface ModalProps extends BaseComponentProps {
// show modal // show modal
show?: boolean show?: boolean
@ -33,57 +32,44 @@ interface ModalProps extends BaseComponentProps {
onClose?: typeof noop onClose?: typeof noop
} }
export class Modal extends React.Component<ModalProps, {}> { export function Modal (props: ModalProps) {
const {
show = true,
title = 'Modal',
size = 'small',
footer= true,
onOk = noop,
onClose = noop,
bodyClassName,
bodyStyle,
className,
style,
children
} = props
static defaultProps: ModalProps = { const portalRef = useRef<HTMLDivElement>(document.createElement('div'))
show: true, const maskRef = useRef<HTMLDivElement>()
title: 'Modal',
size: 'small',
footer: true,
onOk: noop,
onClose: noop
}
// portal container useLayoutEffect(() => {
$container: Element document.body.appendChild(portalRef.current)
return () => document.body.removeChild(portalRef.current)
}, [])
$modal = React.createRef<HTMLDivElement>() function handleMaskClick (e) {
if (e.target === maskRef.current) {
$mask = React.createRef<HTMLDivElement>()
constructor (props) {
super(props)
// create container element
const container = document.createElement('div')
document.body.appendChild(container)
this.$container = container
}
componentWillUnmount () {
document.body.removeChild(this.$container)
}
private handleMaskClick = (e) => {
const { onClose } = this.props
if (e.target === this.$mask) {
onClose() onClose()
} }
} }
render () {
const { show, size, title, footer, children, className, bodyClassName, style, bodyStyle, onOk, onClose } = this.props
const modal = ( const modal = (
<div <div
className={classnames('modal-mask', { 'modal-show': show })} className={classnames('modal-mask', { 'modal-show': show })}
ref={this.$mask} ref={maskRef}
onClick={this.handleMaskClick} onClick={handleMaskClick}
> >
<div <div
className={classnames('modal', `modal-${size}`, className)} className={classnames('modal', `modal-${size}`, className)}
style={style} style={style}
ref={this.$modal}
> >
<div className="modal-title">{title}</div> <div className="modal-title">{title}</div>
<div <div
@ -102,6 +88,5 @@ export class Modal extends React.Component<ModalProps, {}> {
</div> </div>
) )
return createPortal(modal, this.$container) return createPortal(modal, portalRef.current)
}
} }

View File

@ -1,4 +1,4 @@
import React, { useState, useRef, useEffect } from 'react' import React, { useState, useRef, useLayoutEffect } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { BaseComponentProps, I18nProps } from '@models' import { BaseComponentProps, I18nProps } from '@models'
import { noop } from '@lib/helper' import { noop } from '@lib/helper'
@ -21,7 +21,7 @@ export function Tags (props: TagsProps) {
const [showExtend, setShowExtend] = useState(false) const [showExtend, setShowExtend] = useState(false)
const ulRef = useRef<HTMLUListElement>() const ulRef = useRef<HTMLUListElement>()
useEffect(() => { useLayoutEffect(() => {
setShowExtend(ulRef.current.offsetHeight > 30) setShowExtend(ulRef.current.offsetHeight > 30)
}, []) }, [])