[RN] Extract AvatarListItem
This commit is contained in:
parent
555f8b3a99
commit
38b1be1291
|
@ -1 +1,3 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
export * from './native';
|
export * from './native';
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
export * from './_';
|
export * from './_';
|
||||||
export { default as AbstractPage } from './AbstractPage';
|
export { default as AbstractPage } from './AbstractPage';
|
||||||
export { default as NavigateSectionList } from './NavigateSectionList';
|
export { default as NavigateSectionList } from './NavigateSectionList';
|
||||||
|
|
|
@ -0,0 +1,203 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { Text } from 'react-native';
|
||||||
|
|
||||||
|
import { Icon } from '../../../font-icons';
|
||||||
|
import { Avatar } from '../../../participants';
|
||||||
|
import { StyleType } from '../../../styles';
|
||||||
|
|
||||||
|
import { type Item } from '../../Types';
|
||||||
|
|
||||||
|
import Container from './Container';
|
||||||
|
import styles, { AVATAR_SIZE, UNDERLAY_COLOR } from './styles';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preferred size of the avatar.
|
||||||
|
*/
|
||||||
|
avatarSize?: number,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* External style to be applied to the avatar (icon).
|
||||||
|
*/
|
||||||
|
avatarStyle?: StyleType,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* External style to be applied to the avatar (text).
|
||||||
|
*/
|
||||||
|
avatarTextStyle?: StyleType,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Children of the component.
|
||||||
|
*/
|
||||||
|
children?: ?React$Element<*>,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* item containing data to be rendered
|
||||||
|
*/
|
||||||
|
item: Item,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* External style prop to be applied to the extra lines.
|
||||||
|
*/
|
||||||
|
linesStyle?: StyleType,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to invoke on press.
|
||||||
|
*/
|
||||||
|
onPress: ?Function,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* External style prop to be applied to the title.
|
||||||
|
*/
|
||||||
|
titleStyle?: StyleType
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a list item with an avatar rendered for it.
|
||||||
|
*/
|
||||||
|
export default class AvatarListItem extends Component<Props> {
|
||||||
|
/**
|
||||||
|
* Constructor of the component.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this._renderItemLine = this._renderItemLine.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements {@code Component#render}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
avatarSize = AVATAR_SIZE,
|
||||||
|
avatarStyle,
|
||||||
|
avatarTextStyle
|
||||||
|
} = this.props;
|
||||||
|
const { avatar, colorBase, lines, title } = this.props.item;
|
||||||
|
const avatarStyles = {
|
||||||
|
...styles.avatar,
|
||||||
|
...this._getAvatarColor(colorBase),
|
||||||
|
...avatarStyle,
|
||||||
|
borderRadius: avatarSize / 2,
|
||||||
|
height: avatarSize,
|
||||||
|
width: avatarSize
|
||||||
|
};
|
||||||
|
|
||||||
|
const isAvatarURL = Boolean(avatar && avatar.match(/^http[s]*:\/\//i));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container
|
||||||
|
onClick = { this.props.onPress }
|
||||||
|
style = { styles.listItem }
|
||||||
|
underlayColor = { UNDERLAY_COLOR }>
|
||||||
|
<Container style = { styles.avatarContainer }>
|
||||||
|
<Container style = { avatarStyles }>
|
||||||
|
{
|
||||||
|
isAvatarURL && <Avatar
|
||||||
|
size = { avatarSize }
|
||||||
|
uri = { avatar } />
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Boolean(avatar && !isAvatarURL) && <Icon
|
||||||
|
name = { avatar } />
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
!avatar && <Text
|
||||||
|
style = { [
|
||||||
|
styles.avatarContent,
|
||||||
|
avatarTextStyle
|
||||||
|
] }>
|
||||||
|
{ title.substr(0, 1).toUpperCase() }
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
</Container>
|
||||||
|
</Container>
|
||||||
|
<Container style = { styles.listItemDetails }>
|
||||||
|
<Text
|
||||||
|
numberOfLines = { 1 }
|
||||||
|
style = { [
|
||||||
|
styles.listItemText,
|
||||||
|
styles.listItemTitle,
|
||||||
|
this.props.titleStyle
|
||||||
|
] }>
|
||||||
|
{ title }
|
||||||
|
</Text>
|
||||||
|
{this._renderItemLines(lines)}
|
||||||
|
</Container>
|
||||||
|
{ this.props.children }
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a style (color) based on the string that determines the color of
|
||||||
|
* the avatar.
|
||||||
|
*
|
||||||
|
* @param {string} colorBase - The string that is the base of the color.
|
||||||
|
* @private
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
_getAvatarColor(colorBase) {
|
||||||
|
if (!colorBase) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let nameHash = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < colorBase.length; i++) {
|
||||||
|
nameHash += colorBase.codePointAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return styles[`avatarColor${(nameHash % 5) + 1}`];
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderItemLine: (string, number) => React$Node;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a single line from the additional lines.
|
||||||
|
*
|
||||||
|
* @param {string} line - The line text.
|
||||||
|
* @param {number} index - The index of the line.
|
||||||
|
* @private
|
||||||
|
* @returns {React$Node}
|
||||||
|
*/
|
||||||
|
_renderItemLine(line, index) {
|
||||||
|
if (!line) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
key = { index }
|
||||||
|
numberOfLines = { 1 }
|
||||||
|
style = { [
|
||||||
|
styles.listItemText,
|
||||||
|
this.props.linesStyle
|
||||||
|
] }>
|
||||||
|
{line}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderItemLines: Array<string> => Array<React$Node>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the additional item lines, if any.
|
||||||
|
*
|
||||||
|
* @param {Array<string>} lines - The lines to render.
|
||||||
|
* @private
|
||||||
|
* @returns {Array<React$Node>}
|
||||||
|
*/
|
||||||
|
_renderItemLines(lines) {
|
||||||
|
return lines && lines.length ? lines.map(this._renderItemLine) : null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,9 +7,11 @@ import { ColorPalette } from '../../../styles';
|
||||||
|
|
||||||
import Container from './Container';
|
import Container from './Container';
|
||||||
import Text from './Text';
|
import Text from './Text';
|
||||||
import styles, { UNDERLAY_COLOR } from './styles';
|
import styles from './styles';
|
||||||
import type { Item } from '../../Types';
|
import type { Item } from '../../Types';
|
||||||
|
|
||||||
|
import AvatarListItem from './AvatarListItem';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,34 +50,11 @@ export default class NavigateSectionListItem extends Component<Props> {
|
||||||
*/
|
*/
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
this._getAvatarColor = this._getAvatarColor.bind(this);
|
|
||||||
this._renderItemLine = this._renderItemLine.bind(this);
|
this._renderItemLine = this._renderItemLine.bind(this);
|
||||||
this._renderItemLines = this._renderItemLines.bind(this);
|
this._renderItemLines = this._renderItemLines.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
_getAvatarColor: string => Object;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a style (color) based on the string that determines the color of
|
|
||||||
* the avatar.
|
|
||||||
*
|
|
||||||
* @param {string} colorBase - The string that is the base of the color.
|
|
||||||
* @private
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
_getAvatarColor(colorBase) {
|
|
||||||
if (!colorBase) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
let nameHash = 0;
|
|
||||||
|
|
||||||
for (let i = 0; i < colorBase.length; i++) {
|
|
||||||
nameHash += colorBase.codePointAt(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
return styles[`avatarColor${(nameHash % 5) + 1}`];
|
|
||||||
}
|
|
||||||
|
|
||||||
_renderItemLine: (string, number) => React$Node;
|
_renderItemLine: (string, number) => React$Node;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -138,12 +117,8 @@ export default class NavigateSectionListItem extends Component<Props> {
|
||||||
* @returns {ReactElement}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { slideActions } = this.props;
|
const { item, slideActions } = this.props;
|
||||||
const { id, colorBase, lines, title } = this.props.item;
|
const { id } = item;
|
||||||
const avatarStyles = {
|
|
||||||
...styles.avatar,
|
|
||||||
...this._getAvatarColor(colorBase)
|
|
||||||
};
|
|
||||||
let right;
|
let right;
|
||||||
|
|
||||||
// NOTE: The {@code Swipeout} component has an onPress prop encapsulated
|
// NOTE: The {@code Swipeout} component has an onPress prop encapsulated
|
||||||
|
@ -165,31 +140,12 @@ export default class NavigateSectionListItem extends Component<Props> {
|
||||||
<Swipeout
|
<Swipeout
|
||||||
backgroundColor = { ColorPalette.transparent }
|
backgroundColor = { ColorPalette.transparent }
|
||||||
right = { right }>
|
right = { right }>
|
||||||
<Container
|
<AvatarListItem
|
||||||
onClick = { this.props.onPress }
|
item = { item }
|
||||||
style = { styles.listItem }
|
onPress = { this.props.onPress }>
|
||||||
underlayColor = { UNDERLAY_COLOR }>
|
|
||||||
<Container style = { styles.avatarContainer }>
|
|
||||||
<Container style = { avatarStyles }>
|
|
||||||
<Text style = { styles.avatarContent }>
|
|
||||||
{title.substr(0, 1).toUpperCase()}
|
|
||||||
</Text>
|
|
||||||
</Container>
|
|
||||||
</Container>
|
|
||||||
<Container style = { styles.listItemDetails }>
|
|
||||||
<Text
|
|
||||||
numberOfLines = { 1 }
|
|
||||||
style = { [
|
|
||||||
styles.listItemText,
|
|
||||||
styles.listItemTitle
|
|
||||||
] }>
|
|
||||||
{title}
|
|
||||||
</Text>
|
|
||||||
{this._renderItemLines(lines)}
|
|
||||||
</Container>
|
|
||||||
{ this.props.secondaryAction
|
{ this.props.secondaryAction
|
||||||
&& this._renderSecondaryAction() }
|
&& this._renderSecondaryAction() }
|
||||||
</Container>
|
</AvatarListItem>
|
||||||
</Swipeout>
|
</Swipeout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
export { default as AvatarListItem } from './AvatarListItem';
|
||||||
export { default as BackButton } from './BackButton';
|
export { default as BackButton } from './BackButton';
|
||||||
export { default as Container } from './Container';
|
export { default as Container } from './Container';
|
||||||
export { default as Header } from './Header';
|
export { default as Header } from './Header';
|
||||||
|
|
|
@ -5,7 +5,6 @@ import { StyleSheet } from 'react-native';
|
||||||
import { BoxModel, ColorPalette, createStyleSheet } from '../../../styles';
|
import { BoxModel, ColorPalette, createStyleSheet } from '../../../styles';
|
||||||
|
|
||||||
const AVATAR_OPACITY = 0.4;
|
const AVATAR_OPACITY = 0.4;
|
||||||
const AVATAR_SIZE = 65;
|
|
||||||
const HEADER_COLOR = ColorPalette.blue;
|
const HEADER_COLOR = ColorPalette.blue;
|
||||||
|
|
||||||
// Header height is from Android guidelines. Also, this looks good.
|
// Header height is from Android guidelines. Also, this looks good.
|
||||||
|
@ -13,6 +12,7 @@ const HEADER_HEIGHT = 56;
|
||||||
const OVERLAY_FONT_COLOR = 'rgba(255, 255, 255, 0.6)';
|
const OVERLAY_FONT_COLOR = 'rgba(255, 255, 255, 0.6)';
|
||||||
const SECONDARY_ACTION_BUTTON_SIZE = 30;
|
const SECONDARY_ACTION_BUTTON_SIZE = 30;
|
||||||
|
|
||||||
|
export const AVATAR_SIZE = 65;
|
||||||
export const HEADER_PADDING = BoxModel.padding;
|
export const HEADER_PADDING = BoxModel.padding;
|
||||||
export const STATUSBAR_COLOR = ColorPalette.blueHighlight;
|
export const STATUSBAR_COLOR = ColorPalette.blueHighlight;
|
||||||
export const SIDEBAR_WIDTH = 250;
|
export const SIDEBAR_WIDTH = 250;
|
||||||
|
@ -153,10 +153,7 @@ const SECTION_LIST_STYLES = {
|
||||||
avatar: {
|
avatar: {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: `rgba(23, 160, 219, ${AVATAR_OPACITY})`,
|
backgroundColor: `rgba(23, 160, 219, ${AVATAR_OPACITY})`,
|
||||||
borderRadius: AVATAR_SIZE,
|
justifyContent: 'center'
|
||||||
height: AVATAR_SIZE,
|
|
||||||
justifyContent: 'center',
|
|
||||||
width: AVATAR_SIZE
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -200,7 +197,7 @@ const SECTION_LIST_STYLES = {
|
||||||
avatarContent: {
|
avatarContent: {
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0)',
|
backgroundColor: 'rgba(0, 0, 0, 0)',
|
||||||
color: OVERLAY_FONT_COLOR,
|
color: OVERLAY_FONT_COLOR,
|
||||||
fontSize: 32,
|
fontSize: Math.floor(AVATAR_SIZE / 2),
|
||||||
fontWeight: '100',
|
fontWeight: '100',
|
||||||
textAlign: 'center'
|
textAlign: 'center'
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue