pkg-components
Version:
68 lines (61 loc) • 1.96 kB
text/typescript
'use client'
import React from 'react'
/**
* useSlideTransition
*
* Small state machine for mount/unmount + transition stages.
*
* @param params.open - whether the panel should be open
* @param params.duration - animation duration in ms (default 400)
*/
export const useSlideTransition = ({
open,
duration = 400
}: {
open: boolean
duration?: number
}): {
mounted: boolean
stage: 'entering' | 'entered' | 'exiting' | 'exited'
duration: number
} => {
const [stage, setStage] = React.useState<
'entering' | 'entered' | 'exiting' | 'exited'
>(() => (open ? 'entering' : 'exited'))
React.useEffect(() => {
let enterTimeout: number | undefined
let finishEnterRAF: number | undefined
let exitTimeout: number | undefined
if (open) {
if (stage === 'exited') {
setStage('entering')
finishEnterRAF = window.requestAnimationFrame(() => {
enterTimeout = window.setTimeout(() => setStage('entered'), 20)
})
} else if (stage === 'exiting') {
setStage('entering')
finishEnterRAF = window.requestAnimationFrame(() => {
enterTimeout = window.setTimeout(() => setStage('entered'), 20)
})
} else if (stage === 'entering') {
enterTimeout = window.setTimeout(() => setStage('entered'), duration + 20)
}
} else {
if (stage === 'entered' || stage === 'entering') {
setStage('exiting')
exitTimeout = window.setTimeout(() => setStage('exited'), duration)
}
}
return () => {
if (enterTimeout) window.clearTimeout(enterTimeout)
if (exitTimeout) window.clearTimeout(exitTimeout)
if (finishEnterRAF) window.cancelAnimationFrame(finishEnterRAF)
}
// depend on open & duration only (stage handled internally)
}, [open, duration])
return {
mounted: stage !== 'exited',
stage,
duration
}
}