Permalink
Browse files

Add example "Slack" UI (#649)

* Add example SlackMessage, SlackBubble, and App.js

* Add Slack example screenshots and update READMEs

* Update screenshots to point to master
  • Loading branch information...
cooperka committed Dec 23, 2017
1 parent 14562a1 commit edf8fffcb196170e5bdecd077f0065731d2c3f93
View
@@ -101,7 +101,11 @@ class Example extends React.Component {
## Advanced example
See [example/App.js](example/App.js) for a working demo!
See [`example/App.js`](example/App.js) for a working demo!
## "Slack" example
See the files in [`example-slack-message`](example-slack-message) for an example of how to override the default UI to make something that looks more like Slack -- with usernames displayed and all messages on the left.
## Message object
@@ -0,0 +1,9 @@
# "Slack" style UI example
Credit and inspiration comes from [Slack](https://slack.com/).
Screenshots to compare:
| Default style | "Slack" style |
|:-------------:|:-------------:|
| <img src="https://raw.githubusercontent.com/FaridSafi/react-native-gifted-chat/master/example-slack-message/example-default-style.png" alt="Example with default style" width="300"> | <img src="https://raw.githubusercontent.com/FaridSafi/react-native-gifted-chat/master/example-slack-message/example-slack-style.png" alt="Example with Slack style" width="300"> |
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,41 @@
import React, { Component } from 'react';
import { View, Platform } from 'react-native';
import PropTypes from 'prop-types';
import { GiftedChat } from 'react-native-gifted-chat';
import emojiUtils from 'emoji-utils';
import SlackMessage from './SlackMessage';
class App extends Component {
renderMessage(props) {
const { currentMessage: { text: currText } } = props;
let messageTextStyle;
// Make "pure emoji" messages much bigger than plain text.
if (currText && emojiUtils.isPureEmojiString(currText)) {
messageTextStyle = {
fontSize: 28,
// Emoji get clipped if lineHeight isn't increased; make it consistent across platforms.
lineHeight: Platform.OS === 'android' ? 34 : 30,
};
}
return (
<SlackMessage {...props} messageTextStyle={messageTextStyle} />
);
}
render() {
return (
<GiftedChat
messages={[]}
renderMessage={this.renderMessage}
/>
);
}
}
export default App;
@@ -0,0 +1,284 @@
/* eslint-disable no-underscore-dangle, no-use-before-define */
import React from 'react';
import {
Text,
Clipboard,
StyleSheet,
TouchableOpacity,
View,
ViewPropTypes,
Platform,
} from 'react-native';
import { MessageText, MessageImage, Time, utils } from 'react-native-gifted-chat';
const { isSameUser, isSameDay } = utils;
export default class Bubble extends React.Component {
constructor(props) {
super(props);
this.onLongPress = this.onLongPress.bind(this);
}
onLongPress() {
if (this.props.onLongPress) {
this.props.onLongPress(this.context);
} else if (this.props.currentMessage.text) {
const options = [
'Copy Text',
'Cancel',
];
const cancelButtonIndex = options.length - 1;
this.context.actionSheet().showActionSheetWithOptions(
{ options, cancelButtonIndex },
(buttonIndex) => {
if (buttonIndex === 0) {
Clipboard.setString(this.props.currentMessage.text);
}
});
}
}
renderMessageText() {
if (this.props.currentMessage.text) {
const { containerStyle, wrapperStyle, messageTextStyle, ...messageTextProps } = this.props;
if (this.props.renderMessageText) {
return this.props.renderMessageText(messageTextProps);
}
return (
<MessageText
{...messageTextProps}
textStyle={{
left: [styles.standardFont, styles.slackMessageText, messageTextProps.textStyle, messageTextStyle],
}}
/>
);
}
return null;
}
renderMessageImage() {
if (this.props.currentMessage.image) {
const { containerStyle, wrapperStyle, ...messageImageProps } = this.props;
if (this.props.renderMessageImage) {
return this.props.renderMessageImage(messageImageProps);
}
return <MessageImage {...messageImageProps} imageStyle={[styles.slackImage, messageImageProps.imageStyle]} />;
}
return null;
}
renderTicks() {
const { currentMessage } = this.props;
if (this.props.renderTicks) {
return this.props.renderTicks(currentMessage);
}
if (currentMessage.user._id !== this.props.user._id) {
return null;
}
if (currentMessage.sent || currentMessage.received) {
return (
<View style={[styles.headerItem, styles.tickView]}>
{currentMessage.sent && <Text style={[styles.standardFont, styles.tick, this.props.tickStyle]}></Text>}
{currentMessage.received && <Text style={[styles.standardFont, styles.tick, this.props.tickStyle]}></Text>}
</View>
);
}
return null;
}
renderUsername() {
const username = this.props.currentMessage.user.name;
if (username) {
const { containerStyle, wrapperStyle, ...usernameProps } = this.props;
if (this.props.renderUsername) {
return this.props.renderUsername(usernameProps);
}
return (
<Text style={[styles.standardFont, styles.headerItem, styles.username, this.props.usernameStyle]}>
{username}
</Text>
);
}
return null;
}
renderTime() {
if (this.props.currentMessage.createdAt) {
const { containerStyle, wrapperStyle, ...timeProps } = this.props;
if (this.props.renderTime) {
return this.props.renderTime(timeProps);
}
return (
<Time
{...timeProps}
containerStyle={{ left: [styles.timeContainer] }}
textStyle={{ left: [styles.standardFont, styles.headerItem, styles.time, timeProps.textStyle] }}
/>
);
}
return null;
}
renderCustomView() {
if (this.props.renderCustomView) {
return this.props.renderCustomView(this.props);
}
return null;
}
render() {
const isSameThread = isSameUser(this.props.currentMessage, this.props.previousMessage)
&& isSameDay(this.props.currentMessage, this.props.previousMessage);
const messageHeader = isSameThread ? null : (
<View style={styles.headerView}>
{this.renderUsername()}
{this.renderTime()}
{this.renderTicks()}
</View>
);
return (
<View style={[styles.container, this.props.containerStyle]}>
<TouchableOpacity
onLongPress={this.onLongPress}
accessibilityTraits="text"
{...this.props.touchableProps}
>
<View
style={[
styles.wrapper,
this.props.wrapperStyle,
]}
>
<View>
{this.renderCustomView()}
{messageHeader}
{this.renderMessageImage()}
{this.renderMessageText()}
</View>
</View>
</TouchableOpacity>
</View>
);
}
}
// Note: Everything is forced to be "left" positioned with this component.
// The "right" position is only used in the default Bubble.
const styles = StyleSheet.create({
standardFont: {
fontSize: 15,
},
slackMessageText: {
marginLeft: 0,
marginRight: 0,
},
container: {
flex: 1,
alignItems: 'flex-start',
},
wrapper: {
marginRight: 60,
minHeight: 20,
justifyContent: 'flex-end',
},
username: {
fontWeight: 'bold',
},
time: {
textAlign: 'left',
fontSize: 12,
},
timeContainer: {
marginLeft: 0,
marginRight: 0,
marginBottom: 0,
},
headerItem: {
marginRight: 10,
},
headerView: {
// Try to align it better with the avatar on Android.
marginTop: Platform.OS === 'android' ? -2 : 0,
flexDirection: 'row',
alignItems: 'baseline',
},
/* eslint-disable react-native/no-color-literals */
tick: {
backgroundColor: 'transparent',
color: 'white',
},
/* eslint-enable react-native/no-color-literals */
tickView: {
flexDirection: 'row',
},
slackImage: {
borderRadius: 3,
marginLeft: 0,
marginRight: 0,
},
});
Bubble.contextTypes = {
actionSheet: React.PropTypes.func,
};
Bubble.defaultProps = {
touchableProps: {},
onLongPress: null,
renderMessageImage: null,
renderMessageText: null,
renderCustomView: null,
renderTime: null,
currentMessage: {
text: null,
createdAt: null,
image: null,
},
nextMessage: {},
previousMessage: {},
containerStyle: {},
wrapperStyle: {},
tickStyle: {},
containerToNextStyle: {},
containerToPreviousStyle: {},
};
Bubble.propTypes = {
touchableProps: React.PropTypes.object,
onLongPress: React.PropTypes.func,
renderMessageImage: React.PropTypes.func,
renderMessageText: React.PropTypes.func,
renderCustomView: React.PropTypes.func,
renderUsername: React.PropTypes.func,
renderTime: React.PropTypes.func,
renderTicks: React.PropTypes.func,
currentMessage: React.PropTypes.object,
nextMessage: React.PropTypes.object,
previousMessage: React.PropTypes.object,
user: React.PropTypes.object,
containerStyle: React.PropTypes.shape({
left: ViewPropTypes.style,
right: ViewPropTypes.style,
}),
wrapperStyle: React.PropTypes.shape({
left: ViewPropTypes.style,
right: ViewPropTypes.style,
}),
messageTextStyle: Text.propTypes.style,
usernameStyle: Text.propTypes.style,
tickStyle: Text.propTypes.style,
containerToNextStyle: React.PropTypes.shape({
left: ViewPropTypes.style,
right: ViewPropTypes.style,
}),
containerToPreviousStyle: React.PropTypes.shape({
left: ViewPropTypes.style,
right: ViewPropTypes.style,
}),
};
Oops, something went wrong.

0 comments on commit edf8fff

Please sign in to comment.