-
-
Notifications
You must be signed in to change notification settings - Fork 55
/
index.tsx
154 lines (131 loc) · 3.92 KB
/
index.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import React, { forwardRef, useCallback, useMemo } from "react";
import { View, useWindowDimensions } from "react-native";
import Reanimated, {
interpolate,
runOnUI,
useAnimatedStyle,
useDerivedValue,
useSharedValue,
} from "react-native-reanimated";
import { useKeyboardAnimation } from "./hooks";
import type { LayoutRectangle, ViewProps } from "react-native";
export type KeyboardAvoidingViewProps = {
/**
* Specify how to react to the presence of the keyboard.
*/
behavior?: "height" | "position" | "padding";
/**
* Style of the content container when `behavior` is 'position'.
*/
contentContainerStyle?: ViewProps["style"];
/**
* Controls whether this `KeyboardAvoidingView` instance should take effect.
* This is useful when more than one is on the screen. Defaults to true.
*/
enabled?: boolean;
/**
* Distance between the top of the user screen and the React Native view. This
* may be non-zero in some cases. Defaults to 0.
*/
keyboardVerticalOffset?: number;
} & ViewProps;
const defaultLayout: LayoutRectangle = {
x: 0,
y: 0,
width: 0,
height: 0,
};
/**
* View that moves out of the way when the keyboard appears by automatically
* adjusting its height, position, or bottom padding.
*/
const KeyboardAvoidingView = forwardRef<
View,
React.PropsWithChildren<KeyboardAvoidingViewProps>
>(
(
{
behavior,
children,
contentContainerStyle,
enabled = true,
keyboardVerticalOffset = 0,
style,
onLayout: onLayoutProps,
...props
},
ref,
) => {
const initialFrame = useSharedValue<LayoutRectangle | null>(null);
const frame = useDerivedValue(() => initialFrame.value || defaultLayout);
const keyboard = useKeyboardAnimation();
const { height: screenHeight } = useWindowDimensions();
const relativeKeyboardHeight = useCallback(() => {
"worklet";
const keyboardY =
screenHeight - keyboard.heightWhenOpened.value - keyboardVerticalOffset;
return Math.max(frame.value.y + frame.value.height - keyboardY, 0);
}, [screenHeight, keyboardVerticalOffset]);
const onLayoutWorklet = useCallback((layout: LayoutRectangle) => {
"worklet";
if (keyboard.isClosed.value || initialFrame.value === null) {
initialFrame.value = layout;
}
}, []);
const onLayout = useCallback<NonNullable<ViewProps["onLayout"]>>(
(e) => {
runOnUI(onLayoutWorklet)(e.nativeEvent.layout);
onLayoutProps?.(e);
},
[onLayoutProps],
);
const animatedStyle = useAnimatedStyle(() => {
const bottom = interpolate(
keyboard.progress.value,
[0, 1],
[0, relativeKeyboardHeight()],
);
const bottomHeight = enabled ? bottom : 0;
switch (behavior) {
case "height":
if (!keyboard.isClosed.value) {
return {
height: frame.value.height - bottomHeight,
flex: 0,
};
}
return {};
case "position":
return { bottom: bottomHeight };
case "padding":
return { paddingBottom: bottomHeight };
default:
return {};
}
}, [behavior, enabled, relativeKeyboardHeight]);
const isPositionBehavior = behavior === "position";
const containerStyle = isPositionBehavior ? contentContainerStyle : style;
const combinedStyles = useMemo(
() => [containerStyle, animatedStyle],
[containerStyle, animatedStyle],
);
if (isPositionBehavior) {
return (
<View ref={ref} style={style} onLayout={onLayout} {...props}>
<Reanimated.View style={combinedStyles}>{children}</Reanimated.View>
</View>
);
}
return (
<Reanimated.View
ref={ref}
onLayout={onLayout}
style={combinedStyles}
{...props}
>
{children}
</Reanimated.View>
);
},
);
export default KeyboardAvoidingView;