about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--package.json1
-rw-r--r--src/screens/Messages/Conversation/MessageInput.tsx57
-rw-r--r--src/screens/Messages/Conversation/MessageInput.web.tsx91
-rw-r--r--yarn.lock26
4 files changed, 165 insertions, 10 deletions
diff --git a/package.json b/package.json
index 9df20604a..eec87ce60 100644
--- a/package.json
+++ b/package.json
@@ -185,6 +185,7 @@
     "react-native-web-webview": "^1.0.2",
     "react-native-webview": "13.6.4",
     "react-responsive": "^9.0.2",
+    "react-textarea-autosize": "^8.5.3",
     "rn-fetch-blob": "^0.12.0",
     "sentry-expo": "~7.0.1",
     "statsig-react-native-expo": "^4.6.1",
diff --git a/src/screens/Messages/Conversation/MessageInput.tsx b/src/screens/Messages/Conversation/MessageInput.tsx
index 1a63b1b5b..f92773621 100644
--- a/src/screens/Messages/Conversation/MessageInput.tsx
+++ b/src/screens/Messages/Conversation/MessageInput.tsx
@@ -1,5 +1,16 @@
 import React from 'react'
-import {Pressable, TextInput, View} from 'react-native'
+import {
+  Dimensions,
+  Keyboard,
+  NativeSyntheticEvent,
+  Pressable,
+  TextInput,
+  TextInputContentSizeChangeEventData,
+  View,
+} from 'react-native'
+import {useSafeAreaInsets} from 'react-native-safe-area-context'
+import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
 
 import {atoms as a, useTheme} from '#/alf'
 import {Text} from '#/components/Typography'
