Skip to content

Commit

Permalink
feat: allow dynamic snap points (#81)
Browse files Browse the repository at this point in the history
* chore: updated normalized snap points implementation

* chore: reverse snap points transitions

* chore: updated content wrapper

* chore: added bottom sheet container

* chore: updated bottom sheet to handle dynamic snap points

* chore: updated examples

* chore: updated examples

* chore: added handleHeight prop

* chore: rename internal components

* chore: added default background

* chore: updated handling nonflex views

* chore: added dynamic snap point example
  • Loading branch information
gorhom authored Nov 27, 2020
1 parent 377c2b3 commit 7304867
Show file tree
Hide file tree
Showing 48 changed files with 698 additions and 203 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
9 changes: 9 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,15 @@ const App = () => {
require('./screens/advanced/MapExample').default
}
/>
<Stack.Screen
name="Advanced/DynamicSnapPointExample"
options={{
title: 'Dynamic Snap Point',
}}
getComponent={() =>
require('./screens/advanced/DynamicSnapPointExample').default
}
/>
</Stack.Navigator>
</NavigationContainer>
</AppearanceProvider>
Expand Down
51 changes: 30 additions & 21 deletions example/src/components/handle/Handle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,33 @@ interface HandleProps extends BottomSheetHandleProps {

const Handle: React.FC<HandleProps> = ({ 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)],
Expand All @@ -42,8 +54,7 @@ const Handle: React.FC<HandleProps> = ({ style, animatedPositionIndex }) => {
borderTopRightRadius: borderTopRadius,
},
],
// eslint-disable-next-line react-hooks/exhaustive-deps
[style]
[borderTopRadius, style]
);
const leftIndicatorStyle = useMemo(
() => ({
Expand All @@ -57,8 +68,7 @@ const Handle: React.FC<HandleProps> = ({ style, animatedPositionIndex }) => {
}
),
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
[indicatorTransformOriginY, leftIndicatorRotate]
);
const rightIndicatorStyle = useMemo(
() => ({
Expand All @@ -72,8 +82,7 @@ const Handle: React.FC<HandleProps> = ({ style, animatedPositionIndex }) => {
}
),
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
[indicatorTransformOriginY, rightIndicatorRotate]
);
//#endregion

Expand Down
11 changes: 9 additions & 2 deletions example/src/components/weather/Weather.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<number>;
Expand All @@ -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,
}),
},
Expand Down
4 changes: 4 additions & 0 deletions example/src/screens/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ const data = [
name: 'Map',
slug: 'Advanced/MapExample',
},
{
name: 'Dynamic Snap Point',
slug: 'Advanced/DynamicSnapPointExample',
},
],
},
].reverse();
Expand Down
3 changes: 0 additions & 3 deletions example/src/screens/advanced/CustomHandleExample.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -12,7 +11,6 @@ const CustomHandleExample = () => {

// hooks
const bottomSheetRef = useRef<BottomSheet>(null);
const headerHeight = useHeaderHeight();

// variables
const snapPoints = useMemo(() => [150, 300, 450], []);
Expand Down Expand Up @@ -88,7 +86,6 @@ const CustomHandleExample = () => {
enabled={enabled}
snapPoints={snapPoints}
initialSnapIndex={1}
topInset={headerHeight}
handleComponent={Handle}
>
<ContactList type="View" count={3} header={renderHeader} />
Expand Down
147 changes: 147 additions & 0 deletions example/src/screens/advanced/DynamicSnapPointExample.tsx
Original file line number Diff line number Diff line change
@@ -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<BottomSheet>(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(
() => <View style={styles.background} />,
[]
);

// console.log('SCREEN \t\t', 'render \t');
return (
<View style={styles.container}>
<Button
label="Expand"
style={styles.buttonContainer}
onPress={handleExpandPress}
/>
<Button
label="Close"
style={styles.buttonContainer}
onPress={handleClosePress}
/>
<BottomSheet
ref={bottomSheetRef}
snapPoints={snapPoints}
initialSnapIndex={1}
animateOnMount={true}
animationEasing={Easing.out(Easing.quad)}
animationDuration={250}
backgroundComponent={renderBackground}
>
<BottomSheetView
style={contentContainerStyle}
onLayout={handleOnLayout}
>
<Text style={styles.message}>
Could this sheet resize to its content height ?
</Text>
<View style={emojiContainerStyle}>
<Text style={styles.emoji}>😍</Text>
</View>
<Button
label="Yes"
style={styles.buttonContainer}
onPress={handleIncreaseContentPress}
/>
<Button
label="Mayby"
style={styles.buttonContainer}
onPress={handleDecreaseContentPress}
/>
</BottomSheetView>
</BottomSheet>
</View>
);
};

const styles = StyleSheet.create({
container: {
flex: 1,
padding: 24,
},
background: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'white',
},
buttonContainer: {
marginBottom: 6,
},
contentContainerStyle: {
paddingTop: 12,
paddingHorizontal: 24,
backgroundColor: 'white',
},
message: {
fontSize: 24,
fontWeight: '600',
marginBottom: 12,
},
emoji: {
fontSize: 156,
textAlign: 'center',
alignSelf: 'center',
},
emojiContainer: {
justifyContent: 'center',
},
});

export default DynamicSnapPointExample;
19 changes: 10 additions & 9 deletions example/src/screens/advanced/MapExample.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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,
Expand All @@ -23,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
Expand All @@ -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<number>(0);
const animatedModalPosition = useValue<number>(0);
Expand Down Expand Up @@ -91,6 +89,7 @@ const MapExample = () => {
{
initialSnapIndex: 1,
snapPoints,
topInset: topSafeArea,
animatedPosition: animatedModalPosition,
animationDuration: 500,
animationEasing: Easing.out(Easing.exp),
Expand All @@ -103,6 +102,7 @@ const MapExample = () => {
[
snapPoints,
animatedModalPosition,
topSafeArea,
present,
handleCloseLocationDetails,
handleLocationDetailSheetChanges,
Expand Down Expand Up @@ -161,6 +161,7 @@ const MapExample = () => {
pointerEvents="none"
animatedOpacity={animatedOverlayOpacity}
/>

<Weather
animatedPosition={weatherAnimatedPosition}
snapPoints={snapPoints}
Expand All @@ -172,9 +173,9 @@ const MapExample = () => {
topInset={topSafeArea}
animatedPosition={animatedPosition}
animatedPositionIndex={animatedPositionIndex}
handleComponent={SearchHandle}
animationDuration={500}
animationEasing={Easing.out(Easing.exp)}
handleComponent={SearchHandle}
backgroundComponent={BlurredBackground}
onChange={handleSheetChanges}
>
Expand Down
Loading

0 comments on commit 7304867

Please sign in to comment.