Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IBX-6907: Image picker: Translations filter #963

Merged
merged 8 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading