Skip to content

Instantly share code, notes, and snippets.

@mattfysh
Created September 10, 2021 11:45
Show Gist options
  • Save mattfysh/c6c844380c20e92f5959a27f7900b0de to your computer and use it in GitHub Desktop.
Save mattfysh/c6c844380c20e92f5959a27f7900b0de to your computer and use it in GitHub Desktop.
CSS Responsive Grid with drag-and-drop items
<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