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:
1 parent
d52195ab75
commit
2ff1549112
1 file changed
+17
-22
@@ -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 { AnimatePresence, motion } from 'framer-motion';
|
||||||
import {
|
import {
|
||||||
Check,
|
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
|
// 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)
|
// 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)))))`;
|
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
|
// Wide Kanban: a column grows to at most this width, the board stays centred
|
||||||
const KANBAN_COL_MAX = 380;
|
const KANBAN_COL_MAX = 380;
|
||||||
const KANBAN_GAP = 12; // gap-3
|
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
|
// 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;
|
// 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).
|
// 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 first row grows while the bar above the board scrolls away, so a row always fills the screen (with
|
||||||
// the bar above the board scrolls away, so a row always fills the screen; the wheel moves row by row
|
// one row too: the first scroll step hides the bar); the wheel moves row by row (below).
|
||||||
// (below).
|
|
||||||
const kanbanBoardRef = useRef<HTMLDivElement>(null);
|
const kanbanBoardRef = useRef<HTMLDivElement>(null);
|
||||||
const [kanbanColHeight, setKanbanColHeight] = useState<number | null>(null);
|
const [kanbanColHeight, setKanbanColHeight] = useState<number | null>(null);
|
||||||
const [kanbanMultiRow, setKanbanMultiRow] = useState(false);
|
const [kanbanMultiRow, setKanbanMultiRow] = useState(false);
|
||||||
@@ -610,15 +611,15 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
const areaHeight = area ? area.clientHeight : window.innerHeight;
|
const areaHeight = area ? area.clientHeight : window.innerHeight;
|
||||||
const top = el.getBoundingClientRect().top - areaTop;
|
const top = el.getBoundingClientRect().top - areaTop;
|
||||||
setKanbanColHeight(Math.max(KANBAN_MIN_COL_HEIGHT, areaHeight - top - 16));
|
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
|
// A row fills the whole area (--kb-full); the first one grows into the room the bar above the board
|
||||||
// above the board frees while scrolling (--kb-top - --kb-scroll, see the scroll effect)
|
// frees while scrolling (--kb-top - --kb-scroll, see the scroll effect). With one row as well, so the
|
||||||
el.style.setProperty('--kb-full', `${Math.max(KANBAN_MIN_COL_HEIGHT, areaHeight - 24)}px`);
|
// first scroll step hides the bar there too. Set on the board's wrapper: the spacer below needs them.
|
||||||
el.style.setProperty('--kb-top', `${Math.max(0, top - 12)}px`);
|
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(() => {
|
requestAnimationFrame(() => {
|
||||||
const colTops = Array.from(el.querySelectorAll<HTMLElement>('[data-kanban-col]')).map((c) => c.offsetTop);
|
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);
|
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]);
|
}, [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),
|
// 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
|
// 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' });
|
area.scrollTo({ top: next, behavior: 'smooth' });
|
||||||
};
|
};
|
||||||
// The first row grows while the bar above the board scrolls away
|
// 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();
|
onScroll();
|
||||||
area.addEventListener('wheel', onWheel, { passive: false });
|
area.addEventListener('wheel', onWheel, { passive: false });
|
||||||
area.addEventListener('scroll', onScroll, { passive: true });
|
area.addEventListener('scroll', onScroll, { passive: true });
|
||||||
@@ -3163,6 +3155,9 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</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>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
/* STANDARD LIST VIEW (CLEAN & UNCLUTTERED) */
|
/* STANDARD LIST VIEW (CLEAN & UNCLUTTERED) */
|
||||||
|
|||||||
Reference in new issue
Block a user