fix: smooth container collapse behavior

This commit is contained in:
Thomas Camlong committed 2026-08-02 15:57:05 +02:00
1 parent 4db4d947db
commit 56e3bd1cf2
5 files changed
+61 -18

No files matched your search

+1 -1
View File
@@ -182,7 +182,7 @@ Each Container can configure:
- Its border color and custom CSS classes.
Collapsing a Container reduces its full-width header to a half-row expansion bar and compacts the surrounding layout without losing its expanded size.
Expanding it restores those bounds and moves following items out of the way.
Expanding it smoothly restores those bounds and moves following items out of the way without reloading the widgets inside it.
### Background
@@ -30,8 +30,9 @@ interface BoardItemContentProps {
}
const getOverflowFromKind = (kind: SectionItem["kind"]) => {
if (kind === "iframe") return "hidden";
if (kind === "systemResources") return "visible";
return "hidden";
return undefined;
};
export const BoardItemContent = ({ item }: BoardItemContentProps) => {
@@ -66,13 +66,13 @@ export const BoardContainerSection = ({ section }: Props) => {
<Button
className={classes.containerToggle}
pos="absolute"
top={isVisuallyCollapsed ? 0 : -28}
top={isVisuallyCollapsed ? 0 : -24}
left={0}
w="100%"
h={isVisuallyCollapsed ? "100%" : 28}
h={isVisuallyCollapsed ? "100%" : 24}
px={12}
pe={options.showOpenAll ? 40 : 12}
radius={isVisuallyCollapsed ? board.itemRadius : "sm"}
radius={board.itemRadius}
variant="default"
justify={options.showLabel ? "flex-start" : "center"}
leftSection={
@@ -145,8 +145,8 @@ export const BoardContainerSection = ({ section }: Props) => {
{options.showOpenAll && !isEditMode && (
<ActionIcon
pos="absolute"
top={isVisuallyCollapsed ? "50%" : -26}
right={2}
top={isVisuallyCollapsed ? "50%" : -24}
right={0}
style={{ zIndex: 10, transform: isVisuallyCollapsed ? "translateY(-50%)" : undefined }}
variant={options.collapsible ? "subtle" : "default"}
size={24}
@@ -158,15 +158,20 @@ export const BoardContainerSection = ({ section }: Props) => {
<IconExternalLink size={16} />
</ActionIcon>
)}
<Box id={contentId} h="100%" hidden={isVisuallyCollapsed}>
{!isVisuallyCollapsed && (
<SectionGrid
section={section}
columnCount={section.width}
requestedRowCount={section.height}
label={label}
/>
)}
<Box
id={contentId}
className={classes.containerBody}
h="100%"
data-collapsed={isVisuallyCollapsed ? "true" : "false"}
aria-hidden={isVisuallyCollapsed}
inert={isVisuallyCollapsed}
>
<SectionGrid
section={section}
columnCount={section.width}
requestedRowCount={section.height}
label={label}
/>
</Box>
</Card>
{isEditMode && <BoardContainerMenu section={section} />}
@@ -41,3 +41,27 @@
text-overflow: ellipsis;
white-space: nowrap;
}
.containerBody {
visibility: visible;
opacity: 1;
transition:
opacity 140ms ease 80ms,
visibility 0s;
}
.containerBody[data-collapsed="true"] {
visibility: hidden;
pointer-events: none;
opacity: 0;
transition:
opacity 120ms ease,
visibility 0s linear 120ms;
}
@media (prefers-reduced-motion: reduce) {
.containerBody,
.containerBody[data-collapsed="true"] {
transition: none;
}
}
+15 -2
View File
@@ -127,6 +127,8 @@ describe("Board grid", () => {
await expect(logicalTile).toHaveCount(1);
await expectFixedLogicalTileAsync(logicalTile);
await expect(logicalTile).toHaveCSS("overflow", "hidden");
await page.addStyleTag({ content: ".e2e-widget-overflow { overflow: visible; }" });
await expect(secondItem.locator("[data-grid-item-content]")).toHaveCSS("overflow", "visible");
await expect(canvas).toHaveAttribute("data-canvas-overflow", "false");
await expectDocumentNotHorizontallyScrollableAsync(page);
const nestedAppLink = nestedItem.locator('a[href="#board-app-opened"]');
@@ -186,10 +188,15 @@ describe("Board grid", () => {
name: "Collapse: Nested box",
exact: true,
});
await nestedItem.evaluate((element) => {
element.setAttribute("data-e2e-preserved-through-collapse", "true");
});
await collapseContainer.click();
await expect(containerSection).toHaveAttribute("data-grid-h", "0.5");
await expect(belowItem).toHaveAttribute("data-grid-y", "0.5");
await expect(nestedItem).toHaveCount(0);
await expect(nestedItem).toBeHidden();
await expect(nestedItem).toHaveAttribute("data-e2e-preserved-through-collapse", "true");
await expect(containerSection.locator("[data-collapsed='true']")).toHaveAttribute("inert", "");
const collapsedControl = containerSection.getByRole("button", { name: "Expand: Nested box", exact: true });
await expect(collapsedControl).toBeVisible();
await expect(collapsedControl).toHaveAttribute("data-board-container-collapsed-control", "true");
@@ -212,7 +219,8 @@ describe("Board grid", () => {
.click();
await expect(containerSection).toHaveAttribute("data-grid-h", "2");
await expect(belowItem).toHaveAttribute("data-grid-y", "2");
await expect(nestedItem).toHaveCount(1);
await expect(nestedItem).toBeVisible();
await expect(nestedItem).toHaveAttribute("data-e2e-preserved-through-collapse", "true");
await captureBoardScreenshotAsync(canvas, screenshotDirectory, "board-grid-read-only.png");
const viewMainSectionBox = await expectBoundingBoxAsync(mainSection);
@@ -1041,6 +1049,11 @@ const seedBoardGridAsync = async (db: SqliteDatabase, creatorId: string) => {
boardId,
kind: "clock",
options: stringifySuperJSON({ is24HourFormat: true }),
advancedOptions: stringifySuperJSON({
title: null,
customCssClasses: ["e2e-widget-overflow"],
borderColor: "",
}),
},
{
id: belowItemId,