import { CSSProperties , useCallback , useRef , useState } from 'react' ; import { useEffectAfterMount } from '../../hooks/use-effect-after-mount' ; import { GetCollapsePropsInput , GetCollapsePropsOutput , GetTogglePropsInput , GetTogglePropsOutput , UseCollapseInput , UseCollapseOutput , } from './types' ; import { callAll , getElementHeight , mergeRefs , noop , raf } from '../../utils' ; import { useControllableState } from '../../hooks' ; const easeInOut = 'cubic-bezier(0.4, 0, 0.2, 1)' ; function getAutoHeightDuration ( height ) { if ( ! height || typeof height === 'string' ) { return 0 ; } const constant = height / 36 ; return Math . round ( ( 4 + 15 * constant ** 0.25 + constant / 5 ) * 10 ) ; } export function useCollapse ( { duration , easing = easeInOut , collapseStyles = { } , expandStyles = { } , onExpandStart = noop , onExpandEnd = noop , onCollapseStart = noop , onCollapseEnd = noop , isExpanded : isExpandedProp , defaultExpanded = false , ... initialConfig } = { } ) { const [ isExpanded , setExpanded ] = useControllableState ( { value : isExpandedProp , defaultValue : defaultExpanded , } ) ; const el = useRef < HTMLElement > ( null ) ; const collapsedHeight = `${ initialConfig . collapsedHeight || 0 }px` ; const collapsedStyles = { display : collapsedHeight === '0px' ? 'none' : 'block' , height : collapsedHeight , overflow : 'hidden' , } ; const [ styles , setStyles ] = useState < CSSProperties > ( isExpanded ? { } : collapsedStyles ) ; const mergeStyles = useCallback ( ( newStyles ) : void => { setStyles ( ( oldStyles ) => ( { ... oldStyles , ... newStyles } ) ) ; } , [ ] ) ; function getTransitionStyles ( height ) { const _duration = duration || getAutoHeightDuration ( height ) ; return { transition : `height ${ _duration }ms ${ easing }` , } ; } useEffectAfterMount ( ( ) => { if ( isExpanded ) { raf ( ( ) => { onExpandStart ( ) ; mergeStyles ( { ... expandStyles , willChange : 'height' , display : 'block' , overflow : 'hidden' , } ) ; raf ( ( ) => { const height = getElementHeight ( el ) ; console . log ( 'height' , height ) ; mergeStyles ( { ... getTransitionStyles ( height ) , height , } ) ; } ) ; } ) ; } else { raf ( ( ) => { onCollapseStart ( ) ; const height = getElementHeight ( el ) ; mergeStyles ( { ... collapseStyles , ... getTransitionStyles ( height ) , willChange : 'height' , height , } ) ; raf ( ( ) => { mergeStyles ( { height : collapsedHeight , overflow : 'hidden' , } ) ; } ) ; } ) ; } } , [ isExpanded ] ) ; const handleTransitionEnd = ( e ) : void => { if ( e . target !== el . current || e . propertyName !== 'height' ) { return ; } if ( isExpanded ) { const height = getElementHeight ( el ) ; if ( height === styles . height ) { setStyles ( { } ) ; } else { mergeStyles ( { height } ) ; } onExpandEnd ( ) ; } else if ( styles . height === collapsedHeight ) { setStyles ( collapsedStyles ) ; onCollapseEnd ( ) ; } } ; function getToggleProps ( { disabled = false , onClick = noop , ... rest } = { } ) { return { tabIndex : 0 , disabled , ... rest , onClick : disabled ? noop : callAll ( onClick , ( ) => setExpanded ( ( n ) => ! n ) ) , } ; } function getCollapseProps ( { style = { } , onTransitionEnd = noop , refKey = 'ref' , ... rest } = { } ) { const theirRef = rest [ refKey ] ; return { ... rest , [ refKey ] : mergeRefs ( el , theirRef ) , onTransitionEnd : callAll ( handleTransitionEnd , onTransitionEnd ) , style : { boxSizing : 'border-box' , ... style , ... styles , } , } ; } return { getToggleProps , getCollapseProps , isExpanded , setExpanded , } ; }