react-native customize text elipsis to add "More" after elipsis - react-native

Following is from instagram which is known for using react-native
As you can see there is a text (더보기, which means More) after the elipsis (...)
How can I simulate that too?

Well, that is one of those things that on the surface looks easy enough, but actually is a complicated endeavor.
Due to this thing over here: https://github.com/facebook/react-native/issues/22811 you won't be able to nest the <Text> tag and use the numberOfLines prop to achieve what you want.
In the past I've tried to deal with something similar: React Native chat bubble flexbox similar to whatsapp
Back then there was no onTextLayout function(or at least I didn't know about it). The onTextLayout callback on the Text component gives you a line prop. The line is an array that contains information about every line of text.
You could use this to place the more button where you want.
const MoreInfo = text => {
const [moreLeft, setMoreLeft] = React.useState(0)
const [moreTop, setMoreTop] = React.useState(0)
return (
<View style={{ marginTop: 20 }}>
<Text
numberOfLines={2}
ellipsizeMode={"tail"}
onTextLayout={({ nativeEvent: { lines } }) => {
const width = lines[lines.length - 1].width
const height = lines[lines.length - 1].y
setMoreTop(height)
setMoreLeft(width)
}}
>
{text}
</Text>
<Text
style={{
backgroundColor: "white",
position: "absolute",
left: moreLeft - 30,
top: moreTop,
}}
>
... More
</Text>
</View>
)
}
Obviously the above implementation is not perfect. As you can see I just fake the ... by placing them in the More button. Sometimes it looks good, othertimes it cuts off the last letter in the string in the middle. But it serves to show the direction you need to go to solve this.
Some ideas:
inspect the lines array - if it has 2 lines is the last line width smaller than the first line width. Will your "More" text fit in the width difference? If yes, then just set the left position to the width.
if the second line is as long as the first one, then the ellepsis is at the end and you'll need to resort to the "... More" trick.

Here is a quick workaround. Create a function which takes text as an argument and show truncated text with a more button. Upon click, we will render full text with a less button.
const MoreLessComponent = ({ truncatedText, fullText }) => {
const [more, setMore] = React.useState(false);
return (
<Text>
{!more ? `${truncatedText}...` : fullText}
<TouchableOpacity onPress={() => setMore(!more)}>
<Text>{more ? 'less' : 'more'}</Text>
</TouchableOpacity>
</Text>
);
};
const MoreInfo = (text, linesToTruncate) => {
const [clippedText, setClippedText] = React.useState(false);
return clippedText ? (
<MoreLessComponent truncatedText={clippedText} fullText={text} />
) : (
<Text
numberOfLines={linesToTruncate}
ellipsizeMode={'tail'}
onTextLayout={(event) => {
//get all lines
const { lines } = event.nativeEvent;
//get lines after it truncate
let text = lines
.splice(0, linesToTruncate)
.map((line) => line.text)
.join('');
//substring with some random digit, this might need more work here based on the font size
//
setClippedText(text.substr(0, text.length - 9));
}}>
{text}
</Text>
);
};
Now call it like this
<View>
{MoreInfo('Change code in the editor and watch it change on your phone! Save to get a shareable url.')}
</View>
Here is the snack https://snack.expo.io/#saachitech/react-native-more-less

Related

Vertically align Pressable inside a Text component

<View
style={{
flexDirection: "row",
}}
>
<Text
style={{
flex: 1,
}}
>
By continuing, you agree to our{" "}
<Pressable
onPress={...}
>
<Text>
Terms of Service
</Text>
</Pressable>
</Text>
</View>
"Terms of Service" is printed higher than "By continuing, you agree to our". How do I vertically align them?
Or more precisely - how do I get the Pressable Text to vertically align to the bottom?
This is a bug in React Native itself. There are several open reports of this bug on React Native's GitHub, but the chances of it being fixed don't look good:
https://github.com/facebook/react-native/issues/30375 - for the general problem of Views nested in Text being mis-aligned on Android, and a core contributor responded, but appeared to get derailed and stuck in some details specific to mimicking superscript and subscript.
https://github.com/facebook/react-native/issues/31955 - specific to Pressable and someone posted a PR to fix it, but Facebook closed it because no-one from Facebook got around to reviewing it before it became stale and out of date with the main branch.
There's also some discussion in this issue comment, but the issue got closed.
In React Native >= 0.65, if your inline pressable element uses only text styles, you can work around this issue by using <Text> with onPress (and onPressIn and onPressOut to style the pressed state). Crude example:
/**
* Like a simplified Pressable that doesn't look broken inline in `Text` on Android
*/
const TextButton = ({ children, onPress, style, ...rest } => {
const [pressed, setPressed] = useState(false)
const onPressIn = () => setPressed(true)
const onPressOut = () => setPressed(false)
return (
<Text
onPress={onPress}
onPressIn={onPressIn}
onPressOut={onPressOut}
style={typeof style === 'function' ? style({ pressed }) : style}
{...rest}
>
{typeof children === 'function' ? children({ pressed }) : children}
</Text>
)
}
Beware, however, that there are also bugs around selecting interactive elements nested inside text using accessibility tools. If you can simply avoid nesting the interactive element in text, and have it on its own line, that's probably better: link-like interactive nested text isn't well supported in React Native currently.
On older versions of React Native before 0.65, Text didn't support onPressIn or onPressOut:
If you don't need Pressable's pressed state, use Text instead of Pressable (as the asker did: https://stackoverflow.com/a/66590787/568458)
If you do need pressed state, Text doesn't support the required onPressIn/Out handlers. However, TouchableWithoutFeedback does support those, and works by injecting props into its child so the Text will remain Text with no wrapping View. Wrap a Text in TouchableWithoutFeedback and pass the touchable onPress with onPressIn and onPressOut handlers and store the pressed state yourself.
/**
* Like a simplified Pressable that doesn't look broken inline in `Text` on Android
*/
const TextButton = ({ children, onPress, style, ...rest } => {
const [pressed, setPressed] = useState(false)
const onPressIn = () => setPressed(true)
const onPressOut = () => setPressed(false)
// TouchableWithoutFeedback modifies and returns its child; this returns `Text`
// plus press in/out events attached that aren't supported by Text directly.
return (
<TouchableWithoutFeedback
onPress={onPress}
onPressIn={onPressIn}
onPressOut={onPressOut}
>
<Text
style={typeof style === 'function' ? style({ pressed }) : style}
{...rest}
>
{typeof children === 'function' ? children({ pressed }) : children}
</Text>
</TouchableWithoutFeedback>
)
}
Warning: if you're also using React Native Web and React Navigation, don't use the TouchableWithoutFeedback approach on Web, use pure Pressable on web, because React Navigation's navigate functions don't reliably work when passed to Touchable*s on Web due to a quirk of how the event handlers are set up (but they do work in Pressable), and this issue doesn't exist on Web.
Ended up doing this differently, using the onPress property of the <Text> component and finally wrapping all <Text> components in another <Text> component to have a proper line break:
<View>
<Text>
<Text>
By continuing, you agree to our{" "}
</Text>
<Text onPress={() => {...}}>
Terms of Service
</Text>
<Text>
{" "}and our{" "}
</Text>
<Text onPress={() => {...}}>
Privacy Policy
</Text>
</Text>
</View>
The snippet below should work but hard to understand without giving a shot. If you can provide screenshots I can help more in sake of getting a more proper result.
<View>
<Text style={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center'
}}>
By continuing, you agree to our{" "}
<Pressable
onPress={() => {
navigate("LegalStack", { screen: "Terms" });
}}
>
<Text style={{margin: 'auto'}}>
Terms of Service
</Text>
</Pressable>
</Text>
</View>
I found a very hackidy-hack solution...
<Text selectable={true}>
<Text>if you click</Text>
<TouchableOpacity
style={{flexDirection: 'row'}}
onPress={() => Linking.openURL("https://www.google.com")}
>
<Text
style={{
alignSelf: 'flex-end',
marginBottom: -4,
}}
>
here
</Text>
</TouchableOpacity>
<Text>, it will open google</Text>
</Text>
By default the flexDirection is column. Change it to flexDirection:"row"

react native textInput, change position of cursor on press

Right now, in my react native app, I'm not able to change the intermediate value of every input. Because everytime that i presso on a text input, the caret is at the end of the inserted string.
For example:
Value: 123456 ----> I want to delete/change only 23, but i cannot because the caret is over the last value.
i tried to use selectTextOnFocus but it will fired only when i click on other text input, and it doesn't work if the data is pre-filled. I try to explain better
in the image down below I jumped from the first text Mobile Number to the second id RICE.
In the image down below I entered the new values 12321 but i if press a second time on the same input, the text will not be selected. I tried to click and stay for like 10 secs, but nothing.
I'm using xcode to make the build of the app.
this is my input component
const InputField: React.FC<Props> = (
{
placeholder,
iconLeft,
iconRight,
dateTimeIcon,
iconBackground,
type,
id,
style,
name,
value,
keyboardType,
iconRightFunction,
maxLength = 100,
inputBackground,
onChangeText,
onChange,
editable,
textAlign,
setMarginBottom,
onFocus,
multiline,
numberOfLines,
label,
error,
errorLabel,
containerHeight,
onBlur,
},
ref,
) => {
const [hidePassword, showOrHidePassword] = useState(true);
const _handleShowPassword = () => {
showOrHidePassword(!hidePassword);
};
const _handleOnChange = ({ nativeEvent }) => {
onChange && onChange(name, nativeEvent.text);
};
const _handleOnBlur = () => {
onBlur && onBlur(name, value);
};
const input = useRef(null);
const showPassword = type === 'password' && !error && value.length !== 0;
return (
<>
{label && (
<StyledLabelContainer setMargin={setMarginBottom}>
<StyledLabel error={error}>{label}</StyledLabel>
{error && <StyledLabel error={error}>{errorLabel}</StyledLabel>}
</StyledLabelContainer>
)}
<StyledContainer containerHeight={containerHeight} setMargin={setMarginBottom}>
{iconLeft && <StyledIconLeft bgColor={iconBackground}>{iconLeft}</StyledIconLeft>}
<TouchableOpacity
activeOpacity={0}
onPress={() => {
console.log('inputcurrent', input, input.current);
input.current.focus();
}}
style={{ flex: 1 }}
>
<View pointerEvents="none" style={{ flex: 1 }}>
<StyledInput
style={style}
ref={input}
textAlign={textAlign}
maxLength={maxLength}
editable
selectTextOnFocus
clearTextOnFocus={false}
secureTextEntry={type === 'password' && hidePassword}
placeholder={placeholder}
type={type}
autoCapitalize="none"
onChangeText={onChangeText}
onChange={_handleOnChange}
value={value}
id={id}
name={name}
bgColor={inputBackground}
keyboardType={keyboardType}
blurOnSubmit={true}
onFocus={onFocus}
returnKeyType={'done'}
onBlur={_handleOnBlur}
error={error}
multiline={multiline}
numberOfLines={numberOfLines}
/>
</View>
</TouchableOpacity>
Question
How can i set the right position of cursor during a press action ?
Ex: 123456 ----> user press in the middle of the string, i expect to see the caret between the numbers 3 and 4.
My tries
I read about selection but i didn't succeed to implement it, every kind of advices will be very very welcome.
Thanks for the time.
You can use the selection prop to position the cursor where you want it to be. See docs: https://reactnative.dev/docs/textinput.html#selection
You can create a view that looks like a text input but is instead a group of small buttons, one for each character. When the user clicks one of these buttons, you know where they want the caret (cursor) to be. Then you render the real TextInput and use the selection prop to move the caret to the correct position.

How can I display 30 pages of text in a (scrolling) screen

I want to display 30 pages of text on a screen. I've tried ScrollView and FlatList but I get a white screen. Only when I try with ScrollView to display only 2 pages, works fine.
I do not want to use a WebView, because I would like to have all data in the app (no internet connection needed).
Here is what I've already tried:
With FlatList:
I have a text.js as a model, which I use to create a Text Object in an array, which I then use as data for the FlatList. For the renderItem function (of FlatList) I use a TextItem to display the text.
text.js
function Text(info) {
this.id = info.id;
this.text = info.text;
}
export default Text;
LongTextModule.js
import Text from '../../models/text';
export const LONGTEXT = [
new Text({
id:'text_1',
text:`.....longtext....`
})
]
TextItem.js
const TextItem = (props) => {
return (
<View style={styles.screen} >
<Text style={styles.textStyle}>{props.longText}</Text>
</View >
);
};
const styles = StyleSheet.create({
screen: {
flex: 1,
},
textStyle: {
justifyContent: 'flex-start',
alignItems: 'flex-start',
fontFamily: 'GFSNeohellenic-Regular',
fontSize: 20,
padding: 10,
}
});
TextDetailScreen.js
const TextDetailScreen = (props) => {
const renderText = data => {
return <TextItem longText={data.item.text} />
}
return <FlatList
data={LONGTEXT}
keyExtractor={(item, index) => item.id}
renderItem={renderText}
/>
};
I think it's needless to show the code with ScrollView, since ScrollView is only for a small list.
I even tried to render the longText like this in the screen.
Without the ScrollView I get the first portion, but with ScrollView a white screen.
const TextDetailScreen = (props) => {
return (
<ScrollView>
<Text> ...longText...</Text>
</ScrollView>
);
};
I'm sure there is a way to display a lot of pages of text on a screen?
But how?
Thank you :)
It seems not to be an unknown Issue, I've also read from time to time about this issue.
But not to use Webview, because you wan't to have all Data in your app - don't have to be an Argument against Webview. With WebView, you also can display Data from your App-Storage.
Example:
<WebView style={styles.myStyle} source={{html: `<p style="font-size:48px">${longtext}</p>`}} />

What is the best practice to align a header a button, text and right component in UI using react-native

I need to align the Back button and Text and Right element in my header section.
I am using react-native-elements component to align but the styles Which I gave not worked for all text. If the text is small it not working as expected.Also If I try line spacing for the text LIST it not shows space in it. Refer the expo for sample code(here I not added line spacing) and refer the attachment.
https://snack.expo.io/#niph/grounded-watermelon
My question what is the best practice to achieve this. I have added marginLeft values as negative.
You have put your title name under the left button in the header. This eliminates the need to use centerComponent.
And you have to use outerContainerStyles to change the height of the header. Then, adjust the area using flex values and use alignSelf to reposition only text.
Usage If you want to experience this, look at my example.
export default class App extends React.Component {
render() {
return ( <Header
outerContainerStyles={{paddingTop: 0, height:130}}
backgroundColor='green'
leftComponent = {
<View style={{flexDirection:"row",flex:1,marginTop:20}}>
<TouchableOpacity activeOpacity = { .5 }>
<Entypo name = {'chevron-thin-left'} size ={30} style = {{ color:'white'}}></Entypo>
</TouchableOpacity>
<Text style={{fontSize:30, color:'white',alignSelf:"flex-end"}}>NAMES</Text>
</View>
}
// centerComponent = {<Text style={{fontSize:30, color:'white', flexDirection:'row'}}>NAMES</Text>}
rightComponent = {<TouchableOpacity activeOpacity = { .5 } style={{flex:1,marginTop:20}}>
<Text style={{ fontSize:14, color:'white',textDecorationLine:'underline'}}>LIST</Text>
</TouchableOpacity>}
/>
);
}
}
Enabled screen

Separator style for header and body in FlatList

Currently, I'm having a problem with FlatList.
I have a component to render a list book.
By design, the header's width is the width of the screen and the body will be padding left and right 10px.
So I used contentContainerStyle={{paddingHorizontal: 10}}.
But The result is that the header and body are 10px padding left and right.
Please suggest a way to resolve it. Sorry for my bad English!!
Update: I'm so sorry for not describing my problem thoroughly.
In main.tsx
...
public render() {
return (
<FlatList
key...
data={..}
renderItem={this.renderItems}
ListHeaderComponent={this.renderHeader}
contentContainerStyle={styles.contentStyle}
/>
);
}
private renderHeader = () => {
return (
<View style={style.header}
//TODO something ...
</View>
);
}
private renderItems: ListRenderItem<IBook> = ({ item: {bookId} }) => bookId ?
(
<BookGridCell
title={...}
image={...}
//TODO more..
/>
) : <View style={styles.emptyBox} />
}
At renderItems, I called a component BookGridCell. In this component, the design of a book is set up. So if I directly add style inside renderItems, each book will have a left and right margin of 10px, not the entire body.
When use contentContainerStyle
with contenContainerStyle
When directly add style inside renderItems
with not use contentContainerStyle
Give a style to your body.
style={styles.bodyContainer}
and then inside StyleSheet add property.
const styles = StyleSheet.create({
bodyContainer: {
paddingHorizontal: 10
},
This is the correct way or
you can directly add padding inside your View.
style={{ paddingHorizontal: 10 }}