import * as React from 'react' ; import Menu from '@synerise/ds-menu' ; import Result from '@synerise/ds-result' ; import { NOOP } from '@synerise/ds-utils' ; import invariant from 'invariant' ; import TagsListContext , { defaultValue } from './TagsListContext' ; import Item from './Elements/Item/Item' ; import Toolbar from './Elements/Toolbar' ; import { TagsListActions , TagsListItem , TagsListProps , TagVisibility } from './TagsList.types' ; import { sortAlphabetically } from './utils' ; import ShowLessOrMore from './Elements/ShowLessOrMore/ShowLessOrMore' ; import useTexts from './useTexts' ; import './style/index.less' ; import TagsListContainer from './TagsList.styles' ; export const DEFAULT_STEP = 10 ; export const DEFAULT_ITEMS_VISIBLE = 10 ; export function replaceItem ( items , item , index ? ) { const newItems = [ ... items ] ; const idx = index || newItems . findIndex ( ( findItem ) => findItem . id === item . id ) ; if ( idx > - 1 ) newItems [ idx ] = item ; return [ newItems , newItems [ idx ] ] ; } const TagsList = props => { const { items : controlledItems , defaultItems = [ ] , maxItemsVisible = DEFAULT_ITEMS_VISIBLE , onItemsAdd , onChange , onManageTags = NOOP , texts : propTexts = { } , showMoreStep = DEFAULT_STEP , withCheckbox = true , } = props ; const isControlled = 'items' in props ; const texts = useTexts ( propTexts ) ; const [ searchQuery , setSearchQuery ] = React . useState < string > ( '' ) ; const [ searchOpen , setSearchOpen ] = React . useState < boolean > ( false ) ; const searchFilter = ( item ) : RegExpMatchArray | null => { return item . name . toLowerCase ( ) . match ( searchQuery . toLowerCase ( ) ) ; } ; const [ items , setItems ] = React . useState < TagsListItem [ ] > ( controlledItems || defaultItems ) ; const [ visibleItemsCount , setVisibleItemsCount ] = React . useState < number > ( maxItemsVisible ) ; React . useEffect ( ( ) => { invariant ( ! ( isControlled && ! onChange ) , `${ TagsList . displayName }: You've added controlled 'items' list but there is no onChange event function!` ) ; } , [ isControlled , onChange ] ) ; React . useEffect ( ( ) => { if ( isControlled && JSON . stringify ( controlledItems ) !== JSON . stringify ( items ) ) { setItems ( controlledItems || [ ] ) ; } } , [ isControlled , controlledItems , items ] ) ; React . useEffect ( ( ) => { setVisibleItemsCount ( maxItemsVisible && maxItemsVisible > 0 ? maxItemsVisible : DEFAULT_ITEMS_VISIBLE ) ; } , [ maxItemsVisible ] ) ; React . useEffect ( ( ) => { const itemsCount = items . length ; if ( maxItemsVisible && visibleItemsCount > itemsCount ) { setVisibleItemsCount ( itemsCount ) ; } } , [ items , visibleItemsCount , maxItemsVisible ] ) ; const handleOnChange = ( action , item ) : void => { let newItems ; let newItem ; switch ( action ) { case TagsListActions . Favourite : [ newItems , newItem ] = replaceItem ( items , { ... item , favourite : ! item . favourite } ) ; break ; case TagsListActions . Select : [ newItems , newItem ] = replaceItem ( items , { ... item , checked : ! item . checked } ) ; break ; case TagsListActions . Delete : newItems = items . filter ( thisItem => thisItem . id !== item . id ) ; newItem = item ; break ; default : [ newItems , newItem ] = replaceItem ( items , item ) ; } isControlled ? onChange && onChange ( action , newItems , newItem , items , item ) : setItems ( newItems ) ; } ; const onItemEdit = ( item ) : void => { handleOnChange ( TagsListActions . Edit , item ) ; } ; const onItemFavourite = ( item ) : void => { handleOnChange ( TagsListActions . Favourite , item ) ; } ; const onItemVisibility = ( visibility , item ) : void => { handleOnChange ( TagsListActions . Visibility , { ... item , visibility } ) ; } ; const onItemDelete = ( deleted ) : void => { handleOnChange ( TagsListActions . Delete , deleted ) ; } ; const onItemSelect = ( item ) : void => { handleOnChange ( TagsListActions . Select , item ) ; } ; const renderItem = ( item ) : React . ReactNode => ( < Item item = { item } key = { `${ item . id }-${ item . name }` } checked = { item . checked } onDelete = { item . canDelete ? onItemDelete : NOOP } onEdit = { item . canUpdate ? onItemEdit : undefined } onFavouriteChange = { onItemFavourite } onVisibilityChange = { onItemVisibility } withCheckbox = { withCheckbox } onSettingsEnter = { item . canEnterSettings ? ( event ) : void => { onManageTags && onManageTags ( event ) ; } : undefined } onItemSelect = { onItemSelect } texts = { texts } / > ) ; const renderItemsList = ( ) : React . ReactNode => { const favouriteItems = items . filter ( i => i . favourite ) . sort ( sortAlphabetically ) ; const restOfItems = items . filter ( i => ! i . favourite ) . sort ( sortAlphabetically ) ; const total = searchQuery ? [ ... favouriteItems , ... restOfItems ] . filter ( searchFilter ) : [ ... favouriteItems , ... restOfItems ] . slice ( 0 , visibleItemsCount ) ; if ( ! total . length ) return < Result description = "No results" noSearchResults type = "no-results" / > ; return total . map ( renderItem ) ; } ; const contextValue = { ... defaultValue , ... props , texts , searchQuery , setSearchQuery , searchOpen , setSearchOpen , } ; if ( ! isControlled && ! onItemsAdd ) contextValue . onItemsAdd = ( addItems ) : void => { const newItems = addItems . map ( item => ( { ... item , checked : false } ) ) ; const newList = [ ... items ] ; newItems . forEach ( ( item ) => { if ( newList . findIndex ( ( row ) => row . id === item . id ) === - 1 ) newList . push ( item ) ; } ) ; setItems ( newList ) ; } ; return ( < TagsListContainer > < TagsListContext . Provider value = { contextValue } > < Toolbar /> < Menu > { renderItemsList ( ) } < /Menu> { ! searchQuery && ( < ShowLessOrMore onShowMore = { ( more ) : void => { setVisibleItemsCount ( visibleItemsCount + more ) ; } } onShowLess = { ( less ) : void => { setVisibleItemsCount ( visibleItemsCount - less ) ; } } totalItemsCount = { items . length } visibleItemsCount = { items . length <= visibleItemsCount ? items . length : visibleItemsCount } texts = { texts } maxItemsToShow = { Number ( maxItemsVisible ) } showMoreStep = { showMoreStep } / > ) } < /TagsListContext.Provider> < /TagsListContainer>); } ; export default TagsList ;