Prevent swipe rows from opening items
This commit is contained in:
@@ -2,7 +2,6 @@ import { router } from "expo-router";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
Pressable,
|
|
||||||
RefreshControl,
|
RefreshControl,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
@@ -155,8 +154,8 @@ export default function EntitiesScreen() {
|
|||||||
onPress: () => confirmDelete(client.id, client.name),
|
onPress: () => confirmDelete(client.id, client.name),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
onPress={() => router.push(`/(app)/entities/clients/${client.id}`)}
|
||||||
>
|
>
|
||||||
<Pressable onPress={() => router.push(`/(app)/entities/clients/${client.id}`)}>
|
|
||||||
<GlassSurface style={styles.card}>
|
<GlassSurface style={styles.card}>
|
||||||
<View style={styles.cardInner}>
|
<View style={styles.cardInner}>
|
||||||
<Text style={styles.name}>{client.name}</Text>
|
<Text style={styles.name}>{client.name}</Text>
|
||||||
@@ -171,7 +170,6 @@ export default function EntitiesScreen() {
|
|||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
</GlassSurface>
|
</GlassSurface>
|
||||||
</Pressable>
|
|
||||||
</SwipeableRow>
|
</SwipeableRow>
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
@@ -205,8 +203,8 @@ export default function EntitiesScreen() {
|
|||||||
onPress: () => confirmDelete(business.id, business.name),
|
onPress: () => confirmDelete(business.id, business.name),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
onPress={() => router.push(`/(app)/entities/businesses/${business.id}`)}
|
||||||
>
|
>
|
||||||
<Pressable onPress={() => router.push(`/(app)/entities/businesses/${business.id}`)}>
|
|
||||||
<GlassSurface style={styles.card}>
|
<GlassSurface style={styles.card}>
|
||||||
<View style={styles.cardInner}>
|
<View style={styles.cardInner}>
|
||||||
<View style={styles.nameRow}>
|
<View style={styles.nameRow}>
|
||||||
@@ -221,7 +219,6 @@ export default function EntitiesScreen() {
|
|||||||
{business.email ? <Text style={styles.meta}>{business.email}</Text> : null}
|
{business.email ? <Text style={styles.meta}>{business.email}</Text> : null}
|
||||||
</View>
|
</View>
|
||||||
</GlassSurface>
|
</GlassSurface>
|
||||||
</Pressable>
|
|
||||||
</SwipeableRow>
|
</SwipeableRow>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { router } from "expo-router";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
Pressable,
|
|
||||||
RefreshControl,
|
RefreshControl,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
@@ -205,8 +204,10 @@ export default function InvoicesScreen() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SwipeableRow key={invoice.id} actions={actions} backgroundColor={colors.cardGlass}>
|
<SwipeableRow
|
||||||
<Pressable
|
key={invoice.id}
|
||||||
|
actions={actions}
|
||||||
|
backgroundColor={colors.cardGlass}
|
||||||
onPress={() => router.push(`/(app)/invoices/${invoice.id}`)}
|
onPress={() => router.push(`/(app)/invoices/${invoice.id}`)}
|
||||||
onLongPress={() => promptStatusChange(invoice.id, status)}
|
onLongPress={() => promptStatusChange(invoice.id, status)}
|
||||||
>
|
>
|
||||||
@@ -229,7 +230,6 @@ export default function InvoicesScreen() {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</GlassSurface>
|
</GlassSurface>
|
||||||
</Pressable>
|
|
||||||
</SwipeableRow>
|
</SwipeableRow>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Ionicons } from "@expo/vector-icons";
|
|||||||
import { router } from "expo-router";
|
import { router } from "expo-router";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Pressable,
|
|
||||||
RefreshControl,
|
RefreshControl,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
@@ -225,6 +224,12 @@ function ExpenseRow({ expense, onDelete }: { expense: Expense; onDelete: () => v
|
|||||||
return (
|
return (
|
||||||
<SwipeableRow
|
<SwipeableRow
|
||||||
backgroundColor={colors.cardGlass}
|
backgroundColor={colors.cardGlass}
|
||||||
|
contentStyle={({ pressed }) => [
|
||||||
|
styles.row,
|
||||||
|
{ borderColor: colors.border },
|
||||||
|
pressed && styles.rowPressed,
|
||||||
|
]}
|
||||||
|
onPress={() => router.push(`/(app)/more/expenses/${expense.id}` as never)}
|
||||||
actions={[
|
actions={[
|
||||||
{
|
{
|
||||||
key: "open",
|
key: "open",
|
||||||
@@ -243,15 +248,6 @@ function ExpenseRow({ expense, onDelete }: { expense: Expense; onDelete: () => v
|
|||||||
onPress: onDelete,
|
onPress: onDelete,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
|
||||||
<Pressable
|
|
||||||
accessibilityRole="button"
|
|
||||||
onPress={() => router.push(`/(app)/more/expenses/${expense.id}` as never)}
|
|
||||||
style={({ pressed }) => [
|
|
||||||
styles.row,
|
|
||||||
{ borderColor: colors.border },
|
|
||||||
pressed && styles.rowPressed,
|
|
||||||
]}
|
|
||||||
>
|
>
|
||||||
<View style={[styles.categoryIcon, { backgroundColor: colors.muted }]}>
|
<View style={[styles.categoryIcon, { backgroundColor: colors.muted }]}>
|
||||||
<Ionicons name={expenseIcon(expense.category)} size={18} color={colors.primary} />
|
<Ionicons name={expenseIcon(expense.category)} size={18} color={colors.primary} />
|
||||||
@@ -304,7 +300,6 @@ function ExpenseRow({ expense, onDelete }: { expense: Expense; onDelete: () => v
|
|||||||
</Text>
|
</Text>
|
||||||
<Ionicons name="chevron-forward" size={16} color={colors.mutedForeground} />
|
<Ionicons name="chevron-forward" size={16} color={colors.mutedForeground} />
|
||||||
</View>
|
</View>
|
||||||
</Pressable>
|
|
||||||
</SwipeableRow>
|
</SwipeableRow>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import { ReactNode, useRef } from "react";
|
import { ReactNode, useRef } from "react";
|
||||||
import { Pressable, StyleSheet, Text, View } from "react-native";
|
import { Pressable, type PressableProps, StyleSheet, Text, View } from "react-native";
|
||||||
import Swipeable, {
|
import Swipeable, {
|
||||||
type SwipeableMethods,
|
type SwipeableMethods,
|
||||||
} from "react-native-gesture-handler/ReanimatedSwipeable";
|
} from "react-native-gesture-handler/ReanimatedSwipeable";
|
||||||
@@ -24,6 +24,9 @@ type SwipeableRowProps = {
|
|||||||
actions: SwipeAction[];
|
actions: SwipeAction[];
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
backgroundColor?: string;
|
backgroundColor?: string;
|
||||||
|
onPress?: () => void;
|
||||||
|
onLongPress?: () => void;
|
||||||
|
contentStyle?: PressableProps["style"];
|
||||||
};
|
};
|
||||||
|
|
||||||
export function SwipeableRow({
|
export function SwipeableRow({
|
||||||
@@ -31,11 +34,63 @@ export function SwipeableRow({
|
|||||||
actions,
|
actions,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
|
onPress,
|
||||||
|
onLongPress,
|
||||||
|
contentStyle,
|
||||||
}: SwipeableRowProps) {
|
}: SwipeableRowProps) {
|
||||||
const { colors } = useAppTheme();
|
const { colors } = useAppTheme();
|
||||||
const styles = useThemedStyles(createSwipeableRowStyles);
|
const styles = useThemedStyles(createSwipeableRowStyles);
|
||||||
const rowBackground = backgroundColor ?? colors.background;
|
const rowBackground = backgroundColor ?? colors.background;
|
||||||
const swipeRef = useRef<SwipeableMethods>(null);
|
const swipeRef = useRef<SwipeableMethods>(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 (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.row,
|
||||||
|
{ backgroundColor: rowBackground },
|
||||||
|
typeof contentStyle === "function" ? undefined : contentStyle,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Pressable
|
||||||
|
accessibilityRole="button"
|
||||||
|
onPress={handleContentPress}
|
||||||
|
onLongPress={onLongPress}
|
||||||
|
style={(state) => [
|
||||||
|
styles.row,
|
||||||
|
{ backgroundColor: rowBackground },
|
||||||
|
typeof contentStyle === "function" ? contentStyle(state) : contentStyle,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Pressable>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function renderRightActions() {
|
function renderRightActions() {
|
||||||
return (
|
return (
|
||||||
@@ -45,7 +100,9 @@ export function SwipeableRow({
|
|||||||
key={action.key}
|
key={action.key}
|
||||||
style={[styles.actionButton, { backgroundColor: action.backgroundColor }]}
|
style={[styles.actionButton, { backgroundColor: action.backgroundColor }]}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
suppressContentPress();
|
||||||
swipeRef.current?.close();
|
swipeRef.current?.close();
|
||||||
|
rowOpenRef.current = false;
|
||||||
action.onPress();
|
action.onPress();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -58,12 +115,26 @@ export function SwipeableRow({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!enabled || actions.length === 0) {
|
if (!enabled || actions.length === 0) {
|
||||||
return <View style={[styles.row, { backgroundColor: rowBackground }]}>{children}</View>;
|
return renderContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Swipeable ref={swipeRef} renderRightActions={renderRightActions} overshootRight={false}>
|
<Swipeable
|
||||||
<View style={[styles.row, { backgroundColor: rowBackground }]}>{children}</View>
|
ref={swipeRef}
|
||||||
|
renderRightActions={renderRightActions}
|
||||||
|
overshootRight={false}
|
||||||
|
onSwipeableOpenStartDrag={suppressContentPress}
|
||||||
|
onSwipeableCloseStartDrag={suppressContentPress}
|
||||||
|
onSwipeableWillOpen={() => {
|
||||||
|
suppressContentPress();
|
||||||
|
rowOpenRef.current = true;
|
||||||
|
}}
|
||||||
|
onSwipeableWillClose={suppressContentPress}
|
||||||
|
onSwipeableClose={() => {
|
||||||
|
rowOpenRef.current = false;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{renderContent()}
|
||||||
</Swipeable>
|
</Swipeable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user