@react-native-ama/bottom-sheet 2.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,137 @@
1
+ import { renderHook, act } from '@testing-library/react-native';
2
+ import { useKeyboard } from './useKeyboard';
3
+
4
+ const rn = jest.requireMock('react-native');
5
+ const reanimated = jest.requireMock('react-native-reanimated');
6
+
7
+ // Easing is not in the jest.setup.js reanimated mock — patch it before tests run
8
+ const mockEasingFn = jest.fn(() => jest.fn());
9
+ beforeAll(() => {
10
+ reanimated.Easing = {
11
+ in: mockEasingFn,
12
+ out: mockEasingFn,
13
+ inOut: mockEasingFn,
14
+ ease: jest.fn(),
15
+ linear: jest.fn(),
16
+ };
17
+ });
18
+
19
+ type KeyboardCallback = (event: any) => void;
20
+
21
+ let showCallback: KeyboardCallback | null = null;
22
+ let hideCallback: KeyboardCallback | null = null;
23
+
24
+ beforeEach(() => {
25
+ jest.clearAllMocks();
26
+ showCallback = null;
27
+ hideCallback = null;
28
+
29
+ rn.Keyboard.addListener = jest.fn((event: string, cb: KeyboardCallback) => {
30
+ if (event === 'keyboardWillShow' || event === 'keyboardDidShow') {
31
+ showCallback = cb;
32
+ } else if (event === 'keyboardWillHide' || event === 'keyboardDidHide') {
33
+ hideCallback = cb;
34
+ }
35
+ return { remove: jest.fn() };
36
+ });
37
+ });
38
+
39
+ const makeKeyboardEvent = (height: number, easing: string, duration: number) => ({
40
+ endCoordinates: { height },
41
+ duration,
42
+ easing,
43
+ });
44
+
45
+ describe('useKeyboard', () => {
46
+ it('does not register keyboard listeners when shouldHandleKeyboardEvents is false', () => {
47
+ renderHook(() => useKeyboard(false));
48
+ expect(rn.Keyboard.addListener).not.toHaveBeenCalled();
49
+ });
50
+
51
+ it('registers keyboard show and hide listeners when shouldHandleKeyboardEvents is true', () => {
52
+ renderHook(() => useKeyboard(true));
53
+ expect(rn.Keyboard.addListener).toHaveBeenCalledTimes(2);
54
+ });
55
+
56
+ it('updates keyboardHeight and isKeyboardVisible when keyboard shows', () => {
57
+ const { result } = renderHook(() => useKeyboard(true));
58
+
59
+ act(() => {
60
+ showCallback?.(makeKeyboardEvent(300, 'easeIn', 250));
61
+ });
62
+
63
+ expect(reanimated.withTiming).toHaveBeenCalledWith(300, expect.any(Object));
64
+ expect(result.current.keyboardFinalHeight.value).toBe(300);
65
+ expect(result.current.isKeyboardVisible.value).toBe(true);
66
+ });
67
+
68
+ it('resets keyboardHeight when keyboard hides', () => {
69
+ const { result } = renderHook(() => useKeyboard(true));
70
+
71
+ act(() => {
72
+ showCallback?.(makeKeyboardEvent(300, 'easeOut', 250));
73
+ });
74
+
75
+ act(() => {
76
+ hideCallback?.(makeKeyboardEvent(300, 'linear', 200));
77
+ });
78
+
79
+ expect(result.current.keyboardFinalHeight.value).toBe(0);
80
+ expect(result.current.isKeyboardVisible.value).toBe(false);
81
+ });
82
+
83
+ it('removes listeners on unmount', () => {
84
+ const removeMock = jest.fn();
85
+ rn.Keyboard.addListener = jest.fn(() => ({ remove: removeMock }));
86
+
87
+ const { unmount } = renderHook(() => useKeyboard(true));
88
+ unmount();
89
+
90
+ expect(removeMock).toHaveBeenCalledTimes(2);
91
+ });
92
+ });
93
+
94
+ describe('getKeyboardAnimationConfigs easing variants', () => {
95
+ it('uses easeIn config', () => {
96
+ renderHook(() => useKeyboard(true));
97
+ act(() => {
98
+ showCallback?.(makeKeyboardEvent(300, 'easeIn', 250));
99
+ });
100
+ expect(reanimated.withTiming).toHaveBeenCalledWith(300, expect.objectContaining({ duration: 250 }));
101
+ });
102
+
103
+ it('uses easeOut config', () => {
104
+ renderHook(() => useKeyboard(true));
105
+ act(() => {
106
+ showCallback?.(makeKeyboardEvent(300, 'easeOut', 200));
107
+ });
108
+ expect(reanimated.withTiming).toHaveBeenCalledWith(300, expect.objectContaining({ duration: 200 }));
109
+ });
110
+
111
+ it('uses easeInEaseOut config', () => {
112
+ renderHook(() => useKeyboard(true));
113
+ act(() => {
114
+ showCallback?.(makeKeyboardEvent(300, 'easeInEaseOut', 300));
115
+ });
116
+ expect(reanimated.withTiming).toHaveBeenCalledWith(300, expect.objectContaining({ duration: 300 }));
117
+ });
118
+
119
+ it('uses linear config', () => {
120
+ renderHook(() => useKeyboard(true));
121
+ act(() => {
122
+ showCallback?.(makeKeyboardEvent(300, 'linear', 150));
123
+ });
124
+ expect(reanimated.withTiming).toHaveBeenCalledWith(300, expect.objectContaining({ duration: 150 }));
125
+ });
126
+
127
+ it('uses keyboard spring config', () => {
128
+ renderHook(() => useKeyboard(true));
129
+ act(() => {
130
+ showCallback?.(makeKeyboardEvent(300, 'keyboard', 400));
131
+ });
132
+ expect(reanimated.withTiming).toHaveBeenCalledWith(
133
+ 300,
134
+ expect.objectContaining({ damping: 500, stiffness: 1000 }),
135
+ );
136
+ });
137
+ });
@@ -0,0 +1,130 @@
1
+ /*
2
+ * Based on: https://github.com/gorhom/react-native-bottom-sheet
3
+ */
4
+ import { useCallback, useEffect } from 'react';
5
+ import {
6
+ Keyboard,
7
+ KeyboardEvent,
8
+ KeyboardEventEasing,
9
+ KeyboardEventName,
10
+ Platform,
11
+ } from 'react-native';
12
+ import { Easing, useSharedValue, withTiming } from 'react-native-reanimated';
13
+
14
+ const KEYBOARD_EVENT_SHOW: KeyboardEventName = Platform.select({
15
+ ios: 'keyboardWillShow',
16
+ default: 'keyboardDidShow',
17
+ });
18
+
19
+ const KEYBOARD_EVENT_HIDE: KeyboardEventName = Platform.select({
20
+ ios: 'keyboardWillHide',
21
+ default: 'keyboardDidHide',
22
+ });
23
+
24
+ export const useKeyboard = (shouldHandleKeyboardEvents: boolean = true) => {
25
+ const keyboardHeight = useSharedValue(0);
26
+ const keyboardFinalHeight = useSharedValue(0);
27
+ const isKeyboardVisible = useSharedValue(false);
28
+
29
+ const handleKeyboardEvent = useCallback(
30
+ (
31
+ isVisible: boolean,
32
+ endCoordinatesHeight: number,
33
+ duration: number,
34
+ easing: KeyboardEventEasing,
35
+ ) => {
36
+ const finalHeight = isVisible ? endCoordinatesHeight : 0;
37
+
38
+ const animationConfig = getKeyboardAnimationConfigs(easing, duration);
39
+
40
+ keyboardFinalHeight.value = finalHeight;
41
+ keyboardHeight.value = withTiming(
42
+ isVisible ? finalHeight : 0,
43
+ animationConfig,
44
+ );
45
+ isKeyboardVisible.value = isVisible;
46
+ },
47
+ // eslint-disable-next-line react-hooks/exhaustive-deps
48
+ [],
49
+ );
50
+
51
+ useEffect(() => {
52
+ if (!shouldHandleKeyboardEvents) {
53
+ return;
54
+ }
55
+
56
+ const handleEvent = (isVisible: boolean) => {
57
+ return (event: KeyboardEvent) => {
58
+ return handleKeyboardEvent(
59
+ isVisible,
60
+ event.endCoordinates.height,
61
+ event.duration,
62
+ event.easing,
63
+ );
64
+ };
65
+ };
66
+
67
+ const showSubscription = Keyboard.addListener(
68
+ KEYBOARD_EVENT_SHOW,
69
+ handleEvent(true),
70
+ );
71
+
72
+ const hideSubscription = Keyboard.addListener(
73
+ KEYBOARD_EVENT_HIDE,
74
+ handleEvent(false),
75
+ );
76
+
77
+ return () => {
78
+ showSubscription.remove();
79
+ hideSubscription.remove();
80
+ };
81
+ }, [handleKeyboardEvent, shouldHandleKeyboardEvents]);
82
+
83
+ return {
84
+ keyboardHeight,
85
+ keyboardFinalHeight,
86
+ isKeyboardVisible,
87
+ };
88
+ };
89
+
90
+ const getKeyboardAnimationConfigs = (
91
+ easing: KeyboardEventEasing,
92
+ duration: number,
93
+ ) => {
94
+ switch (easing) {
95
+ case 'easeIn':
96
+ return {
97
+ easing: Easing.in(Easing.ease),
98
+ duration,
99
+ };
100
+
101
+ case 'easeOut':
102
+ return {
103
+ easing: Easing.out(Easing.ease),
104
+ duration,
105
+ };
106
+
107
+ case 'easeInEaseOut':
108
+ return {
109
+ easing: Easing.inOut(Easing.ease),
110
+ duration,
111
+ };
112
+
113
+ case 'linear':
114
+ return {
115
+ easing: Easing.linear,
116
+ duration,
117
+ };
118
+
119
+ case 'keyboard':
120
+ return {
121
+ damping: 500,
122
+ stiffness: 1000,
123
+ mass: 3,
124
+ overshootClamping: true,
125
+ restDisplacementThreshold: 10,
126
+ restSpeedThreshold: 10,
127
+ duration,
128
+ };
129
+ }
130
+ };
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ // Components
2
+ export { BottomSheet } from './components/BottomSheet';
3
+
4
+ // Hooks
5
+ export { useBottomSheetGestureHandler } from './hooks/useBottomSheetGestureHandler';
6
+ export { useKeyboard } from './hooks/useKeyboard';