mirror of
https://github.com/woodchen-ink/clash-and-dashboard.git
synced 2025-07-18 05:51:56 +08:00
Migration: Select to hooks component
This commit is contained in:
parent
724cd44d69
commit
a92ee4862f
@ -1,4 +1,4 @@
|
||||
import * as React from 'react'
|
||||
import React, { useRef, useLayoutEffect, useState, useMemo } from 'react'
|
||||
import classnames from 'classnames'
|
||||
import { Icon } from '@components'
|
||||
import { BaseComponentProps } from '@models'
|
||||
@ -17,81 +17,59 @@ interface SelectProps extends BaseComponentProps {
|
||||
onSelect?: (value: OptionValue, e: React.MouseEvent<HTMLLIElement>) => void
|
||||
}
|
||||
|
||||
interface SelectState {
|
||||
dropdownListStyles: React.CSSProperties
|
||||
showDropDownList: boolean,
|
||||
hasCreateDropList: boolean
|
||||
}
|
||||
export function Select (props: SelectProps) {
|
||||
const { value, onSelect, children, className: cn, style } = props
|
||||
|
||||
export class Select extends React.Component<SelectProps, SelectState> {
|
||||
const portalRef = useRef<HTMLDivElement>()
|
||||
const attachmentRef = useRef<HTMLDivElement>()
|
||||
const targetRef = useRef<HTMLDivElement>()
|
||||
|
||||
// portal container
|
||||
$container: Element
|
||||
|
||||
// drop down list
|
||||
$attachment = React.createRef<HTMLDivElement>()
|
||||
|
||||
// target position element
|
||||
$target = React.createRef<HTMLDivElement>()
|
||||
|
||||
state = {
|
||||
dropdownListStyles: {},
|
||||
showDropDownList: false,
|
||||
hasCreateDropList: false
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
document.addEventListener('click', this.handleGlobalClick, true)
|
||||
this.setState({ dropdownListStyles: this.calculateAttachmentPosition() })
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
if (this.state.hasCreateDropList) {
|
||||
document.body.removeChild(this.$container)
|
||||
useLayoutEffect(() => {
|
||||
document.addEventListener('click', handleGlobalClick, true)
|
||||
return () => {
|
||||
document.addEventListener('click', handleGlobalClick, true)
|
||||
if (portalRef.current) {
|
||||
document.body.removeChild(portalRef.current)
|
||||
}
|
||||
}
|
||||
document.removeEventListener('click', this.handleGlobalClick, true)
|
||||
}
|
||||
}, [])
|
||||
|
||||
shouldComponentUpdate (nextProps, nextState) {
|
||||
if (nextProps.value === this.props.value && nextState.showDropDownList === this.state.showDropDownList) {
|
||||
return false
|
||||
const [showDropDownList, setShowDropDownList] = useState(false)
|
||||
const [hasCreateDropList, setHasCreateDropList] = useState(false)
|
||||
const dropdownListStyles = useMemo(() => {
|
||||
if (targetRef.current) {
|
||||
const targetRectInfo = targetRef.current.getBoundingClientRect()
|
||||
return {
|
||||
top: Math.floor(targetRectInfo.top) - 10,
|
||||
left: Math.floor(targetRectInfo.left) - 10
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return {}
|
||||
}, [])
|
||||
|
||||
handleShowDropList = () => {
|
||||
if (!this.state.hasCreateDropList) {
|
||||
// create container element
|
||||
const container = document.createElement('div')
|
||||
document.body.appendChild(container)
|
||||
this.$container = container
|
||||
this.setState({
|
||||
hasCreateDropList: true
|
||||
})
|
||||
}
|
||||
this.setState({
|
||||
showDropDownList: true
|
||||
})
|
||||
}
|
||||
|
||||
private handleGlobalClick = (e) => {
|
||||
const el = this.$attachment.current
|
||||
function handleGlobalClick (e) {
|
||||
const el = attachmentRef.current
|
||||
|
||||
if (el && !el.contains(e.target)) {
|
||||
this.setState({ showDropDownList: false })
|
||||
setShowDropDownList(false)
|
||||
}
|
||||
}
|
||||
|
||||
private calculateAttachmentPosition () {
|
||||
const targetRectInfo = this.$target.current.getBoundingClientRect()
|
||||
|
||||
return {
|
||||
top: Math.floor(targetRectInfo.top) - 10,
|
||||
left: Math.floor(targetRectInfo.left) - 10
|
||||
function handleShowDropList (e) {
|
||||
if (!hasCreateDropList) {
|
||||
if (!portalRef.current) {
|
||||
// create container element
|
||||
const container = document.createElement('div')
|
||||
document.body.appendChild(container)
|
||||
portalRef.current = container
|
||||
}
|
||||
setHasCreateDropList(true)
|
||||
}
|
||||
setShowDropDownList(true)
|
||||
}
|
||||
|
||||
private getSelectedOption = (value: OptionValue, children: React.ReactNode) => {
|
||||
const matchChild = useMemo(() => {
|
||||
let matchChild: React.ReactElement<any> = null
|
||||
|
||||
React.Children.forEach(children, (child: React.ReactElement<any>) => {
|
||||
@ -101,13 +79,9 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
||||
})
|
||||
|
||||
return matchChild
|
||||
}
|
||||
}, [value, children])
|
||||
|
||||
private hookChildren = (
|
||||
children: React.ReactNode,
|
||||
value: OptionValue,
|
||||
onSelect: SelectProps['onSelect']
|
||||
) => {
|
||||
const hookedChildren = useMemo(() => {
|
||||
return React.Children.map(children, (child: React.ReactElement<any>) => {
|
||||
if (!child.props || !child.type) {
|
||||
return child
|
||||
@ -123,43 +97,40 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
||||
return React.cloneElement(child, Object.assign({}, child.props, {
|
||||
onClick: (e: React.MouseEvent<HTMLLIElement>) => {
|
||||
onSelect(child.props.value, e)
|
||||
this.setState({ showDropDownList: false })
|
||||
setShowDropDownList(false)
|
||||
rawOnClickEvent && rawOnClickEvent(e)
|
||||
},
|
||||
className
|
||||
}))
|
||||
})
|
||||
}
|
||||
}, [children, value, onSelect])
|
||||
|
||||
render () {
|
||||
const { value, onSelect, children, className: cn, style } = this.props
|
||||
const { dropdownListStyles, showDropDownList, hasCreateDropList } = this.state
|
||||
const matchChild = this.getSelectedOption(value, children)
|
||||
const dropDownList = (
|
||||
<div
|
||||
className={classnames('select-list', { 'select-list-show': showDropDownList })}
|
||||
ref={this.$attachment}
|
||||
style={dropdownListStyles}
|
||||
>
|
||||
<ul className="list">
|
||||
{this.hookChildren(children, value, onSelect)}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
const dropDownList = (
|
||||
<div
|
||||
className={classnames('select-list', { 'select-list-show': showDropDownList })}
|
||||
ref={attachmentRef}
|
||||
style={dropdownListStyles}
|
||||
>
|
||||
<ul className="list">
|
||||
{ hookedChildren }
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
|
||||
return <>
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={classnames('select', cn)}
|
||||
style={style}
|
||||
ref={this.$target}
|
||||
onClick={this.handleShowDropList}
|
||||
ref={targetRef}
|
||||
onClick={handleShowDropList}
|
||||
>
|
||||
{matchChild && matchChild.props && matchChild.props.children}
|
||||
<Icon type="triangle-down" />
|
||||
</div>
|
||||
{hasCreateDropList && createPortal(dropDownList, this.$container)}
|
||||
{hasCreateDropList && createPortal(dropDownList, portalRef.current)}
|
||||
</>
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
interface OptionProps extends BaseComponentProps {
|
||||
@ -169,13 +140,11 @@ interface OptionProps extends BaseComponentProps {
|
||||
onClick?: (e: React.MouseEvent<HTMLLIElement>) => void
|
||||
}
|
||||
|
||||
export class Option extends React.Component<OptionProps, {}> {
|
||||
render () {
|
||||
const { className: cn, style, key, disabled = false, children, onClick = () => {} } = this.props
|
||||
const className = classnames('option', { disabled }, cn)
|
||||
export const Option: React.SFC<OptionProps> = props => {
|
||||
const { className: cn, style, key, disabled = false, children, onClick = () => {} } = props
|
||||
const className = classnames('option', { disabled }, cn)
|
||||
|
||||
return (
|
||||
<li className={className} style={style} key={key} onClick={onClick}>{children}</li>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<li className={className} style={style} key={key} onClick={onClick}>{children}</li>
|
||||
)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user