From dd484022147877f39ff47d346b58420537a59e29 Mon Sep 17 00:00:00 2001 From: Danish Ahmed Mirza <77742477+try-catch-stack@users.noreply.github.com> Date: Thu, 14 Jul 2022 01:01:58 +0530 Subject: [PATCH] [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 --- app/containers/ActionSheet/ActionSheet.tsx | 7 +- .../ActionSheet/BottomSheetContent.tsx | 2 +- app/containers/ActionSheet/Handle.tsx | 2 +- app/containers/ActionSheet/Provider.tsx | 3 +- app/containers/ActionSheet/styles.ts | 10 ++ app/containers/UIKit/MultiSelect/Chips.tsx | 42 ++--- app/containers/UIKit/MultiSelect/Input.tsx | 40 ++--- app/containers/UIKit/MultiSelect/Items.tsx | 31 ++-- .../UIKit/MultiSelect/MultiSelectContent.tsx | 85 ++++++++++ app/containers/UIKit/MultiSelect/index.tsx | 147 ++++-------------- app/containers/UIKit/MultiSelect/styles.ts | 11 +- .../CreateDiscussionView/SelectChannel.tsx | 29 ++-- .../CreateDiscussionView/SelectUsers.tsx | 26 ++-- app/views/CreateDiscussionView/index.tsx | 2 - app/views/CreateDiscussionView/interfaces.ts | 3 - app/views/ForwardLivechatView.tsx | 4 +- 16 files changed, 232 insertions(+), 212 deletions(-) create mode 100644 app/containers/UIKit/MultiSelect/MultiSelectContent.tsx diff --git a/app/containers/ActionSheet/ActionSheet.tsx b/app/containers/ActionSheet/ActionSheet.tsx index 83c2e8ff9..fb00d5267 100644 --- a/app/containers/ActionSheet/ActionSheet.tsx +++ b/app/containers/ActionSheet/ActionSheet.tsx @@ -101,6 +101,11 @@ const ActionSheet = React.memo( ); + const onClose = () => { + toggleVisible(); + data?.onClose && data?.onClose(); + }; + const renderBackdrop = useCallback( props => ( index === -1 && toggleVisible()} + onChange={index => index === -1 && onClose()} {...androidTablet}> diff --git a/app/containers/ActionSheet/BottomSheetContent.tsx b/app/containers/ActionSheet/BottomSheetContent.tsx index ea3b1f1f8..6c6e6e979 100644 --- a/app/containers/ActionSheet/BottomSheetContent.tsx +++ b/app/containers/ActionSheet/BottomSheetContent.tsx @@ -53,7 +53,7 @@ const BottomSheetContent = React.memo(({ options, hasCancel, hide, children }: I /> ); } - return {children}; + return {children}; }); export default BottomSheetContent; diff --git a/app/containers/ActionSheet/Handle.tsx b/app/containers/ActionSheet/Handle.tsx index 650d44ef5..0b3a7caf1 100644 --- a/app/containers/ActionSheet/Handle.tsx +++ b/app/containers/ActionSheet/Handle.tsx @@ -8,7 +8,7 @@ import { useTheme } from '../../theme'; export const Handle = React.memo(() => { const { theme } = useTheme(); return ( - + ); diff --git a/app/containers/ActionSheet/Provider.tsx b/app/containers/ActionSheet/Provider.tsx index e28d49c22..945bb9aff 100644 --- a/app/containers/ActionSheet/Provider.tsx +++ b/app/containers/ActionSheet/Provider.tsx @@ -20,7 +20,8 @@ export type TActionSheetOptions = { hasCancel?: boolean; type?: string; children?: React.ReactElement | null; - snaps?: string[] | number[]; + snaps?: (string | number)[]; + onClose?: () => void; }; export interface IActionSheetProvider { showActionSheet: (item: TActionSheetOptions) => void; diff --git a/app/containers/ActionSheet/styles.ts b/app/containers/ActionSheet/styles.ts index 68d371ea6..3ffb90240 100644 --- a/app/containers/ActionSheet/styles.ts +++ b/app/containers/ActionSheet/styles.ts @@ -63,5 +63,15 @@ export default StyleSheet.create({ }, rightContainer: { paddingLeft: 12 + }, + footerButtonsContainer: { + flexDirection: 'row', + paddingTop: 16 + }, + buttonSeparator: { + marginRight: 8 + }, + contentContainer: { + flex: 1 } }); diff --git a/app/containers/UIKit/MultiSelect/Chips.tsx b/app/containers/UIKit/MultiSelect/Chips.tsx index 49b011bbd..aaabe9d72 100644 --- a/app/containers/UIKit/MultiSelect/Chips.tsx +++ b/app/containers/UIKit/MultiSelect/Chips.tsx @@ -3,50 +3,50 @@ import { Text, View } from 'react-native'; import Touchable from 'react-native-platform-touchable'; import FastImage from 'react-native-fast-image'; -import { themes } from '../../../lib/constants'; import { textParser } from '../utils'; import { CustomIcon } from '../../CustomIcon'; import styles from './styles'; import { IItemData } from '.'; -import { TSupportedThemes } from '../../../theme'; +import { useTheme } from '../../../theme'; interface IChip { item: IItemData; onSelect: (item: IItemData) => void; style?: object; - theme: TSupportedThemes; } interface IChips { items: IItemData[]; onSelect: (item: IItemData) => void; style?: object; - theme: TSupportedThemes; } const keyExtractor = (item: IItemData) => item.value.toString(); -const Chip = ({ item, onSelect, style, theme }: IChip) => ( - onSelect(item)} - style={[styles.chip, { backgroundColor: themes[theme].auxiliaryBackground }, style]} - background={Touchable.Ripple(themes[theme].bannerBackground)}> - <> - {item.imageUrl ? : null} - - {textParser([item.text])} - - - - -); +const Chip = ({ item, onSelect, style }: IChip) => { + const { colors } = useTheme(); + return ( + onSelect(item)} + style={[styles.chip, { backgroundColor: colors.auxiliaryBackground }, style]} + background={Touchable.Ripple(colors.bannerBackground)}> + <> + {item.imageUrl ? : null} + + {textParser([item.text])} + + + + + ); +}; Chip.propTypes = {}; -const Chips = ({ items, onSelect, style, theme }: IChips) => ( +const Chips = ({ items, onSelect, style }: IChips) => ( {items.map(item => ( - + ))} ); diff --git a/app/containers/UIKit/MultiSelect/Input.tsx b/app/containers/UIKit/MultiSelect/Input.tsx index e1b1e9fb2..d1157e8a9 100644 --- a/app/containers/UIKit/MultiSelect/Input.tsx +++ b/app/containers/UIKit/MultiSelect/Input.tsx @@ -3,15 +3,13 @@ import { Text, View } from 'react-native'; import Touchable from 'react-native-platform-touchable'; import { CustomIcon } from '../../CustomIcon'; -import { themes } from '../../../lib/constants'; import ActivityIndicator from '../../ActivityIndicator'; import styles from './styles'; -import { TSupportedThemes } from '../../../theme'; +import { useTheme } from '../../../theme'; interface IInput { children?: JSX.Element; onPress: () => void; - theme: TSupportedThemes; inputStyle?: object; disabled?: boolean | null; placeholder?: string; @@ -19,21 +17,23 @@ interface IInput { innerInputStyle?: object; } -const Input = ({ children, onPress, theme, loading, inputStyle, placeholder, disabled, innerInputStyle }: IInput) => ( - - - {placeholder ? {placeholder} : children} - {loading ? ( - - ) : ( - - )} - - -); - +const Input = ({ children, onPress, loading, inputStyle, placeholder, disabled, innerInputStyle }: IInput) => { + const { colors } = useTheme(); + return ( + + + {placeholder ? {placeholder} : children} + {loading ? ( + + ) : ( + + )} + + + ); +}; export default Input; diff --git a/app/containers/UIKit/MultiSelect/Items.tsx b/app/containers/UIKit/MultiSelect/Items.tsx index 70cabc06b..2bcdb2fdf 100644 --- a/app/containers/UIKit/MultiSelect/Items.tsx +++ b/app/containers/UIKit/MultiSelect/Items.tsx @@ -1,61 +1,54 @@ import React from 'react'; -import { FlatList, Text } from 'react-native'; +import { Text } from 'react-native'; import Touchable from 'react-native-platform-touchable'; import FastImage from 'react-native-fast-image'; +import { FlatList } from 'react-native-gesture-handler'; import Check from '../../Check'; import * as List from '../../List'; import { textParser } from '../utils'; -import { themes } from '../../../lib/constants'; import styles from './styles'; import { IItemData } from '.'; -import { TSupportedThemes } from '../../../theme'; +import { useTheme } from '../../../theme'; interface IItem { item: IItemData; selected?: string; onSelect: Function; - theme: TSupportedThemes; } interface IItems { items: IItemData[]; selected: string[]; 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) -const Item = ({ item, selected, onSelect, theme }: IItem) => { +const Item = ({ item, selected, onSelect }: IItem) => { const itemName = item.value?.name || item.text.text.toLowerCase(); + const { colors } = useTheme(); return ( - onSelect(item)} - style={[styles.item, { backgroundColor: themes[theme].backgroundColor }]}> + onSelect(item)} style={[styles.item]}> <> {item.imageUrl ? : null} - {textParser([item.text])} + {textParser([item.text])} {selected ? : null} ); }; -const Items = ({ items, selected, onSelect, theme }: IItems) => ( +const Items = ({ items, selected, onSelect }: IItems) => ( ( - s === item.value)} /> - )} + renderItem={({ item }) => s === item.value)} />} /> ); diff --git a/app/containers/UIKit/MultiSelect/MultiSelectContent.tsx b/app/containers/UIKit/MultiSelect/MultiSelectContent.tsx new file mode 100644 index 000000000..358adb317 --- /dev/null +++ b/app/containers/UIKit/MultiSelect/MultiSelectContent.tsx @@ -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; + options?: IItemData[]; + multiselect: boolean; + select: React.Dispatch; + onChange: Function; + setCurrentValue: React.Dispatch>; + 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(Array.isArray(selectedItems) ? selectedItems : []); + const [items, setItems] = useState(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 ( + + { + setTimeout(() => { + hideActionSheet(); + }, 150); + }} + /> + + + ); + } +); diff --git a/app/containers/UIKit/MultiSelect/index.tsx b/app/containers/UIKit/MultiSelect/index.tsx index daafbd33d..da47f107b 100644 --- a/app/containers/UIKit/MultiSelect/index.tsx +++ b/app/containers/UIKit/MultiSelect/index.tsx @@ -1,29 +1,15 @@ import React, { useEffect, useState } from 'react'; -import { - Animated, - Easing, - KeyboardAvoidingView, - Modal, - StyleSheet, - Text, - TouchableWithoutFeedback, - View, - TextStyle -} from 'react-native'; +import { Text, TextStyle } from 'react-native'; import { BlockContext } from '@rocket.chat/ui-kit'; 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 { IText } from '../interfaces'; import Chips from './Chips'; -import Items from './Items'; import Input from './Input'; import styles from './styles'; +import { useActionSheet } from '../../ActionSheet'; +import { MultiSelectContent } from './MultiSelectContent'; export interface IItemData { value: any; @@ -38,7 +24,7 @@ interface IMultiSelect { context?: BlockContext; loading?: boolean; multiselect?: boolean; - onSearch?: () => void; + onSearch?: (keyword: string) => IItemData[] | Promise; onClose?: () => void; inputStyle?: TextStyle; value?: any[]; @@ -46,16 +32,6 @@ interface IMultiSelect { 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( ({ options = [], @@ -71,12 +47,11 @@ export const MultiSelect = React.memo( inputStyle, innerInputStyle }: IMultiSelect) => { - const { theme } = useTheme(); - const [selected, select] = useState(Array.isArray(values) ? values : []); - const [open, setOpen] = useState(false); - const [search, onSearchChange] = useState(''); + const { colors } = useTheme(); + const [selected, select] = useState(Array.isArray(values) ? values : []); const [currentValue, setCurrentValue] = useState(''); - const [showContent, setShowContent] = useState(false); + + const { showActionSheet, hideActionSheet } = useActionSheet(); useEffect(() => { if (Array.isArray(values)) { @@ -84,10 +59,6 @@ export const MultiSelect = React.memo( } }, [values]); - useEffect(() => { - setOpen(showContent); - }, [showContent]); - useEffect(() => { if (values && values.length && !multiselect) { setCurrentValue(values[0].text); @@ -95,19 +66,26 @@ export const MultiSelect = React.memo( }, []); const onShow = () => { - Animated.timing(animatedValue, { - toValue: 1, - ...ANIMATION_PROPS - }).start(); - setShowContent(true); + showActionSheet({ + children: ( + + ), + onClose, + headerHeight: 275 + }); }; - const onHide = () => { onClose(); - Animated.timing(animatedValue, { - toValue: 0, - ...ANIMATION_PROPS - }).start(() => setShowContent(false)); + hideActionSheet(); }; const onSelect = (item: IItemData) => { @@ -127,45 +105,14 @@ export const MultiSelect = React.memo( } else { onChange({ value }); setCurrentValue(text); - onHide(); } }; - const renderContent = () => { - const items: any = onSearch - ? options - : options.filter((option: any) => textParser([option.text]).toLowerCase().includes(search.toLowerCase())); - - return ( - - - - - - - ); - }; - - const translateY = animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [600, 0] - }); - let button = multiselect ? (