@wix/design-system
Version:
@wix/design-system
252 lines • 11.1 kB
JavaScript
import React from 'react';
import SelectorListContent from './Content';
import Box from '../Box';
import Search from '../Search';
import Text from '../Text';
import ToggleAllCheckbox from './ToggleAllCheckbox';
import { dataHooks } from './SelectorList.helpers';
import { DEFAULT_IMAGE_SIZE_BY_SIZE, SEARCH_SIZE_BY_SIZE } from './constants';
import { st, classes } from './SelectorList.st.css.js';
/**
* Use this component when needed to select one / multiple items having complex descriptions.
* E.g.: choosing products to promote via ShoutOuts
*/
class SelectorList extends React.PureComponent {
constructor() {
super(...arguments);
this.state = {
isLoaded: false,
isSearching: false,
items: [],
searchValue: '',
selectedItems: [],
indeterminateItems: [],
noResultsFound: false,
isEmpty: false,
};
this._renderList = () => {
const { dataHook, emptyState, renderNoResults, height, maxHeight, size, imageSize, imageShape, multiple, showDivider, } = this.props;
const { items, isLoaded, isEmpty, isSearching, searchValue, noResultsFound, selectedItems, } = this.state;
const hasMore = this._hasMore();
const contentProps = {
items,
selectedItems,
onToggle: this._onToggle,
emptyState,
renderNoResults,
isEmpty,
isLoading: !isLoaded || isSearching,
noResultsFound,
size,
imageSize: imageSize || DEFAULT_IMAGE_SIZE_BY_SIZE[size],
imageShape,
multiple,
showDivider,
loadMore: this._loadMore,
hasMore,
checkIsSelected: this._checkIsSelected,
checkIndeterminate: this._checkIndeterminate,
searchValue,
};
const shouldRenderSubheader = isLoaded && !isEmpty;
return (React.createElement(Box, { direction: "vertical", overflow: "hidden", dataHook,
height,
maxHeight },
shouldRenderSubheader && this._renderSubheader(),
React.createElement(SelectorListContent, { ...contentProps })));
};
this._renderSubheader = () => {
const { subtitle, withSearch, size, searchDebounceMs, searchPlaceholder, autoFocus, searchMaxLength, } = this.props;
const { searchValue } = this.state;
return (React.createElement("div", { className: st(classes.subheaderWrapper, {
withSearch,
size,
}) },
subtitle && (React.createElement("div", { className: classes.subtitleWrapper }, typeof subtitle === 'string' ? (React.createElement(Text, { dataHook: dataHooks.subtitle }, subtitle)) : (subtitle))),
withSearch && (React.createElement(Search, { dataHook: dataHooks.search, placeholder: searchPlaceholder, onChange: this._onSearchChange, onClear: this._onClear, debounceMs: searchDebounceMs, value: searchValue, size: SEARCH_SIZE_BY_SIZE[size], autoFocus: autoFocus, maxLength: searchMaxLength }))));
};
this._renderToggleAllCheckbox = () => {
const { selectAllText, deselectAllText, size } = this.props;
const { items, selectedItems } = this.state;
const enabledItemsAmount = this._getEnabledItems(items).length;
const selectedEnabledItemsAmount = this._getEnabledItems(selectedItems).length;
const checkboxProps = {
selectAllText,
deselectAllText,
size,
enabledItemsAmount,
selectedEnabledItemsAmount,
selectAll: this._selectAll,
deselectAll: this._deselectAll,
};
return React.createElement(ToggleAllCheckbox, { ...checkboxProps });
};
this._updateSearchValue = searchValue => this.setState({
searchValue,
isSearching: true,
items: [],
}, () => this._loadInitialItems(searchValue));
this._onSearchChange = event => this._updateSearchValue(event.target.value);
this._onClear = () => {
const searchValue = '';
this.setState({
searchValue,
isSearching: true,
items: [],
}, () => {
this._getInitialData(searchValue).then(dataSourceProps => {
const { items, selectedItems, indeterminateItems } = this.state;
const newItems = [...items, ...dataSourceProps.items];
const newSelectedItems = selectedItems;
const newIndeterminateItems = indeterminateItems;
const noResultsFound = newItems.length === 0 && Boolean(searchValue);
const isEmpty = newItems.length === 0 && !searchValue;
this.setState({
items: newItems,
selectedItems: newSelectedItems,
indeterminateItems: newIndeterminateItems,
isLoaded: true,
isEmpty,
isSearching: false,
noResultsFound,
totalCount: dataSourceProps.totalCount,
});
});
});
};
this._checkIsSelected = item => {
const { selectedItems } = this.state;
return !!selectedItems.find(({ id }) => item.id === id);
};
this._checkIndeterminate = item => {
const { indeterminateItems } = this.state;
return !!indeterminateItems.find(({ id }) => item.id === id);
};
this._toggleItem = item => {
const { multiple } = this.props;
this.setState(({ selectedItems, indeterminateItems }) => ({
selectedItems: multiple
? this._checkIsSelected(item)
? selectedItems.filter(({ id }) => item.id !== id)
: selectedItems.concat(item)
: [item],
indeterminateItems: indeterminateItems.filter(({ id }) => item.id !== id),
}));
};
this._onToggle = item => {
const { onSelect } = this.props;
this._toggleItem(item);
if (onSelect) {
onSelect(item);
}
};
this._selectAll = () => {
const { selectedItems, items } = this.state;
const enabledItems = this._getEnabledItems(items);
this.setState({
selectedItems: selectedItems.concat(enabledItems),
indeterminateItems: [],
});
};
this._deselectAll = () => this.setState(({ selectedItems }) => ({
selectedItems: selectedItems.filter(({ disabled }) => disabled),
indeterminateItems: [],
}));
this._updateItems = ({ resetItems, items: nextPageItems, totalCount, searchValue, }) => {
const { items, selectedItems, indeterminateItems } = this.state;
// react only to the resolve of the relevant search
if (searchValue !== this.state.searchValue) {
return;
}
const newItems = [...(resetItems ? [] : items), ...nextPageItems];
const newSelectedItems = selectedItems
.concat(nextPageItems.filter(({ selected }) => selected))
.filter((value, index, self) => self.findIndex(({ id }) => id === value.id) === index);
const newIndeterminateItems = indeterminateItems
.concat(nextPageItems.filter(({ indeterminate }) => indeterminate))
.filter((value, index, self) => self.findIndex(({ id }) => id === value.id) === index);
const noResultsFound = newItems.length === 0 && Boolean(searchValue);
const isEmpty = newItems.length === 0 && !searchValue;
this.setState({
items: newItems,
selectedItems: newSelectedItems,
indeterminateItems: newIndeterminateItems,
isLoaded: true,
isEmpty,
isSearching: false,
totalCount,
noResultsFound,
});
};
this._loadInitialItems = (searchValue = '', { resetItems } = {}) => {
this._getInitialData(searchValue).then(dataSourceProps => {
return this._updateItems({
resetItems,
...dataSourceProps,
searchValue,
});
});
};
this._getInitialData = (searchValue = '') => {
const { dataSource, itemsPerPage } = this.props;
const initialAmountToLoad = this.props.initialAmountToLoad || itemsPerPage;
return dataSource(searchValue, 0, initialAmountToLoad);
};
this._loadMore = () => {
const { dataSource, itemsPerPage } = this.props;
const { items, searchValue } = this.state;
dataSource(searchValue, items.length, itemsPerPage).then(dataSourceProps => this._updateItems({
...dataSourceProps,
searchValue,
}));
};
this._getEnabledItems = items => items.filter(({ disabled }) => !disabled);
}
componentDidMount() {
this._loadInitialItems();
}
/** Resets list items and loads first page from dataSource while persisting searchValue */
reloadInitialItems() {
const searchValue = this.state.searchValue;
this.setState({
searchValue,
isSearching: Boolean(searchValue),
isLoaded: false,
});
this._loadInitialItems(searchValue, { resetItems: true });
}
render() {
const { children } = this.props;
const { selectedItems } = this.state;
if (typeof children === 'function') {
return children({
renderList: this._renderList,
renderToggleAllCheckbox: this._renderToggleAllCheckbox,
selectedItems,
});
}
return this._renderList();
}
_hasMore() {
const { items, isLoaded, totalCount, isSearching } = this.state;
return ((items.length === 0 && !isLoaded) ||
items.length < totalCount ||
isSearching);
}
}
SelectorList.displayName = 'SelectorList';
SelectorList.defaultProps = {
searchPlaceholder: 'Search...',
size: 'medium',
imageShape: 'rectangular',
showDivider: false,
itemsPerPage: 50,
withSearch: true,
height: '100%',
maxHeight: '100%',
deselectAllText: 'Deselect all',
multiple: false,
selectAllText: 'Select all',
};
export default SelectorList;
//# sourceMappingURL=SelectorList.js.map