diff --git a/lib/shared/typeahead-utils.js b/lib/shared/typeahead-utils.js --- a/lib/shared/typeahead-utils.js +++ b/lib/shared/typeahead-utils.js @@ -15,6 +15,20 @@ +end: number, }; +function getTypeaheadRegexMatches( + text: string, + selection: Selection, + regex: RegExp, +): null | RegExp$matchResult { + if ( + selection.start === selection.end && + (selection.start === text.length || /\s/.test(text[selection.end])) + ) { + return text.slice(0, selection.start).match(regex); + } + return null; +} + function getTypeaheadUserSuggestions( userSearchIndex: SearchIndex, threadMembers: $ReadOnlyArray, @@ -54,4 +68,8 @@ return { newText, newSelectionStart }; } -export { getTypeaheadUserSuggestions, getNewTextAndSelection }; +export { + getTypeaheadUserSuggestions, + getNewTextAndSelection, + getTypeaheadRegexMatches, +}; diff --git a/native/chat/chat-input-bar.react.js b/native/chat/chat-input-bar.react.js --- a/native/chat/chat-input-bar.react.js +++ b/native/chat/chat-input-bar.react.js @@ -43,7 +43,11 @@ draftKeyFromThreadID, colorIsDark, } from 'lib/shared/thread-utils'; -import type { Selection } from 'lib/shared/typeahead-utils'; +import { + getTypeaheadUserSuggestions, + getTypeaheadRegexMatches, + type Selection, +} from 'lib/shared/typeahead-utils'; import type { CalendarQuery } from 'lib/types/entry-types'; import type { LoadingStatus } from 'lib/types/loading-types'; import type { PhotoPaste } from 'lib/types/media-types'; @@ -88,8 +92,10 @@ import type { LayoutEvent, SelectionChangeEvent } from '../types/react-native'; import { type AnimatedViewStyle, AnimatedView } from '../types/styles'; import { runTiming } from '../utils/animation-utils'; +import { nativeTypeaheadRegex } from '../utils/typeahead-utils'; import { ChatContext } from './chat-context'; import type { ChatNavigationProp } from './chat.react'; +import TypeaheadTooltip from './typeahead-tooltip.react'; /* eslint-disable import/no-named-as-default-member */ const { @@ -464,6 +470,39 @@ ); } + const typeaheadRegexMatches = getTypeaheadRegexMatches( + this.state.text, + this.state.selection, + nativeTypeaheadRegex, + ); + + let typeaheadTooltip = null; + + if (typeaheadRegexMatches) { + const typeaheadMatchedStrings = { + textBeforeAtSymbol: typeaheadRegexMatches[1] ?? '', + usernamePrefix: typeaheadRegexMatches[4] ?? '', + }; + + const suggestedUsers = getTypeaheadUserSuggestions( + this.props.userSearchIndex, + this.props.threadMembers, + this.props.viewerID, + typeaheadMatchedStrings.usernamePrefix, + ); + + if (suggestedUsers.length > 0) { + typeaheadTooltip = ( + + ); + } + } + let content; const defaultMembersAreVoiced = checkIfDefaultMembersAreVoiced( this.props.threadInfo, @@ -509,6 +548,7 @@ style={this.props.styles.container} onLayout={this.props.onInputBarLayout} > + {typeaheadTooltip} {joinButton} {content} {keyboardInputHost}