fix(board): fix container void/overflow gap across scrollable and collapse states (#6750)
A container's outer grid cell and its own inner content grid are sized by two independent calculations that only agree when the container is scrollable and expanded. When collapsed, or when non-scrollable content needs more rows than declared, the inner grid keeps rendering at its old/ content-driven size while the outer cell shrinks or stays fixed, leaving an invisible-but-space-occupying box that inflates the board's scroll height into a visible empty gap below the container. Pass the outer instance's collapsed row count down as a viewportRowCountOverride so the inner grid matches it exactly when collapsed, and grow the outer placement's height to the already-computed content minimum (getContainerMinimumSizes) for non-scrollable containers so it stays in sync with the inner grid's own growth.
This commit is contained in:
1 parent
5e23659d99
commit
feea3512d0
2 files changed
+17
-10
No files matched your search
@@ -8,6 +8,7 @@ import { useEditMode } from "@homarr/boards/edit-mode";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
|
||||
import type { ContainerSectionItem } from "~/app/[locale]/boards/_types";
|
||||
import { COLLAPSED_SECTION_ROW_COUNT } from "~/components/board/layout";
|
||||
import { SectionGrid } from "./grid/section-grid";
|
||||
import { useSectionCollapse } from "./section-collapse";
|
||||
import { useOpenSectionApps } from "./use-open-section-apps";
|
||||
@@ -149,7 +150,13 @@ export const BoardContainerSection = ({ section }: Props) => {
|
||||
aria-hidden={isVisuallyCollapsed}
|
||||
inert={isVisuallyCollapsed}
|
||||
>
|
||||
<SectionGrid section={section} columnCount={section.width} requestedRowCount={section.height} label={label} />
|
||||
<SectionGrid
|
||||
section={section}
|
||||
columnCount={section.width}
|
||||
requestedRowCount={section.height}
|
||||
viewportRowCountOverride={isVisuallyCollapsed ? COLLAPSED_SECTION_ROW_COUNT : undefined}
|
||||
label={label}
|
||||
/>
|
||||
</Box>
|
||||
</Card>
|
||||
{isEditMode && <BoardContainerMenu section={section} />}
|
||||
|
||||
@@ -33,6 +33,7 @@ interface SectionGridProps {
|
||||
section: Exclude<Section, { kind: "container" }> | ContainerSectionItem;
|
||||
columnCount: number;
|
||||
requestedRowCount?: number;
|
||||
viewportRowCountOverride?: number;
|
||||
label: string;
|
||||
railPlacement?: "main" | "left" | "right";
|
||||
className?: string;
|
||||
@@ -42,6 +43,7 @@ export const SectionGrid = ({
|
||||
section,
|
||||
columnCount,
|
||||
requestedRowCount = 0,
|
||||
viewportRowCountOverride,
|
||||
label,
|
||||
railPlacement = "main",
|
||||
className,
|
||||
@@ -67,6 +69,8 @@ export const SectionGrid = ({
|
||||
() =>
|
||||
[...items, ...innerSections].map((item): SectionGridPlacement => {
|
||||
const minimum = item.type === "section" ? minimumBySectionId.get(item.id) : undefined;
|
||||
const isNonScrollableContainer = item.type === "section" && !item.options.scrollable;
|
||||
const height = isNonScrollableContainer && minimum ? Math.max(item.height, minimum.height) : item.height;
|
||||
return normalizeGridPlacement(
|
||||
{
|
||||
id: item.id,
|
||||
@@ -74,7 +78,7 @@ export const SectionGrid = ({
|
||||
x: item.xOffset,
|
||||
y: item.yOffset,
|
||||
w: item.width,
|
||||
h: item.height,
|
||||
h: height,
|
||||
minW: minimum?.width,
|
||||
minH: minimum?.height,
|
||||
},
|
||||
@@ -137,7 +141,8 @@ export const SectionGrid = ({
|
||||
// A scrollable container isn't forced to grow with its content - it scrolls internally instead
|
||||
// of expanding to fit every widget, so its viewport height is capped independently of rowCount.
|
||||
const isScrollableContainer = section.kind === "container" && section.options.scrollable;
|
||||
const viewportRowCount = isScrollableContainer ? Math.max(requestedRowCount, 1) : rowCount;
|
||||
const viewportRowCount =
|
||||
viewportRowCountOverride ?? (isScrollableContainer ? Math.max(requestedRowCount, 1) : rowCount);
|
||||
// A container's own visible card is inset from its allocated board cell by the board's
|
||||
// standard per-item gap (see the base, non-container `.staticItem[data-type="item"] >
|
||||
// .contentMount` rule in section-grid.module.css - a container is placed as an "item" like
|
||||
@@ -159,14 +164,9 @@ export const SectionGrid = ({
|
||||
// all, the header bar covers the first row of the container's content instead of sitting
|
||||
// above it. Only the vertical axis is affected - the toggle spans the full width already.
|
||||
const collapsibleHeaderInset =
|
||||
section.kind === "container" && section.options.collapsible
|
||||
? CONTAINER_HEADER_HEIGHT / effectiveCanvasScale
|
||||
: 0;
|
||||
section.kind === "container" && section.options.collapsible ? CONTAINER_HEADER_HEIGHT / effectiveCanvasScale : 0;
|
||||
const logicalWidth = getLogicalGridSize(columnCount) - outerCardInset;
|
||||
const viewportHeight = Math.max(
|
||||
1,
|
||||
getLogicalGridSize(viewportRowCount) - outerCardInset - collapsibleHeaderInset,
|
||||
);
|
||||
const viewportHeight = Math.max(1, getLogicalGridSize(viewportRowCount) - outerCardInset - collapsibleHeaderInset);
|
||||
// Items are positioned in a coordinate space sized to the *un-inset* column/row count
|
||||
// (fullGridWidth/Height below - see getLogicalItemStyle), but logicalWidth/Height above are
|
||||
// deliberately smaller by outerCardInset to match the container's actual visible card size.
|
||||
|
||||
Reference in new issue
Block a user