UNPKG

@wix/design-system

Version:

@wix/design-system

252 lines 11.1 kB
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