From 468d750ecaf59123f0a0a10f0d9fdfa3d62d43f5 Mon Sep 17 00:00:00 2001 From: Marcelo T Prado Date: Sun, 29 Oct 2023 18:51:10 -0300 Subject: [PATCH] feat: `KeyboardStickyView` + example with `KeyboardAwareScrollView` (#245) Co-authored-by: kirillzyusko --- .../KeyboardAwareScrollView.tsx | 15 ++-- .../src/components/AwareScrollView/index.ts | 1 + .../useSmoothKeyboardHandler.ts | 0 .../TextInput/index.tsx} | 0 FabricExample/src/constants/screenNames.ts | 1 + .../src/navigation/ExamplesStack/index.tsx | 10 +++ .../Examples/AwareScrollView/index.tsx | 6 +- .../AwareScrollViewStickyFooter/index.tsx | 81 +++++++++++++++++++ .../AwareScrollViewStickyFooter/styles.ts | 40 +++++++++ .../src/screens/Examples/Main/constants.ts | 5 ++ README.md | 2 +- .../components/keyboard-sticky-view/index.mdx | 37 +++++++++ .../keyboard-sticky-view/ksv.lottie.json | 1 + .../KeyboardAwareScrollView.tsx | 15 ++-- .../src/components/AwareScrollView/index.ts | 1 + .../useSmoothKeyboardHandler.ts | 0 .../TextInput/index.tsx} | 0 example/src/constants/screenNames.ts | 1 + .../src/navigation/ExamplesStack/index.tsx | 10 +++ .../Examples/AwareScrollView/index.tsx | 6 +- .../AwareScrollViewStickyFooter/index.tsx | 81 +++++++++++++++++++ .../AwareScrollViewStickyFooter/styles.ts | 40 +++++++++ .../src/screens/Examples/Main/constants.ts | 5 ++ package.json | 1 + src/components/KeyboardStickyView/index.tsx | 63 +++++++++++++++ src/components/index.ts | 1 + src/index.ts | 2 +- 27 files changed, 405 insertions(+), 20 deletions(-) rename FabricExample/src/{screens/Examples => components}/AwareScrollView/KeyboardAwareScrollView.tsx (95%) create mode 100644 FabricExample/src/components/AwareScrollView/index.ts rename FabricExample/src/{screens/Examples => components}/AwareScrollView/useSmoothKeyboardHandler.ts (100%) rename FabricExample/src/{screens/Examples/AwareScrollView/TextInput.tsx => components/TextInput/index.tsx} (100%) create mode 100644 FabricExample/src/screens/Examples/AwareScrollViewStickyFooter/index.tsx create mode 100644 FabricExample/src/screens/Examples/AwareScrollViewStickyFooter/styles.ts create mode 100644 docs/docs/api/components/keyboard-sticky-view/index.mdx create mode 100644 docs/docs/api/components/keyboard-sticky-view/ksv.lottie.json rename example/src/{screens/Examples => components}/AwareScrollView/KeyboardAwareScrollView.tsx (95%) create mode 100644 example/src/components/AwareScrollView/index.ts rename example/src/{screens/Examples => components}/AwareScrollView/useSmoothKeyboardHandler.ts (100%) rename example/src/{screens/Examples/AwareScrollView/TextInput.tsx => components/TextInput/index.tsx} (100%) create mode 100644 example/src/screens/Examples/AwareScrollViewStickyFooter/index.tsx create mode 100644 example/src/screens/Examples/AwareScrollViewStickyFooter/styles.ts create mode 100644 src/components/KeyboardStickyView/index.tsx diff --git a/FabricExample/src/screens/Examples/AwareScrollView/KeyboardAwareScrollView.tsx b/FabricExample/src/components/AwareScrollView/KeyboardAwareScrollView.tsx similarity index 95% rename from FabricExample/src/screens/Examples/AwareScrollView/KeyboardAwareScrollView.tsx rename to FabricExample/src/components/AwareScrollView/KeyboardAwareScrollView.tsx index 6019acf6e..3d028f080 100644 --- a/FabricExample/src/screens/Examples/AwareScrollView/KeyboardAwareScrollView.tsx +++ b/FabricExample/src/components/AwareScrollView/KeyboardAwareScrollView.tsx @@ -13,7 +13,9 @@ import Reanimated, { } from 'react-native-reanimated'; import { useSmoothKeyboardHandler } from './useSmoothKeyboardHandler'; -const BOTTOM_OFFSET = 50; +type KeyboardAwareScrollViewProps = { + bottomOffset?: number; +} & ScrollViewProps; /** * Everything begins from `onStart` handler. This handler is called every time, @@ -53,8 +55,9 @@ const BOTTOM_OFFSET = 50; * +============================+ +============================+ +=====================================+ * */ -const KeyboardAwareScrollView: FC = ({ +const KeyboardAwareScrollView: FC = ({ children, + bottomOffset = 0, ...rest }) => { const scrollViewAnimatedRef = useAnimatedRef(); @@ -85,11 +88,11 @@ const KeyboardAwareScrollView: FC = ({ const visibleRect = height - keyboardHeight.value; const point = (layout.value?.layout.absoluteY || 0) + (layout.value?.layout.height || 0); - if (visibleRect - point <= BOTTOM_OFFSET) { + if (visibleRect - point <= bottomOffset) { const interpolatedScrollTo = interpolate( e, [initialKeyboardSize.value, keyboardHeight.value], - [0, keyboardHeight.value - (height - point) + BOTTOM_OFFSET] + [0, keyboardHeight.value - (height - point) + bottomOffset] ); const targetScrollY = Math.max(interpolatedScrollTo, 0) + scrollPosition.value; @@ -99,7 +102,7 @@ const KeyboardAwareScrollView: FC = ({ } return 0; - }, []); + }, [bottomOffset]); useSmoothKeyboardHandler( { @@ -158,7 +161,7 @@ const KeyboardAwareScrollView: FC = ({ scrollPosition.value = position.value; }, }, - [height] + [height, maybeScroll] ); useAnimatedReaction(() => input.value, (current, previous) => { diff --git a/FabricExample/src/components/AwareScrollView/index.ts b/FabricExample/src/components/AwareScrollView/index.ts new file mode 100644 index 000000000..5903f0ef7 --- /dev/null +++ b/FabricExample/src/components/AwareScrollView/index.ts @@ -0,0 +1 @@ +export { default } from "./KeyboardAwareScrollView"; diff --git a/FabricExample/src/screens/Examples/AwareScrollView/useSmoothKeyboardHandler.ts b/FabricExample/src/components/AwareScrollView/useSmoothKeyboardHandler.ts similarity index 100% rename from FabricExample/src/screens/Examples/AwareScrollView/useSmoothKeyboardHandler.ts rename to FabricExample/src/components/AwareScrollView/useSmoothKeyboardHandler.ts diff --git a/FabricExample/src/screens/Examples/AwareScrollView/TextInput.tsx b/FabricExample/src/components/TextInput/index.tsx similarity index 100% rename from FabricExample/src/screens/Examples/AwareScrollView/TextInput.tsx rename to FabricExample/src/components/TextInput/index.tsx diff --git a/FabricExample/src/constants/screenNames.ts b/FabricExample/src/constants/screenNames.ts index 6020257e6..911d71bba 100644 --- a/FabricExample/src/constants/screenNames.ts +++ b/FabricExample/src/constants/screenNames.ts @@ -3,6 +3,7 @@ export enum ScreenNames { REANIMATED_CHAT = 'REANIMATED_CHAT', EVENTS = 'EVENTS', AWARE_SCROLL_VIEW = 'AWARE_SCROLL_VIEW', + AWARE_SCROLL_VIEW_STICKY_FOOTER = 'AWARE_SCROLL_VIEW_STICKY_FOOTER', STATUS_BAR = 'STATUS_BAR', LOTTIE = 'LOTTIE', EXAMPLES_STACK = 'EXAMPLES_STACK', diff --git a/FabricExample/src/navigation/ExamplesStack/index.tsx b/FabricExample/src/navigation/ExamplesStack/index.tsx index b6c7e497d..61d8f62bc 100644 --- a/FabricExample/src/navigation/ExamplesStack/index.tsx +++ b/FabricExample/src/navigation/ExamplesStack/index.tsx @@ -15,12 +15,14 @@ import InteractiveKeyboardIOS from '../../screens/Examples/InteractiveKeyboardIO import NativeStack from '../NestedStack'; import KeyboardAvoidingViewExample from '../../screens/Examples/KeyboardAvoidingView'; import EnabledDisabled from '../../screens/Examples/EnabledDisabled'; +import AwareScrollViewStickyFooter from '../../screens/Examples/AwareScrollViewStickyFooter'; export type ExamplesStackParamList = { [ScreenNames.ANIMATED_EXAMPLE]: undefined; [ScreenNames.REANIMATED_CHAT]: undefined; [ScreenNames.EVENTS]: undefined; [ScreenNames.AWARE_SCROLL_VIEW]: undefined; + [ScreenNames.AWARE_SCROLL_VIEW_STICKY_FOOTER]: undefined; [ScreenNames.STATUS_BAR]: undefined; [ScreenNames.LOTTIE]: undefined; [ScreenNames.NON_UI_PROPS]: undefined; @@ -46,6 +48,9 @@ const options = { [ScreenNames.AWARE_SCROLL_VIEW]: { title: 'Aware scroll view', }, + [ScreenNames.AWARE_SCROLL_VIEW_STICKY_FOOTER]: { + title: 'Aware scroll view sticky footer', + }, [ScreenNames.STATUS_BAR]: { headerShown: false, title: 'Status bar manipulation', @@ -95,6 +100,11 @@ const ExamplesStack = () => ( component={AwareScrollView} options={options[ScreenNames.AWARE_SCROLL_VIEW]} /> + + {new Array(10).fill(0).map((_, i) => ( ; + +const variants = ['v1', 'v2', 'v3'] as const; +type Variant = typeof variants[number]; + +export default function AwareScrollViewStickyFooter({ navigation }: Props) { + useResizeMode(); + + const { bottom } = useSafeAreaInsets(); + const [footerHeight, setFooterHeight] = useState(0); + const [variant, setVariant] = useState("v1"); + + const handleLayout = useCallback((evt: LayoutChangeEvent) => { + setFooterHeight(evt.nativeEvent.layout.height); + }, []); + const offset = useMemo(() => ({closed: 0, opened: variant === "v1" ? 0 : bottom }), [bottom, variant]); + const offsetV3 = useMemo(() => ({closed: -50, opened: bottom - 25}), [bottom]); + + useEffect(() => { + navigation.setOptions({ + headerRight: () => ( + { + const index = variants.indexOf(variant); + setVariant( + variants[index === variants.length - 1 ? 0 : index + 1] + ); + }} + > + {variant} + + ), + }); + }, [variant]); + + const v1v2 = variant === "v1" || variant === "v2"; + + return ( + + + {new Array(10).fill(0).map((_, i) => ( + + ))} + + {v1v2 && ( + + + A mocked sticky footer +