Created
September 10, 2021 11:45
-
-
Save mattfysh/c6c844380c20e92f5959a27f7900b0de to your computer and use it in GitHub Desktop.
CSS Responsive Grid with drag-and-drop items
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script> | |
import { flip } from 'svelte/animate' | |
import { dndzone, TRIGGERS } from 'svelte-dnd-action' | |
import { zip } from 'lodash' | |
export let ids | |
export let getItem | |
// distribute items over column count | |
let colCount = 4 | |
const createColContainers = () => new Array(4).fill(0).map(() => []) | |
let cols | |
$: { | |
const container = createColContainers() | |
ids.forEach((id, index) => { | |
const item = getItem(id) | |
const colIndex = index % colCount | |
container[colIndex].push({ | |
id, | |
item, | |
}) | |
}) | |
cols = container | |
} | |
// on resize, calculate number of columns that have not wrapped | |
const getGridCols = el => | |
getComputedStyle(el) | |
.gridTemplateColumns.split(' ') | |
.filter(w => w !== '0px').length | |
const updateColCount = node => { | |
const update = () => { | |
const grid1 = node.firstChild | |
const grid2 = grid1.firstChild | |
colCount = [grid1, grid2].map(getGridCols).reduce((a, b) => a * b) | |
} | |
update() | |
window.addEventListener('resize', update) | |
return { | |
destroy() { | |
window.removeEventListener('resize', update) | |
}, | |
} | |
} | |
// drag and drop to rerder | |
const flipDurationMs = 300 | |
const onDndConsider = index => e => (cols[index] = e.detail.items) | |
const onDndFinalize = index => e => { | |
cols[index] = e.detail.items | |
// when moving between columns, only run the following once | |
if (e.detail.info.trigger === TRIGGERS.DROPPED_INTO_ZONE) { | |
// de-distribute to get list of ordered ids | |
const rows = zip(...cols) | |
ids = [] | |
.concat(...rows) | |
.filter(Boolean) | |
.map(cell => cell.id) | |
} | |
} | |
</script> | |
<div class="container" use:updateColCount> | |
<div class="grid1"> | |
{#each [0, 1] as g1} | |
<div class="grid2"> | |
{#each [0, 1] as g2} | |
<div | |
class="col" | |
use:dndzone={{ | |
items: cols[g1 * 2 + g2], | |
flipDurationMs, | |
}} | |
on:consider={onDndConsider(g1 * 2 + g2)} | |
on:finalize={onDndFinalize(g1 * 2 + g2)}> | |
{#each cols[g1 * 2 + g2] as cell (cell.id)} | |
<div class="cell" animate:flip={{ duration: flipDurationMs }}> | |
<slot item={cell.item} /> | |
</div> | |
{/each} | |
</div> | |
{/each} | |
</div> | |
{/each} | |
</div> | |
</div> | |
<style> | |
.container { | |
max-width: 1690px; | |
margin: 0 auto; | |
} | |
.grid1 { | |
display: grid; | |
grid-template-columns: repeat(auto-fit, minmax(min(430px, 100%), 1fr)); | |
gap: 30px; | |
} | |
.grid2 { | |
display: grid; | |
grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr)); | |
gap: 30px; | |
} | |
.col { | |
max-width: 100%; | |
overflow: hidden; | |
} | |
.cell { | |
margin-bottom: 15px; | |
overflow: hidden; | |
} | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment