Recipes
Many resources / horizontal scroll
100 resources with minColumnWidth — the grid scrolls horizontally instead of squishing.
When you have more columns than horizontal space, set minColumnWidth and the calculator will sum widths and let CSS handle horizontal overflow. The time column stays sticky.
const START_HOUR = 8
const END_HOUR = 19
const HOUR_HEIGHT_PX = 60
const TIME_COLUMN_WIDTH_PX = 72 // left gutter
const MIN_COLUMN_WIDTH_PX = 140 // each lane is ≥ 140px; below that, scroll
const COLUMN_COUNT = 100
const SNAP_MIN = 15
const cols = Array.from({ length: COLUMN_COUNT }, (_, i) => ({
id: `r${i + 1}`,
label: `Tech ${i + 1}`,
}))
const calc = calculateShadulerData(
cols,
tasks,
START_HOUR,
END_HOUR,
HOUR_HEIGHT_PX,
{
timeColumnWidth: TIME_COLUMN_WIDTH_PX,
minColumnWidth: MIN_COLUMN_WIDTH_PX,
},
)Total grid width here is 100 × 140 + 72 = 14072px — the wrapper scrolls horizontally and the time column stays sticky on the left.
Generating sample data
For a demo at this scale, the tasks are generated with a seeded PRNG so that server-side render and client hydration produce the same list (Math.random() would mismatch and trigger a hydration warning).
function makeRng(seed: number) {
let s = seed >>> 0
return () => {
s = (s + 0x6d2b79f5) >>> 0
let t = s
t = Math.imul(t ^ (t >>> 15), t | 1)
t ^= t + Math.imul(t ^ (t >>> 7), t | 61)
return ((t ^ (t >>> 14)) >>> 0) / 4294967296
}
}
const rng = makeRng(42)
const END_MIN = END_HOUR * 60
// Sequential cursor: each task starts after the previous + a small gap,
// so tasks inside a column never overlap. Stops early if we'd exceed END_HOUR.
const tasks = cols.flatMap((col) => {
const count = 2 + Math.floor(rng() * 4) // up to 2–5 tasks per column
const out: ShadulerTaskData[] = []
let cursorMin = START_HOUR * 60
for (let j = 0; j < count; j++) {
const gapMin = Math.floor(rng() * 7) * SNAP_MIN // 0–90 min gap
const durationMin = (2 + Math.floor(rng() * 5)) * SNAP_MIN // 30–90 min
cursorMin += gapMin
if (cursorMin + durationMin > END_MIN) break
out.push({
id: `${col.id}-t${j}`,
column: col.id,
name: TASK_NAMES[Math.floor(rng() * TASK_NAMES.length)],
startTime: minutesToTime(cursorMin),
endTime: minutesToTime(cursorMin + durationMin),
})
cursorMin += durationMin
}
return out
})Tasks scroll with the grid; column headers and the time column track via CSS sticky.