import { Ionicons } from "@expo/vector-icons"; import { ReactNode, useRef } from "react"; import { Pressable, type PressableProps, StyleSheet, Text, View } from "react-native"; import Swipeable, { type SwipeableMethods, } from "react-native-gesture-handler/ReanimatedSwipeable"; import { fonts, radii, spacing } from "@/constants/theme"; import { useAppTheme } from "@/contexts/ThemeContext"; import type { ThemeColors } from "@/lib/theme-palette"; import { useThemedStyles } from "@/lib/use-themed-styles"; export type SwipeAction = { key: string; label: string; icon: keyof typeof Ionicons.glyphMap; color: string; backgroundColor: string; onPress: () => void; }; type SwipeableRowProps = { children: ReactNode; actions: SwipeAction[]; enabled?: boolean; backgroundColor?: string; onPress?: () => void; onLongPress?: () => void; contentStyle?: PressableProps["style"]; }; export function SwipeableRow({ children, actions, enabled = true, backgroundColor, onPress, onLongPress, contentStyle, }: SwipeableRowProps) { const { colors } = useAppTheme(); const styles = useThemedStyles(createSwipeableRowStyles); const rowBackground = backgroundColor ?? colors.background; const swipeRef = useRef(null); const rowOpenRef = useRef(false); const suppressPressUntilRef = useRef(0); function suppressContentPress() { suppressPressUntilRef.current = Date.now() + 350; } function handleContentPress() { if (!onPress) return; if (rowOpenRef.current || Date.now() < suppressPressUntilRef.current) { swipeRef.current?.close(); rowOpenRef.current = false; return; } onPress(); } function renderContent() { if (!onPress && !onLongPress) { return ( {children} ); } return ( [ styles.row, { backgroundColor: rowBackground }, typeof contentStyle === "function" ? contentStyle(state) : contentStyle, ]} > {children} ); } function renderRightActions() { return ( {actions.map((action) => ( { suppressContentPress(); swipeRef.current?.close(); rowOpenRef.current = false; action.onPress(); }} > {action.label} ))} ); } if (!enabled || actions.length === 0) { return renderContent(); } return ( { suppressContentPress(); rowOpenRef.current = true; }} onSwipeableWillClose={suppressContentPress} onSwipeableClose={() => { rowOpenRef.current = false; }} > {renderContent()} ); } const createSwipeableRowStyles = (colors: ThemeColors) => StyleSheet.create({ row: { backgroundColor: colors.background, borderRadius: radii.lg, overflow: "hidden", marginBottom: spacing.xs, }, actions: { flexDirection: "row", alignItems: "stretch", }, actionButton: { width: 80, alignItems: "center", justifyContent: "center", gap: spacing.xs, borderRadius: radii.md, marginLeft: spacing.xs, }, actionLabel: { fontFamily: fonts.bodyMedium, fontSize: 11, }, });