/**
* External dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import {
Button,
Spinner,
TextControl,
withSpokenMessages,
} from '@wordpress/components';
import {
useState,
useMemo,
useEffect,
useCallback,
Fragment,
} from '@wordpress/element';
import { Icon, info } from '@wordpress/icons';
import classnames from 'classnames';
import { useInstanceId } from '@wordpress/compose';
/**
* Internal dependencies
*/
import { getFilteredList, defaultMessages } from './utils';
import SearchListItem from './item';
import Tag from '../tag';
import type {
SearchListItemsType,
SearchListItemType,
SearchListControlProps,
SearchListMessages,
renderItemArgs,
} from './types';
const defaultRenderListItem = ( args: renderItemArgs ): JSX.Element => {
return ;
};
const ListItems = ( props: {
list: SearchListItemsType;
selected: SearchListItemsType;
renderItem: ( args: renderItemArgs ) => JSX.Element;
depth?: number;
onSelect: ( item: SearchListItemType ) => () => void;
instanceId: string | number;
isSingle: boolean;
search: string;
} ): JSX.Element | null => {
const {
list,
selected,
renderItem,
depth = 0,
onSelect,
instanceId,
isSingle,
search,
} = props;
if ( ! list ) {
return null;
}
return (
<>
{ list.map( ( item ) => {
const isSelected =
selected.findIndex( ( { id } ) => id === item.id ) !== -1;
return (
{ renderItem( {
item,
isSelected,
onSelect,
isSingle,
search,
depth,
controlId: instanceId,
} ) }
);
} ) }
>
);
};
const SelectedListItems = ( {
isLoading,
isSingle,
selected,
messages,
onChange,
onRemove,
}: SearchListControlProps & {
messages: SearchListMessages;
onRemove: ( itemId: string | number ) => () => void;
} ) => {
if ( isLoading || isSingle || ! selected ) {
return null;
}
const selectedCount = selected.length;
return (
{ messages.selected( selectedCount ) }
{ selectedCount > 0 ? (
) : null }
{ selectedCount > 0 ? (
{ selected.map( ( item, i ) => (
-
) ) }
) : null }
);
};
const ListItemsContainer = ( {
filteredList,
search,
onSelect,
instanceId,
...props
}: SearchListControlProps & {
messages: SearchListMessages;
filteredList: SearchListItemsType;
search: string;
instanceId: string | number;
onSelect: ( item: SearchListItemType ) => () => void;
} ) => {
const { messages, renderItem, selected, isSingle } = props;
const renderItemCallback = renderItem || defaultRenderListItem;
if ( filteredList.length === 0 ) {
return (
{ search
? // eslint-disable-next-line @wordpress/valid-sprintf
sprintf( messages.noResults, search )
: messages.noItems }
);
}
return (
);
};
/**
* Component to display a searchable, selectable list of items.
*/
export const SearchListControl = (
props: SearchListControlProps
): JSX.Element => {
const {
className = '',
isCompact,
isHierarchical,
isLoading,
isSingle,
list,
messages: customMessages = defaultMessages,
onChange,
onSearch,
selected,
debouncedSpeak,
} = props;
const [ search, setSearch ] = useState( '' );
const instanceId = useInstanceId( SearchListControl );
const messages = useMemo(
() => ( { ...defaultMessages, ...customMessages } ),
[ customMessages ]
);
const filteredList = useMemo( () => {
return getFilteredList( list, search, isHierarchical );
}, [ list, search, isHierarchical ] );
useEffect( () => {
if ( debouncedSpeak ) {
debouncedSpeak( messages.updated );
}
}, [ debouncedSpeak, messages ] );
useEffect( () => {
if ( typeof onSearch === 'function' ) {
onSearch( search );
}
}, [ search, onSearch ] );
const onRemove = useCallback(
( itemId: string | number ) => () => {
if ( isSingle ) {
onChange( [] );
}
const i = selected.findIndex(
( { id: selectedId } ) => selectedId === itemId
);
onChange( [
...selected.slice( 0, i ),
...selected.slice( i + 1 ),
] );
},
[ isSingle, selected, onChange ]
);
const onSelect = useCallback(
( item: SearchListItemType ) => () => {
if ( selected.findIndex( ( { id } ) => id === item.id ) !== -1 ) {
onRemove( item.id )();
return;
}
if ( isSingle ) {
onChange( [ item ] );
} else {
onChange( [ ...selected, item ] );
}
},
[ isSingle, onRemove, onChange, selected ]
);
return (
setSearch( value ) }
/>
{ isLoading ? (
) : (
) }
);
};
export default withSpokenMessages( SearchListControl );