diff --git a/native/chat/typeahead-tooltip.react.js b/native/chat/typeahead-tooltip.react.js new file mode 100644 --- /dev/null +++ b/native/chat/typeahead-tooltip.react.js @@ -0,0 +1,148 @@ +// @flow + +import * as React from 'react'; +import { Platform, Text } from 'react-native'; +import { PanGestureHandler, FlatList } from 'react-native-gesture-handler'; + +import { + type TypeaheadMatchedStrings, + type Selection, + getNewTextAndSelection, +} from 'lib/shared/typeahead-utils'; +import type { RelativeMemberInfo } from 'lib/types/thread-types'; + +import Button from '../components/button.react'; +import { useStyles } from '../themes/colors'; + +export type TypeaheadTooltipProps = { + +text: string, + +matchedStrings: TypeaheadMatchedStrings, + +suggestedUsers: $ReadOnlyArray, + +focusAndUpdateTextAndSelection: (text: string, selection: Selection) => void, +}; + +function TypeaheadTooltip(props: TypeaheadTooltipProps): React.Node { + const { + text, + matchedStrings, + suggestedUsers, + focusAndUpdateTextAndSelection, + } = props; + + const { textBeforeAtSymbol, usernamePrefix } = matchedStrings; + + const styles = useStyles(unboundStyles); + + const renderTypeaheadButton = React.useCallback( + ({ item }: { item: RelativeMemberInfo, ... }) => { + const onPress = () => { + const { newText, newSelectionStart } = getNewTextAndSelection( + textBeforeAtSymbol, + text, + usernamePrefix, + item, + ); + + focusAndUpdateTextAndSelection(newText, { + start: newSelectionStart, + end: newSelectionStart, + }); + }; + + return ( + + ); + }, + [ + focusAndUpdateTextAndSelection, + styles.button, + styles.buttonLabel, + text, + textBeforeAtSymbol, + usernamePrefix, + ], + ); + + // This is a hack that was introduced due to a buggy behavior of a + // absolutely positioned FlatList on Android. + + // There was a bug that was present when there were too few items in a + // FlatList and it wasn't scrollable. It was only present on Android as + // iOS has a default "bounce" animation, even if the list is too short. + // The bug manifested itself when we tried to scroll the FlatList. + // Because it was unscrollable we were really scrolling FlatList + // below it (in the ChatList) as FlatList here has "position: absolute" + // and is positioned over the other FlatList. + + // The hack here solves it by using a PanGestureHandler. This way Pan events + // on TypeaheadTooltip FlatList are always caught by handler. + // When the FlatList is scrollable it scrolls normally, because handler + // passes those events down to it. + + // If it's not scrollable, the PanGestureHandler "swallows" them. + // Normally it would trigger onGestureEvent callback, but we don't need to + // handle those events. We just want them to be ignored + // and that's what's actually happening. + + const flatList = React.useMemo( + () => ( + + ), + [ + renderTypeaheadButton, + styles.container, + styles.contentContainer, + suggestedUsers, + ], + ); + + const listWithConditionalHandler = React.useMemo(() => { + if (Platform.OS === 'android') { + return {flatList}; + } + return flatList; + }, [flatList]); + + return listWithConditionalHandler; +} + +const unboundStyles = { + container: { + position: 'absolute', + maxHeight: 200, + left: 0, + right: 0, + bottom: '100%', + backgroundColor: 'typeaheadTooltipBackground', + borderBottomWidth: 1, + borderTopWidth: 1, + borderColor: 'typeaheadTooltipBorder', + borderStyle: 'solid', + }, + contentContainer: { + padding: 8, + }, + button: { + flexDirection: 'row', + innerHeight: 24, + padding: 8, + color: 'typeaheadTooltipText', + }, + buttonLabel: { + color: 'white', + fontSize: 16, + fontWeight: '400', + }, +}; + +export default TypeaheadTooltip; diff --git a/native/themes/colors.js b/native/themes/colors.js --- a/native/themes/colors.js +++ b/native/themes/colors.js @@ -175,6 +175,9 @@ subthreadsModalClose: '#808080', subthreadsModalBackground: '#1F1F1F', subthreadsModalSearch: '#FFFFFF04', + typeaheadTooltipBackground: '#1F1F1f', + typeaheadTooltipBorder: '#404040', + typeaheadTooltipText: 'white', }); const colors = { light, dark };