Skip to content

Commit

Permalink
IBX-6907: Image picker: Translations filter (#963)
Browse files Browse the repository at this point in the history
  • Loading branch information
tischsoic committed Nov 20, 2023
1 parent 9daf31b commit 837cc5a
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 19 deletions.
119 changes: 105 additions & 14 deletions src/bundle/ui-dev/src/modules/common/dropdown/dropdown.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef } from 'react';
import React, { useState, useEffect, useRef, useLayoutEffect } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';

Expand All @@ -10,17 +10,34 @@ const MIN_SEARCH_ITEMS_DEFAULT = 5;
const MIN_ITEMS_LIST_HEIGHT = 150;
const ITEMS_LIST_WIDGET_MARGIN = 8;
const ITEMS_LIST_SITE_MARGIN = ITEMS_LIST_WIDGET_MARGIN + 4;
const RESTRICTED_AREA_ITEMS_CONTAINER = 190;

const Dropdown = ({ dropdownListRef, value, options, onChange, small, single, extraClasses, renderSelectedItem, minSearchItems }) => {
const Dropdown = ({
dropdownListRef,
value,
options,
onChange,
small,
single,
placeholder,
extraClasses,
renderSelectedItem,
minSearchItems,
}) => {
const containerRef = useRef();
const containerItemsRef = useRef();
const selectionInfoRef = useRef();
const [isExpanded, setIsExpanded] = useState(false);
const [filterText, setFilterText] = useState('');
const [itemsListStyles, setItemsListStyles] = useState({});
const selectedItem = options.find((option) => option.value === value);
const [overflowItemsCount, setOverflowItemsCount] = useState(0);
const selectedItems = single
? [options.find((option) => option.value === value)]
: value.map((singleValue) => options.find((option) => option.value === singleValue));
const dropdownClassName = createCssClassNames({
'ibexa-dropdown': true,
'ibexa-dropdown--single': single,
'ibexa-dropdown--multi': !single,
'ibexa-dropdown--small': small,
'ibexa-dropdown--expanded': isExpanded,
[extraClasses]: true,
Expand All @@ -42,9 +59,10 @@ const Dropdown = ({ dropdownListRef, value, options, onChange, small, single, ex
return itemValueLowerCase.indexOf(searchedTermLowerCase) === 0;
};
const renderItem = (item) => {
const isItemSelected = single ? item.value === value : value.includes(item.value);
const itemClassName = createCssClassNames({
'ibexa-dropdown__item': true,
'ibexa-dropdown__item--selected': item.value === value,
'ibexa-dropdown__item--selected': isItemSelected,
'ibexa-dropdown__item--hidden': !showItem(item.label, filterText),
});

Expand All @@ -54,13 +72,19 @@ const Dropdown = ({ dropdownListRef, value, options, onChange, small, single, ex
key={item.value}
onClick={() => {
onChange(item.value);
toggleExpanded();

if (single) {
toggleExpanded();
}
}}
>
{!single && <input type="checkbox" className="ibexa-input ibexa-input--checkbox" checked={isItemSelected} />}
<span className="ibexa-dropdown__item-label">{item.label}</span>
<div className="ibexa-dropdown__item-check">
<Icon name="checkmark" extraClasses="ibexa-icon--tiny-small ibexa-dropdown__item-check-icon" />
</div>
{single && (
<div className="ibexa-dropdown__item-check">
<Icon name="checkmark" extraClasses="ibexa-icon--tiny-small ibexa-dropdown__item-check-icon" />
</div>
)}
</li>
);
};
Expand All @@ -86,7 +110,7 @@ const Dropdown = ({ dropdownListRef, value, options, onChange, small, single, ex
setItemsListStyles(itemsStyles);
};
const renderItemsList = () => {
const placeholder = Translator.trans(/*@Desc("Search...")*/ 'dropdown.placeholder', {}, 'ibexa_universal_discovery_widget');
const searchPlaceholder = Translator.trans(/*@Desc("Search...")*/ 'dropdown.placeholder', {}, 'ibexa_universal_discovery_widget');
const itemsContainerClass = createCssClassNames({
'ibexa-dropdown__items': true,
'ibexa-dropdown__items--search-hidden': options.length < minSearchItems,
Expand All @@ -97,7 +121,7 @@ const Dropdown = ({ dropdownListRef, value, options, onChange, small, single, ex
<div className="ibexa-input-text-wrapper">
<input
type="text"
placeholder={placeholder}
placeholder={searchPlaceholder}
className="ibexa-dropdown__items-filter ibexa-input ibexa-input--small ibexa-input--text form-control"
onChange={updateFilterValue}
value={filterText}
Expand All @@ -109,7 +133,7 @@ const Dropdown = ({ dropdownListRef, value, options, onChange, small, single, ex
tabIndex="-1"
onClick={resetInputValue}
>
<Icon name="discard" />
<Icon name="discard" extraClasses="ibexa-icon--small" />
</button>
<button
type="button"
Expand All @@ -124,6 +148,25 @@ const Dropdown = ({ dropdownListRef, value, options, onChange, small, single, ex
</div>
);
};
const renderPlaceholder = () => {
if (!placeholder) {
return null;
}

return (
<li className="ibexa-dropdown__selected-item ibexa-dropdown__selected-item--predefined ibexa-dropdown__selected-placeholder">
{placeholder}
</li>
);
};
const renderSelectedMultipleItem = (item) => {
return (
<li className="ibexa-dropdown__selected-item">
{item.label}
<span className="ibexa-dropdown__remove-selection" onClick={() => onChange(item.value)} />
</li>
);
};

useEffect(() => {
if (!isExpanded) {
Expand All @@ -150,16 +193,62 @@ const Dropdown = ({ dropdownListRef, value, options, onChange, small, single, ex
};
}, [isExpanded]);

useLayoutEffect(() => {
if (single || !selectionInfoRef.current) {
return;
}

let itemsWidth = 0;
let numberOfOverflowItems = 0;
const selectedItemsNodes = selectionInfoRef.current.querySelectorAll('.ibexa-dropdown__selected-item');
const selectedItemsOverflow = selectionInfoRef.current.querySelector('.ibexa-dropdown__selected-overflow-number');
const dropdownItemsContainerWidth = selectionInfoRef.current.offsetWidth - RESTRICTED_AREA_ITEMS_CONTAINER;

if (selectedItemsOverflow) {
selectedItemsNodes.forEach((item) => {
item.hidden = false;
});
selectedItemsNodes.forEach((item, index) => {
const isOverflowNumber = item.classList.contains('ibexa-dropdown__selected-overflow-number');

itemsWidth += item.offsetWidth;

if (!isOverflowNumber && index !== 0 && itemsWidth > dropdownItemsContainerWidth) {
const isPlaceholder = item.classList.contains('ibexa-dropdown__selected-placeholder');

item.hidden = true;

if (!isPlaceholder) {
numberOfOverflowItems++;
}
}
});

selectedItemsOverflow.hidden = !numberOfOverflowItems;

if (numberOfOverflowItems !== overflowItemsCount) {
setOverflowItemsCount(numberOfOverflowItems);
}
}
}, [value]);

useEffect(() => {
setIsExpanded(false);
if (single) {
setIsExpanded(false);
}
}, [value]);

return (
<>
<div className={dropdownClassName} ref={containerRef} onClick={toggleExpanded}>
<div className="ibexa-dropdown__wrapper">
<ul className="ibexa-dropdown__selection-info">
<li className="ibexa-dropdown__selected-item">{renderSelectedItem(selectedItem)}</li>
<ul className="ibexa-dropdown__selection-info" ref={selectionInfoRef}>
{selectedItems.length === 0 && renderPlaceholder()}
{single && <li className="ibexa-dropdown__selected-item">{renderSelectedItem(selectedItems[0])}</li>}
{!single && selectedItems.map((singleValue) => renderSelectedMultipleItem(singleValue))}
<li className="ibexa-dropdown__selected-item ibexa-dropdown__selected-item--predefined ibexa-dropdown__selected-overflow-number">
{overflowItemsCount}
</li>
</ul>
</div>
</div>
Expand All @@ -175,6 +264,7 @@ Dropdown.propTypes = {
onChange: PropTypes.func.isRequired,
small: PropTypes.bool,
single: PropTypes.bool,
placeholder: PropTypes.string,
extraClasses: PropTypes.string,
renderSelectedItem: PropTypes.func,
minSearchItems: PropTypes.number,
Expand All @@ -183,6 +273,7 @@ Dropdown.propTypes = {
Dropdown.defaultProps = {
small: false,
single: false,
placeholder: null,
extraClasses: '',
renderSelectedItem: (item) => item?.label,
minSearchItems: MIN_SEARCH_ITEMS_DEFAULT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,25 @@ export const useSearchByQueryFetch = () => {
const [, dispatchLoadedLocationsAction] = useContext(LoadedLocationsMapContext);
const [{ isLoading, data }, dispatch] = useReducer(searchByQueryReducer, { isLoading: false, data: {} });
const searchByQuery = useCallback(
(searchText, contentTypesIdentifiers, sectionIdentifier, subtreePathString, limit, offset, languageCode) => {
(
searchText,
contentTypesIdentifiers,
sectionIdentifier,
subtreePathString,
limit,
offset,
languageCode,
imageCriterionData = null,
aggregations = {},
filters = {},
fullTextCriterion = null,
) => {
const handleFetch = (response) => {
setMarkedLocationId(null);
dispatchLoadedLocationsAction({ type: 'CLEAR_LOCATIONS' });
dispatch({ type: SEARCH_END, response });
};
const query = { FullTextCriterion: `${searchText}*` };
const query = { FullTextCriterion: fullTextCriterion ? fullTextCriterion : `${searchText}*` };

if (contentTypesIdentifiers && contentTypesIdentifiers.length) {
query.ContentTypeIdentifierCriterion = contentTypesIdentifiers;
Expand All @@ -46,8 +58,19 @@ export const useSearchByQueryFetch = () => {
query.SubtreeCriterion = subtreePathString;
}

const isImageCriterionDataEmpty = !imageCriterionData || Object.keys(imageCriterionData).length === 0;

if (!isImageCriterionDataEmpty) {
const imageCriterion = {
fieldDefIdentifier: 'image',
...imageCriterionData,
};

query.ImageCriterion = imageCriterion;
}

dispatch({ type: SEARCH_START });
findLocationsBySearchQuery({ ...restInfo, query, limit, offset, languageCode }, handleFetch);
findLocationsBySearchQuery({ ...restInfo, query, aggregations, filters, limit, offset, languageCode }, handleFetch);
},
[restInfo, dispatch],
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export const loadAccordionData = (
};

export const findLocationsBySearchQuery = (
{ token, siteaccess, query, limit = QUERY_LIMIT, offset = 0, languageCode = null },
{ token, siteaccess, query, aggregations, filters, limit = QUERY_LIMIT, offset = 0, languageCode = null },
callback,
) => {
const useAlwaysAvailable = true;
Expand All @@ -151,6 +151,8 @@ export const findLocationsBySearchQuery = (
FacetBuilders: {},
SortClauses: {},
Query: query,
Aggregations: aggregations,
Filters: filters,
limit,
offset,
},
Expand All @@ -167,11 +169,12 @@ export const findLocationsBySearchQuery = (
fetch(request)
.then(handleRequestResponse)
.then((response) => {
const { count, searchHits } = response.View.Result;
const { count, aggregations: searchAggregations, searchHits } = response.View.Result;
const items = searchHits.searchHit.map((searchHit) => searchHit.value.Location);

callback({
items,
aggregations: searchAggregations,
count,
});
})
Expand Down

0 comments on commit 837cc5a

Please sign in to comment.