Skip to content
This repository has been archived by the owner on Jan 20, 2024. It is now read-only.

Custom bottom sheet handle animation #240

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
157 changes: 147 additions & 10 deletions components/CustomHandle.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,159 @@
import React from "react";
import React, { useMemo } from "react";
import { useColorScheme } from "react-native";
import Animated from "react-native-reanimated";

import { styles } from "../lib/pages";
import { StyleProp, StyleSheet, ViewStyle } from "react-native";
import { BottomSheetHandleProps } from "@gorhom/bottom-sheet";
import Animated, {
Extrapolate,
interpolate,
useAnimatedStyle,
useDerivedValue,
} from "react-native-reanimated";
import { toRad } from "react-native-redash";
import { color } from "react-native-elements/dist/helpers";

// @ts-ignore
export const transformOrigin = ({ x, y }, ...transformations) => {
"worklet";
return [
{ translateX: x },
{ translateY: y },
...transformations,
{ translateX: x * -1 },
{ translateY: y * -1 },
];
};

interface HandleProps extends BottomSheetHandleProps {
style?: StyleProp<ViewStyle>;
}

const Handle: React.FC<HandleProps> = ({ style, animatedIndex }) => {

const Handle = () => {
const colorScheme = useColorScheme();

//#region animations
const indicatorTransformOriginY = useDerivedValue(() =>
interpolate(animatedIndex.value, [0, 1, 2], [-1, 0, 1], Extrapolate.CLAMP)
);
//#endregion

//#region styles

const containerStyle = useMemo(
() => [
{
...styles.header,
backgroundColor: colorScheme === "dark" ? "#404040" : "#fff",
borderBottomColor: colorScheme === "dark" ? "#404040" : "#fff",
},
style,
],
[style]
);
const containerAnimatedStyle = useAnimatedStyle(() => {
const borderTopRadius = interpolate(
animatedIndex.value,
[1, 2],
[20, 0],
Extrapolate.CLAMP
);
return {
borderTopLeftRadius: borderTopRadius,
borderTopRightRadius: borderTopRadius,
};
});
const leftIndicatorStyle = useMemo(
() => ({
...styles.indicator,
...styles.leftIndicator,
}),
[]
);
const leftIndicatorAnimatedStyle = useAnimatedStyle(() => {
const leftIndicatorRotate = interpolate(
animatedIndex.value,
[0, 1, 2],
[toRad(-30), 0, toRad(30)],
Extrapolate.CLAMP
);
return {
transform: transformOrigin(
{ x: 0, y: indicatorTransformOriginY.value },
{
rotate: `${leftIndicatorRotate}rad`,
},
{
translateX: -5,
}
),
};
});
const rightIndicatorStyle = useMemo(
() => ({
...styles.indicator,
...styles.rightIndicator,
}),
[]
);
const rightIndicatorAnimatedStyle = useAnimatedStyle(() => {
const rightIndicatorRotate = interpolate(
animatedIndex.value,
[0, 1, 2],
[toRad(30), 0, toRad(-30)],
Extrapolate.CLAMP
);
return {
transform: transformOrigin(
{ x: 0, y: indicatorTransformOriginY.value },
{
rotate: `${rightIndicatorRotate}rad`,
},
{
translateX: 5,
}
),
};
});
//#endregion

// render
return (
<Animated.View style={styles.header}>
<Animated.View
style={[containerStyle, containerAnimatedStyle]}
renderToHardwareTextureAndroid={true}
>
<Animated.View style={[leftIndicatorStyle, leftIndicatorAnimatedStyle]} />
<Animated.View
style={{
...styles.indicator,
backgroundColor:
colorScheme === "dark" ? "white" : "rgba(0, 0, 0, 0.75)",
}}
style={[rightIndicatorStyle, rightIndicatorAnimatedStyle]}
/>
</Animated.View>
);
};

export default Handle;

const styles = StyleSheet.create({
header: {
alignContent: "center",
alignItems: "center",
justifyContent: "center",
backgroundColor: "white",
paddingVertical: 14,
borderBottomWidth: 1,
},
indicator: {
position: "absolute",
width: 10,
height: 4,
backgroundColor: "#999",
},
leftIndicator: {
borderTopStartRadius: 2,
borderBottomStartRadius: 2,
},
rightIndicator: {
borderTopEndRadius: 2,
borderBottomEndRadius: 2,
},
});