[Clipclops] Dynamic input height (#3778)
* input max height/scrollability * remove unused imports * add a web-specific version * enter and shift enter for web * missing onSubmit for native * missing attributes * improve layout of input on web * use the correct text color in the input * trim messages * remove `onSubmit` * move prop up * trim message on web * remove extra function call --------- Co-authored-by: Samuel Newman <mozzius@protonmail.com>zio/stable
parent
6f9993ca55
commit
333ccdad39
|
@ -185,6 +185,7 @@
|
||||||
"react-native-web-webview": "^1.0.2",
|
"react-native-web-webview": "^1.0.2",
|
||||||
"react-native-webview": "13.6.4",
|
"react-native-webview": "13.6.4",
|
||||||
"react-responsive": "^9.0.2",
|
"react-responsive": "^9.0.2",
|
||||||
|
"react-textarea-autosize": "^8.5.3",
|
||||||
"rn-fetch-blob": "^0.12.0",
|
"rn-fetch-blob": "^0.12.0",
|
||||||
"sentry-expo": "~7.0.1",
|
"sentry-expo": "~7.0.1",
|
||||||
"statsig-react-native-expo": "^4.6.1",
|
"statsig-react-native-expo": "^4.6.1",
|
||||||
|
|
|
@ -1,5 +1,16 @@
|
||||||
import React from 'react'
|
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 {atoms as a, useTheme} from '#/alf'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
@ -13,41 +24,67 @@ export function MessageInput({
|
||||||
onFocus: () => void
|
onFocus: () => void
|
||||||
onBlur: () => void
|
onBlur: () => void
|
||||||
}) {
|
}) {
|
||||||
|
const {_} = useLingui()
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const [message, setMessage] = React.useState('')
|
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 inputRef = React.useRef<TextInput>(null)
|
||||||
|
|
||||||
const onSubmit = React.useCallback(() => {
|
const onSubmit = React.useCallback(() => {
|
||||||
onSendMessage(message)
|
if (message.trim() === '') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
onSendMessage(message.trimEnd())
|
||||||
setMessage('')
|
setMessage('')
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
inputRef.current?.focus()
|
inputRef.current?.focus()
|
||||||
}, 100)
|
}, 100)
|
||||||
}, [message, onSendMessage])
|
}, [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 (
|
return (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
a.flex_row,
|
a.flex_row,
|
||||||
a.py_sm,
|
a.py_sm,
|
||||||
a.px_sm,
|
a.px_sm,
|
||||||
a.rounded_full,
|
a.pl_md,
|
||||||
a.mt_sm,
|
a.mt_sm,
|
||||||
t.atoms.bg_contrast_25,
|
t.atoms.bg_contrast_25,
|
||||||
|
{borderRadius: 23},
|
||||||
]}>
|
]}>
|
||||||
<TextInput
|
<TextInput
|
||||||
accessibilityLabel="Text input field"
|
accessibilityLabel={_(msg`Message input field`)}
|
||||||
accessibilityHint="Write a message"
|
accessibilityHint={_(msg`Type your message here`)}
|
||||||
|
placeholder={_(msg`Write a message`)}
|
||||||
|
placeholderTextColor={t.palette.contrast_500}
|
||||||
value={message}
|
value={message}
|
||||||
|
multiline={true}
|
||||||
onChangeText={setMessage}
|
onChangeText={setMessage}
|
||||||
placeholder="Write a message"
|
style={[a.flex_1, a.text_md, a.px_sm, t.atoms.text, {maxHeight}]}
|
||||||
style={[a.flex_1, a.text_sm, a.px_sm, t.atoms.text]}
|
keyboardAppearance={t.name === 'light' ? 'light' : 'dark'}
|
||||||
onSubmitEditing={onSubmit}
|
scrollEnabled={isInputScrollable}
|
||||||
|
blurOnSubmit={false}
|
||||||
onFocus={onFocus}
|
onFocus={onFocus}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
placeholderTextColor={t.palette.contrast_500}
|
onContentSizeChange={onInputLayout}
|
||||||
keyboardAppearance={t.name === 'light' ? 'light' : 'dark'}
|
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
/>
|
/>
|
||||||
<Pressable
|
<Pressable
|
||||||
|
|
|
@ -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>
|
||||||
|
)
|
||||||
|
}
|
26
yarn.lock
26
yarn.lock
|
@ -18981,6 +18981,15 @@ react-test-renderer@18.2.0:
|
||||||
react-shallow-renderer "^16.15.0"
|
react-shallow-renderer "^16.15.0"
|
||||||
scheduler "^0.23.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:
|
react@18.2.0:
|
||||||
version "18.2.0"
|
version "18.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
|
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
|
||||||
|
@ -21353,6 +21362,16 @@ use-callback-ref@^1.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib "^2.0.0"
|
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:
|
use-latest-callback@^0.1.5:
|
||||||
version "0.1.6"
|
version "0.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/use-latest-callback/-/use-latest-callback-0.1.6.tgz#3fa6e7babbb5f9bfa24b5094b22939e1e92ebcf6"
|
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"
|
resolved "https://registry.yarnpkg.com/use-latest-callback/-/use-latest-callback-0.1.9.tgz#10191dc54257e65a8e52322127643a8940271e2a"
|
||||||
integrity sha512-CL/29uS74AwreI/f2oz2hLTW7ZqVeV5+gxFeGudzQrgkCytrHw33G4KbnQOrRlAEzzAFXi7dDLMC9zhWcVpzmw==
|
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:
|
use-sidecar@^1.1.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.2.tgz#2f43126ba2d7d7e117aa5855e5d8f0276dfe73c2"
|
resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.2.tgz#2f43126ba2d7d7e117aa5855e5d8f0276dfe73c2"
|
||||||
|
|
Loading…
Reference in New Issue