mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-05-16 08:30:06 +08:00
Add 'Tweet' to performance benchmarks
This commit is contained in:
115
performance/components/AppText/index.js
Normal file
115
performance/components/AppText/index.js
Normal file
@@ -0,0 +1,115 @@
|
||||
import theme from '../theme';
|
||||
import React, { PropTypes, PureComponent } from 'react';
|
||||
import { StyleSheet, Text } from 'react-native';
|
||||
|
||||
class AppText extends PureComponent {
|
||||
static displayName = 'AppText';
|
||||
|
||||
static propTypes = {
|
||||
align: PropTypes.oneOf(['center', 'left', 'right']),
|
||||
color: PropTypes.oneOf(['blue', 'deepGray', 'normal', 'red', 'white']),
|
||||
fontStyle: PropTypes.oneOf(['normal', 'italic']),
|
||||
size: PropTypes.oneOf(['small', 'normal', 'large']),
|
||||
uppercase: PropTypes.bool,
|
||||
weight: PropTypes.oneOf(['normal', 'bold'])
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
align,
|
||||
color,
|
||||
fontStyle,
|
||||
size,
|
||||
uppercase,
|
||||
weight,
|
||||
...other
|
||||
} = this.props;
|
||||
|
||||
const style = [
|
||||
styles.root,
|
||||
align && alignStyles[align],
|
||||
color && colorStyles[color],
|
||||
fontStyle && fontStyles[fontStyle],
|
||||
size && sizeStyles[size],
|
||||
weight && weightStyles[weight],
|
||||
uppercase === true && styles.uppercase
|
||||
];
|
||||
|
||||
return <Text {...other} style={style} />;
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
fontFamily: theme.fontFamily,
|
||||
fontSize: theme.fontSize.normal,
|
||||
fontWeight: 'normal',
|
||||
lineHeight: theme.createLength(theme.lineHeight),
|
||||
wordWrap: 'break-word'
|
||||
},
|
||||
uppercase: {
|
||||
textTransform: 'uppercase'
|
||||
}
|
||||
});
|
||||
|
||||
const alignStyles = StyleSheet.create({
|
||||
center: {
|
||||
textAlign: 'center'
|
||||
},
|
||||
left: {
|
||||
textAlign: 'left'
|
||||
},
|
||||
right: {
|
||||
textAlign: 'right'
|
||||
}
|
||||
});
|
||||
|
||||
const colorStyles = StyleSheet.create({
|
||||
blue: {
|
||||
color: theme.colors.blue
|
||||
},
|
||||
deepGray: {
|
||||
color: theme.colors.deepGray
|
||||
},
|
||||
normal: {
|
||||
color: theme.colors.textBlack
|
||||
},
|
||||
red: {
|
||||
color: theme.colors.red
|
||||
},
|
||||
white: {
|
||||
color: theme.colors.white
|
||||
}
|
||||
});
|
||||
|
||||
const fontStyles = StyleSheet.create({
|
||||
normal: {
|
||||
fontStyle: 'normal'
|
||||
},
|
||||
italic: {
|
||||
fontStyle: 'italic'
|
||||
}
|
||||
});
|
||||
|
||||
const sizeStyles = StyleSheet.create({
|
||||
small: {
|
||||
fontSize: theme.fontSize.small
|
||||
},
|
||||
normal: {
|
||||
fontSize: theme.fontSize.normal
|
||||
},
|
||||
large: {
|
||||
fontSize: theme.fontSize.large
|
||||
}
|
||||
});
|
||||
|
||||
const weightStyles = StyleSheet.create({
|
||||
normal: {
|
||||
fontWeight: '400'
|
||||
},
|
||||
bold: {
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
});
|
||||
|
||||
export default AppText;
|
||||
40
performance/components/AspectRatio/index.js
Normal file
40
performance/components/AspectRatio/index.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import React, { PureComponent, PropTypes } from 'react';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
|
||||
class AspectRatio extends PureComponent {
|
||||
static displayName = 'AspectRatio';
|
||||
|
||||
static propTypes = {
|
||||
children: PropTypes.any,
|
||||
ratio: PropTypes.number,
|
||||
style: PropTypes.object
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
ratio: 1
|
||||
};
|
||||
|
||||
render() {
|
||||
const { children, ratio, style } = this.props;
|
||||
const percentage = 100 / ratio;
|
||||
|
||||
return (
|
||||
<View style={[styles.root, style]}>
|
||||
<View style={[styles.shim, { paddingBottom: `${percentage}%` }]} />
|
||||
<View style={StyleSheet.absoluteFill}>{children}</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
overflow: 'hidden'
|
||||
},
|
||||
shim: {
|
||||
display: 'block',
|
||||
width: '100%'
|
||||
}
|
||||
});
|
||||
|
||||
export default AspectRatio;
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import classnames from 'classnames';
|
||||
import React from 'react';
|
||||
import View from '../View';
|
||||
import View from '../View/index.css-modules';
|
||||
import styles from './styles.css';
|
||||
|
||||
const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other }) => (
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import { css } from 'glamor';
|
||||
import React from 'react';
|
||||
import View from '../View';
|
||||
import View from '../View/index.glamor';
|
||||
|
||||
const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other }) => (
|
||||
<View
|
||||
@@ -1,7 +1,6 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React from 'react';
|
||||
import StyleSheet from 'react-native/apis/StyleSheet';
|
||||
import View from '../View';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
|
||||
const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other }) => (
|
||||
<View
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import classnames from 'classnames';
|
||||
import React from 'react';
|
||||
import View from '../View';
|
||||
import View from '../View/index.platform';
|
||||
|
||||
const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other }) => (
|
||||
<View
|
||||
@@ -1,5 +1,5 @@
|
||||
import styled from 'styled-components';
|
||||
import View from '../View';
|
||||
import View from '../View/index.styled';
|
||||
|
||||
const getColor = color => {
|
||||
switch (color) {
|
||||
50
performance/components/GridView/index.js
Normal file
50
performance/components/GridView/index.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import theme from '../theme';
|
||||
|
||||
class GridView extends Component {
|
||||
static displayName = 'GridView';
|
||||
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
hasGap: PropTypes.bool,
|
||||
style: PropTypes.object
|
||||
};
|
||||
|
||||
render() {
|
||||
const { children, hasGap, style, ...other } = this.props;
|
||||
|
||||
return (
|
||||
<View {...other} style={[style, styles.root, hasGap && styles.hasGap]}>
|
||||
{React.Children.map(children, child => {
|
||||
return child &&
|
||||
React.cloneElement(child, {
|
||||
style: [child.props.style, styles.column, hasGap && styles.hasGapColumn]
|
||||
});
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
flexDirection: 'row'
|
||||
},
|
||||
/**
|
||||
* 1. Distribute all space (rather than extra space)
|
||||
* 2. Prevent wide content from forcing wider flex columns
|
||||
*/
|
||||
column: {
|
||||
flexBasis: 0, // 1
|
||||
minWidth: 0 // 2
|
||||
},
|
||||
hasGap: {
|
||||
marginHorizontal: theme.createLength(theme.spaceX * (-0.5), 'rem')
|
||||
},
|
||||
hasGapColumn: {
|
||||
marginHorizontal: theme.createLength(theme.spaceX * 0.5, 'rem')
|
||||
}
|
||||
});
|
||||
|
||||
export default GridView;
|
||||
20
performance/components/Icons/DirectMessage.js
Normal file
20
performance/components/Icons/DirectMessage.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import { createDOMElement } from 'react-native';
|
||||
import React from 'react';
|
||||
import styles from './styles';
|
||||
|
||||
const IconDirectMessage = props => createDOMElement('svg', {
|
||||
children: (
|
||||
<g>
|
||||
<path d="M43.34 14H12.66L28 27.946z" />
|
||||
<path
|
||||
d="M51.392 14.789L30.018 34.22c-.009.008-.028.006-.039.012-.563.5-1.266.768-1.98.768-.72 0-1.442-.258-2.017-.78L4.609 14.79A3.957 3.957 0 0 0 3 18v37a1.998 1.998 0 0 0 2 2c.464 0 .924-.162 1.292-.473L19 46h30c2.243 0 4-1.757 4-4V18a3.96 3.96 0 0 0-1.608-3.211z"
|
||||
/>
|
||||
</g>
|
||||
),
|
||||
style: [styles.icon, props.style],
|
||||
viewBox: '0 0 56 72'
|
||||
});
|
||||
|
||||
IconDirectMessage.metadata = { height: 72, width: 56 };
|
||||
|
||||
export default IconDirectMessage;
|
||||
19
performance/components/Icons/Heart.js
Normal file
19
performance/components/Icons/Heart.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { createDOMElement } from 'react-native';
|
||||
import React from 'react';
|
||||
import styles from './styles';
|
||||
|
||||
const IconHeart = props => createDOMElement('svg', {
|
||||
children: (
|
||||
<g>
|
||||
<path
|
||||
d="M38.723 12c-7.187 0-11.16 7.306-11.723 8.131C26.437 19.306 22.504 12 15.277 12 8.791 12 3.533 18.163 3.533 24.647 3.533 39.964 21.891 55.907 27 56c5.109-.093 23.467-16.036 23.467-31.353C50.467 18.163 45.209 12 38.723 12z"
|
||||
/>
|
||||
</g>
|
||||
),
|
||||
style: [styles.icon, props.style],
|
||||
viewBox: '0 0 54 72'
|
||||
});
|
||||
|
||||
IconHeart.metadata = { height: 72, width: 54 };
|
||||
|
||||
export default IconHeart;
|
||||
19
performance/components/Icons/Reply.js
Normal file
19
performance/components/Icons/Reply.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { createDOMElement } from 'react-native';
|
||||
import React from 'react';
|
||||
import styles from './styles';
|
||||
|
||||
const IconReply = props => createDOMElement('svg', {
|
||||
children: (
|
||||
<g>
|
||||
<path
|
||||
d="M41 31h-9V19a2.999 2.999 0 0 0-4.817-2.386l-21 16a3 3 0 0 0-.001 4.773l21 16a3.006 3.006 0 0 0 3.15.301A2.997 2.997 0 0 0 32 51V39h9c5.514 0 10 4.486 10 10a4 4 0 0 0 8 0c0-9.925-8.075-18-18-18z"
|
||||
/>
|
||||
</g>
|
||||
),
|
||||
style: [styles.icon, props.style],
|
||||
viewBox: '0 0 62 72'
|
||||
});
|
||||
|
||||
IconReply.metadata = { height: 72, width: 62 };
|
||||
|
||||
export default IconReply;
|
||||
19
performance/components/Icons/Retweet.js
Normal file
19
performance/components/Icons/Retweet.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { createDOMElement } from 'react-native';
|
||||
import React from 'react';
|
||||
import styles from './styles';
|
||||
|
||||
const IconRetweet = props => createDOMElement('svg', {
|
||||
children: (
|
||||
<g>
|
||||
<path
|
||||
d="M70.676 36.644A3 3 0 0 0 68 35h-7V19a4 4 0 0 0-4-4H34a4 4 0 0 0 0 8h18a1 1 0 0 1 1 .998V35h-7a3.001 3.001 0 0 0-2.419 4.775l11 15a3.003 3.003 0 0 0 4.839-.001l11-15a3.001 3.001 0 0 0 .256-3.13zM40.001 48H22a.995.995 0 0 1-.992-.96L21.001 36h7a3.001 3.001 0 0 0 2.419-4.775l-11-15a3.003 3.003 0 0 0-4.839.001l-11 15A3 3 0 0 0 6.001 36h7l.011 16.003a4 4 0 0 0 4 3.997h22.989a4 4 0 0 0 0-8z"
|
||||
/>
|
||||
</g>
|
||||
),
|
||||
style: [styles.icon, props.style],
|
||||
viewBox: '0 0 74 72'
|
||||
});
|
||||
|
||||
IconRetweet.metadata = { height: 72, width: 74 };
|
||||
|
||||
export default IconRetweet;
|
||||
15
performance/components/Icons/styles.js
Normal file
15
performance/components/Icons/styles.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { StyleSheet } from 'react-native';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
icon: {
|
||||
display: 'inline-block',
|
||||
fill: 'currentcolor',
|
||||
height: '1.25em',
|
||||
maxWidth: '100%',
|
||||
position: 'relative',
|
||||
userSelect: 'none',
|
||||
verticalAlign: 'text-bottom'
|
||||
}
|
||||
});
|
||||
|
||||
export default styles;
|
||||
144
performance/components/Tweet/index.js
Normal file
144
performance/components/Tweet/index.js
Normal file
@@ -0,0 +1,144 @@
|
||||
import AspectRatio from '../AspectRatio';
|
||||
import GridView from '../GridView';
|
||||
import TweetActionsBar from '../TweetActionsBar';
|
||||
import TweetText from '../TweetText';
|
||||
import UserAvatar from '../UserAvatar';
|
||||
import UserNames from '../UserNames';
|
||||
import { Image, StyleSheet, Text, View } from 'react-native';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import theme from '../theme';
|
||||
|
||||
export class Tweet extends Component {
|
||||
static displayName = 'Tweet';
|
||||
|
||||
static propTypes = {
|
||||
tweet: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
render() {
|
||||
const { tweet } = this.props;
|
||||
const { id, lang, media, textParts, timestamp, user } = tweet;
|
||||
const { fullName, profileImageUrl, screenName } = user;
|
||||
|
||||
return (
|
||||
<View accessibilityRole="article" accessible style={styles.root}>
|
||||
<GridView hasGap>
|
||||
<View style={styles.avatarColumn}>
|
||||
<View
|
||||
accessibilityRole="link"
|
||||
accessible
|
||||
href={`/${screenName}`}
|
||||
style={styles.avatarLink}
|
||||
>
|
||||
<UserAvatar style={styles.avatar} uri={profileImageUrl} />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.bodyColumn}>
|
||||
<View style={styles.body}>
|
||||
<View style={styles.row}>
|
||||
<Text
|
||||
accessibilityRole="link"
|
||||
children={timestamp}
|
||||
href={`/${screenName}/status/${id}`}
|
||||
style={styles.timestamp}
|
||||
/>
|
||||
<UserNames fullName={fullName} screenName={screenName} />
|
||||
</View>
|
||||
|
||||
<View accessibilityRole="heading" aria-level="4">
|
||||
<TweetText displayMode={'links'} lang={lang} textParts={textParts} />
|
||||
</View>
|
||||
|
||||
{media
|
||||
? <View style={styles.richContent}>
|
||||
<AspectRatio ratio={16 / 9}>
|
||||
<Image
|
||||
resizeMode={Image.resizeMode.cover}
|
||||
source={media.source}
|
||||
style={styles.media}
|
||||
/>
|
||||
</AspectRatio>
|
||||
</View>
|
||||
: null}
|
||||
|
||||
</View>
|
||||
|
||||
<TweetActionsBar
|
||||
actions={[
|
||||
{ name: 'reply', label: 'Reply' },
|
||||
{
|
||||
name: 'retweet',
|
||||
label: 'Retweet',
|
||||
count: tweet.retweet_count,
|
||||
highlighted: tweet.retweeted
|
||||
},
|
||||
{
|
||||
name: 'like',
|
||||
label: 'Like',
|
||||
count: tweet.favorite_count,
|
||||
highlighted: tweet.favorited
|
||||
},
|
||||
{ name: 'directMessage', label: 'Direct Message' }
|
||||
]}
|
||||
style={styles.actionBar}
|
||||
/>
|
||||
</View>
|
||||
</GridView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
paddingVertical: theme.createLength(theme.spaceY * 0.75, 'rem'),
|
||||
paddingHorizontal: theme.createLength(theme.spaceX, 'rem')
|
||||
},
|
||||
avatarColumn: {
|
||||
flexGrow: 1,
|
||||
minWidth: 32
|
||||
},
|
||||
bodyColumn: {
|
||||
flexGrow: 7
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between'
|
||||
},
|
||||
avatarLink: {
|
||||
display: 'block',
|
||||
flexShrink: 1,
|
||||
flexGrow: 0,
|
||||
width: '100%'
|
||||
},
|
||||
avatar: {
|
||||
width: '100%'
|
||||
},
|
||||
body: {
|
||||
marginTop: '-0.15rem'
|
||||
},
|
||||
timestamp: {
|
||||
color: theme.colors.deepGray,
|
||||
marginLeft: theme.createLength(theme.spaceX, 'rem'),
|
||||
order: 1,
|
||||
textDecorationLine: 'none',
|
||||
whiteSpace: 'nowrap'
|
||||
},
|
||||
actionBar: {
|
||||
marginTop: theme.createLength(theme.spaceY * 0.5, 'rem')
|
||||
},
|
||||
richContent: {
|
||||
borderRadius: '0.35rem',
|
||||
marginTop: theme.createLength(theme.spaceY * 0.5, 'rem'),
|
||||
overflow: 'hidden'
|
||||
},
|
||||
media: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
margin: 'auto',
|
||||
width: 'auto',
|
||||
height: 'auto'
|
||||
}
|
||||
});
|
||||
|
||||
export default Tweet;
|
||||
77
performance/components/TweetAction/index.js
Normal file
77
performance/components/TweetAction/index.js
Normal file
@@ -0,0 +1,77 @@
|
||||
import IconReply from '../Icons/Reply';
|
||||
import IconHeart from '../Icons/Heart';
|
||||
import IconRetweet from '../Icons/Retweet';
|
||||
import IconDirectMessage from '../Icons/DirectMessage';
|
||||
import { Text, View, StyleSheet } from 'react-native';
|
||||
import React, { PropTypes } from 'react';
|
||||
import theme from '../theme';
|
||||
|
||||
const getIcon = (icon, highlighted) => {
|
||||
switch (icon) {
|
||||
case 'like':
|
||||
return <IconHeart />;
|
||||
case 'reply':
|
||||
return <IconReply />;
|
||||
case 'retweet':
|
||||
return <IconRetweet />;
|
||||
case 'directMessage':
|
||||
return <IconDirectMessage />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export default class TweetAction extends React.Component {
|
||||
static displayName = 'TweetAction';
|
||||
|
||||
static propTypes = {
|
||||
count: PropTypes.number,
|
||||
displayMode: PropTypes.oneOf(['like', 'reply', 'retweet', 'directMessage']),
|
||||
highlighted: PropTypes.bool,
|
||||
onPress: PropTypes.func,
|
||||
style: PropTypes.object
|
||||
};
|
||||
|
||||
render() {
|
||||
const { count, displayMode, highlighted, onPress, style } = this.props;
|
||||
|
||||
return (
|
||||
<View accessibilityRole="button" onPress={onPress} style={[styles.root, style]}>
|
||||
<Text
|
||||
style={[
|
||||
styles.inner,
|
||||
displayMode === 'like' && highlighted && styles.likedColor,
|
||||
displayMode === 'retweet' && highlighted && styles.retweetedColor
|
||||
]}
|
||||
>
|
||||
{getIcon(displayMode, highlighted)}
|
||||
{count > 0 ? <Text style={styles.count}>{count}</Text> : null}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
minHeight: theme.createLength(theme.lineHeight, 'rem'),
|
||||
overflow: 'visible',
|
||||
userSelect: 'none',
|
||||
whiteSpace: 'nowrap'
|
||||
},
|
||||
inner: {
|
||||
alignItems: 'center',
|
||||
color: theme.colors.deepGray,
|
||||
display: 'flex',
|
||||
flexDirection: 'row'
|
||||
},
|
||||
count: {
|
||||
marginLeft: '0.25em'
|
||||
},
|
||||
retweetedColor: {
|
||||
color: theme.colors.green
|
||||
},
|
||||
likedColor: {
|
||||
color: theme.colors.red
|
||||
}
|
||||
});
|
||||
51
performance/components/TweetActionsBar/index.js
Normal file
51
performance/components/TweetActionsBar/index.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import TweetAction from '../TweetAction';
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
import React, { PropTypes, PureComponent } from 'react';
|
||||
|
||||
const actionNames = ['reply', 'retweet', 'like', 'directMessage'];
|
||||
|
||||
export default class TweetActionsBar extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
count: PropTypes.number,
|
||||
label: PropTypes.string,
|
||||
highlighted: PropTypes.bool,
|
||||
name: PropTypes.oneOf(actionNames).isRequired,
|
||||
onPress: PropTypes.func
|
||||
})
|
||||
),
|
||||
style: PropTypes.object
|
||||
};
|
||||
|
||||
render() {
|
||||
const { actions, style } = this.props;
|
||||
|
||||
/* eslint-disable react/jsx-handler-names */
|
||||
return (
|
||||
<View style={[styles.root, style]}>
|
||||
{actions.map((action, i) => (
|
||||
<TweetAction
|
||||
accessibilityLabel={actions.label}
|
||||
count={action.count}
|
||||
displayMode={action.name}
|
||||
highlighted={action.highlighted}
|
||||
key={i}
|
||||
onPress={action.onPress}
|
||||
style={styles.action}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
flexDirection: 'row'
|
||||
},
|
||||
action: {
|
||||
display: 'block',
|
||||
marginRight: '10%'
|
||||
}
|
||||
});
|
||||
28
performance/components/TweetText/index.js
Normal file
28
performance/components/TweetText/index.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import AppText from '../AppText';
|
||||
import TweetTextPart from '../TweetTextPart';
|
||||
import React, { PropTypes } from 'react';
|
||||
|
||||
class TweetText extends React.Component {
|
||||
static displayName = 'TweetText';
|
||||
|
||||
static propTypes = {
|
||||
displayMode: TweetTextPart.propTypes.displayMode,
|
||||
lang: PropTypes.string,
|
||||
numberOfLines: PropTypes.number,
|
||||
textParts: PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
render() {
|
||||
const { displayMode, lang, numberOfLines, textParts, ...other } = this.props;
|
||||
|
||||
return (
|
||||
<AppText {...other} lang={lang} numberOfLines={numberOfLines}>
|
||||
{textParts.map((part, i) => (
|
||||
<TweetTextPart displayMode={displayMode} key={i} part={part} />
|
||||
))}
|
||||
</AppText>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default TweetText;
|
||||
112
performance/components/TweetTextPart/index.js
Normal file
112
performance/components/TweetTextPart/index.js
Normal file
@@ -0,0 +1,112 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import { Image, StyleSheet, Text } from 'react-native';
|
||||
import React, { PropTypes } from 'react';
|
||||
import theme from '../theme';
|
||||
|
||||
const createTextEntity = ({ part }) => <Text>{`${part.prefix}${part.text}`}</Text>;
|
||||
|
||||
const createTwemojiEntity = ({ part }) => (
|
||||
<Image
|
||||
accessibilityLabel={part.text}
|
||||
draggable={false}
|
||||
source={{ uri: part.emoji }}
|
||||
style={styles.twemoji}
|
||||
/>
|
||||
);
|
||||
|
||||
// @mention, #hashtag, $cashtag
|
||||
const createSymbolEntity = ({ displayMode, part }) => {
|
||||
const links = displayMode === 'links';
|
||||
return (
|
||||
<Text accessibilityRole={links ? 'link' : null} href={part.url} style={[links && styles.link]}>
|
||||
{`${part.prefix}${part.text}`}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
// internal links
|
||||
const createLinkEntity = ({ displayMode, part }) => {
|
||||
const { displayUrl, linkRelation, url } = part;
|
||||
const links = displayMode === 'links';
|
||||
|
||||
return (
|
||||
<Text
|
||||
accessibilityRole={links ? 'link' : null}
|
||||
href={url}
|
||||
rel={links ? linkRelation : null}
|
||||
style={[links && styles.link]}
|
||||
>
|
||||
{displayUrl}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
// external links
|
||||
const createExternalLinkEntity = ({ displayMode, part }) => {
|
||||
const { displayUrl, linkRelation, url } = part;
|
||||
const links = displayMode === 'links';
|
||||
|
||||
return (
|
||||
<Text
|
||||
accessibilityRole={links ? 'link' : null}
|
||||
href={url}
|
||||
rel={links ? linkRelation : null}
|
||||
style={[links && styles.link]}
|
||||
target="_blank"
|
||||
>
|
||||
{displayUrl}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
class TweetTextPart extends React.Component {
|
||||
static displayName = 'TweetTextPart';
|
||||
|
||||
static propTypes = {
|
||||
displayMode: PropTypes.oneOf(['links', 'no-links']),
|
||||
part: PropTypes.object
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
displayMode: 'links'
|
||||
};
|
||||
|
||||
render() {
|
||||
let renderer;
|
||||
const { isEmoji, isEntity, isHashtag, isMention, isMedia, isUrl } = this.props.part;
|
||||
|
||||
if (isEmoji || isEntity || isUrl || isMedia) {
|
||||
if (isUrl) {
|
||||
renderer = createExternalLinkEntity;
|
||||
} else if (isHashtag || isMention) {
|
||||
renderer = createSymbolEntity;
|
||||
} else if (isEmoji) {
|
||||
renderer = createTwemojiEntity;
|
||||
} else {
|
||||
renderer = createLinkEntity;
|
||||
}
|
||||
} else {
|
||||
renderer = createTextEntity;
|
||||
}
|
||||
|
||||
return renderer(this.props);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
link: {
|
||||
color: theme.colors.blue,
|
||||
textDecorationLine: 'none',
|
||||
unicodeBidi: 'embed'
|
||||
},
|
||||
twemoji: {
|
||||
display: 'inline-block',
|
||||
height: '1.25em',
|
||||
width: '1.25em',
|
||||
paddingRight: '0.05em',
|
||||
paddingLeft: '0.1em',
|
||||
verticalAlign: '-0.2em'
|
||||
}
|
||||
});
|
||||
|
||||
export default TweetTextPart;
|
||||
64
performance/components/UserAvatar/index.js
Normal file
64
performance/components/UserAvatar/index.js
Normal file
@@ -0,0 +1,64 @@
|
||||
import AspectRatio from '../AspectRatio';
|
||||
import { Image, StyleSheet } from 'react-native';
|
||||
import React, { PropTypes, PureComponent } from 'react';
|
||||
import theme from '../theme';
|
||||
|
||||
class UserAvatar extends PureComponent {
|
||||
static displayName = 'UserAvatar';
|
||||
|
||||
static propTypes = {
|
||||
accessibilityLabel: PropTypes.string,
|
||||
circle: PropTypes.bool,
|
||||
style: PropTypes.object,
|
||||
uri: PropTypes.string
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
circle: false
|
||||
};
|
||||
|
||||
render() {
|
||||
const { accessibilityLabel, circle, style, uri } = this.props;
|
||||
|
||||
return (
|
||||
<AspectRatio ratio={1} style={[styles.root, style]}>
|
||||
{uri
|
||||
? <Image
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
onLoad={this._handleLoad}
|
||||
ref={this._setImageRef}
|
||||
source={{ uri }}
|
||||
style={[styles.image, circle && styles.circle]}
|
||||
/>
|
||||
: null}
|
||||
</AspectRatio>
|
||||
);
|
||||
}
|
||||
|
||||
_handleLoad = () => {
|
||||
this._imageRef && this._imageRef.setNativeProps(nativeProps);
|
||||
};
|
||||
|
||||
_setImageRef = component => {
|
||||
this._imageRef = component;
|
||||
};
|
||||
}
|
||||
|
||||
const nativeProps = { style: { backgroundColor: '#fff' } };
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
borderRadius: '0.35rem'
|
||||
},
|
||||
circle: {
|
||||
borderRadius: '9999px'
|
||||
},
|
||||
image: {
|
||||
backgroundColor: theme.colors.fadedGray,
|
||||
display: 'block',
|
||||
height: '100%',
|
||||
width: '100%'
|
||||
}
|
||||
});
|
||||
|
||||
export default UserAvatar;
|
||||
56
performance/components/UserNames/index.js
Normal file
56
performance/components/UserNames/index.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import AppText from '../AppText';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import React, { PropTypes, PureComponent } from 'react';
|
||||
|
||||
class UserNames extends PureComponent {
|
||||
static displayName = 'UserNames';
|
||||
|
||||
static propTypes = {
|
||||
fullName: PropTypes.string,
|
||||
layout: PropTypes.oneOf(['nowrap', 'stack']),
|
||||
onPress: PropTypes.func,
|
||||
screenName: PropTypes.string,
|
||||
style: PropTypes.object
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
layout: 'nowrap'
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
fullName,
|
||||
layout,
|
||||
onPress,
|
||||
screenName,
|
||||
style,
|
||||
...other
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<AppText
|
||||
{...other}
|
||||
color="deepGray"
|
||||
numberOfLines={layout === 'nowrap' ? 1 : null}
|
||||
onPress={onPress}
|
||||
style={[styles.root, style]}
|
||||
>
|
||||
<AppText color="normal" weight="bold">{fullName}</AppText>
|
||||
{layout === 'stack' ? ' \u000A' : ' '}
|
||||
<AppText color="deepGray" style={styles.screenName}>{`@${screenName}`}</AppText>
|
||||
</AppText>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
display: 'inline-block'
|
||||
},
|
||||
screenName: {
|
||||
unicodeBidi: 'embed',
|
||||
writingDirection: 'ltr'
|
||||
}
|
||||
});
|
||||
|
||||
export default UserNames;
|
||||
7
performance/components/css-modules.js
Normal file
7
performance/components/css-modules.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import Box from './Box/index.css-modules';
|
||||
import View from './View/index.css-modules';
|
||||
|
||||
export default {
|
||||
Box,
|
||||
View
|
||||
};
|
||||
7
performance/components/glamor.js
Normal file
7
performance/components/glamor.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import Box from './Box/index.glamor';
|
||||
import View from './View/index.glamor';
|
||||
|
||||
export default {
|
||||
Box,
|
||||
View
|
||||
};
|
||||
7
performance/components/platform.js
Normal file
7
performance/components/platform.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import Box from './Box/index.platform';
|
||||
import View from './View/index.platform';
|
||||
|
||||
export default {
|
||||
Box,
|
||||
View
|
||||
};
|
||||
9
performance/components/react-native-web.js
vendored
Normal file
9
performance/components/react-native-web.js
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import Box from './Box';
|
||||
import Tweet from './Tweet';
|
||||
import { View } from 'react-native';
|
||||
|
||||
export default {
|
||||
Box,
|
||||
Tweet,
|
||||
View
|
||||
};
|
||||
7
performance/components/styled-components.js
Normal file
7
performance/components/styled-components.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import Box from './Box/index.styled';
|
||||
import View from './View/index.styled';
|
||||
|
||||
export default {
|
||||
Box,
|
||||
View
|
||||
};
|
||||
37
performance/components/theme.js
Normal file
37
performance/components/theme.js
Normal file
@@ -0,0 +1,37 @@
|
||||
const colors = {
|
||||
blue: '#1B95E0',
|
||||
lightBlue: '#71C9F8',
|
||||
green: '#17BF63',
|
||||
orange: '#F45D22',
|
||||
purple: '#794BC4',
|
||||
red: '#E0245E',
|
||||
white: '#FFFFFF',
|
||||
yellow: '#FFAD1F',
|
||||
deepGray: '#657786',
|
||||
fadedGray: '#E6ECF0',
|
||||
faintGray: '#F5F8FA',
|
||||
gray: '#AAB8C2',
|
||||
lightGray: '#CCD6DD',
|
||||
textBlack: '#14171A'
|
||||
};
|
||||
|
||||
const fontSize = {
|
||||
root: '14px',
|
||||
// font scale
|
||||
small: '0.85rem',
|
||||
normal: '1rem',
|
||||
large: '1.25rem'
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
colors,
|
||||
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Helvetica Neue", sans-serif, ' +
|
||||
'"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"', // emoji fonts
|
||||
fontSize,
|
||||
lineHeight: 1.3125,
|
||||
spaceX: 0.6,
|
||||
spaceY: 1.3125,
|
||||
createLength(num, unit) {
|
||||
return `${num}${unit}`;
|
||||
}
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
import Box from './Box';
|
||||
import View from './View';
|
||||
|
||||
export default {
|
||||
Box,
|
||||
View
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
import Box from './Box';
|
||||
import View from './View';
|
||||
|
||||
export default {
|
||||
Box,
|
||||
View
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
import Box from './Box';
|
||||
import View from './View';
|
||||
|
||||
export default {
|
||||
Box,
|
||||
View
|
||||
};
|
||||
@@ -1,2 +0,0 @@
|
||||
import View from 'react-native/components/View';
|
||||
export default View;
|
||||
@@ -1,7 +0,0 @@
|
||||
import Box from './Box';
|
||||
import View from './View';
|
||||
|
||||
export default {
|
||||
Box,
|
||||
View
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
import Box from './Box';
|
||||
import View from './View';
|
||||
|
||||
export default {
|
||||
Box,
|
||||
View
|
||||
};
|
||||
@@ -1,13 +1,16 @@
|
||||
import cssModules from './implementations/css-modules';
|
||||
import glamor from './implementations/glamor';
|
||||
import platform from './implementations/platform';
|
||||
import reactNative from './implementations/react-native-web';
|
||||
import styledComponents from './implementations/styled-components';
|
||||
import cssModules from './components/css-modules';
|
||||
import glamor from './components/glamor';
|
||||
import platform from './components/platform';
|
||||
import reactNative from './components/react-native-web';
|
||||
import styledComponents from './components/styled-components';
|
||||
|
||||
import renderDeepTree from './tests/renderDeepTree';
|
||||
import renderTweet from './tests/renderTweet';
|
||||
import renderWideTree from './tests/renderWideTree';
|
||||
|
||||
const tests = [
|
||||
// tweet
|
||||
() => renderTweet('react-native-web', reactNative),
|
||||
// deep tree
|
||||
() => renderDeepTree('platform', platform),
|
||||
() => renderDeepTree('css-modules', cssModules),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import createRenderBenchmark from '../modules/createRenderBenchmark';
|
||||
import NestedTree from '../modules/NestedTree';
|
||||
import createRenderBenchmark from '../createRenderBenchmark';
|
||||
import NestedTree from '../components/NestedTree';
|
||||
import React from 'react';
|
||||
|
||||
const renderDeepTree = (label, components) => createRenderBenchmark({
|
||||
|
||||
112
performance/tests/renderTweet.js
Normal file
112
performance/tests/renderTweet.js
Normal file
@@ -0,0 +1,112 @@
|
||||
import createRenderBenchmark from '../createRenderBenchmark';
|
||||
import Tweet from '../components/Tweet';
|
||||
import React from 'react';
|
||||
|
||||
const tweet1 = {
|
||||
favorite_count: 30,
|
||||
favorited: true,
|
||||
id: '834889712556875776',
|
||||
lang: 'en',
|
||||
retweet_count: 6,
|
||||
retweeted: false,
|
||||
textParts: [
|
||||
{
|
||||
prefix: '',
|
||||
text: 'Living burrito to burrito '
|
||||
},
|
||||
{
|
||||
emoji: 'https://abs-0.twimg.com/emoji/v2/svg/1f32f.svg',
|
||||
isEmoji: true,
|
||||
prefix: '',
|
||||
text: '🌯'
|
||||
},
|
||||
{
|
||||
emoji: 'https://abs-0.twimg.com/emoji/v2/svg/1f32f.svg',
|
||||
isEmoji: true,
|
||||
prefix: '',
|
||||
text: '🌯'
|
||||
},
|
||||
{
|
||||
emoji: 'https://abs-0.twimg.com/emoji/v2/svg/1f32f.svg',
|
||||
isEmoji: true,
|
||||
prefix: '',
|
||||
text: '🌯'
|
||||
}
|
||||
],
|
||||
timestamp: 'Feb 23',
|
||||
user: {
|
||||
fullName: 'Nicolas',
|
||||
screenName: 'necolas',
|
||||
profileImageUrl: 'https://pbs.twimg.com/profile_images/804365942360719360/dQnPejph_normal.jpg'
|
||||
}
|
||||
};
|
||||
|
||||
const tweet2 = {
|
||||
favorite_count: 84,
|
||||
favorited: false,
|
||||
id: '730896800060579840',
|
||||
lang: 'en',
|
||||
media: {
|
||||
source: {
|
||||
uri: 'https://pbs.twimg.com/media/CiSqvsJVEAAtLZ1.jpg',
|
||||
width: 600,
|
||||
height: 338
|
||||
}
|
||||
},
|
||||
retweet_count: 4,
|
||||
retweeted: true,
|
||||
textParts: [
|
||||
{
|
||||
prefix: '',
|
||||
text: 'Presenting '
|
||||
},
|
||||
{
|
||||
displayUrl: 'mobile.twitter.com',
|
||||
expandedUrl: 'https://mobile.twitter.com',
|
||||
isEntity: true,
|
||||
isUrl: true,
|
||||
linkRelation: 'nofollow',
|
||||
prefix: '',
|
||||
text: '',
|
||||
textDirection: 'ltr',
|
||||
url: 'https://t.co/4hRCAxiUUG'
|
||||
},
|
||||
{
|
||||
prefix: '',
|
||||
text: ' with '
|
||||
},
|
||||
{
|
||||
isEntity: true,
|
||||
isMention: true,
|
||||
prefix: '@',
|
||||
text: 'davidbellona',
|
||||
textDirection: 'ltr',
|
||||
url: '/davidbellona'
|
||||
},
|
||||
{
|
||||
prefix: '',
|
||||
text: " at Twitter's all hands meeting "
|
||||
}
|
||||
],
|
||||
timestamp: 'May 12',
|
||||
user: {
|
||||
fullName: 'Nicolas',
|
||||
screenName: 'necolas',
|
||||
profileImageUrl: 'https://pbs.twimg.com/profile_images/804365942360719360/dQnPejph_normal.jpg'
|
||||
}
|
||||
};
|
||||
|
||||
const renderTweet = (label, components) => createRenderBenchmark({
|
||||
name: `Tweet [${label}]`,
|
||||
runs: 10,
|
||||
getElement() {
|
||||
return (
|
||||
<div style={{ width: 500 }}>
|
||||
<Tweet tweet={tweet1} />
|
||||
<Tweet tweet={tweet2} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default renderTweet;
|
||||
@@ -1,5 +1,5 @@
|
||||
import createRenderBenchmark from '../modules/createRenderBenchmark';
|
||||
import NestedTree from '../modules/NestedTree';
|
||||
import createRenderBenchmark from '../createRenderBenchmark';
|
||||
import NestedTree from '../components/NestedTree';
|
||||
import React from 'react';
|
||||
|
||||
const renderWideTree = (label, components) => createRenderBenchmark({
|
||||
|
||||
Reference in New Issue
Block a user