Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions example/src/screens/advanced/DynamicSnapPointExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ const DynamicSnapPointExample = () => {
layout: { height },
},
}) => {
// console.log('SCREEN \t\t', 'handleOnLayout', height);
setContentHeight(height);
},
[]
Expand Down Expand Up @@ -64,7 +63,6 @@ const DynamicSnapPointExample = () => {
[]
);

// console.log('SCREEN \t\t', 'render \t');
return (
<View style={styles.container}>
<Button
Expand Down Expand Up @@ -102,7 +100,7 @@ const DynamicSnapPointExample = () => {
onPress={handleIncreaseContentPress}
/>
<Button
label="Mayby"
label="Maybe"
style={styles.buttonContainer}
onPress={handleDecreaseContentPress}
/>
Expand Down
8 changes: 8 additions & 0 deletions example/src/screens/static/BasicExamples.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ const createExampleScreen = ({ type, count = 20 }: ExampleScreenProps) =>
const handleSheetChange = useCallback(index => {
console.log('handleSheetChange', index);
}, []);

const handleSheetAnimate = useCallback(
(fromIndex: number, toIndex: number) => {
console.log('handleSheetAnimate', `from ${fromIndex} to ${toIndex}`);
},
[]
);
const handleSnapPress = useCallback(index => {
bottomSheetRef.current?.snapTo(index);
}, []);
Expand Down Expand Up @@ -115,6 +122,7 @@ const createExampleScreen = ({ type, count = 20 }: ExampleScreenProps) =>
enableContentPanningGesture={enableContentPanningGesture}
enableHandlePanningGesture={enableHandlePanningGesture}
onChange={handleSheetChange}
onAnimate={handleSheetAnimate}
>
<ContactList key={`${type}.list`} type={type} count={count} />
</BottomSheet>
Expand Down
108 changes: 82 additions & 26 deletions src/components/bottomSheet/BottomSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import React, {
useEffect,
useState,
} from 'react';
import { ViewStyle } from 'react-native';
import { Dimensions, ViewStyle } from 'react-native';
import isEqual from 'lodash.isequal';
import invariant from 'invariant';
import Animated, {
Expand Down Expand Up @@ -65,6 +65,8 @@ const {
} = require('react-native-reanimated');
const interpolate = interpolateV2 || interpolateV1;

const { height: windowHeight } = Dimensions.get('window');

type BottomSheet = BottomSheetMethods;

Animated.addWhitelistedUIProps({
Expand All @@ -78,22 +80,23 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
animationDuration = DEFAULT_ANIMATION_DURATION,
animationEasing = DEFAULT_ANIMATION_EASING,
// configurations
index: initialSnapIndex = 0,
index: _index = 0,
snapPoints: _snapPoints,
handleHeight: _handleHeight,
topInset = 0,
enableContentPanningGesture = DEFAULT_ENABLE_CONTENT_PANNING_GESTURE,
enableHandlePanningGesture = DEFAULT_ENABLE_HANDLE_PANNING_GESTURE,
animateOnMount = DEFAULT_ANIMATE_ON_MOUNT,
// container props
containerHeight,
containerHeight: _containerHeight,
containerTapGestureRef,
containerTapGestureState,
// animated nodes callback
animatedPosition: _animatedPositionCallbackNode,
animatedIndex: _animatedIndexCallbackNode,
// callbacks
onChange: _onChange,
onAnimate: _onAnimate,
// components
handleComponent: HandleComponent,
backgroundComponent: BackgroundComponent = DefaultBackground,
Expand All @@ -113,15 +116,15 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
`'snapPoints' was provided with no points! please provide at least one snap point.`
);

// validate `initialSnapIndex`
// validate `index`
invariant(
typeof initialSnapIndex === 'number',
`'initialSnapIndex' was provided but with wrong type ! expected type is a number.`
typeof _index === 'number',
`'index' was provided but with wrong type ! expected type is a number.`
);

invariant(
initialSnapIndex >= -1 && initialSnapIndex <= _snapPoints.length - 1,
`'initialSnapIndex' was provided but out of the provided snap points range! expected value to be between -1, ${
_index >= -1 && _index <= _snapPoints.length - 1,
`'index' was provided but out of the provided snap points range! expected value to be between -1, ${
_snapPoints.length - 1
}`
);
Expand Down Expand Up @@ -156,11 +159,35 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
//#endregion

//#region refs
const currentPositionIndexRef = useRef<number>(initialSnapIndex);
const currentIndexRef = useRef<number>(_index);
const handlePanGestureRef = useRef<PanGestureHandler>(null);

// ref values
const didMountOnAnimate = useRef(false);
const didSetHandleHeight = useRef(false);
//#endregion

//#region variables
const isHandleHeightCalculated = useMemo(() => {
// user did provide handle height prop
return _handleHeight !== undefined
? true
: // user did not provide a handle component, we will be using the default handle height
HandleComponent === undefined || HandleComponent === null
? true
: // user did provide a handle component, and handle layout been calculated
handleHeight && didSetHandleHeight.current
? true
: false;
}, [_handleHeight, handleHeight, HandleComponent]);
const isLayoutCalculated = useMemo(
() => _containerHeight !== -1 && isHandleHeightCalculated,
[_containerHeight, isHandleHeightCalculated]
);
const containerHeight = useMemo(
() => (_containerHeight !== -1 ? _containerHeight : windowHeight),
[_containerHeight]
);
const {
scrollableContentOffsetY,
setScrollableRef,
Expand All @@ -178,9 +205,9 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
);

const initialPosition = useMemo(() => {
return currentPositionIndexRef.current < 0 || animateOnMount
return currentIndexRef.current < 0 || animateOnMount
? containerHeight - topInset
: snapPoints[currentPositionIndexRef.current];
: snapPoints[currentIndexRef.current];
}, [snapPoints, animateOnMount, containerHeight, topInset]);
//#endregion

Expand All @@ -200,12 +227,20 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
//#endregion

//#region animation
const handleOnAnimate = useStableCallback(
(fromIndex: number, toIndex: number) => {
if (_onAnimate) {
_onAnimate(fromIndex, toIndex);
}
}
);
const {
position,
manualSnapToPoint,
currentPosition,
currentGesture,
} = useTransition({
isLayoutCalculated,
animationDuration,
animationEasing,
contentPanGestureState,
Expand All @@ -217,6 +252,8 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
scrollableContentOffsetY,
snapPoints,
initialPosition,
currentIndexRef,
onAnimate: handleOnAnimate,
});

// animated values
Expand Down Expand Up @@ -253,19 +290,27 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
//#endregion

//#region styles
const sheetContainerStyle = useMemo<Animated.AnimateStyle<ViewStyle>>(
const containerStyle = useMemo<Animated.AnimateStyle<ViewStyle>>(
() => ({
...styles.container,
transform: [
{ translateY: isLayoutCalculated ? position : containerHeight },
],
}),
[containerHeight, position, isLayoutCalculated]
);
const contentContainerStyle = useMemo(
() => ({
...styles.sheetContainer,
...styles.contentContainer,
height: sheetHeight,
transform: [{ translateY: position }],
}),
[sheetHeight, position]
[sheetHeight]
);
//#endregion

//#region private methods
const refreshUIElements = useCallback(() => {
const currentPositionIndex = Math.max(currentPositionIndexRef.current, 0);
const currentPositionIndex = Math.max(currentIndexRef.current, 0);
if (currentPositionIndex === snapPoints.length - 1) {
flashScrollableIndicators();
// @ts-ignore
Expand Down Expand Up @@ -302,7 +347,7 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
HandleComponent !== null &&
_handleHeight === undefined
) {
// console.log('BottomSheet \t\t', 'handleHandleOnLayout', height);
didSetHandleHeight.current = true;
setHandleHeight(height);
}
},
Expand Down Expand Up @@ -389,19 +434,30 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
* when component is mounted.
*/
useLayoutEffect(() => {
if (animateOnMount) {
manualSnapToPoint.setValue(snapPoints[initialSnapIndex]);
if (
animateOnMount &&
isLayoutCalculated &&
didMountOnAnimate.current === false
) {
manualSnapToPoint.setValue(snapPoints[_index]);
didMountOnAnimate.current = true;
}
}, [animateOnMount, initialSnapIndex, manualSnapToPoint, snapPoints]);
}, [
animateOnMount,
_index,
isLayoutCalculated,
manualSnapToPoint,
snapPoints,
]);

/*
* keep animated position synced with snap points.
*/
useEffect(() => {
if (currentPositionIndexRef.current !== -1) {
manualSnapToPoint.setValue(snapPoints[currentPositionIndexRef.current]);
if (isLayoutCalculated && currentIndexRef.current !== -1) {
manualSnapToPoint.setValue(snapPoints[currentIndexRef.current]);
}
}, [snapPoints, manualSnapToPoint]);
}, [isLayoutCalculated, snapPoints, manualSnapToPoint]);

/**
* @DEV
Expand All @@ -426,7 +482,7 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
) {
return;
}
currentPositionIndexRef.current = currentPositionIndex;
currentIndexRef.current = currentPositionIndex;
refreshUIElements();
handleOnChange(currentPositionIndex);
}),
Expand Down Expand Up @@ -482,7 +538,7 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
);
return (
<>
<Animated.View style={sheetContainerStyle}>
<Animated.View style={containerStyle}>
{renderBackground()}
<BottomSheetProvider value={externalContextVariables}>
<PanGestureHandler
Expand All @@ -498,7 +554,7 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
</PanGestureHandler>

<BottomSheetInternalProvider value={internalContextVariables}>
<DraggableView style={styles.contentContainer}>
<DraggableView style={contentContainerStyle}>
{children}
</DraggableView>
</BottomSheetInternalProvider>
Expand Down
2 changes: 1 addition & 1 deletion src/components/bottomSheet/styles.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { StyleSheet } from 'react-native';

export const styles = StyleSheet.create({
sheetContainer: {
container: {
position: 'absolute',
left: 0,
right: 0,
Expand Down
25 changes: 25 additions & 0 deletions src/components/bottomSheet/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ export type BottomSheetProps = {
* @type (index: number) => void;
*/
onChange?: (index: number) => void;
/**
* Callback when the sheet about to animate to a new position.
* @type (fromIndex: number, toIndex: number) => void;
*/
onAnimate?: (fromIndex: number, toIndex: number) => void;

// components
/**
Expand Down Expand Up @@ -126,3 +131,23 @@ export interface BottomSheetAnimationConfigs {
*/
animationDuration?: number;
}

export interface BottomSheetTransitionConfig
extends Required<BottomSheetAnimationConfigs>,
Pick<BottomSheetProps, 'onAnimate'> {
isLayoutCalculated: boolean;

contentPanGestureState: Animated.Value<State>;
contentPanGestureTranslationY: Animated.Value<number>;
contentPanGestureVelocityY: Animated.Value<number>;

handlePanGestureState: Animated.Value<State>;
handlePanGestureTranslationY: Animated.Value<number>;
handlePanGestureVelocityY: Animated.Value<number>;

scrollableContentOffsetY: Animated.Value<number>;
snapPoints: number[];
initialPosition: number;

currentIndexRef: React.RefObject<number>;
}
Loading