lodev09 / react-native-true-sheet

The true native bottom sheet experience đź’©
https://sheet.lodev09.com
MIT License
394 stars 12 forks source link

How to handle Keyboard when footer contains TextInput #76

Open spicy-xili opened 1 month ago

spicy-xili commented 1 month ago

I have put a TextInput in the footer component but I don't know how to properly handle the keyboard so that it doesn't hide the input itself. Normally I use KeyboardAvoidingView but it is not working at all. I also have tried keyboardMode="resize" but it didn't work. Any ideas? Thanks in advance! PS: I use Android.

https://github.com/user-attachments/assets/cae44391-dfdc-485e-9893-0304e2a4ed0f

This is my code:

import React, { forwardRef, useRef, useState, type Ref } from "react";
import { View, TextInput, TouchableOpacity, Text, StyleSheet, type ViewStyle } from "react-native";
import { TrueSheet, type TrueSheetProps } from "@lodev09/react-native-true-sheet";
import { FlatList } from "react-native-gesture-handler";
import { KeyboardAvoidingView } from "react-native-keyboard-controller";

import { DemoContent } from "./DemoContent";

const DARK = "#282e37";
const DARK_GRAY = "#333b48";
const SPACING = 16;
const INPUT_HEIGHT = SPACING * 3;

interface FlatListSheetProps extends TrueSheetProps {
  postId: string;
  userId: string;
}

const times = <T,>(length: number, iteratee: (index: number) => T): T[] => Array.from({ length }, (_, k) => iteratee(k));

export const CommentsSheet = forwardRef((props: FlatListSheetProps, ref: Ref<TrueSheet>) => {
  const { postId, userId, ...rest } = props;
  const flatListRef = useRef<FlatList>(null);
  const [comment, setComment] = useState("");

  const resetScroll = () => {
    flatListRef.current?.scrollToOffset({ animated: false, offset: 0 });
  };

  const handleSend = () => {
    console.log("Comment by user ${userId} on post ${postId}: ${comment}");
    setComment(""); // Clear the input after sending
  };

  const resetTextInput = () => {
    setComment(""); // Reset the text input
  };

  return (
    <TrueSheet
      ref={ref}
      scrollRef={flatListRef}
      sizes={["medium", "large"]}
      cornerRadius={20}
      dimmed={true}
      blurTint="dark"
      backgroundColor={DARK}
      keyboardMode="pan"
      grabber={false}
      name={postId}
      onDismiss={() => {
        console.log("Sheet FlatList dismissed!");
        resetScroll(); // Reset scroll position on dismiss
        resetTextInput(); // Reset text input on dismiss
      }}
      onPresent={() => console.log("Sheet FlatList presented!")}
      FooterComponent={
        <View style={styles.footerContainer}>
          <TextInput
            style={styles.input}
            placeholder="Write a comment..."
            placeholderTextColor="#999"
            value={comment}
            onChangeText={setComment}
          />
          <TouchableOpacity style={styles.sendButton} onPress={handleSend}>
            <Text style={styles.sendButtonText}>Send</Text>
          </TouchableOpacity>
        </View>
      }
      {...rest}
    >
      <FlatList<number>
        ref={flatListRef}
        nestedScrollEnabled
        data={times(50, (i) => i)}
        contentContainerStyle={$content}
        indicatorStyle="black"
        renderItem={() => <DemoContent color={DARK_GRAY} />}
      />
    </TrueSheet>
  );
});

CommentsSheet.displayName = "CommentsSheet";

const $content: ViewStyle = {
  padding: SPACING,
  paddingTop: INPUT_HEIGHT + SPACING * 4,
};

const styles = StyleSheet.create({
  footerContainer: {
    flexDirection: "row",
    alignItems: "center",
    padding: SPACING,
    backgroundColor: DARK,
  },
  input: {
    flex: 1,
    height: INPUT_HEIGHT,
    backgroundColor: DARK_GRAY,
    borderRadius: 20,
    paddingHorizontal: SPACING,
    color: "#fff",
  },
  sendButton: {
    marginLeft: SPACING,
    backgroundColor: "#1DA1F2",
    borderRadius: 20,
    paddingVertical: SPACING / 2,
    paddingHorizontal: SPACING,
  },
  sendButtonText: {
    color: "#fff",
    fontWeight: "bold",
  },
});
spicy-xili commented 1 month ago

