diff --git a/src/components/SectionList/index.android.tsx b/src/components/SectionList/index.android.tsx index c119e8e9bcf3..1aa9b501146c 100644 --- a/src/components/SectionList/index.android.tsx +++ b/src/components/SectionList/index.android.tsx @@ -1,22 +1,19 @@ import React, {forwardRef} from 'react'; -import type {ForwardedRef} from 'react'; import {SectionList as RNSectionList} from 'react-native'; -import type {SectionListProps} from 'react-native'; +import type ForwardedSectionList from './types'; // eslint-disable-next-line react/function-component-definition -function SectionListWithRef(props: SectionListProps, ref: ForwardedRef>) { - return ( - - ); -} +const SectionListWithRef: ForwardedSectionList = (props, ref) => ( + +); SectionListWithRef.displayName = 'SectionListWithRef'; diff --git a/src/components/SectionList/index.tsx b/src/components/SectionList/index.tsx index 1129b2bdbb8f..4af7ad33705c 100644 --- a/src/components/SectionList/index.tsx +++ b/src/components/SectionList/index.tsx @@ -1,17 +1,16 @@ import React, {forwardRef} from 'react'; -import type {ForwardedRef} from 'react'; import {SectionList as RNSectionList} from 'react-native'; -import type {SectionListProps} from 'react-native'; +import type ForwardedSectionList from './types'; // eslint-disable-next-line react/function-component-definition -function SectionList(props: SectionListProps, ref: ForwardedRef>) { - return ( - - ); -} +const SectionList: ForwardedSectionList = (props, ref) => ( + +); + +SectionList.displayName = 'SectionList'; export default forwardRef(SectionList); diff --git a/src/components/SectionList/types.ts b/src/components/SectionList/types.ts new file mode 100644 index 000000000000..4648172aabfd --- /dev/null +++ b/src/components/SectionList/types.ts @@ -0,0 +1,9 @@ +import type {ForwardedRef} from 'react'; +import type {SectionList, SectionListProps} from 'react-native'; + +type ForwardedSectionList = { + (props: SectionListProps, ref: ForwardedRef): React.ReactNode; + displayName: string; +}; + +export default ForwardedSectionList; diff --git a/src/components/SelectionList/BaseListItem.tsx b/src/components/SelectionList/BaseListItem.js similarity index 73% rename from src/components/SelectionList/BaseListItem.tsx rename to src/components/SelectionList/BaseListItem.js index 59a1c4dd08ce..6a067ea0fe3d 100644 --- a/src/components/SelectionList/BaseListItem.tsx +++ b/src/components/SelectionList/BaseListItem.js @@ -1,3 +1,4 @@ +import lodashGet from 'lodash/get'; import React from 'react'; import {View} from 'react-native'; import Icon from '@components/Icon'; @@ -11,10 +12,10 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; import RadioListItem from './RadioListItem'; -import type {BaseListItemProps, RadioItem, User} from './types'; +import {baseListItemPropTypes} from './selectionListPropTypes'; import UserListItem from './UserListItem'; -function BaseListItem({ +function BaseListItem({ item, isFocused = false, isDisabled = false, @@ -25,12 +26,13 @@ function BaseListItem({ onDismissError = () => {}, rightHandSideComponent, keyForList, -}: BaseListItemProps) { +}) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); - const isRadioItem = item.rightElement === undefined; + const isUserItem = lodashGet(item, 'icons.length', 0) > 0; + const ListItem = isUserItem ? UserListItem : RadioListItem; const rightHandSideComponentRender = () => { if (canSelectMultiple || !rightHandSideComponent) { @@ -68,7 +70,7 @@ function BaseListItem({ styles.justifyContentBetween, styles.sidebarLinkInner, styles.userSelectNone, - isRadioItem ? styles.optionRow : styles.peopleRow, + isUserItem ? styles.peopleRow : styles.optionRow, isFocused && styles.sidebarLinkActive, ]} > @@ -98,32 +100,20 @@ function BaseListItem({ )} - - {isRadioItem ? ( - onSelectRow(item)} - showTooltip={showTooltip} - /> - ) : ( - onSelectRow(item)} - showTooltip={showTooltip} - /> - )} + {!canSelectMultiple && item.isSelected && !rightHandSideComponent && ( ({ )} {rightHandSideComponentRender()} - {!!item.invitedSecondaryLogin && ( + {Boolean(item.invitedSecondaryLogin) && ( {translate('workspace.people.invitedBySecondaryLogin', {secondaryLogin: item.invitedSecondaryLogin})} @@ -150,5 +140,6 @@ function BaseListItem({ } BaseListItem.displayName = 'BaseListItem'; +BaseListItem.propTypes = baseListItemPropTypes; export default BaseListItem; diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.js similarity index 76% rename from src/components/SelectionList/BaseSelectionList.tsx rename to src/components/SelectionList/BaseSelectionList.js index cc55b8e4fc17..960618808fd9 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.js @@ -1,8 +1,8 @@ import {useFocusEffect, useIsFocused} from '@react-navigation/native'; -import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} from 'react'; -import type {ForwardedRef} from 'react'; +import lodashGet from 'lodash/get'; +import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; -import type {LayoutChangeEvent, SectionList as RNSectionList, TextInput as RNTextInput, SectionListRenderItemInfo} from 'react-native'; +import _ from 'underscore'; import ArrowKeyFocusManager from '@components/ArrowKeyFocusManager'; import Button from '@components/Button'; import Checkbox from '@components/Checkbox'; @@ -13,60 +13,69 @@ import SafeAreaConsumer from '@components/SafeAreaConsumer'; import SectionList from '@components/SectionList'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; +import withKeyboardState, {keyboardStatePropTypes} from '@components/withKeyboardState'; import useActiveElementRole from '@hooks/useActiveElementRole'; import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; import useLocalize from '@hooks/useLocalize'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import Log from '@libs/Log'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import BaseListItem from './BaseListItem'; -import type {BaseSelectionListProps, ButtonOrCheckBoxRoles, FlattenedSectionsReturn, RadioItem, Section, SectionListDataType, User} from './types'; - -function BaseSelectionList( - { - sections, - canSelectMultiple = false, - onSelectRow, - onSelectAll, - onDismissError, - textInputLabel = '', - textInputPlaceholder = '', - textInputValue = '', - textInputHint, - textInputMaxLength, - inputMode = CONST.INPUT_MODE.TEXT, - onChangeText, - initiallyFocusedOptionKey = '', - onScroll, - onScrollBeginDrag, - headerMessage = '', - confirmButtonText = '', - onConfirm = () => {}, - headerContent, - footerContent, - showScrollIndicator = false, - showLoadingPlaceholder = false, - showConfirmButton = false, - shouldPreventDefaultFocusOnSelectRow = false, - containerStyle, - isKeyboardShown = false, - disableKeyboardShortcuts = false, - children, - shouldStopPropagation = false, - shouldShowTooltips = true, - shouldUseDynamicMaxToRenderPerBatch = false, - rightHandSideComponent, - }: BaseSelectionListProps, - inputRef: ForwardedRef, -) { +import {propTypes as selectionListPropTypes} from './selectionListPropTypes'; + +const propTypes = { + ...keyboardStatePropTypes, + ...selectionListPropTypes, +}; + +function BaseSelectionList({ + sections, + canSelectMultiple = false, + onSelectRow, + onSelectAll, + onDismissError, + textInputLabel = '', + textInputPlaceholder = '', + textInputValue = '', + textInputHint = '', + textInputMaxLength, + inputMode = CONST.INPUT_MODE.TEXT, + onChangeText, + initiallyFocusedOptionKey = '', + onScroll, + onScrollBeginDrag, + headerMessage = '', + confirmButtonText = '', + onConfirm, + headerContent, + footerContent, + showScrollIndicator = false, + showLoadingPlaceholder = false, + showConfirmButton = false, + shouldPreventDefaultFocusOnSelectRow = false, + isKeyboardShown = false, + containerStyle = [], + disableInitialFocusOptionStyle = false, + inputRef = null, + disableKeyboardShortcuts = false, + children, + shouldStopPropagation = false, + shouldShowTooltips = true, + shouldUseDynamicMaxToRenderPerBatch = false, + rightHandSideComponent, +}) { + const theme = useTheme(); const styles = useThemeStyles(); + const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); - const listRef = useRef>>(null); - const textInputRef = useRef(null); - const focusTimeoutRef = useRef(null); - const shouldShowTextInput = !!textInputLabel; - const shouldShowSelectAll = !!onSelectAll; + const listRef = useRef(null); + const textInputRef = useRef(null); + const focusTimeoutRef = useRef(null); + const shouldShowTextInput = Boolean(textInputLabel); + const shouldShowSelectAll = Boolean(onSelectAll); const activeElementRole = useActiveElementRole(); const isFocused = useIsFocused(); const [maxToRenderPerBatch, setMaxToRenderPerBatch] = useState(shouldUseDynamicMaxToRenderPerBatch ? 0 : CONST.MAX_TO_RENDER_PER_BATCH.DEFAULT); @@ -78,24 +87,26 @@ function BaseSelectionList( * - `disabledOptionsIndexes`: Contains the indexes of all the disabled items in the list, to be used by the ArrowKeyFocusManager * - `itemLayouts`: Contains the layout information for each item, header and footer in the list, * so we can calculate the position of any given item when scrolling programmatically + * + * @return {{itemLayouts: [{offset: number, length: number}], disabledOptionsIndexes: *[], allOptions: *[]}} */ - const flattenedSections = useMemo>(() => { - const allOptions: TItem[] = []; + const flattenedSections = useMemo(() => { + const allOptions = []; - const disabledOptionsIndexes: number[] = []; + const disabledOptionsIndexes = []; let disabledIndex = 0; let offset = 0; const itemLayouts = [{length: 0, offset}]; - const selectedOptions: TItem[] = []; + const selectedOptions = []; - sections.forEach((section, sectionIndex) => { + _.each(sections, (section, sectionIndex) => { const sectionHeaderHeight = variables.optionsListSectionHeaderHeight; itemLayouts.push({length: sectionHeaderHeight, offset}); offset += sectionHeaderHeight; - section.data.forEach((item, optionIndex) => { + _.each(section.data, (item, optionIndex) => { // Add item to the general flattened array allOptions.push({ ...item, @@ -104,7 +115,7 @@ function BaseSelectionList( }); // If disabled, add to the disabled indexes array - if (!!section.isDisabled || item.isDisabled) { + if (section.isDisabled || item.isDisabled) { disabledOptionsIndexes.push(disabledIndex); } disabledIndex += 1; @@ -144,19 +155,19 @@ function BaseSelectionList( }, [canSelectMultiple, sections]); // If `initiallyFocusedOptionKey` is not passed, we fall back to `-1`, to avoid showing the highlight on the first member - const [focusedIndex, setFocusedIndex] = useState(() => flattenedSections.allOptions.findIndex((option) => option.keyForList === initiallyFocusedOptionKey)); + const [focusedIndex, setFocusedIndex] = useState(() => _.findIndex(flattenedSections.allOptions, (option) => option.keyForList === initiallyFocusedOptionKey)); // Disable `Enter` shortcut if the active element is a button or checkbox - const disableEnterShortcut = activeElementRole && [CONST.ROLE.BUTTON, CONST.ROLE.CHECKBOX].includes(activeElementRole as ButtonOrCheckBoxRoles); + const disableEnterShortcut = activeElementRole && [CONST.ROLE.BUTTON, CONST.ROLE.CHECKBOX].includes(activeElementRole); /** * Scrolls to the desired item index in the section list * - * @param index - the index of the item to scroll to - * @param animated - whether to animate the scroll + * @param {Number} index - the index of the item to scroll to + * @param {Boolean} animated - whether to animate the scroll */ const scrollToIndex = useCallback( - (index: number, animated = true) => { + (index, animated = true) => { const item = flattenedSections.allOptions[index]; if (!listRef.current || !item) { @@ -171,7 +182,7 @@ function BaseSelectionList( // Otherwise, it will cause an index-out-of-bounds error and crash the app. let adjustedSectionIndex = sectionIndex; for (let i = 0; i < sectionIndex; i++) { - if (sections[i].data) { + if (_.isEmpty(lodashGet(sections, `[${i}].data`))) { adjustedSectionIndex--; } } @@ -186,10 +197,10 @@ function BaseSelectionList( /** * Logic to run when a row is selected, either with click/press or keyboard hotkeys. * - * @param item - the list item - * @param shouldUnfocusRow - flag to decide if we should unfocus all rows. True when selecting a row with click or press (not keyboard) + * @param {Object} item - the list item + * @param {Boolean} shouldUnfocusRow - flag to decide if we should unfocus all rows. True when selecting a row with click or press (not keyboard) */ - const selectRow = (item: TItem, shouldUnfocusRow = false) => { + const selectRow = (item, shouldUnfocusRow = false) => { // In single-selection lists we don't care about updating the focused index, because the list is closed after selecting an item if (canSelectMultiple) { if (sections.length > 1) { @@ -222,15 +233,15 @@ function BaseSelectionList( }; const selectAllRow = () => { - onSelectAll?.(); - + onSelectAll(); if (shouldShowTextInput && shouldPreventDefaultFocusOnSelectRow && textInputRef.current) { textInputRef.current.focus(); } }; - const selectFocusedOption = () => { - const focusedOption = flattenedSections.allOptions[focusedIndex]; + const selectFocusedOption = (e) => { + const focusedItemKey = lodashGet(e, ['target', 'attributes', 'id', 'value']); + const focusedOption = focusedItemKey ? _.find(flattenedSections.allOptions, (option) => option.keyForList === focusedItemKey) : flattenedSections.allOptions[focusedIndex]; if (!focusedOption || focusedOption.isDisabled) { return; @@ -243,8 +254,8 @@ function BaseSelectionList( * This function is used to compute the layout of any given item in our list. * We need to implement it so that we can programmatically scroll to items outside the virtual render window of the SectionList. * - * @param data - This is the same as the data we pass into the component - * @param flatDataArrayIndex - This index is provided by React Native, and refers to a flat array with data from all the sections. This flat array has some quirks: + * @param {Array} data - This is the same as the data we pass into the component + * @param {Number} flatDataArrayIndex - This index is provided by React Native, and refers to a flat array with data from all the sections. This flat array has some quirks: * * 1. It ALWAYS includes a list header and a list footer, even if we don't provide/render those. * 2. Each section includes a header, even if we don't provide/render one. @@ -252,8 +263,10 @@ function BaseSelectionList( * For example, given a list with two sections, two items in each section, no header, no footer, and no section headers, the flat array might look something like this: * * [{header}, {sectionHeader}, {item}, {item}, {sectionHeader}, {item}, {item}, {footer}] + * + * @returns {Object} */ - const getItemLayout = (data: Array> | null, flatDataArrayIndex: number) => { + const getItemLayout = (data, flatDataArrayIndex) => { const targetItem = flattenedSections.itemLayouts[flatDataArrayIndex]; if (!targetItem) { @@ -271,8 +284,8 @@ function BaseSelectionList( }; }; - const renderSectionHeader = ({section}: {section: SectionListDataType}) => { - if (!section.title || !section.data) { + const renderSectionHeader = ({section}) => { + if (!section.title || _.isEmpty(section.data)) { return null; } @@ -287,10 +300,9 @@ function BaseSelectionList( ); }; - const renderItem = ({item, index, section}: SectionListRenderItemInfo>) => { - const indexOffset = section.indexOffset ? section.indexOffset : 0; - const normalizedIndex = index + indexOffset; - const isDisabled = !!section.isDisabled || item.isDisabled; + const renderItem = ({item, index, section}) => { + const normalizedIndex = index + lodashGet(section, 'indexOffset', 0); + const isDisabled = section.isDisabled || item.isDisabled; const isItemFocused = !isDisabled && focusedIndex === normalizedIndex; // We only create tooltips for the first 10 users or so since some reports have hundreds of users, causing performance to degrade. const showTooltip = shouldShowTooltips && normalizedIndex < 10; @@ -300,9 +312,11 @@ function BaseSelectionList( item={item} isFocused={isItemFocused} isDisabled={isDisabled} + isHide={!maxToRenderPerBatch} showTooltip={showTooltip} canSelectMultiple={canSelectMultiple} onSelectRow={() => selectRow(item, true)} + disableIsFocusStyle={disableInitialFocusOptionStyle} onDismissError={onDismissError} shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow} rightHandSideComponent={rightHandSideComponent} @@ -312,10 +326,11 @@ function BaseSelectionList( }; const scrollToFocusedIndexOnFirstRender = useCallback( - (nativeEvent: LayoutChangeEvent) => { + ({nativeEvent}) => { if (shouldUseDynamicMaxToRenderPerBatch) { - const listHeight = nativeEvent.nativeEvent.layout.height; - const itemHeight = nativeEvent.nativeEvent.layout.y; + const listHeight = lodashGet(nativeEvent, 'layout.height', 0); + const itemHeight = lodashGet(nativeEvent, 'layout.y', 0); + setMaxToRenderPerBatch((Math.ceil(listHeight / itemHeight) || 0) + CONST.MAX_TO_RENDER_PER_BATCH.DEFAULT); } @@ -329,7 +344,7 @@ function BaseSelectionList( ); const updateAndScrollToFocusedIndex = useCallback( - (newFocusedIndex: number) => { + (newFocusedIndex) => { setFocusedIndex(newFocusedIndex); scrollToIndex(newFocusedIndex, true); }, @@ -340,12 +355,7 @@ function BaseSelectionList( useFocusEffect( useCallback(() => { if (shouldShowTextInput) { - focusTimeoutRef.current = setTimeout(() => { - if (!textInputRef.current) { - return; - } - textInputRef.current.focus(); - }, CONST.ANIMATED_TRANSITION); + focusTimeoutRef.current = setTimeout(() => textInputRef.current.focus(), CONST.ANIMATED_TRANSITION); } return () => { if (!focusTimeoutRef.current) { @@ -372,7 +382,7 @@ function BaseSelectionList( /** Selects row when pressing Enter */ useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.ENTER, selectFocusedOption, { captureOnInputs: true, - shouldBubble: !flattenedSections.allOptions[focusedIndex], + shouldBubble: () => !flattenedSections.allOptions[focusedIndex], shouldStopPropagation, isActive: !disableKeyboardShortcuts && !disableEnterShortcut && isFocused, }); @@ -380,8 +390,8 @@ function BaseSelectionList( /** Calls confirm action when pressing CTRL (CMD) + Enter */ useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.CTRL_ENTER, onConfirm, { captureOnInputs: true, - shouldBubble: !flattenedSections.allOptions[focusedIndex], - isActive: !disableKeyboardShortcuts && !!onConfirm && isFocused, + shouldBubble: () => !flattenedSections.allOptions[focusedIndex], + isActive: !disableKeyboardShortcuts && Boolean(onConfirm) && isFocused, }); return ( @@ -391,22 +401,19 @@ function BaseSelectionList( maxIndex={flattenedSections.allOptions.length - 1} onFocusedIndexChanged={updateAndScrollToFocusedIndex} > + {/* */} {({safeAreaPaddingBottomStyle}) => ( - + {shouldShowTextInput && ( { - textInputRef.current = element as RNTextInput; - - if (!inputRef) { - return; - } - - if (typeof inputRef === 'function') { - inputRef(element as RNTextInput); + ref={(el) => { + if (inputRef) { + // eslint-disable-next-line no-param-reassign + inputRef.current = el; } + textInputRef.current = el; }} label={textInputLabel} accessibilityLabel={textInputLabel} @@ -420,16 +427,16 @@ function BaseSelectionList( selectTextOnFocus spellCheck={false} onSubmitEditing={selectFocusedOption} - blurOnSubmit={!!flattenedSections.allOptions.length} + blurOnSubmit={Boolean(flattenedSections.allOptions.length)} /> )} - {!!headerMessage && ( + {Boolean(headerMessage) && ( {headerMessage} )} - {!!headerContent && headerContent} + {Boolean(headerContent) && headerContent} {flattenedSections.allOptions.length === 0 && showLoadingPlaceholder ? ( ) : ( @@ -465,9 +472,9 @@ function BaseSelectionList( getItemLayout={getItemLayout} onScroll={onScroll} onScrollBeginDrag={onScrollBeginDrag} - keyExtractor={(item: TItem) => item.keyForList} + keyExtractor={(item) => item.keyForList} extraData={focusedIndex} - indicatorStyle="white" + indicatorStyle={theme.white} keyboardShouldPersistTaps="always" showsVerticalScrollIndicator={showScrollIndicator} initialNumToRender={12} @@ -493,7 +500,7 @@ function BaseSelectionList( /> )} - {!!footerContent && {footerContent}} + {Boolean(footerContent) && {footerContent}} )} @@ -502,5 +509,6 @@ function BaseSelectionList( } BaseSelectionList.displayName = 'BaseSelectionList'; +BaseSelectionList.propTypes = propTypes; -export default forwardRef(BaseSelectionList); +export default withKeyboardState(BaseSelectionList); diff --git a/src/components/SelectionList/RadioListItem.tsx b/src/components/SelectionList/RadioListItem.js similarity index 87% rename from src/components/SelectionList/RadioListItem.tsx rename to src/components/SelectionList/RadioListItem.js index 769eaa80df4b..2de0c96932ea 100644 --- a/src/components/SelectionList/RadioListItem.tsx +++ b/src/components/SelectionList/RadioListItem.js @@ -3,11 +3,10 @@ import {View} from 'react-native'; import Text from '@components/Text'; import Tooltip from '@components/Tooltip'; import useThemeStyles from '@hooks/useThemeStyles'; -import type {RadioListItemProps} from './types'; +import {radioListItemPropTypes} from './selectionListPropTypes'; -function RadioListItem({item, showTooltip, textStyles, alternateTextStyles}: RadioListItemProps) { +function RadioListItem({item, showTooltip, textStyles, alternateTextStyles}) { const styles = useThemeStyles(); - return ( - {!!item.alternateText && ( + {Boolean(item.alternateText) && ( - {!!item.icons && ( + {Boolean(item.icons) && ( )} @@ -23,19 +26,19 @@ function UserListItem({item, textStyles, alternateTextStyles, showTooltip, style text={item.text} > {item.text} - {!!item.alternateText && ( + {Boolean(item.alternateText) && ( {item.alternateText} @@ -43,11 +46,12 @@ function UserListItem({item, textStyles, alternateTextStyles, showTooltip, style )} - {!!item.rightElement && item.rightElement} + {Boolean(item.rightElement) && item.rightElement} ); } UserListItem.displayName = 'UserListItem'; +UserListItem.propTypes = userListItemPropTypes; export default UserListItem; diff --git a/src/components/SelectionList/index.android.js b/src/components/SelectionList/index.android.js new file mode 100644 index 000000000000..53d5b6bbce06 --- /dev/null +++ b/src/components/SelectionList/index.android.js @@ -0,0 +1,17 @@ +import React, {forwardRef} from 'react'; +import {Keyboard} from 'react-native'; +import BaseSelectionList from './BaseSelectionList'; + +const SelectionList = forwardRef((props, ref) => ( + Keyboard.dismiss()} + /> +)); + +SelectionList.displayName = 'SelectionList'; + +export default SelectionList; diff --git a/src/components/SelectionList/index.android.tsx b/src/components/SelectionList/index.android.tsx deleted file mode 100644 index 8487c6e2cc67..000000000000 --- a/src/components/SelectionList/index.android.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React, {forwardRef} from 'react'; -import type {ForwardedRef} from 'react'; -import {Keyboard} from 'react-native'; -import type {TextInput} from 'react-native'; -import BaseSelectionList from './BaseSelectionList'; -import type {BaseSelectionListProps, RadioItem, User} from './types'; - -function SelectionList(props: BaseSelectionListProps, ref: ForwardedRef) { - return ( - Keyboard.dismiss()} - /> - ); -} - -SelectionList.displayName = 'SelectionList'; - -export default forwardRef(SelectionList); diff --git a/src/components/SelectionList/index.ios.js b/src/components/SelectionList/index.ios.js new file mode 100644 index 000000000000..7f2a282aeb89 --- /dev/null +++ b/src/components/SelectionList/index.ios.js @@ -0,0 +1,16 @@ +import React, {forwardRef} from 'react'; +import {Keyboard} from 'react-native'; +import BaseSelectionList from './BaseSelectionList'; + +const SelectionList = forwardRef((props, ref) => ( + Keyboard.dismiss()} + /> +)); + +SelectionList.displayName = 'SelectionList'; + +export default SelectionList; diff --git a/src/components/SelectionList/index.ios.tsx b/src/components/SelectionList/index.ios.tsx deleted file mode 100644 index 9c32d38314e2..000000000000 --- a/src/components/SelectionList/index.ios.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React, {forwardRef} from 'react'; -import type {ForwardedRef} from 'react'; -import {Keyboard} from 'react-native'; -import type {TextInput} from 'react-native'; -import BaseSelectionList from './BaseSelectionList'; -import type {BaseSelectionListProps, RadioItem, User} from './types'; - -function SelectionList(props: BaseSelectionListProps, ref: ForwardedRef) { - return ( - - // eslint-disable-next-line react/jsx-props-no-spreading - {...props} - ref={ref} - onScrollBeginDrag={() => Keyboard.dismiss()} - /> - ); -} - -SelectionList.displayName = 'SelectionList'; - -export default forwardRef(SelectionList); diff --git a/src/components/SelectionList/index.tsx b/src/components/SelectionList/index.js similarity index 82% rename from src/components/SelectionList/index.tsx rename to src/components/SelectionList/index.js index 93754926cacb..24ea60d29be5 100644 --- a/src/components/SelectionList/index.tsx +++ b/src/components/SelectionList/index.js @@ -1,12 +1,9 @@ import React, {forwardRef, useEffect, useState} from 'react'; -import type {ForwardedRef} from 'react'; import {Keyboard} from 'react-native'; -import type {TextInput} from 'react-native'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import BaseSelectionList from './BaseSelectionList'; -import type {BaseSelectionListProps, RadioItem, User} from './types'; -function SelectionList(props: BaseSelectionListProps, ref: ForwardedRef) { +const SelectionList = forwardRef((props, ref) => { const [isScreenTouched, setIsScreenTouched] = useState(false); const touchStart = () => setIsScreenTouched(true); @@ -42,8 +39,8 @@ function SelectionList(props: BaseSelectionListP }} /> ); -} +}); SelectionList.displayName = 'SelectionList'; -export default forwardRef(SelectionList); +export default SelectionList; diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts deleted file mode 100644 index 5c28a139903d..000000000000 --- a/src/components/SelectionList/types.ts +++ /dev/null @@ -1,277 +0,0 @@ -import type {ReactElement, ReactNode} from 'react'; -import type {GestureResponderEvent, InputModeOptions, SectionListData, StyleProp, TextStyle, ViewStyle} from 'react-native'; -import type {SubAvatar} from '@components/SubscriptAvatar'; -import type {Errors, PendingAction} from '@src/types/onyx/OnyxCommon'; -import type ChildrenProps from '@src/types/utils/ChildrenProps'; - -type CommonListItemProps = { - /** Whether this item is focused (for arrow key controls) */ - isFocused?: boolean; - - /** Style to be applied to Text */ - textStyles?: StyleProp; - - /** Style to be applied on the alternate text */ - alternateTextStyles?: StyleProp; - - /** Whether this item is disabled */ - isDisabled?: boolean; - - /** Whether this item should show Tooltip */ - showTooltip: boolean; - - /** Whether to use the Checkbox (multiple selection) instead of the Checkmark (single selection) */ - canSelectMultiple?: boolean; - - /** Callback to fire when the item is pressed */ - onSelectRow: (item: TItem) => void; - - /** Callback to fire when an error is dismissed */ - onDismissError?: (item: TItem) => void; - - /** Component to display on the right side */ - rightHandSideComponent?: ((item: TItem) => ReactElement) | ReactElement | null; -}; - -type User = { - /** Text to display */ - text: string; - - /** Alternate text to display */ - alternateText?: string; - - /** Key used internally by React */ - keyForList: string; - - /** Whether this option is selected */ - isSelected?: boolean; - - /** Whether this option is disabled for selection */ - isDisabled?: boolean; - - /** User accountID */ - accountID?: number; - - /** User login */ - login?: string; - - /** Element to show on the right side of the item */ - rightElement: ReactElement; - - /** Icons for the user (can be multiple if it's a Workspace) */ - icons?: SubAvatar[]; - - /** Errors that this user may contain */ - errors?: Errors; - - /** The type of action that's pending */ - pendingAction?: PendingAction; - - invitedSecondaryLogin?: string; - - /** Represents the index of the section it came from */ - sectionIndex: number; - - /** Represents the index of the option within the section it came from */ - index: number; -}; - -type UserListItemProps = CommonListItemProps & { - /** The section list item */ - item: User; - - /** Additional styles to apply to text */ - style?: StyleProp; -}; - -type RadioItem = { - /** Text to display */ - text: string; - - /** Alternate text to display */ - alternateText?: string; - - /** Key used internally by React */ - keyForList: string; - - /** Whether this option is selected */ - isSelected?: boolean; - - /** Element to show on the right side of the item */ - rightElement?: undefined; - - /** Whether this option is disabled for selection */ - isDisabled?: undefined; - - invitedSecondaryLogin?: undefined; - - /** Errors that this user may contain */ - errors?: undefined; - - /** The type of action that's pending */ - pendingAction?: undefined; - - /** Represents the index of the section it came from */ - sectionIndex: number; - - /** Represents the index of the option within the section it came from */ - index: number; -}; - -type RadioListItemProps = CommonListItemProps & { - /** The section list item */ - item: RadioItem; -}; - -type BaseListItemProps = CommonListItemProps & { - item: TItem; - shouldPreventDefaultFocusOnSelectRow?: boolean; - keyForList?: string; -}; - -type Section = { - /** Title of the section */ - title?: string; - - /** The initial index of this section given the total number of options in each section's data array */ - indexOffset?: number; - - /** Array of options */ - data: TItem[]; - - /** Whether this section items disabled for selection */ - isDisabled?: boolean; -}; - -type BaseSelectionListProps = Partial & { - /** Sections for the section list */ - sections: Array>; - - /** Whether this is a multi-select list */ - canSelectMultiple?: boolean; - - /** Callback to fire when a row is pressed */ - onSelectRow: (item: TItem) => void; - - /** Callback to fire when "Select All" checkbox is pressed. Only use along with `canSelectMultiple` */ - onSelectAll?: () => void; - - /** Callback to fire when an error is dismissed */ - onDismissError?: () => void; - - /** Label for the text input */ - textInputLabel?: string; - - /** Placeholder for the text input */ - textInputPlaceholder?: string; - - /** Hint for the text input */ - textInputHint?: string; - - /** Value for the text input */ - textInputValue?: string; - - /** Max length for the text input */ - textInputMaxLength?: number; - - /** Callback to fire when the text input changes */ - onChangeText?: (text: string) => void; - - /** Input mode for the text input */ - inputMode?: InputModeOptions; - - /** Item `keyForList` to focus initially */ - initiallyFocusedOptionKey?: string; - - /** Callback to fire when the list is scrolled */ - onScroll?: () => void; - - /** Callback to fire when the list is scrolled and the user begins dragging */ - onScrollBeginDrag?: () => void; - - /** Message to display at the top of the list */ - headerMessage?: string; - - /** Text to display on the confirm button */ - confirmButtonText?: string; - - /** Callback to fire when the confirm button is pressed */ - onConfirm?: (e?: GestureResponderEvent | KeyboardEvent | undefined) => void; - - /** Whether to show the vertical scroll indicator */ - showScrollIndicator?: boolean; - - /** Whether to show the loading placeholder */ - showLoadingPlaceholder?: boolean; - - /** Whether to show the default confirm button */ - showConfirmButton?: boolean; - - /** Whether tooltips should be shown */ - shouldShowTooltips?: boolean; - - /** Whether to stop automatic form submission on pressing enter key or not */ - shouldStopPropagation?: boolean; - - /** Whether to prevent default focusing of options and focus the textinput when selecting an option */ - shouldPreventDefaultFocusOnSelectRow?: boolean; - - /** Custom content to display in the header */ - headerContent?: ReactNode; - - /** Custom content to display in the footer */ - footerContent?: ReactNode; - - /** Whether to use dynamic maxToRenderPerBatch depending on the visible number of elements */ - shouldUseDynamicMaxToRenderPerBatch?: boolean; - - /** Whether keyboard shortcuts should be disabled */ - disableKeyboardShortcuts?: boolean; - - /** Whether to disable initial styling for focused option */ - disableInitialFocusOptionStyle?: boolean; - - /** Styles to apply to SelectionList container */ - containerStyle?: ViewStyle; - - /** Whether keyboard is visible on the screen */ - isKeyboardShown?: boolean; - - /** Whether focus event should be delayed */ - shouldDelayFocus?: boolean; - - /** Component to display on the right side of each child */ - rightHandSideComponent?: ((item: TItem) => ReactElement) | ReactElement | null; -}; - -type ItemLayout = { - length: number; - offset: number; -}; - -type FlattenedSectionsReturn = { - allOptions: TItem[]; - selectedOptions: TItem[]; - disabledOptionsIndexes: number[]; - itemLayouts: ItemLayout[]; - allSelected: boolean; -}; - -type ButtonOrCheckBoxRoles = 'button' | 'checkbox'; - -type SectionListDataType = SectionListData>; - -export type { - BaseSelectionListProps, - CommonListItemProps, - UserListItemProps, - Section, - RadioListItemProps, - BaseListItemProps, - User, - RadioItem, - FlattenedSectionsReturn, - ItemLayout, - ButtonOrCheckBoxRoles, - SectionListDataType, -}; diff --git a/src/components/SubscriptAvatar.tsx b/src/components/SubscriptAvatar.tsx index 2e2ae6d06e0f..00cf248ad838 100644 --- a/src/components/SubscriptAvatar.tsx +++ b/src/components/SubscriptAvatar.tsx @@ -104,4 +104,3 @@ function SubscriptAvatar({mainAvatar = {}, secondaryAvatar = {}, size = CONST.AV SubscriptAvatar.displayName = 'SubscriptAvatar'; export default memo(SubscriptAvatar); -export type {SubAvatar}; diff --git a/src/hooks/useKeyboardShortcut.ts b/src/hooks/useKeyboardShortcut.ts index 1c5bbc426ef2..6bf8b2c52bc3 100644 --- a/src/hooks/useKeyboardShortcut.ts +++ b/src/hooks/useKeyboardShortcut.ts @@ -26,7 +26,7 @@ type KeyboardShortcutConfig = { * Register a keyboard shortcut handler. * Recommendation: To ensure stability, wrap the `callback` function with the useCallback hook before using it with this hook. */ -export default function useKeyboardShortcut(shortcut: Shortcut, callback: (e?: GestureResponderEvent | KeyboardEvent) => void, config: KeyboardShortcutConfig = {}) { +export default function useKeyboardShortcut(shortcut: Shortcut, callback: (e?: GestureResponderEvent | KeyboardEvent) => void, config: KeyboardShortcutConfig | Record = {}) { const { captureOnInputs = true, shouldBubble = false,