diff --git a/src/components/SectionList/index.android.tsx b/src/components/SectionList/index.android.tsx
index 1aa9b501146c..c119e8e9bcf3 100644
--- a/src/components/SectionList/index.android.tsx
+++ b/src/components/SectionList/index.android.tsx
@@ -1,19 +1,22 @@
import React, {forwardRef} from 'react';
+import type {ForwardedRef} from 'react';
import {SectionList as RNSectionList} from 'react-native';
-import type ForwardedSectionList from './types';
+import type {SectionListProps} from 'react-native';
// eslint-disable-next-line react/function-component-definition
-const SectionListWithRef: ForwardedSectionList = (props, ref) => (
-
-);
+function SectionListWithRef(props: SectionListProps, ref: ForwardedRef>) {
+ return (
+
+ );
+}
SectionListWithRef.displayName = 'SectionListWithRef';
diff --git a/src/components/SectionList/index.tsx b/src/components/SectionList/index.tsx
index 4af7ad33705c..1129b2bdbb8f 100644
--- a/src/components/SectionList/index.tsx
+++ b/src/components/SectionList/index.tsx
@@ -1,16 +1,17 @@
import React, {forwardRef} from 'react';
+import type {ForwardedRef} from 'react';
import {SectionList as RNSectionList} from 'react-native';
-import type ForwardedSectionList from './types';
+import type {SectionListProps} from 'react-native';
// eslint-disable-next-line react/function-component-definition
-const SectionList: ForwardedSectionList = (props, ref) => (
-
-);
-
-SectionList.displayName = 'SectionList';
+function SectionList(props: SectionListProps, ref: ForwardedRef>) {
+ return (
+
+ );
+}
export default forwardRef(SectionList);
diff --git a/src/components/SectionList/types.ts b/src/components/SectionList/types.ts
deleted file mode 100644
index 4648172aabfd..000000000000
--- a/src/components/SectionList/types.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-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.js b/src/components/SelectionList/BaseListItem.tsx
similarity index 73%
rename from src/components/SelectionList/BaseListItem.js
rename to src/components/SelectionList/BaseListItem.tsx
index 6a067ea0fe3d..59a1c4dd08ce 100644
--- a/src/components/SelectionList/BaseListItem.js
+++ b/src/components/SelectionList/BaseListItem.tsx
@@ -1,4 +1,3 @@
-import lodashGet from 'lodash/get';
import React from 'react';
import {View} from 'react-native';
import Icon from '@components/Icon';
@@ -12,10 +11,10 @@ import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import CONST from '@src/CONST';
import RadioListItem from './RadioListItem';
-import {baseListItemPropTypes} from './selectionListPropTypes';
+import type {BaseListItemProps, RadioItem, User} from './types';
import UserListItem from './UserListItem';
-function BaseListItem({
+function BaseListItem({
item,
isFocused = false,
isDisabled = false,
@@ -26,13 +25,12 @@ function BaseListItem({
onDismissError = () => {},
rightHandSideComponent,
keyForList,
-}) {
+}: BaseListItemProps) {
const theme = useTheme();
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const {translate} = useLocalize();
- const isUserItem = lodashGet(item, 'icons.length', 0) > 0;
- const ListItem = isUserItem ? UserListItem : RadioListItem;
+ const isRadioItem = item.rightElement === undefined;
const rightHandSideComponentRender = () => {
if (canSelectMultiple || !rightHandSideComponent) {
@@ -70,7 +68,7 @@ function BaseListItem({
styles.justifyContentBetween,
styles.sidebarLinkInner,
styles.userSelectNone,
- isUserItem ? styles.peopleRow : styles.optionRow,
+ isRadioItem ? styles.optionRow : styles.peopleRow,
isFocused && styles.sidebarLinkActive,
]}
>
@@ -100,20 +98,32 @@ function BaseListItem({
)}
-
+
+ {isRadioItem ? (
+ onSelectRow(item)}
+ showTooltip={showTooltip}
+ />
+ ) : (
+ onSelectRow(item)}
+ showTooltip={showTooltip}
+ />
+ )}
{!canSelectMultiple && item.isSelected && !rightHandSideComponent && (
- {Boolean(item.invitedSecondaryLogin) && (
+ {!!item.invitedSecondaryLogin && (
{translate('workspace.people.invitedBySecondaryLogin', {secondaryLogin: item.invitedSecondaryLogin})}
@@ -140,6 +150,5 @@ function BaseListItem({
}
BaseListItem.displayName = 'BaseListItem';
-BaseListItem.propTypes = baseListItemPropTypes;
export default BaseListItem;
diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.tsx
similarity index 76%
rename from src/components/SelectionList/BaseSelectionList.js
rename to src/components/SelectionList/BaseSelectionList.tsx
index 960618808fd9..cc55b8e4fc17 100644
--- a/src/components/SelectionList/BaseSelectionList.js
+++ b/src/components/SelectionList/BaseSelectionList.tsx
@@ -1,8 +1,8 @@
import {useFocusEffect, useIsFocused} from '@react-navigation/native';
-import lodashGet from 'lodash/get';
-import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
+import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} from 'react';
+import type {ForwardedRef} from 'react';
import {View} from 'react-native';
-import _ from 'underscore';
+import type {LayoutChangeEvent, SectionList as RNSectionList, TextInput as RNTextInput, SectionListRenderItemInfo} from 'react-native';
import ArrowKeyFocusManager from '@components/ArrowKeyFocusManager';
import Button from '@components/Button';
import Checkbox from '@components/Checkbox';
@@ -13,69 +13,60 @@ 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 {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();
+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,
+) {
const styles = useThemeStyles();
- const StyleUtils = useStyleUtils();
const {translate} = useLocalize();
- const listRef = useRef(null);
- const textInputRef = useRef(null);
- const focusTimeoutRef = useRef(null);
- const shouldShowTextInput = Boolean(textInputLabel);
- const shouldShowSelectAll = Boolean(onSelectAll);
+ const listRef = useRef>>(null);
+ const textInputRef = useRef(null);
+ const focusTimeoutRef = useRef(null);
+ const shouldShowTextInput = !!textInputLabel;
+ const shouldShowSelectAll = !!onSelectAll;
const activeElementRole = useActiveElementRole();
const isFocused = useIsFocused();
const [maxToRenderPerBatch, setMaxToRenderPerBatch] = useState(shouldUseDynamicMaxToRenderPerBatch ? 0 : CONST.MAX_TO_RENDER_PER_BATCH.DEFAULT);
@@ -87,26 +78,24 @@ 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 = [];
+ const flattenedSections = useMemo>(() => {
+ const allOptions: TItem[] = [];
- const disabledOptionsIndexes = [];
+ const disabledOptionsIndexes: number[] = [];
let disabledIndex = 0;
let offset = 0;
const itemLayouts = [{length: 0, offset}];
- const selectedOptions = [];
+ const selectedOptions: TItem[] = [];
- _.each(sections, (section, sectionIndex) => {
+ sections.forEach((section, sectionIndex) => {
const sectionHeaderHeight = variables.optionsListSectionHeaderHeight;
itemLayouts.push({length: sectionHeaderHeight, offset});
offset += sectionHeaderHeight;
- _.each(section.data, (item, optionIndex) => {
+ section.data.forEach((item, optionIndex) => {
// Add item to the general flattened array
allOptions.push({
...item,
@@ -115,7 +104,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;
@@ -155,19 +144,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(() => _.findIndex(flattenedSections.allOptions, (option) => option.keyForList === initiallyFocusedOptionKey));
+ const [focusedIndex, setFocusedIndex] = useState(() => flattenedSections.allOptions.findIndex((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);
+ const disableEnterShortcut = activeElementRole && [CONST.ROLE.BUTTON, CONST.ROLE.CHECKBOX].includes(activeElementRole as ButtonOrCheckBoxRoles);
/**
* Scrolls to the desired item index in the section list
*
- * @param {Number} index - the index of the item to scroll to
- * @param {Boolean} animated - whether to animate the scroll
+ * @param index - the index of the item to scroll to
+ * @param animated - whether to animate the scroll
*/
const scrollToIndex = useCallback(
- (index, animated = true) => {
+ (index: number, animated = true) => {
const item = flattenedSections.allOptions[index];
if (!listRef.current || !item) {
@@ -182,7 +171,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 (_.isEmpty(lodashGet(sections, `[${i}].data`))) {
+ if (sections[i].data) {
adjustedSectionIndex--;
}
}
@@ -197,10 +186,10 @@ function BaseSelectionList({
/**
* Logic to run when a row is selected, either with click/press or keyboard hotkeys.
*
- * @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)
+ * @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)
*/
- const selectRow = (item, shouldUnfocusRow = false) => {
+ const selectRow = (item: TItem, 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) {
@@ -233,15 +222,15 @@ function BaseSelectionList({
};
const selectAllRow = () => {
- onSelectAll();
+ onSelectAll?.();
+
if (shouldShowTextInput && shouldPreventDefaultFocusOnSelectRow && textInputRef.current) {
textInputRef.current.focus();
}
};
- const selectFocusedOption = (e) => {
- const focusedItemKey = lodashGet(e, ['target', 'attributes', 'id', 'value']);
- const focusedOption = focusedItemKey ? _.find(flattenedSections.allOptions, (option) => option.keyForList === focusedItemKey) : flattenedSections.allOptions[focusedIndex];
+ const selectFocusedOption = () => {
+ const focusedOption = flattenedSections.allOptions[focusedIndex];
if (!focusedOption || focusedOption.isDisabled) {
return;
@@ -254,8 +243,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 {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:
+ * @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:
*
* 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.
@@ -263,10 +252,8 @@ 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, flatDataArrayIndex) => {
+ const getItemLayout = (data: Array> | null, flatDataArrayIndex: number) => {
const targetItem = flattenedSections.itemLayouts[flatDataArrayIndex];
if (!targetItem) {
@@ -284,8 +271,8 @@ function BaseSelectionList({
};
};
- const renderSectionHeader = ({section}) => {
- if (!section.title || _.isEmpty(section.data)) {
+ const renderSectionHeader = ({section}: {section: SectionListDataType}) => {
+ if (!section.title || !section.data) {
return null;
}
@@ -300,9 +287,10 @@ function BaseSelectionList({
);
};
- const renderItem = ({item, index, section}) => {
- const normalizedIndex = index + lodashGet(section, 'indexOffset', 0);
- const isDisabled = section.isDisabled || item.isDisabled;
+ const renderItem = ({item, index, section}: SectionListRenderItemInfo>) => {
+ const indexOffset = section.indexOffset ? section.indexOffset : 0;
+ const normalizedIndex = index + indexOffset;
+ 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;
@@ -312,11 +300,9 @@ 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}
@@ -326,11 +312,10 @@ function BaseSelectionList({
};
const scrollToFocusedIndexOnFirstRender = useCallback(
- ({nativeEvent}) => {
+ (nativeEvent: LayoutChangeEvent) => {
if (shouldUseDynamicMaxToRenderPerBatch) {
- const listHeight = lodashGet(nativeEvent, 'layout.height', 0);
- const itemHeight = lodashGet(nativeEvent, 'layout.y', 0);
-
+ const listHeight = nativeEvent.nativeEvent.layout.height;
+ const itemHeight = nativeEvent.nativeEvent.layout.y;
setMaxToRenderPerBatch((Math.ceil(listHeight / itemHeight) || 0) + CONST.MAX_TO_RENDER_PER_BATCH.DEFAULT);
}
@@ -344,7 +329,7 @@ function BaseSelectionList({
);
const updateAndScrollToFocusedIndex = useCallback(
- (newFocusedIndex) => {
+ (newFocusedIndex: number) => {
setFocusedIndex(newFocusedIndex);
scrollToIndex(newFocusedIndex, true);
},
@@ -355,7 +340,12 @@ function BaseSelectionList({
useFocusEffect(
useCallback(() => {
if (shouldShowTextInput) {
- focusTimeoutRef.current = setTimeout(() => textInputRef.current.focus(), CONST.ANIMATED_TRANSITION);
+ focusTimeoutRef.current = setTimeout(() => {
+ if (!textInputRef.current) {
+ return;
+ }
+ textInputRef.current.focus();
+ }, CONST.ANIMATED_TRANSITION);
}
return () => {
if (!focusTimeoutRef.current) {
@@ -382,7 +372,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,
});
@@ -390,8 +380,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 && Boolean(onConfirm) && isFocused,
+ shouldBubble: !flattenedSections.allOptions[focusedIndex],
+ isActive: !disableKeyboardShortcuts && !!onConfirm && isFocused,
});
return (
@@ -401,19 +391,22 @@ function BaseSelectionList({
maxIndex={flattenedSections.allOptions.length - 1}
onFocusedIndexChanged={updateAndScrollToFocusedIndex}
>
- {/* */}
{({safeAreaPaddingBottomStyle}) => (
-
+
{shouldShowTextInput && (
{
- if (inputRef) {
- // eslint-disable-next-line no-param-reassign
- inputRef.current = el;
+ ref={(element) => {
+ textInputRef.current = element as RNTextInput;
+
+ if (!inputRef) {
+ return;
+ }
+
+ if (typeof inputRef === 'function') {
+ inputRef(element as RNTextInput);
}
- textInputRef.current = el;
}}
label={textInputLabel}
accessibilityLabel={textInputLabel}
@@ -427,16 +420,16 @@ function BaseSelectionList({
selectTextOnFocus
spellCheck={false}
onSubmitEditing={selectFocusedOption}
- blurOnSubmit={Boolean(flattenedSections.allOptions.length)}
+ blurOnSubmit={!!flattenedSections.allOptions.length}
/>
)}
- {Boolean(headerMessage) && (
+ {!!headerMessage && (
{headerMessage}
)}
- {Boolean(headerContent) && headerContent}
+ {!!headerContent && headerContent}
{flattenedSections.allOptions.length === 0 && showLoadingPlaceholder ? (
) : (
@@ -472,9 +465,9 @@ function BaseSelectionList({
getItemLayout={getItemLayout}
onScroll={onScroll}
onScrollBeginDrag={onScrollBeginDrag}
- keyExtractor={(item) => item.keyForList}
+ keyExtractor={(item: TItem) => item.keyForList}
extraData={focusedIndex}
- indicatorStyle={theme.white}
+ indicatorStyle="white"
keyboardShouldPersistTaps="always"
showsVerticalScrollIndicator={showScrollIndicator}
initialNumToRender={12}
@@ -500,7 +493,7 @@ function BaseSelectionList({
/>
)}
- {Boolean(footerContent) && {footerContent}}
+ {!!footerContent && {footerContent}}
)}
@@ -509,6 +502,5 @@ function BaseSelectionList({
}
BaseSelectionList.displayName = 'BaseSelectionList';
-BaseSelectionList.propTypes = propTypes;
-export default withKeyboardState(BaseSelectionList);
+export default forwardRef(BaseSelectionList);
diff --git a/src/components/SelectionList/RadioListItem.js b/src/components/SelectionList/RadioListItem.tsx
similarity index 87%
rename from src/components/SelectionList/RadioListItem.js
rename to src/components/SelectionList/RadioListItem.tsx
index 2de0c96932ea..769eaa80df4b 100644
--- a/src/components/SelectionList/RadioListItem.js
+++ b/src/components/SelectionList/RadioListItem.tsx
@@ -3,10 +3,11 @@ import {View} from 'react-native';
import Text from '@components/Text';
import Tooltip from '@components/Tooltip';
import useThemeStyles from '@hooks/useThemeStyles';
-import {radioListItemPropTypes} from './selectionListPropTypes';
+import type {RadioListItemProps} from './types';
-function RadioListItem({item, showTooltip, textStyles, alternateTextStyles}) {
+function RadioListItem({item, showTooltip, textStyles, alternateTextStyles}: RadioListItemProps) {
const styles = useThemeStyles();
+
return (
- {Boolean(item.alternateText) && (
+ {!!item.alternateText && (
- {Boolean(item.icons) && (
+ {!!item.icons && (
)}
@@ -26,19 +23,19 @@ function UserListItem({item, textStyles, alternateTextStyles, showTooltip, style
text={item.text}
>
{item.text}
- {Boolean(item.alternateText) && (
+ {!!item.alternateText && (
{item.alternateText}
@@ -46,12 +43,11 @@ function UserListItem({item, textStyles, alternateTextStyles, showTooltip, style
)}
- {Boolean(item.rightElement) && item.rightElement}
+ {!!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
deleted file mode 100644
index 53d5b6bbce06..000000000000
--- a/src/components/SelectionList/index.android.js
+++ /dev/null
@@ -1,17 +0,0 @@
-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
new file mode 100644
index 000000000000..8487c6e2cc67
--- /dev/null
+++ b/src/components/SelectionList/index.android.tsx
@@ -0,0 +1,22 @@
+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
deleted file mode 100644
index 7f2a282aeb89..000000000000
--- a/src/components/SelectionList/index.ios.js
+++ /dev/null
@@ -1,16 +0,0 @@
-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
new file mode 100644
index 000000000000..9c32d38314e2
--- /dev/null
+++ b/src/components/SelectionList/index.ios.tsx
@@ -0,0 +1,21 @@
+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.js b/src/components/SelectionList/index.tsx
similarity index 82%
rename from src/components/SelectionList/index.js
rename to src/components/SelectionList/index.tsx
index 24ea60d29be5..93754926cacb 100644
--- a/src/components/SelectionList/index.js
+++ b/src/components/SelectionList/index.tsx
@@ -1,9 +1,12 @@
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';
-const SelectionList = forwardRef((props, ref) => {
+function SelectionList(props: BaseSelectionListProps, ref: ForwardedRef) {
const [isScreenTouched, setIsScreenTouched] = useState(false);
const touchStart = () => setIsScreenTouched(true);
@@ -39,8 +42,8 @@ const SelectionList = forwardRef((props, ref) => {
}}
/>
);
-});
+}
SelectionList.displayName = 'SelectionList';
-export default SelectionList;
+export default forwardRef(SelectionList);
diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts
new file mode 100644
index 000000000000..5c28a139903d
--- /dev/null
+++ b/src/components/SelectionList/types.ts
@@ -0,0 +1,277 @@
+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 00cf248ad838..2e2ae6d06e0f 100644
--- a/src/components/SubscriptAvatar.tsx
+++ b/src/components/SubscriptAvatar.tsx
@@ -104,3 +104,4 @@ 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 6bf8b2c52bc3..1c5bbc426ef2 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 | Record = {}) {
+export default function useKeyboardShortcut(shortcut: Shortcut, callback: (e?: GestureResponderEvent | KeyboardEvent) => void, config: KeyboardShortcutConfig = {}) {
const {
captureOnInputs = true,
shouldBubble = false,