Skip to content

Commit

Permalink
Merge pull request #33715 from software-mansion-labs/ts/ContextMenu
Browse files Browse the repository at this point in the history
  • Loading branch information
francoisl authored Jan 16, 2024
2 parents 2a5a3a9 + 0247630 commit 84df181
Show file tree
Hide file tree
Showing 28 changed files with 630 additions and 578 deletions.
2 changes: 1 addition & 1 deletion src/components/ConfirmModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type ConfirmModalProps = {
onConfirm: () => void;

/** A callback to call when the form has been closed */
onCancel?: (ref?: React.RefObject<HTMLElement>) => void;
onCancel?: () => void;

/** Modal visibility */
isVisible: boolean;
Expand Down
1 change: 1 addition & 0 deletions src/components/ContextMenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,4 @@ function ContextMenuItem(
ContextMenuItem.displayName = 'ContextMenuItem';

export default forwardRef(ContextMenuItem);
export type {ContextMenuItemHandle};
3 changes: 1 addition & 2 deletions src/components/Modal/index.android.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import {AppState} from 'react-native';
import withWindowDimensions from '@components/withWindowDimensions';
import ComposerFocusManager from '@libs/ComposerFocusManager';
import BaseModal from './BaseModal';
import type BaseModalProps from './types';
Expand Down Expand Up @@ -28,4 +27,4 @@ function Modal({useNativeDriver = true, ...rest}: BaseModalProps) {
}

Modal.displayName = 'Modal';
export default withWindowDimensions(Modal);
export default Modal;
3 changes: 1 addition & 2 deletions src/components/Modal/index.ios.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react';
import withWindowDimensions from '@components/withWindowDimensions';
import BaseModal from './BaseModal';
import type BaseModalProps from './types';

Expand All @@ -15,4 +14,4 @@ function Modal({children, ...rest}: BaseModalProps) {
}

Modal.displayName = 'Modal';
export default withWindowDimensions(Modal);
export default Modal;
3 changes: 1 addition & 2 deletions src/components/Modal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, {useState} from 'react';
import withWindowDimensions from '@components/withWindowDimensions';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import StatusBar from '@libs/StatusBar';
Expand Down Expand Up @@ -55,4 +54,4 @@ function Modal({fullscreen = true, onModalHide = () => {}, type, onModalShow = (
}

Modal.displayName = 'Modal';
export default withWindowDimensions(Modal);
export default Modal;
76 changes: 37 additions & 39 deletions src/components/Modal/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type {View, ViewStyle} from 'react-native';
import type {ViewStyle} from 'react-native';
import type {ModalProps} from 'react-native-modal';
import type {ValueOf} from 'type-fest';
import type {WindowDimensionsProps} from '@components/withWindowDimensions/types';
import type CONST from '@src/CONST';

type PopoverAnchorPosition = {
Expand All @@ -11,57 +10,56 @@ type PopoverAnchorPosition = {
left?: number;
};

type BaseModalProps = WindowDimensionsProps &
Partial<ModalProps> & {
/** Decides whether the modal should cover fullscreen. FullScreen modal has backdrop */
fullscreen?: boolean;
type BaseModalProps = Partial<ModalProps> & {
/** Decides whether the modal should cover fullscreen. FullScreen modal has backdrop */
fullscreen?: boolean;

/** Should we close modal on outside click */
shouldCloseOnOutsideClick?: boolean;
/** Should we close modal on outside click */
shouldCloseOnOutsideClick?: boolean;

/** Should we announce the Modal visibility changes? */
shouldSetModalVisibility?: boolean;
/** Should we announce the Modal visibility changes? */
shouldSetModalVisibility?: boolean;

/** Callback method fired when the user requests to close the modal */
onClose: (ref?: React.RefObject<View | HTMLDivElement>) => void;
/** Callback method fired when the user requests to close the modal */
onClose: () => void;

/** State that determines whether to display the modal or not */
isVisible: boolean;
/** State that determines whether to display the modal or not */
isVisible: boolean;

/** Callback method fired when the user requests to submit the modal content. */
onSubmit?: () => void;
/** Callback method fired when the user requests to submit the modal content. */
onSubmit?: () => void;

/** Callback method fired when the modal is hidden */
onModalHide?: () => void;
/** Callback method fired when the modal is hidden */
onModalHide?: () => void;

/** Callback method fired when the modal is shown */
onModalShow?: () => void;
/** Callback method fired when the modal is shown */
onModalShow?: () => void;

/** Style of modal to display */
type?: ValueOf<typeof CONST.MODAL.MODAL_TYPE>;
/** Style of modal to display */
type?: ValueOf<typeof CONST.MODAL.MODAL_TYPE>;

/** The anchor position of a popover modal. Has no effect on other modal types. */
popoverAnchorPosition?: PopoverAnchorPosition;
/** The anchor position of a popover modal. Has no effect on other modal types. */
popoverAnchorPosition?: PopoverAnchorPosition;

outerStyle?: ViewStyle;
outerStyle?: ViewStyle;

/** Whether the modal should go under the system statusbar */
statusBarTranslucent?: boolean;
/** Whether the modal should go under the system statusbar */
statusBarTranslucent?: boolean;

/** Whether the modal should avoid the keyboard */
avoidKeyboard?: boolean;
/** Whether the modal should avoid the keyboard */
avoidKeyboard?: boolean;

/** Modal container styles */
innerContainerStyle?: ViewStyle;
/** Modal container styles */
innerContainerStyle?: ViewStyle;

/**
* Whether the modal should hide its content while animating. On iOS, set to true
* if `useNativeDriver` is also true, to avoid flashes in the UI.
*
* See: https://github.com/react-native-modal/react-native-modal/pull/116
* */
hideModalContentWhileAnimating?: boolean;
};
/**
* Whether the modal should hide its content while animating. On iOS, set to true
* if `useNativeDriver` is also true, to avoid flashes in the UI.
*
* See: https://github.com/react-native-modal/react-native-modal/pull/116
* */
hideModalContentWhileAnimating?: boolean;
};

export default BaseModalProps;
export type {PopoverAnchorPosition};
2 changes: 1 addition & 1 deletion src/components/PopoverWithoutOverlay/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function PopoverWithoutOverlay(
close: onClose,
anchorRef,
});
removeOnClose = Modal.setCloseModal(() => onClose(anchorRef));
removeOnClose = Modal.setCloseModal(onClose);
} else {
onModalHide();
close(anchorRef);
Expand Down
17 changes: 5 additions & 12 deletions src/components/Reactions/MiniQuickEmojiReactions.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, {useRef} from 'react';
import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import type {Emoji} from '@assets/emojis/types';
import BaseMiniContextMenuItem from '@components/BaseMiniContextMenuItem';
Expand All @@ -16,16 +15,7 @@ import * as EmojiPickerAction from '@userActions/EmojiPickerAction';
import * as Session from '@userActions/Session';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {ReportActionReactions} from '@src/types/onyx';
import type {BaseQuickEmojiReactionsProps} from './QuickEmojiReactions/types';

type MiniQuickEmojiReactionsOnyxProps = {
/** All the emoji reactions for the report action. */
emojiReactions: OnyxEntry<ReportActionReactions>;

/** The user's preferred skin tone. */
preferredSkinTone: OnyxEntry<string | number>;
};
import type {BaseQuickEmojiReactionsOnyxProps, BaseQuickEmojiReactionsProps} from './QuickEmojiReactions/types';

type MiniQuickEmojiReactionsProps = BaseQuickEmojiReactionsProps & {
/**
Expand Down Expand Up @@ -112,11 +102,14 @@ function MiniQuickEmojiReactions({

MiniQuickEmojiReactions.displayName = 'MiniQuickEmojiReactions';

export default withOnyx<MiniQuickEmojiReactionsProps, MiniQuickEmojiReactionsOnyxProps>({
export default withOnyx<MiniQuickEmojiReactionsProps, BaseQuickEmojiReactionsOnyxProps>({
preferredSkinTone: {
key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE,
},
emojiReactions: {
key: ({reportActionID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`,
},
preferredLocale: {
key: ONYXKEYS.NVP_PREFERRED_LOCALE,
},
})(MiniQuickEmojiReactions);
28 changes: 15 additions & 13 deletions src/components/Reactions/QuickEmojiReactions/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,7 @@ type OpenPickerCallback = (element?: PickerRefElement, anchorOrigin?: AnchorOrig

type CloseContextMenuCallback = () => void;

type BaseQuickEmojiReactionsOnyxProps = {
/** All the emoji reactions for the report action. */
emojiReactions: OnyxEntry<ReportActionReactions>;

/** The user's preferred locale. */
preferredLocale: OnyxEntry<Locale>;

/** The user's preferred skin tone. */
preferredSkinTone: OnyxEntry<string | number>;
};

type BaseQuickEmojiReactionsProps = BaseQuickEmojiReactionsOnyxProps & {
type BaseReactionsProps = {
/** Callback to fire when an emoji is selected. */
onEmojiSelected: (emoji: Emoji, emojiReactions: OnyxEntry<ReportActionReactions>) => void;

Expand All @@ -45,7 +34,20 @@ type BaseQuickEmojiReactionsProps = BaseQuickEmojiReactionsOnyxProps & {
reportActionID: string;
};

type QuickEmojiReactionsProps = BaseQuickEmojiReactionsProps & {
type BaseQuickEmojiReactionsOnyxProps = {
/** All the emoji reactions for the report action. */
emojiReactions: OnyxEntry<ReportActionReactions>;

/** The user's preferred locale. */
preferredLocale: OnyxEntry<Locale>;

/** The user's preferred skin tone. */
preferredSkinTone: OnyxEntry<string | number>;
};

type BaseQuickEmojiReactionsProps = BaseReactionsProps & BaseQuickEmojiReactionsOnyxProps;

type QuickEmojiReactionsProps = BaseReactionsProps & {
/**
* Function that can be called to close the context menu
* in which this component is rendered.
Expand Down
6 changes: 4 additions & 2 deletions src/components/ReportActionItem/TaskPreview.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import Str from 'expensify-common/lib/str';
import React from 'react';
// eslint-disable-next-line no-restricted-imports
import type {Text as RNText} from 'react-native';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
Expand Down Expand Up @@ -63,7 +65,7 @@ type TaskPreviewProps = WithCurrentUserPersonalDetailsProps &
chatReportID: string;

/** Popover context menu anchor, used for showing context menu */
contextMenuAnchor: Element;
contextMenuAnchor: RNText | null;

/** Callback for updating context menu active state, used for showing context menu */
checkIfContextMenuActive: () => void;
Expand Down Expand Up @@ -112,7 +114,7 @@ function TaskPreview({
onPress={() => Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(taskReportID))}
onPressIn={() => DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()}
onPressOut={() => ControlSelection.unblock()}
onLongPress={(event) => showContextMenuForReport(event, contextMenuAnchor, chatReportID, action ?? {}, checkIfContextMenuActive)}
onLongPress={(event) => showContextMenuForReport(event, contextMenuAnchor, chatReportID, action, checkIfContextMenuActive)}
style={[styles.flexRow, styles.justifyContentBetween]}
role={CONST.ROLE.BUTTON}
accessibilityLabel={translate('task.task')}
Expand Down
45 changes: 0 additions & 45 deletions src/components/ShowContextMenuContext.js

This file was deleted.

64 changes: 64 additions & 0 deletions src/components/ShowContextMenuContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {createContext} from 'react';
// eslint-disable-next-line no-restricted-imports
import type {GestureResponderEvent, Text as RNText} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import * as ReportUtils from '@libs/ReportUtils';
import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu';
import CONST from '@src/CONST';
import type {Report, ReportAction} from '@src/types/onyx';

type ShowContextMenuContextProps = {
anchor: RNText | null;
report: OnyxEntry<Report>;
action: OnyxEntry<ReportAction>;
checkIfContextMenuActive: () => void;
};

const ShowContextMenuContext = createContext<ShowContextMenuContextProps>({
anchor: null,
report: null,
action: null,
checkIfContextMenuActive: () => {},
});

ShowContextMenuContext.displayName = 'ShowContextMenuContext';

/**
* Show the report action context menu.
*
* @param event - Press event object
* @param anchor - Context menu anchor
* @param reportID - Active Report ID
* @param action - ReportAction for ContextMenu
* @param checkIfContextMenuActive Callback to update context menu active state
* @param isArchivedRoom - Is the report an archived room
*/
function showContextMenuForReport(
event: GestureResponderEvent | MouseEvent,
anchor: RNText | null,
reportID: string,
action: OnyxEntry<ReportAction>,
checkIfContextMenuActive: () => void,
isArchivedRoom = false,
) {
if (!DeviceCapabilities.canUseTouchScreen()) {
return;
}

ReportActionContextMenu.showContextMenu(
CONST.CONTEXT_MENU_TYPES.REPORT_ACTION,
event,
'',
anchor,
reportID,
action?.reportActionID,
ReportUtils.getOriginalReportID(reportID, action),
undefined,
checkIfContextMenuActive,
checkIfContextMenuActive,
isArchivedRoom,
);
}

export {ShowContextMenuContext, showContextMenuForReport};
Loading

0 comments on commit 84df181

Please sign in to comment.