Skip to content

Commit 15ce0c3

Browse files
author
Claudéric Demers
authored
docs: add example for grouping items (#511)
1 parent 4471a0a commit 15ce0c3

File tree

9 files changed

+292
-9
lines changed

9 files changed

+292
-9
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import React from 'react';
2+
import classNames from 'classnames';
3+
import {sortableElement} from '../../../../src';
4+
5+
import styles from './Item.scss';
6+
7+
function Item(props) {
8+
const {dragging, onClick, selected, selectedItemsCount, value} = props;
9+
const shouldRenderItemCountBadge = dragging && selectedItemsCount > 1;
10+
11+
return (
12+
<div
13+
className={classNames(
14+
styles.Item,
15+
selected && !dragging && styles.selected,
16+
dragging && styles.dragging,
17+
)}
18+
onClick={() => onClick(value)}
19+
>
20+
Item {value}
21+
{shouldRenderItemCountBadge ? <Badge count={selectedItemsCount} /> : null}
22+
</div>
23+
);
24+
}
25+
26+
function Badge(props) {
27+
return <div className={styles.Badge}>{props.count}</div>;
28+
}
29+
30+
export default sortableElement(Item);
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
$color: #333;
2+
$white: #fff;
3+
$backgroundColor: $white;
4+
5+
$boxShadow: 0 5px 5px -5px rgba(0, 0, 0, 0.2);
6+
7+
$fontWeight-regular: 400;
8+
$fontWeight-bold: 600;
9+
10+
$borderRadius: 3px;
11+
$borderWidth: 1px;
12+
$borderColor: #efefef;
13+
14+
$selectedColor: $white;
15+
$selectedBackgroundColor: #759fff;
16+
$selectedBorderColor: #5e83d6;
17+
18+
$badgeColor: $white;
19+
$badgeBackgroundColor: #f75959;
20+
$badgeBorderColor: #da4553;
21+
22+
.Item {
23+
display: flex;
24+
align-items: center;
25+
width: 100%;
26+
padding: 20px;
27+
background-color: $backgroundColor;
28+
border-top: $borderWidth solid #efefef;
29+
box-sizing: border-box;
30+
user-select: none;
31+
32+
color: $color;
33+
font-weight: $fontWeight-regular;
34+
35+
&:first-child {
36+
border-top: none;
37+
}
38+
39+
&.selected {
40+
background: $selectedBackgroundColor;
41+
border: 1px solid $selectedBorderColor;
42+
color: $selectedColor;
43+
font-weight: $fontWeight-bold;
44+
45+
& + .Item {
46+
border-top: none;
47+
}
48+
}
49+
50+
&.dragging {
51+
border-radius: $borderRadius;
52+
border: $borderWidth solid #efefef;
53+
box-shadow: $boxShadow;
54+
}
55+
}
56+
57+
.Badge {
58+
position: absolute;
59+
top: -8px;
60+
right: -8px;
61+
padding: 0.35em 0.5em;
62+
border-radius: 0.3rem;
63+
64+
color: $badgeColor;
65+
font-size: 0.8em;
66+
font-weight: $fontWeight-bold;
67+
background-color: $badgeBackgroundColor;
68+
border: $borderWidth solid $badgeBorderColor;
69+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import Item from './Item';
2+
3+
export default Item;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React from 'react';
2+
import {sortableContainer} from '../../../../src';
3+
4+
import Item from '../Item';
5+
6+
import styles from './List.scss';
7+
8+
function List({items, selectedItems, sortingItemKey, onItemSelect}) {
9+
return (
10+
<div className={styles.List}>
11+
{items.map((value, index) => {
12+
const isSelected = selectedItems.includes(value);
13+
const itemIsBeingDragged = sortingItemKey === value;
14+
15+
return (
16+
<Item
17+
key={`item-${value}`}
18+
selected={isSelected}
19+
dragging={itemIsBeingDragged}
20+
index={index}
21+
value={value}
22+
onClick={onItemSelect}
23+
selectedItemsCount={selectedItems.length}
24+
/>
25+
);
26+
})}
27+
</div>
28+
);
29+
}
30+
31+
export default sortableContainer(List);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
$backgroundColor: #f3f3f3;
2+
$borderColor: #efefef;
3+
$borderWidth: 1px;
4+
5+
.List {
6+
position: relative;
7+
width: 400px;
8+
height: 600px;
9+
overflow: auto;
10+
-webkit-overflow-scrolling: touch;
11+
z-index: 0;
12+
background-color: $backgroundColor;
13+
border: $borderWidth solid $borderColor;
14+
border-radius: 3px;
15+
outline: none;
16+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import List from './List';
2+
3+
export default List;

src/.stories/grouping-items/index.js

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import React from 'react';
2+
import arrayMove from 'array-move';
3+
import {generateItems} from './utils';
4+
5+
import SortableList from './List';
6+
7+
class GroupedItems extends React.Component {
8+
state = {
9+
selectedItems: [],
10+
items: generateItems(50),
11+
};
12+
13+
render() {
14+
const {items, selectedItems, sortingItemKey} = this.state;
15+
16+
return (
17+
<SortableList
18+
items={items.filter(this.filterItems)}
19+
sortingItemKey={sortingItemKey}
20+
selectedItems={selectedItems}
21+
onItemSelect={this.handleItemSelect}
22+
shouldCancelStart={this.handleShouldCancelStart}
23+
updateBeforeSortStart={this.handleUpdateBeforeSortStart}
24+
onSortEnd={this.handleSortEnd}
25+
distance={3}
26+
/>
27+
);
28+
}
29+
30+
filterItems = (value) => {
31+
const {selectedItems, sortingItemKey, isSorting} = this.state;
32+
33+
// Do not hide the ghost of the element currently being sorted
34+
if (sortingItemKey === value) {
35+
return true;
36+
}
37+
38+
// Hide the other items that are selected
39+
if (isSorting && selectedItems.includes(value)) {
40+
return false;
41+
}
42+
43+
// Do not hide any other items
44+
return true;
45+
};
46+
47+
handleUpdateBeforeSortStart = ({index}) => {
48+
return new Promise((resolve) =>
49+
this.setState(
50+
({items}) => ({
51+
sortingItemKey: items[index],
52+
isSorting: true,
53+
}),
54+
resolve,
55+
),
56+
);
57+
};
58+
59+
handleSortEnd = ({oldIndex, newIndex}) => {
60+
const {selectedItems} = this.state;
61+
let newItems;
62+
63+
if (selectedItems.length) {
64+
const items = this.state.items.filter(
65+
(value) => !selectedItems.includes(value),
66+
);
67+
68+
newItems = [
69+
...items.slice(0, newIndex),
70+
...selectedItems,
71+
...items.slice(newIndex, items.length),
72+
];
73+
} else {
74+
newItems = arrayMove(this.state.items, oldIndex, newIndex);
75+
}
76+
77+
this.setState({
78+
items: newItems,
79+
isSorting: false,
80+
sortingItemKey: null,
81+
selectedItems: [],
82+
});
83+
};
84+
85+
handleItemSelect = (item) => {
86+
this.setState(({selectedItems}) => {
87+
if (selectedItems.includes(item)) {
88+
return {
89+
selectedItems: selectedItems.filter((value) => value !== item),
90+
};
91+
}
92+
93+
return {
94+
selectedItems: [...selectedItems, item],
95+
};
96+
});
97+
};
98+
99+
handleShouldCancelStart = (event) => {
100+
const {items, selectedItems} = this.state;
101+
const item = items[event.target.sortableInfo.index];
102+
103+
// Never cancel start if there are no selected items
104+
if (!selectedItems.length) {
105+
return false;
106+
}
107+
108+
// If there are selected items, we want to cancel sorting
109+
// from starting when dragging elements that are not selected
110+
return !selectedItems.includes(item);
111+
};
112+
}
113+
114+
export default GroupedItems;

src/.stories/grouping-items/utils.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function generateItems(length) {
2+
return Array.from(Array(length), (_, index) => index.toString());
3+
}

src/.stories/index.js

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import range from 'lodash/range';
1414
import random from 'lodash/random';
1515
import classNames from 'classnames';
1616

17+
import GroupedItems from './grouping-items';
18+
1719
function getItems(count, height) {
1820
var heights = [65, 110, 140, 65, 90, 65];
1921
return range(count).map((value) => {
@@ -585,7 +587,10 @@ storiesOf('General | Configuration / Customization', module)
585587
);
586588
});
587589

588-
storiesOf('Other | Virtualization libraries / react-tiny-virtual-list', module)
590+
storiesOf(
591+
'Advanced examples | Virtualization libraries / react-tiny-virtual-list',
592+
module,
593+
)
589594
.add('Basic setup', () => {
590595
return (
591596
<div className={style.root}>
@@ -611,7 +616,7 @@ storiesOf('Other | Virtualization libraries / react-tiny-virtual-list', module)
611616
);
612617
});
613618

614-
storiesOf('Other | Virtualization libraries / react-window', module)
619+
storiesOf('Advanced examples | Virtualization libraries / react-window', module)
615620
.add('Basic setup', () => {
616621
return (
617622
<div className={style.root}>
@@ -650,7 +655,10 @@ storiesOf('Other | Virtualization libraries / react-window', module)
650655
);
651656
});
652657

653-
storiesOf('Other | Virtualization libraries / react-virtualized', module)
658+
storiesOf(
659+
'Advanced examples | Virtualization libraries / react-virtualized',
660+
module,
661+
)
654662
.add('Basic setup', () => {
655663
return (
656664
<div className={style.root}>
@@ -696,7 +704,10 @@ storiesOf('Other | Virtualization libraries / react-virtualized', module)
696704
);
697705
});
698706

699-
storiesOf('Other | Virtualization libraries / react-infinite', module)
707+
storiesOf(
708+
'Advanced examples | Virtualization libraries / react-infinite',
709+
module,
710+
)
700711
.add('Basic setup', () => {
701712
return (
702713
<div className={style.root}>
@@ -720,9 +731,13 @@ storiesOf('Other | Virtualization libraries / react-infinite', module)
720731
);
721732
});
722733

723-
storiesOf('Stress Test | Re-rendering before sorting', module).add(
724-
'Elements that shrink',
725-
() => {
734+
storiesOf('Advanced examples | Re-rendering before sorting', module)
735+
.add('Grouping items', () => (
736+
<div className={style.root}>
737+
<GroupedItems />
738+
</div>
739+
))
740+
.add('Elements that shrink', () => {
726741
const getHelperDimensions = ({node}) => ({
727742
height: 20,
728743
width: node.offsetWidth,
@@ -737,5 +752,4 @@ storiesOf('Stress Test | Re-rendering before sorting', module).add(
737752
/>
738753
</div>
739754
);
740-
},
741-
);
755+
});

0 commit comments

Comments
 (0)