Update: I have moved the text input out of the footer. However, the keyboard animation looks weird. When you start typing, the screen below the sheet modal is visible for some ms in both keyboardMode types. Also, keyboardMode == pan partially hides the text input. If I place the TextInput to the top, as the one in the example, there are no visual bugs, but when it is in the bottom side of the screen, it clashes with the keyboard.

https://github.com/user-attachments/assets/3c700d33-aec1-4161-aa51-d4a8cb6f36fa

Code:

import React, { forwardRef, useRef, useState, type Ref } from "react";
import { View, TextInput, TouchableOpacity, StyleSheet, KeyboardAvoidingView, Platform } from "react-native";
import { TrueSheet, type TrueSheetProps } from "@lodev09/react-native-true-sheet";
import { FlatList } from "react-native-gesture-handler";
import { DemoContent } from "./DemoContent";
import { Ionicons } from "@expo/vector-icons";

const DARK = "#282e37";
const DARK_GRAY = "#333b48";
const COLORS = {
  primary: "#282e37",
  secondary: "#1DA1F2",
  secondaryLighter: "#333b48",
  secondaryEvenLighter: "#999",
  primaryDarker: "#1c1f25",
};

const SPACING = 16;
const INPUT_HEIGHT = SPACING * 3;

interface FlatListSheetProps extends TrueSheetProps {
  postId: string;
  userId: string;
}

const times = <T,>(length: number, iteratee: (index: number) => T): T[] => Array.from({ length }, (_, k) => iteratee(k));

export const CommentsSheet = forwardRef((props: FlatListSheetProps, ref: Ref<TrueSheet>) => {
  const { postId, userId, ...rest } = props;
  const flatListRef = useRef<FlatList>(null);
  const [comment, setComment] = useState("");

  const resetScroll = () => {
    flatListRef.current?.scrollToOffset({ animated: false, offset: 0 });
  };

  const handleSend = () => {
    console.log(`Comment by user ${userId} on post ${postId}: ${comment}`);
    setComment(""); // Clear the input after sending
  };

  const resetTextInput = () => {
    setComment(""); // Reset the text input
  };

  return (
    <TrueSheet
      ref={ref}
      scrollRef={flatListRef}
      sizes={["medium", "large"]}
      cornerRadius={20}
      dimmed={true}
      blurTint="dark"
      keyboardMode="pan"
      backgroundColor={DARK}
      grabber={false}
      name={postId}
      onDismiss={() => {
        console.log("Sheet FlatList dismissed!");
        resetScroll(); // Reset scroll position on dismiss
        resetTextInput(); // Reset text input on dismiss
      }}
      onPresent={() => console.log("Sheet FlatList presented!")}
      {...rest}
    >
      <FlatList<number>
        ref={flatListRef}
        nestedScrollEnabled
        data={times(10, (i) => i)}
        contentContainerStyle={styles.flatListContentContainer}
        indicatorStyle="black"
        renderItem={() => <DemoContent color={DARK_GRAY} />}
      />

      <View style={styles.cont}>
        <View style={styles.inputContainer}>
          <TextInput
            style={styles.textInput}
            placeholder="Write a comment..."
            placeholderTextColor={COLORS.secondaryEvenLighter}
            value={comment}
            onChangeText={setComment}
          />
          <TouchableOpacity style={styles.sendButton} onPress={handleSend}>
            <Ionicons name="send" size={24} color={COLORS.secondary} />
          </TouchableOpacity>
        </View>
      </View>
    </TrueSheet>
  );
});

CommentsSheet.displayName = "CommentsSheet";

const styles = StyleSheet.create({
  flatListContentContainer: {
    paddingHorizontal: 10,
    paddingBottom: 70, // Add some padding to avoid overlapping with the input
  },
  cont: {
    position: "absolute",
    bottom: 0,
    left: 0,
    right: 0,
  },
  inputContainer: {
    flexDirection: "row",
    alignItems: "center",
    padding: 10,
    backgroundColor: COLORS.primary,
  },
  textInput: {
    flex: 1,
    padding: 10,
    borderRadius: 20,
    backgroundColor: COLORS.secondaryLighter,
    color: COLORS.secondary,
    fontSize: 14,
  },
  sendButton: {
    marginLeft: 10,
    backgroundColor: COLORS.primaryDarker,
    borderRadius: 20,
    padding: 10,
  },
});