[IMPROVE] Migrate UIKit/MultiSelect to ActionSheet (#4255)

* Migrate UIKit/MultiSelect to ActionSheet

* Fix no options initially on CreateDiscussion  view

* Change backgroundColor and use colors from useTheme

* Define missing types

* onSearch function for the ActionSheet

* Add onClose function to the ActionSheet and use colors from useTheme

* fix theme and bottomSheet

* fix actionSheet

* fix style

Co-authored-by: GleidsonDaniel <gleidson10daniel@hotmail.com>
This commit is contained in:
Danish Ahmed Mirza 2022-07-14 01:01:58 +05:30 committed by GitHub
parent 785ae0325b
commit dd48402214
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 232 additions and 212 deletions

View File

@ -101,6 +101,11 @@ const ActionSheet = React.memo(
</> </>
); );
const onClose = () => {
toggleVisible();
data?.onClose && data?.onClose();
};
const renderBackdrop = useCallback( const renderBackdrop = useCallback(
props => ( props => (
<BottomSheetBackdrop <BottomSheetBackdrop
@ -134,7 +139,7 @@ const ActionSheet = React.memo(
enablePanDownToClose enablePanDownToClose
style={{ ...styles.container, ...bottomSheet }} style={{ ...styles.container, ...bottomSheet }}
backgroundStyle={{ backgroundColor: colors.focusedBackground }} backgroundStyle={{ backgroundColor: colors.focusedBackground }}
onChange={index => index === -1 && toggleVisible()} onChange={index => index === -1 && onClose()}
{...androidTablet}> {...androidTablet}>
<BottomSheetContent options={data?.options} hide={hide} children={data?.children} hasCancel={data?.hasCancel} /> <BottomSheetContent options={data?.options} hide={hide} children={data?.children} hasCancel={data?.hasCancel} />
</BottomSheet> </BottomSheet>

View File

@ -53,7 +53,7 @@ const BottomSheetContent = React.memo(({ options, hasCancel, hide, children }: I
/> />
); );
} }
return <BottomSheetView>{children}</BottomSheetView>; return <BottomSheetView style={styles.contentContainer}>{children}</BottomSheetView>;
}); });
export default BottomSheetContent; export default BottomSheetContent;

View File

