Skip to content

Commit 77c924d

Browse files
authored
Feat/Text highlight (#1514)
* update highlight method * update text highlight tests * update highlight method + tests * fix parts array * update getArrayPartsByHighlight
1 parent b055d37 commit 77c924d

File tree

4 files changed

+120
-56
lines changed

4 files changed

+120
-56
lines changed

demo/src/screens/componentScreens/TextScreen.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ class TextScreen extends Component {
8080
<Text text50R highlightString={'danc'} highlightStyle={{fontWeight: '200', color: Colors.grey20}}>
8181
Dancing in The Dark
8282
</Text>
83+
<Text text60R highlightString={['dancing', 'da']} highlightStyle={{color: Colors.green30}}>
84+
Dancing in The Dark
85+
</Text>
8386
</View>
8487
{this.renderDivider()}
8588
<View padding-20 centerH>

generatedTypes/src/components/text/index.d.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export declare type TextProps = RNTextProps & TypographyModifiers & ColorsModifi
1717
/**
1818
* Substring to highlight
1919
*/
20-
highlightString?: string;
20+
highlightString?: string | string[];
2121
/**
2222
* Custom highlight style for highlight string
2323
*/
@@ -42,7 +42,18 @@ declare type PropsTypes = BaseComponentInjectedProps & ForwardRefInjectedProps &
4242
declare class Text extends PureComponent<PropsTypes> {
4343
static displayName: string;
4444
private TextContainer;
45-
getTextPartsByHighlight(targetString?: string, highlightString?: string): string[];
45+
getPartsByHighlight(targetString: string | undefined, highlightString: string | string[]): {
46+
string: string;
47+
shouldHighlight: boolean;
48+
}[];
49+
getTextPartsByHighlight(targetString?: string, highlightString?: string): {
50+
string: string;
51+
shouldHighlight: boolean;
52+
}[];
53+
getArrayPartsByHighlight(targetString?: string, highlightString?: string[]): {
54+
string: string;
55+
shouldHighlight: boolean;
56+
}[];
4657
renderText(children: any): any;
4758
render(): JSX.Element;
4859
}

src/components/text/__tests__/index.spec.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,53 +6,53 @@ describe('Text', () => {
66
it('should return the whole string as a single part when highlight string is empty', () => {
77
const uut = new Text({});
88
const result = uut.getTextPartsByHighlight('Playground Screen', '');
9-
expect(result).toEqual(['Playground Screen']);
9+
expect(result).toEqual([{string: 'Playground Screen', shouldHighlight: false}]);
1010
});
1111
it('should return the whole string as a single part when highlight string dont match', () => {
1212
const uut = new Text({});
1313
const result = uut.getTextPartsByHighlight('Playground Screen', 'aaa');
14-
expect(result).toEqual(['Playground Screen']);
14+
expect(result).toEqual([{string: 'Playground Screen', shouldHighlight: false}]);
1515
});
1616
it('should break text to parts according to highlight string', () => {
1717
const uut = new Text({});
1818
const result = uut.getTextPartsByHighlight('Playground Screen', 'Scr');
19-
expect(result).toEqual(['Playground ', 'Scr', 'een']);
19+
expect(result).toEqual([{string: 'Playground ', shouldHighlight: false}, {string: 'Scr', shouldHighlight: true}, {string: 'een', shouldHighlight: false}]);
2020
});
2121

2222
it('should handle case when highlight repeats more than once', () => {
2323
const uut = new Text({});
2424
const result = uut.getTextPartsByHighlight('Dancing in the Dark', 'Da');
25-
expect(result).toEqual(['Da', 'ncing in the ', 'Da', 'rk']);
25+
expect(result).toEqual([{string: 'Da', shouldHighlight: true}, {string: 'ncing in the ', shouldHighlight: false}, {string: 'Da', shouldHighlight: true}, {string: 'rk', shouldHighlight: false}]);
2626
});
2727

2828
it('should handle ignore case-sensetive', () => {
2929
const uut = new Text({});
3030
const result = uut.getTextPartsByHighlight('Dancing in the Dark', 'da');
31-
expect(result).toEqual(['Da', 'ncing in the ', 'Da', 'rk']);
31+
expect(result).toEqual([{string: 'Da', shouldHighlight: true}, {string: 'ncing in the ', shouldHighlight: false}, {string: 'Da', shouldHighlight: true}, {string: 'rk', shouldHighlight: false}]);
3232
});
3333

3434
it('Should handle special characters @', () => {
3535
const uut = new Text({});
3636
const result = uut.getTextPartsByHighlight('@ancing in the @ark', '@a');
37-
expect(result).toEqual(['@a', 'ncing in the ', '@a', 'rk']);
37+
expect(result).toEqual([{string: '@a', shouldHighlight: true}, {string: 'ncing in the ', shouldHighlight: false}, {string: '@a', shouldHighlight: true}, {string: 'rk', shouldHighlight: false}]);
3838
});
3939

4040
it('Should handle special characters !', () => {
4141
const uut = new Text({});
4242
const result = uut.getTextPartsByHighlight('!ancing in the !ark', '!a');
43-
expect(result).toEqual(['!a', 'ncing in the ', '!a', 'rk']);
43+
expect(result).toEqual([{string: '!a', shouldHighlight: true}, {string: 'ncing in the ', shouldHighlight: false}, {string: '!a', shouldHighlight: true}, {string: 'rk', shouldHighlight: false}]);
4444
});
4545

4646
it('Should handle special characters starts with @', () => {
4747
const uut = new Text({});
4848
const result = uut.getTextPartsByHighlight('[email protected]', '@wix');
49-
expect(result).toEqual(['uilib', '@wix', '.com']);
49+
expect(result).toEqual([{string: 'uilib', shouldHighlight: false}, {string: '@wix', shouldHighlight: true}, {string: '.com', shouldHighlight: false}]);
5050
});
5151

5252
it('Should handle empty string .', () => {
5353
const uut = new Text({});
5454
const result = uut.getTextPartsByHighlight('@ancing in the @ark', '');
55-
expect(result).toEqual(['@ancing in the @ark']);
55+
expect(result).toEqual([{string: '@ancing in the @ark', shouldHighlight: false}]);
5656
});
5757
});
5858
});

