feat(tasks): with one Kanban row too, the first scroll step hides the bar above

On the desktop the board always works like with several rows: the first
row grows while the bar above scrolls away, and a spacer below as high as
what is still visible of the bar lets the page scroll even with one row.
Replaces the one-row overflow correction.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-11-02 22:11:10 +01:00
1 parent d52195ab75
commit 2ff1549112
1 file changed
+17 -22
+17 -22
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useLayoutEffect, useState, useRef, useMemo, useCallback } from 'react';
import React, { useEffect, useState, useRef, useMemo, useCallback } from 'react';
import { AnimatePresence, motion } from 'framer-motion';
import {
Check,
@@ -82,6 +82,8 @@ const KANBAN_MIN_PHONE_COL_HEIGHT = 280;
// First row of a Kanban board with several rows: the full height minus what is still visible of the bar
// above the board (--kb-top at the start, shrinking with the scroll position --kb-scroll)
const KANBAN_FIRST_ROW_HEIGHT = `max(${KANBAN_MIN_COL_HEIGHT}px, calc(var(--kb-full) - max(0px, calc(var(--kb-top) - var(--kb-scroll, 0px)))))`;
// Below the board: what the first row has not grown into yet, so the page keeps its height while scrolling
const KANBAN_BAR_SPACER = 'max(0px, calc(var(--kb-top) - var(--kb-scroll, 0px)))';
// Wide Kanban: a column grows to at most this width, the board stays centred
const KANBAN_COL_MAX = 380;
const KANBAN_GAP = 12; // gap-3
@@ -563,9 +565,8 @@ export const TasksView: React.FC<TasksViewProps> = ({
// Desktop Kanban: every column, in every row, is as high as the room from the board's top to the bottom of
// the scroll area, so the first row fits completely on opening and each further row fills one screen;
// more cards scroll inside the column. At least room for about three cards (the page scrolls below that).
// One row: the page itself does not scroll (like the workspace). Several rows: the first row grows while
// the bar above the board scrolls away, so a row always fills the screen; the wheel moves row by row
// (below).
// The first row grows while the bar above the board scrolls away, so a row always fills the screen (with
// one row too: the first scroll step hides the bar); the wheel moves row by row (below).
const kanbanBoardRef = useRef<HTMLDivElement>(null);
const [kanbanColHeight, setKanbanColHeight] = useState<number | null>(null);
const [kanbanMultiRow, setKanbanMultiRow] = useState(false);
@@ -610,15 +611,15 @@ export const TasksView: React.FC<TasksViewProps> = ({
const areaHeight = area ? area.clientHeight : window.innerHeight;
const top = el.getBoundingClientRect().top - areaTop;
setKanbanColHeight(Math.max(KANBAN_MIN_COL_HEIGHT, areaHeight - top - 16));
// Several rows: a row fills the whole area (--kb-full); the first one grows into the room the bar
// above the board frees while scrolling (--kb-top - --kb-scroll, see the scroll effect)
el.style.setProperty('--kb-full', `${Math.max(KANBAN_MIN_COL_HEIGHT, areaHeight - 24)}px`);
el.style.setProperty('--kb-top', `${Math.max(0, top - 12)}px`);
// A row fills the whole area (--kb-full); the first one grows into the room the bar above the board
// frees while scrolling (--kb-top - --kb-scroll, see the scroll effect). With one row as well, so the
// first scroll step hides the bar there too. Set on the board's wrapper: the spacer below needs them.
const vars = el.parentElement ?? el;
vars.style.setProperty('--kb-full', `${Math.max(KANBAN_MIN_COL_HEIGHT, areaHeight - 24)}px`);
vars.style.setProperty('--kb-top', `${Math.max(0, top - 12)}px`);
setKanbanMultiRow(true);
requestAnimationFrame(() => {
const colTops = Array.from(el.querySelectorAll<HTMLElement>('[data-kanban-col]')).map((c) => c.offsetTop);
const tops = new Set(colTops);
const multi = tops.size > 1;
setKanbanMultiRow(multi);
setKanbanPerRow(colTops.filter((t) => t === Math.min(...colTops)).length);
});
};
@@ -636,16 +637,6 @@ export const TasksView: React.FC<TasksViewProps> = ({
};
}, [isKanban, isKanbanWide, boardColumns.length, selectedListId, loading]);
// One row on the desktop: take back what still overflows (paddings), so the page does not scroll. Measured
// only once the new height is on the page; measured right away, the old layout (e.g. two tall rows before
// stretching the board to full width) counted as overflow and squeezed the columns to their minimum.
useLayoutEffect(() => {
if (!kanbanColHeight || kanbanMultiRow || kanbanPhoneSnap) return;
const area = kanbanBoardRef.current?.closest<HTMLElement>('[data-app-scroll]');
if (!area) return;
const over = area.scrollHeight - area.clientHeight;
if (over > 0) setKanbanColHeight((h) => (h ? Math.max(KANBAN_MIN_COL_HEIGHT, h - over) : h));
}, [kanbanColHeight, kanbanMultiRow, kanbanPhoneSnap]);
// Several rows: one wheel step moves to the next row (or back up to the previous row / the page top),
// smoothly. The browser's own snapping goes back to the nearest point, so a wheel step shorter than a row
@@ -694,7 +685,8 @@ export const TasksView: React.FC<TasksViewProps> = ({
area.scrollTo({ top: next, behavior: 'smooth' });
};
// The first row grows while the bar above the board scrolls away
const onScroll = () => board.style.setProperty('--kb-scroll', `${area.scrollTop}px`);
const vars = board.parentElement ?? board;
const onScroll = () => vars.style.setProperty('--kb-scroll', `${area.scrollTop}px`);
onScroll();
area.addEventListener('wheel', onWheel, { passive: false });
area.addEventListener('scroll', onScroll, { passive: true });
@@ -3163,6 +3155,9 @@ export const TasksView: React.FC<TasksViewProps> = ({
);
})}
</div>
{/* Desktop: room as high as what is still visible of the bar above the board, so the page can
scroll the bar away even with one row while the first row grows (the page keeps its height) */}
{kanbanMultiRow && <div aria-hidden style={{ height: KANBAN_BAR_SPACER }} />}
</div>
) : (
/* STANDARD LIST VIEW (CLEAN & UNCLUTTERED) */