react-native-see-more-inline
Version:
Show a "read more", "see more", "read less", "see less" inline with your text in React Native
174 lines (149 loc) • 4.41 kB
JavaScript
import React from 'react';
import { Text, PanResponder } from 'react-native';
import PropTypes from 'prop-types';
import SeeMoreUtil from './SeeMoreUtil';
class SeeMore extends React.Component {
panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onPanResponderTerminationRequest: () => true,
onPanResponderGrant: () => this.handleLinkPressed(),
onPanResponderTerminate: () => this.handleLinkTerminated(),
onPanResponderRelease: () => this.handleLinkReleased(),
});
/**
* Map of containerWidth and truncationIndex so that we don't calculate it each time
*/
containerWidthToTruncationIndexMap;
constructor(props) {
super(props);
this.state = {
isLinkPressed: false,
isShowingMore: false,
truncationIndex: undefined,
};
}
isExpanded = () => {
const { isShowingMore } = this.state;
return isShowingMore;
};
onLayout = (e) => {
// e.persist() keeps the original synthetic event intact
e.persist();
this.findAndUpdateTruncationIndex(e.nativeEvent.layout.width);
};
findAndUpdateTruncationIndex = async (containerWidth) => {
const truncationIndex = await this.findTruncationIndex(containerWidth);
this.setState({ truncationIndex });
};
findTruncationIndex = async (containerWidth) => {
if (
this.containerWidthToTruncationIndexMap &&
this.containerWidthToTruncationIndexMap[containerWidth]
) {
return this.containerWidthToTruncationIndexMap[containerWidth];
}
const {
children: text,
style: { fontSize, fontFamily, fontWeight },
numberOfLines,
seeMoreText,
} = this.props;
const truncationIndex = await SeeMoreUtil.getTruncationIndex(
text,
numberOfLines,
fontSize,
fontFamily,
fontWeight,
containerWidth,
seeMoreText,
);
this.containerWidthToTruncationIndexMap = {
...this.containerWidthToTruncationIndexMap,
[containerWidth]: truncationIndex,
};
return truncationIndex;
};
collapse() {
return new Promise((resolve) => {
this.setState({ isShowingMore: false }, () => resolve());
});
}
handleLinkPressed() {
this.setState({
isLinkPressed: true,
});
}
handleLinkTerminated() {
this.setState({
isLinkPressed: false,
});
}
handleLinkReleased() {
const { isShowingMore } = this.state;
this.setState({
isLinkPressed: false,
isShowingMore: !isShowingMore,
});
}
renderSeeMoreSeeLessLink() {
const { isLinkPressed, isShowingMore, truncationIndex } = this.state;
const {
children: text,
linkColor,
linkPressedColor,
linkStyle,
seeMoreText,
seeLessText,
} = this.props;
const isTruncable = truncationIndex < text.length;
if (!isTruncable) {
return null;
}
return (
<Text {...this.props} {...this.panResponder.panHandlers}>
{isShowingMore ? null : <Text {...this.props}>...</Text>}
<Text style={[linkStyle, { color: isLinkPressed ? linkPressedColor : linkColor }]}>
{isShowingMore ? ` ${seeLessText}` : ` ${seeMoreText}`}
</Text>
</Text>
);
}
render() {
const { isShowingMore, truncationIndex } = this.state;
const { children: text, numberOfLines } = this.props;
return (
<Text
testID="SeeMore"
onLayout={isShowingMore ? undefined : this.onLayout}
numberOfLines={isShowingMore ? undefined : numberOfLines}
{...this.panResponder.panHandlers}
>
<Text {...this.props}>{isShowingMore ? text : text.slice(0, truncationIndex)}</Text>
{this.renderSeeMoreSeeLessLink()}
</Text>
);
}
}
SeeMore.propTypes = {
children: PropTypes.string.isRequired,
numberOfLines: PropTypes.number.isRequired,
linkColor: PropTypes.string,
linkPressedColor: PropTypes.string,
linkStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
seeMoreText: PropTypes.string,
seeLessText: PropTypes.string,
style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
};
SeeMore.defaultProps = {
linkColor: '#2E75F0',
linkPressedColor: '#163772',
seeMoreText: 'see more',
seeLessText: 'see less',
style: {
fontFamily: undefined,
fontSize: 14,
fontWeight: '300',
},
linkStyle: undefined,
};
export default SeeMore;