Chore: Evaluate ActionSheet - TypeScript (#3927)

This commit is contained in:
Gleidson Daniel Silva 2022-03-22 17:44:27 -03:00 committed by GitHub
parent e8d23791d3
commit 2fb7d917a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 78 additions and 72 deletions

View File

@ -1,30 +1,29 @@
import { useBackHandler } from '@react-native-community/hooks';
import * as Haptics from 'expo-haptics';
import React, { forwardRef, isValidElement, useEffect, useImperativeHandle, useRef, useState } from 'react'; import React, { forwardRef, isValidElement, useEffect, useImperativeHandle, useRef, useState } from 'react';
import { Keyboard, Text } from 'react-native'; import { Keyboard, Text } from 'react-native';
import { HandlerStateChangeEventPayload, State, TapGestureHandler } from 'react-native-gesture-handler';
import Animated, { Easing, Extrapolate, interpolateNode, Value } from 'react-native-reanimated';
import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { State, TapGestureHandler } from 'react-native-gesture-handler';
import ScrollBottomSheet from 'react-native-scroll-bottom-sheet'; import ScrollBottomSheet from 'react-native-scroll-bottom-sheet';
import Animated, { Easing, Extrapolate, Value, interpolateNode } from 'react-native-reanimated';
import * as Haptics from 'expo-haptics';
import { useBackHandler } from '@react-native-community/hooks';
import { Item } from './Item';
import { Handle } from './Handle';
import { Button } from './Button';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import styles, { ITEM_HEIGHT } from './styles'; import { useDimensions, useOrientation } from '../../dimensions';
import I18n from '../../i18n';
import { useTheme } from '../../theme';
import { isIOS, isTablet } from '../../utils/deviceInfo'; import { isIOS, isTablet } from '../../utils/deviceInfo';
import * as List from '../List'; import * as List from '../List';
import I18n from '../../i18n'; import { Button } from './Button';
import { IDimensionsContextProps, useDimensions, useOrientation } from '../../dimensions'; import { Handle } from './Handle';
import { IActionSheetItem, Item } from './Item';
import { TActionSheetOptions, TActionSheetOptionsItem } from './Provider';
import styles, { ITEM_HEIGHT } from './styles';
interface IActionSheetData { const getItemLayout = (data: TActionSheetOptionsItem[] | null | undefined, index: number) => ({
options: any; length: ITEM_HEIGHT,
headerHeight?: number; offset: ITEM_HEIGHT * index,
hasCancel?: boolean; index
customHeader: any; });
}
const getItemLayout = (data: any, index: number) => ({ length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index });
const HANDLE_HEIGHT = isIOS ? 40 : 56; const HANDLE_HEIGHT = isIOS ? 40 : 56;
const MAX_SNAP_HEIGHT = 16; const MAX_SNAP_HEIGHT = 16;
@ -39,16 +38,17 @@ const ANIMATION_CONFIG = {
}; };
const ActionSheet = React.memo( const ActionSheet = React.memo(
forwardRef(({ children, theme }: { children: JSX.Element; theme: string }, ref) => { forwardRef(({ children }: { children: React.ReactElement }, ref) => {
const bottomSheetRef: any = useRef(); const { theme } = useTheme();
const [data, setData] = useState<IActionSheetData>({} as IActionSheetData); const bottomSheetRef = useRef<ScrollBottomSheet<TActionSheetOptionsItem>>(null);
const [data, setData] = useState<TActionSheetOptions>({} as TActionSheetOptions);
const [isVisible, setVisible] = useState(false); const [isVisible, setVisible] = useState(false);
const { height }: Partial<IDimensionsContextProps> = useDimensions(); const { height } = useDimensions();
const { isLandscape } = useOrientation(); const { isLandscape } = useOrientation();
const insets = useSafeAreaInsets(); const insets = useSafeAreaInsets();
const maxSnap = Math.max( const maxSnap = Math.max(
height! - height -
// Items height // Items height
ITEM_HEIGHT * (data?.options?.length || 0) - ITEM_HEIGHT * (data?.options?.length || 0) -
// Handle height // Handle height
@ -69,7 +69,7 @@ const ActionSheet = React.memo(
* we'll provide more one snap * we'll provide more one snap
* that point 50% of the whole screen * that point 50% of the whole screen
*/ */
const snaps: any = height! - maxSnap > height! * 0.6 && !isLandscape ? [maxSnap, height! * 0.5, height] : [maxSnap, height]; const snaps = height - maxSnap > height * 0.6 && !isLandscape ? [maxSnap, height * 0.5, height] : [maxSnap, height];
const openedSnapIndex = snaps.length > 2 ? 1 : 0; const openedSnapIndex = snaps.length > 2 ? 1 : 0;
const closedSnapIndex = snaps.length - 1; const closedSnapIndex = snaps.length - 1;
@ -79,12 +79,12 @@ const ActionSheet = React.memo(
bottomSheetRef.current?.snapTo(closedSnapIndex); bottomSheetRef.current?.snapTo(closedSnapIndex);
}; };
const show = (options: any) => { const show = (options: TActionSheetOptions) => {
setData(options); setData(options);
toggleVisible(); toggleVisible();
}; };
const onBackdropPressed = ({ nativeEvent }: any) => { const onBackdropPressed = ({ nativeEvent }: { nativeEvent: HandlerStateChangeEventPayload }) => {
if (nativeEvent.oldState === State.ACTIVE) { if (nativeEvent.oldState === State.ACTIVE) {
hide(); hide();
} }
@ -117,7 +117,7 @@ const ActionSheet = React.memo(
const renderHandle = () => ( const renderHandle = () => (
<> <>
<Handle theme={theme} /> <Handle />
{isValidElement(data?.customHeader) ? data.customHeader : null} {isValidElement(data?.customHeader) ? data.customHeader : null}
</> </>
); );
@ -127,21 +127,23 @@ const ActionSheet = React.memo(
<Button <Button
onPress={hide} onPress={hide}
style={[styles.button, { backgroundColor: themes[theme].auxiliaryBackground }]} style={[styles.button, { backgroundColor: themes[theme].auxiliaryBackground }]}
// TODO: Remove when migrate Touch
theme={theme} theme={theme}
accessibilityLabel={I18n.t('Cancel')}> accessibilityLabel={I18n.t('Cancel')}>
<Text style={[styles.text, { color: themes[theme].bodyText }]}>{I18n.t('Cancel')}</Text> <Text style={[styles.text, { color: themes[theme].bodyText }]}>{I18n.t('Cancel')}</Text>
</Button> </Button>
) : null; ) : null;
const renderItem = ({ item }: any) => <Item item={item} hide={hide} theme={theme} />; const renderItem = ({ item }: { item: IActionSheetItem['item'] }) => <Item item={item} hide={hide} />;
const animatedPosition = React.useRef(new Value(0)); const animatedPosition = React.useRef(new Value(0));
// TODO: Similar to https://github.com/wcandillon/react-native-redash/issues/307#issuecomment-827442320
const opacity = interpolateNode(animatedPosition.current, { const opacity = interpolateNode(animatedPosition.current, {
inputRange: [0, 1], inputRange: [0, 1],
outputRange: [0, themes[theme].backdropOpacity], outputRange: [0, themes[theme].backdropOpacity],
extrapolate: Extrapolate.CLAMP extrapolate: Extrapolate.CLAMP
}) as any; }) as any; // The function's return differs from the expected type of opacity, however this problem is something related to lib, maybe when updating the types will be fixed.
const bottomSheet = isLandscape || isTablet ? styles.bottomSheet : {};
return ( return (
<> <>
@ -160,7 +162,7 @@ const ActionSheet = React.memo(
]} ]}
/> />
</TapGestureHandler> </TapGestureHandler>
<ScrollBottomSheet <ScrollBottomSheet<TActionSheetOptionsItem>
testID='action-sheet' testID='action-sheet'
ref={bottomSheetRef} ref={bottomSheetRef}
componentType='FlatList' componentType='FlatList'
@ -169,18 +171,11 @@ const ActionSheet = React.memo(
renderHandle={renderHandle} renderHandle={renderHandle}
onSettle={index => index === closedSnapIndex && toggleVisible()} onSettle={index => index === closedSnapIndex && toggleVisible()}
animatedPosition={animatedPosition.current} animatedPosition={animatedPosition.current}
containerStyle={ containerStyle={{ ...styles.container, ...bottomSheet, backgroundColor: themes[theme].focusedBackground }}
[
styles.container,
{ backgroundColor: themes[theme].focusedBackground },
(isLandscape || isTablet) && styles.bottomSheet
] as any
}
animationConfig={ANIMATION_CONFIG} animationConfig={ANIMATION_CONFIG}
// FlatList props data={data.options}
data={data?.options}
renderItem={renderItem} renderItem={renderItem}
keyExtractor={(item: any) => item.title} keyExtractor={item => item.title}
style={{ backgroundColor: themes[theme].focusedBackground }} style={{ backgroundColor: themes[theme].focusedBackground }}
contentContainerStyle={styles.content} contentContainerStyle={styles.content}
ItemSeparatorComponent={List.Separator} ItemSeparatorComponent={List.Separator}

View File

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

View File

@ -3,23 +3,24 @@ import { Text, View } from 'react-native';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
import { useTheme } from '../../theme';
import { Button } from './Button'; import { Button } from './Button';
import styles from './styles'; import styles from './styles';
interface IActionSheetItem { export interface IActionSheetItem {
item: { item: {
title: string; title: string;
icon: string; icon: string;
danger: boolean; danger?: boolean;
testID: string; testID?: string;
onPress(): void; onPress: () => void;
right: Function; right?: Function;
}; };
theme: string;
hide(): void; hide(): void;
} }
export const Item = React.memo(({ item, hide, theme }: IActionSheetItem) => { export const Item = React.memo(({ item, hide }: IActionSheetItem) => {
const { theme } = useTheme();
const onPress = () => { const onPress = () => {
hide(); hide();
item?.onPress(); item?.onPress();

View File

@ -1,14 +1,21 @@
import React, { ForwardedRef, forwardRef, useContext, useRef } from 'react'; import React, { ForwardedRef, forwardRef, useContext, useRef } from 'react';
import ActionSheet from './ActionSheet'; import ActionSheet from './ActionSheet';
import { useTheme } from '../../theme';
export type TActionSheetOptionsItem = { title: string; icon: string; onPress: () => void };
export type TActionSheetOptions = {
options: TActionSheetOptionsItem[];
headerHeight: number;
customHeader: React.ReactElement | null;
hasCancel?: boolean;
};
interface IActionSheetProvider { interface IActionSheetProvider {
Provider: any; showActionSheet: (item: TActionSheetOptions) => void;
Consumer: any; hideActionSheet: () => void;
} }
const context: IActionSheetProvider = React.createContext({ const context = React.createContext<IActionSheetProvider>({
showActionSheet: () => {}, showActionSheet: () => {},
hideActionSheet: () => {} hideActionSheet: () => {}
}); });
@ -17,17 +24,16 @@ export const useActionSheet = () => useContext(context);
const { Provider, Consumer } = context; const { Provider, Consumer } = context;
export const withActionSheet = (Component: any): any => export const withActionSheet = (Component: React.ComponentType<any>): typeof Component =>
forwardRef((props: any, ref: ForwardedRef<any>) => ( forwardRef((props: typeof React.Component, ref: ForwardedRef<IActionSheetProvider>) => (
<Consumer>{(contexts: any) => <Component {...props} {...contexts} ref={ref} />}</Consumer> <Consumer>{(contexts: IActionSheetProvider) => <Component {...props} {...contexts} ref={ref} />}</Consumer>
)); ));
export const ActionSheetProvider = React.memo(({ children }: { children: JSX.Element | JSX.Element[] }) => { export const ActionSheetProvider = React.memo(({ children }: { children: React.ReactElement | React.ReactElement[] }) => {
const ref: ForwardedRef<any> = useRef(); const ref: ForwardedRef<IActionSheetProvider> = useRef(null);
const { theme }: any = useTheme();
const getContext = () => ({ const getContext = () => ({
showActionSheet: (options: any) => { showActionSheet: (options: TActionSheetOptions) => {
ref.current?.showActionSheet(options); ref.current?.showActionSheet(options);
}, },
hideActionSheet: () => { hideActionSheet: () => {
@ -37,7 +43,7 @@ export const ActionSheetProvider = React.memo(({ children }: { children: JSX.Ele
return ( return (
<Provider value={getContext()}> <Provider value={getContext()}>
<ActionSheet ref={ref} theme={theme}> <ActionSheet ref={ref}>
<>{children}</> <>{children}</>
</ActionSheet> </ActionSheet>
</Provider> </Provider>

View File

@ -6,10 +6,10 @@ import { TNavigationOptions } from './definitions/navigationTypes';
export interface IDimensionsContextProps { export interface IDimensionsContextProps {
width: number; width: number;
height?: number; height: number;
scale: number; scale?: number;
fontScale: number; fontScale?: number;
setDimensions: ({ setDimensions?: ({
width, width,
height, height,
scale, scale,
@ -22,7 +22,7 @@ export interface IDimensionsContextProps {
}) => void; }) => void;
} }
export const DimensionsContext = React.createContext<Partial<IDimensionsContextProps>>(Dimensions.get('window')); export const DimensionsContext = React.createContext<IDimensionsContextProps>(Dimensions.get('window'));
export function withDimensions<T extends object>(Component: React.ComponentType<T> & TNavigationOptions): typeof Component { export function withDimensions<T extends object>(Component: React.ComponentType<T> & TNavigationOptions): typeof Component {
const DimensionsComponent = (props: T) => ( const DimensionsComponent = (props: T) => (
@ -37,7 +37,7 @@ export const useDimensions = () => React.useContext(DimensionsContext);
export const useOrientation = () => { export const useOrientation = () => {
const { width, height } = React.useContext(DimensionsContext); const { width, height } = React.useContext(DimensionsContext);
const isPortrait = height! > width!; const isPortrait = height > width;
return { return {
isPortrait, isPortrait,
isLandscape: !isPortrait isLandscape: !isPortrait

View File

@ -666,4 +666,4 @@ const mapStateToProps = (state: IApplicationState) => ({
viewAllTeamsPermission: state.permissions['view-all-teams'] viewAllTeamsPermission: state.permissions['view-all-teams']
}); });
export default connect(mapStateToProps)(withActionSheet(withTheme(RoomMembersView))); export default connect(mapStateToProps)(withTheme(withActionSheet(RoomMembersView)));