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