[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:
parent
785ae0325b
commit
dd48402214
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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) => {
|
||||||
<Touchable
|
const { colors } = useTheme();
|
||||||
key={item.value}
|
return (
|
||||||
onPress={() => onSelect(item)}
|
<Touchable
|
||||||
style={[styles.chip, { backgroundColor: themes[theme].auxiliaryBackground }, style]}
|
key={item.value}
|
||||||
background={Touchable.Ripple(themes[theme].bannerBackground)}>
|
onPress={() => onSelect(item)}
|
||||||
<>
|
style={[styles.chip, { backgroundColor: colors.auxiliaryBackground }, style]}
|
||||||
{item.imageUrl ? <FastImage style={styles.chipImage} source={{ uri: item.imageUrl }} /> : null}
|
background={Touchable.Ripple(colors.bannerBackground)}>
|
||||||
<Text numberOfLines={1} style={[styles.chipText, { color: themes[theme].titleText }]}>
|
<>
|
||||||
{textParser([item.text])}
|
{item.imageUrl ? <FastImage style={styles.chipImage} source={{ uri: item.imageUrl }} /> : null}
|
||||||
</Text>
|
<Text numberOfLines={1} style={[styles.chipText, { color: colors.titleText }]}>
|
||||||
<CustomIcon name='close' size={16} color={themes[theme].auxiliaryText} />
|
{textParser([item.text])}
|
||||||
</>
|
</Text>
|
||||||
</Touchable>
|
<CustomIcon name='close' size={16} color={colors.auxiliaryText} />
|
||||||
);
|
</>
|
||||||
|
</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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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) => {
|
||||||
<Touchable
|
const { colors } = useTheme();
|
||||||
onPress={onPress}
|
return (
|
||||||
style={[{ backgroundColor: themes[theme].backgroundColor }, inputStyle]}
|
<Touchable
|
||||||
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
onPress={onPress}
|
||||||
disabled={disabled}>
|
style={[{ backgroundColor: colors.backgroundColor }, inputStyle]}
|
||||||
<View style={[styles.input, { borderColor: themes[theme].separatorColor }, innerInputStyle]}>
|
background={Touchable.Ripple(colors.bannerBackground)}
|
||||||
{placeholder ? <Text style={[styles.pickerText, { color: themes[theme].auxiliaryText }]}>{placeholder}</Text> : children}
|
disabled={disabled}>
|
||||||
{loading ? (
|
<View style={[styles.input, { borderColor: colors.separatorColor }, innerInputStyle]}>
|
||||||
<ActivityIndicator style={[styles.loading, styles.icon]} />
|
{placeholder ? <Text style={[styles.pickerText, { color: colors.auxiliaryText }]}>{placeholder}</Text> : children}
|
||||||
) : (
|
{loading ? (
|
||||||
<CustomIcon name='chevron-down' size={22} color={themes[theme].auxiliaryText} style={styles.icon} />
|
<ActivityIndicator style={styles.icon} />
|
||||||
)}
|
) : (
|
||||||
</View>
|
<CustomIcon name='chevron-down' size={22} color={colors.auxiliaryText} style={styles.icon} />
|
||||||
</Touchable>
|
)}
|
||||||
);
|
</View>
|
||||||
|
</Touchable>
|
||||||
|
);
|
||||||
|
};
|
||||||
export default Input;
|
export default Input;
|
||||||
|
|
|
@ -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)} />
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
|
@ -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}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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')}...` }}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 ? (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue