Skip to content

Commit ef5c83f

Browse files
committed
feat!: add ability for defined pattern list custom style - #13
Added `partTypes` property for defining list of possible mention types or patterns for parsing. Updated README.md. Added tests for new method for parsing value. BREAKING CHANGES: `mentionTypes` renamed to `partTypes`. `MentionType` changed to `PartType` which can be either `MentionPartType` or `PatternPartType`. New types defined in README.md.
1 parent 241cd05 commit ef5c83f

File tree

6 files changed

+244
-117
lines changed

6 files changed

+244
-117
lines changed

README.md

+15-5
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,14 @@ Import the [MentionInput](#mentioninput-component-props) component:
3434
import { MentionInput } from 'react-native-controlled-mentions'
3535
```
3636

37-
Replace your [TextInput](https://reactnative.dev/docs/textinput) by [MentionInput](#mentioninput-component-props) component and add the `mentionTypes` property where you can define what mention types you want to support. It takes an array of [MentionType](#mentiontype-type-props) objects.
37+
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.
3838

3939
```tsx
4040
<Mentions
4141
value={value}
4242
onChange={setValue}
4343

44-
mentionTypes={[
44+
partTypes={[
4545
{
4646
trigger: '@', // Should be a single character like '@' or '#'
4747
renderSuggestions,
@@ -100,12 +100,15 @@ API
100100
|------------------- |-------------------------------------------------------------------- |---------------------------------------- |------------ |------------ |
101101
| `value` | The same as in `TextInput` | string | true | |
102102
| `onChange` | The same as in `TextInput` | (value: string) => void | true | |
103-
| `mentionTypes` | Declare what mention types you want to support (mentions, hashtags) | [MentionType](#mentiontype-type-props)[] | false | [] |
103+
| `partTypes` | Declare what part types you want to support (mentions, hashtags, urls)| [PartType](#parttype-type)[] | false | [] |
104104
| `inputRef` | Reference to the `TextInput` component inside `MentionInput` | Ref\<TextInput> | false | |
105105
| `containerStyle` | Style to the `MentionInput`'s root component | StyleProp\<TextStyle> | false | |
106106
| ...textInputProps | Other text input props | TextInputProps | false | |
107107

108-
### `MentionType` type props
108+
### `PartType` type
109+
[MentionPartType](#mentionparttype-type-props) | [PatternPartType](#patternparttype-type-props)
110+
111+
### `MentionPartType` type props
109112

110113
| **Property name** | **Description** | **Type** | **Required** | **Default** |
111114
|--------------------------- |----------------------------------------------------------------------------------- |----------------------------------------------------------------------------------- |------------ |----------- |
@@ -115,6 +118,13 @@ API
115118
| `textStyle` | Text style for mentions in `TextInput` | StyleProp\<TextStyle> | false | |
116119
| `getPlainString` | Function for generating custom mention text in text input | (mention: [MentionData](#mentiondata-type-props)) => string | false | |
117120

121+
### `PatternPartType` type props
122+
123+
| **Property name** | **Description** | **Type** | **Required** | **Default** |
124+
|--------------------------- |----------------------------------------------------------------------------------- |----------------------------------------------------------------------------------- |------------ |----------- |
125+
| `pattern` | RegExp for parsing a pattern, should include global flag | RegExp | true | |
126+
| `textStyle` | Text style for pattern in `TextInput` | StyleProp\<TextStyle> | false | |
127+
118128
### `MentionSuggestionsProps` type props
119129

120130
`keyword: string | undefined`
@@ -199,7 +209,7 @@ console.log(replaceMentionValues(value, ({name}) => `@${name}`)); // Hello @Davi
199209
To Do
200210
-
201211

202-
* Add support for different text formatting (e.g. URLs)
212+
* ~~Add support for different text formatting (e.g. URLs)~~
203213
* ~~Add more customizations~~ DONE
204214
* ~~Add ability to handle few mention types ("#", "@" etc)~~ DONE
205215

example/mentions-app.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ const App = () => {
5656
value={value}
5757
onChange={setValue}
5858

59-
mentionTypes={[
59+
partTypes={[
6060
{
6161
trigger: '@',
6262
renderSuggestions: renderMentionSuggestions,
@@ -66,6 +66,10 @@ const App = () => {
6666
renderSuggestions: renderHashtagSuggestions,
6767
textStyle: {fontWeight: 'bold', color: 'grey'},
6868
},
69+
{
70+
pattern: /(https?:\/\/|www\.)[-a-zA-Z0-9@:%._\+~#=]{1,256}\.(xn--)?[a-z0-9-]{2,20}\b([-a-zA-Z0-9@:%_\+\[\],.~#?&\/=]*[-a-zA-Z0-9@:%_\+\]~#?&\/=])*/gi,
71+
textStyle: {color: 'blue'},
72+
},
6973
]}
7074

7175
placeholder="Type here..."

src/components/mention-input.tsx

+18-14
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,21 @@ import {
77
View,
88
} from 'react-native';
99

10-
import { MentionInputProps, MentionType, Suggestion } from '../types';
10+
import { MentionInputProps, MentionPartType, Suggestion } from '../types';
1111
import {
1212
defaultMentionTextStyle,
1313
generateValueFromPartsAndChangedText,
1414
generateValueWithAddedSuggestion,
15-
getPartsFromValue,
15+
isMentionPartType,
16+
parseValue,
1617
} from '../utils';
1718

1819
const MentionInput: FC<MentionInputProps> = (
1920
{
2021
value,
2122
onChange,
2223

23-
mentionTypes = [],
24+
partTypes = [],
2425

2526
inputRef: propInputRef,
2627

@@ -35,7 +36,10 @@ const MentionInput: FC<MentionInputProps> = (
3536

3637
const [selection, setSelection] = useState({start: 0, end: 0});
3738

38-
const {plainText, parts} = useMemo(() => getPartsFromValue(mentionTypes, value), [value]);
39+
const {
40+
plainText,
41+
parts,
42+
} = useMemo(() => parseValue(value, partTypes), [value, partTypes]);
3943

4044
const handleSelectionChange = (event: NativeSyntheticEvent<TextInputSelectionChangeEventData>) => {
4145
setSelection(event.nativeEvent.selection);
@@ -68,7 +72,7 @@ const MentionInput: FC<MentionInputProps> = (
6872
const keywordByTrigger = useMemo((): { [trigger: string]: string | undefined } => {
6973
const newKeywordByTrigger: { [trigger: string]: string | undefined } = {};
7074

71-
mentionTypes.forEach((mentionType) => {
75+
partTypes.filter(isMentionPartType).forEach((mentionPartType) => {
7276
if (selection.end != selection.start) {
7377
return undefined;
7478
}
@@ -77,7 +81,7 @@ const MentionInput: FC<MentionInputProps> = (
7781
return undefined;
7882
}
7983

80-
const triggerIndex = plainText.lastIndexOf(mentionType.trigger, selection.end);
84+
const triggerIndex = plainText.lastIndexOf(mentionPartType.trigger, selection.end);
8185
const spaceIndex = plainText.lastIndexOf(' ', selection.end - 1);
8286
const newLineIndex = plainText.lastIndexOf('\n', selection.end - 1);
8387

@@ -99,22 +103,22 @@ const MentionInput: FC<MentionInputProps> = (
99103

100104
// When we have a mention just after space
101105
case spaceIndex + 1 === triggerIndex:
102-
newKeywordByTrigger[mentionType.trigger] = plainText.substring(
106+
newKeywordByTrigger[mentionPartType.trigger] = plainText.substring(
103107
triggerIndex + 1,
104108
selection.end,
105109
);
106110
}
107111
});
108112

109113
return newKeywordByTrigger;
110-
}, [parts, plainText, selection, mentionTypes]);
114+
}, [parts, plainText, selection, partTypes]);
111115

112116
/**
113117
* Callback on mention suggestion press. We should:
114118
* - Get updated value
115119
* - Trigger onChange callback with new value
116120
*/
117-
const onSuggestionPress = (mentionType: MentionType) => (suggestion: Suggestion) => {
121+
const onSuggestionPress = (mentionType: MentionPartType) => (suggestion: Suggestion) => {
118122
const newValue = generateValueWithAddedSuggestion(
119123
parts,
120124
mentionType,
@@ -157,8 +161,8 @@ const MentionInput: FC<MentionInputProps> = (
157161

158162
return (
159163
<View style={containerStyle}>
160-
{mentionTypes
161-
.filter(one => one.renderSuggestions != null)
164+
{(partTypes
165+
.filter(one => isMentionPartType(one) && one.renderSuggestions != null) as MentionPartType[])
162166
.map((mentionType) => (
163167
<React.Fragment key={mentionType.trigger}>
164168
{mentionType.renderSuggestions && mentionType.renderSuggestions({
@@ -180,10 +184,10 @@ const MentionInput: FC<MentionInputProps> = (
180184
onSelectionChange={handleSelectionChange}
181185
>
182186
<Text>
183-
{parts.map(({text, data}, index) => data ? (
187+
{parts.map(({text, partType, data}, index) => partType ? (
184188
<Text
185-
key={`${index}-${data.trigger}`}
186-
style={mentionTypes?.find(one => one.trigger === data.trigger)?.textStyle ?? defaultMentionTextStyle}
189+
key={`${index}-${data?.trigger ?? 'pattern'}`}
190+
style={partType.textStyle ?? defaultMentionTextStyle}
187191
>
188192
{text}
189193
</Text>

src/types/types.ts

+26-9
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ type MentionData = {
1414
};
1515

1616
type RegexMatchResult = {
17+
// Matched string
18+
0: string;
19+
1720
// Start position of matched text in whole string
1821
index: number;
1922

@@ -27,18 +30,12 @@ type Position = {
2730
end: number;
2831
};
2932

30-
type Part = {
31-
text: string;
32-
position: Position;
33-
data?: MentionData;
34-
};
35-
3633
type MentionSuggestionsProps = {
3734
keyword: string | undefined;
3835
onSuggestionPress: (suggestion: Suggestion) => void;
3936
};
4037

41-
type MentionType = {
38+
type MentionPartType = {
4239
// single trigger character eg '@' or '#'
4340
trigger: string;
4441

@@ -55,11 +52,29 @@ type MentionType = {
5552
getPlainString?: (mention: MentionData) => string;
5653
};
5754

55+
type PatternPartType = {
56+
// RexExp with global flag
57+
pattern: RegExp;
58+
59+
textStyle?: StyleProp<TextStyle>;
60+
};
61+
62+
type PartType = MentionPartType | PatternPartType;
63+
64+
type Part = {
65+
text: string;
66+
position: Position;
67+
68+
partType?: PartType;
69+
70+
data?: MentionData;
71+
};
72+
5873
type MentionInputProps = Omit<TextInputProps, 'onChange'> & {
5974
value: string;
6075
onChange: (value: string) => any;
6176

62-
mentionTypes?: MentionType[];
77+
partTypes?: PartType[];
6378

6479
inputRef?: Ref<TextInput>;
6580

@@ -73,6 +88,8 @@ export {
7388
Position,
7489
Part,
7590
MentionSuggestionsProps,
76-
MentionType,
91+
MentionPartType,
92+
PatternPartType,
93+
PartType,
7794
MentionInputProps,
7895
};

0 commit comments

Comments
 (0)