Skip to content

Commit

Permalink
Merge pull request #113 from kenshoo/add-item-grouping
Browse files Browse the repository at this point in the history
Item grouping
  • Loading branch information
liorheber authored Dec 7, 2018
2 parents c5abf3e + c189ea2 commit f461c67
Show file tree
Hide file tree
Showing 22 changed files with 513 additions and 64 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ class Example extends Component {
| `searchValue` | `string` | | The value of the search field.
| `searchValueChanged` | `function` | | Function to handle the change of search field. Accepts value as a single argument.
|`responsiveHeight` | `string` | 400px | Responsive height of the wrapping component, can send percent for example: `70%`
|`withGrouping` | `boolean` | false | Your items will be grouped by the group prop values - see "item grouping" section below


## Customization
Expand Down Expand Up @@ -122,6 +123,8 @@ Each item receives the following props:

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

`group` - group item - no checkbox, not clickable, black colored

<br/>

**Select All**
Expand Down Expand Up @@ -236,6 +239,20 @@ You can use your own messages. Here is the default messages object:
}
```

#### Item grouping

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.
```jsx
<MultiSelect
items={[{id: 1, label: "item1", group: "group1"},
{id: 2, label: "item2", group: "group1"}
{id: 3, label: "item3", group: "group2"}]}
withGrouping
selectedItems={selectedItems}
onChange={this.handleChange}
/>
```

## How to Contribute

#### Setting up development environment
Expand Down
15 changes: 11 additions & 4 deletions src/components/destination_list.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import List from "./list/items_list";
import NoItems from "./items/no_items";
import SelectedItem from "./items/selected_item";
import SelectionStatus from "./selection_status/selection_status";
import { groupItems } from "./item_grouping_util";

const DestinationList = ({
selectionStatusRenderer,
Expand All @@ -17,9 +18,13 @@ const DestinationList = ({
height,
unselectItems,
selectedItemRenderer,
noItemsRenderer
noItemsRenderer,
withGrouping
}) => {
const SelectionStatusRenderer = selectionStatusRenderer;
const updatedSelectedItems = withGrouping
? groupItems(selectedItems)
: selectedItems;
return (
<Column>
<SelectionStatusRenderer
Expand All @@ -30,7 +35,7 @@ const DestinationList = ({
noneSelectedMessage={messages.noneSelectedMessage}
/>
<List
items={selectedItems}
items={updatedSelectedItems}
itemHeight={itemHeight}
height={height - 45}
onClick={(event, id) => unselectItems([id])}
Expand All @@ -52,7 +57,8 @@ DestinationList.propTypes = {
height: PropTypes.number,
unselectItems: PropTypes.func,
selectedItemRenderer: PropTypes.any,
noItemsRenderer: PropTypes.any
noItemsRenderer: PropTypes.any,
withGrouping: PropTypes.bool
};

DestinationList.defaultProps = {
Expand All @@ -63,7 +69,8 @@ DestinationList.defaultProps = {
itemHeight: 40,
height: 400,
selectedItemRenderer: SelectedItem,
noItemsRenderer: NoItems
noItemsRenderer: NoItems,
withGrouping: false
};

export default DestinationList;
23 changes: 23 additions & 0 deletions src/components/item_grouping_util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const generateGroup = name => ({
id: name,
label: name,
isGroup: true,
disabled: true
});

const getGroupItems = (groupName, items) => {
return items.filter(item => item.group === groupName);
};

export const groupItems = items => {
if (!items || items.length === 0) {
return items;
}
const uniqueGroups = Array.from(new Set(items.map(item => item.group)));
return uniqueGroups.reduce((result, groupName) => {
const groupItems = getGroupItems(groupName, items);
result.push(generateGroup(groupName));
result.push(...groupItems);
return result;
}, []);
};
20 changes: 12 additions & 8 deletions src/components/items/item.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const Item = ({
height,
onClick,
withBorder,
group,
checked,
indeterminate,
disabled
Expand All @@ -19,18 +20,21 @@ const Item = ({
className={classnames(styles.item, {
[styles.with_border]: withBorder,
[styles.selected]: checked,
[styles.disabled]: disabled
[styles.disabled]: disabled,
[styles.with_grouping]: group
})}
style={{ height }}
onClick={onClick}
>
<Checkbox
type="checkbox"
color="primary"
checked={checked}
indeterminate={indeterminate}
disabled={disabled}
/>
{!group && (
<Checkbox
type="checkbox"
color="primary"
checked={checked}
indeterminate={indeterminate}
disabled={disabled}
/>
)}
<ItemLabel label={item.label} />
</div>
);
Expand Down
7 changes: 6 additions & 1 deletion src/components/items/item.scss
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,9 @@

.with_border {
border-bottom: 1px solid $border-color;
}
}

.with_grouping {
color: $color-black;
padding-left: 10px;
}
15 changes: 10 additions & 5 deletions src/components/items/selected_item.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ import ItemLabel from "./item_label";

import styles from "./selected_item.scss";

const SelectedItem = ({ item, height }) => (
<div className={styles.selected_item} style={{ height }}>
const SelectedItem = ({ item, height, group }) => (
<div
className={group ? styles.with_grouping : styles.selected_item}
style={{ height }}
>
<ItemLabel label={item.label} />
<IconButton>
<CloseIcon />
</IconButton>
{!group && (
<IconButton>
<CloseIcon />
</IconButton>
)}
</div>
);

Expand Down
8 changes: 7 additions & 1 deletion src/components/items/selected_item.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,10 @@
&:hover {
background-color: $hover-background-color;
}
}
}

.with_grouping {
@extend .selected_item;
pointer-events: none;
color: $color-black;
}
1 change: 1 addition & 0 deletions src/components/list/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class InnerList extends PureComponent {
>
<Renderer
item={item}
group={item.isGroup}
height={itemHeight}
checked={checked}
disabled={disabled && !checked}
Expand Down
11 changes: 8 additions & 3 deletions src/components/multi_select.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ export class MultiSelect extends PureComponent {
itemHeight: PropTypes.number,
selectAllHeight: PropTypes.number,
loaderRenderer: PropTypes.any,
maxSelectedItems: PropTypes.number
maxSelectedItems: PropTypes.number,
withGrouping: PropTypes.bool
};

static defaultProps = {
Expand All @@ -40,7 +41,8 @@ export class MultiSelect extends PureComponent {
showSelectedItems: true,
height: 400,
itemHeight: 40,
loaderRenderer: Loader
loaderRenderer: Loader,
withGrouping: false
};

calculateHeight() {
Expand Down Expand Up @@ -88,7 +90,8 @@ export class MultiSelect extends PureComponent {
messages,
loading,
maxSelectedItems,
searchValue
searchValue,
withGrouping
} = this.props;
const calculatedHeight = this.calculateHeight();
const selectedIds = selectedItems.map(item => item.id);
Expand Down Expand Up @@ -123,6 +126,7 @@ export class MultiSelect extends PureComponent {
noItemsRenderer={noItemsRenderer}
disabled={disabled}
selectAllHeight={selectAllHeight}
withGrouping={withGrouping}
/>
)}
{!loading &&
Expand All @@ -138,6 +142,7 @@ export class MultiSelect extends PureComponent {
unselectItems={unselectItems}
selectedItemRenderer={selectedItemRenderer}
noItemsRenderer={noItemsRenderer}
withGrouping={withGrouping}
/>
)}
</div>
Expand Down
15 changes: 11 additions & 4 deletions src/components/source_list.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import NoItems from "./items/no_items";
import Search from "./search/search";
import SelectAll from "./items/select_all";
import Item from "./items/item";
import { groupItems } from "./item_grouping_util";

const SourceList = ({
searchRenderer,
Expand All @@ -28,10 +29,14 @@ const SourceList = ({
selectItem,
noItemsRenderer,
disabled,
searchValue
searchValue,
withGrouping
}) => {
const SearchRenderer = searchRenderer;
const SelectAllRenderer = selectAllRenderer;
const updatedFilteredItems = withGrouping
? groupItems(filteredItems)
: filteredItems;
return (
<Column>
{showSearch && (
Expand All @@ -55,7 +60,7 @@ const SourceList = ({
<List
ref={getList}
offset={1}
items={filteredItems}
items={updatedFilteredItems}
itemHeight={itemHeight}
height={calculatedHeight}
onClick={selectItem}
Expand Down Expand Up @@ -89,7 +94,8 @@ SourceList.propTypes = {
selectAllItems: PropTypes.func,
getList: PropTypes.func,
selectItem: PropTypes.func,
disabled: PropTypes.bool
disabled: PropTypes.bool,
withGrouping: PropTypes.bool
};

SourceList.defaultProps = {
Expand All @@ -105,7 +111,8 @@ SourceList.defaultProps = {
selectedIds: [],
filteredItems: [],
messages: {},
disabled: false
disabled: false,
withGrouping: false
};

export default SourceList;
Loading

0 comments on commit f461c67

Please sign in to comment.