My use case was quite specific:
I required a text element that could wrap with varying sizes, and within this text, I needed to emphasize certain words by underlining them (to signify that they are clickable).
The solution seemed simple at first, except for the fact that I couldn't control the underline's appearance in any way (distance from the text, color, etc.). This prompted me to dive deep into finding a solution, which eventually led to the idea of splitting each word, wrapping it in separate Text components, enclosed within View elements.
Here is the simplified code snippet:
import React from 'react';
import { StyleSheet, View, TouchableOpacity, Text } from 'react-native';
import Colors from '../../styles/Colors';
import Fonts from '../../styles/Fonts';
const styles = StyleSheet.create({
container: {
flex: 1,
},
});
export default class SaltText extends React.Component {
getTheme (type) {
if (type === 'robomonoregular10gray') {
return {
fontSize: Fonts.SIZES.TEN,
fontFamily: Fonts.ROBOTOMONO_REGULAR,
color: Colors.getColorOpacity(Colors.GRAY, 70),
lineHeight: Fonts.SIZES.TEN + 10
};
}
throw new Error('not supported');
}
splitText (text) {
const parts = [];
const maps = [];
let currentPart = '';
let matchIndex = 0;
for (const letter of text) {
const isOpening = letter === '[';
const isClosing = letter === ']';
if (!isOpening && !isClosing) {
currentPart += letter;
continue;
}
if (isOpening) {
parts.push(currentPart);
currentPart = '';
}
if (isClosing) {
parts.push(`[${matchIndex}]`);
maps.push(currentPart);
currentPart = '';
matchIndex++;
}
}
const partsModified = [];
for (const part of parts) {
const splitted = part
.split(' ')
.filter(f => f.length);
partsModified.push(...splitted);
}
return { parts: partsModified, maps };
}
render () {
const textProps = this.getTheme(this.props.type);
const children = this.props.children;
const getTextStyle = () => {
return {
...textProps,
};
};
const getTextUnderlineStyle = () => {
return {
...textProps,
borderBottomWidth: 1,
borderColor: textProps.color
};
};
const getViewStyle = () => {
return {
flexDirection: 'row',
flexWrap: 'wrap',
};
};
const { parts, maps } = this.splitText(children);
return (
<View style={getViewStyle()}>
{parts.map((part, index) => {
const key = `${part}_${index}`;
const isLast = parts.length === index + 1;
if (part[0] === '[') {
const mapIndex = part.substring(1, part.length - 1);
const val = maps[mapIndex];
const onPressHandler = () => {
this.props.onPress(parseInt(mapIndex, 10));
};
return (
<View key={key} style={getTextUnderlineStyle()}>
<Text style={getTextStyle()} onPress={() => onPressHandler()}>
{val}{isLast ? '' : ' '}
</Text>
</View>
);
}
return (
<Text key={key} style={getTextStyle()}>
{part}{isLast ? '' : ' '}
</Text>
);
})}
</View>
);
}
}
And here is an example of how you can use this component:
renderPrivacy () {
const openTermsOfService = () => {
Linking.openURL('https://myterms.com');
};
const openPrivacyPolicy = () => {
Linking.openURL('https://mypolicy.com');
};
const onUrlClick = (index) => {
if (index === 0) {
openTermsOfService();
}
if (index === 1) {
openPrivacyPolicy();
}
};
return (
<SaltText type="robomonoregular10gray" onPress={(index) => onUrlClick(index)}>
By tapping "Create an account" or "Continue", I agree to [Terms of Service] and [Privacy Policy]
</SaltText>
);
}
This implementation results in the following output:
https://i.sstatic.net/CWDIb.png