Skip to content

Commit 4efcaa2

Browse files
feat: Add CSS Grid grid-gap support (#657)
* Added CSS grid-gap check to SortableContainer * Add variable scale grid sorting story * Update types and expose nodes and helper for all events Co-authored-by: Matteo Mazzarolo <[email protected]>
1 parent c8c5147 commit 4efcaa2

File tree

5 files changed

+183
-54
lines changed

5 files changed

+183
-54
lines changed

src/.stories/Storybook.scss

+49-29
Original file line numberDiff line numberDiff line change
@@ -112,31 +112,31 @@ $focusedOutlineColor: #4c9ffe;
112112

113113
// Grid
114114
.grid {
115-
display: block;
116-
width: 130 * 4px;
117-
height: 350px;
115+
display: grid;
116+
height: 130 * 3px + 20px;
117+
grid-gap: 10px;
118+
grid-template-columns: auto auto auto auto;
119+
width: auto;
118120
white-space: nowrap;
119121
border: 0;
120122
background-color: transparent;
121123
}
122124

123125
.gridItem {
124-
float: left;
125126
width: 130px;
126-
padding: 8px;
127-
background: transparent;
128-
border: 0;
127+
height: 130px;
128+
padding: 0;
129+
border: none;
130+
background-color: transparent;
129131

130132
.wrapper {
131133
display: flex;
132134
align-items: center;
133135
justify-content: center;
134-
135136
width: 100%;
136137
height: 100%;
137-
background: #fff;
138-
border: 1px solid #efefef;
139-
138+
background-color: #fff;
139+
transform: scale(1);
140140
font-size: 28px;
141141

142142
span {
@@ -145,6 +145,31 @@ $focusedOutlineColor: #4c9ffe;
145145
}
146146
}
147147

148+
.gridVariableSized {
149+
.gridItem {
150+
&[data-index='0'] {
151+
width: auto !important;
152+
height: auto !important;
153+
grid-column-end: span 2;
154+
grid-row-end: span 2;
155+
}
156+
}
157+
158+
&.isSorting {
159+
.gridItem {
160+
transition: transform 150ms ease;
161+
}
162+
}
163+
}
164+
165+
.gridItemVariableSized {
166+
&[data-index='0'] {
167+
.wrapper {
168+
font-size: 56px;
169+
}
170+
}
171+
}
172+
148173
// Nested
149174
.category {
150175
height: auto;
@@ -183,29 +208,24 @@ $focusedOutlineColor: #4c9ffe;
183208
}
184209

185210
.stylizedHelper {
186-
border: 1px solid #efefef;
187-
box-shadow: 0 5px 5px -5px rgba(0, 0, 0, 0.2);
188-
background-color: rgba(255, 255, 255, 0.9);
189-
border-radius: 3px;
190-
191-
&.horizontalItem {
192-
cursor: col-resize;
193-
}
211+
&:not(.gridItem),
212+
&.gridItem .wrapper {
213+
border: 1px solid #efefef;
214+
box-shadow: 0 5px 5px -5px rgba(0, 0, 0, 0.2);
215+
background-color: rgba(255, 255, 255, 0.9);
216+
border-radius: 3px;
194217

195-
&.gridItem {
196-
background-color: transparent;
197-
white-space: nowrap;
198-
box-shadow: none;
199-
border: none;
218+
&.horizontalItem {
219+
cursor: col-resize;
220+
}
200221

201-
.wrapper {
202-
background-color: rgba(255, 255, 255, 0.9);
203-
box-shadow: 0 0 7px rgba(0, 0, 0, 0.15);
222+
&:focus {
223+
box-shadow: 0 0px 5px 1px $focusedOutlineColor;
204224
}
205225
}
206226

207-
&:focus {
208-
box-shadow: 0 0px 5px 1px $focusedOutlineColor;
227+
&.gridItem .wrapper {
228+
transition: transform 150ms ease-in-out;
209229
}
210230
}
211231

src/.stories/index.js

+94-19
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ function getItems(count, height) {
2222
return range(count).map((value) => {
2323
return {
2424
value,
25-
height: height || heights[random(0, heights.length - 1)],
25+
height: height == null ? heights[random(0, heights.length - 1)] : height,
2626
};
2727
});
2828
}
@@ -47,7 +47,7 @@ const Item = SortableElement(
4747
style: propStyle,
4848
shouldUseDragHandle,
4949
value,
50-
type,
50+
itemIndex,
5151
isSorting,
5252
}) => {
5353
const bodyTabIndex = tabbable && !shouldUseDragHandle ? 0 : -1;
@@ -66,6 +66,7 @@ const Item = SortableElement(
6666
...propStyle,
6767
}}
6868
tabIndex={bodyTabIndex}
69+
data-index={itemIndex}
6970
>
7071
{shouldUseDragHandle && <Handle tabIndex={handleTabIndex} />}
7172
<div className={style.wrapper}>
@@ -99,6 +100,7 @@ const SortableList = SortableContainer(
99100
isDisabled={disabled}
100101
className={itemClass}
101102
index={index}
103+
itemIndex={index}
102104
value={value}
103105
height={height}
104106
shouldUseDragHandle={shouldUseDragHandle}
@@ -178,14 +180,14 @@ class ListWrapper extends Component {
178180
height: 600,
179181
};
180182

181-
onSortStart = () => {
183+
onSortStart = (...args) => {
182184
const {onSortStart} = this.props;
183185
this.setState({isSorting: true});
184186

185187
document.body.style.cursor = 'grabbing';
186188

187189
if (onSortStart) {
188-
onSortStart(this.refs.component);
190+
onSortStart(this.refs.component, ...args);
189191
}
190192
};
191193

@@ -230,7 +232,7 @@ const SortableReactWindow = (Component) =>
230232
return (
231233
<Component
232234
ref="VirtualList"
233-
className={className}
235+
className={classNames(className, style.isSorting)}
234236
itemSize={
235237
itemHeight == null ? (index) => items[index].height : itemHeight
236238
}
@@ -510,20 +512,93 @@ storiesOf('General | Layout / Horizontal list', module).add(
510512
},
511513
);
512514

513-
storiesOf('General | Layout / Grid', module).add('Basic setup', () => {
514-
return (
515-
<div className={style.root}>
516-
<ListWrapper
517-
component={SortableList}
518-
axis={'xy'}
519-
items={getItems(10, 110)}
520-
helperClass={style.stylizedHelper}
521-
className={classNames(style.list, style.stylizedList, style.grid)}
522-
itemClass={classNames(style.stylizedItem, style.gridItem)}
523-
/>
524-
</div>
525-
);
526-
});
515+
storiesOf('General | Layout / Grid', module)
516+
.add('Basic setup', () => {
517+
const transformOrigin = {
518+
x: 0,
519+
y: 0,
520+
};
521+
522+
return (
523+
<div className={style.root}>
524+
<ListWrapper
525+
component={SortableList}
526+
axis={'xy'}
527+
items={getItems(10, false)}
528+
helperClass={style.stylizedHelper}
529+
className={classNames(style.list, style.stylizedList, style.grid)}
530+
itemClass={classNames(style.stylizedItem, style.gridItem)}
531+
/>
532+
</div>
533+
);
534+
})
535+
.add('Large first item', () => {
536+
const transformOrigin = {
537+
x: 0,
538+
y: 0,
539+
};
540+
541+
return (
542+
<div className={style.root}>
543+
<ListWrapper
544+
component={SortableList}
545+
axis={'xy'}
546+
items={getItems(9, false)}
547+
helperClass={style.stylizedHelper}
548+
className={classNames(
549+
style.list,
550+
style.stylizedList,
551+
style.grid,
552+
style.gridVariableSized,
553+
)}
554+
itemClass={classNames(
555+
style.stylizedItem,
556+
style.gridItem,
557+
style.gridItemVariableSized,
558+
)}
559+
onSortStart={(_, {node}, event) => {
560+
const boundingClientRect = node.getBoundingClientRect();
561+
562+
transformOrigin.x =
563+
((event.clientX - boundingClientRect.left) /
564+
boundingClientRect.width) *
565+
100;
566+
transformOrigin.y =
567+
((event.clientY - boundingClientRect.top) /
568+
boundingClientRect.height) *
569+
100;
570+
}}
571+
onSortOver={({nodes, newIndex, index, helper}) => {
572+
const finalNodes = arrayMove(nodes, index, newIndex);
573+
const oldNode = nodes[index].node;
574+
const newNode = nodes[newIndex].node;
575+
const helperScale = newNode.offsetWidth / oldNode.offsetWidth;
576+
const helperWrapperNode = helper.childNodes[0];
577+
578+
helperWrapperNode.style.transform = `scale(${helperScale})`;
579+
helperWrapperNode.style.transformOrigin = `${transformOrigin.x}% ${transformOrigin.y}%`;
580+
581+
finalNodes.forEach(({node}, i) => {
582+
const oldNode = nodes[i].node;
583+
const scale = oldNode.offsetWidth / node.offsetWidth;
584+
const wrapperNode = node.childNodes[0];
585+
586+
wrapperNode.style.transform = `scale(${scale})`;
587+
wrapperNode.style.transformOrigin =
588+
newIndex > i ? '0 0' : '100% 0';
589+
});
590+
}}
591+
onSortEnd={() => {
592+
[...document.querySelectorAll(`.${style.wrapper}`)].forEach(
593+
(node) => {
594+
node.style.transform = '';
595+
},
596+
);
597+
}}
598+
/>
599+
</div>
600+
);
601+
});
527602

528603
storiesOf('General | Configuration / Options', module)
529604
.add('Drag handle', () => {

src/SortableContainer/index.js

+21-5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
closest,
1212
events,
1313
getScrollingParent,
14+
getContainerGridGap,
1415
getEdgeOffset,
1516
getElementMargin,
1617
getLockPixelOffsets,
@@ -260,16 +261,18 @@ export default function sortableContainer(
260261
// Need to get the latest value for `index` in case it changes during `updateBeforeSortStart`
261262
const {index} = node.sortableInfo;
262263
const margin = getElementMargin(node);
264+
const gridGap = getContainerGridGap(this.container);
263265
const containerBoundingRect = this.scrollContainer.getBoundingClientRect();
264-
const dimensions = getHelperDimensions({collection, index, node});
266+
const dimensions = getHelperDimensions({index, node, collection});
265267

266268
this.node = node;
267269
this.margin = margin;
270+
this.gridGap = gridGap;
268271
this.width = dimensions.width;
269272
this.height = dimensions.height;
270273
this.marginOffset = {
271-
x: this.margin.left + this.margin.right,
272-
y: Math.max(this.margin.top, this.margin.bottom),
274+
x: this.margin.left + this.margin.right + this.gridGap.x,
275+
y: Math.max(this.margin.top, this.margin.bottom, this.gridGap.y),
273276
};
274277
this.boundingClientRect = node.getBoundingClientRect();
275278
this.containerBoundingRect = containerBoundingRect;
@@ -425,7 +428,17 @@ export default function sortableContainer(
425428
});
426429

427430
if (onSortStart) {
428-
onSortStart({node, index, collection, isKeySorting}, event);
431+
onSortStart(
432+
{
433+
node,
434+
index,
435+
collection,
436+
isKeySorting,
437+
nodes: this.manager.getOrderedRefs(),
438+
helper: this.helper,
439+
},
440+
event,
441+
);
429442
}
430443

431444
if (isKeySorting) {
@@ -458,7 +471,7 @@ export default function sortableContainer(
458471
active: {collection},
459472
isKeySorting,
460473
} = this.manager;
461-
const nodes = this.manager.refs[collection];
474+
const nodes = this.manager.getOrderedRefs();
462475

463476
// Remove the event listeners if the node is still in the DOM
464477
if (this.listenerNode) {
@@ -533,6 +546,7 @@ export default function sortableContainer(
533546
newIndex: this.newIndex,
534547
oldIndex: this.index,
535548
isKeySorting,
549+
nodes,
536550
},
537551
event,
538552
);
@@ -818,6 +832,8 @@ export default function sortableContainer(
818832
newIndex: this.newIndex,
819833
oldIndex,
820834
isKeySorting,
835+
nodes,
836+
helper: this.helper,
821837
});
822838
}
823839
}

src/utils.js

+13
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,19 @@ export function getScrollingParent(el) {
259259
}
260260
}
261261

262+
export function getContainerGridGap(element) {
263+
const style = window.getComputedStyle(element);
264+
265+
if (style.display === 'grid') {
266+
return {
267+
x: getPixelValue(style.gridColumnGap),
268+
y: getPixelValue(style.gridRowGap),
269+
};
270+
}
271+
272+
return {x: 0, y: 0};
273+
}
274+
262275
export const KEYCODE = {
263276
TAB: 9,
264277
ESC: 27,

0 commit comments

Comments
 (0)