UNPKG

react-native-controlled-mentions

Version:
301 lines (225 loc) 11.7 kB
react-native-controlled-mentions [![npm version][npm-image]][npm-url] - Pretty simple and fully controlled mention input. It can: * Gracefully render formatted mentions directly in RN `TextInput` component * Support for different mention types (**[@user mentions](#demo)**, **#hashtags**, etc) * Use `value`/`onChange` as in usual `TextInput` props * Completely typed (written on TypeScript) * No need for native libraries In addition, you can add custom styling for a regex pattern (like URLs) using the optimized recursive function for parsing the value. Demo - Try it on Expo Snack: https://snack.expo.io/@dabakovich/mentionsapp ![](demo.gif) Getting started - Install the library using either Yarn: ``yarn add react-native-controlled-mentions`` or npm: ``npm install --save react-native-controlled-mentions`` Usage - Import the [MentionInput](#mentioninput-component-props) component: ```tsx import { MentionInput } from 'react-native-controlled-mentions' ``` Replace your [TextInput](https://reactnative.dev/docs/textinput) by [MentionInput](#mentioninput-component-props) component and add the `partTypes` property where you can define what mention or pattern types you want to support. It takes an array of [PartType](#parttype-type) objects. ```tsx <MentionInput value={value} onChange={setValue} partTypes={[ { trigger: '@', // Should be a single character like '@' or '#' renderSuggestions, textStyle: {fontWeight: 'bold', color: 'blue'}, // The mention style in the input }, ]} /> ``` Define your `renderSuggestions` functional component that receive [MentionSuggestionsProps](#mentionsuggestionsprops-type-props): ```tsx const suggestions = [ {id: '1', name: 'David Tabaka'}, {id: '2', name: 'Mary'}, {id: '3', name: 'Tony'}, {id: '4', name: 'Mike'}, {id: '5', name: 'Grey'}, ]; const renderSuggestions: FC<MentionSuggestionsProps> = ({keyword, onSuggestionPress}) => { if (keyword == null) { return null; } return ( <View> {suggestions .filter(one => one.name.toLocaleLowerCase().includes(keyword.toLocaleLowerCase())) .map(one => ( <Pressable key={one.id} onPress={() => onSuggestionPress(one)} style={{padding: 12}} > <Text>{one.name}</Text> </Pressable> )) } </View> ); }; ``` You're done! The whole example is in the `/example` folder. API - ### `MentionInput` component props | **Property name** | **Description** | **Type** | **Required** | **Default** | |------------------- |-------------------------------------------------------------------- |---------------------------------------- |------------ |------------ | | `value` | The same as in `TextInput` | string | true | | | `onChange` | The same as in `TextInput` | (value: string) => void | true | | | `partTypes` | Declare what part types you want to support (mentions, hashtags, urls)| [PartType](#parttype-type)[] | false | [] | | `inputRef` | Reference to the `TextInput` component inside `MentionInput` | Ref\<TextInput> | false | | | `containerStyle` | Style to the `MentionInput`'s root component | StyleProp\<TextStyle> | false | | | ...textInputProps | Other text input props | TextInputProps | false | | ### `PartType` type [MentionPartType](#mentionparttype-type-props) | [PatternPartType](#patternparttype-type-props) ### `MentionPartType` type props | **Property name** | **Description** | **Type** | **Required** | **Default** | |------------------------------------ |----------------------------------------------------------------------------------- |----------------------------------------------------------------------------------- |------------ |----------- | | `trigger` | Character that will trigger current mention type | string | true | | | `renderSuggestions` | Renderer for mention suggestions component | (props: [MentionSuggestionsProps](#mentionsuggestionsprops-type-props)) => ReactNode | false | | | `allowedSpacesCount` | How much spaces are allowed for mention keyword | number | false | 1 | | `isInsertSpaceAfterMention` | Should we add a space after selected mentions if the mention is at the end of row | boolean | false | false | | `isBottomMentionSuggestionsRender` | Should we render either at the top or bottom of the input | boolean | false | | | `textStyle` | Text style for mentions in `TextInput` | StyleProp\<TextStyle> | false | | | `getPlainString` | Function for generating custom mention text in text input | (mention: [MentionData](#mentiondata-type-props)) => string | false | | ### `PatternPartType` type props | **Property name** | **Description** | **Type** | **Required** | **Default** | |--------------------------- |----------------------------------------------------------------------------------- |----------------------------------------------------------------------------------- |------------ |----------- | | `pattern` | RegExp for parsing a pattern, should include global flag | RegExp | true | | | `textStyle` | Text style for pattern in `TextInput` | StyleProp\<TextStyle> | false | | ### `MentionSuggestionsProps` type props `keyword: string | undefined` Keyword that will provide string between trigger character (e.g. '@') and cursor. If the cursor is not tracking any mention typing the `keyword` will be `undefined`. Examples where @name is just plain text yet, not mention and `|` is cursor position: ``` '|abc @name dfg' - keyword is undefined 'abc @| dfg' - keyword is '' 'abc @name| dfg' - keyword is 'name' 'abc @na|me dfg' - keyword is 'na' 'abc @|name dfg' - keyword is against '' 'abc @name |dfg' - keyword is against undefined ``` `onSuggestionPress: (suggestion: Suggestion) => void` You should call that callback when user selects any suggestion. ### `Suggestion` type props `id: string` Unique id for each suggestion. `name: string` Name that will be shown in `MentionInput` when user will select the suggestion. ### `MentionData` type props For example, we have that mention value `@[David Tabaka](123)`. Then after parsing that string by `mentionRegEx` we will get next properties: `original: string` The whole mention value string - `@[David Tabaka](123)` `trigger: string` The extracted trigger - `@` `name: string` The extracted name - `David Tabaka` `id: string` The extracted id - `123` ### `mentionRegEx` ```jsregexp /(?<original>(?<trigger>.)\[(?<name>([^[]*))]\((?<id>([\d\w-]*))\))/gi; ``` Parsing `MentionInput`'s value - You can import RegEx that is using in the component and then extract all your mentions from `MentionInput`'s value using your own logic. ```ts import { mentionRegEx } from 'react-native-controlled-mentions'; ``` Or you can use `replaceMentionValues` helper to replace all mentions from `MentionInput`'s input using your replacer function that receives [MentionData](#mentiondata-type-props) type and returns string. ```ts import { replaceMentionValues } from 'react-native-controlled-mentions'; const value = 'Hello @[David Tabaka](5)! How are you?'; console.log(replaceMentionValues(value, ({id}) => `@${id}`)); // Hello @5! How are you? console.log(replaceMentionValues(value, ({name}) => `@${name}`)); // Hello @David Tabaka! How are you? ``` Rendering `MentionInput`'s value - If you want to parse and render your value somewhere else you can use `parseValue` tool which gives you array of parts and then use your own part renderer to resolve this issue. Here is an example: ```tsx import { Part, PartType, parseValue, isMentionPartType, } from 'react-native-controlled-mentions'; /** * Part renderer * * @param part * @param index */ const renderPart = ( part: Part, index: number, ) => { // Just plain text if (!part.partType) { return <Text key={index}>{part.text}</Text>; } // Mention type part if (isMentionPartType(part.partType)) { return ( <Text key={`${index}-${part.data?.trigger}`} style={part.partType.textStyle} onPress={() => console.log('Pressed', part.data)} > {part.text} </Text> ); } // Other styled part types return ( <Text key={`${index}-pattern`} style={part.partType.textStyle} > {part.text} </Text> ); }; /** * Value renderer. Parsing value to parts array and then mapping the array using 'renderPart' * * @param value - value from MentionInput * @param partTypes - the part types array that you providing to MentionInput */ const renderValue: FC = ( value: string, partTypes: PartType[], ) => { const {parts} = parseValue(value, partTypes); return <Text>{parts.map(renderPart)}</Text>; }; ``` To Do - * ~~Add support for different text formatting (e.g. URLs)~~ * ~~Add more customizations~~ DONE * ~~Add ability to handle few mention types ("#", "@" etc)~~ DONE Known issues - * Mention name regex accepts white spaces (e.g. `{name: ' ', value: 1}`) * ~~Keyboard auto-correction not working if suggested word has the same length~~ FIXED * ~~Text becomes transparent when setting custom font size in TextInput~~ FIXED Support Me - <a href="https://www.buymeacoffee.com/dabakovich" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" ></a> [npm-image]: https://img.shields.io/npm/v/react-native-controlled-mentions [npm-url]: https://npmjs.org/package/react-native-controlled-mentions