Files
react-native/packages/rn-tester/js/examples/Text/TextExample.ios.js
Dawid 08e8f6adfd fix(Android) drawing additional empty line when 'textAlign' is set to 'justify' (#47122)
Summary:
Fixes https://github.com/facebook/react-native/issues/46908

The `justificationMode` is not set for multiline text without unicode characters with known width on both architectures. This caused the issue of drawing additional empty line at the end of `TextView` because Yoga thought that text takes 5 lines and falsely calculated it's height.

Currently, on the old architecture, the `justificationMode` is set only on text that is not boring (contains unicode characters) with unknown width. I am not sure why is that, so I am opening this as a draft for now as I am still checking if it doesn't break anything.

## Changelog:

<!-- Help reviewers and the release process by writing your own changelog entry.

Pick one each for the category and type tags:

[ANDROID|GENERAL|IOS|INTERNAL] [BREAKING|ADDED|CHANGED|DEPRECATED|REMOVED|FIXED|SECURITY] - Message

For more details, see:
https://reactnative.dev/contributing/changelogs-in-pull-requests
-->

[ANDROID] [FIXED] - fix generating empty line at the end of multiline text view when `textAlign` is set to `justify`

Pull Request resolved: https://github.com/facebook/react-native/pull/47122

Test Plan: I've tested on both architectures on repro provided in the issue.

Reviewed By: javache

Differential Revision: D65002386

Pulled By: NickGerleman

fbshipit-source-id: 0187956c88e6eb1e637c24e82b3052cc82581a64
2024-10-29 14:22:59 -07:00

1464 lines
42 KiB
JavaScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
'use strict';
import type {RNTesterModule} from '../../types/RNTesterTypes';
import TextLegend from '../../components/TextLegend';
import TextInlineViewsExample from './TextInlineViewsExample';
const TextInlineView = require('../../components/TextInlineView');
const React = require('react');
const {
Button,
LayoutAnimation,
Platform,
Text,
TextInput,
View,
} = require('react-native');
const TextAncestor = require('react-native/Libraries/Text/TextAncestor');
// TODO: Is there a cleaner way to flip the TextAncestor value to false? I
// suspect apps won't even be able to leverage this workaround because
// TextAncestor is not public.
/* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's
* LTI update could not be added via codemod */
function InlineView(props) {
return (
<TextAncestor.Provider value={false}>
<View {...props} />
</TextAncestor.Provider>
);
}
type TextAlignExampleRTLState = {|
isRTL: boolean,
|};
class TextAlignRTLExample extends React.Component<
{},
TextAlignExampleRTLState,
> {
constructor(...args: Array<any>) {
super(...args);
this.state = {
isRTL: false,
};
}
render(): React.Node {
const {isRTL} = this.state;
const toggleRTL = () => this.setState({isRTL: !isRTL});
return (
<View
style={{direction: isRTL ? 'rtl' : 'ltr'}}
testID="text-align-example">
<Text>auto (default) - english LTR</Text>
<Text>
{'\u0623\u062D\u0628 \u0627\u0644\u0644\u063A\u0629 ' +
'\u0627\u0644\u0639\u0631\u0628\u064A\u0629 auto (default) - arabic RTL'}
</Text>
<Text style={{textAlign: 'left'}}>
left left left left left left left left left left left left left left
left
</Text>
<Text style={{textAlign: 'center'}}>
center center center center center center center center center center
center
</Text>
<Text style={{textAlign: 'right'}}>
right right right right right right right right right right right
right right
</Text>
<Text style={{textAlign: 'justify'}}>
justify: this text component{"'"}s contents are laid out with
"textAlign: justify" and as you can see all of the lines except the
last one span the available width of the parent container.
</Text>
<Button
onPress={toggleRTL}
title={`Switch to ${isRTL ? 'LTR' : 'RTL'}`}
/>
</View>
);
}
}
class Entity extends React.Component<$FlowFixMeProps> {
render(): React.Node {
return (
<Text style={{fontWeight: '500', color: '#527fe4'}}>
{this.props.children}
</Text>
);
}
}
class AttributeToggler extends React.Component<{...}, $FlowFixMeState> {
state: any | {fontSize: number, fontWeight: string} = {
fontWeight: 'bold',
fontSize: 15,
};
toggleWeight = () => {
this.setState({
fontWeight: this.state.fontWeight === 'bold' ? 'normal' : 'bold',
});
};
increaseSize = () => {
this.setState({
fontSize: this.state.fontSize + 1,
});
};
render(): React.Node {
const curStyle = {
fontWeight: this.state.fontWeight,
fontSize: this.state.fontSize,
};
return (
<View>
{/* $FlowFixMe[incompatible-type] */}
<Text style={curStyle}>
Tap the controls below to change attributes.
</Text>
<Text>
<Text>
See how it will even work on {/* $FlowFixMe[incompatible-type] */}
<Text style={curStyle}>this nested text</Text>
</Text>
</Text>
<Text
style={{backgroundColor: '#ffaaaa', marginTop: 5}}
onPress={this.toggleWeight}>
Toggle Weight
</Text>
<Text
style={{backgroundColor: '#aaaaff', marginTop: 5}}
onPress={this.increaseSize}>
Increase Size
</Text>
</View>
);
}
}
type AdjustingFontSizeProps = $ReadOnly<{||}>;
type AdjustingFontSizeState = {|
dynamicText: string,
shouldRender: boolean,
|};
class AdjustingFontSize extends React.Component<
AdjustingFontSizeProps,
AdjustingFontSizeState,
> {
state: AdjustingFontSizeState = {
dynamicText: '',
shouldRender: true,
};
reset = () => {
LayoutAnimation.easeInEaseOut();
this.setState({
shouldRender: false,
});
setTimeout(() => {
LayoutAnimation.easeInEaseOut();
this.setState({
dynamicText: '',
shouldRender: true,
});
}, 300);
};
addText = () => {
this.setState({
dynamicText:
this.state.dynamicText +
(Math.floor((Math.random() * 10) % 2) ? ' foo' : ' bar'),
});
};
removeText = () => {
this.setState({
dynamicText: this.state.dynamicText.slice(
0,
this.state.dynamicText.length - 4,
),
});
};
render(): React.Node {
if (!this.state.shouldRender) {
return <View />;
}
return (
<View>
<Text
ellipsizeMode="tail"
numberOfLines={1}
style={{fontSize: 36, marginVertical: 6}}>
Truncated text is baaaaad.
</Text>
<Text
numberOfLines={1}
adjustsFontSizeToFit={true}
style={{fontSize: 40, marginVertical: 6}}>
Shrinking to fit available space is much better!
</Text>
<Text
adjustsFontSizeToFit={true}
numberOfLines={1}
style={{fontSize: 30, marginVertical: 6}}>
{'Add text to me to watch me shrink!' + ' ' + this.state.dynamicText}
</Text>
<Text
adjustsFontSizeToFit={true}
numberOfLines={4}
style={{fontSize: 20, marginVertical: 6}}>
{'Multiline text component shrinking is supported, watch as this reeeeaaaally loooooong teeeeeeext grooooows and then shriiiinks as you add text to me! ioahsdia soady auydoa aoisyd aosdy ' +
' ' +
this.state.dynamicText}
</Text>
<Text
adjustsFontSizeToFit={true}
style={{fontSize: 20, marginVertical: 6, maxHeight: 50}}>
{'Text limited by height, watch as this reeeeaaaally loooooong teeeeeeext grooooows and then shriiiinks as you add text to me! ioahsdia soady auydoa aoisyd aosdy ' +
' ' +
this.state.dynamicText}
</Text>
<Text
adjustsFontSizeToFit={true}
numberOfLines={1}
style={{marginVertical: 6}}>
<Text style={{fontSize: 14}}>
{'Differently sized nested elements will shrink together. '}
</Text>
<Text style={{fontSize: 20}}>
{'LARGE TEXT! ' + this.state.dynamicText}
</Text>
</Text>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-around',
marginTop: 5,
marginVertical: 6,
}}>
<Text style={{backgroundColor: '#ffaaaa'}} onPress={this.reset}>
Reset
</Text>
<Text style={{backgroundColor: '#aaaaff'}} onPress={this.removeText}>
Remove Text
</Text>
<Text style={{backgroundColor: '#aaffaa'}} onPress={this.addText}>
Add Text
</Text>
</View>
</View>
);
}
}
class TextBaseLineLayoutExample extends React.Component<{}, mixed> {
render(): React.Node {
const texts = [];
for (let i = 9; i >= 0; i--) {
texts.push(
<Text key={i} style={{fontSize: 8 + i * 5, backgroundColor: '#eee'}}>
{i}
</Text>,
);
}
const marker = (
<View style={{width: 20, height: 20, backgroundColor: 'gray'}} />
);
const subtitleStyle = {fontSize: 16, marginTop: 8, fontWeight: 'bold'};
return (
<View>
<Text style={subtitleStyle}>{'Nested <Text/>s:'}</Text>
<View style={{flexDirection: 'row', alignItems: 'baseline'}}>
{marker}
<Text>{texts}</Text>
{marker}
</View>
<Text style={subtitleStyle}>{'Array of <Text/>s in <View>:'}</Text>
<View style={{flexDirection: 'row', alignItems: 'baseline'}}>
{marker}
{texts}
{marker}
</View>
<Text style={subtitleStyle}>{'Interleaving <View> and <Text>:'}</Text>
<View style={{flexDirection: 'row', alignItems: 'baseline'}}>
{marker}
<Text selectable={true}>
Some text.
<View
style={{
flexDirection: 'row',
alignItems: 'baseline',
backgroundColor: '#eee',
}}>
{marker}
<Text>Text inside View.</Text>
{marker}
</View>
</Text>
{marker}
</View>
<Text style={subtitleStyle}>
{'Multi-line interleaved <View> and <Text>:'}
</Text>
<View style={{flexDirection: 'row', alignItems: 'baseline'}}>
<Text selectable={true}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris
venenatis,{' '}
<View
style={{
backgroundColor: 'yellow',
}}>
<Text>mauris eu commodo maximus</Text>
</View>{' '}
, ante arcu vestibulum ligula, et scelerisque diam.
</Text>
</View>
<Text style={subtitleStyle}>{'Multi-line <Text> alignment'}</Text>
<View style={{flexDirection: 'row', alignItems: 'baseline'}}>
<View style={{width: 50, height: 50, backgroundColor: 'gray'}} />
<View style={{width: 125, backgroundColor: '#eee'}}>
<Text style={{fontSize: 15}}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua.
</Text>
</View>
<View style={{width: 125, backgroundColor: '#eee'}}>
<Text style={{fontSize: 10}}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua.
</Text>
</View>
</View>
<Text style={subtitleStyle}>{'<TextInput/>:'}</Text>
<View style={{flexDirection: 'row', alignItems: 'baseline'}}>
{marker}
<TextInput style={{margin: 0, padding: 0}}>{texts}</TextInput>
{marker}
</View>
<Text style={subtitleStyle}>{'<TextInput multiline/>:'}</Text>
<View style={{flexDirection: 'row', alignItems: 'baseline'}}>
{marker}
<TextInput multiline={true} style={{margin: 0, padding: 0}}>
{texts}
</TextInput>
{marker}
</View>
</View>
);
}
}
class TextRenderInfoExample extends React.Component<
{},
{
fontSize: number,
numberOfTextBlocks: number,
textMetrics: $ReadOnly<{
ascender: number,
capHeight: number,
descender: number,
height: number,
text?: string,
width: number,
x: number,
xHeight: number,
y: number,
}>,
},
> {
state: {
fontSize: number,
numberOfTextBlocks: number,
textMetrics: $ReadOnly<{
ascender: number,
capHeight: number,
descender: number,
height: number,
text?: string,
width: number,
x: number,
xHeight: number,
y: number,
}>,
} = {
textMetrics: {
x: 0,
y: 0,
width: 0,
height: 0,
capHeight: 0,
descender: 0,
ascender: 0,
xHeight: 0,
},
numberOfTextBlocks: 1,
fontSize: 14,
};
render(): React.Node {
const topOfBox =
this.state.textMetrics.y +
this.state.textMetrics.height -
(this.state.textMetrics.descender + this.state.textMetrics.capHeight);
return (
<View>
<View>
<View
style={{
position: 'absolute',
left: this.state.textMetrics.x + this.state.textMetrics.width,
top: topOfBox,
width: 5,
height: Math.ceil(
this.state.textMetrics.capHeight -
this.state.textMetrics.xHeight,
),
backgroundColor: 'red',
}}
/>
<View
style={{
position: 'absolute',
left: this.state.textMetrics.x + this.state.textMetrics.width,
top:
topOfBox +
(this.state.textMetrics.capHeight -
this.state.textMetrics.xHeight),
width: 5,
height: Math.ceil(this.state.textMetrics.xHeight),
backgroundColor: 'green',
}}
/>
<Text
style={{fontSize: this.state.fontSize}}
onTextLayout={event => {
const {lines} = event.nativeEvent;
if (lines.length > 0) {
this.setState({textMetrics: lines[lines.length - 1]});
}
}}>
{new Array<string>(this.state.numberOfTextBlocks)
.fill('A tiny block of text.')
.join(' ')}
</Text>
</View>
<Text
onPress={() =>
this.setState({
numberOfTextBlocks: this.state.numberOfTextBlocks + 1,
})
}>
More text
</Text>
<Text
onPress={() => this.setState({fontSize: this.state.fontSize + 1})}>
Increase size
</Text>
<Text
onPress={() => this.setState({fontSize: this.state.fontSize - 1})}>
Decrease size
</Text>
</View>
);
}
}
class TextWithCapBaseBox extends React.Component<
{children: string, style?: any},
{
textMetrics: $ReadOnly<{
ascender: number,
capHeight: number,
descender: number,
height: number,
text?: string,
width: number,
x: number,
xHeight: number,
y: number,
}>,
},
> {
state: {
textMetrics: $ReadOnly<{
ascender: number,
capHeight: number,
descender: number,
height: number,
text?: string,
width: number,
x: number,
xHeight: number,
y: number,
}>,
} = {
textMetrics: {
x: 0,
y: 0,
width: 0,
height: 0,
capHeight: 0,
descender: 0,
ascender: 0,
xHeight: 0,
},
};
render(): React.Node {
return (
<Text
onTextLayout={event => {
const {lines} = event.nativeEvent;
if (lines.length > 0) {
this.setState({textMetrics: lines[0]});
}
}}
style={[
{
marginTop: Math.ceil(
-(
this.state.textMetrics.ascender -
this.state.textMetrics.capHeight
),
),
marginBottom: Math.ceil(-this.state.textMetrics.descender),
},
this.props.style,
]}>
{this.props.children}
</Text>
);
}
}
const examples = [
{
title: 'Wrap',
render: function (): React.Node {
return (
<Text>
The text should wrap if it goes on multiple lines. See, this is going
to the next line.
</Text>
);
},
},
{
title: "Substring Emoji (should only see 'test')",
render: function (): React.Node {
return <Text>{'test🙃'.substring(0, 5)}</Text>;
},
},
{
title: 'Transparent Background Color',
render: function (): React.Node {
return (
<Text style={{backgroundColor: '#00000020', padding: 10}}>
Text in a gray box!
<Text style={{backgroundColor: 'red'}}>
Another text in a (inline) red box (which is inside the gray box).
</Text>
</Text>
);
},
},
{
title: 'Background Color and Border Width',
name: 'background-border-width',
render: function (): React.Node {
return (
<View testID="background-border-width">
<Text
style={{
backgroundColor: '#F000F0',
padding: 10,
}}>
Text with background color only
</Text>
<Text
style={{
backgroundColor: '#F000F0',
borderRadius: 10,
padding: 10,
marginTop: 10,
}}>
Text with background color and uniform borderRadii
</Text>
<Text
style={{
backgroundColor: '#F000F0',
borderTopRightRadius: 10,
borderTopLeftRadius: 20,
borderBottomRightRadius: 20,
borderBottomLeftRadius: 10,
padding: 10,
marginTop: 10,
}}>
Text with background color and non-uniform borders
</Text>
<Text
style={{
borderWidth: 1,
borderColor: 'red',
borderTopRightRadius: 10,
borderTopLeftRadius: 20,
borderBottomRightRadius: 20,
borderBottomLeftRadius: 10,
padding: 10,
marginTop: 10,
}}>
Text with borderWidth
</Text>
<Text
style={{
backgroundColor: '#00AA00',
borderWidth: 2,
borderColor: 'blue',
borderRadius: 10,
padding: 10,
marginTop: 10,
}}>
Text with background AND borderWidth
</Text>
</View>
);
},
},
{
title: 'Text metrics',
render: function (): React.Node {
return <TextRenderInfoExample />;
},
},
{
title: 'Text metrics legend',
render: (): React.Node => <TextLegend />,
},
{
title: 'Baseline capheight box',
render: (): React.Node => (
<View style={{backgroundColor: 'red'}}>
<TextWithCapBaseBox>Some example text.</TextWithCapBaseBox>
</View>
),
},
{
title: 'Padding',
render: function (): React.Node {
return (
<Text style={{padding: 10}}>
This text is indented by 10px padding on all sides.
</Text>
);
},
},
{
title: 'Font Family',
render: function (): React.Node {
return (
<View>
<Text style={{fontFamily: Platform.isTV ? 'Times' : 'Cochin'}}>
Cochin
</Text>
<Text
style={{
fontFamily: Platform.isTV ? 'Times' : 'Cochin',
fontWeight: 'bold',
}}>
Cochin bold
</Text>
<Text style={{fontFamily: 'Helvetica'}}>Helvetica</Text>
<Text style={{fontFamily: 'Helvetica', fontWeight: 'bold'}}>
Helvetica bold
</Text>
<Text style={{fontFamily: Platform.isTV ? 'Courier' : 'Verdana'}}>
Verdana
</Text>
<Text
style={{
fontFamily: Platform.isTV ? 'Courier' : 'Verdana',
fontWeight: 'bold',
}}>
Verdana bold
</Text>
<Text style={{fontFamily: 'Unknown Font Family'}}>
Unknown Font Family
</Text>
</View>
);
},
},
{
title: 'Font Size',
render: function (): React.Node {
return (
<View>
<Text style={{fontSize: 23}}>Size 23</Text>
<Text style={{fontSize: 8}}>Size 8</Text>
</View>
);
},
},
{
title: 'Color',
render: function (): React.Node {
return (
<View>
<Text style={{color: 'red'}}>Red color</Text>
<Text style={{color: 'blue'}}>Blue color</Text>
</View>
);
},
},
{
title: 'Font Weight',
render: function (): React.Node {
return (
<View>
<Text style={{fontWeight: 'bold'}}>Move fast and be bold</Text>
<Text style={{fontWeight: 'normal'}}>Move fast and be normal</Text>
<Text style={{fontWeight: '900'}}>FONT WEIGHT 900</Text>
<Text style={{fontWeight: '800'}}>FONT WEIGHT 800</Text>
<Text style={{fontWeight: '700'}}>FONT WEIGHT 700</Text>
<Text style={{fontWeight: '600'}}>FONT WEIGHT 600</Text>
<Text style={{fontWeight: '500'}}>FONT WEIGHT 500</Text>
<Text style={{fontWeight: '400'}}>FONT WEIGHT 400</Text>
<Text style={{fontWeight: '300'}}>FONT WEIGHT 300</Text>
<Text style={{fontWeight: '200'}}>FONT WEIGHT 200</Text>
<Text style={{fontWeight: '100'}}>FONT WEIGHT 100</Text>
<Text style={{fontWeight: 900}}>FONT WEIGHT 900</Text>
<Text style={{fontWeight: 800}}>FONT WEIGHT 800</Text>
<Text style={{fontWeight: 700}}>FONT WEIGHT 700</Text>
<Text style={{fontWeight: 600}}>FONT WEIGHT 600</Text>
<Text style={{fontWeight: 500}}>FONT WEIGHT 500</Text>
<Text style={{fontWeight: 400}}>FONT WEIGHT 400</Text>
<Text style={{fontWeight: 300}}>FONT WEIGHT 300</Text>
<Text style={{fontWeight: 200}}>FONT WEIGHT 200</Text>
<Text style={{fontWeight: 100}}>FONT WEIGHT 100</Text>
</View>
);
},
},
{
title: 'Font Style',
render: function (): React.Node {
return (
<View>
<Text style={{fontStyle: 'normal'}}>Normal text</Text>
<Text style={{fontStyle: 'italic'}}>Italic text</Text>
</View>
);
},
},
{
title: 'Selectable',
render: function (): React.Node {
return (
<View>
<Text selectable={true}>
This text is <Text style={{fontWeight: 'bold'}}>selectable</Text> if
you click-and-hold.
</Text>
</View>
);
},
},
{
title: 'Text Decoration',
render: function (): React.Node {
return (
<View>
<Text
style={{
textDecorationLine: 'underline',
textDecorationStyle: 'solid',
}}>
Solid underline
</Text>
<Text
style={{
textDecorationLine: 'underline',
textDecorationStyle: 'double',
textDecorationColor: '#ff0000',
}}>
Double underline with custom color
</Text>
<Text
style={{
textDecorationLine: 'underline',
textDecorationStyle: 'dashed',
textDecorationColor: '#9CDC40',
}}>
Dashed underline with custom color
</Text>
<Text
style={{
textDecorationLine: 'underline',
textDecorationStyle: 'dotted',
textDecorationColor: 'blue',
}}>
Dotted underline with custom color
</Text>
<Text style={{textDecorationLine: 'none'}}>None textDecoration</Text>
<Text
style={{
textDecorationLine: 'line-through',
textDecorationStyle: 'solid',
}}>
Solid line-through
</Text>
<Text
style={{
textDecorationLine: 'line-through',
textDecorationStyle: 'double',
textDecorationColor: '#ff0000',
}}>
Double line-through with custom color
</Text>
<Text
style={{
textDecorationLine: 'line-through',
textDecorationStyle: 'dashed',
textDecorationColor: '#9CDC40',
}}>
Dashed line-through with custom color
</Text>
<Text
style={{
textDecorationLine: 'line-through',
textDecorationStyle: 'dotted',
textDecorationColor: 'blue',
}}>
Dotted line-through with custom color
</Text>
<Text style={{textDecorationLine: 'underline line-through'}}>
Both underline and line-through
</Text>
</View>
);
},
},
{
title: 'Nested',
description: ('Nested text components will inherit the styles of their ' +
'parents (only backgroundColor is inherited from non-Text parents). ' +
'<Text> only supports other <Text> and raw text (strings) as children.': string),
render: function (): React.Node {
return (
<View>
<Text>
(Normal text,
<Text style={{fontWeight: 'bold'}}>
(and bold
<Text style={{fontSize: 11, color: '#527fe4'}}>
(and tiny inherited bold blue)
</Text>
)
</Text>
)
</Text>
<Text style={{opacity: 0.7}}>
(opacity
<Text>
(is inherited
<Text style={{opacity: 0.7}}>
(and accumulated
<Text style={{backgroundColor: '#ffaaaa'}}>
(and also applies to the background)
</Text>
)
</Text>
)
</Text>
)
</Text>
<Text style={{fontSize: 12}}>
<Entity>Entity Name</Entity>
</Text>
</View>
);
},
},
{
title: 'Text Align',
render: function (): React.Node {
return (
<View>
<Text>auto (default) - english LTR</Text>
<Text>
{'\u0623\u062D\u0628 \u0627\u0644\u0644\u063A\u0629 ' +
'\u0627\u0644\u0639\u0631\u0628\u064A\u0629 auto (default) - arabic ' +
'RTL'}
</Text>
<Text style={{textAlign: 'left'}}>
left left left left left left left left left left left left left
left left
</Text>
<Text style={{textAlign: 'center'}}>
center center center center center center center center center
center center
</Text>
<Text style={{textAlign: 'right'}}>
right right right right right right right right right right right
right right
</Text>
<Text style={{textAlign: 'justify'}}>
justify: this text component{"'"}s contents are laid out with
"textAlign: justify" and as you can see all of the lines except the
last one span the available width of the parent container.
</Text>
</View>
);
},
},
{
title: 'Letter Spacing',
render: function (): React.Node {
return (
<View>
<Text style={{letterSpacing: 0}}>letterSpacing = 0</Text>
<Text style={{letterSpacing: 2, marginTop: 5}}>
letterSpacing = 2
</Text>
<Text style={{letterSpacing: 9, marginTop: 5}}>
letterSpacing = 9
</Text>
<View style={{flexDirection: 'row'}}>
<Text
style={{
fontSize: 12,
letterSpacing: 2,
backgroundColor: 'fuchsia',
marginTop: 5,
}}>
With size and background color
</Text>
</View>
<Text style={{letterSpacing: -1, marginTop: 5}}>
letterSpacing = -1
</Text>
<Text
style={{
letterSpacing: 3,
backgroundColor: '#dddddd',
marginTop: 5,
}}>
[letterSpacing = 3]
<Text style={{letterSpacing: 0, backgroundColor: '#bbbbbb'}}>
[Nested letterSpacing = 0]
</Text>
<Text style={{letterSpacing: 6, backgroundColor: '#eeeeee'}}>
[Nested letterSpacing = 6]
</Text>
</Text>
</View>
);
},
},
{
title: 'Spaces',
render: function (): React.Node {
return (
<Text>
A {'generated'} {'string'} and some &nbsp;&nbsp;&nbsp; spaces
</Text>
);
},
},
{
title: 'Line Height',
render: function (): React.Node {
return (
<Text>
<Text style={{lineHeight: 35}}>
A lot of space between the lines of this long passage that should
wrap once.
</Text>
</Text>
);
},
},
{
title: 'Empty Text',
description: "It's ok to have Text with zero or null children.",
render: function (): React.Node {
return <Text />;
},
},
{
title: 'Toggling Attributes',
render: function (): React.MixedElement {
return <AttributeToggler />;
},
},
{
title: 'backgroundColor attribute',
description: 'backgroundColor is inherited from all types of views.',
render: function (): React.Node {
return (
<Text style={{backgroundColor: 'yellow'}}>
Yellow container background,
<Text style={{backgroundColor: '#ffaaaa'}}>
{' '}
red background,
<Text style={{backgroundColor: '#aaaaff'}}>
{' '}
blue background,
<Text>
{' '}
inherited blue background,
<Text style={{backgroundColor: '#aaffaa'}}>
{' '}
nested green background.
</Text>
</Text>
</Text>
</Text>
</Text>
);
},
},
{
title: 'numberOfLines attribute',
render: function (): React.Node {
return (
<View>
<Text numberOfLines={1}>
Maximum of one line, no matter how much I write here. If I keep
writing, it{"'"}ll just truncate after one line.
</Text>
<Text numberOfLines={2} style={{marginTop: 20}}>
Maximum of two lines, no matter how much I write here. If I keep
writing, it{"'"}ll just truncate after two lines.
</Text>
<Text style={{marginTop: 20}}>
No maximum lines specified, no matter how much I write here. If I
keep writing, it{"'"}ll just keep going and going.
</Text>
</View>
);
},
},
{
title: 'Text highlighting (tap the link to see highlight)',
render: function (): React.Node {
return (
<View>
<Text>
Lorem ipsum dolor sit amet,{' '}
<Text
suppressHighlighting={false}
style={{
backgroundColor: 'white',
textDecorationLine: 'underline',
color: 'blue',
}}
onPress={() => null}>
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud
</Text>{' '}
exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat.
</Text>
</View>
);
},
},
{
title: 'allowFontScaling attribute',
render: function (): React.Node {
return (
<View>
<Text>
By default, text will respect Text Size accessibility setting on
iOS. It means that all font sizes will be increased or decreased
depending on the value of Text Size setting in{' '}
<Text style={{fontWeight: 'bold'}}>
Settings.app - Display & Brightness - Text Size
</Text>
</Text>
<Text style={{marginTop: 10}}>
You can disable scaling for your Text component by passing {'"'}
allowFontScaling={'{'}false{'}"'} prop.
</Text>
<Text allowFontScaling={false} style={{marginTop: 20, fontSize: 15}}>
This text will not scale.{' '}
<Text style={{fontSize: 15}}>
This text also won't scale because it inherits "allowFontScaling"
from its parent.
</Text>
</Text>
</View>
);
},
},
{
title: 'Inline views',
render: (): React.Node => <TextInlineView.Basic />,
},
{
title: 'Inline image/view clipped by <Text>',
render: (): React.Node => <TextInlineView.ClippedByText />,
},
{
title: 'Relayout inline image',
render: (): React.Node => <TextInlineView.ChangeImageSize />,
},
{
title: 'Relayout inline view',
render: (): React.Node => <TextInlineView.ChangeViewSize />,
},
{
title: 'Relayout nested inline view',
render: (): React.Node => <TextInlineView.ChangeInnerViewSize />,
},
{
title: 'Text shadow',
render: function (): React.Node {
return (
<View>
<Text
style={{
fontSize: 20,
textShadowOffset: {width: 2, height: 2},
textShadowRadius: 1,
textShadowColor: '#00cccc',
}}>
Demo text shadow
</Text>
</View>
);
},
},
{
title: 'Ellipsize mode',
render: function (): React.Node {
return (
<View>
<Text numberOfLines={1}>
This very long text should be truncated with dots in the end.
</Text>
<Text ellipsizeMode="middle" numberOfLines={1}>
This very long text should be truncated with dots in the middle.
</Text>
<Text ellipsizeMode="head" numberOfLines={1}>
This very long text should be truncated with dots in the beginning.
</Text>
<Text ellipsizeMode="clip" numberOfLines={1}>
This very looooooooooooooooooooooooooooong text should be clipped.
</Text>
</View>
);
},
},
{
title: 'Font variants',
render: function (): React.Node {
return (
<View>
<Text style={{fontVariant: ['small-caps']}}>Small Caps{'\n'}</Text>
<Text
style={{
fontFamily: Platform.isTV ? 'Times' : 'Hoefler Text',
fontVariant: ['oldstyle-nums'],
}}>
Old Style nums 0123456789{'\n'}
</Text>
<Text
style={{
fontFamily: Platform.isTV ? 'Times' : 'Hoefler Text',
fontVariant: ['lining-nums'],
}}>
Lining nums 0123456789{'\n'}
</Text>
<Text style={{fontVariant: ['tabular-nums']}}>
Tabular nums{'\n'}
1111{'\n'}
2222{'\n'}
</Text>
<Text style={{fontVariant: ['proportional-nums']}}>
Proportional nums{'\n'}
1111{'\n'}
2222{'\n'}
</Text>
</View>
);
},
},
{
title: 'Nested content',
render: function (): React.Node {
// iOS-only because it relies on inline views being able to size to content.
// Android's implementation requires that a width and height be specified
// on the inline view.
return (
<Text>
This text has a view
<InlineView style={{borderColor: 'red', borderWidth: 1}}>
<Text style={{borderColor: 'blue', borderWidth: 1}}>which has</Text>
<Text style={{borderColor: 'green', borderWidth: 1}}>
another text inside.
</Text>
<Text style={{borderColor: 'yellow', borderWidth: 1}}>
And moreover, it has another view
<InlineView style={{borderColor: 'red', borderWidth: 1}}>
<Text style={{borderColor: 'blue', borderWidth: 1}}>
with another text inside!
</Text>
</InlineView>
</Text>
</InlineView>
Because we need to go deeper.
</Text>
);
},
},
{
title: 'Dynamic Font Size Adjustment',
render: function (): React.MixedElement {
return <AdjustingFontSize />;
},
},
{
title: 'Text Align with RTL',
name: 'textAlign',
render: function (): React.Node {
return <TextAlignRTLExample />;
},
},
{
title: "Text `alignItems: 'baseline'` style",
render: function (): React.Node {
return <TextBaseLineLayoutExample />;
},
},
{
title: 'Transform',
render: function (): React.Node {
return (
<View>
<Text style={{textTransform: 'uppercase'}}>
This text should be uppercased.
</Text>
<Text style={{textTransform: 'lowercase'}}>
This TEXT SHOULD be lowercased.
</Text>
<Text style={{textTransform: 'capitalize'}}>
This text should be CAPITALIZED.
</Text>
<Text>
Capitalize a date:
<Text style={{textTransform: 'capitalize'}}>
the 9th of november, 1998
</Text>
</Text>
<Text>
Capitalize a 2 digit date:
<Text style={{textTransform: 'capitalize'}}>
the 25th of december
</Text>
</Text>
<Text style={{textTransform: 'capitalize'}}>
Mixed: <Text style={{textTransform: 'uppercase'}}>uppercase </Text>
<Text style={{textTransform: 'lowercase'}}>LoWeRcAsE </Text>
<Text style={{textTransform: 'capitalize'}}>
capitalize each word
</Text>
</Text>
<Text>
Should be "ABC":
<Text style={{textTransform: 'uppercase'}}>
a<Text>b</Text>c
</Text>
</Text>
<Text>
Should be "AbC":
<Text style={{textTransform: 'uppercase'}}>
a<Text style={{textTransform: 'none'}}>b</Text>c
</Text>
</Text>
</View>
);
},
},
{
title: 'Selectable Text',
render: function (): React.Node {
return (
<View>
<Text style={{userSelect: 'auto'}}>Text element is selectable</Text>
</View>
);
},
},
{
title: 'Line Break Strategy',
render: function (): React.Node {
const lineBreakStrategy = ['none', 'standard', 'hangul-word', 'push-out'];
const textByCode = {
en: 'lineBreakStrategy lineBreakStrategy lineBreakStrategy lineBreakStrategy',
ko: '한글개행 한글개행 한글개행 한글개행 한글개행 한글개행 한글개행 한글개행',
ja: 'かいぎょう かいぎょう かいぎょう かいぎょう かいぎょう かいぎょう',
cn: '改行 改行 改行 改行 改行 改行 改行 改行 改行 改行 改行 改行',
};
return (
<View>
{lineBreakStrategy.map(strategy => {
return (
<View key={strategy} style={{marginBottom: 12}}>
<Text
style={{
backgroundColor: 'lightgrey',
}}>{`Strategy: ${strategy}`}</Text>
{Object.keys(textByCode).map(code => {
return (
<View key={code}>
<Text style={{fontWeight: 'bold'}}>{`[${code}]`}</Text>
<Text lineBreakStrategyIOS={strategy}>
{textByCode[code]}
</Text>
</View>
);
})}
</View>
);
})}
</View>
);
},
},
{
title: 'Dynamic Type (iOS only)',
render: function (): React.Node {
const boldStyle = {fontWeight: 'bold'};
const boxStyle = {
borderWidth: 1,
padding: 8,
margin: 8,
};
return (
<View style={{marginTop: 10, marginBottom: 10}}>
<Text>
Adjust text size in Accessibility settings and watch how the font
sizes change relative to each other.
</Text>
<View style={boxStyle}>
<Text style={boldStyle}>With `dynamicTypeRamp`:</Text>
<Text style={{fontSize: 34}} dynamicTypeRamp="largeTitle">
Large Title
</Text>
<Text style={{fontSize: 28}} dynamicTypeRamp="title1">
Title
</Text>
<Text style={{fontSize: 22}} dynamicTypeRamp="title2">
Title 2
</Text>
<Text style={{fontSize: 20}} dynamicTypeRamp="title3">
Title 3
</Text>
<Text style={{fontSize: 17}} dynamicTypeRamp="body">
Body
</Text>
</View>
<View style={boxStyle}>
<Text style={boldStyle}>Without `dynamicTypeRamp`:</Text>
<Text style={{fontSize: 34}}>Large Title</Text>
<Text style={{fontSize: 28}}>Title</Text>
<Text style={{fontSize: 22}}>Title 2</Text>
<Text style={{fontSize: 20}}>Title 3</Text>
<Text style={{fontSize: 17}}>Body</Text>
</View>
</View>
);
},
},
{
title: 'Clipping',
name: 'clipping',
render: function (): React.Node {
return (
<View>
<Text
testID="text-clipping"
style={{
borderRadius: 50,
padding: 0,
borderColor: 'red',
borderWidth: 5,
overflow: 'hidden',
fontSize: 16,
}}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua.
</Text>
</View>
);
},
},
{
title: 'Box Shadow',
name: 'boxShadow',
render: function (): React.Node {
return (
<View>
<Text
testID="text-box-shadow"
style={{
borderRadius: 10,
boxShadow: '0 0 10px red',
}}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua.
</Text>
</View>
);
},
},
TextInlineViewsExample,
];
module.exports = ({
title: 'Text',
documentationURL: 'https://reactnative.dev/docs/text',
category: 'Basic',
description: 'Base component for rendering styled text.',
displayName: 'TextExample',
examples,
}: RNTesterModule);