fix(web): Widgets nicht mehr zur Mitte versetzen

Die Zentrierung der belegten Spalten (Design Mosaik, Runde 3) liess
Widgets nach dem Bearbeiten springen, z. B. ein einzelnes Widget oben
links in die Seitenmitte (Nutzer, live 29.09.). Auf Wunsch ersatzlos
entfernt: Ansicht = Bearbeitungsraster. centeringOffset, breakpointFor
und die ungenutzte Prop onInsetChange entfallen.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-29 10:13:03 +02:00
parent c703d87a1c
commit bc4c0119de
4 changed files with 4 additions and 61 deletions
@@ -93,33 +93,6 @@ const GRID_CONSTRAINTS: LayoutConstraint[] = [...defaultConstraints, RESIZE_AXIS
const GRID_MARGIN = 12;
/**
* Seitlicher Versatz, um die belegten Spalten in der Ansicht mittig zu
* stellen (Design „Mosaik“, Runde 3): auf breiten Bildschirmen blieb rechts
* ein leerer Streifen, weil die Kacheln links beginnen. Belegt die Anordnung
* schon (fast) die ganze Breite, bleibt es bei 0.
*/
export function centeringOffset(
items: Array<{ x: number; w: number }> | undefined,
cols: number,
width: number,
): number {
if (!items || items.length === 0 || cols <= 0 || width <= 0) return 0;
const usedCols = Math.min(cols, Math.max(...items.map((l) => l.x + l.w)));
if (usedCols >= cols) return 0;
const colWidth = (width - GRID_MARGIN * (cols + 1)) / cols;
const freeWidth = (cols - usedCols) * (colWidth + GRID_MARGIN);
return Math.max(0, Math.floor(freeWidth / 2));
}
/** Breakpoint zur Breite — gleiche Regel wie react-grid-layout (strikt groesser). */
function breakpointFor(width: number): keyof typeof COLS {
const order = (Object.keys(BREAKPOINTS) as Array<keyof typeof BREAKPOINTS>).sort(
(a, b) => BREAKPOINTS[b] - BREAKPOINTS[a],
);
return order.find((bp) => width > BREAKPOINTS[bp]) ?? 'xxs';
}
interface LayoutItemShape {
i: string;
x: number;
@@ -140,8 +113,6 @@ interface DashboardGridProps {
onOpenCatalog?: () => void;
/** Leerer Zustand: fuegt eine vorgeschlagene Kachel direkt hinzu. */
onQuickAdd?: (type: WidgetType) => void;
/** Meldet den seitlichen Versatz der zentrierten Ansicht (0 im Bearbeitungsmodus). */
onInsetChange?: (px: number) => void;
}
/** Vorschlaege im leeren Zustand — Plattform-Kacheln, immer verfuegbar. */
@@ -210,7 +181,6 @@ export function DashboardGrid({
onRemoveWidget,
onOpenCatalog,
onQuickAdd,
onInsetChange,
}: DashboardGridProps) {
const t = useTranslations('widgets');
const [width, setWidth] = useState(1200);
@@ -291,17 +261,6 @@ export function DashboardGrid({
// stabil bleibt.
const effectiveLayouts = useMemo(() => applyConstraintMinima(layouts, widgets), [layouts, widgets]);
// Zentrieren nur in der Ansicht — im Bearbeitungsmodus braucht es das
// volle Raster, damit Kacheln auch in die freien Spalten rechts passen.
const breakpoint = breakpointFor(width);
const inset = isEditMode
? 0
: centeringOffset(effectiveLayouts[breakpoint] ?? effectiveLayouts.lg, COLS[breakpoint], width);
useEffect(() => {
onInsetChange?.(widgets.length === 0 ? 0 : inset);
}, [inset, onInsetChange, widgets.length]);
// Leerer Zustand (D-02, Design „Mosaik“ Runde 2): freundlich ohne
// Illustration — Titel, ein Satz, drei Vorschlaege zum direkten
// Hinzufuegen und der Hauptknopf zum Katalog.
@@ -347,14 +306,7 @@ export function DashboardGrid({
}
return (
// overflow-x-clip: die verschobene Rasterflaeche ragt rechts ueber den
// Rand; `clip` (nicht `hidden`) laesst die Schatten oben/unten stehen.
<div ref={measureRef} className="overflow-x-clip">
<div
data-testid="dashboard-grid-inset"
className="transition-transform duration-200 ease-out motion-reduce:transition-none"
style={inset > 0 ? { transform: `translateX(${inset}px)` } : undefined}
>
<div ref={measureRef}>
<Responsive
width={width}
breakpoints={BREAKPOINTS}
@@ -403,7 +355,6 @@ export function DashboardGrid({
);
})}
</Responsive>
</div>
</div>
);
}