Skip to content

Commit 5ce1072

Browse files
committed
IBX-9449: Product Picker base object selection with variants (#1433)
1 parent 61cd3f1 commit 5ce1072

File tree

10 files changed

+141
-47
lines changed

10 files changed

+141
-47
lines changed

src/bundle/Resources/public/scss/ui/modules/universal-discovery/_search.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
.c-search {
22
height: 100%;
3+
width: 100%;
34

45
&__top-bar {
56
display: flex;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
11
.c-selected-items-panel-item {
22
@include c-selected-items-panel-item;
3+
4+
flex-direction: column;
5+
padding: 0;
6+
overflow: hidden;
7+
8+
&__main-content,
9+
&__extra-content {
10+
width: 100%;
11+
}
12+
13+
&__main-content {
14+
display: flex;
15+
align-items: center;
16+
padding: calculateRem(8px);
17+
margin-bottom: calculateRem(8px);
18+
}
319
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
.c-selected-items-panel {
22
@include c-selected-items-panel;
3+
4+
width: calculateRem(400px);
35
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
import { parse as parseMiddleEllipsis } from '@ibexa-admin-ui/src/bundle/Resources/public/js/scripts/helpers/middle.ellipsis';
5+
6+
const MiddleEllipsis = ({ name }) => {
7+
return (
8+
<span className="ibexa-middle-ellipsis" title={name} ref={(node) => parseMiddleEllipsis(node)}>
9+
<span className="ibexa-middle-ellipsis__name ibexa-middle-ellipsis__name--start">
10+
<span className="ibexa-middle-ellipsis__name-ellipsized">{name}</span>
11+
</span>
12+
<span className="ibexa-middle-ellipsis__separator">...</span>
13+
<span className="ibexa-middle-ellipsis__name ibexa-middle-ellipsis__name--end">
14+
<span className="ibexa-middle-ellipsis__name-ellipsized">{name}</span>
15+
</span>
16+
</span>
17+
);
18+
};
19+
20+
MiddleEllipsis.propTypes = {
21+
name: PropTypes.string.isRequired,
22+
};
23+
24+
export default MiddleEllipsis;

src/bundle/ui-dev/src/modules/common/popup/popup.component.js

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const Popup = ({
3232
actionBtnsConfig,
3333
size,
3434
noHeader,
35+
noFooter,
3536
noCloseBtn,
3637
extraClasses,
3738
showTooltip,
@@ -122,20 +123,24 @@ const Popup = ({
122123
</div>
123124
)}
124125
<div className="modal-body c-popup__body">{children}</div>
125-
<div className="modal-footer c-popup__footer">
126-
{actionBtnsConfig.map(({ className, onClick, disabled = false, preventClose = false, label, ...extraProps }) => (
127-
<button
128-
key={label}
129-
type="button"
130-
className={`btn ibexa-btn ${className}`}
131-
onClick={onClick ? (event) => handleOnClick(event, onClick, preventClose) : hidePopup}
132-
disabled={disabled}
133-
{...extraProps}
134-
>
135-
{label}
136-
</button>
137-
))}
138-
</div>
126+
{!noFooter && (
127+
<div className="modal-footer c-popup__footer">
128+
{actionBtnsConfig.map(
129+
({ className, onClick, disabled = false, preventClose = false, label, ...extraProps }) => (
130+
<button
131+
key={label}
132+
type="button"
133+
className={`btn ibexa-btn ${className}`}
134+
onClick={onClick ? (event) => handleOnClick(event, onClick, preventClose) : hidePopup}
135+
disabled={disabled}
136+
{...extraProps}
137+
>
138+
{label}
139+
</button>
140+
),
141+
)}
142+
</div>
143+
)}
139144
</div>
140145
</div>
141146
</div>
@@ -160,6 +165,7 @@ Popup.propTypes = {
160165
hasFocus: PropTypes.bool,
161166
size: PropTypes.string,
162167
noHeader: PropTypes.bool,
168+
noFooter: PropTypes.bool,
163169
noCloseBtn: PropTypes.bool,
164170
noKeyboard: PropTypes.bool,
165171
extraClasses: PropTypes.string,
@@ -172,6 +178,7 @@ Popup.defaultProps = {
172178
onClose: null,
173179
size: 'large',
174180
noHeader: false,
181+
noFooter: false,
175182
noCloseBtn: false,
176183
extraClasses: '',
177184
title: null,

src/bundle/ui-dev/src/modules/universal-discovery/components/selected-items/selected.items.panel.item.js

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { SelectedItemsContext } from '../../universal.discovery.module';
1313

1414
import { REMOVE_SELECTED_ITEMS } from '../../hooks/useSelectedItemsReducer';
1515

16-
const SelectedItemsPanelItem = ({ item, thumbnailData, name, description }) => {
16+
const SelectedItemsPanelItem = ({ item, thumbnailData, name, description, extraContent }) => {
1717
const adminUiConfig = getAdminUiConfig();
1818
const Translator = getTranslator();
1919
const refSelectedLocationsItem = useRef(null);
@@ -25,7 +25,7 @@ const SelectedItemsPanelItem = ({ item, thumbnailData, name, description }) => {
2525
);
2626
const removeFromSelection = () => {
2727
hideAllTooltips(refSelectedLocationsItem.current);
28-
dispatchSelectedItemsAction({ type: REMOVE_SELECTED_ITEMS, ids: [{ id: item.id, type: item.type }] });
28+
dispatchSelectedItemsAction({ type: REMOVE_SELECTED_ITEMS, itemsIdsWithTypes: [{ id: item.id, type: item.type }] });
2929
};
3030
const sortedActions = useMemo(() => {
3131
const { universalSelectItemActions } = adminUiConfig.universalDiscoveryWidget;
@@ -44,29 +44,32 @@ const SelectedItemsPanelItem = ({ item, thumbnailData, name, description }) => {
4444
parseTooltip(node);
4545
}}
4646
>
47-
<div className="c-selected-items-panel-item__image-wrapper">
48-
<Thumbnail thumbnailData={thumbnailData} iconExtraClasses="ibexa-icon--small" />
49-
</div>
50-
<div className="c-selected-items-panel-item__info">
51-
<span className="c-selected-items-panel-item__info-name">{name}</span>
52-
<span className="c-selected-items-panel-item__info-description">{description}</span>
53-
</div>
54-
<div className="c-selected-items-panel-item__actions-wrapper">
55-
{sortedActions.map((action) => {
56-
const Component = action.component;
47+
<div className="c-selected-items-panel-item__main-content">
48+
<div className="c-selected-items-panel-item__image-wrapper">
49+
<Thumbnail thumbnailData={thumbnailData} iconExtraClasses="ibexa-icon--small" />
50+
</div>
51+
<div className="c-selected-items-panel-item__info">
52+
<span className="c-selected-items-panel-item__info-name">{name}</span>
53+
<span className="c-selected-items-panel-item__info-description">{description}</span>
54+
</div>
55+
<div className="c-selected-items-panel-item__actions-wrapper">
56+
{sortedActions.map((action) => {
57+
const Component = action.component;
5758

58-
return <Component key={action.id} item={item} />;
59-
})}
60-
<button
61-
type="button"
62-
className="c-selected-items-panel-item__remove-button btn ibexa-btn ibexa-btn--ghost ibexa-btn--no-text"
63-
onClick={removeFromSelection}
64-
title={removeItemLabel}
65-
data-tooltip-container-selector=".c-udw-tab"
66-
>
67-
<Icon name="discard" extraClasses="ibexa-icon--tiny-small" />
68-
</button>
59+
return <Component key={action.id} item={item} />;
60+
})}
61+
<button
62+
type="button"
63+
className="c-selected-items-panel-item__remove-button btn ibexa-btn ibexa-btn--ghost ibexa-btn--no-text"
64+
onClick={removeFromSelection}
65+
title={removeItemLabel}
66+
data-tooltip-container-selector=".c-udw-tab"
67+
>
68+
<Icon name="discard" extraClasses="ibexa-icon--tiny-small" />
69+
</button>
70+
</div>
6971
</div>
72+
<div className="c-selected-items-panel-item__extra-content">{extraContent}</div>
7073
</div>
7174
);
7275
};
@@ -78,7 +81,12 @@ SelectedItemsPanelItem.propTypes = {
7881
resource: PropTypes.string.isRequired,
7982
}).isRequired,
8083
name: PropTypes.string.isRequired,
81-
description: PropTypes.string.isRequired,
84+
description: PropTypes.node.isRequired,
85+
extraContent: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]),
86+
};
87+
88+
SelectedItemsPanelItem.defaultProps = {
89+
extraContent: null,
8290
};
8391

8492
export default SelectedItemsPanelItem;

src/bundle/ui-dev/src/modules/universal-discovery/components/toggle-selection/toggle.item.selection.js

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import React, { useContext } from 'react';
1+
import React, { useContext, useEffect, useRef } from 'react';
22
import PropTypes from 'prop-types';
33

44
import { createCssClassNames } from '../../../common/helpers/css.class.names';
55
import { MultipleConfigContext, SelectedItemsContext } from '../../universal.discovery.module';
66

7-
const ToggleItemSelection = ({ item, isDisabled, isHidden }) => {
7+
const ToggleItemSelection = ({ item, isDisabled, isPreselected, isHidden, isIndeterminate }) => {
88
const { selectedItems } = useContext(SelectedItemsContext);
99
const [multiple, multipleItemsLimit] = useContext(MultipleConfigContext);
10+
const inputRef = useRef(null);
1011
const isSelected = selectedItems.some((selectedItem) => selectedItem.type === item.type && selectedItem.id === item.id);
1112
const isSelectionBlocked = multipleItemsLimit !== 0 && selectedItems.length >= multipleItemsLimit && !isSelected;
1213
const className = createCssClassNames({
@@ -17,11 +18,20 @@ const ToggleItemSelection = ({ item, isDisabled, isHidden }) => {
1718
});
1819
const inputType = multiple ? 'checkbox' : 'radio';
1920

21+
useEffect(() => {
22+
if (!inputRef.current) {
23+
return;
24+
}
25+
26+
inputRef.current.indeterminate = isIndeterminate;
27+
}, [isIndeterminate]);
28+
2029
return (
2130
<input
31+
ref={inputRef}
2232
type={inputType}
2333
className={className}
24-
checked={isSelected}
34+
checked={isPreselected || isSelected}
2535
disabled={isSelectionBlocked || isDisabled || isHidden}
2636
readOnly={true}
2737
/>
@@ -32,11 +42,15 @@ ToggleItemSelection.propTypes = {
3242
item: PropTypes.object.isRequired,
3343
isHidden: PropTypes.bool,
3444
isDisabled: PropTypes.bool,
45+
isPreselected: PropTypes.bool,
46+
isIndeterminate: PropTypes.bool,
3547
};
3648

3749
ToggleItemSelection.defaultProps = {
3850
isHidden: false,
3951
isDisabled: false,
52+
isPreselected: false,
53+
isIndeterminate: false,
4054
};
4155

4256
export default ToggleItemSelection;

src/bundle/ui-dev/src/modules/universal-discovery/hooks/usePaginableFetch.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import { useEffect, useContext, useReducer } from 'react';
2-
3-
import { RestInfoContext } from '../universal.discovery.module';
1+
import { useEffect, useReducer } from 'react';
42

53
const fetchInitialState = {
64
isLoading: false,
@@ -40,8 +38,7 @@ const fetchReducer = (state, action) => {
4038
}
4139
};
4240

43-
export const usePaginableFetch = ({ itemsPerPage, extraFetchParams }, fetchFunction) => {
44-
const restInfo = useContext(RestInfoContext);
41+
export const usePaginableFetch = ({ restInfo, itemsPerPage, extraFetchParams }, fetchFunction) => {
4542
const [state, dispatch] = useReducer(fetchReducer, fetchInitialState);
4643
const changePage = (pageIndex) => dispatch({ type: CHANGE_PAGE, pageIndex });
4744

src/bundle/ui-dev/src/modules/universal-discovery/hooks/useSelectedItemsReducer.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useReducer } from 'react';
22

33
export const ADD_SELECTED_ITEMS = 'ADD_SELECTED_ITEMS';
4+
export const UPDATE_SELECTED_ITEMS = 'UPDATE_SELECTED_ITEMS';
45
export const REMOVE_SELECTED_ITEMS = 'REMOVE_SELECTED_ITEMS';
56
export const TOGGLE_SELECTED_ITEMS = 'TOGGLE_SELECTED_ITEMS';
67
export const CLEAR_SELECTED_ITEMS = 'CLEAR_SELECTED_ITEMS';
@@ -31,8 +32,29 @@ const selectedItemsReducer = (state, action) => {
3132
items: newItems,
3233
};
3334
}
35+
case UPDATE_SELECTED_ITEMS: {
36+
const updatedSelectedItems = [...items];
37+
38+
for (const updatedItem of action.items) {
39+
const updatedItemIndex = updatedSelectedItems.findIndex(
40+
(selectedItem) => selectedItem.type === updatedItem.type && selectedItem.id === updatedItem.id,
41+
);
42+
43+
if (updatedItemIndex > -1) {
44+
updatedSelectedItems[updatedItemIndex] = updatedItem;
45+
}
46+
}
47+
48+
return {
49+
...state,
50+
items: updatedSelectedItems,
51+
};
52+
}
3453
case REMOVE_SELECTED_ITEMS:
35-
return filterOutSelectedItems(action.itemsIdsWithTypes, items);
54+
return {
55+
...state,
56+
items: filterOutSelectedItems(action.itemsIdsWithTypes, items),
57+
};
3658
case TOGGLE_SELECTED_ITEMS: {
3759
const oldItemsWithoutDeselectedItems = filterOutSelectedItems(action.items, items);
3860
const newItemsWithoutDeselectedItems = filterOutSelectedItems(items, action.items);

src/bundle/ui-dev/src/modules/universal-discovery/universal.discovery.module.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ const UniversalDiscoveryModule = (props) => {
260260
return {
261261
isInitLocationsDeselectionBlocked: props.isInitLocationsDeselectionBlocked,
262262
initSelectedLocationsIds: props.selectedLocations,
263+
initSelectedItems: props.initSelectedItems,
263264
deselectAlertTitle: deselectAlertTitle,
264265
};
265266
}, []);
@@ -713,6 +714,7 @@ UniversalDiscoveryModule.propTypes = {
713714
}),
714715
).isRequired,
715716
selectedLocations: PropTypes.array,
717+
initSelectedItems: PropTypes.array,
716718
isInitLocationsDeselectionBlocked: PropTypes.bool,
717719
deselectAlertTitle: PropTypes.string,
718720
allowRedirects: PropTypes.bool.isRequired,
@@ -739,6 +741,7 @@ UniversalDiscoveryModule.defaultProps = {
739741
activeSortOrder: 'ascending',
740742
activeView: 'finder',
741743
selectedLocations: [],
744+
initSelectedItems: [],
742745
isInitLocationsDeselectionBlocked: false,
743746
deselectAlertTitle: null,
744747
restInfo: defaultRestInfo,

0 commit comments

Comments
 (0)