animation

This commit is contained in:
Thomas Demirdjian
2025-10-03 17:25:44 +02:00
parent 45b7571c14
commit 3d14e47770
@@ -1,5 +1,7 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { import {
Animated,
Easing,
FlatList, FlatList,
Image, Image,
Pressable, Pressable,
@@ -43,6 +45,7 @@ const ProjectDropDown = ({
}) => { }) => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [triggerLayout, setTriggerLayout] = useState(null); const [triggerLayout, setTriggerLayout] = useState(null);
const dropdownAnimation = useRef(new Animated.Value(0)).current;
const normalizedProjects = Array.isArray(projects) ? projects : []; const normalizedProjects = Array.isArray(projects) ? projects : [];
const isDisabled = normalizedProjects.length === 0; const isDisabled = normalizedProjects.length === 0;
@@ -102,6 +105,52 @@ const ProjectDropDown = ({
[triggerLayout], [triggerLayout],
); );
useEffect(() => {
Animated.timing(dropdownAnimation, {
toValue: isOpen ? 1 : 0,
duration: 220,
easing: isOpen ? Easing.out(Easing.cubic) : Easing.in(Easing.cubic),
useNativeDriver: true,
}).start();
}, [dropdownAnimation, isOpen]);
const overlayTranslateY = useMemo(
() =>
dropdownAnimation.interpolate({
inputRange: [0, 1],
outputRange: [-12, 0],
}),
[dropdownAnimation],
);
const overlayScale = useMemo(
() =>
dropdownAnimation.interpolate({
inputRange: [0, 1],
outputRange: [0.96, 1],
}),
[dropdownAnimation],
);
const triggerOpacity = useMemo(
() =>
dropdownAnimation.interpolate({
inputRange: [0, 1],
outputRange: [1, 0],
extrapolate: "clamp",
}),
[dropdownAnimation],
);
const triggerTranslateY = useMemo(
() =>
dropdownAnimation.interpolate({
inputRange: [0, 1],
outputRange: [0, -6],
}),
[dropdownAnimation],
);
return ( return (
<View style={[styles.container, style]}> <View style={[styles.container, style]}>
{isOpen ? ( {isOpen ? (
@@ -111,6 +160,16 @@ const ProjectDropDown = ({
<View <View
style={styles.triggerWrapper} style={styles.triggerWrapper}
onLayout={(event) => setTriggerLayout(event.nativeEvent.layout)} onLayout={(event) => setTriggerLayout(event.nativeEvent.layout)}
>
<Animated.View
pointerEvents={isOpen ? "none" : "auto"}
style={[
styles.dropdownTriggerContainer,
{
opacity: triggerOpacity,
transform: [{ translateY: triggerTranslateY }],
},
]}
> >
<BlurView <BlurView
intensity={BUTTON_BLUR_INTENSITY} intensity={BUTTON_BLUR_INTENSITY}
@@ -118,7 +177,6 @@ const ProjectDropDown = ({
style={[ style={[
styles.dropdownBlur, styles.dropdownBlur,
isDisabled && styles.dropdownBlurDisabled, isDisabled && styles.dropdownBlurDisabled,
isOpen && styles.dropdownBlurHidden,
]} ]}
> >
<ProjectRow <ProjectRow
@@ -129,16 +187,26 @@ const ProjectDropDown = ({
onSelect={toggleDropdown} onSelect={toggleDropdown}
/> />
</BlurView> </BlurView>
</Animated.View>
<Animated.View
pointerEvents={isOpen ? "auto" : "none"}
style={[
styles.dropdownOverlayContainer,
{ width: dropdownWidth || "100%" },
{
opacity: dropdownAnimation,
transform: [
{ translateY: overlayTranslateY },
{ scale: overlayScale },
],
},
]}
>
<BlurView <BlurView
intensity={BUTTON_BLUR_INTENSITY} intensity={BUTTON_BLUR_INTENSITY}
tint="dark" tint="dark"
pointerEvents={isOpen ? "auto" : "none"} style={styles.dropdownOverlay}
style={[
styles.dropdownOverlay,
{ width: dropdownWidth || "100%" },
!isOpen && styles.dropdownOverlayHidden,
]}
> >
<ProjectRow <ProjectRow
isTitle={true} isTitle={true}
@@ -175,6 +243,7 @@ const ProjectDropDown = ({
/> />
</View> </View>
</BlurView> </BlurView>
</Animated.View>
</View> </View>
</View> </View>
); );
@@ -295,29 +364,30 @@ const styles = StyleSheet.create({
dropdownBlurDisabled: { dropdownBlurDisabled: {
opacity: 0.6, opacity: 0.6,
}, },
dropdownBlurHidden: {
opacity: 0,
pointerEvents: "none",
},
triggerWrapper: { triggerWrapper: {
position: "relative", position: "relative",
zIndex: 3, zIndex: 3,
}, },
dropdownOverlay: { dropdownTriggerContainer: {
width: "100%",
},
dropdownOverlayContainer: {
position: "absolute", position: "absolute",
top: 0, top: 0,
left: 0, left: 0,
borderRadius: 16, borderRadius: 16,
overflow: "hidden",
zIndex: 4,
elevation: 4,
maxHeight: 400,
},
dropdownOverlay: {
flex: 1,
width: "100%",
paddingHorizontal: 8, paddingHorizontal: 8,
paddingVertical: 8, paddingVertical: 8,
gap: 9, gap: 9,
maxHeight: 400, maxHeight: 400,
zIndex: 4,
elevation: 4,
overflow: "hidden",
},
dropdownOverlayHidden: {
opacity: 0,
}, },
dropdownOverlayContent: { dropdownOverlayContent: {
maxHeight: 260, maxHeight: 260,