@@ -13,41 +24,67 @@ export function MessageInput({
   onFocus: () => void
   onBlur: () => void
 }) {
+  const {_} = useLingui()
   const t = useTheme()
   const [message, setMessage] = React.useState('')
+  const [maxHeight, setMaxHeight] = React.useState<number | undefined>()
+  const [isInputScrollable, setIsInputScrollable] = React.useState(false)
+
+  const {top: topInset} = useSafeAreaInsets()
 
   const inputRef = React.useRef<TextInput>(null)
 
   const onSubmit = React.useCallback(() => {
-    onSendMessage(message)
+    if (message.trim() === '') {
+      return
+    }
+    onSendMessage(message.trimEnd())
     setMessage('')
     setTimeout(() => {
       inputRef.current?.focus()
     }, 100)
   }, [message, onSendMessage])
 
+  const onInputLayout = React.useCallback(
+    (e: NativeSyntheticEvent<TextInputContentSizeChangeEventData>) => {
+      const keyboardHeight = Keyboard.metrics()?.height ?? 0
+      const windowHeight = Dimensions.get('window').height
+
+      const max = windowHeight - keyboardHeight - topInset - 100
+      const availableSpace = max - e.nativeEvent.contentSize.height
+
+      setMaxHeight(max)
+      setIsInputScrollable(availableSpace < 30)
+    },
+    [topInset],
+  )
+
   return (
     <View
       style={[
         a.flex_row,
         a.py_sm,
         a.px_sm,
-        a.rounded_full,
+        a.pl_md,
         a.mt_sm,
         t.atoms.bg_contrast_25,
+        {borderRadius: 23},
       ]}>
       <TextInput
-        accessibilityLabel="Text input field"
-        accessibilityHint="Write a message"
+        accessibilityLabel={_(msg`Message input field`)}
+        accessibilityHint={_(msg`Type your message here`)}
+        placeholder={_(msg`Write a message`)}
+        placeholderTextColor={t.palette.contrast_500}
         value={message}
+        multiline={true}
         onChangeText={setMessage}
-        placeholder="Write a message"
-        style={[a.flex_1, a.text_sm, a.px_sm, t.atoms.text]}
-        onSubmitEditing={onSubmit}
+        style={[a.flex_1, a.text_md, a.px_sm, t.atoms.text, {maxHeight}]}
+        keyboardAppearance={t.name === 'light' ? 'light' : 'dark'}
+        scrollEnabled={isInputScrollable}
+        blurOnSubmit={false}
         onFocus={onFocus}
         onBlur={onBlur}
-        placeholderTextColor={t.palette.contrast_500}
-        keyboardAppearance={t.name === 'light' ? 'light' : 'dark'}
+        onContentSizeChange={onInputLayout}
         ref={inputRef}
       />
       <Pressable
diff --git a/src/screens/Messages/Conversation/MessageInput.web.tsx b/src/screens/Messages/Conversation/MessageInput.web.tsx
new file mode 100644
index 000000000..ab57457d7
--- /dev/null
+++ b/src/screens/Messages/Conversation/MessageInput.web.tsx
@@ -0,0 +1,91 @@
+import React from 'react'
+import {Pressable, StyleSheet, View} from 'react-native'
+import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import TextareaAutosize from 'react-textarea-autosize'
+
+import {atoms as a, useTheme} from '#/alf'
+import {Text} from '#/components/Typography'
+
+export function MessageInput({
+  onSendMessage,
+}: {
+  onSendMessage: (message: string) => void
+  onFocus: () => void
+  onBlur: () => void
+}) {
+  const {_} = useLingui()
+  const t = useTheme()
+  const [message, setMessage] = React.useState('')
+
+  const onSubmit = React.useCallback(() => {
+    if (message.trim() === '') {
+      return
+    }
+    onSendMessage(message.trimEnd())
+    setMessage('')
+  }, [message, onSendMessage])
+
+  const onKeyDown = React.useCallback(
+    (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
+      if (e.key === 'Enter') {
+        if (e.shiftKey) return
+        e.preventDefault()
+        onSubmit()
+      }
+    },
+    [onSubmit],
+  )
+
+  const onChange = React.useCallback(
+    (e: React.ChangeEvent<HTMLTextAreaElement>) => {
+      setMessage(e.target.value)
+    },
+    [],
+  )
+
+  return (
+    <View
+      style={[
+        a.flex_row,
+        a.py_sm,
+        a.px_sm,
+        a.pl_md,
+        a.mt_sm,
+        t.atoms.bg_contrast_25,
+        {borderRadius: 23},
+      ]}>
+      <TextareaAutosize
+        style={StyleSheet.flatten([
+          a.flex_1,
+          a.px_sm,
+          a.border_0,
+          t.atoms.text,
+          {
+            backgroundColor: 'transparent',
+            resize: 'none',
+            paddingTop: 6,
+          },
+        ])}
+        maxRows={12}
+        placeholder={_(msg`Write a message`)}
+        defaultValue=""
+        value={message}
+        dirName="ltr"
+        autoFocus={true}
+        onChange={onChange}
+        onKeyDown={onKeyDown}
+      />
+      <Pressable
+        accessibilityRole="button"
+        style={[
+          a.rounded_full,
+          a.align_center,
+          a.justify_center,
+          {height: 30, width: 30, backgroundColor: t.palette.primary_500},
+        ]}>
+        <Text style={a.text_md}>🐴</Text>
+      </Pressable>
+    </View>
+  )
+}
diff --git a/yarn.lock b/yarn.lock
index 8c014e28e..749c374f3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -18981,6 +18981,15 @@ react-test-renderer@18.2.0:
     react-shallow-renderer "^16.15.0"
     scheduler "^0.23.0"
 
+react-textarea-autosize@^8.5.3:
+  version "8.5.3"
+  resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz#d1e9fe760178413891484847d3378706052dd409"
+  integrity sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ==
+  dependencies:
+    "@babel/runtime" "^7.20.13"
+    use-composed-ref "^1.3.0"
+    use-latest "^1.2.1"
+
 react@18.2.0:
   version "18.2.0"
   resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
@@ -21353,6 +21362,16 @@ use-callback-ref@^1.3.0:
   dependencies:
     tslib "^2.0.0"
 
+use-composed-ref@^1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.3.0.tgz#3d8104db34b7b264030a9d916c5e94fbe280dbda"
+  integrity sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==
+
+use-isomorphic-layout-effect@^1.1.1:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb"
+  integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==
+
 use-latest-callback@^0.1.5:
   version "0.1.6"
   resolved "https://registry.yarnpkg.com/use-latest-callback/-/use-latest-callback-0.1.6.tgz#3fa6e7babbb5f9bfa24b5094b22939e1e92ebcf6"
@@ -21363,6 +21382,13 @@ use-latest-callback@^0.1.9:
   resolved "https://registry.yarnpkg.com/use-latest-callback/-/use-latest-callback-0.1.9.tgz#10191dc54257e65a8e52322127643a8940271e2a"
   integrity sha512-CL/29uS74AwreI/f2oz2hLTW7ZqVeV5+gxFeGudzQrgkCytrHw33G4KbnQOrRlAEzzAFXi7dDLMC9zhWcVpzmw==
 
+use-latest@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/use-latest/-/use-latest-1.2.1.tgz#d13dfb4b08c28e3e33991546a2cee53e14038cf2"
+  integrity sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw==
+  dependencies:
+    use-isomorphic-layout-effect "^1.1.1"
+
 use-sidecar@^1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.2.tgz#2f43126ba2d7d7e117aa5855e5d8f0276dfe73c2"