Chore: Evaluate ActionSheet - TypeScript (#3927)
This commit is contained in:
parent
e8d23791d3
commit
2fb7d917a7
|
@ -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}
|
||||||
|
|
|
@ -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(() => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
return (
|
||||||
<View style={[styles.handle, { backgroundColor: themes[theme].focusedBackground }]} testID='action-sheet-handle'>
|
<View style={[styles.handle, { backgroundColor: themes[theme].focusedBackground }]} testID='action-sheet-handle'>
|
||||||
<View style={[styles.handleIndicator, { backgroundColor: themes[theme].auxiliaryText }]} />
|
<View style={[styles.handleIndicator, { backgroundColor: themes[theme].auxiliaryText }]} />
|
||||||
</View>
|
</View>
|
||||||
));
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)));
|
||||||
|
|
Loading…
Reference in New Issue