feat(workouts): add swipe-to-delete with gesture handler and pull-to-refresh
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
import { useRef } from "react";
|
||||
import { View, Text, Pressable, Animated, StyleSheet } from "react-native";
|
||||
import { PanGestureHandler, State } from "react-native-gesture-handler";
|
||||
|
||||
type SwipeableRowProps = {
|
||||
children: React.ReactNode;
|
||||
onDelete: () => void;
|
||||
};
|
||||
|
||||
export function SwipeableRow({ children, onDelete }: SwipeableRowProps) {
|
||||
const translateX = useRef(new Animated.Value(0)).current;
|
||||
const lastOffset = useRef(0);
|
||||
|
||||
const onGestureEvent = Animated.event([{ nativeEvent: { translationX: translateX } }], {
|
||||
useNativeDriver: true,
|
||||
});
|
||||
|
||||
const onHandlerStateChange = (event: any) => {
|
||||
if (event.oldState === State.ACTIVE) {
|
||||
const { translationX } = event.nativeEvent;
|
||||
const newOffset = lastOffset.current + translationX;
|
||||
|
||||
if (newOffset < -80) {
|
||||
// Swiped left enough — show delete
|
||||
Animated.spring(translateX, {
|
||||
toValue: -80,
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
lastOffset.current = -80;
|
||||
} else {
|
||||
// Snap back
|
||||
Animated.spring(translateX, {
|
||||
toValue: 0,
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
lastOffset.current = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
Animated.timing(translateX, {
|
||||
toValue: -400,
|
||||
duration: 200,
|
||||
useNativeDriver: true,
|
||||
}).start(() => {
|
||||
onDelete();
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.deleteBackground}>
|
||||
<Pressable onPress={handleDelete} style={styles.deleteButton}>
|
||||
<Text style={styles.deleteText}>Delete</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
<PanGestureHandler
|
||||
onGestureEvent={onGestureEvent}
|
||||
onHandlerStateChange={onHandlerStateChange}
|
||||
>
|
||||
<Animated.View style={[styles.foreground, { transform: [{ translateX }] }]}>
|
||||
{children}
|
||||
</Animated.View>
|
||||
</PanGestureHandler>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
marginBottom: 12,
|
||||
},
|
||||
deleteBackground: {
|
||||
position: "absolute",
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
width: 80,
|
||||
borderRadius: 16,
|
||||
backgroundColor: "#ef4444",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
deleteButton: {
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
},
|
||||
deleteText: {
|
||||
color: "white",
|
||||
fontWeight: "600",
|
||||
fontSize: 13,
|
||||
},
|
||||
foreground: {
|
||||
backgroundColor: "white",
|
||||
borderRadius: 16,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user