Files
native/src/lib/use-sheet-drag.ts
T
Linus Rath cf3f919fa2 feat: enhance SettingsScreen with dynamic localization and theming
- Integrated useColors hook for dynamic theming in SettingsScreen.
- Updated tab labels and titles to use localized strings.
- Refactored styles to utilize theme palette for consistent styling.
- Improved locale subscription to re-render labels on language change.

feat: implement useSheetDrag hook for bottom sheet gesture handling

- Added useSheetDrag hook to manage drag-to-dismiss functionality for bottom sheets.
- Configured pan responder for handling drag gestures and dismissing the sheet based on distance and velocity.

refactor: restructure color and token management

- Introduced useColors and useResolvedTheme hooks for better theme management.
- Separated light and dark color palettes for improved maintainability.
- Updated tokens to reflect new theming structure and ensure consistency across components.
2026-04-27 13:30:45 +02:00

77 lines
2.3 KiB
TypeScript

import React from 'react';
import { Animated, PanResponder } from 'react-native';
interface Options {
/** Animated value driving the sheet's translateY. Resting (open) position is 0. */
slideY: Animated.Value;
/** Off-screen target the sheet animates to when dismissed. */
closedY: number;
/** Called when the gesture commits a dismissal. */
onClose: () => void;
/** Pixels of downward drag needed to commit a dismiss on release. */
closeDistance?: number;
/** Downward velocity (px/ms) that commits a dismiss regardless of distance. */
closeVelocity?: number;
}
/**
* Wires drag-to-dismiss on a bottom sheet's grab handle. Spread the returned
* panHandlers on the handle/header area — not the scrollable body — so vertical
* scrolling inside the sheet is not intercepted.
*/
export function useSheetDrag({
slideY,
closedY,
onClose,
closeDistance = 100,
closeVelocity = 0.6,
}: Options) {
const startRef = React.useRef(0);
const panResponder = React.useMemo(
() =>
PanResponder.create({
onStartShouldSetPanResponder: () => true,
onMoveShouldSetPanResponder: (_, g) =>
Math.abs(g.dy) > 4 && Math.abs(g.dy) > Math.abs(g.dx),
onPanResponderGrant: () => {
slideY.stopAnimation((v) => {
startRef.current = v;
});
},
onPanResponderMove: (_, g) => {
const next = startRef.current + Math.max(0, g.dy);
slideY.setValue(next);
},
onPanResponderRelease: (_, g) => {
const traveled = startRef.current + Math.max(0, g.dy);
if (traveled > closeDistance || g.vy > closeVelocity) {
Animated.timing(slideY, {
toValue: closedY,
duration: 180,
useNativeDriver: true,
}).start(({ finished }) => {
if (finished) onClose();
});
} else {
Animated.spring(slideY, {
toValue: 0,
useNativeDriver: true,
bounciness: 0,
}).start();
}
},
onPanResponderTerminate: () => {
Animated.spring(slideY, {
toValue: 0,
useNativeDriver: true,
bounciness: 0,
}).start();
},
}),
[slideY, closedY, onClose, closeDistance, closeVelocity],
);
return panResponder.panHandlers;
}