src/components/text/index.tsx

Lines changed: 95 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -12,34 +12,37 @@ import {
1212
} from '../../commons/new';
1313
import {Colors} from 'style';
1414

15-
export type TextProps = RNTextProps & TypographyModifiers & ColorsModifiers & MarginModifiers & {
16-
/**
17-
* color of the text
18-
*/
19-
color?: string;
20-
/**
21-
* whether to center the text (using textAlign)
22-
*/
23-
center?: boolean;
24-
/**
25-
* whether to change the text to uppercase
26-
*/
27-
uppercase?: boolean;
28-
/**
29-
* Substring to highlight
30-
*/
31-
highlightString?: string;
32-
/**
33-
* Custom highlight style for highlight string
34-
*/
35-
highlightStyle?: TextStyle;
36-
/**
37-
* Use Animated.Text as a container
38-
*/
39-
animated?: boolean;
40-
textAlign?: string;
41-
style?: StyleProp<TextStyle | Animated.AnimatedProps<TextStyle>>;
42-
}
15+
export type TextProps = RNTextProps &
16+
TypographyModifiers &
17+
ColorsModifiers &
18+
MarginModifiers & {
19+
/**
20+
* color of the text
21+
*/
22+
color?: string;
23+
/**
24+
* whether to center the text (using textAlign)
25+
*/
26+
center?: boolean;
27+
/**
28+
* whether to change the text to uppercase
29+
*/
30+
uppercase?: boolean;
31+
/**
32+
* Substring to highlight
33+
*/
34+
highlightString?: string | string[];
35+
/**
36+
* Custom highlight style for highlight string
37+
*/
38+
highlightStyle?: TextStyle;
39+
/**
40+
* Use Animated.Text as a container
41+
*/
42+
animated?: boolean;
43+
textAlign?: string;
44+
style?: StyleProp<TextStyle | Animated.AnimatedProps<TextStyle>>;
45+
};
4346
export type TextPropTypes = TextProps; //TODO: remove after ComponentPropTypes deprecation;
4447

