Skip to content

Commit 468d750

Browse files
feat: KeyboardStickyView + example with KeyboardAwareScrollView (#245)
Co-authored-by: kirillzyusko <[email protected]>
1 parent 4e65942 commit 468d750

File tree

27 files changed

+405
-20
lines changed

27 files changed

+405
-20
lines changed

FabricExample/src/screens/Examples/AwareScrollView/KeyboardAwareScrollView.tsx renamed to FabricExample/src/components/AwareScrollView/KeyboardAwareScrollView.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ import Reanimated, {
1313
} from 'react-native-reanimated';
1414
import { useSmoothKeyboardHandler } from './useSmoothKeyboardHandler';
1515

16-
const BOTTOM_OFFSET = 50;
16+
type KeyboardAwareScrollViewProps = {
17+
bottomOffset?: number;
18+
} & ScrollViewProps;
1719

1820
/**
1921
* Everything begins from `onStart` handler. This handler is called every time,
@@ -53,8 +55,9 @@ const BOTTOM_OFFSET = 50;
5355
* +============================+ +============================+ +=====================================+
5456
*
5557
*/
56-
const KeyboardAwareScrollView: FC<ScrollViewProps> = ({
58+
const KeyboardAwareScrollView: FC<KeyboardAwareScrollViewProps> = ({
5759
children,
60+
bottomOffset = 0,
5861
...rest
5962
}) => {
6063
const scrollViewAnimatedRef = useAnimatedRef<Reanimated.ScrollView>();
@@ -85,11 +88,11 @@ const KeyboardAwareScrollView: FC<ScrollViewProps> = ({
8588
const visibleRect = height - keyboardHeight.value;
8689
const point = (layout.value?.layout.absoluteY || 0) + (layout.value?.layout.height || 0);
8790

88-
if (visibleRect - point <= BOTTOM_OFFSET) {
91+
if (visibleRect - point <= bottomOffset) {
8992
const interpolatedScrollTo = interpolate(
9093
e,
9194
[initialKeyboardSize.value, keyboardHeight.value],
92-
[0, keyboardHeight.value - (height - point) + BOTTOM_OFFSET]
95+
[0, keyboardHeight.value - (height - point) + bottomOffset]
9396
);
9497
const targetScrollY =
9598
Math.max(interpolatedScrollTo, 0) + scrollPosition.value;
@@ -99,7 +102,7 @@ const KeyboardAwareScrollView: FC<ScrollViewProps> = ({
99102
}
100103

101104
return 0;
102-
}, []);
105+
}, [bottomOffset]);
103106

104107
useSmoothKeyboardHandler(
105108
{
@@ -158,7 +161,7 @@ const KeyboardAwareScrollView: FC<ScrollViewProps> = ({
158161
scrollPosition.value = position.value;
159162
},
160163
},
161-
[height]
164+
[height, maybeScroll]
162165
);
163166

164167
useAnimatedReaction(() => input.value, (current, previous) => {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from "./KeyboardAwareScrollView";

FabricExample/src/constants/screenNames.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export enum ScreenNames {
33
REANIMATED_CHAT = 'REANIMATED_CHAT',
44
EVENTS = 'EVENTS',
55
AWARE_SCROLL_VIEW = 'AWARE_SCROLL_VIEW',
6+
AWARE_SCROLL_VIEW_STICKY_FOOTER = 'AWARE_SCROLL_VIEW_STICKY_FOOTER',
67
STATUS_BAR = 'STATUS_BAR',
78
LOTTIE = 'LOTTIE',
89
EXAMPLES_STACK = 'EXAMPLES_STACK',

FabricExample/src/navigation/ExamplesStack/index.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ import InteractiveKeyboardIOS from '../../screens/Examples/InteractiveKeyboardIO
1515
import NativeStack from '../NestedStack';
1616
import KeyboardAvoidingViewExample from '../../screens/Examples/KeyboardAvoidingView';
1717
import EnabledDisabled from '../../screens/Examples/EnabledDisabled';
18+
import AwareScrollViewStickyFooter from '../../screens/Examples/AwareScrollViewStickyFooter';
1819

1920
export type ExamplesStackParamList = {
2021
[ScreenNames.ANIMATED_EXAMPLE]: undefined;
2122
[ScreenNames.REANIMATED_CHAT]: undefined;
2223
[ScreenNames.EVENTS]: undefined;
2324
[ScreenNames.AWARE_SCROLL_VIEW]: undefined;
25+
[ScreenNames.AWARE_SCROLL_VIEW_STICKY_FOOTER]: undefined;
2426
[ScreenNames.STATUS_BAR]: undefined;
2527
[ScreenNames.LOTTIE]: undefined;
2628
[ScreenNames.NON_UI_PROPS]: undefined;
@@ -46,6 +48,9 @@ const options = {
4648
[ScreenNames.AWARE_SCROLL_VIEW]: {
4749
title: 'Aware scroll view',
4850
},
51+
[ScreenNames.AWARE_SCROLL_VIEW_STICKY_FOOTER]: {
52+
title: 'Aware scroll view sticky footer',
53+
},
4954
[ScreenNames.STATUS_BAR]: {
5055
headerShown: false,
5156
title: 'Status bar manipulation',
@@ -95,6 +100,11 @@ const ExamplesStack = () => (
95100
component={AwareScrollView}
96101
options={options[ScreenNames.AWARE_SCROLL_VIEW]}
97102
/>
103+
<Stack.Screen
104+
name={ScreenNames.AWARE_SCROLL_VIEW_STICKY_FOOTER}
105+
component={AwareScrollViewStickyFooter}
106+
options={options[ScreenNames.AWARE_SCROLL_VIEW_STICKY_FOOTER]}
107+
/>
98108
<Stack.Screen
99109
name={ScreenNames.STATUS_BAR}
100110
component={StatusBar}

FabricExample/src/screens/Examples/AwareScrollView/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import React from 'react';
22
import { useResizeMode } from 'react-native-keyboard-controller';
33

4-
import KeyboardAwareScrollView from './KeyboardAwareScrollView';
5-
import TextInput from './TextInput';
4+
import KeyboardAwareScrollView from '../../../components/AwareScrollView';
5+
import TextInput from '../../../components/TextInput';
66
import { styles } from './styles';
77

88
export default function AwareScrollView() {
99
useResizeMode();
1010

1111
return (
12-
<KeyboardAwareScrollView style={styles.container} contentContainerStyle={styles.content}>
12+
<KeyboardAwareScrollView bottomOffset={50} style={styles.container} contentContainerStyle={styles.content}>
1313
{new Array(10).fill(0).map((_, i) => (
1414
<TextInput
1515
key={i}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import React, { useCallback, useEffect, useMemo, useState } from 'react';
2+
import { LayoutChangeEvent, View, Text, Button } from 'react-native';
3+
import { StackScreenProps } from '@react-navigation/stack';
4+
import { useResizeMode, KeyboardStickyView } from 'react-native-keyboard-controller';
5+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
6+
7+
import { ExamplesStackParamList } from '../../../navigation/ExamplesStack';
8+
import KeyboardAwareScrollView from '../../../components/AwareScrollView';
9+
import TextInput from '../../../components/TextInput';
10+
import { styles } from './styles';
11+
12+
type Props = StackScreenProps<ExamplesStackParamList>;
13+
14+
const variants = ['v1', 'v2', 'v3'] as const;
15+
type Variant = typeof variants[number];
16+
17+
export default function AwareScrollViewStickyFooter({ navigation }: Props) {
18+
useResizeMode();
19+
20+
const { bottom } = useSafeAreaInsets();
21+
const [footerHeight, setFooterHeight] = useState(0);
22+
const [variant, setVariant] = useState<Variant>("v1");
23+
24+
const handleLayout = useCallback((evt: LayoutChangeEvent) => {
25+
setFooterHeight(evt.nativeEvent.layout.height);
26+
}, []);
27+
const offset = useMemo(() => ({closed: 0, opened: variant === "v1" ? 0 : bottom }), [bottom, variant]);
28+
const offsetV3 = useMemo(() => ({closed: -50, opened: bottom - 25}), [bottom]);
29+
30+
useEffect(() => {
31+
navigation.setOptions({
32+
headerRight: () => (
33+
<Text
34+
style={styles.header}
35+
onPress={() => {
36+
const index = variants.indexOf(variant);
37+
setVariant(
38+
variants[index === variants.length - 1 ? 0 : index + 1]
39+
);
40+
}}
41+
>
42+
{variant}
43+
</Text>
44+
),
45+
});
46+
}, [variant]);
47+
48+
const v1v2 = variant === "v1" || variant === "v2";
49+
50+
return (
51+
<View style={[styles.pageContainer, { paddingBottom: variant === "v1" ? 0 : bottom }]}>
52+
<KeyboardAwareScrollView
53+
style={styles.container}
54+
contentContainerStyle={styles.content}
55+
bottomOffset={(v1v2 ? footerHeight : 0) + 50}
56+
keyboardShouldPersistTaps="handled"
57+
>
58+
{new Array(10).fill(0).map((_, i) => (
59+
<TextInput
60+
key={i}
61+
placeholder={`TextInput#${i}`}
62+
keyboardType={i % 2 === 0 ? 'numeric' : 'default'}
63+
/>
64+
))}
65+
</KeyboardAwareScrollView>
66+
{v1v2 && (
67+
<KeyboardStickyView offset={offset}>
68+
<View onLayout={handleLayout} style={styles.footer}>
69+
<Text style={styles.footerText}>A mocked sticky footer</Text>
70+
<Button title="Click me" />
71+
</View>
72+
</KeyboardStickyView>
73+
)}
74+
{variant === "v3" && (
75+
<KeyboardStickyView offset={offsetV3}>
76+
<View style={styles.circle} />
77+
</KeyboardStickyView>
78+
)}
79+
</View>
80+
);
81+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { StyleSheet } from 'react-native';
2+
3+
export const styles = StyleSheet.create({
4+
container: {
5+
flexGrow: 1,
6+
paddingHorizontal: 16,
7+
},
8+
content: {
9+
paddingTop: 50,
10+
},
11+
pageContainer: {
12+
flex: 1,
13+
},
14+
footer: {
15+
backgroundColor: 'green',
16+
alignItems: 'center',
17+
justifyContent: 'center',
18+
padding: 20,
19+
width: '100%',
20+
gap: 10,
21+
},
22+
footerText: {
23+
color: 'white',
24+
fontWeight: 'bold',
25+
},
26+
circle: {
27+
position: 'absolute',
28+
bottom: 0,
29+
right: 30,
30+
justifyContent: "flex-end",
31+
width: 60,
32+
height: 60,
33+
borderRadius: 30,
34+
backgroundColor: "#002099",
35+
},
36+
header: {
37+
color: 'black',
38+
paddingRight: 12,
39+
},
40+
});

FabricExample/src/screens/Examples/Main/constants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ export const examples: Example[] = [
1414
info: ScreenNames.AWARE_SCROLL_VIEW,
1515
icons: '🤓',
1616
},
17+
{
18+
title: 'Aware scroll view sticky footer',
19+
info: ScreenNames.AWARE_SCROLL_VIEW_STICKY_FOOTER,
20+
icons: '🤓🩹',
21+
},
1722
{
1823
title: 'Status Bar',
1924
info: ScreenNames.STATUS_BAR,

0 commit comments

Comments
 (0)