Files
react/components/shared/expand.tsx
2020-05-06 14:25:10 +08:00

95 lines
2.5 KiB
TypeScript

import React, { useEffect, useRef, useState } from 'react'
import withDefaults from '../utils/with-defaults'
import useRealShape from '../utils/use-real-shape'
interface Props {
isExpanded?: boolean
delay?: number
}
const defaultProps = {
isExpanded: false,
delay: 200,
}
export type ExpandProps = Props & typeof defaultProps
const Expand: React.FC<React.PropsWithChildren<ExpandProps>> = ({
isExpanded,
delay,
children,
}) => {
const [height, setHeight] = useState<string>(isExpanded ? 'auto' : '0')
const [selfExpanded, setSelfExpanded] = useState<boolean>(isExpanded)
const [visible, setVisible] = useState<boolean>(isExpanded)
const contentRef = useRef<HTMLDivElement>(null)
const entryTimer = useRef<number>()
const leaveTimer = useRef<number>()
const resetTimer = useRef<number>()
const [state, updateShape] = useRealShape<HTMLDivElement>(contentRef)
useEffect(() => setHeight(`${state.height}px`), [state.height])
useEffect(() => {
// show element or reset height.
// force an update once manually, even if the element does not change.
// (the height of the element might be "auto")
if (isExpanded) {
setVisible(isExpanded)
} else {
updateShape()
setHeight(`${state.height}px`)
}
// show expand animation
entryTimer.current = window.setTimeout(() => {
setSelfExpanded(isExpanded)
clearTimeout(entryTimer.current)
}, 30)
// Reset height after animation
if (isExpanded) {
resetTimer.current = window.setTimeout(() => {
setHeight('auto')
clearTimeout(resetTimer.current)
}, delay)
} else {
leaveTimer.current = window.setTimeout(() => {
setVisible(isExpanded)
clearTimeout(leaveTimer.current)
}, delay / 2)
}
return () => {
clearTimeout(entryTimer.current)
clearTimeout(leaveTimer.current)
clearTimeout(resetTimer.current)
}
}, [isExpanded])
return (
<div className={`container ${selfExpanded ? 'expanded' : ''}`}>
<div ref={contentRef} className="content">
{children}
</div>
<style jsx>{`
.container {
padding: 0;
margin: 0;
height: 0;
overflow: hidden;
visibility: ${visible ? 'visible' : 'hidden'};
transition: height ${delay}ms ease;
}
.expanded {
height: ${height};
visibility: visible;
}
`}</style>
</div>
)
}
export default withDefaults(Expand, defaultProps)