fix(tasks): preserve board header stack while scrolling

This commit is contained in:
lizerk 2026-08-06 17:37:12 +03:00
parent 0f8ae2095d
commit ba5dc52d10
2 changed files with 73 additions and 18 deletions

View File

@ -995,3 +995,55 @@ html[data-bs-theme="dark"] .shadow-xl {
.tasks-column-scroll::-webkit-scrollbar-thumb:hover {
background: var(--accent-primary);
}
@media (min-width: 80rem) {
.tasks-board-column-group {
--tasks-board-stack-gap: 0.5rem;
--tasks-board-column-header-height: 4rem;
--tasks-board-subcolumn-header-height: 5.125rem;
}
.tasks-board-column-header {
position: sticky;
top: 0;
z-index: 30;
min-height: var(--tasks-board-column-header-height, 4rem);
background: var(--surface-elevated);
box-shadow: 0 var(--tasks-board-stack-gap, 0.5rem) 0 var(--background);
}
.tasks-board-subcolumn-header-row {
position: sticky;
top: calc(
var(--tasks-board-column-header-height, 4rem)
+ var(--tasks-board-stack-gap, 0.5rem)
);
z-index: 20;
min-height: var(--tasks-board-subcolumn-header-height, 5.125rem);
background: var(--surface-secondary);
box-shadow: 0 var(--tasks-board-stack-gap, 0.5rem) 0 var(--background);
}
.tasks-board-lane-header {
position: sticky;
top: calc(
var(--tasks-board-column-header-height, 4rem)
+ var(--tasks-board-subcolumn-header-height, 5.125rem)
+ var(--tasks-board-stack-gap, 0.5rem)
+ var(--tasks-board-stack-gap, 0.5rem)
);
z-index: 15;
background: var(--surface-secondary);
box-shadow: 0 var(--tasks-board-stack-gap, 0.5rem) 0 var(--surface-secondary);
}
.tasks-board-content-section-continuous {
margin-bottom: -3.9375rem;
}
.tasks-board-content-section-continuous::after {
display: block;
height: 3.9375rem;
content: "";
}
}

View File

@ -1010,7 +1010,7 @@ function BoardColumn({
}`}
>
<div
className={`flex items-center justify-between gap-3 rounded-xl border bg-[var(--surface-primary)] px-3 py-3 shadow-sm transition xl:sticky xl:top-0 xl:z-10 ${columnBorderClass} ${
className={`tasks-board-column-header app-surface flex min-h-16 items-center justify-between gap-3 rounded-xl px-3 py-3 transition ${columnBorderClass} ${
isDragging ? "shadow-xl" : ""
}`}
>
@ -1406,9 +1406,9 @@ function BoardColumnGroup({
transition,
zIndex: isDragging ? 20 : undefined,
}}
className={`${hideOnMobile ? "hidden xl:flex" : "flex"} min-w-0 flex-col ${isDragging ? "opacity-70" : ""}`}
className={`${hideOnMobile ? "hidden xl:flex" : "flex"} tasks-board-column-group min-w-0 flex-col ${isDragging ? "opacity-70" : ""}`}
>
<div className="app-surface xl:sticky xl:top-0 xl:z-30 flex items-center justify-between gap-3 rounded-xl border border-[var(--border-subtle)] px-3 py-3 shadow-sm">
<div className="tasks-board-column-header app-surface flex min-h-16 items-center justify-between gap-3 rounded-xl px-3 py-3">
<div
ref={setActivatorNodeRef}
className="min-w-0 flex-1 cursor-grab touch-none active:cursor-grabbing"
@ -1534,6 +1534,7 @@ function BoardColumnRowView({
onDelete,
expanded,
onToggle,
hasFollowingSection,
}: {
row: TaskColumnRow;
children: ReactNode;
@ -1543,10 +1544,13 @@ function BoardColumnRowView({
onDelete: () => void;
expanded: boolean;
onToggle: () => void;
hasFollowingSection: boolean;
}) {
return (
<section className="min-w-0 rounded-xl border border-[var(--border-subtle)] bg-[var(--surface-muted)] p-2">
<div className={`flex items-center justify-between gap-3 px-1 ${expanded ? "mb-2" : ""}`}>
<section className={`tasks-board-content-section min-w-0 rounded-xl border border-[var(--border-subtle)] bg-[var(--surface-secondary)] p-2 ${
hasFollowingSection ? "tasks-board-content-section-continuous" : ""
}`}>
<div className={`tasks-board-lane-header flex items-center justify-between gap-3 px-1 ${expanded ? "mb-2" : ""}`}>
<button
type="button"
onClick={onToggle}
@ -1616,7 +1620,7 @@ function BoardBaseColumnsView({
gridTemplateColumns,
expanded,
onToggle,
attachedToHeaders = false,
hasFollowingSection,
}: {
collapsible: boolean;
tasksCount: number;
@ -1625,14 +1629,12 @@ function BoardBaseColumnsView({
gridTemplateColumns: string;
expanded: boolean;
onToggle: () => void;
attachedToHeaders?: boolean;
hasFollowingSection: boolean;
}) {
return (
<section
className={`min-w-0 border border-[var(--border-subtle)] bg-[var(--surface-muted)] p-2 ${
attachedToHeaders ? "-mt-px rounded-b-xl border-t-0 pt-2" : "rounded-xl"
}`}
>
<section className={`tasks-board-content-section min-w-0 rounded-xl border border-[var(--border-subtle)] bg-[var(--surface-secondary)] p-2 ${
hasFollowingSection ? "tasks-board-content-section-continuous" : ""
}`}>
{headers ? (
<div
className="flex min-w-0 flex-col gap-2 xl:grid xl:w-max"
@ -1645,7 +1647,7 @@ function BoardBaseColumnsView({
<button
type="button"
onClick={onToggle}
className={`flex w-full items-center gap-2 rounded-lg px-1 py-1 text-left ${headers ? "mt-2" : ""} ${expanded ? "mb-2" : ""}`}
className={`tasks-board-lane-header flex w-full items-center gap-2 rounded-lg px-1 py-1 text-left ${headers ? "mt-2" : ""} ${expanded ? "mb-2" : ""}`}
aria-expanded={expanded}
>
<ChevronRight
@ -7048,7 +7050,7 @@ function TasksPageContent() {
gridTemplateColumns={gridTemplateColumns}
expanded={!boardViewState.collapsed_base_column_ids.includes(column.id)}
onToggle={() => toggleBoardViewState("base", column.id)}
attachedToHeaders
hasFollowingSection={columnRows.length > 0}
>
{previewCellColumns.map((item) => (
renderTaskCell(item, null)
@ -7066,16 +7068,16 @@ function TasksPageContent() {
) : renderSubcolumnHeader(column, false);
return (
<div className="flex min-w-0 flex-col xl:w-max">
<div className="flex min-w-0 flex-col gap-2 xl:w-max">
<div
className="z-20 flex min-w-0 flex-col gap-2 rounded-t-xl border border-b-0 border-[var(--border-subtle)] bg-[var(--surface-muted)] p-2 pb-0 xl:sticky xl:top-[4.75rem] xl:grid xl:w-max"
className="tasks-board-subcolumn-header-row flex min-w-0 flex-col gap-2 rounded-xl border border-[var(--border-subtle)] bg-[var(--surface-secondary)] p-2 xl:grid xl:w-max"
style={{ gridTemplateColumns }}
>
{subcolumnHeaders}
</div>
{baseColumns}
<div className="mt-2 flex min-w-0 flex-col gap-2">
{columnRows.map((row) => {
<div className="flex min-w-0 flex-col gap-2">
{columnRows.map((row, rowIndex) => {
const rowTasksCount = filteredTasks.filter((task) => task.row === row.id).length;
return (
<BoardColumnRowView
@ -7089,6 +7091,7 @@ function TasksPageContent() {
onDelete={() => void deleteColumnRow(row)}
expanded={!boardViewState.collapsed_row_ids.includes(row.id)}
onToggle={() => toggleBoardViewState("row", row.id)}
hasFollowingSection={rowIndex < columnRows.length - 1}
>
{previewCellColumns.map((item) => renderTaskCell(item, row.id))}
</BoardColumnRowView>