Skip to content

Commit f461c67

Browse files
authored
Merge pull request #113 from kenshoo/add-item-grouping
Item grouping
2 parents c5abf3e + c189ea2 commit f461c67

22 files changed

+513
-64
lines changed

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ class Example extends Component {
9494
| `searchValue` | `string` | | The value of the search field.
9595
| `searchValueChanged` | `function` | | Function to handle the change of search field. Accepts value as a single argument.
9696
|`responsiveHeight` | `string` | 400px | Responsive height of the wrapping component, can send percent for example: `70%`
97+
|`withGrouping` | `boolean` | false | Your items will be grouped by the group prop values - see "item grouping" section below
9798

9899

99100
## Customization
@@ -122,6 +123,8 @@ Each item receives the following props:
122123

123124
`disabled` - defines if item should be disabled. Item won't be clickable for selection and will be ignored when clicking "Select All".
124125

126+
`group` - group item - no checkbox, not clickable, black colored
127+
125128
<br/>
126129

127130
**Select All**
@@ -236,6 +239,20 @@ You can use your own messages. Here is the default messages object:
236239
}
237240
```
238241

242+
#### Item grouping
243+
244+
You can add also grouping to your items - add a group prop with the group name as a value to each of your items and add withGrouping prop as well.
245+
```jsx
246+
<MultiSelect
247+
items={[{id: 1, label: "item1", group: "group1"},
248+
{id: 2, label: "item2", group: "group1"}
249+
{id: 3, label: "item3", group: "group2"}]}
250+
withGrouping
251+
selectedItems={selectedItems}
252+
onChange={this.handleChange}
253+
/>
254+
```
255+
239256
## How to Contribute
240257

241258
#### Setting up development environment

src/components/destination_list.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import List from "./list/items_list";
66
import NoItems from "./items/no_items";
77
import SelectedItem from "./items/selected_item";
88
import SelectionStatus from "./selection_status/selection_status";
9+
import { groupItems } from "./item_grouping_util";
910

1011
const DestinationList = ({
1112
selectionStatusRenderer,
@@ -17,9 +18,13 @@ const DestinationList = ({
1718
height,
1819
unselectItems,
1920
selectedItemRenderer,
20-
noItemsRenderer
21+
noItemsRenderer,
22+
withGrouping
2123
}) => {
2224
const SelectionStatusRenderer = selectionStatusRenderer;
25+
const updatedSelectedItems = withGrouping
26+
? groupItems(selectedItems)
27+
: selectedItems;
2328
return (
2429
<Column>
2530
<SelectionStatusRenderer
@@ -30,7 +35,7 @@ const DestinationList = ({
3035
noneSelectedMessage={messages.noneSelectedMessage}
3136
/>
3237
<List
33-
items={selectedItems}
38+
items={updatedSelectedItems}
3439
itemHeight={itemHeight}
3540
height={height - 45}
3641
onClick={(event, id) => unselectItems([id])}
@@ -52,7 +57,8 @@ DestinationList.propTypes = {
5257
height: PropTypes.number,
5358
unselectItems: PropTypes.func,
5459
selectedItemRenderer: PropTypes.any,
55-
noItemsRenderer: PropTypes.any
60+
noItemsRenderer: PropTypes.any,
61+
withGrouping: PropTypes.bool
5662
};
5763

5864
DestinationList.defaultProps = {
@@ -63,7 +69,8 @@ DestinationList.defaultProps = {
6369
itemHeight: 40,
6470
height: 400,
6571
selectedItemRenderer: SelectedItem,
66-
noItemsRenderer: NoItems
72+
noItemsRenderer: NoItems,
73+
withGrouping: false
6774
};
6875

6976
export default DestinationList;

src/components/item_grouping_util.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
const generateGroup = name => ({
2+
id: name,
3+
label: name,
4+
isGroup: true,
5+
disabled: true
6+
});
7+
8+
const getGroupItems = (groupName, items) => {
9+
return items.filter(item => item.group === groupName);
10+
};
11+
12+
export const groupItems = items => {
13+
if (!items || items.length === 0) {
14+
return items;
15+
}
16+
const uniqueGroups = Array.from(new Set(items.map(item => item.group)));
17+
return uniqueGroups.reduce((result, groupName) => {
18+
const groupItems = getGroupItems(groupName, items);
19+
result.push(generateGroup(groupName));
20+
result.push(...groupItems);
21+
return result;
22+
}, []);
23+
};

src/components/items/item.js

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const Item = ({
1111
height,
1212
onClick,
1313
withBorder,
14+
group,
1415
checked,
1516
indeterminate,
1617
disabled
@@ -19,18 +20,21 @@ const Item = ({
1920
className={classnames(styles.item, {
2021
[styles.with_border]: withBorder,
2122
[styles.selected]: checked,
22-
[styles.disabled]: disabled
23+
[styles.disabled]: disabled,
24+
[styles.with_grouping]: group
2325
})}
2426
style={{ height }}
2527
onClick={onClick}
2628
>
27-
<Checkbox
28-
type="checkbox"
29-
color="primary"
30-
checked={checked}
31-
indeterminate={indeterminate}
32-
disabled={disabled}
33-
/>
29+
{!group && (
30+
<Checkbox
31+
type="checkbox"
32+
color="primary"
33+
checked={checked}
34+
indeterminate={indeterminate}
35+
disabled={disabled}
36+
/>
37+
)}
3438
<ItemLabel label={item.label} />
3539
</div>
3640
);

src/components/items/item.scss

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,9 @@
2525

2626
.with_border {
2727
border-bottom: 1px solid $border-color;
28-
}
28+
}
29+
30+
.with_grouping {
31+
color: $color-black;
32+
padding-left: 10px;
33+
}

src/components/items/selected_item.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,17 @@ import ItemLabel from "./item_label";
66

77
import styles from "./selected_item.scss";
88

9-
const SelectedItem = ({ item, height }) => (
10-
<div className={styles.selected_item} style={{ height }}>
9+
const SelectedItem = ({ item, height, group }) => (
10+
<div
11+
className={group ? styles.with_grouping : styles.selected_item}
12+
style={{ height }}
13+
>
1114
<ItemLabel label={item.label} />
12-
<IconButton>
13-
<CloseIcon />
14-
</IconButton>
15+
{!group && (
16+
<IconButton>
17+
<CloseIcon />
18+
</IconButton>
19+
)}
1520
</div>
1621
);
1722

src/components/items/selected_item.scss

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,10 @@
1111
&:hover {
1212
background-color: $hover-background-color;
1313
}
14-
}
14+
}
15+
16+
.with_grouping {
17+
@extend .selected_item;
18+
pointer-events: none;
19+
color: $color-black;
20+
}

src/components/list/list.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ class InnerList extends PureComponent {
6969
>
7070
<Renderer
7171
item={item}
72+
group={item.isGroup}
7273
height={itemHeight}
7374
checked={checked}
7475
disabled={disabled && !checked}

src/components/multi_select.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ export class MultiSelect extends PureComponent {
2828
itemHeight: PropTypes.number,
2929
selectAllHeight: PropTypes.number,
3030
loaderRenderer: PropTypes.any,
31-
maxSelectedItems: PropTypes.number
31+
maxSelectedItems: PropTypes.number,
32+
withGrouping: PropTypes.bool
3233
};
3334

3435
static defaultProps = {
@@ -40,7 +41,8 @@ export class MultiSelect extends PureComponent {
4041
showSelectedItems: true,
4142
height: 400,
4243
itemHeight: 40,
43-
loaderRenderer: Loader
44+
loaderRenderer: Loader,
45+
withGrouping: false
4446
};
4547

4648
calculateHeight() {
@@ -88,7 +90,8 @@ export class MultiSelect extends PureComponent {
8890
messages,
8991
loading,
9092
maxSelectedItems,
91-
searchValue
93+
searchValue,
94+
withGrouping
9295
} = this.props;
9396
const calculatedHeight = this.calculateHeight();
9497
const selectedIds = selectedItems.map(item => item.id);
@@ -123,6 +126,7 @@ export class MultiSelect extends PureComponent {
123126
noItemsRenderer={noItemsRenderer}
124127
disabled={disabled}
125128
selectAllHeight={selectAllHeight}
129+
withGrouping={withGrouping}
126130
/>
127131
)}
128132
{!loading &&
@@ -138,6 +142,7 @@ export class MultiSelect extends PureComponent {
138142
unselectItems={unselectItems}
139143
selectedItemRenderer={selectedItemRenderer}
140144
noItemsRenderer={noItemsRenderer}
145+
withGrouping={withGrouping}
141146
/>
142147
)}
143148
</div>

src/components/source_list.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import NoItems from "./items/no_items";
77
import Search from "./search/search";
88
import SelectAll from "./items/select_all";
99
import Item from "./items/item";
10+
import { groupItems } from "./item_grouping_util";
1011

1112
const SourceList = ({
1213
searchRenderer,
@@ -28,10 +29,14 @@ const SourceList = ({
2829
selectItem,
2930
noItemsRenderer,
3031
disabled,
31-
searchValue
32+
searchValue,
33+
withGrouping
3234
}) => {
3335
const SearchRenderer = searchRenderer;
3436
const SelectAllRenderer = selectAllRenderer;
37+
const updatedFilteredItems = withGrouping
38+
? groupItems(filteredItems)
39+
: filteredItems;
3540
return (
3641
<Column>
3742
{showSearch && (
@@ -55,7 +60,7 @@ const SourceList = ({
5560
<List
5661
ref={getList}
5762
offset={1}
58-
items={filteredItems}
63+
items={updatedFilteredItems}
5964
itemHeight={itemHeight}
6065
height={calculatedHeight}
6166
onClick={selectItem}
@@ -89,7 +94,8 @@ SourceList.propTypes = {
8994
selectAllItems: PropTypes.func,
9095
getList: PropTypes.func,
9196
selectItem: PropTypes.func,
92-
disabled: PropTypes.bool
97+
disabled: PropTypes.bool,
98+
withGrouping: PropTypes.bool
9399
};
94100

95101
SourceList.defaultProps = {
@@ -105,7 +111,8 @@ SourceList.defaultProps = {
105111
selectedIds: [],
106112
filteredItems: [],
107113
messages: {},
108-
disabled: false
114+
disabled: false,
115+
withGrouping: false
109116
};
110117

111118
export default SourceList;

0 commit comments

Comments
 (0)