From 7b972992235e91d791aa523c7f30089248f30248 Mon Sep 17 00:00:00 2001 From: Mo Gorhom Date: Sat, 21 Nov 2020 19:52:14 +0100 Subject: [PATCH 01/12] chore: updated normalized snap points implementation --- src/hooks/index.ts | 1 + src/hooks/useNormalizedSnapPoints.ts | 42 ++++++++++++++++++++++++++++ src/utilities/normalizeSnapPoints.ts | 6 ++-- 3 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 src/hooks/useNormalizedSnapPoints.ts diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 01316e143..10f810db1 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -4,3 +4,4 @@ export { useScrollableInternal } from './useScrollableInternal'; export { useStableCallback } from './useStableCallback'; export { useReactiveValue } from './useReactiveValue'; export { useReactiveValues } from './useReactiveValues'; +export { useNormalizedSnapPoints } from './useNormalizedSnapPoints'; diff --git a/src/hooks/useNormalizedSnapPoints.ts b/src/hooks/useNormalizedSnapPoints.ts new file mode 100644 index 000000000..759ed4085 --- /dev/null +++ b/src/hooks/useNormalizedSnapPoints.ts @@ -0,0 +1,42 @@ +import { useMemo } from 'react'; +import { normalizeSnapPoints } from '../utilities'; + +export const useNormalizedSnapPoints = ( + snapPoints: Array, + topInset: number, + containerHeight: number, + handleHeight: number +) => { + const _snapPoints = useMemo(() => { + let normalizedSnapPoints = normalizeSnapPoints( + snapPoints, + containerHeight, + topInset + ); + normalizedSnapPoints = normalizedSnapPoints.map(normalizedSnapPoint => { + /** + * if user sets point to zero and `excludeHandleHeight` true, + * we subset handleHeight from the `normalizedSnapPoint` to make + * sure that sheets and its handle will be out of the screen. + */ + if (normalizedSnapPoint === 0 && handleHeight !== 0) { + normalizedSnapPoint = normalizedSnapPoint - handleHeight; + } + + return Math.max( + containerHeight - normalizedSnapPoint - handleHeight, // - topInset, + topInset + ); + }); + return normalizedSnapPoints; + }, [snapPoints, topInset, containerHeight, handleHeight]); + + const sheetHeight = useMemo( + () => containerHeight - _snapPoints[_snapPoints.length - 1], + [_snapPoints, containerHeight] + ); + return { + snapPoints: _snapPoints, + sheetHeight, + }; +}; diff --git a/src/utilities/normalizeSnapPoints.ts b/src/utilities/normalizeSnapPoints.ts index bae257ae0..383ae9698 100644 --- a/src/utilities/normalizeSnapPoints.ts +++ b/src/utilities/normalizeSnapPoints.ts @@ -1,18 +1,16 @@ -import { Dimensions } from 'react-native'; import { validateSnapPoint } from './validateSnapPoint'; -const { height: windowHeight } = Dimensions.get('window'); - /** * Converts snap points with percentage to fixed numbers. */ export const normalizeSnapPoints = ( snapPoints: Array, + containerHeight: number, topInset: number ) => snapPoints.map(snapPoint => { validateSnapPoint(snapPoint); return typeof snapPoint === 'number' ? snapPoint - : (Number(snapPoint.split('%')[0]) * (windowHeight - topInset)) / 100; + : (Number(snapPoint.split('%')[0]) * (containerHeight - topInset)) / 100; }); From 2036bc83a3196eac274a061f56b68380330f4a55 Mon Sep 17 00:00:00 2001 From: Mo Gorhom Date: Sat, 21 Nov 2020 20:36:05 +0100 Subject: [PATCH 02/12] chore: reverse snap points transitions --- src/components/bottomSheet/useTransition.ts | 37 +++++++++++++++++---- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/src/components/bottomSheet/useTransition.ts b/src/components/bottomSheet/useTransition.ts index 3e980d05e..279691deb 100644 --- a/src/components/bottomSheet/useTransition.ts +++ b/src/components/bottomSheet/useTransition.ts @@ -118,7 +118,7 @@ export const useTransition = ({ cond( eq(currentGesture, GESTURE.CONTENT), cond( - eq(currentPosition, 0), + eq(currentPosition, snapPoints[snapPoints.length - 1]), add( contentPanGestureTranslationY, multiply(scrollableContentOffsetY, -1) @@ -128,9 +128,10 @@ export const useTransition = ({ handlePanGestureTranslationY ), [ - contentPanGestureTranslationY, + snapPoints, currentGesture, currentPosition, + contentPanGestureTranslationY, handlePanGestureTranslationY, scrollableContentOffsetY, ] @@ -172,8 +173,16 @@ export const useTransition = ({ ), // debug('start panning', translateY), cond( - not(greaterOrEq(add(currentPosition, translateY), 0)), - [set(animationState.position, 0), set(animationState.finished, 0)], + not( + greaterOrEq( + add(currentPosition, translateY), + snapPoints[snapPoints.length - 1] + ) + ), + [ + set(animationState.position, snapPoints[snapPoints.length - 1]), + set(animationState.finished, 0), + ], cond( not(lessOrEq(add(currentPosition, translateY), snapPoints[0])), [ @@ -246,8 +255,24 @@ export const useTransition = ({ animationState.position, ]), - // eslint-disable-next-line react-hooks/exhaustive-deps - [snapPoints] + [ + animationState, + clock, + config, + currentGesture, + currentPosition, + finishTiming, + isAnimationInterrupted, + isPanning, + isPanningContent, + manualSnapToPoint, + shouldAnimate, + snapPoints, + translateY, + velocityY, + contentPanGestureState, + handlePanGestureState, + ] ); return { From fb3463995e5ec841b3bec3945778aa670a05e3dd Mon Sep 17 00:00:00 2001 From: Mo Gorhom Date: Sat, 21 Nov 2020 20:44:25 +0100 Subject: [PATCH 03/12] chore: updated content wrapper --- .../contentWrapper/ContentWrapper.android.tsx | 12 +++++++++--- src/components/contentWrapper/ContentWrapper.ios.tsx | 9 ++++++--- src/components/contentWrapper/types.d.ts | 6 +++--- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/components/contentWrapper/ContentWrapper.android.tsx b/src/components/contentWrapper/ContentWrapper.android.tsx index 44232fbc3..b48c821cf 100644 --- a/src/components/contentWrapper/ContentWrapper.android.tsx +++ b/src/components/contentWrapper/ContentWrapper.android.tsx @@ -1,6 +1,7 @@ import React, { forwardRef, memo } from 'react'; import isEqual from 'lodash.isequal'; import { TapGestureHandler } from 'react-native-gesture-handler'; +import Animated from 'react-native-reanimated'; import type { BottomSheetContentWrapperProps } from './types'; const ContentWrapperComponent = forwardRef< @@ -8,19 +9,24 @@ const ContentWrapperComponent = forwardRef< BottomSheetContentWrapperProps >( ( - { children, initialMaxDeltaY, onGestureEvent, onHandlerStateChange }, + { style, children, onLayout, onGestureEvent, onHandlerStateChange }, ref ) => { return ( - {children} + + {children} + ); } diff --git a/src/components/contentWrapper/ContentWrapper.ios.tsx b/src/components/contentWrapper/ContentWrapper.ios.tsx index 13399b5c8..b48c821cf 100644 --- a/src/components/contentWrapper/ContentWrapper.ios.tsx +++ b/src/components/contentWrapper/ContentWrapper.ios.tsx @@ -9,19 +9,22 @@ const ContentWrapperComponent = forwardRef< BottomSheetContentWrapperProps >( ( - { children, initialMaxDeltaY, style, onGestureEvent, onHandlerStateChange }, + { style, children, onLayout, onGestureEvent, onHandlerStateChange }, ref ) => { return ( - + {children} diff --git a/src/components/contentWrapper/types.d.ts b/src/components/contentWrapper/types.d.ts index 85d3f5fa7..3ebffc0cc 100644 --- a/src/components/contentWrapper/types.d.ts +++ b/src/components/contentWrapper/types.d.ts @@ -1,11 +1,11 @@ import type { RefAttributes } from 'react'; -import type { StyleProp, ViewStyle } from 'react-native'; +import type { StyleProp, ViewProps, ViewStyle } from 'react-native'; import type { TapGestureHandler } from 'react-native-gesture-handler'; export type BottomSheetContentWrapperProps = { - children: React.ReactNode; - initialMaxDeltaY: number; style: StyleProp; + children: React.ReactNode; + onLayout?: ViewProps['onLayout']; onHandlerStateChange: (...args: any[]) => void; onGestureEvent: (...args: any[]) => void; }; From bc15b8c21f2abd3ed6ae6e6c1c306b3fc1f6d6db Mon Sep 17 00:00:00 2001 From: Mo Gorhom Date: Sat, 21 Nov 2020 20:47:28 +0100 Subject: [PATCH 04/12] chore: added bottom sheet container --- .../BottomSheetContainer.tsx | 95 +++++++++++++++++++ src/components/bottomSheetContainer/index.ts | 2 + src/components/bottomSheetContainer/styles.ts | 11 +++ .../bottomSheetContainer/types.d.ts | 13 +++ 4 files changed, 121 insertions(+) create mode 100644 src/components/bottomSheetContainer/BottomSheetContainer.tsx create mode 100644 src/components/bottomSheetContainer/index.ts create mode 100644 src/components/bottomSheetContainer/styles.ts create mode 100644 src/components/bottomSheetContainer/types.d.ts diff --git a/src/components/bottomSheetContainer/BottomSheetContainer.tsx b/src/components/bottomSheetContainer/BottomSheetContainer.tsx new file mode 100644 index 000000000..0c5298bda --- /dev/null +++ b/src/components/bottomSheetContainer/BottomSheetContainer.tsx @@ -0,0 +1,95 @@ +import React, { + forwardRef, + memo, + useCallback, + useMemo, + useRef, + useState, +} from 'react'; +import isEqual from 'lodash.isequal'; +import invariant from 'invariant'; +import { TapGestureHandler } from 'react-native-gesture-handler'; +import { useTapGestureHandler } from 'react-native-redash'; +import BottomSheetView from '../bottomSheet'; +import ContentWrapper from '../contentWrapper'; +import type { BottomSheetMethods } from '../../types'; +import type { BottomSheetContainerProps } from './types'; +import { styles } from './styles'; + +type BottomSheet = BottomSheetMethods; + +const BottomSheetContainerComponent = forwardRef< + BottomSheet, + BottomSheetContainerProps +>(({ height: _height, ...rest }, ref) => { + //#region validate props + invariant( + typeof _height === 'number' || typeof _height === 'undefined', + `'height' was provided but with wrong type ! expected type is a number or undefined.` + ); + //#endregion + + //#region state + const [containerHeight, setContainerHeight] = useState(-1); + //#endregion + + //#region refs + const containerTapGestureRef = useRef(null); + //#endregion + + //#region variables + const height = useMemo( + () => (_height !== undefined ? _height : containerHeight), + [_height, containerHeight] + ); + //#endregion + + //#region gestures + const { + state: containerTapGestureState, + gestureHandler: containerTapGestureHandler, + } = useTapGestureHandler(); + //#endregion + + //#region callbacks + const handleContainerOnLayout = useCallback( + ({ nativeEvent: { layout } }) => { + if (_height === undefined) { + console.log( + 'BottomSheetContainer', + 'handleContainerOnLayout', + layout.height + ); + setContainerHeight(layout.height); + } + }, + [_height] + ); + //#endregion + + //#region render + console.log('BottomSheetContainer', 'render'); + return ( + + {height !== undefined && height !== -1 ? ( + + ) : null} + + ); + //#endregion +}); + +const BottomSheet = memo(BottomSheetContainerComponent, isEqual); + +export default BottomSheet; diff --git a/src/components/bottomSheetContainer/index.ts b/src/components/bottomSheetContainer/index.ts new file mode 100644 index 000000000..ff4646d1a --- /dev/null +++ b/src/components/bottomSheetContainer/index.ts @@ -0,0 +1,2 @@ +export { default } from './BottomSheetContainer'; +export type { BottomSheetContainerProps } from './types'; diff --git a/src/components/bottomSheetContainer/styles.ts b/src/components/bottomSheetContainer/styles.ts new file mode 100644 index 000000000..42836a23f --- /dev/null +++ b/src/components/bottomSheetContainer/styles.ts @@ -0,0 +1,11 @@ +import { StyleSheet } from 'react-native'; + +export const styles = StyleSheet.create({ + container: { + position: 'absolute', + left: 0, + right: 0, + bottom: 0, + top: 0, + }, +}); diff --git a/src/components/bottomSheetContainer/types.d.ts b/src/components/bottomSheetContainer/types.d.ts new file mode 100644 index 000000000..395e60149 --- /dev/null +++ b/src/components/bottomSheetContainer/types.d.ts @@ -0,0 +1,13 @@ +import type { BottomSheetProps } from '../bottomSheet/types'; + +export type BottomSheetContainerProps = { + /** + * Parent height value to help calculate snap points values, + * if not provided, the library will measure the layout height. + * @type number + */ + height?: number; +} & Omit< + BottomSheetProps, + 'containerHeight' | 'containerTapGestureState' | 'containerTapGestureRef' +>; From 562e7dfcae84329f5687b2f7c4b3126043d596d4 Mon Sep 17 00:00:00 2001 From: Mo Gorhom Date: Sat, 21 Nov 2020 20:50:20 +0100 Subject: [PATCH 05/12] chore: updated bottom sheet to handle dynamic snap points --- src/components/bottomSheet/BottomSheet.tsx | 202 ++++++++++-------- src/components/bottomSheet/styles.ts | 4 +- src/components/bottomSheet/types.d.ts | 25 ++- .../bottomSheetModal/BottomSheetModal.tsx | 7 +- .../BottomSheetModalContainer.tsx | 3 + .../draggableView/DraggableView.tsx | 8 +- src/components/flatList/FlatList.tsx | 4 +- src/components/scrollView/ScrollView.tsx | 4 +- src/components/sectionList/SectionList.tsx | 4 +- src/contexts/internal.ts | 2 +- src/index.ts | 2 +- src/types.ts | 4 +- 12 files changed, 152 insertions(+), 117 deletions(-) diff --git a/src/components/bottomSheet/BottomSheet.tsx b/src/components/bottomSheet/BottomSheet.tsx index 34c032555..2bcf2d93b 100644 --- a/src/components/bottomSheet/BottomSheet.tsx +++ b/src/components/bottomSheet/BottomSheet.tsx @@ -6,6 +6,8 @@ import React, { useImperativeHandle, memo, useLayoutEffect, + useEffect, + useState, } from 'react'; import { ViewStyle } from 'react-native'; import isEqual from 'lodash.isequal'; @@ -19,28 +21,25 @@ import Animated, { neq, and, // concat, - greaterThan, Extrapolate, set, - // defined, sub, + abs, + greaterThan, } from 'react-native-reanimated'; -import { - PanGestureHandler, - TapGestureHandler, - State, -} from 'react-native-gesture-handler'; +import { PanGestureHandler, State } from 'react-native-gesture-handler'; import { usePanGestureHandler, - useTapGestureHandler, // ReText, } from 'react-native-redash'; import DraggableView from '../draggableView'; import Handle from '../handle'; -import ContentWrapper from '../contentWrapper'; import { useTransition } from './useTransition'; -import { useStableCallback, useScrollable } from '../../hooks'; -import { normalizeSnapPoints } from '../../utilities'; +import { + useStableCallback, + useScrollable, + useNormalizedSnapPoints, +} from '../../hooks'; import { BottomSheetInternalProvider, BottomSheetProvider, @@ -74,12 +73,16 @@ const BottomSheetComponent = forwardRef( // animations animationDuration = DEFAULT_ANIMATION_DURATION, animationEasing = DEFAULT_ANIMATION_EASING, - // general + // configurations initialSnapIndex = 0, snapPoints: _snapPoints, topInset = 0, enabled = true, animateOnMount = DEFAULT_ANIMATE_ON_MOUNT, + // container props + containerHeight, + containerTapGestureRef, + containerTapGestureState, // animated nodes callback animatedPosition: _animatedPosition, animatedPositionIndex: _animatedPositionIndex, @@ -140,9 +143,12 @@ const BottomSheetComponent = forwardRef( ); //#endregion + //#region state + const [handleHeight, setHandleHeight] = useState(-1); + //#endregion + //#region refs const currentPositionIndexRef = useRef(initialSnapIndex); - const rootTapGestureRef = useRef(null); const handlePanGestureRef = useRef(null); //#endregion @@ -156,22 +162,18 @@ const BottomSheetComponent = forwardRef( } = useScrollable(); // normalize snap points - const { snapPoints, sheetHeight } = useMemo(() => { - const normalizedSnapPoints = normalizeSnapPoints(_snapPoints, topInset); - const maxSnapPoint = - normalizedSnapPoints[normalizedSnapPoints.length - 1]; - return { - snapPoints: normalizedSnapPoints.map( - normalizedSnapPoint => maxSnapPoint - normalizedSnapPoint - ), - sheetHeight: maxSnapPoint, - }; - }, [_snapPoints, topInset]); + const { snapPoints, sheetHeight } = useNormalizedSnapPoints( + _snapPoints, + topInset, + containerHeight, + handleHeight + ); + const initialPosition = useMemo(() => { - return initialSnapIndex < 0 || animateOnMount - ? sheetHeight - : snapPoints[initialSnapIndex]; - }, [initialSnapIndex, animateOnMount, sheetHeight, snapPoints]); + return currentPositionIndexRef.current < 0 || animateOnMount + ? containerHeight - topInset + : snapPoints[currentPositionIndexRef.current]; + }, [snapPoints, animateOnMount, containerHeight, topInset]); //#endregion //#region gestures @@ -187,11 +189,6 @@ const BottomSheetComponent = forwardRef( translation: { y: contentPanGestureTranslationY }, velocity: { y: contentPanGestureVelocityY }, } = usePanGestureHandler(); - - const { - state: tapGestureState, - gestureHandler: tapGestureHandler, - } = useTapGestureHandler(); //#endregion //#region animation @@ -230,22 +227,20 @@ const BottomSheetComponent = forwardRef( * Scrollable animated props. */ const decelerationRate = useMemo( - () => cond(greaterThan(position, 0), 0.001, NORMAL_DECELERATION_RATE), - [position] + () => + cond( + greaterThan(position, snapPoints[snapPoints.length - 1]), + 0.001, + NORMAL_DECELERATION_RATE + ), + [position, snapPoints] ); //#endregion //#region styles - const containerStyle = useMemo( - () => ({ - ...styles.container, - height: sheetHeight, - }), - [sheetHeight] - ); - const contentContainerStyle = useMemo>( + const sheetContainerStyle = useMemo>( () => ({ - ...styles.container, + ...styles.sheetContainer, height: sheetHeight, transform: [{ translateY: position }], }), @@ -253,22 +248,22 @@ const BottomSheetComponent = forwardRef( ); //#endregion - //#region callbacks + //#region private methods const refreshUIElements = useCallback(() => { const currentPositionIndex = Math.max(currentPositionIndexRef.current, 0); if (currentPositionIndex === snapPoints.length - 1) { flashScrollableIndicators(); // @ts-ignore - rootTapGestureRef.current.setNativeProps({ + containerTapGestureRef.current.setNativeProps({ maxDeltaY: 0, }); } else { // @ts-ignore - rootTapGestureRef.current.setNativeProps({ + containerTapGestureRef.current.setNativeProps({ maxDeltaY: snapPoints[currentPositionIndex], }); } - }, [snapPoints, flashScrollableIndicators]); + }, [snapPoints, flashScrollableIndicators, containerTapGestureRef]); const handleOnChange = useStableCallback((index: number) => { if (_onChange) { _onChange(index); @@ -281,9 +276,20 @@ const BottomSheetComponent = forwardRef( }, [setScrollableRef, refreshUIElements] ); + const handleHandleOnLayout = useCallback( + ({ + nativeEvent: { + layout: { height }, + }, + }) => { + console.log('BottomSheet', 'handleHandleOnLayout', height); + setHandleHeight(height); + }, + [] + ); //#endregion - //#region methods + //#region public methods const handleSnapTo = useCallback( (index: number) => { invariant( @@ -297,8 +303,8 @@ const BottomSheetComponent = forwardRef( [snapPoints, manualSnapToPoint] ); const handleClose = useCallback(() => { - manualSnapToPoint.setValue(sheetHeight); - }, [sheetHeight, manualSnapToPoint]); + manualSnapToPoint.setValue(containerHeight - topInset); + }, [manualSnapToPoint, containerHeight, topInset]); const handleExpand = useCallback(() => { manualSnapToPoint.setValue(snapPoints[snapPoints.length - 1]); }, [snapPoints, manualSnapToPoint]); @@ -307,11 +313,11 @@ const BottomSheetComponent = forwardRef( }, [snapPoints, manualSnapToPoint]); //#endregion - //#region + //#region context variables const internalContextVariables = useMemo( () => ({ enabled, - rootTapGestureRef, + containerTapGestureRef, handlePanGestureState, handlePanGestureTranslationY, handlePanGestureVelocityY, @@ -355,6 +361,15 @@ const BottomSheetComponent = forwardRef( } }, [animateOnMount, initialSnapIndex, manualSnapToPoint, snapPoints]); + /* + * keep animated position synced with snap points. + */ + useEffect(() => { + if (currentPositionIndexRef.current !== -1) { + manualSnapToPoint.setValue(snapPoints[currentPositionIndexRef.current]); + } + }, [snapPoints, manualSnapToPoint]); + /** * @DEV * here we track the current position and @@ -372,7 +387,10 @@ const BottomSheetComponent = forwardRef( /** * if animation was interrupted, we ignore the change. */ - if (currentPositionIndex === -1 && args[0] !== sheetHeight) { + if ( + currentPositionIndex === -1 && + args[0] !== containerHeight - topInset + ) { return; } currentPositionIndexRef.current = currentPositionIndex; @@ -380,7 +398,7 @@ const BottomSheetComponent = forwardRef( handleOnChange(currentPositionIndex); }), ]), - [snapPoints, refreshUIElements] + [snapPoints, containerHeight, topInset, refreshUIElements] ); /** @@ -393,59 +411,53 @@ const BottomSheetComponent = forwardRef( () => cond( and( - eq(tapGestureState, State.FAILED), + eq(containerTapGestureState, State.FAILED), eq(currentGesture, GESTURE.CONTENT), - neq(position, 0) + neq(position, snapPoints[snapPoints.length - 1]) ), call([], () => { scrollToTop(); }) ), - [] + [snapPoints] ); //#endregion // render - console.log('X', 'render'); + console.log('BottomSheet', 'render'); return ( <> - - - {BackgroundComponent && ( - - )} - - - - - - - - - - {children} - - - - - + + {BackgroundComponent && } + + + + + + + + + + {children} + + + + {_animatedPosition && ( )} @@ -461,7 +473,7 @@ const BottomSheetComponent = forwardRef( /> ( /> */} diff --git a/src/components/bottomSheet/styles.ts b/src/components/bottomSheet/styles.ts index 4c1601094..018a8eb77 100644 --- a/src/components/bottomSheet/styles.ts +++ b/src/components/bottomSheet/styles.ts @@ -1,11 +1,11 @@ import { StyleSheet } from 'react-native'; export const styles = StyleSheet.create({ - container: { + sheetContainer: { position: 'absolute', left: 0, right: 0, - bottom: 0, + top: 0, }, contentContainer: { flex: 1, diff --git a/src/components/bottomSheet/types.d.ts b/src/components/bottomSheet/types.d.ts index 27dabe7e6..3408a00b0 100644 --- a/src/components/bottomSheet/types.d.ts +++ b/src/components/bottomSheet/types.d.ts @@ -1,9 +1,10 @@ import type React from 'react'; +import type { ViewProps } from 'react-native'; import type Animated from 'react-native-reanimated'; +import type { State, TapGestureHandler } from 'react-native-gesture-handler'; import type { BottomSheetHandleProps } from '../handle'; -import type { ViewProps } from 'react-native'; -export interface BottomSheetProps extends BottomSheetAnimationConfigs { +export type BottomSheetProps = { /** * Initial snap index, you also could provide {`-1`} to initiate bottom sheet in closed state. * @type number @@ -19,7 +20,8 @@ export interface BottomSheetProps extends BottomSheetAnimationConfigs { */ snapPoints: Array; /** - * Top inset value helps to calculate percentage snap points values. usually comes from `@react-navigation/stack` hook `useHeaderHeight` or from `react-native-safe-area-context` hook `useSafeArea`. + * Top inset value helps to calculate percentage snap points values, + * usually comes from `@react-navigation/stack` hook `useHeaderHeight` or from `react-native-safe-area-context` hook `useSafeArea`. * @type number * @default 0 */ @@ -36,6 +38,21 @@ export interface BottomSheetProps extends BottomSheetAnimationConfigs { * @default false */ animateOnMount?: boolean; + /** + * Container height value to help calculate snap points values. + * @type number + */ + containerHeight: number; + /** + * Container tap gesture state. + * @type Animated.Value + */ + containerTapGestureState: Animated.Value; + /** + * Container tap gesture ref. + * @type React.RefObject + */ + containerTapGestureRef: React.RefObject; /** * Animated value to be used as a callback of the position node internally. * @type Animated.Value @@ -67,7 +84,7 @@ export interface BottomSheetProps extends BottomSheetAnimationConfigs { * @type React.ReactNode[] | React.ReactNode */ children: React.ReactNode[] | React.ReactNode; -} +} & BottomSheetAnimationConfigs; export interface BottomSheetAnimationConfigs { /** diff --git a/src/components/bottomSheetModal/BottomSheetModal.tsx b/src/components/bottomSheetModal/BottomSheetModal.tsx index 6ffe3417e..87bd865f3 100644 --- a/src/components/bottomSheetModal/BottomSheetModal.tsx +++ b/src/components/bottomSheetModal/BottomSheetModal.tsx @@ -9,7 +9,7 @@ import React, { import isEqual from 'lodash.isequal'; import { useValue } from 'react-native-redash'; import Animated, { Extrapolate, set } from 'react-native-reanimated'; -import BottomSheet from '../bottomSheet'; +import BottomSheet from '../bottomSheetContainer'; import { DEFAULT_OVERLAY_OPACITY, DEFAULT_DISMISS_ON_OVERLAY_PRESS, @@ -105,6 +105,9 @@ const BottomSheetModalComponent = forwardRef< bottomSheetRef.current?.collapse(); } }, [dismissOnScrollDown]); + const handleExpand = useCallback(() => { + bottomSheetRef.current?.expand(); + }, []); const handleSnapTo = useCallback( (index: number) => { bottomSheetRef.current?.snapTo(index + (dismissOnScrollDown ? 1 : 0)); @@ -128,7 +131,7 @@ const BottomSheetModalComponent = forwardRef< useImperativeHandle(ref, () => ({ close: handleClose, snapTo: handleSnapTo, - expand: bottomSheetRef.current!.expand, + expand: handleExpand, collapse: handleCollapse, temporaryCloseSheet: handleTemporaryCloseSheet, restoreSheetPosition: handleRestoreSheetPosition, diff --git a/src/components/bottomSheetModalContainer/BottomSheetModalContainer.tsx b/src/components/bottomSheetModalContainer/BottomSheetModalContainer.tsx index ea903d2dd..5877ee50a 100644 --- a/src/components/bottomSheetModalContainer/BottomSheetModalContainer.tsx +++ b/src/components/bottomSheetModalContainer/BottomSheetModalContainer.tsx @@ -28,6 +28,9 @@ const BottomSheetModalContainerComponent = forwardRef< >((_, ref) => { //#region state const [sheets, setSheets] = useState>({}); + //#endregion + + //#region refs const currentPresentedSheet = useRef(null); const previousPresentedSheet = useRef([]); const didHandleRestorePreviousSheet = useRef(false); diff --git a/src/components/draggableView/DraggableView.tsx b/src/components/draggableView/DraggableView.tsx index 24a689325..3ce762ed3 100644 --- a/src/components/draggableView/DraggableView.tsx +++ b/src/components/draggableView/DraggableView.tsx @@ -19,7 +19,7 @@ const BottomSheetDraggableViewComponent = ({ // hooks const { enabled, - rootTapGestureRef, + containerTapGestureRef, handlePanGestureState, handlePanGestureTranslationY, handlePanGestureVelocityY, @@ -32,9 +32,9 @@ const BottomSheetDraggableViewComponent = ({ const simultaneousHandlers = useMemo( () => nativeGestureRef - ? [rootTapGestureRef, nativeGestureRef] - : rootTapGestureRef, - [rootTapGestureRef, nativeGestureRef] + ? [containerTapGestureRef, nativeGestureRef] + : containerTapGestureRef, + [containerTapGestureRef, nativeGestureRef] ); // styles diff --git a/src/components/flatList/FlatList.tsx b/src/components/flatList/FlatList.tsx index 776fe49ea..cd6e23f1a 100644 --- a/src/components/flatList/FlatList.tsx +++ b/src/components/flatList/FlatList.tsx @@ -45,7 +45,7 @@ const BottomSheetFlatListComponent = forwardRef( } = useScrollableInternal('FlatList'); const { enabled, - rootTapGestureRef, + containerTapGestureRef, decelerationRate, } = useBottomSheetInternal(); @@ -64,7 +64,7 @@ const BottomSheetFlatListComponent = forwardRef( ; + containerTapGestureRef: Ref; contentPanGestureState: Animated.Value; contentPanGestureTranslationY: Animated.Value; contentPanGestureVelocityY: Animated.Value; diff --git a/src/index.ts b/src/index.ts index 51a06b7f1..e83fb62ab 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -export { default } from './components/bottomSheet'; +export { default } from './components/bottomSheetContainer'; // scrollables export { default as BottomSheetFlatList } from './components/flatList'; diff --git a/src/types.ts b/src/types.ts index ff7273e46..efd07e776 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,6 @@ import type { FC } from 'react'; import type { FlatList, ScrollView, SectionList } from 'react-native'; -import type { BottomSheetProps } from './components/bottomSheet'; +import type { BottomSheetContainerProps } from './components/bottomSheetContainer'; import type { BottomSheetOverlayProps } from './components/overlay'; export type BottomSheetMethods = { @@ -70,4 +70,4 @@ export type BottomSheetModalConfigs = { * @default true */ dismissOnScrollDown?: boolean; -} & Omit; +} & Omit; From d2616bec5d3f8780e0cd56b97568da016d9519fe Mon Sep 17 00:00:00 2001 From: Mo Gorhom Date: Sat, 21 Nov 2020 20:50:43 +0100 Subject: [PATCH 06/12] chore: updated examples --- example/src/components/handle/Handle.tsx | 51 +++++++++++-------- .../screens/advanced/CustomHandleExample.tsx | 3 -- .../src/screens/advanced/NavigatorExample.tsx | 3 -- .../src/screens/advanced/OverlayExample.tsx | 3 -- example/src/screens/modal/SimpleExample.tsx | 2 +- example/src/screens/static/BasicExample.tsx | 8 ++- example/src/screens/static/BasicExamples.tsx | 5 +- example/src/utils/index.ts | 8 +-- 8 files changed, 43 insertions(+), 40 deletions(-) diff --git a/example/src/components/handle/Handle.tsx b/example/src/components/handle/Handle.tsx index 623f486c2..42432de01 100644 --- a/example/src/components/handle/Handle.tsx +++ b/example/src/components/handle/Handle.tsx @@ -10,21 +10,33 @@ interface HandleProps extends BottomSheetHandleProps { const Handle: React.FC = ({ style, animatedPositionIndex }) => { //#region animations - const borderTopRadius = interpolate(animatedPositionIndex, { - inputRange: [1, 2], - outputRange: [20, 0], - extrapolate: Extrapolate.CLAMP, - }); - const indicatorTransformOriginY = interpolate(animatedPositionIndex, { - inputRange: [0, 1, 2], - outputRange: [-1, 0, 1], - extrapolate: Extrapolate.CLAMP, - }); - const leftIndicatorRotate = interpolate(animatedPositionIndex, { - inputRange: [0, 1, 2], - outputRange: [toRad(-30), 0, toRad(30)], - extrapolate: Extrapolate.CLAMP, - }); + const borderTopRadius = useMemo( + () => + interpolate(animatedPositionIndex, { + inputRange: [1, 2], + outputRange: [20, 0], + extrapolate: Extrapolate.CLAMP, + }), + [animatedPositionIndex] + ); + const indicatorTransformOriginY = useMemo( + () => + interpolate(animatedPositionIndex, { + inputRange: [0, 1, 2], + outputRange: [-1, 0, 1], + extrapolate: Extrapolate.CLAMP, + }), + [animatedPositionIndex] + ); + const leftIndicatorRotate = useMemo( + () => + interpolate(animatedPositionIndex, { + inputRange: [0, 1, 2], + outputRange: [toRad(-30), 0, toRad(30)], + extrapolate: Extrapolate.CLAMP, + }), + [animatedPositionIndex] + ); const rightIndicatorRotate = interpolate(animatedPositionIndex, { inputRange: [0, 1, 2], outputRange: [toRad(30), 0, toRad(-30)], @@ -42,8 +54,7 @@ const Handle: React.FC = ({ style, animatedPositionIndex }) => { borderTopRightRadius: borderTopRadius, }, ], - // eslint-disable-next-line react-hooks/exhaustive-deps - [style] + [borderTopRadius, style] ); const leftIndicatorStyle = useMemo( () => ({ @@ -57,8 +68,7 @@ const Handle: React.FC = ({ style, animatedPositionIndex }) => { } ), }), - // eslint-disable-next-line react-hooks/exhaustive-deps - [] + [indicatorTransformOriginY, leftIndicatorRotate] ); const rightIndicatorStyle = useMemo( () => ({ @@ -72,8 +82,7 @@ const Handle: React.FC = ({ style, animatedPositionIndex }) => { } ), }), - // eslint-disable-next-line react-hooks/exhaustive-deps - [] + [indicatorTransformOriginY, rightIndicatorRotate] ); //#endregion diff --git a/example/src/screens/advanced/CustomHandleExample.tsx b/example/src/screens/advanced/CustomHandleExample.tsx index 39f393852..8ae46cc4c 100644 --- a/example/src/screens/advanced/CustomHandleExample.tsx +++ b/example/src/screens/advanced/CustomHandleExample.tsx @@ -1,6 +1,5 @@ import React, { useCallback, useMemo, useRef, useState } from 'react'; import { View, StyleSheet, Text } from 'react-native'; -import { useHeaderHeight } from '@react-navigation/stack'; import BottomSheet from '@gorhom/bottom-sheet'; import Handle from '../../components/handle'; import Button from '../../components/button'; @@ -12,7 +11,6 @@ const CustomHandleExample = () => { // hooks const bottomSheetRef = useRef(null); - const headerHeight = useHeaderHeight(); // variables const snapPoints = useMemo(() => [150, 300, 450], []); @@ -88,7 +86,6 @@ const CustomHandleExample = () => { enabled={enabled} snapPoints={snapPoints} initialSnapIndex={1} - topInset={headerHeight} handleComponent={Handle} > diff --git a/example/src/screens/advanced/NavigatorExample.tsx b/example/src/screens/advanced/NavigatorExample.tsx index f38c2d0a2..dd02ec8b9 100644 --- a/example/src/screens/advanced/NavigatorExample.tsx +++ b/example/src/screens/advanced/NavigatorExample.tsx @@ -1,7 +1,6 @@ import React, { useCallback, useMemo, useRef, useState } from 'react'; import { View, StyleSheet } from 'react-native'; import { - useHeaderHeight, createStackNavigator, HeaderBackButton, StackNavigationOptions, @@ -75,7 +74,6 @@ const NavigatorExample = () => { // hooks const bottomSheetRef = useRef(null); - const headerHeight = useHeaderHeight(); // variables const snapPoints = useMemo(() => ['25%', '50%', '90%'], []); @@ -146,7 +144,6 @@ const NavigatorExample = () => { enabled={enabled} snapPoints={snapPoints} initialSnapIndex={1} - topInset={headerHeight} onChange={handleSheetChange} > diff --git a/example/src/screens/advanced/OverlayExample.tsx b/example/src/screens/advanced/OverlayExample.tsx index feba46ced..4af3903bd 100644 --- a/example/src/screens/advanced/OverlayExample.tsx +++ b/example/src/screens/advanced/OverlayExample.tsx @@ -1,6 +1,5 @@ import React, { useCallback, useMemo, useRef } from 'react'; import { View, StyleSheet, Text } from 'react-native'; -import { useHeaderHeight } from '@react-navigation/stack'; import Animated, { interpolate, Extrapolate } from 'react-native-reanimated'; import { useValue } from 'react-native-redash'; import BottomSheet from '@gorhom/bottom-sheet'; @@ -10,7 +9,6 @@ import ContactList from '../../components/contactList'; const OverlayExample = () => { // hooks const bottomSheetRef = useRef(null); - const headerHeight = useHeaderHeight(); // variables const snapPoints = useMemo(() => ['25%', '50%', '90%'], []); @@ -93,7 +91,6 @@ const OverlayExample = () => { ref={bottomSheetRef} snapPoints={snapPoints} initialSnapIndex={1} - topInset={headerHeight} animatedPositionIndex={animatedPositionIndex} onChange={handleSheetChanges} > diff --git a/example/src/screens/modal/SimpleExample.tsx b/example/src/screens/modal/SimpleExample.tsx index d75f1ae21..3caab9bd6 100644 --- a/example/src/screens/modal/SimpleExample.tsx +++ b/example/src/screens/modal/SimpleExample.tsx @@ -25,7 +25,7 @@ const SimpleExample = () => { }, []); const handlePresentPress = useCallback(() => { present(, { - snapPoints: [300, '50%'], + snapPoints: ['25%', '50%'], animationDuration: 250, onChange: handleChange, }); diff --git a/example/src/screens/static/BasicExample.tsx b/example/src/screens/static/BasicExample.tsx index efb2c0269..a1f48163f 100644 --- a/example/src/screens/static/BasicExample.tsx +++ b/example/src/screens/static/BasicExample.tsx @@ -77,7 +77,13 @@ const BasicExample = () => { animatedPosition={position} onChange={handleSheetChanges} > - + + {/* */} ); diff --git a/example/src/screens/static/BasicExamples.tsx b/example/src/screens/static/BasicExamples.tsx index 170996a52..496abc71d 100644 --- a/example/src/screens/static/BasicExamples.tsx +++ b/example/src/screens/static/BasicExamples.tsx @@ -1,6 +1,5 @@ import React, { useCallback, memo, useRef, useMemo, useState } from 'react'; import { StyleSheet, View } from 'react-native'; -import { useHeaderHeight } from '@react-navigation/stack'; import BottomSheet from '@gorhom/bottom-sheet'; import ContactList from '../../components/contactList'; import Button from '../../components/button'; @@ -11,14 +10,13 @@ interface ExampleScreenProps { count?: number; } -const createExampleScreen = ({ type, count = 50 }: ExampleScreenProps) => +const createExampleScreen = ({ type, count = 20 }: ExampleScreenProps) => memo(() => { // state const [enabled, setEnabled] = useState(true); // hooks const bottomSheetRef = useRef(null); - const headerHeight = useHeaderHeight(); // variables const snapPoints = useMemo(() => ['25%', '50%', '90%'], []); @@ -89,7 +87,6 @@ const createExampleScreen = ({ type, count = 50 }: ExampleScreenProps) => snapPoints={snapPoints} initialSnapIndex={1} animateOnMount={true} - topInset={headerHeight} onChange={handleSheetChange} > diff --git a/example/src/utils/index.ts b/example/src/utils/index.ts index 37d82b2d9..0d21b7c7c 100644 --- a/example/src/utils/index.ts +++ b/example/src/utils/index.ts @@ -16,7 +16,7 @@ export type Location = { photos: string[]; }; -export const createContactListMockData = (count: number = 50): Contact[] => { +export const createContactListMockData = (count: number = 20): Contact[] => { return new Array(count).fill(0).map(() => ({ name: `${Faker.name.firstName()} ${Faker.name.lastName()}`, address: `${Faker.address.city()}, ${Faker.address.country()}`, @@ -24,10 +24,10 @@ export const createContactListMockData = (count: number = 50): Contact[] => { })); }; -export const createContactSectionsMockData = (count: number = 50) => { - return new Array(Math.round(count / 2)).fill(0).map(() => ({ +export const createContactSectionsMockData = (count: number = 20) => { + return new Array(Math.round(count / 4)).fill(0).map(() => ({ title: Faker.address.country(), - data: new Array(Math.round(count / 2)).fill(0).map(() => ({ + data: new Array(Math.round(count / 4)).fill(0).map(() => ({ name: `${Faker.name.firstName()} ${Faker.name.lastName()}`, address: `${Faker.address.city()}, ${Faker.address.country()}`, jobTitle: Faker.name.jobTitle(), From 193733d5d087dfe3ef90aa671181c26ac050a282 Mon Sep 17 00:00:00 2001 From: Mo Gorhom Date: Sun, 22 Nov 2020 10:33:07 +0100 Subject: [PATCH 07/12] chore: updated examples --- example/src/components/weather/Weather.tsx | 11 +++++++++-- example/src/screens/advanced/MapExample.tsx | 17 +++++++++-------- src/components/bottomSheet/BottomSheet.tsx | 9 +++------ .../BottomSheetContainer.tsx | 16 +++++++++++++--- 4 files changed, 34 insertions(+), 19 deletions(-) diff --git a/example/src/components/weather/Weather.tsx b/example/src/components/weather/Weather.tsx index c94712edc..8138beae2 100644 --- a/example/src/components/weather/Weather.tsx +++ b/example/src/components/weather/Weather.tsx @@ -3,6 +3,7 @@ import { StyleSheet } from 'react-native'; import Animated, { Extrapolate, interpolate } from 'react-native-reanimated'; import { useAppearance } from '../../hooks'; import Text from '../text'; +import { SEARCH_HANDLE_HEIGHT } from '../../components/searchHandle'; interface WeatherProps { animatedPosition: Animated.Node; @@ -21,8 +22,14 @@ const Weather = ({ animatedPosition, snapPoints }: WeatherProps) => { transform: [ { translateY: interpolate(animatedPosition, { - inputRange: [snapPoints[0], snapPoints[1]], - outputRange: [-snapPoints[0], -snapPoints[1]], + inputRange: [ + snapPoints[0] + SEARCH_HANDLE_HEIGHT, + snapPoints[1] + SEARCH_HANDLE_HEIGHT, + ], + outputRange: [ + -(snapPoints[0] + SEARCH_HANDLE_HEIGHT), + -(snapPoints[1] + SEARCH_HANDLE_HEIGHT), + ], extrapolate: Extrapolate.CLAMP, }), }, diff --git a/example/src/screens/advanced/MapExample.tsx b/example/src/screens/advanced/MapExample.tsx index b6ba682d6..2f917cf61 100644 --- a/example/src/screens/advanced/MapExample.tsx +++ b/example/src/screens/advanced/MapExample.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useMemo, useRef } from 'react'; -import { View, StyleSheet, Dimensions, StatusBar } from 'react-native'; +import { View, StyleSheet, Dimensions } from 'react-native'; import MapView from 'react-native-maps'; import { interpolate, Extrapolate, Easing, max } from 'react-native-reanimated'; import { useValue } from 'react-native-redash'; @@ -12,9 +12,7 @@ import BottomSheet, { } from '@gorhom/bottom-sheet'; import withModalProvider from '../withModalProvider'; import { createLocationListMockData, Location } from '../../utils'; -import SearchHandle, { - SEARCH_HANDLE_HEIGHT, -} from '../../components/searchHandle'; +import SearchHandle from '../../components/searchHandle'; import LocationItem from '../../components/locationItem'; import LocationDetails, { LOCATION_DETAILS_HEIGHT, @@ -39,11 +37,11 @@ const MapExample = () => { const data = useMemo(() => createLocationListMockData(15), []); const snapPoints = useMemo( () => [ - SEARCH_HANDLE_HEIGHT + bottomSafeArea, + bottomSafeArea, LOCATION_DETAILS_HEIGHT + bottomSafeArea, - SCREEN_HEIGHT - topSafeArea - (StatusBar.currentHeight ?? 0), + SCREEN_HEIGHT, ], - [topSafeArea, bottomSafeArea] + [bottomSafeArea] ); const animatedPosition = useValue(0); const animatedModalPosition = useValue(0); @@ -91,6 +89,7 @@ const MapExample = () => { { initialSnapIndex: 1, snapPoints, + topInset: topSafeArea, animatedPosition: animatedModalPosition, animationDuration: 500, animationEasing: Easing.out(Easing.exp), @@ -103,6 +102,7 @@ const MapExample = () => { [ snapPoints, animatedModalPosition, + topSafeArea, present, handleCloseLocationDetails, handleLocationDetailSheetChanges, @@ -161,6 +161,7 @@ const MapExample = () => { pointerEvents="none" animatedOpacity={animatedOverlayOpacity} /> + { topInset={topSafeArea} animatedPosition={animatedPosition} animatedPositionIndex={animatedPositionIndex} - handleComponent={SearchHandle} animationDuration={500} animationEasing={Easing.out(Easing.exp)} + handleComponent={SearchHandle} backgroundComponent={BlurredBackground} onChange={handleSheetChanges} > diff --git a/src/components/bottomSheet/BottomSheet.tsx b/src/components/bottomSheet/BottomSheet.tsx index 2bcf2d93b..4918c999f 100644 --- a/src/components/bottomSheet/BottomSheet.tsx +++ b/src/components/bottomSheet/BottomSheet.tsx @@ -303,8 +303,8 @@ const BottomSheetComponent = forwardRef( [snapPoints, manualSnapToPoint] ); const handleClose = useCallback(() => { - manualSnapToPoint.setValue(containerHeight - topInset); - }, [manualSnapToPoint, containerHeight, topInset]); + manualSnapToPoint.setValue(containerHeight); + }, [manualSnapToPoint, containerHeight]); const handleExpand = useCallback(() => { manualSnapToPoint.setValue(snapPoints[snapPoints.length - 1]); }, [snapPoints, manualSnapToPoint]); @@ -454,10 +454,7 @@ const BottomSheetComponent = forwardRef( {_animatedPosition && ( )} diff --git a/src/components/bottomSheetContainer/BottomSheetContainer.tsx b/src/components/bottomSheetContainer/BottomSheetContainer.tsx index 0c5298bda..b59c6ea87 100644 --- a/src/components/bottomSheetContainer/BottomSheetContainer.tsx +++ b/src/components/bottomSheetContainer/BottomSheetContainer.tsx @@ -6,6 +6,7 @@ import React, { useRef, useState, } from 'react'; +import { Dimensions } from 'react-native'; import isEqual from 'lodash.isequal'; import invariant from 'invariant'; import { TapGestureHandler } from 'react-native-gesture-handler'; @@ -16,6 +17,8 @@ import type { BottomSheetMethods } from '../../types'; import type { BottomSheetContainerProps } from './types'; import { styles } from './styles'; +const { height: windowHeight } = Dimensions.get('window'); + type BottomSheet = BottomSheetMethods; const BottomSheetContainerComponent = forwardRef< @@ -30,7 +33,7 @@ const BottomSheetContainerComponent = forwardRef< //#endregion //#region state - const [containerHeight, setContainerHeight] = useState(-1); + const [containerHeight, setContainerHeight] = useState(windowHeight); //#endregion //#region refs @@ -76,7 +79,7 @@ const BottomSheetContainerComponent = forwardRef< onLayout={handleContainerOnLayout} {...containerTapGestureHandler} > - {height !== undefined && height !== -1 ? ( + {/* {height !== undefined && height !== -1 ? ( - ) : null} + ) : null} */} + ); //#endregion From a60b2a2c8f14f9d1a21c155fda2c0663f53a4199 Mon Sep 17 00:00:00 2001 From: Mo Gorhom Date: Sun, 22 Nov 2020 11:16:24 +0100 Subject: [PATCH 08/12] chore: added handleHeight prop --- README.md | 6 ++++++ example/src/screens/advanced/MapExample.tsx | 2 +- src/components/bottomSheet/BottomSheet.tsx | 14 ++++++++++---- src/components/bottomSheet/constants.ts | 2 ++ src/components/bottomSheet/types.d.ts | 5 +++++ 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 98154095f..da91e28f6 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,12 @@ To start the sheet closed and snap to initial index when it's mounted. > `required:` NO | `type:` boolean | `default:` false +#### `handleHeight` + +Handle height to help adjust snap points. + +> `required:` NO | `type:` number | `default:` undefined + #### `animationDuration` Snapping animation duration. diff --git a/example/src/screens/advanced/MapExample.tsx b/example/src/screens/advanced/MapExample.tsx index 2f917cf61..b5268bbb2 100644 --- a/example/src/screens/advanced/MapExample.tsx +++ b/example/src/screens/advanced/MapExample.tsx @@ -21,7 +21,7 @@ import LocationDetailsHandle from '../../components/locationDetailsHandle'; import Weather from '../../components/weather'; import BlurredBackground from '../../components/blurredBackground'; -const { height: SCREEN_HEIGHT } = Dimensions.get('window'); +const { height: SCREEN_HEIGHT } = Dimensions.get('screen'); const MapExample = () => { // refs diff --git a/src/components/bottomSheet/BottomSheet.tsx b/src/components/bottomSheet/BottomSheet.tsx index 4918c999f..01dba3de9 100644 --- a/src/components/bottomSheet/BottomSheet.tsx +++ b/src/components/bottomSheet/BottomSheet.tsx @@ -50,6 +50,7 @@ import { DEFAULT_ANIMATE_ON_MOUNT, DEFAULT_ANIMATION_EASING, DEFAULT_ANIMATION_DURATION, + DEFAULT_HANDLE_HEIGHT, } from './constants'; import type { ScrollableRef, BottomSheetMethods } from '../../types'; import type { BottomSheetProps } from './types'; @@ -79,6 +80,7 @@ const BottomSheetComponent = forwardRef( topInset = 0, enabled = true, animateOnMount = DEFAULT_ANIMATE_ON_MOUNT, + handleHeight: _handleHeight, // container props containerHeight, containerTapGestureRef, @@ -144,7 +146,9 @@ const BottomSheetComponent = forwardRef( //#endregion //#region state - const [handleHeight, setHandleHeight] = useState(-1); + const [handleHeight, setHandleHeight] = useState( + _handleHeight || DEFAULT_HANDLE_HEIGHT + ); //#endregion //#region refs @@ -282,10 +286,12 @@ const BottomSheetComponent = forwardRef( layout: { height }, }, }) => { - console.log('BottomSheet', 'handleHandleOnLayout', height); - setHandleHeight(height); + if (HandleComponent !== undefined && _handleHeight === undefined) { + console.log('BottomSheet', 'handleHandleOnLayout', height); + setHandleHeight(height); + } }, - [] + [_handleHeight, HandleComponent] ); //#endregion diff --git a/src/components/bottomSheet/constants.ts b/src/components/bottomSheet/constants.ts index d1158f8e1..3904c56c5 100644 --- a/src/components/bottomSheet/constants.ts +++ b/src/components/bottomSheet/constants.ts @@ -18,3 +18,5 @@ export const NORMAL_DECELERATION_RATE = Platform.select({ }); export const DEFAULT_ANIMATE_ON_MOUNT = false; + +export const DEFAULT_HANDLE_HEIGHT = 24; diff --git a/src/components/bottomSheet/types.d.ts b/src/components/bottomSheet/types.d.ts index 3408a00b0..601d41dc5 100644 --- a/src/components/bottomSheet/types.d.ts +++ b/src/components/bottomSheet/types.d.ts @@ -38,6 +38,11 @@ export type BottomSheetProps = { * @default false */ animateOnMount?: boolean; + /** + * Handle height to help adjust snap points. + * @type number + */ + handleHeight?: number; /** * Container height value to help calculate snap points values. * @type number From 679d2844777395b50e52cf888db8a84536dd142c Mon Sep 17 00:00:00 2001 From: Mo Gorhom Date: Sun, 22 Nov 2020 22:21:19 +0100 Subject: [PATCH 09/12] chore: rename internal components --- .../BottomSheetDefaultHandle.tsx} | 9 ++++++--- src/components/{handle => defaultHandle}/index.ts | 2 +- src/components/{handle => defaultHandle}/styles.ts | 0 src/components/{handle => defaultHandle}/types.d.ts | 0 4 files changed, 7 insertions(+), 4 deletions(-) rename src/components/{handle/Handle.tsx => defaultHandle/BottomSheetDefaultHandle.tsx} (58%) rename src/components/{handle => defaultHandle}/index.ts (50%) rename src/components/{handle => defaultHandle}/styles.ts (100%) rename src/components/{handle => defaultHandle}/types.d.ts (100%) diff --git a/src/components/handle/Handle.tsx b/src/components/defaultHandle/BottomSheetDefaultHandle.tsx similarity index 58% rename from src/components/handle/Handle.tsx rename to src/components/defaultHandle/BottomSheetDefaultHandle.tsx index 453aae630..aff81a801 100644 --- a/src/components/handle/Handle.tsx +++ b/src/components/defaultHandle/BottomSheetDefaultHandle.tsx @@ -3,7 +3,7 @@ import { View } from 'react-native'; import isEqual from 'lodash.isequal'; import { styles } from './styles'; -const BottomSheetHandleComponent = () => { +const BottomSheetDefaultHandleComponent = () => { return ( @@ -11,6 +11,9 @@ const BottomSheetHandleComponent = () => { ); }; -const BottomSheetHandle = memo(BottomSheetHandleComponent, isEqual); +const BottomSheetDefaultHandle = memo( + BottomSheetDefaultHandleComponent, + isEqual +); -export default BottomSheetHandle; +export default BottomSheetDefaultHandle; diff --git a/src/components/handle/index.ts b/src/components/defaultHandle/index.ts similarity index 50% rename from src/components/handle/index.ts rename to src/components/defaultHandle/index.ts index db9d54de8..238554627 100644 --- a/src/components/handle/index.ts +++ b/src/components/defaultHandle/index.ts @@ -1,2 +1,2 @@ -export { default } from './Handle'; +export { default } from './BottomSheetDefaultHandle'; export type { BottomSheetHandleProps } from './types'; diff --git a/src/components/handle/styles.ts b/src/components/defaultHandle/styles.ts similarity index 100% rename from src/components/handle/styles.ts rename to src/components/defaultHandle/styles.ts diff --git a/src/components/handle/types.d.ts b/src/components/defaultHandle/types.d.ts similarity index 100% rename from src/components/handle/types.d.ts rename to src/components/defaultHandle/types.d.ts From 40f4956a719756dbc1c91af2c69170352aff8209 Mon Sep 17 00:00:00 2001 From: Mo Gorhom Date: Sun, 22 Nov 2020 22:21:48 +0100 Subject: [PATCH 10/12] chore: added default background --- .../BottomSheetDefaultBackground.tsx | 15 +++++++++++++++ src/components/defaultBackground/index.ts | 1 + src/components/defaultBackground/styles.ts | 12 ++++++++++++ 3 files changed, 28 insertions(+) create mode 100644 src/components/defaultBackground/BottomSheetDefaultBackground.tsx create mode 100644 src/components/defaultBackground/index.ts create mode 100644 src/components/defaultBackground/styles.ts diff --git a/src/components/defaultBackground/BottomSheetDefaultBackground.tsx b/src/components/defaultBackground/BottomSheetDefaultBackground.tsx new file mode 100644 index 000000000..1bfc6c994 --- /dev/null +++ b/src/components/defaultBackground/BottomSheetDefaultBackground.tsx @@ -0,0 +1,15 @@ +import React, { memo } from 'react'; +import { View } from 'react-native'; +import isEqual from 'lodash.isequal'; +import { styles } from './styles'; + +const BottomSheetDefaultBackgroundComponent = () => ( + +); + +const BottomSheetDefaultBackground = memo( + BottomSheetDefaultBackgroundComponent, + isEqual +); + +export default BottomSheetDefaultBackground; diff --git a/src/components/defaultBackground/index.ts b/src/components/defaultBackground/index.ts new file mode 100644 index 000000000..4138d106a --- /dev/null +++ b/src/components/defaultBackground/index.ts @@ -0,0 +1 @@ +export { default } from './BottomSheetDefaultBackground'; diff --git a/src/components/defaultBackground/styles.ts b/src/components/defaultBackground/styles.ts new file mode 100644 index 000000000..00e3270de --- /dev/null +++ b/src/components/defaultBackground/styles.ts @@ -0,0 +1,12 @@ +import { StyleSheet } from 'react-native'; + +export const styles = StyleSheet.create({ + container: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: -100, + backgroundColor: 'white', + }, +}); From 03ba3295fa77739fc8a26530da71c04420d5a2aa Mon Sep 17 00:00:00 2001 From: Mo Gorhom Date: Sun, 22 Nov 2020 22:22:53 +0100 Subject: [PATCH 11/12] chore: updated handling nonflex views --- src/components/bottomSheet/BottomSheet.tsx | 53 +++++++++++++++---- src/components/bottomSheet/styles.ts | 4 +- src/components/bottomSheet/types.d.ts | 2 +- .../BottomSheetContainer.tsx | 12 ++--- src/components/view/View.tsx | 9 +++- src/components/view/styles.ts | 2 +- src/index.ts | 3 +- 7 files changed, 60 insertions(+), 25 deletions(-) diff --git a/src/components/bottomSheet/BottomSheet.tsx b/src/components/bottomSheet/BottomSheet.tsx index 01dba3de9..370c2f50a 100644 --- a/src/components/bottomSheet/BottomSheet.tsx +++ b/src/components/bottomSheet/BottomSheet.tsx @@ -33,7 +33,8 @@ import { // ReText, } from 'react-native-redash'; import DraggableView from '../draggableView'; -import Handle from '../handle'; +import DefaultHandle from '../defaultHandle'; +import DefaultBackground from '../defaultBackground'; import { useTransition } from './useTransition'; import { useStableCallback, @@ -91,8 +92,8 @@ const BottomSheetComponent = forwardRef( // callbacks onChange: _onChange, // components - handleComponent: HandleComponent = Handle, - backgroundComponent: BackgroundComponent = null, + handleComponent: HandleComponent, + backgroundComponent: BackgroundComponent = DefaultBackground, children, }, ref @@ -286,8 +287,12 @@ const BottomSheetComponent = forwardRef( layout: { height }, }, }) => { - if (HandleComponent !== undefined && _handleHeight === undefined) { - console.log('BottomSheet', 'handleHandleOnLayout', height); + if ( + HandleComponent !== undefined && + HandleComponent !== null && + _handleHeight === undefined + ) { + // console.log('BottomSheet \t\t', 'handleHandleOnLayout', height); setHandleHeight(height); } }, @@ -429,12 +434,39 @@ const BottomSheetComponent = forwardRef( ); //#endregion - // render - console.log('BottomSheet', 'render'); + //#region render + const renderBackground = useCallback( + () => + BackgroundComponent ? ( + + ) : null, + [BackgroundComponent] + ); + const renderHandle = useCallback( + () => + HandleComponent === null ? null : HandleComponent === undefined ? ( + + ) : ( + + ), + [HandleComponent, animatedPositionIndex] + ); + // console.log( + // 'BottomSheet \t\t', + // 'render', + // 'containerHeight: ', + // containerHeight, + // 'handleHeight: ', + // handleHeight, + // 'sheetHeight: ', + // sheetHeight, + // 'snapPoints: ', + // snapPoints + // ); return ( <> - {BackgroundComponent && } + {renderBackground()} ( {...handlePanGestureHandler} > - + {renderHandle()} @@ -508,6 +538,7 @@ const BottomSheetComponent = forwardRef( */} ); + //#endregion } ); diff --git a/src/components/bottomSheet/styles.ts b/src/components/bottomSheet/styles.ts index 018a8eb77..731644ddc 100644 --- a/src/components/bottomSheet/styles.ts +++ b/src/components/bottomSheet/styles.ts @@ -7,9 +7,7 @@ export const styles = StyleSheet.create({ right: 0, top: 0, }, - contentContainer: { - flex: 1, - }, + contentContainer: {}, debug: { position: 'absolute', left: 20, diff --git a/src/components/bottomSheet/types.d.ts b/src/components/bottomSheet/types.d.ts index 601d41dc5..9355821d9 100644 --- a/src/components/bottomSheet/types.d.ts +++ b/src/components/bottomSheet/types.d.ts @@ -2,7 +2,7 @@ import type React from 'react'; import type { ViewProps } from 'react-native'; import type Animated from 'react-native-reanimated'; import type { State, TapGestureHandler } from 'react-native-gesture-handler'; -import type { BottomSheetHandleProps } from '../handle'; +import type { BottomSheetHandleProps } from '../defaultHandle'; export type BottomSheetProps = { /** diff --git a/src/components/bottomSheetContainer/BottomSheetContainer.tsx b/src/components/bottomSheetContainer/BottomSheetContainer.tsx index b59c6ea87..100bb3d1b 100644 --- a/src/components/bottomSheetContainer/BottomSheetContainer.tsx +++ b/src/components/bottomSheetContainer/BottomSheetContainer.tsx @@ -58,11 +58,11 @@ const BottomSheetContainerComponent = forwardRef< const handleContainerOnLayout = useCallback( ({ nativeEvent: { layout } }) => { if (_height === undefined) { - console.log( - 'BottomSheetContainer', - 'handleContainerOnLayout', - layout.height - ); + // console.log( + // 'BottomSheetContainer \t', + // 'handleContainerOnLayout', + // layout.height + // ); setContainerHeight(layout.height); } }, @@ -71,7 +71,7 @@ const BottomSheetContainerComponent = forwardRef< //#endregion //#region render - console.log('BottomSheetContainer', 'render'); + // console.log('BottomSheetContainer \t', 'render \t'); return ( { // hooks const { scrollableContentOffsetY } = useBottomSheetInternal(); @@ -33,7 +34,11 @@ const BottomSheetViewComponent = ({ useFocusHook(handleSettingScrollable); //render - return {children}; + return ( + + {children} + + ); }; const BottomSheetView = memo(BottomSheetViewComponent, isEqual); diff --git a/src/components/view/styles.ts b/src/components/view/styles.ts index 31e937d84..0216192e5 100644 --- a/src/components/view/styles.ts +++ b/src/components/view/styles.ts @@ -2,6 +2,6 @@ import { StyleSheet } from 'react-native'; export const styles = StyleSheet.create({ container: { - flex: 1, + // flex: 1, }, }); diff --git a/src/index.ts b/src/index.ts index e83fb62ab..3abdee844 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,7 +16,8 @@ export const { } = BottomSheetTouchable; // default components / types -export type { BottomSheetHandleProps } from './components/handle'; +export type { BottomSheetHandleProps } from './components/defaultHandle'; + // overlay export { default as BottomSheetOverlay } from './components/overlay'; export type { BottomSheetOverlayProps } from './components/overlay'; From 57ce7598ff7d690add13b453bcdf622c20a64ee6 Mon Sep 17 00:00:00 2001 From: Mo Gorhom Date: Sun, 22 Nov 2020 22:23:29 +0100 Subject: [PATCH 12/12] chore: added dynamic snap point example --- example/src/App.tsx | 9 ++ example/src/screens/Root.tsx | 4 + .../advanced/DynamicSnapPointExample.tsx | 147 ++++++++++++++++++ example/src/types.ts | 1 + 4 files changed, 161 insertions(+) create mode 100644 example/src/screens/advanced/DynamicSnapPointExample.tsx diff --git a/example/src/App.tsx b/example/src/App.tsx index c4ae9d976..16465daf1 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -115,6 +115,15 @@ const App = () => { require('./screens/advanced/MapExample').default } /> + + require('./screens/advanced/DynamicSnapPointExample').default + } + /> diff --git a/example/src/screens/Root.tsx b/example/src/screens/Root.tsx index b55ed8989..7e40449f4 100644 --- a/example/src/screens/Root.tsx +++ b/example/src/screens/Root.tsx @@ -62,6 +62,10 @@ const data = [ name: 'Map', slug: 'Advanced/MapExample', }, + { + name: 'Dynamic Snap Point', + slug: 'Advanced/DynamicSnapPointExample', + }, ], }, ].reverse(); diff --git a/example/src/screens/advanced/DynamicSnapPointExample.tsx b/example/src/screens/advanced/DynamicSnapPointExample.tsx new file mode 100644 index 000000000..8379dc368 --- /dev/null +++ b/example/src/screens/advanced/DynamicSnapPointExample.tsx @@ -0,0 +1,147 @@ +import React, { useCallback, useMemo, useRef, useState } from 'react'; +import { View, StyleSheet, Text } from 'react-native'; +import BottomSheet, { BottomSheetView } from '@gorhom/bottom-sheet'; +import { useSafeArea } from 'react-native-safe-area-context'; +import { Easing } from 'react-native-reanimated'; +import Button from '../../components/button'; + +const DynamicSnapPointExample = () => { + // state + const [count, setCount] = useState(0); + const [contentHeight, setContentHeight] = useState(0); + + // hooks + const bottomSheetRef = useRef(null); + const { bottom: safeBottomArea } = useSafeArea(); + + // variables + const snapPoints = useMemo(() => [0, contentHeight], [contentHeight]); + + // callbacks + const handleIncreaseContentPress = useCallback(() => { + setCount(state => state + 1); + }, []); + const handleDecreaseContentPress = useCallback(() => { + setCount(state => Math.max(state - 1, 0)); + }, []); + const handleExpandPress = useCallback(() => { + bottomSheetRef.current?.expand(); + }, []); + const handleClosePress = useCallback(() => { + bottomSheetRef.current?.close(); + }, []); + const handleOnLayout = useCallback( + ({ + nativeEvent: { + layout: { height }, + }, + }) => { + // console.log('SCREEN \t\t', 'handleOnLayout', height); + setContentHeight(height); + }, + [] + ); + + // styles + const contentContainerStyle = useMemo( + () => ({ + ...styles.contentContainerStyle, + paddingBottom: safeBottomArea, + }), + [safeBottomArea] + ); + const emojiContainerStyle = useMemo( + () => ({ + ...styles.emojiContainer, + height: 50 * count, + }), + [count] + ); + + // renders + const renderBackground = useCallback( + () => , + [] + ); + + // console.log('SCREEN \t\t', 'render \t'); + return ( + +