fix: refine container interactions

This commit is contained in:
Thomas Camlong authored and ajnart committed 2026-08-14 11:07:06 +02:00
1 parent 34f2a0bd24
commit dfe2ddf34b
11 files changed
+340 -144

No files matched your search

@@ -38,7 +38,7 @@ export const BoardContainerSection = ({ section }: Props) => {
});
const label = options.title.trim() || t("untitled");
const contentId = `board-container-${section.id}-content`;
const labelLeft = options.collapsible && !isEditMode ? 40 : 8;
const labelLeft = isEditMode ? 40 : 8;
const labelRight = isEditMode ? 48 : options.showOpenAll ? 40 : 8;
return (
@@ -62,29 +62,29 @@ export const BoardContainerSection = ({ section }: Props) => {
radius={board.itemRadius}
p={0}
>
{isVisuallyCollapsed && (
{options.collapsible && !isEditMode && (
<Button
className={classes.containerToggle}
pos="absolute"
top={0}
top={isVisuallyCollapsed ? 0 : -28}
left={0}
maw={`calc(100% - ${options.showOpenAll ? 40 : 0}px)`}
size="compact-sm"
radius="xl"
w="100%"
h={isVisuallyCollapsed ? "100%" : 28}
px={12}
pe={options.showOpenAll ? 40 : 12}
radius={isVisuallyCollapsed ? board.itemRadius : "sm"}
variant="default"
leftSection={<IconChevronDown size={14} />}
justify={options.showLabel ? "flex-start" : "center"}
leftSection={options.showLabel ? isVisuallyCollapsed ? <IconChevronDown size={16} /> : <IconChevronUp size={16} /> : undefined}
onClick={toggle}
aria-expanded={false}
aria-expanded={!isVisuallyCollapsed}
aria-controls={contentId}
aria-label={`${t("action.expand")}: ${label}`}
data-board-container-collapsed-control
styles={{
label: {
overflow: "hidden",
textOverflow: "ellipsis",
},
}}
aria-label={`${t(isVisuallyCollapsed ? "action.expand" : "action.collapse")}: ${label}`}
data-board-container-collapsed-control={isVisuallyCollapsed ? "true" : undefined}
data-board-container-label
title={options.showLabel ? label : undefined}
>
{label}
{options.showLabel ? label : isVisuallyCollapsed ? <IconChevronDown size={16} /> : <IconChevronUp size={16} />}
</Button>
)}
{isEditMode && (
@@ -103,7 +103,7 @@ export const BoardContainerSection = ({ section }: Props) => {
<IconGripVertical size={16} />
</ActionIcon>
)}
{!isVisuallyCollapsed && options.showLabel && options.title && (
{!isVisuallyCollapsed && (!options.collapsible || isEditMode) && options.showLabel && options.title && (
<Badge
pos="absolute"
top={-24}
@@ -128,30 +128,13 @@ export const BoardContainerSection = ({ section }: Props) => {
{options.title}
</Badge>
)}
{options.collapsible && !isEditMode && !isVisuallyCollapsed && (
<ActionIcon
pos="absolute"
top={-24}
left={4}
style={{ zIndex: 10 }}
variant="default"
size={24}
radius="sm"
onClick={toggle}
aria-expanded={!isVisuallyCollapsed}
aria-controls={contentId}
aria-label={`${isVisuallyCollapsed ? t("action.expand") : t("action.collapse")}: ${label}`}
>
<IconChevronUp size={16} />
</ActionIcon>
)}
{options.showOpenAll && !isEditMode && (
<ActionIcon
pos="absolute"
top={isVisuallyCollapsed ? 0 : -24}
right={4}
style={{ zIndex: 10 }}
variant="default"
top={isVisuallyCollapsed ? "50%" : -26}
right={2}
style={{ zIndex: 10, transform: isVisuallyCollapsed ? "translateY(-50%)" : undefined }}
variant={options.collapsible ? "subtle" : "default"}
size={24}
radius="sm"
loading={areAppsLoading}
@@ -1,7 +1,13 @@
import { describe, expect, test } from "vitest";
import { getLogicalTrackSize, LOGICAL_GRID_PITCH } from "~/components/board/layout";
import { getPointerProjectedShape, getSnappedGridCoordinates, getSnappedGridDelta } from "./coordinates";
import {
getContinuousGridDelta,
getContinuousResizePlacement,
getPointerProjectedShape,
getSnappedGridCoordinates,
getSnappedGridDelta,
} from "./coordinates";
describe("dnd grid coordinates", () => {
test.each([0.75, 1, 1.25, 2])("maps transformed drag geometry at %sx scale", (scale) => {
@@ -64,6 +70,56 @@ describe("dnd grid coordinates", () => {
).toEqual({ x: -2, y: 4 });
});
test("keeps the visual resize continuous between snapped grid breakpoints", () => {
const visualScale = 0.75;
const delta = getContinuousGridDelta({
initial: { x: 10, y: 20 },
current: {
x: 10 + LOGICAL_GRID_PITCH * visualScale * 0.42,
y: 20 + LOGICAL_GRID_PITCH * visualScale * 0.68,
},
visualScale,
});
expect(delta?.x).toBeCloseTo(0.42);
expect(delta?.y).toBeCloseTo(0.68);
expect(
getSnappedGridDelta({
initial: { x: 10, y: 20 },
current: {
x: 10 + LOGICAL_GRID_PITCH * visualScale * 0.42,
y: 20 + LOGICAL_GRID_PITCH * visualScale * 0.68,
},
visualScale,
}),
).toEqual({ x: 0, y: 1 });
});
test("projects a continuous resize while anchoring the opposite edges and respecting bounds", () => {
const placement = { id: "widget", x: 2, y: 3, w: 3, h: 2 };
expect(
getContinuousResizePlacement({
placement,
direction: "nw",
deltaColumns: 0.35,
deltaRows: -0.6,
columnCount: 8,
maxRowCount: 10,
}),
).toEqual({ id: "widget", x: 2.35, y: 2.4, w: 2.65, h: 2.6 });
expect(
getContinuousResizePlacement({
placement,
direction: "se",
deltaColumns: 99,
deltaRows: 99,
columnCount: 8,
maxRowCount: 7,
}),
).toEqual({ id: "widget", x: 2, y: 3, w: 6, h: 4 });
});
test("uses independently measured visual axes for transformed resize geometry", () => {
expect(
getSnappedGridDelta({
@@ -1,4 +1,6 @@
import { getLogicalTrackSize, LOGICAL_GRID_PITCH } from "~/components/board/layout";
import type { GridPlacement } from "~/components/board/layout";
import type { GridResizeDirection } from "./types";
interface RectangleLike {
left: number;
@@ -54,11 +56,78 @@ export const getSnappedGridDelta = ({
current: { x: number; y: number };
initial: { x: number; y: number };
visualScale: number | { x: number; y: number };
}) => {
const delta = getContinuousGridDelta({ current, initial, visualScale });
if (!delta) return null;
return {
x: Math.round(delta.x),
y: Math.round(delta.y),
};
};
export const getContinuousGridDelta = ({
current,
initial,
visualScale,
}: {
current: { x: number; y: number };
initial: { x: number; y: number };
visualScale: number | { x: number; y: number };
}) => {
const scale = typeof visualScale === "number" ? { x: visualScale, y: visualScale } : visualScale;
if (!Number.isFinite(scale.x) || scale.x <= 0 || !Number.isFinite(scale.y) || scale.y <= 0) return null;
return {
x: Math.round((current.x - initial.x) / (LOGICAL_GRID_PITCH * scale.x)),
y: Math.round((current.y - initial.y) / (LOGICAL_GRID_PITCH * scale.y)),
x: (current.x - initial.x) / (LOGICAL_GRID_PITCH * scale.x),
y: (current.y - initial.y) / (LOGICAL_GRID_PITCH * scale.y),
};
};
/** Projects a resize continuously while the committed layout remains snapped. */
export const getContinuousResizePlacement = <TPlacement extends GridPlacement>({
placement,
direction,
deltaColumns,
deltaRows,
columnCount,
maxRowCount,
minWidth = 1,
minHeight = 1,
}: {
placement: TPlacement;
direction: GridResizeDirection;
deltaColumns: number;
deltaRows: number;
columnCount: number;
maxRowCount: number | null;
minWidth?: number;
minHeight?: number;
}): TPlacement => {
const fixedRight = placement.x + placement.w;
const fixedBottom = placement.y + placement.h;
let x = placement.x;
let y = placement.y;
let w = placement.w;
let h = placement.h;
if (direction.includes("w")) {
w = clamp(placement.w - deltaColumns, minWidth, fixedRight);
x = fixedRight - w;
} else if (direction.includes("e")) {
w = clamp(placement.w + deltaColumns, minWidth, columnCount - placement.x);
}
if (direction.includes("n")) {
h = clamp(placement.h - deltaRows, minHeight, fixedBottom);
y = fixedBottom - h;
} else if (direction.includes("s")) {
h = clamp(
placement.h + deltaRows,
minHeight,
maxRowCount === null ? Number.MAX_SAFE_INTEGER : maxRowCount - placement.y,
);
}
return { ...placement, x, y, w, h };
};
const clamp = (value: number, minimum: number, maximum: number) => Math.min(Math.max(value, minimum), maximum);
@@ -6,7 +6,13 @@ export {
previewGridResize,
resolvePinnedGridCollisions,
} from "./engine";
export { getPointerProjectedShape, getSnappedGridCoordinates, getSnappedGridDelta } from "./coordinates";
export {
getContinuousGridDelta,
getContinuousResizePlacement,
getPointerProjectedShape,
getSnappedGridCoordinates,
getSnappedGridDelta,
} from "./coordinates";
export type { DragProjectionOrigin } from "./coordinates";
export { getPreferredNestedExitTargetId } from "./targeting";
export type { GridTargetGeometry } from "./targeting";
@@ -5,16 +5,7 @@ import { createContext, useContext } from "react";
export type GridEditorRuntimeStatus = "idle" | "loading" | "ready" | "error";
export interface GridResizePreview {
id: string;
x: number;
y: number;
w: number;
h: number;
}
const GridEditorRuntimeContext = createContext<GridEditorRuntimeStatus>("idle");
const GridResizePreviewContext = createContext<GridResizePreview | null>(null);
export const GridEditorRuntimeProvider = ({
children,
@@ -24,15 +15,3 @@ export const GridEditorRuntimeProvider = ({
);
export const useGridEditorRuntimeStatus = () => useContext(GridEditorRuntimeContext);
export const GridResizePreviewProvider = ({
children,
value,
}: PropsWithChildren<{ value: GridResizePreview | null }>) => (
<GridResizePreviewContext.Provider value={value}>{children}</GridResizePreviewContext.Provider>
);
export const useGridResizePreview = (entryId: string) => {
const preview = useContext(GridResizePreviewContext);
return preview?.id === entryId ? preview : null;
};
@@ -73,8 +73,8 @@
zoom: var(--board-grid-overlay-content-zoom, 1);
}
body[data-board-grid-interacting] .board-grid-entry,
body[data-board-grid-interacting] .board-grid-placeholder {
body[data-board-grid-interacting="drag"] .board-grid-entry,
body[data-board-grid-interacting="drag"] .board-grid-placeholder {
transition: none;
}
@@ -99,6 +99,24 @@ body[data-board-grid-interacting] .board-grid-placeholder {
height 100ms ease;
}
.board-grid-placeholder[data-grid-placeholder-mode="resize"] {
z-index: 1001;
background: color-mix(in srgb, var(--mantine-primary-color-filled) 10%, transparent);
}
.board-grid-resize-outline {
z-index: 1002;
background: color-mix(in srgb, var(--mantine-primary-color-filled) 6%, transparent);
border: 2px solid var(--mantine-primary-color-filled);
border-radius: var(--mantine-radius-md);
pointer-events: none;
}
.board-grid-resize-outline[data-grid-resize-valid="false"] {
background: color-mix(in srgb, var(--mantine-color-red-filled) 8%, transparent);
border-color: var(--mantine-color-red-filled);
}
.board-grid-editor[data-dnd-drop-target="true"] {
outline: 2px solid color-mix(in srgb, var(--mantine-primary-color-filled) 60%, transparent);
outline-offset: 4px;
@@ -110,7 +128,7 @@ body[data-board-grid-interacting] .board-grid-placeholder {
.board-grid-resize-handle {
position: absolute;
z-index: 8;
z-index: 20;
display: block;
padding: 0;
appearance: none;
@@ -246,6 +264,7 @@ body[data-board-grid-interacting] .board-grid-placeholder {
@media (prefers-reduced-motion: reduce) {
.board-grid-entry,
.board-grid-placeholder,
.board-grid-resize-outline,
.board-grid-resize-handle::after {
transition: none;
}
@@ -36,6 +36,8 @@ import {
beginGridTransaction,
cancelGridTransaction,
commitGridTransaction,
getContinuousGridDelta,
getContinuousResizePlacement,
getPreferredNestedExitTargetId,
getPointerProjectedShape,
getSnappedGridCoordinates,
@@ -45,7 +47,6 @@ import {
} from "./dnd";
import type { DragProjectionOrigin, GridResizeDirection, GridTransaction, TransactionalGridState } from "./dnd";
import { getGridDepth } from "./grid-depth";
import { GridResizePreviewProvider } from "./grid-editor-runtime";
import { useBoardGridPortalHost } from "./grid-portal-host";
import type { CommitSectionGridInput, SectionGridPlacement } from "./use-grid-layout-actions";
import { useGridLayoutActions } from "./use-grid-layout-actions";
@@ -602,7 +603,10 @@ export const BoardGridEditorProvider = ({ children }: PropsWithChildren) => {
activeId: active.transaction.activeId,
sourceGridId: active.transaction.sourceGridId,
targetGridId: active.transaction.sourceGridId,
targetPlacement: null,
targetPlacement:
result.transaction.preview.grids
.find((grid) => grid.id === active.transaction.sourceGridId)
?.placements.find((placement) => placement.id === active.transaction.activeId) ?? null,
state: result.transaction.preview,
mode: "resize",
valid: true,
@@ -644,15 +648,6 @@ export const BoardGridEditorProvider = ({ children }: PropsWithChildren) => {
registerGrid,
],
);
const resizePreview = useMemo(() => {
if (interaction?.mode !== "resize") return null;
return (
interaction.state.grids
.flatMap((grid) => grid.placements)
.find((placement) => placement.id === interaction.activeId) ?? null
);
}, [interaction]);
useEffect(() => {
if (interaction?.mode !== "resize") return;
const handleKeyDown = (event: KeyboardEvent) => {
@@ -666,35 +661,33 @@ export const BoardGridEditorProvider = ({ children }: PropsWithChildren) => {
return (
<BoardGridEditorContext.Provider value={value}>
<GridResizePreviewProvider value={resizePreview}>
<DragDropProvider
plugins={configureEditorPlugins}
onCollision={handleCollision}
onBeforeDragStart={handleBeforeDragStart}
onDragStart={handleDragStart}
onDragMove={handleDragMove}
onDragOver={handleDragOver}
onDragEnd={handleDragEnd}
<DragDropProvider
plugins={configureEditorPlugins}
onCollision={handleCollision}
onBeforeDragStart={handleBeforeDragStart}
onDragStart={handleDragStart}
onDragMove={handleDragMove}
onDragOver={handleDragOver}
onDragEnd={handleDragEnd}
>
{children}
<DragOverlay
className="board-grid-drag-overlay"
dropAnimation={null}
style={
{
zoom: overlayZoom,
"--board-grid-overlay-content-zoom": 1 / overlayZoom,
} as CSSProperties
}
>
{children}
<DragOverlay
className="board-grid-drag-overlay"
dropAnimation={null}
style={
{
zoom: overlayZoom,
"--board-grid-overlay-content-zoom": 1 / overlayZoom,
} as CSSProperties
}
>
{(source) => {
const data = getGridEntryData(source);
return data ? <div className="board-grid-drag-overlay-card">{data.label}</div> : null;
}}
</DragOverlay>
<GridCollisionSynchronizer interaction={interaction} />
</DragDropProvider>
</GridResizePreviewProvider>
{(source) => {
const data = getGridEntryData(source);
return data ? <div className="board-grid-drag-overlay-card">{data.label}</div> : null;
}}
</DragOverlay>
<GridCollisionSynchronizer interaction={interaction} />
</DragDropProvider>
</BoardGridEditorContext.Provider>
);
};
@@ -726,23 +719,21 @@ export default function GridEditor({ sectionId, columnCount, rowCount, placement
const renderPlacements = useMemo(() => {
if (!previewGrid || !interaction) return placements;
const preview = previewGrid.placements.filter(
(placement) => interaction.mode === "resize" || placement.id !== interaction.activeId,
);
if (interaction.mode === "drag" && interaction.sourceGridId === sectionId) {
if (interaction.sourceGridId === sectionId) {
const original = controlledById.get(interaction.activeId);
return original ? [...preview, original] : preview;
if (!original) return previewGrid.placements;
return previewGrid.placements.some((placement) => placement.id === interaction.activeId)
? previewGrid.placements.map((placement) => (placement.id === interaction.activeId ? original : placement))
: [...previewGrid.placements, original];
}
return preview;
return previewGrid.placements.filter((placement) => placement.id !== interaction.activeId);
}, [controlledById, interaction, placements, previewGrid, sectionId]);
const entryById = useMemo(
() => new Map([...items, ...innerSections].map((entry) => [entry.id, entry])),
[innerSections, items],
);
const targetPlacement =
interaction?.mode === "drag" && interaction.valid && interaction.targetGridId === sectionId
? interaction.targetPlacement
: null;
interaction?.valid && interaction.targetGridId === sectionId ? interaction.targetPlacement : null;
const renderedRowCount =
section.kind !== "container" && interaction?.mode === "resize"
? Math.max(rowCount, getLayoutRowCount(renderPlacements))
@@ -817,13 +808,23 @@ export default function GridEditor({ sectionId, columnCount, rowCount, placement
? entry.advancedOptions.title?.trim() || t(`widget.${entry.kind}.name`)
: entry.options.title.trim() || t("section.container.untitled")
: placement.id;
return <DndGridEntry key={placement.id} sectionId={sectionId} placement={placement} label={label} />;
return (
<DndGridEntry
key={placement.id}
sectionId={sectionId}
placement={placement}
label={label}
columnCount={columnCount}
maxRowCount={section.kind === "container" ? rowCount : null}
/>
);
})}
{targetPlacement && (
<div
className="board-grid-placeholder"
style={getLogicalItemStyle(targetPlacement)}
data-grid-placeholder-for={interaction?.activeId}
data-grid-placeholder-mode={interaction?.mode}
data-grid-x={targetPlacement.x}
data-grid-y={targetPlacement.y}
data-grid-w={targetPlacement.w}
@@ -845,12 +846,18 @@ interface DndGridEntryProps {
sectionId: string;
placement: SectionGridPlacement;
label: string;
columnCount: number;
maxRowCount: number | null;
}
const DndGridEntry = ({ sectionId, placement, label }: DndGridEntryProps) => {
const DndGridEntry = ({ sectionId, placement, label, columnCount, maxRowCount }: DndGridEntryProps) => {
const { acquireContainer } = useBoardGridPortalHost();
const { interaction, beginResize, previewResize, completeResize, cancelResize } = useBoardGridEditor();
const [isSectionChromeActive, setIsSectionChromeActive] = useState(false);
const [continuousResize, setContinuousResize] = useState<{
placement: SectionGridPlacement;
valid: boolean;
} | null>(null);
const mountRef = useRef<HTMLDivElement>(null);
const shellRef = useRef<HTMLDivElement>(null);
const sourceData = useMemo<GridEntryData>(
@@ -932,6 +939,14 @@ const DndGridEntry = ({ sectionId, placement, label }: DndGridEntryProps) => {
const isActive = interaction?.activeId === placement.id;
const style = getLogicalItemStyle(placement) as CSSProperties;
const continuousResizeStyle =
continuousResize && isActive
? getLogicalItemStyle({
...continuousResize.placement,
x: continuousResize.placement.x - placement.x,
y: continuousResize.placement.y - placement.y,
})
: undefined;
return (
<div
@@ -950,15 +965,27 @@ const DndGridEntry = ({ sectionId, placement, label }: DndGridEntryProps) => {
data-dnd-dropping={isDropping ? "true" : undefined}
>
<div ref={mountRef} className="board-grid-content-mount" />
{continuousResizeStyle && (
<div
className="board-grid-resize-outline"
style={continuousResizeStyle}
data-grid-resize-outline
data-grid-resize-valid={String(continuousResize?.valid)}
aria-hidden="true"
/>
)}
<GridResizeHandles
sectionId={sectionId}
placement={placement}
label={label}
disabled={interaction !== null || (placement.type === "section" && !isSectionChromeActive)}
columnCount={columnCount}
maxRowCount={maxRowCount}
beginResize={beginResize}
previewResize={previewResize}
completeResize={completeResize}
cancelResize={cancelResize}
setContinuousResize={setContinuousResize}
/>
</div>
);
@@ -969,10 +996,13 @@ interface GridResizeHandlesProps {
placement: SectionGridPlacement;
label: string;
disabled: boolean;
columnCount: number;
maxRowCount: number | null;
beginResize: BoardGridEditorContextValue["beginResize"];
previewResize: BoardGridEditorContextValue["previewResize"];
completeResize: BoardGridEditorContextValue["completeResize"];
cancelResize: BoardGridEditorContextValue["cancelResize"];
setContinuousResize: (preview: { placement: SectionGridPlacement; valid: boolean } | null) => void;
}
const RESIZE_DIRECTIONS: readonly GridResizeDirection[] = ["n", "ne", "e", "se", "s", "sw", "w", "nw"];
@@ -982,10 +1012,13 @@ const GridResizeHandles = ({
placement,
label,
disabled,
columnCount,
maxRowCount,
beginResize,
previewResize,
completeResize,
cancelResize,
setContinuousResize,
}: GridResizeHandlesProps) => {
const pointerStartRef = useRef<{
id: number;
@@ -1033,18 +1066,35 @@ const GridResizeHandles = ({
const started = pointerStartRef.current;
if (!started || started.id !== event.pointerId) return;
event.preventDefault();
const delta = getSnappedGridDelta({
const input = {
initial: { x: started.x, y: started.y },
current: { x: event.clientX, y: event.clientY },
visualScale: started.visualScale,
};
const snappedDelta = getSnappedGridDelta(input);
const continuousDelta = getContinuousGridDelta(input);
if (!snappedDelta || !continuousDelta) return;
const valid = update(started.direction, snappedDelta.x, snappedDelta.y);
setContinuousResize({
placement: getContinuousResizePlacement({
placement,
direction: started.direction,
deltaColumns: continuousDelta.x,
deltaRows: continuousDelta.y,
columnCount,
maxRowCount,
minWidth: placement.minW ?? 1,
minHeight: placement.minH ?? 1,
}),
valid,
});
if (delta) update(started.direction, delta.x, delta.y);
};
const handlePointerEnd = (event: ReactPointerEvent<HTMLSpanElement>, canceled: boolean) => {
const started = pointerStartRef.current;
if (!started || started.id !== event.pointerId) return;
pointerStartRef.current = null;
setContinuousResize(null);
if (event.currentTarget.hasPointerCapture(event.pointerId))
event.currentTarget.releasePointerCapture(event.pointerId);
if (canceled) cancelResize();
@@ -1073,6 +1123,7 @@ const GridResizeHandles = ({
onLostPointerCapture={(event) => {
if (pointerStartRef.current?.id !== event.pointerId) return;
pointerStartRef.current = null;
setContinuousResize(null);
cancelResize();
}}
/>
@@ -14,7 +14,6 @@ import { getBoardLaneColumnCount } from "~/components/board/layout";
import { SectionContentItem } from "../content";
import { SectionProvider } from "../section-context";
import { useSectionItems } from "../use-section-items";
import { useGridResizePreview } from "./grid-editor-runtime";
import classes from "./section-grid.module.css";
interface GridPortalHostContextValue {
@@ -138,15 +137,12 @@ const GridPortalContent = ({ itemId, ownerSectionId, integrations, announce }: G
const board = useRequiredBoard();
const currentLayoutId = useCurrentLayout();
const { items, innerSections } = useSectionItems(ownerSectionId);
const entryPreview = useGridResizePreview(itemId);
const ownerPreview = useGridResizePreview(ownerSectionId);
const persistedEntry = [...items, ...innerSections].find((candidate) => candidate.id === itemId);
const entry = withResizePreview(persistedEntry, entryPreview);
const entry = persistedEntry;
const rawSection = board.sections.find((section) => section.id === ownerSectionId);
const currentLayout = board.layouts.find((layout) => layout.id === currentLayoutId);
const persistedSection = toGridSection(rawSection, currentLayoutId);
const section =
persistedSection?.kind === "container" ? withResizePreview(persistedSection, ownerPreview) : persistedSection;
const section = persistedSection;
if (!entry || !section || !currentLayout) return null;
@@ -181,22 +177,6 @@ const GridPortalContent = ({ itemId, ownerSectionId, integrations, announce }: G
);
};
const withResizePreview = <
TValue extends { xOffset: number; yOffset: number; width: number; height: number } | null | undefined,
>(
value: TValue,
preview: { x: number; y: number; w: number; h: number } | null,
): TValue =>
value && preview
? ({
...value,
xOffset: preview.x,
yOffset: preview.y,
width: preview.w,
height: preview.h,
} as TValue)
: value;
const toGridSection = (
section: Section | undefined,
layoutId: string,
@@ -21,3 +21,23 @@
border-color: transparent !important;
box-shadow: none !important;
}
.containerToggle {
z-index: 9;
overflow: hidden;
color: var(--mantine-color-text);
background-color: var(--background-color);
border-color: var(--border-color);
}
.containerToggle :global(.mantine-Button-inner) {
width: 100%;
min-width: 0;
}
.containerToggle :global(.mantine-Button-label) {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}