@attio/react-native-bottom-sheet-toolbox
Version:
155 lines (129 loc) • 5.73 kB
text/typescript
// import { nanoid } from "nanoid/non-secure";
import * as React from "react"
import {useAnimatedReaction, useSharedValue} from "react-native-reanimated"
import {useBottomSheetToolboxInternalContext} from "../context"
import {DEFAULT_CLOSE_SHEET_SNAP_POINT_SPRING_CONFIG} from "../utils"
import {BottomSheetPositionBuiltInLockPriority, CLOSED_SNAP_POINT} from "../utils/constants"
import {useBottomSheetOnSnapDetection} from "./use-bottom-sheet-on-snap-detection"
import {useBottomSheetPositionLock} from "./use-bottom-sheet-position-lock"
import {useWorkletUiCallback} from "./use-worklet-ui-callback"
export interface BottomSheetToolboxActions {
snapToIndex: (snapPointIndex: number) => void
expand: () => void
close: () => void
getCurrentSnapIndex: () => number | null
}
/**
* Creates a set of actions that can be used to imperatively control the bottom sheet.
*/
export function useBottomSheetActions(id: string) {
const {currentPositionSharedValue, normalizedSnapPointsSharedValue} =
useBottomSheetToolboxInternalContext()
const {
lock: snapLock,
release: setSnapRelease,
setPositionAnimate: setSnapPositionAnimate,
setVelocity: setSnapVelocity,
hasLockSharedValue: hasSnapLockSharedValue,
} = useBottomSheetPositionLock(
`useBottomSheetActions:${id}:snap`,
BottomSheetPositionBuiltInLockPriority.ACTIONS_SNAP
)
// We apply imperative snaps reactively, like this, so we can adjust for changes in the snap points etc.
const snapDestinationIndexSharedValue = useSharedValue<number | null>(null)
const lastSnapPointsSharedValue = useSharedValue<number[] | null>(null)
useAnimatedReaction(
() => ({
currentPosition: currentPositionSharedValue.get(),
snapDestinationIndex: snapDestinationIndexSharedValue.get(),
snapPoints: normalizedSnapPointsSharedValue.get(),
hasSnapLock: hasSnapLockSharedValue.get(),
}),
({currentPosition, snapPoints, snapDestinationIndex, hasSnapLock}, lastResult) => {
if (!hasSnapLock && lastResult?.hasSnapLock === true) {
snapDestinationIndexSharedValue.set(null)
return
}
if (snapPoints === null || snapDestinationIndex === null) return
const lastSnapPoints = lastSnapPointsSharedValue.get()
const didSnapChange =
lastSnapPoints === null ||
lastSnapPoints[snapDestinationIndex] !== snapPoints[snapDestinationIndex] ||
lastResult?.snapDestinationIndex !== snapDestinationIndex
const snapPoint = snapPoints[snapDestinationIndex] ?? null
if (snapPoint === null || currentPosition === snapPoint) {
snapDestinationIndexSharedValue.set(null)
setSnapRelease()
return
}
if (!didSnapChange) return
lastSnapPointsSharedValue.set(snapPoints)
setSnapVelocity(0)
setSnapPositionAnimate(snapPoint)
},
[hasSnapLockSharedValue, currentPositionSharedValue, normalizedSnapPointsSharedValue]
)
const snapToIndex = useWorkletUiCallback(
(snapPointIndex: number) => {
"worklet"
const normalizedSnapPoints = normalizedSnapPointsSharedValue.get()
if (!normalizedSnapPoints || normalizedSnapPoints.length === 0) {
throw new Error("No snap-points to snap to.")
}
if (!(snapPointIndex in normalizedSnapPoints)) {
throw new Error(`Invalid snap point index: ${snapPointIndex}`)
}
snapLock()
snapDestinationIndexSharedValue.set(snapPointIndex)
},
[snapLock, normalizedSnapPointsSharedValue]
)
const {
lock: closeLock,
release: closeRelease,
setPositionAnimate: setClosePositionAnimate,
setVelocity: setCloseVelocity,
hasLockSharedValue: hasCloseLockSharedValue,
} = useBottomSheetPositionLock(
`useBottomSheetActions:${id}:close`,
BottomSheetPositionBuiltInLockPriority.ACTIONS_CLOSE
)
const expand = useWorkletUiCallback(() => {
"worklet"
const normalizedSnapPoints = normalizedSnapPointsSharedValue.get()
if (!normalizedSnapPoints || normalizedSnapPoints.length === 0) {
throw new Error("No snap-points to snap to.")
} else if (normalizedSnapPoints.length === 1 && normalizedSnapPoints[0] === 0) {
throw new Error("Sheet cannot expand as only available snap-point is '0'.")
}
snapToIndex(normalizedSnapPoints.length - 1)
}, [normalizedSnapPointsSharedValue, snapToIndex])
const close = useWorkletUiCallback(() => {
"worklet"
if (hasCloseLockSharedValue.get()) return
closeLock()
setCloseVelocity(0)
setClosePositionAnimate(
CLOSED_SNAP_POINT,
DEFAULT_CLOSE_SHEET_SNAP_POINT_SPRING_CONFIG,
closeRelease
)
}, [
hasCloseLockSharedValue,
closeLock,
setCloseVelocity,
setClosePositionAnimate,
closeRelease,
])
const currentSnapRef = React.useRef<number | null>(null)
useBottomSheetOnSnapDetection(
React.useCallback((snap: number) => {
currentSnapRef.current = snap
}, [])
)
const getCurrentSnapIndex = React.useCallback(() => currentSnapRef.current, [])
return React.useMemo<BottomSheetToolboxActions>(
() => ({snapToIndex, expand, close, getCurrentSnapIndex}),
[snapToIndex, expand, close, getCurrentSnapIndex]
)
}