{!is_footer && (
diff --git a/packages/components/src/components/data-list/data-list-row.jsx b/packages/components/src/components/data-list/data-list-row.tsx
similarity index 83%
rename from packages/components/src/components/data-list/data-list-row.jsx
rename to packages/components/src/components/data-list/data-list-row.tsx
index 04ae01fb6a90..ed96acdd15fe 100644
--- a/packages/components/src/components/data-list/data-list-row.jsx
+++ b/packages/components/src/components/data-list/data-list-row.tsx
@@ -1,8 +1,24 @@
import classNames from 'classnames';
-import PropTypes from 'prop-types';
import React from 'react';
import { NavLink } from 'react-router-dom';
import { useIsMounted } from '@deriv/shared';
+import { TPassThrough, TRow, TRowRenderer } from './data-list';
+
+type TDataListRow = {
+ action_desc?: {
+ component: React.ReactNode;
+ };
+ destination_link?: string;
+ row_gap?: number;
+ row_key: string | number;
+ rowRenderer: TRowRenderer;
+ measure?: () => void;
+ is_dynamic_height?: boolean;
+ is_new_row: boolean;
+ is_scrolling: boolean;
+ passthrough?: TPassThrough;
+ row: TRow;
+};
const DataListRow = ({
action_desc,
@@ -13,7 +29,7 @@ const DataListRow = ({
measure,
is_dynamic_height,
...other_props
-}) => {
+}: TDataListRow) => {
const [show_desc, setShowDesc] = React.useState(false);
const isMounted = useIsMounted();
@@ -62,14 +78,4 @@ const DataListRow = ({
);
};
-DataListRow.propTypes = {
- action_desc: PropTypes.object,
- destination_link: PropTypes.string,
- row_gap: PropTypes.number,
- row_key: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
- rowRenderer: PropTypes.func,
- measure: PropTypes.func,
- is_dynamic_height: PropTypes.bool,
-};
-
export default React.memo(DataListRow);
diff --git a/packages/components/src/components/data-list/data-list.jsx b/packages/components/src/components/data-list/data-list.jsx
deleted file mode 100644
index a6565b13605d..000000000000
--- a/packages/components/src/components/data-list/data-list.jsx
+++ /dev/null
@@ -1,204 +0,0 @@
-import classNames from 'classnames';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { TransitionGroup } from 'react-transition-group';
-import { CellMeasurer, CellMeasurerCache } from '@enykeev/react-virtualized/dist/es/CellMeasurer';
-import { AutoSizer } from '@enykeev/react-virtualized/dist/es/AutoSizer';
-import { List } from '@enykeev/react-virtualized/dist/es/List';
-import { isMobile, isDesktop } from '@deriv/shared';
-import DataListCell from './data-list-cell.jsx';
-import DataListRow from './data-list-row.jsx';
-import ThemedScrollbars from '../themed-scrollbars';
-
-const DataList = React.memo(
- ({
- children,
- className,
- data_source,
- footer,
- getRowSize,
- keyMapper,
- onRowsRendered,
- onScroll,
- setListRef,
- overscanRowCount,
- ...other_props
- }) => {
- const [is_loading, setLoading] = React.useState(true);
- const [is_scrolling, setIsScrolling] = React.useState(false);
- const [scroll_top, setScrollTop] = React.useState(0);
-
- const cache = React.useRef();
- const list_ref = React.useRef();
- const items_transition_map_ref = React.useRef({});
- const data_source_ref = React.useRef();
- data_source_ref.current = data_source;
-
- const is_dynamic_height = !getRowSize;
-
- const trackItemsForTransition = React.useCallback(() => {
- data_source.forEach((item, index) => {
- const row_key = keyMapper?.(item) || `${index}-0`;
- items_transition_map_ref.current[row_key] = true;
- });
- }, [data_source, keyMapper]);
-
- React.useEffect(() => {
- if (is_dynamic_height) {
- cache.current = new CellMeasurerCache({
- fixedWidth: true,
- keyMapper: row_index => {
- if (row_index < data_source_ref.current.length)
- return keyMapper?.(data_source_ref.current[row_index]) || row_index;
- return row_index;
- },
- });
- }
- trackItemsForTransition();
- setLoading(false);
- }, []); // eslint-disable-line react-hooks/exhaustive-deps
-
- React.useEffect(() => {
- if (is_dynamic_height) {
- list_ref.current?.recomputeGridSize(0);
- }
- trackItemsForTransition();
- }, [data_source, is_dynamic_height, trackItemsForTransition]);
-
- const footerRowRenderer = () => {
- return
{other_props.rowRenderer({ row: footer, is_footer: true })};
- };
-
- const rowRenderer = ({ style, index, key, parent }) => {
- const { getRowAction, passthrough, row_gap } = other_props;
- const row = data_source[index];
- const action = getRowAction && getRowAction(row);
- const destination_link = typeof action === 'string' ? action : undefined;
- const action_desc = typeof action === 'object' ? action : undefined;
- const row_key = keyMapper?.(row) || key;
-
- const getContent = ({ measure } = {}) => (
-
- );
-
- return is_dynamic_height ? (
-
- {({ measure }) => {getContent({ measure })}
}
-
- ) : (
-
- {getContent()}
-
- );
- };
-
- const handleScroll = ev => {
- clearTimeout(timeout);
- if (!is_scrolling) {
- setIsScrolling(true);
- }
- const timeout = setTimeout(() => {
- if (!is_loading) {
- setIsScrolling(false);
- }
- }, 200);
-
- setScrollTop(ev.target.scrollTop);
- if (typeof onScroll === 'function') {
- onScroll(ev);
- }
- };
-
- const setRef = ref => {
- list_ref.current = ref;
- if (typeof setListRef === 'function') {
- setListRef(ref);
- }
- };
-
- if (is_loading) {
- return
;
- }
- return (
-
-
-
-
- {({ width, height }) => (
- // Don't remove `TransitionGroup`. When `TransitionGroup` is removed, transition life cycle events like `onEntered` won't be fired sometimes on it's `CSSTransition` children
-
-
- setRef(ref)}
- rowCount={data_source.length}
- rowHeight={is_dynamic_height ? cache?.current.rowHeight : getRowSize}
- rowRenderer={rowRenderer}
- scrollingResetTimeInterval={0}
- width={width}
- {...(isDesktop()
- ? { scrollTop: scroll_top, autoHeight: true }
- : { onScroll: target => handleScroll({ target }) })}
- />
-
-
- )}
-
-
- {children}
-
- {footer && (
-
- {footerRowRenderer()}
-
- )}
-
- );
- }
-);
-
-DataList.displayName = 'DataList';
-
-DataList.Cell = DataListCell;
-DataList.propTypes = {
- className: PropTypes.string,
- data_source: PropTypes.array,
- footer: PropTypes.object,
- getRowAction: PropTypes.func,
- getRowSize: PropTypes.func,
- keyMapper: PropTypes.func,
- onRowsRendered: PropTypes.func,
- onScroll: PropTypes.func,
- passthrough: PropTypes.object,
- row_gap: PropTypes.number,
- setListRef: PropTypes.func,
- children: PropTypes.oneOfType([PropTypes.node, PropTypes.array]),
- overscanRowCount: PropTypes.number,
-};
-
-export default DataList;
diff --git a/packages/components/src/components/data-list/data-list.tsx b/packages/components/src/components/data-list/data-list.tsx
new file mode 100644
index 000000000000..f32a326be2d1
--- /dev/null
+++ b/packages/components/src/components/data-list/data-list.tsx
@@ -0,0 +1,239 @@
+import classNames from 'classnames';
+import React from 'react';
+import { TransitionGroup } from 'react-transition-group';
+import {
+ List as _List,
+ CellMeasurer as _CellMeasurer,
+ CellMeasurerCache,
+ CellMeasurerProps,
+ AutoSizer as _AutoSizer,
+ type AutoSizerProps,
+ ScrollParams,
+ ListProps,
+ ListRowProps,
+} from 'react-virtualized';
+import { isMobile, isDesktop } from '@deriv/shared';
+import DataListCell from './data-list-cell';
+import DataListRow from './data-list-row';
+import ThemedScrollbars from '../themed-scrollbars';
+import { MeasuredCellParent } from 'react-virtualized/dist/es/CellMeasurer';
+
+const List = _List as unknown as React.FC
;
+const AutoSizer = _AutoSizer as unknown as React.FC;
+const CellMeasurer = _CellMeasurer as unknown as React.FC;
+export type TRowRenderer = (params: { row: any; is_footer?: boolean; measure?: () => void }) => React.ReactNode;
+export type TPassThrough = { isTopUp: (item: TRow) => boolean };
+export type TRow = {
+ [key: string]: string;
+};
+type DataListProps = {
+ className?: string;
+ data_source: TRow[];
+ footer?: React.ReactNode;
+ getRowAction?: (row: TRow) => string;
+ getRowSize?: (params: { index: number }) => number;
+ keyMapper?: (row: TRow) => number | string;
+ onRowsRendered?: () => void;
+ onScroll?: (ev: ScrollParams) => void;
+ passthrough?: TPassThrough;
+ row_gap?: number;
+ setListRef?: (ref: MeasuredCellParent) => void;
+ rowRenderer: TRowRenderer;
+ children?: React.ReactNode;
+ overscanRowCount?: number;
+};
+type GetContentType = { measure?: () => void | undefined };
+
+const DataList = ({
+ children,
+ className,
+ data_source,
+ footer,
+ getRowSize,
+ keyMapper,
+ onRowsRendered,
+ onScroll,
+ setListRef,
+ overscanRowCount,
+ rowRenderer: rowRendererProp,
+ row_gap,
+ getRowAction,
+ passthrough,
+}: DataListProps) => {
+ const [is_loading, setLoading] = React.useState(true);
+ const [is_scrolling, setIsScrolling] = React.useState(false);
+ const [scroll_top, setScrollTop] = React.useState(0);
+
+ const cache = React.useRef();
+ const list_ref = React.useRef(null);
+ const items_transition_map_ref = React.useRef<{ [key: string]: boolean }>({});
+ const data_source_ref = React.useRef(null);
+ data_source_ref.current = data_source;
+
+ const is_dynamic_height = !getRowSize;
+
+ const trackItemsForTransition = React.useCallback(() => {
+ data_source.forEach((item: TRow, index: number) => {
+ const row_key: string | number = keyMapper?.(item) || `${index}-0`;
+ items_transition_map_ref.current[row_key] = true;
+ });
+ }, [data_source, keyMapper]);
+
+ React.useEffect(() => {
+ if (is_dynamic_height) {
+ cache.current = new CellMeasurerCache({
+ fixedWidth: true,
+ keyMapper: row_index => {
+ if (data_source_ref?.current && row_index < data_source_ref?.current.length)
+ return keyMapper?.(data_source_ref.current[row_index]) || row_index;
+ return row_index;
+ },
+ });
+ }
+ trackItemsForTransition();
+ setLoading(false);
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
+
+ React.useEffect(() => {
+ if (is_dynamic_height) {
+ list_ref.current?.recomputeGridSize?.({ columnIndex: 0, rowIndex: 0 });
+ }
+ trackItemsForTransition();
+ }, [data_source, is_dynamic_height, trackItemsForTransition]);
+
+ const footerRowRenderer = () => {
+ return {rowRendererProp({ row: footer, is_footer: true })};
+ };
+
+ const rowRenderer = ({ style, index, key, parent }: ListRowProps) => {
+ const row = data_source[index];
+ const action = getRowAction && getRowAction(row);
+ const destination_link = typeof action === 'string' ? action : undefined;
+ const action_desc = typeof action === 'object' ? action : undefined;
+ const row_key = keyMapper?.(row) || key;
+
+ const getContent = ({ measure }: GetContentType = {}) => (
+
+ );
+
+ return is_dynamic_height && cache.current ? (
+
+ {({ measure }) =>