Skip to content

Commit

Permalink
feat(List): add loading and onLoadMore props (#934)
Browse files Browse the repository at this point in the history
Co-authored-by: Yevhenii Chernovol <[email protected]>
  • Loading branch information
imechoim and imechoim authored Aug 24, 2023
1 parent a2219ea commit 5eab30f
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 8 deletions.
7 changes: 7 additions & 0 deletions src/components/List/List.scss
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,11 @@ $block: '.#{variables.$ns}list';
flex: 0 0 auto;
color: var(--g-color-text-hint);
}

&__loading-indicator {
display: flex;
width: 100%;
align-items: center;
justify-content: center;
}
}
41 changes: 34 additions & 7 deletions src/components/List/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import {SortableContainer, SortableElement} from 'react-sortable-hoc';
import AutoSizer, {Size} from 'react-virtualized-auto-sizer';
import {VariableSizeList as ListContainer} from 'react-window';

import {SelectLoadingIndicator} from '../Select/components/SelectList/SelectLoadingIndicator';
import {TextInput} from '../controls';
import {MobileContext} from '../mobile';
import {block} from '../utils/cn';

import {ListItem, SimpleContainer} from './components';
import {ListItem, SimpleContainer, defaultRenderItem} from './components';
import {listNavigationIgnoredKeys} from './constants';
import type {ListItemData, ListProps, ListSortParams} from './types';
import type {ListItemData, ListItemProps, ListProps, ListSortParams} from './types';

import './List.scss';

Expand Down Expand Up @@ -77,6 +78,9 @@ export class List<T = unknown> extends React.Component<ListProps<T>, ListState<T
refFilter = React.createRef<HTMLInputElement>();
refContainer = React.createRef<any>();
blurTimer: ReturnType<typeof setTimeout> | null = null;
loadingItem = {value: '__LIST_ITEM_LOADING__', disabled: true} as unknown as ListItemData<
T & {value: string}
>;

componentDidUpdate(prevProps: ListProps<T>) {
if (this.props.items !== prevProps.items) {
Expand Down Expand Up @@ -137,6 +141,14 @@ export class List<T = unknown> extends React.Component<ListProps<T>, ListState<T
return this.state.items;
}

getItemsWithLoading() {
if (this.props.sortable) {
return this.getItems();
}

return this.props.loading ? [...this.state.items, this.loadingItem] : this.getItems();
}

getActiveItem() {
return typeof this.state.activeItem === 'number' ? this.state.activeItem : null;
}
Expand Down Expand Up @@ -196,10 +208,24 @@ export class List<T = unknown> extends React.Component<ListProps<T>, ListState<T
}
};

private renderItemContent: ListItemProps<T>['renderItem'] = (item, isItemActive, itemIndex) => {
const {onLoadMore} = this.props;

if ('value' in item && item.value === this.loadingItem.value) {
return (
<SelectLoadingIndicator onIntersect={itemIndex === 0 ? undefined : onLoadMore} />
);
}

return this.props.renderItem
? this.props.renderItem(item, isItemActive, itemIndex)
: defaultRenderItem(item);
};

private renderItem = ({index, style}: {index: number; style?: React.CSSProperties}) => {
const {sortHandleAlign} = this.props;
const {items, activeItem} = this.state;
const item = items[index];
const item = this.getItemsWithLoading()[index];
const sortable = this.props.sortable && items.length > 1 && !this.getFilter();
const active = index === activeItem || index === this.props.activeItemIndex;
const Item = sortable ? SortableListItem : ListItem;
Expand All @@ -213,7 +239,7 @@ export class List<T = unknown> extends React.Component<ListProps<T>, ListState<T
item={item}
sortable={sortable}
sortHandleAlign={sortHandleAlign}
renderItem={this.props.renderItem}
renderItem={this.renderItemContent}
itemClassName={this.props.itemClassName}
active={active}
selected={index === this.props.selectedItemIndex}
Expand Down Expand Up @@ -255,7 +281,7 @@ export class List<T = unknown> extends React.Component<ListProps<T>, ListState<T

private renderSimpleContainer() {
const {sortable} = this.props;
const {items} = this.state;
const items = this.getItemsWithLoading();
const Container = sortable ? SortableSimpleContainer : SimpleContainer;

return (
Expand All @@ -278,6 +304,7 @@ export class List<T = unknown> extends React.Component<ListProps<T>, ListState<T
private renderVirtualizedContainer() {
const Container = this.props.sortable ? SortableListContainer : ListContainer;

const items = this.getItemsWithLoading();
return (
<AutoSizer>
{({width, height}: Size) => (
Expand All @@ -286,8 +313,8 @@ export class List<T = unknown> extends React.Component<ListProps<T>, ListState<T
width={width}
height={height}
itemSize={this.getVirtualizedItemHeight}
itemData={this.state.items}
itemCount={this.state.items.length}
itemData={items}
itemCount={items.length}
overscanCount={10}
helperClass={b('item', {sorting: true})}
distance={5}
Expand Down
18 changes: 18 additions & 0 deletions src/components/List/ListLoadingIndicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react';

import {Loader} from '../Loader';
import {block} from '../utils/cn';
import {useIntersection} from '../utils/useIntersection';

const b = block('list');
export const SelectLoadingIndicator = (props: {onIntersect?: () => void}) => {
const ref = React.useRef<HTMLDivElement | null>(null);

useIntersection({element: ref.current, onIntersect: props?.onIntersect});

return (
<div ref={ref} className={b('loading-indicator')}>
<Loader />
</div>
);
};
2 changes: 2 additions & 0 deletions src/components/List/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ Lets you filter and sort items, render items of different height, and select the
| selectedItemIndex | `Number` | | | If a value is set, an item with this index is rendered as selected (the background color is from `--g-color-base-selection`). |
| itemClassName | `String` | | | Custom class name to be added to an item container |
| itemsClassName | `String` | | | Custom class name to be added to an item list |
| loading | `boolean` | | | Add the loading item to the end of the items list. Works like persistant loading indicator while the items list is empty. Does Not work with `sortable` prop. |
| onLoadMore | `Function` | | | Fires when loading indicator gets visible. |

### Virtualization

Expand Down
2 changes: 1 addition & 1 deletion src/components/List/components/ListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {DragHandleIcon} from './DragHandleIcon';

const b = block('list');

const defaultRenderItem = <T extends unknown>(item: T) => String(item);
export const defaultRenderItem = <T extends unknown>(item: T) => String(item);

export class ListItem<T = unknown> extends React.Component<ListItemProps<T>> {
private static publishEvent = eventBroker.withEventPublisher('List');
Expand Down
2 changes: 2 additions & 0 deletions src/components/List/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export type ListProps<T = unknown> = QAProps & {
onFilterEnd?: ({items}: {items: ListItemData<T>[]}) => void;
onSortEnd?: (params: ListSortParams) => void;
autoFocus?: boolean;
loading?: boolean;
onLoadMore?: () => void;
};

export type ListItemProps<T> = {
Expand Down

0 comments on commit 5eab30f

Please sign in to comment.