New FlexMasonry component implements a masonry layout using flexbox
This commit is contained in:
parent
d9083fdde6
commit
f27895c4c8
|
@ -0,0 +1,66 @@
|
|||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { run } from '@ember/runloop';
|
||||
import { action } from '@ember/object';
|
||||
import { minIndex, max } from 'd3-array';
|
||||
|
||||
export default class FlexMasonry extends Component {
|
||||
@tracked element = null;
|
||||
|
||||
@action
|
||||
captureElement(element) {
|
||||
this.element = element;
|
||||
}
|
||||
|
||||
@action
|
||||
reflow() {
|
||||
run.next(() => {
|
||||
// There's nothing to do if this is single column layout
|
||||
if (!this.element || this.args.columns === 1 || !this.args.columns) return;
|
||||
|
||||
const columns = new Array(this.args.columns).fill(null).map(() => ({
|
||||
height: 0,
|
||||
elements: [],
|
||||
}));
|
||||
|
||||
const items = this.element.querySelectorAll('.flex-masonry-item');
|
||||
|
||||
// First pass: assign each element to a column based on the running heights of each column
|
||||
for (let item of items) {
|
||||
const styles = window.getComputedStyle(item);
|
||||
const marginTop = parseFloat(styles.marginTop);
|
||||
const marginBottom = parseFloat(styles.marginBottom);
|
||||
const height = item.clientHeight;
|
||||
|
||||
// Pick the shortest column accounting for margins
|
||||
const column = columns[minIndex(columns, c => c.height)];
|
||||
|
||||
// Add the new element's height to the column height
|
||||
column.height += marginTop + height + marginBottom;
|
||||
column.elements.push(item);
|
||||
}
|
||||
|
||||
// Second pass: assign an order to each element based on their column and position in the column
|
||||
columns
|
||||
.mapBy('elements')
|
||||
.flat()
|
||||
.forEach((dc, index) => {
|
||||
dc.style.order = index;
|
||||
});
|
||||
|
||||
// Gaurantee column wrapping as predicted (if the first item of a column is shorter than the difference
|
||||
// beteen the height of the column and the previous column, then flexbox will naturally place the first
|
||||
// item at the end of the previous column).
|
||||
columns.forEach((column, index) => {
|
||||
const nextHeight = index < columns.length - 1 ? columns[index + 1].height : 0;
|
||||
const item = column.elements.lastObject;
|
||||
if (item) {
|
||||
item.style.flexBasis = item.clientHeight + Math.max(0, nextHeight - column.height) + 'px';
|
||||
}
|
||||
});
|
||||
|
||||
// Set the max height of the container to the height of the tallest column
|
||||
this.element.style.maxHeight = max(columns.mapBy('height')) + 1 + 'px';
|
||||
});
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@
|
|||
@import './components/event';
|
||||
@import './components/exec-button';
|
||||
@import './components/exec-window';
|
||||
@import './components/flex-masonry';
|
||||
@import './components/fs-explorer';
|
||||
@import './components/global-search-container';
|
||||
@import './components/global-search-dropdown';
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
.flex-masonry {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
align-content: space-between;
|
||||
margin-top: -0.75em;
|
||||
|
||||
&.flex-masonry-columns-1 > .flex-masonry-item {
|
||||
width: 100%;
|
||||
}
|
||||
&.flex-masonry-columns-2 > .flex-masonry-item {
|
||||
width: 50%;
|
||||
}
|
||||
&.flex-masonry-columns-3 > .flex-masonry-item {
|
||||
width: 33%;
|
||||
}
|
||||
&.flex-masonry-columns-4 > .flex-masonry-item {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
&.with-spacing {
|
||||
> .flex-masonry-item {
|
||||
margin-top: 0.75em;
|
||||
margin-bottom: 0.75em;
|
||||
}
|
||||
|
||||
&.flex-masonry-columns-2 > .flex-masonry-item {
|
||||
width: calc(50% - 0.75em);
|
||||
}
|
||||
&.flex-masonry-columns-3 > .flex-masonry-item {
|
||||
width: calc(33% - 0.75em);
|
||||
}
|
||||
&.flex-masonry-columns-4 > .flex-masonry-item {
|
||||
width: calc(25% - 0.75em);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<div
|
||||
class="flex-masonry {{if @withSpacing "with-spacing"}} flex-masonry-columns-{{@columns}}"
|
||||
{{did-insert this.captureElement}}
|
||||
{{did-insert this.reflow}}
|
||||
{{did-update this.reflow}}>
|
||||
{{#each @items as |item|}}
|
||||
<div class="flex-masonry-item">
|
||||
{{yield item (action this.reflow)}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
Loading…
Reference in New Issue