4548
type PropsTypes = BaseComponentInjectedProps & ForwardRefInjectedProps & TextProps;
@@ -62,50 +65,97 @@ class Text extends PureComponent<PropsTypes> {
6265
// this._root.setNativeProps(nativeProps); // eslint-disable-line
6366
// }
6467

65-
getTextPartsByHighlight(targetString = '', highlightString = '') {
66-
if (_.isEmpty(highlightString.trim())) {
67-
return [targetString];
68+
getPartsByHighlight(targetString = '', highlightString: string | string[]) {
69+
if (typeof highlightString === 'string') {
70+
if (_.isEmpty(highlightString.trim())) {
71+
return [{string: targetString, shouldHighlight: false}];
72+
}
73+
return this.getTextPartsByHighlight(targetString, highlightString);
74+
} else {
75+
return this.getArrayPartsByHighlight(targetString, highlightString);
6876
}
77+
}
6978

79+
getTextPartsByHighlight(targetString = '', highlightString = '') {
80+
if (highlightString === '') {
81+
return [{string: targetString, shouldHighlight: false}];
82+
}
7083
const textParts = [];
7184
let highlightIndex;
72-
7385
do {
7486
highlightIndex = targetString.toLowerCase().indexOf(highlightString.toLowerCase());
7587
if (highlightIndex !== -1) {
7688
if (highlightIndex > 0) {
77-
textParts.push(targetString.substring(0, highlightIndex));
89+
textParts.push({string: targetString.substring(0, highlightIndex), shouldHighlight: false});
7890
}
79-
textParts.push(targetString.substr(highlightIndex, highlightString.length));
91+
textParts.push({string: targetString.substr(highlightIndex, highlightString.length), shouldHighlight: true});
8092
targetString = targetString.substr(highlightIndex + highlightString.length);
8193
} else {
82-
textParts.push(targetString);
94+
textParts.push({string: targetString, shouldHighlight: false});
8395
}
8496
} while (highlightIndex !== -1);
8597

8698
return textParts;
8799
}
88100

101+
getArrayPartsByHighlight(targetString = '', highlightString = ['']) {
102+
const target = _.toLower(targetString);
103+
const indices = [];
104+
let index = 0;
105+
let lastWordLength = 0;
106+
for (let j = 0; j < highlightString.length; j++) {
107+
const word = _.toLower(highlightString[j]);
108+
const targetSuffix = target.substring(index + lastWordLength);
109+
const i = targetSuffix.indexOf(word);
110+
if (i >= 0) {
111+
const newIndex = index + lastWordLength + i;
112+
indices.push({start: index + lastWordLength + i, end: index + lastWordLength + i + word.length});
113+
index = newIndex;
114+
lastWordLength = word.length;
115+
} else {
116+
break;
117+
}
118+
}
119+
const parts = [];
120+
for (let k = 0; k < indices.length; k++) {
121+
if (k === 0 && indices[k].start !== 0) {
122+
parts.push({string: targetString.substring(0, indices[k].start), shouldHighlight: false});
123+
}
124+
parts.push({string: targetString.substring(indices[k].start, indices[k].end), shouldHighlight: true});
125+
if (k === indices.length - 1) {
126+
parts.push({string: targetString.substring(indices[k].end), shouldHighlight: false});
127+
} else {
128+
parts.push({string: targetString.substring(indices[k].end, indices[k + 1].start), shouldHighlight: false});
129+
}
130+
}
131+
return parts;
132+
}
133+
89134
renderText(children: any): any {
90135
const {highlightString, highlightStyle} = this.props;
91136

92137
if (!_.isEmpty(highlightString)) {
93138
if (_.isArray(children)) {
94-
return _.map(children, (child) => {
139+
return _.map(children, child => {
95140
return this.renderText(child);
96141
});
97142
}
98143

99144
if (_.isString(children)) {
100-
const textParts = this.getTextPartsByHighlight(children, highlightString);
101-
return _.map(textParts, (text, index) => {
102-
const shouldHighlight = _.lowerCase(text) === _.lowerCase(highlightString);
103-
return (
104-
<RNText key={index} style={shouldHighlight ? [styles.highlight, highlightStyle] : styles.notHighlight}>
105-
{text}
106-
</RNText>
107-
);
108-
});
145+
const textParts = highlightString && this.getPartsByHighlight(children, highlightString);
146+
return (
147+
textParts &&
148+
_.map(textParts, (text, index) => {
149+
return (
150+
<RNText
151+
key={index}
152+
style={text.shouldHighlight ? [styles.highlight, highlightStyle] : styles.notHighlight}
153+
>
154+
{text.string}
155+
</RNText>
156+
);
157+
})
158+
);
109159
}
110160
}
111161
return children;

0 commit comments

Comments
 (0)