@attio/react-native-bottom-sheet-toolbox
Version:
117 lines (96 loc) • 4.32 kB
text/typescript
import {type SharedValue, useDerivedValue} from "react-native-reanimated"
import {useBottomSheetToolboxInternalContext, useKeyboardHeightSharedValue} from "../context"
import {useFooterFreezeWhenExpanding} from "./use-footer-freeze-when-expanding"
import {useLastSharedValueBeforeLock} from "./use-last-shared-value-before-lock"
export interface UseFooterBottomPositionSharedValueArgs {
/**
* A shared value that tells the footer how it should be moved with the
* sheet. By default, this is the internal children height of the sheet.
*/
bufferSharedValue?: SharedValue<number | null> | undefined
/**
* Whether the footer should be adjusted for the keyboard.
*/
disableKeyboardAdjust?: boolean | undefined
/**
* Whether the footer should freeze its position when the sheet is expanding.
* This is useful when adding or removing items and is the default behavior.
*/
freezeWhenExpanding?: boolean | undefined
}
/**
* A shared value used as the bottom position for a footer.
*/
export function useFooterBottomPositionSharedValue({
bufferSharedValue: outerBufferSharedValue,
disableKeyboardAdjust = false,
freezeWhenExpanding = true,
}: UseFooterBottomPositionSharedValueArgs) {
const {
isReadySharedValue,
currentPositionSharedValue,
childrenHeightSharedValue,
wrapperHeightSharedValue,
footerHeightSharedValue,
} = useBottomSheetToolboxInternalContext()
const keyboardHeightSharedValue = useKeyboardHeightSharedValue()
const bufferSharedValue = useDerivedValue(() => {
if (outerBufferSharedValue) return outerBufferSharedValue.get()
const childrenHeight = childrenHeightSharedValue.get()
const wrapperHeight = wrapperHeightSharedValue.get()
const footerHeight = footerHeightSharedValue.get()
if (childrenHeight === null || wrapperHeight === null || footerHeight === null) {
return null
}
return Math.min(childrenHeight, wrapperHeight - footerHeight)
}, [
outerBufferSharedValue,
childrenHeightSharedValue,
wrapperHeightSharedValue,
footerHeightSharedValue,
])
const rawBottomSharedValue = useDerivedValue(() => {
const footerHeight = footerHeightSharedValue.get()
const buffer = bufferSharedValue.get()
if (!isReadySharedValue.get() || buffer === null || footerHeight === null) {
return null
}
const keyboardAdjust = disableKeyboardAdjust ? 0 : keyboardHeightSharedValue.get()
const unclampedClosingOffset =
currentPositionSharedValue.get() - footerHeight - buffer - keyboardAdjust
const closingOffset = Math.min(0, unclampedClosingOffset)
const bottom = keyboardAdjust + closingOffset
return bottom
}, [
isReadySharedValue,
currentPositionSharedValue,
disableKeyboardAdjust,
footerHeightSharedValue,
keyboardHeightSharedValue,
])
const bottomAtTimeOfLockSharedValue = useLastSharedValueBeforeLock(rawBottomSharedValue)
const freezeWhenExpandingSharedValue = useFooterFreezeWhenExpanding({
bufferSharedValue,
bottomAtTimeOfLockSharedValue,
})
/**
* If the content-size of the sheet increases, then we actually want the
* footer to stay where it is (not shift down as if it's animating back in).
*
*
* To determine this, we snapshot the sheet position at the time of the lock
* and see if we're expanding up or down. We also keep a snapshot of the
* bottom right before the lock was engaged.
*/
const frozenBottomSharedValue = useDerivedValue(() => {
const bottom = rawBottomSharedValue.get()
const bottomAtTimeOfLock = bottomAtTimeOfLockSharedValue.get()
const shouldFreezeBottom = freezeWhenExpanding && freezeWhenExpandingSharedValue.get()
return shouldFreezeBottom ? (bottomAtTimeOfLock ?? bottom) : bottom
}, [bottomAtTimeOfLockSharedValue, freezeWhenExpanding, freezeWhenExpandingSharedValue])
/**
* Note: returning different SharedValue references doesn't play well with
* useAnimatedStyle. Hence we always return the derived value.
*/
return frozenBottomSharedValue
}