@ -8,7 +8,7 @@ import { useTheme } from '../../theme';
export const Handle = React.memo(() => { export const Handle = React.memo(() => {
const { theme } = useTheme(); const { theme } = useTheme();
return ( return (
<View style={[styles.handle, { backgroundColor: themes[theme].focusedBackground }]} testID='action-sheet-handle'> <View style={[styles.handle]} testID='action-sheet-handle'>
<View style={[styles.handleIndicator, { backgroundColor: themes[theme].auxiliaryText }]} /> <View style={[styles.handleIndicator, { backgroundColor: themes[theme].auxiliaryText }]} />
</View> </View>
); );

View File

@ -20,7 +20,8 @@ export type TActionSheetOptions = {
hasCancel?: boolean; hasCancel?: boolean;
type?: string; type?: string;
children?: React.ReactElement | null; children?: React.ReactElement | null;
snaps?: string[] | number[]; snaps?: (string | number)[];
onClose?: () => void;
}; };
export interface IActionSheetProvider { export interface IActionSheetProvider {
showActionSheet: (item: TActionSheetOptions) => void; showActionSheet: (item: TActionSheetOptions) => void;

View File

@ -63,5 +63,15 @@ export default StyleSheet.create({
}, },
rightContainer: { rightContainer: {
paddingLeft: 12 paddingLeft: 12
},
footerButtonsContainer: {
flexDirection: 'row',
paddingTop: 16
},
buttonSeparator: {
marginRight: 8
},
contentContainer: {
flex: 1
} }
}); });

View File

@ -3,50 +3,50 @@ import { Text, View } from 'react-native';
import Touchable from 'react-native-platform-touchable'; import Touchable from 'react-native-platform-touchable';
import FastImage from 'react-native-fast-image'; import FastImage from 'react-native-fast-image';
import { themes } from '../../../lib/constants';
import { textParser } from '../utils'; import { textParser } from '../utils';
import { CustomIcon } from '../../CustomIcon'; import { CustomIcon } from '../../CustomIcon';
import styles from './styles'; import styles from './styles';
import { IItemData } from '.'; import { IItemData } from '.';
import { TSupportedThemes } from '../../../theme'; import { useTheme } from '../../../theme';
interface IChip { interface IChip {
item: IItemData; item: IItemData;
onSelect: (item: IItemData) => void; onSelect: (item: IItemData) => void;
style?: object; style?: object;
theme: TSupportedThemes;
} }
interface IChips { interface IChips {
items: IItemData[]; items: IItemData[];
onSelect: (item: IItemData) => void; onSelect: (item: IItemData) => void;
style?: object; style?: object;
theme: TSupportedThemes;
} }
const keyExtractor = (item: IItemData) => item.value.toString(); const keyExtractor = (item: IItemData) => item.value.toString();
const Chip = ({ item, onSelect, style, theme }: IChip) => ( const Chip = ({ item, onSelect, style }: IChip) => {
const { colors } = useTheme();
return (
<Touchable <Touchable
key={item.value} key={item.value}
onPress={() => onSelect(item)} onPress={() => onSelect(item)}
style={[styles.chip, { backgroundColor: themes[theme].auxiliaryBackground }, style]} style={[styles.chip, { backgroundColor: colors.auxiliaryBackground }, style]}
background={Touchable.Ripple(themes[theme].bannerBackground)}> background={Touchable.Ripple(colors.bannerBackground)}>
<> <>
{item.imageUrl ? <FastImage style={styles.chipImage} source={{ uri: item.imageUrl }} /> : null} {item.imageUrl ? <FastImage style={styles.chipImage} source={{ uri: item.imageUrl }} /> : null}
<Text numberOfLines={1} style={[styles.chipText, { color: themes[theme].titleText }]}> <Text numberOfLines={1} style={[styles.chipText, { color: colors.titleText }]}>
{textParser([item.text])} {textParser([item.text])}
</Text> </Text>
<CustomIcon name='close' size={16} color={themes[theme].auxiliaryText} /> <CustomIcon name='close' size={16} color={colors.auxiliaryText} />
</> </>
</Touchable> </Touchable>
); );
};
Chip.propTypes = {}; Chip.propTypes = {};
const Chips = ({ items, onSelect, style, theme }: IChips) => ( const Chips = ({ items, onSelect, style }: IChips) => (
<View style={styles.chips}> <View style={styles.chips}>
{items.map(item => ( {items.map(item => (
<Chip key={keyExtractor(item)} item={item} onSelect={onSelect} style={style} theme={theme} /> <Chip key={keyExtractor(item)} item={item} onSelect={onSelect} style={style} />
))} ))}
</View> </View>
); );

View File

@ -3,15 +3,13 @@ import { Text, View } from 'react-native';
import Touchable from 'react-native-platform-touchable'; import Touchable from 'react-native-platform-touchable';
import { CustomIcon } from '../../CustomIcon'; import { CustomIcon } from '../../CustomIcon';
import { themes } from '../../../lib/constants';
import ActivityIndicator from '../../ActivityIndicator'; import ActivityIndicator from '../../ActivityIndicator';
import styles from './styles'; import styles from './styles';
import { TSupportedThemes } from '../../../theme'; import { useTheme } from '../../../theme';
interface IInput { interface IInput {
children?: JSX.Element; children?: JSX.Element;
onPress: () => void; onPress: () => void;
theme: TSupportedThemes;
inputStyle?: object; inputStyle?: object;
disabled?: boolean | null; disabled?: boolean | null;
placeholder?: string; placeholder?: string;
@ -19,21 +17,23 @@ interface IInput {
innerInputStyle?: object; innerInputStyle?: object;
} }
const Input = ({ children, onPress, theme, loading, inputStyle, placeholder, disabled, innerInputStyle }: IInput) => ( const Input = ({ children, onPress, loading, inputStyle, placeholder, disabled, innerInputStyle }: IInput) => {
const { colors } = useTheme();
return (
<Touchable <Touchable
onPress={onPress} onPress={onPress}
style={[{ backgroundColor: themes[theme].backgroundColor }, inputStyle]} style={[{ backgroundColor: colors.backgroundColor }, inputStyle]}
background={Touchable.Ripple(themes[theme].bannerBackground)} background={Touchable.Ripple(colors.bannerBackground)}
disabled={disabled}> disabled={disabled}>
<View style={[styles.input, { borderColor: themes[theme].separatorColor }, innerInputStyle]}> <View style={[styles.input, { borderColor: colors.separatorColor }, innerInputStyle]}>
{placeholder ? <Text style={[styles.pickerText, { color: themes[theme].auxiliaryText }]}>{placeholder}</Text> : children} {placeholder ? <Text style={[styles.pickerText, { color: colors.auxiliaryText }]}>{placeholder}</Text> : children}
{loading ? ( {loading ? (
<ActivityIndicator style={[styles.loading, styles.icon]} /> <ActivityIndicator style={styles.icon} />
) : ( ) : (
<CustomIcon name='chevron-down' size={22} color={themes[theme].auxiliaryText} style={styles.icon} /> <CustomIcon name='chevron-down' size={22} color={colors.auxiliaryText} style={styles.icon} />
)} )}
</View> </View>
</Touchable> </Touchable>
); );
};
export default Input; export default Input;

View File

@ -1,61 +1,54 @@
import React from 'react'; import React from 'react';
import { FlatList, Text } from 'react-native'; import { Text } from 'react-native';
import Touchable from 'react-native-platform-touchable'; import Touchable from 'react-native-platform-touchable';
import FastImage from 'react-native-fast-image'; import FastImage from 'react-native-fast-image';
import { FlatList } from 'react-native-gesture-handler';
import Check from '../../Check'; import Check from '../../Check';
import * as List from '../../List'; import * as List from '../../List';
import { textParser } from '../utils'; import { textParser } from '../utils';
import { themes } from '../../../lib/constants';
import styles from './styles'; import styles from './styles';
import { IItemData } from '.'; import { IItemData } from '.';
import { TSupportedThemes } from '../../../theme'; import { useTheme } from '../../../theme';
interface IItem { interface IItem {
item: IItemData; item: IItemData;
selected?: string; selected?: string;
onSelect: Function; onSelect: Function;
theme: TSupportedThemes;
} }
interface IItems { interface IItems {
items: IItemData[]; items: IItemData[];
selected: string[]; selected: string[];
onSelect: Function; onSelect: Function;
theme: TSupportedThemes;
} }
const keyExtractor = (item: IItemData) => item.value.toString(); const keyExtractor = (item: IItemData) => item.value?.name || item.text?.text;
// RectButton doesn't work on modal (Android) // RectButton doesn't work on modal (Android)
const Item = ({ item, selected, onSelect, theme }: IItem) => { const Item = ({ item, selected, onSelect }: IItem) => {
const itemName = item.value?.name || item.text.text.toLowerCase(); const itemName = item.value?.name || item.text.text.toLowerCase();
const { colors } = useTheme();
return ( return (
<Touchable <Touchable testID={`multi-select-item-${itemName}`} key={itemName} onPress={() => onSelect(item)} style={[styles.item]}>
testID={`multi-select-item-${itemName}`}
key={itemName}
onPress={() => onSelect(item)}
style={[styles.item, { backgroundColor: themes[theme].backgroundColor }]}>
<> <>
{item.imageUrl ? <FastImage style={styles.itemImage} source={{ uri: item.imageUrl }} /> : null} {item.imageUrl ? <FastImage style={styles.itemImage} source={{ uri: item.imageUrl }} /> : null}
<Text style={{ color: themes[theme].titleText }}>{textParser([item.text])}</Text> <Text style={{ color: colors.titleText }}>{textParser([item.text])}</Text>
{selected ? <Check /> : null} {selected ? <Check /> : null}
</> </>
</Touchable> </Touchable>
); );
}; };
const Items = ({ items, selected, onSelect, theme }: IItems) => ( const Items = ({ items, selected, onSelect }: IItems) => (
<FlatList <FlatList
data={items} data={items}
style={[styles.items, { backgroundColor: themes[theme].backgroundColor }]} style={[styles.items]}
contentContainerStyle={[styles.itemContent, { backgroundColor: themes[theme].backgroundColor }]} contentContainerStyle={[styles.itemContent]}
keyboardShouldPersistTaps='always' keyboardShouldPersistTaps='always'
ItemSeparatorComponent={List.Separator} ItemSeparatorComponent={List.Separator}
keyExtractor={keyExtractor} keyExtractor={keyExtractor}
renderItem={({ item }) => ( renderItem={({ item }) => <Item item={item} onSelect={onSelect} selected={selected.find(s => s === item.value)} />}
<Item item={item} onSelect={onSelect} theme={theme} selected={selected.find(s => s === item.value)} />
)}
/> />
); );

View File

@ -0,0 +1,85 @@
import React, { useState } from 'react';
import { View } from 'react-native';
import { FormTextInput } from '../../TextInput/FormTextInput';
import { textParser } from '../utils';
import I18n from '../../../i18n';
import Items from './Items';
import styles from './styles';
import { useTheme } from '../../../theme';
import { IItemData } from '.';
import { debounce } from '../../../lib/methods/helpers/debounce';
import { isIOS } from '../../../lib/methods/helpers';
import { useActionSheet } from '../../ActionSheet';
interface IMultiSelectContentProps {
onSearch?: (keyword: string) => IItemData[] | Promise<IItemData[] | undefined>;
options?: IItemData[];
multiselect: boolean;
select: React.Dispatch<any>;
onChange: Function;
setCurrentValue: React.Dispatch<React.SetStateAction<string>>;
onHide: Function;
selectedItems: string[];
}
export const MultiSelectContent = React.memo(
({ onSearch, options, multiselect, select, onChange, setCurrentValue, onHide, selectedItems }: IMultiSelectContentProps) => {
const { colors } = useTheme();
const [selected, setSelected] = useState<string[]>(Array.isArray(selectedItems) ? selectedItems : []);
const [items, setItems] = useState<IItemData[] | undefined>(options);
const { hideActionSheet } = useActionSheet();
const onSelect = (item: IItemData) => {
const {
value,
text: { text }
} = item;
if (multiselect) {
let newSelect = [];
if (!selected.includes(value)) {
newSelect = [...selected, value];
} else {
newSelect = selected.filter((s: any) => s !== value);
}
setSelected(newSelect);
select(newSelect);
onChange({ value: newSelect });
} else {
onChange({ value });
setCurrentValue(text);
onHide();
}
};
const handleSearch = debounce(
async (text: string) => {
if (onSearch) {
const res = await onSearch(text);
setItems(res);
} else {
setItems(options?.filter((option: any) => textParser([option.text]).toLowerCase().includes(text.toLowerCase())));
}
},
onSearch ? 300 : 0
);
return (
<View style={[styles.actionSheetContainer]}>
<FormTextInput
testID='multi-select-search'
onChangeText={handleSearch}
placeholder={I18n.t('Search')}
inputStyle={{ backgroundColor: colors.focusedBackground }}
bottomSheet={isIOS}
onSubmitEditing={() => {
setTimeout(() => {
hideActionSheet();
}, 150);
}}
/>
<Items items={items || []} selected={selected} onSelect={onSelect} />
</View>
);
}
);

View File

@ -1,29 +1,15 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { import { Text, TextStyle } from 'react-native';
Animated,
Easing,
KeyboardAvoidingView,
Modal,
StyleSheet,
Text,
TouchableWithoutFeedback,
View,
TextStyle
} from 'react-native';
import { BlockContext } from '@rocket.chat/ui-kit'; import { BlockContext } from '@rocket.chat/ui-kit';
import Button from '../../Button'; import Button from '../../Button';
import { FormTextInput } from '../../TextInput';
import { textParser } from '../utils';
import { themes } from '../../../lib/constants';
import I18n from '../../../i18n';
import { isIOS } from '../../../lib/methods/helpers';
import { useTheme } from '../../../theme'; import { useTheme } from '../../../theme';
import { IText } from '../interfaces'; import { IText } from '../interfaces';
import Chips from './Chips'; import Chips from './Chips';
import Items from './Items';
import Input from './Input'; import Input from './Input';
import styles from './styles'; import styles from './styles';
import { useActionSheet } from '../../ActionSheet';
import { MultiSelectContent } from './MultiSelectContent';
export interface IItemData { export interface IItemData {
value: any; value: any;
@ -38,7 +24,7 @@ interface IMultiSelect {
context?: BlockContext; context?: BlockContext;
loading?: boolean; loading?: boolean;
multiselect?: boolean; multiselect?: boolean;
onSearch?: () => void; onSearch?: (keyword: string) => IItemData[] | Promise<IItemData[] | undefined>;
onClose?: () => void; onClose?: () => void;
inputStyle?: TextStyle; inputStyle?: TextStyle;
value?: any[]; value?: any[];
@ -46,16 +32,6 @@ interface IMultiSelect {
innerInputStyle?: object; innerInputStyle?: object;
} }
const ANIMATION_DURATION = 200;
const ANIMATION_PROPS = {
duration: ANIMATION_DURATION,
easing: Easing.inOut(Easing.quad),
useNativeDriver: true
};
const animatedValue = new Animated.Value(0);
const behavior = isIOS ? 'padding' : null;
export const MultiSelect = React.memo( export const MultiSelect = React.memo(
({ ({
options = [], options = [],
@ -71,12 +47,11 @@ export const MultiSelect = React.memo(
inputStyle, inputStyle,
innerInputStyle innerInputStyle
}: IMultiSelect) => { }: IMultiSelect) => {
const { theme } = useTheme(); const { colors } = useTheme();
const [selected, select] = useState<any>(Array.isArray(values) ? values : []); const [selected, select] = useState<string[]>(Array.isArray(values) ? values : []);
const [open, setOpen] = useState(false);
const [search, onSearchChange] = useState('');
const [currentValue, setCurrentValue] = useState(''); const [currentValue, setCurrentValue] = useState('');
const [showContent, setShowContent] = useState(false);
const { showActionSheet, hideActionSheet } = useActionSheet();
useEffect(() => { useEffect(() => {
if (Array.isArray(values)) { if (Array.isArray(values)) {
@ -84,10 +59,6 @@ export const MultiSelect = React.memo(
} }
}, [values]); }, [values]);
useEffect(() => {
setOpen(showContent);
}, [showContent]);
useEffect(() => { useEffect(() => {
if (values && values.length && !multiselect) { if (values && values.length && !multiselect) {
setCurrentValue(values[0].text); setCurrentValue(values[0].text);
@ -95,19 +66,26 @@ export const MultiSelect = React.memo(
}, []); }, []);
const onShow = () => { const onShow = () => {
Animated.timing(animatedValue, { showActionSheet({
toValue: 1, children: (
...ANIMATION_PROPS <MultiSelectContent
}).start(); options={options}
setShowContent(true); onSearch={onSearch}
select={select}
onChange={onChange}
setCurrentValue={setCurrentValue}
onHide={onHide}
multiselect={multiselect}
selectedItems={selected}
/>
),
onClose,
headerHeight: 275
});
}; };
const onHide = () => { const onHide = () => {
onClose(); onClose();
Animated.timing(animatedValue, { hideActionSheet();
toValue: 0,
...ANIMATION_PROPS
}).start(() => setShowContent(false));
}; };
const onSelect = (item: IItemData) => { const onSelect = (item: IItemData) => {
@ -127,45 +105,14 @@ export const MultiSelect = React.memo(
} else { } else {
onChange({ value }); onChange({ value });
setCurrentValue(text); setCurrentValue(text);
onHide();
} }
}; };
const renderContent = () => {
const items: any = onSearch
? options
: options.filter((option: any) => textParser([option.text]).toLowerCase().includes(search.toLowerCase()));
return (
<View style={[styles.modal, { backgroundColor: themes[theme].backgroundColor }]}>
<View style={[styles.content, { backgroundColor: themes[theme].backgroundColor }]}>
<FormTextInput
testID='multi-select-search'
onChangeText={onSearch || onSearchChange}
placeholder={I18n.t('Search')}
/>
<Items items={items} selected={selected} onSelect={onSelect} theme={theme} />
</View>
</View>
);
};
const translateY = animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [600, 0]
});
let button = multiselect ? ( let button = multiselect ? (
<Button title={`${selected.length} selecteds`} onPress={onShow} loading={loading} /> <Button title={`${selected.length} selecteds`} onPress={onShow} loading={loading} />
) : ( ) : (
<Input <Input onPress={onShow} loading={loading} disabled={disabled} inputStyle={inputStyle} innerInputStyle={innerInputStyle}>
onPress={onShow} <Text style={[styles.pickerText, { color: currentValue ? colors.titleText : colors.auxiliaryText }]}>
theme={theme}
loading={loading}
disabled={disabled}
inputStyle={inputStyle}
innerInputStyle={innerInputStyle}>
<Text style={[styles.pickerText, { color: currentValue ? themes[theme].titleText : themes[theme].auxiliaryText }]}>
{currentValue || placeholder.text} {currentValue || placeholder.text}
</Text> </Text>
</Input> </Input>
@ -173,48 +120,18 @@ export const MultiSelect = React.memo(
if (context === BlockContext.FORM) { if (context === BlockContext.FORM) {
const items: any = options.filter((option: any) => selected.includes(option.value)); const items: any = options.filter((option: any) => selected.includes(option.value));
button = ( button = (
<Input <Input onPress={onShow} loading={loading} disabled={disabled} inputStyle={inputStyle} innerInputStyle={innerInputStyle}>
onPress={onShow}
theme={theme}
loading={loading}
disabled={disabled}
inputStyle={inputStyle}
innerInputStyle={innerInputStyle}>
{items.length ? ( {items.length ? (
<Chips items={items} onSelect={(item: any) => (disabled ? {} : onSelect(item))} theme={theme} /> <Chips items={items} onSelect={(item: any) => (disabled ? {} : onSelect(item))} />
) : ( ) : (
<Text style={[styles.pickerText, { color: themes[theme].auxiliaryText }]}>{placeholder.text}</Text> <Text style={[styles.pickerText, { color: colors.auxiliaryText }]}>{placeholder.text}</Text>
)} )}
</Input> </Input>
); );
} }
return ( return <>{button}</>;
<>
<Modal animationType='fade' transparent visible={open} onRequestClose={onHide} onShow={onShow}>
<TouchableWithoutFeedback onPress={onHide}>
<View style={styles.container}>
<View
style={[
StyleSheet.absoluteFill,
{
opacity: themes[theme].backdropOpacity,
backgroundColor: themes[theme].backdropColor
}
]}
/>
{/* @ts-ignore*/}
<KeyboardAvoidingView style={styles.keyboardView} behavior={behavior}>
<Animated.View style={[styles.animatedContent, { transform: [{ translateY }] }]}>
{showContent ? renderContent() : null}
</Animated.View>
</KeyboardAvoidingView>
</View>
</TouchableWithoutFeedback>
</Modal>
{button}
</>
);
} }
); );

View File

@ -2,18 +2,15 @@ import { StyleSheet } from 'react-native';
import sharedStyles from '../../../views/Styles'; import sharedStyles from '../../../views/Styles';
export default StyleSheet.create<any>({ export default StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
alignItems: 'center', alignItems: 'center',
justifyContent: 'flex-end' justifyContent: 'flex-end'
}, },
modal: { actionSheetContainer: {
height: 300, padding: 16,
width: '100%', flex: 1
borderTopRightRadius: 16,
borderTopLeftRadius: 16,
overflow: 'hidden'
}, },
content: { content: {
padding: 16 padding: 16

View File

@ -1,7 +1,6 @@
import React, { useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Text } from 'react-native'; import { Text } from 'react-native';
import { themes } from '../../lib/constants';
import { MultiSelect } from '../../containers/UIKit/MultiSelect'; import { MultiSelect } from '../../containers/UIKit/MultiSelect';
import { ISearchLocal } from '../../definitions'; import { ISearchLocal } from '../../definitions';
import I18n from '../../i18n'; import I18n from '../../i18n';
@ -9,7 +8,8 @@ import { getAvatarURL } from '../../lib/methods/helpers/getAvatarUrl';
import { ICreateDiscussionViewSelectChannel } from './interfaces'; import { ICreateDiscussionViewSelectChannel } from './interfaces';
import styles from './styles'; import styles from './styles';
import { localSearch } from '../../lib/methods'; import { localSearch } from '../../lib/methods';
import { getRoomAvatar, getRoomTitle, debounce } from '../../lib/methods/helpers'; import { getRoomAvatar, getRoomTitle } from '../../lib/methods/helpers';
import { useTheme } from '../../theme';
const SelectChannel = ({ const SelectChannel = ({
server, server,
@ -18,19 +18,28 @@ const SelectChannel = ({
onChannelSelect, onChannelSelect,
initial, initial,
blockUnauthenticatedAccess, blockUnauthenticatedAccess,
serverVersion, serverVersion
theme
}: ICreateDiscussionViewSelectChannel): React.ReactElement => { }: ICreateDiscussionViewSelectChannel): React.ReactElement => {
const [channels, setChannels] = useState<ISearchLocal[]>([]); const [channels, setChannels] = useState<ISearchLocal[]>([]);
const { colors } = useTheme();
const getChannels = debounce(async (keyword = '') => { const getChannels = async (keyword = '') => {
try { try {
const res = (await localSearch({ text: keyword })) as ISearchLocal[]; const res = (await localSearch({ text: keyword, filterUsers: false })) as ISearchLocal[];
setChannels(res); setChannels(res);
return res.map(channel => ({
value: channel,
text: { text: getRoomTitle(channel) },
imageUrl: getAvatar(channel)
}));
} catch { } catch {
// do nothing // do nothing
} }
}, 300); };
useEffect(() => {
getChannels('');
}, []);
const getAvatar = (item: ISearchLocal) => const getAvatar = (item: ISearchLocal) =>
getAvatarURL({ getAvatarURL({
@ -47,7 +56,7 @@ const SelectChannel = ({
return ( return (
<> <>
<Text style={[styles.label, { color: themes[theme].titleText }]}>{I18n.t('Parent_channel_or_group')}</Text> <Text style={[styles.label, { color: colors.titleText }]}>{I18n.t('Parent_channel_or_group')}</Text>
<MultiSelect <MultiSelect
inputStyle={styles.inputStyle} inputStyle={styles.inputStyle}
onChange={onChannelSelect} onChange={onChannelSelect}
@ -59,7 +68,7 @@ const SelectChannel = ({
text: { text: getRoomTitle(channel) }, text: { text: getRoomTitle(channel) },
imageUrl: getAvatar(channel) imageUrl: getAvatar(channel)
}))} }))}
onClose={() => setChannels([])} onClose={() => getChannels('')}
placeholder={{ text: `${I18n.t('Select_a_Channel')}...` }} placeholder={{ text: `${I18n.t('Select_a_Channel')}...` }}
/> />
</> </>

View File

@ -1,16 +1,16 @@
import React, { useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Text } from 'react-native'; import { Text } from 'react-native';
import { BlockContext } from '@rocket.chat/ui-kit'; import { BlockContext } from '@rocket.chat/ui-kit';
import { getAvatarURL } from '../../lib/methods/helpers/getAvatarUrl'; import { getAvatarURL } from '../../lib/methods/helpers/getAvatarUrl';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { MultiSelect } from '../../containers/UIKit/MultiSelect'; import { MultiSelect } from '../../containers/UIKit/MultiSelect';
import { themes } from '../../lib/constants';
import styles from './styles'; import styles from './styles';
import { ICreateDiscussionViewSelectUsers } from './interfaces'; import { ICreateDiscussionViewSelectUsers } from './interfaces';
import { SubscriptionType, IUser } from '../../definitions'; import { SubscriptionType, IUser } from '../../definitions';
import { search } from '../../lib/methods'; import { search } from '../../lib/methods';
import { getRoomAvatar, getRoomTitle, debounce } from '../../lib/methods/helpers'; import { getRoomAvatar, getRoomTitle } from '../../lib/methods/helpers';
import { useTheme } from '../../theme';
const SelectUsers = ({ const SelectUsers = ({
server, server,
@ -19,22 +19,31 @@ const SelectUsers = ({
selected, selected,
onUserSelect, onUserSelect,
blockUnauthenticatedAccess, blockUnauthenticatedAccess,
serverVersion, serverVersion
theme
}: ICreateDiscussionViewSelectUsers): React.ReactElement => { }: ICreateDiscussionViewSelectUsers): React.ReactElement => {
const [users, setUsers] = useState<any[]>([]); const [users, setUsers] = useState<any[]>([]);
const { colors } = useTheme();
const getUsers = debounce(async (keyword = '') => { const getUsers = async (keyword = '') => {
try { try {
const res = await search({ text: keyword, filterRooms: false }); const res = await search({ text: keyword, filterRooms: false });
const selectedUsers = users.filter((u: IUser) => selected.includes(u.name)); const selectedUsers = users.filter((u: IUser) => selected.includes(u.name));
const filteredUsers = res.filter(r => !selectedUsers.find((u: IUser) => u.name === r.name)); const filteredUsers = res.filter(r => !selectedUsers.find((u: IUser) => u.name === r.name));
const items = [...selectedUsers, ...filteredUsers]; const items = [...selectedUsers, ...filteredUsers];
setUsers(items); setUsers(items);
return items.map((user: IUser) => ({
value: user.name,
text: { text: getRoomTitle(user) },
imageUrl: getAvatar(user)
}));
} catch { } catch {
// do nothing // do nothing
} }
}, 300); };
useEffect(() => {
getUsers('');
}, []);
const getAvatar = (item: IUser) => const getAvatar = (item: IUser) =>
getAvatarURL({ getAvatarURL({
@ -50,7 +59,7 @@ const SelectUsers = ({
return ( return (
<> <>
<Text style={[styles.label, { color: themes[theme].titleText }]}>{I18n.t('Invite_users')}</Text> <Text style={[styles.label, { color: colors.titleText }]}>{I18n.t('Invite_users')}</Text>
<MultiSelect <MultiSelect
inputStyle={styles.inputStyle} inputStyle={styles.inputStyle}
onSearch={getUsers} onSearch={getUsers}
@ -60,7 +69,6 @@ const SelectUsers = ({
text: { text: getRoomTitle(user) }, text: { text: getRoomTitle(user) },
imageUrl: getAvatar(user) imageUrl: getAvatar(user)
}))} }))}
onClose={() => setUsers(users.filter((u: IUser) => selected.includes(u.name)))}
placeholder={{ text: `${I18n.t('Select_Users')}...` }} placeholder={{ text: `${I18n.t('Select_Users')}...` }}
context={BlockContext.FORM} context={BlockContext.FORM}
multiselect multiselect

View File

@ -164,7 +164,6 @@ class CreateChannelView extends React.Component<ICreateChannelViewProps, ICreate
onChannelSelect={this.selectChannel} onChannelSelect={this.selectChannel}
blockUnauthenticatedAccess={blockUnauthenticatedAccess} blockUnauthenticatedAccess={blockUnauthenticatedAccess}
serverVersion={serverVersion} serverVersion={serverVersion}
theme={theme}
/> />
<FormTextInput <FormTextInput
label={I18n.t('Discussion_name')} label={I18n.t('Discussion_name')}
@ -182,7 +181,6 @@ class CreateChannelView extends React.Component<ICreateChannelViewProps, ICreate
onUserSelect={this.selectUsers} onUserSelect={this.selectUsers}
blockUnauthenticatedAccess={blockUnauthenticatedAccess} blockUnauthenticatedAccess={blockUnauthenticatedAccess}
serverVersion={serverVersion} serverVersion={serverVersion}
theme={theme}
/> />
{this.isEncryptionEnabled ? ( {this.isEncryptionEnabled ? (
<> <>

View File

@ -1,7 +1,6 @@
import { NewMessageStackParamList } from '../../stacks/types'; import { NewMessageStackParamList } from '../../stacks/types';
import { ISubscription, SubscriptionType } from '../../definitions/ISubscription'; import { ISubscription, SubscriptionType } from '../../definitions/ISubscription';
import { IBaseScreen, IMessage, ISearchLocal, IUser } from '../../definitions'; import { IBaseScreen, IMessage, ISearchLocal, IUser } from '../../definitions';
import { TSupportedThemes } from '../../theme';
export interface IResult { export interface IResult {
rid: string; rid: string;
@ -43,7 +42,6 @@ export interface ICreateDiscussionViewSelectChannel {
onChannelSelect: Function; onChannelSelect: Function;
blockUnauthenticatedAccess: boolean; blockUnauthenticatedAccess: boolean;
serverVersion: string; serverVersion: string;
theme: TSupportedThemes;
} }
export interface ICreateDiscussionViewSelectUsers { export interface ICreateDiscussionViewSelectUsers {
@ -54,5 +52,4 @@ export interface ICreateDiscussionViewSelectUsers {
onUserSelect: Function; onUserSelect: Function;
blockUnauthenticatedAccess: boolean; blockUnauthenticatedAccess: boolean;
serverVersion: string; serverVersion: string;
theme: TSupportedThemes;
} }

View File

@ -150,9 +150,9 @@ const ForwardLivechatView = ({ navigation, route }: IBaseScreen<ChatsStackParamL
return ( return (
<View style={[styles.container, { backgroundColor: colors.auxiliaryBackground }]}> <View style={[styles.container, { backgroundColor: colors.auxiliaryBackground }]}>
<Input onPress={onPressDepartment} placeholder={I18n.t('Select_a_Department')} theme={theme} /> <Input onPress={onPressDepartment} placeholder={I18n.t('Select_a_Department')} />
<OrSeparator theme={theme} /> <OrSeparator theme={theme} />
<Input onPress={onPressUser} placeholder={I18n.t('Select_a_User')} theme={theme} /> <Input onPress={onPressUser} placeholder={I18n.t('Select_a_User')} />
</View> </View>
); );
}; };