Unverified Commit b87472f1 authored by Djorkaeff Alexandre's avatar Djorkaeff Alexandre Committed by GitHub
Browse files

[NEW] UiKit Beta (#1497)

parent dc0cabf1
export default {
window: () => null
};
export const uiKitMessage = () => () => null;
export const uiKitModal = () => () => null;
export class UiKitParserMessage {}
export class UiKitParserModal {}
import React from 'react';
import PropTypes from 'prop-types';
import { StyleSheet, Text } from 'react-native';
import { RectButton } from 'react-native-gesture-handler';
import Touchable from 'react-native-platform-touchable';
import { themes } from '../../constants/colors';
import sharedStyles from '../../views/Styles';
import ActivityIndicator from '../ActivityIndicator';
/* eslint-disable react-native/no-unused-styles */
const styles = StyleSheet.create({
container: {
paddingHorizontal: 15,
......@@ -48,9 +47,9 @@ export default class Button extends React.PureComponent {
} = this.props;
const isPrimary = type === 'primary';
return (
<RectButton
<Touchable
onPress={onPress}
enabled={!(disabled || loading)}
disabled={disabled || loading}
style={[
styles.container,
backgroundColor
......@@ -76,7 +75,7 @@ export default class Button extends React.PureComponent {
</Text>
)
}
</RectButton>
</Touchable>
);
}
}
......@@ -9,6 +9,7 @@ import DocumentPicker from 'react-native-document-picker';
import ActionSheet from 'react-native-action-sheet';
import { Q } from '@nozbe/watermelondb';
import { generateTriggerId } from '../../lib/methods/actions';
import TextInput from '../../presentation/TextInput';
import { userTyping as userTypingAction } from '../../actions/room';
import RocketChat from '../../lib/rocketchat';
......@@ -104,7 +105,8 @@ class MessageBox extends Component {
isVisible: false
},
commandPreview: [],
showCommandPreview: false
showCommandPreview: false,
command: {}
};
this.text = '';
this.focused = false;
......@@ -280,7 +282,7 @@ class MessageBox extends Component {
try {
const command = await commandsCollection.find(name);
if (command.providesPreview) {
return this.setCommandPreview(name, params);
return this.setCommandPreview(command, name, params);
}
} catch (e) {
console.log('Slash command not found');
......@@ -339,16 +341,19 @@ class MessageBox extends Component {
}
onPressCommandPreview = (item) => {
const { command } = this.state;
const { rid } = this.props;
const { text } = this;
const command = text.substr(0, text.indexOf(' ')).slice(1);
const name = text.substr(0, text.indexOf(' ')).slice(1);
const params = text.substr(text.indexOf(' ') + 1) || 'params';
this.setState({ commandPreview: [], showCommandPreview: false });
this.setState({ commandPreview: [], showCommandPreview: false, command: {} });
this.stopTrackingMention();
this.clearInput();
this.handleTyping(false);
try {
RocketChat.executeCommandPreview(command, params, rid, item);
const { appId } = command;
const triggerId = generateTriggerId(appId);
RocketChat.executeCommandPreview(name, params, rid, item, triggerId);
} catch (e) {
log(e);
}
......@@ -452,13 +457,13 @@ class MessageBox extends Component {
}, 1000);
}
setCommandPreview = async(command, params) => {
setCommandPreview = async(command, name, params) => {
const { rid } = this.props;
try {
const { preview } = await RocketChat.getCommandPreview(command, rid, params);
this.setState({ commandPreview: preview.items, showCommandPreview: true });
const { preview } = await RocketChat.getCommandPreview(name, rid, params);
this.setState({ commandPreview: preview.items, showCommandPreview: true, command });
} catch (e) {
this.setState({ commandPreview: [], showCommandPreview: true });
this.setState({ commandPreview: [], showCommandPreview: true, command: {} });
log(e);
}
}
......@@ -669,7 +674,9 @@ class MessageBox extends Component {
if (slashCommand.length > 0) {
try {
const messageWithoutCommand = message.replace(/([^\s]+)/, '').trim();
RocketChat.runSlashCommand(command, roomId, messageWithoutCommand);
const [{ appId }] = slashCommand;
const triggerId = generateTriggerId(appId);
RocketChat.runSlashCommand(command, roomId, messageWithoutCommand, triggerId);
} catch (e) {
log(e);
}
......
......@@ -7,6 +7,7 @@ import sharedStyles from '../views/Styles';
import TextInput from '../presentation/TextInput';
import { themes } from '../constants/colors';
import { CustomIcon } from '../lib/Icons';
import ActivityIndicator from './ActivityIndicator';
const styles = StyleSheet.create({
error: {
......@@ -56,6 +57,7 @@ export default class RCTextInput extends React.PureComponent {
static propTypes = {
label: PropTypes.string,
error: PropTypes.object,
loading: PropTypes.bool,
secureTextEntry: PropTypes.bool,
containerStyle: PropTypes.any,
inputStyle: PropTypes.object,
......@@ -102,6 +104,11 @@ export default class RCTextInput extends React.PureComponent {
);
}
get loading() {
const { theme } = this.props;
return <ActivityIndicator style={[styles.iconContainer, styles.iconRight, { color: themes[theme].bodyText }]} />;
}
tooglePassword = () => {
this.setState(prevState => ({ showPassword: !prevState.showPassword }));
}
......@@ -109,7 +116,7 @@ export default class RCTextInput extends React.PureComponent {
render() {
const { showPassword } = this.state;
const {
label, error, secureTextEntry, containerStyle, inputRef, iconLeft, inputStyle, testID, placeholder, theme, ...inputProps
label, error, loading, secureTextEntry, containerStyle, inputRef, iconLeft, inputStyle, testID, placeholder, theme, ...inputProps
} = this.props;
const { dangerColor } = themes[theme];
return (
......@@ -131,10 +138,6 @@ export default class RCTextInput extends React.PureComponent {
<TextInput
style={[
styles.input,
error.error && {
color: dangerColor,
borderColor: dangerColor
},
iconLeft && styles.inputIconLeft,
secureTextEntry && styles.inputIconRight,
{
......@@ -142,6 +145,10 @@ export default class RCTextInput extends React.PureComponent {
borderColor: themes[theme].separatorColor,
color: themes[theme].titleText
},
error.error && {
color: dangerColor,
borderColor: dangerColor
},
inputStyle
]}
ref={inputRef}
......@@ -158,8 +165,9 @@ export default class RCTextInput extends React.PureComponent {
/>
{iconLeft ? this.iconLeft : null}
{secureTextEntry ? this.iconPassword : null}
{loading ? this.loading : null}
</View>
{error.error ? <Text style={[styles.error, { color: dangerColor }]}>{error.reason}</Text> : null}
{error && error.reason ? <Text style={[styles.error, { color: dangerColor }]}>{error.reason}</Text> : null}
</View>
);
}
......
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
import Button from '../Button';
import I18n from '../../i18n';
export const Actions = ({
blockId, appId, elements, parser, theme
}) => {
const [showMoreVisible, setShowMoreVisible] = useState(() => elements.length > 5);
const renderedElements = showMoreVisible ? elements.slice(0, 5) : elements;
const Elements = () => renderedElements
.map(element => parser.renderActions({ blockId, appId, ...element }, BLOCK_CONTEXT.ACTION, parser));
return (
<>
<Elements />
{showMoreVisible && (<Button theme={theme} title={I18n.t('Show_more')} onPress={() => setShowMoreVisible(false)} />)}
</>
);
};
Actions.propTypes = {
blockId: PropTypes.string,
appId: PropTypes.string,
elements: PropTypes.array,
parser: PropTypes.object,
theme: PropTypes.string
};
import React from 'react';
import { View, StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
const styles = StyleSheet.create({
container: {
minHeight: 36,
alignItems: 'center',
flexDirection: 'row'
}
});
export const Context = ({ elements, parser }) => (
<View style={styles.container}>
{elements.map(element => parser.renderContext(element, BLOCK_CONTEXT.CONTEXT, parser))}
</View>
);
Context.propTypes = {
elements: PropTypes.array,
parser: PropTypes.object
};
import React, { useState } from 'react';
import { View, StyleSheet, Text } from 'react-native';
import PropTypes from 'prop-types';
import DateTimePicker from '@react-native-community/datetimepicker';
import Touchable from 'react-native-platform-touchable';
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
import moment from 'moment';
import Button from '../Button';
import { textParser } from './utils';
import { themes } from '../../constants/colors';
import sharedStyles from '../../views/Styles';
import { CustomIcon } from '../../lib/Icons';
import { isAndroid } from '../../utils/deviceInfo';
import ActivityIndicator from '../ActivityIndicator';
const styles = StyleSheet.create({
input: {
height: 48,
paddingLeft: 16,
borderWidth: StyleSheet.hairlineWidth,
borderRadius: 2,
alignItems: 'center',
flexDirection: 'row'
},
inputText: {
...sharedStyles.textRegular,
fontSize: 14
},
icon: {
right: 16,
position: 'absolute'
},
loading: {
padding: 0
}
});
export const DatePicker = ({
element, language, action, context, theme, loading, value, error
}) => {
const [show, onShow] = useState(false);
const { initial_date, placeholder } = element;
const [currentDate, onChangeDate] = useState(new Date(initial_date || value));
const onChange = ({ nativeEvent: { timestamp } }, date) => {
const newDate = date || new Date(timestamp);
onChangeDate(newDate);
action({ value: moment(newDate).format('YYYY-MM-DD') });
if (isAndroid) {
onShow(false);
}
};
let button = (
<Button
title={textParser([placeholder])}
onPress={() => onShow(!show)}
loading={loading}
theme={theme}
/>
);
if (context === BLOCK_CONTEXT.FORM) {
button = (
<Touchable
onPress={() => onShow(!show)}
style={{ backgroundColor: themes[theme].backgroundColor }}
background={Touchable.Ripple(themes[theme].bannerBackground)}
>
<View style={[styles.input, { borderColor: error ? themes[theme].dangerColor : themes[theme].separatorColor }]}>
<Text
style={[
styles.inputText,
{ color: error ? themes[theme].dangerColor : themes[theme].titleText }
]}
>
{currentDate.toLocaleDateString(language)}
</Text>
{
loading
? <ActivityIndicator style={[styles.loading, styles.icon]} />
: <CustomIcon name='calendar' size={20} color={error ? themes[theme].dangerColor : themes[theme].auxiliaryText} style={styles.icon} />
}
</View>
</Touchable>
);
}
const content = show ? (
<DateTimePicker
mode='date'
display='default'
value={currentDate}
onChange={onChange}
textColor={themes[theme].titleText}
/>
) : null;
return (
<>
{button}
{content}
</>
);
};
DatePicker.propTypes = {
element: PropTypes.object,
language: PropTypes.string,
action: PropTypes.func,
context: PropTypes.number,
loading: PropTypes.bool,
theme: PropTypes.string,
value: PropTypes.string,
error: PropTypes.string
};
import React from 'react';
import { StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
import Separator from '../Separator';
const styles = StyleSheet.create({
separator: {
width: '100%',
alignSelf: 'center',
marginBottom: 16
}
});
export const Divider = ({ theme }) => <Separator style={styles.separator} theme={theme} />;
Divider.propTypes = {
theme: PropTypes.string
};
import React from 'react';
import { View, StyleSheet } from 'react-native';
import FastImage from 'react-native-fast-image';
import PropTypes from 'prop-types';
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
import ImageContainer from '../message/Image';
import Navigation from '../../lib/Navigation';
const styles = StyleSheet.create({
image: {
borderRadius: 2
},
mediaContext: {
marginRight: 8
}
});
const ThumbContext = args => <View style={styles.mediaContext}><Thumb size={20} {...args} /></View>;
export const Thumb = ({ element, size = 88 }) => (
<FastImage
style={[{ width: size, height: size }, styles.image]}
source={{ uri: element.imageUrl }}
/>
);
Thumb.propTypes = {
element: PropTypes.object,
size: PropTypes.number
};
export const Media = ({ element, theme }) => {
const showAttachment = attachment => Navigation.navigate('AttachmentView', { attachment });
const { imageUrl } = element;
return (
<ImageContainer
file={{ image_url: imageUrl }}
imageUrl={imageUrl}
showAttachment={showAttachment}
theme={theme}
/>
);
};
Media.propTypes = {
element: PropTypes.object,
theme: PropTypes.string
};
const genericImage = (element, context, theme) => {
switch (context) {
case BLOCK_CONTEXT.SECTION:
return <Thumb element={element} />;
case BLOCK_CONTEXT.CONTEXT:
return <ThumbContext element={element} />;
default:
return <Media element={element} theme={theme} />;
}
};
export const Image = ({ element, context, theme }) => genericImage(element, context, theme);
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import PropTypes from 'prop-types';
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
import sharedStyles from '../../views/Styles';
import { themes } from '../../constants/colors';
const styles = StyleSheet.create({
container: {
marginBottom: 16
},
label: {
fontSize: 14,
marginVertical: 10,
...sharedStyles.textSemibold
},
description: {
marginBottom: 10,
fontSize: 15,
...sharedStyles.textRegular
},
error: {
marginTop: 8,
fontSize: 14,
...sharedStyles.textRegular,
...sharedStyles.textAlignCenter
},
hint: {
fontSize: 14,
...sharedStyles.textRegular
}
});
export const Input = ({
element, parser, label, description, error, hint, theme
}) => (
<View style={styles.container}>
{label ? <Text style={[styles.label, { color: error ? themes[theme].dangerColor : themes[theme].titleText }]}>{label}</Text> : null}
{description ? <Text style={[styles.description, { color: themes[theme].auxiliaryText }]}>{description}</Text> : null}
{parser.renderInputs({ ...element }, BLOCK_CONTEXT.FORM, parser)}
{error ? <Text style={[styles.error, { color: themes[theme].dangerColor }]}>{error}</Text> : null}
{hint ? <Text style={[styles.hint, { color: themes[theme].auxiliaryText }]}>{hint}</Text> : null}
</View>
);
Input.propTypes = {
element: PropTypes.object,
parser: PropTypes.object,
label: PropTypes.string,
description: PropTypes.string,
error: PropTypes.string,
hint: PropTypes.string,
theme: PropTypes.string
};
import React from 'react';
import PropTypes from 'prop-types';
import { UiKitMessage, UiKitModal } from './index';
import { KitContext } from './utils';
export const messageBlockWithContext = context => props => (
<KitContext.Provider value={context}>
<MessageBlock {...props} />
</KitContext.Provider>
);
const MessageBlock = ({ blocks }) => UiKitMessage(blocks);
MessageBlock.propTypes = {
blocks: PropTypes.any
};
export const modalBlockWithContext = context => data => (
<KitContext.Provider value={{ ...context, ...data }}>
<ModalBlock {...data} />
</KitContext.Provider>
);
const ModalBlock = ({ blocks }) => UiKitModal(blocks);
ModalBlock.propTypes = {
blocks: PropTypes.any
};
import React from 'react';
import { Text, View, Image } from 'react-native';
import PropTypes from 'prop-types';
import Touchable from 'react-native-platform-touchable';
import { themes } from '../../../constants/colors';
import { textParser } from '../utils';
import { CustomIcon } from '../../../lib/Icons';
import styles from './styles';
const keyExtractor = item => item.value.toString();
const Chip = ({ item, onSelect, theme }) => (
<Touchable
key={item.value}
onPress={() => onSelect(item)}
style={[styles.chip, { backgroundColor: themes[theme].auxiliaryBackground }]}
background={Touchable.Ripple(themes[theme].bannerBackground)}
>
<>
{item.imageUrl ? <Image style={styles.chipImage} source={{ uri: item.imageUrl }} /> : null}
<Text style={[styles.chipText, { color: themes[theme].titleText }]}>{textParser([item.text])}</Text>
<CustomIcon name='cross' size={16} color={themes[theme].auxiliaryText} />
</>
</Touchable>
);
Chip.propTypes = {
item: PropTypes.object,
onSelect: PropTypes.func,
theme: PropTypes.string
};
const Chips = ({ items, onSelect, theme }) => (
<View style={styles.chips}>
{items.map(item => <Chip key={keyExtractor(item)} item={item} onSelect={onSelect} theme={theme} />)}
</View>
);
Chips.propTypes = {
items: PropTypes.array,
onSelect: PropTypes.func,
theme: PropTypes.string
};
export default Chips;
import React from 'react';
import { View } from 'react-native';
import PropTypes from 'prop-types';
import Touchable from 'react-native-platform-touchable';
import { CustomIcon } from '../../../lib/Icons';
import { themes } from '../../../constants/colors';
import ActivityIndicator from '../../ActivityIndicator';
import styles from './styles';
const Input = ({
children, open, theme, loading