react-native-controlled-mentions
Version:
Fully controlled React Native mentions component
301 lines (225 loc) • 11.7 kB
Markdown
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

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