Bluesky app fork with some witchin' additions 💫

[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>

authored by hailey.at

Samuel Newman and committed by
GitHub
333ccdad 6f9993ca

+165 -10
+1
package.json
··· 185 185 "react-native-web-webview": "^1.0.2", 186 186 "react-native-webview": "13.6.4", 187 187 "react-responsive": "^9.0.2", 188 + "react-textarea-autosize": "^8.5.3", 188 189 "rn-fetch-blob": "^0.12.0", 189 190 "sentry-expo": "~7.0.1", 190 191 "statsig-react-native-expo": "^4.6.1",
+47 -10
src/screens/Messages/Conversation/MessageInput.tsx
··· 1 1 import React from 'react' 2 - import {Pressable, TextInput, View} from 'react-native' 2 + import { 3 + Dimensions, 4 + Keyboard, 5 + NativeSyntheticEvent, 6 + Pressable, 7 + TextInput, 8 + TextInputContentSizeChangeEventData, 9 + View, 10 + } from 'react-native' 11 + import {useSafeAreaInsets} from 'react-native-safe-area-context' 12 + import {msg} from '@lingui/macro' 13 + import {useLingui} from '@lingui/react' 3 14 4 15 import {atoms as a, useTheme} from '#/alf' 5 16 import {Text} from '#/components/Typography' ··· 13 24 onFocus: () => void 14 25 onBlur: () => void 15 26 }) { 27 + const {_} = useLingui() 16 28 const t = useTheme() 17 29 const [message, setMessage] = React.useState('') 30 + const [maxHeight, setMaxHeight] = React.useState<number | undefined>() 31 + const [isInputScrollable, setIsInputScrollable] = React.useState(false) 32 + 33 + const {top: topInset} = useSafeAreaInsets() 18 34 19 35 const inputRef = React.useRef<TextInput>(null) 20 36 21 37 const onSubmit = React.useCallback(() => { 22 - onSendMessage(message) 38 + if (message.trim() === '') { 39 + return 40 + } 41 + onSendMessage(message.trimEnd()) 23 42 setMessage('') 24 43 setTimeout(() => { 25 44 inputRef.current?.focus() 26 45 }, 100) 27 46 }, [message, onSendMessage]) 28 47 48 + const onInputLayout = React.useCallback( 49 + (e: NativeSyntheticEvent<TextInputContentSizeChangeEventData>) => { 50 + const keyboardHeight = Keyboard.metrics()?.height ?? 0 51 + const windowHeight = Dimensions.get('window').height 52 + 53 + const max = windowHeight - keyboardHeight - topInset - 100 54 + const availableSpace = max - e.nativeEvent.contentSize.height 55 + 56 + setMaxHeight(max) 57 + setIsInputScrollable(availableSpace < 30) 58 + }, 59 + [topInset], 60 + ) 61 + 29 62 return ( 30 63 <View 31 64 style={[ 32 65 a.flex_row, 33 66 a.py_sm, 34 67 a.px_sm, 35 - a.rounded_full, 68 + a.pl_md, 36 69 a.mt_sm, 37 70 t.atoms.bg_contrast_25, 71 + {borderRadius: 23}, 38 72 ]}> 39 73 <TextInput 40 - accessibilityLabel="Text input field" 41 - accessibilityHint="Write a message" 74 + accessibilityLabel={_(msg`Message input field`)} 75 + accessibilityHint={_(msg`Type your message here`)} 76 + placeholder={_(msg`Write a message`)} 77 + placeholderTextColor={t.palette.contrast_500} 42 78 value={message} 79 + multiline={true} 43 80 onChangeText={setMessage} 44 - placeholder="Write a message" 45 - style={[a.flex_1, a.text_sm, a.px_sm, t.atoms.text]} 46 - onSubmitEditing={onSubmit} 81 + style={[a.flex_1, a.text_md, a.px_sm, t.atoms.text, {maxHeight}]} 82 + keyboardAppearance={t.name === 'light' ? 'light' : 'dark'} 83 + scrollEnabled={isInputScrollable} 84 + blurOnSubmit={false} 47 85 onFocus={onFocus} 48 86 onBlur={onBlur} 49 - placeholderTextColor={t.palette.contrast_500} 50 - keyboardAppearance={t.name === 'light' ? 'light' : 'dark'} 87 + onContentSizeChange={onInputLayout} 51 88 ref={inputRef} 52 89 /> 53 90 <Pressable
+91
src/screens/Messages/Conversation/MessageInput.web.tsx
··· 1 + import React from 'react' 2 + import {Pressable, StyleSheet, View} from 'react-native' 3 + import {msg} from '@lingui/macro' 4 + import {useLingui} from '@lingui/react' 5 + import TextareaAutosize from 'react-textarea-autosize' 6 + 7 + import {atoms as a, useTheme} from '#/alf' 8 + import {Text} from '#/components/Typography' 9 + 10 + export function MessageInput({ 11 + onSendMessage, 12 + }: { 13 + onSendMessage: (message: string) => void 14 + onFocus: () => void 15 + onBlur: () => void 16 + }) { 17 + const {_} = useLingui() 18 + const t = useTheme() 19 + const [message, setMessage] = React.useState('') 20 + 21 + const onSubmit = React.useCallback(() => { 22 + if (message.trim() === '') { 23 + return 24 + } 25 + onSendMessage(message.trimEnd()) 26 + setMessage('') 27 + }, [message, onSendMessage]) 28 + 29 + const onKeyDown = React.useCallback( 30 + (e: React.KeyboardEvent<HTMLTextAreaElement>) => { 31 + if (e.key === 'Enter') { 32 + if (e.shiftKey) return 33 + e.preventDefault() 34 + onSubmit() 35 + } 36 + }, 37 + [onSubmit], 38 + ) 39 + 40 + const onChange = React.useCallback( 41 + (e: React.ChangeEvent<HTMLTextAreaElement>) => { 42 + setMessage(e.target.value) 43 + }, 44 + [], 45 + ) 46 + 47 + return ( 48 + <View 49 + style={[ 50 + a.flex_row, 51 + a.py_sm, 52 + a.px_sm, 53 + a.pl_md, 54 + a.mt_sm, 55 + t.atoms.bg_contrast_25, 56 + {borderRadius: 23}, 57 + ]}> 58 + <TextareaAutosize 59 + style={StyleSheet.flatten([ 60 + a.flex_1, 61 + a.px_sm, 62 + a.border_0, 63 + t.atoms.text, 64 + { 65 + backgroundColor: 'transparent', 66 + resize: 'none', 67 + paddingTop: 6, 68 + }, 69 + ])} 70 + maxRows={12} 71 + placeholder={_(msg`Write a message`)} 72 + defaultValue="" 73 + value={message} 74 + dirName="ltr" 75 + autoFocus={true} 76 + onChange={onChange} 77 + onKeyDown={onKeyDown} 78 + /> 79 + <Pressable 80 + accessibilityRole="button" 81 + style={[ 82 + a.rounded_full, 83 + a.align_center, 84 + a.justify_center, 85 + {height: 30, width: 30, backgroundColor: t.palette.primary_500}, 86 + ]}> 87 + <Text style={a.text_md}>🐴</Text> 88 + </Pressable> 89 + </View> 90 + ) 91 + }
+26
yarn.lock
··· 18981 18981 react-shallow-renderer "^16.15.0" 18982 18982 scheduler "^0.23.0" 18983 18983 18984 + react-textarea-autosize@^8.5.3: 18985 + version "8.5.3" 18986 + resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz#d1e9fe760178413891484847d3378706052dd409" 18987 + integrity sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ== 18988 + dependencies: 18989 + "@babel/runtime" "^7.20.13" 18990 + use-composed-ref "^1.3.0" 18991 + use-latest "^1.2.1" 18992 + 18984 18993 react@18.2.0: 18985 18994 version "18.2.0" 18986 18995 resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" ··· 21353 21362 dependencies: 21354 21363 tslib "^2.0.0" 21355 21364 21365 + use-composed-ref@^1.3.0: 21366 + version "1.3.0" 21367 + resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.3.0.tgz#3d8104db34b7b264030a9d916c5e94fbe280dbda" 21368 + integrity sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ== 21369 + 21370 + use-isomorphic-layout-effect@^1.1.1: 21371 + version "1.1.2" 21372 + resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb" 21373 + integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA== 21374 + 21356 21375 use-latest-callback@^0.1.5: 21357 21376 version "0.1.6" 21358 21377 resolved "https://registry.yarnpkg.com/use-latest-callback/-/use-latest-callback-0.1.6.tgz#3fa6e7babbb5f9bfa24b5094b22939e1e92ebcf6" ··· 21362 21381 version "0.1.9" 21363 21382 resolved "https://registry.yarnpkg.com/use-latest-callback/-/use-latest-callback-0.1.9.tgz#10191dc54257e65a8e52322127643a8940271e2a" 21364 21383 integrity sha512-CL/29uS74AwreI/f2oz2hLTW7ZqVeV5+gxFeGudzQrgkCytrHw33G4KbnQOrRlAEzzAFXi7dDLMC9zhWcVpzmw== 21384 + 21385 + use-latest@^1.2.1: 21386 + version "1.2.1" 21387 + resolved "https://registry.yarnpkg.com/use-latest/-/use-latest-1.2.1.tgz#d13dfb4b08c28e3e33991546a2cee53e14038cf2" 21388 + integrity sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw== 21389 + dependencies: 21390 + use-isomorphic-layout-effect "^1.1.1" 21365 21391 21366 21392 use-sidecar@^1.1.2: 21367 21393 version "1.1.2"