Add responsive grid layout for mobile/tablet support
Switch from fixed 48-column grid to responsive breakpoints: - lg (>=1200px): 48 columns, free positioning (unchanged desktop behavior) - md (>=900px): 24 columns, vertical compaction - sm (>=600px): 12 columns, vertical compaction - xs (<600px): 6 columns, vertical compaction Widgets automatically reflow and stack on smaller screens instead of being squished. Layout changes are only persisted from the desktop breakpoint. Drag/resize editing is desktop-only. Also adds mobile CSS refinements for topbar, tabs, and workspace padding. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import type { ComponentType, ReactNode } from "react";
|
||||
import { useMemo } from "react";
|
||||
import ReactGridLayoutBase, { WidthProvider } from "react-grid-layout/legacy";
|
||||
import { useCallback, useMemo, useRef, useState } from "react";
|
||||
import { Responsive, WidthProvider } from "react-grid-layout/legacy";
|
||||
import type { DashboardGridWidget, DashboardLayoutItem } from "@/lib/dashboard-layout";
|
||||
|
||||
export type DashboardGridProps = {
|
||||
@@ -13,7 +13,10 @@ export type DashboardGridProps = {
|
||||
onLayoutChange: (layout: DashboardLayoutItem[]) => void;
|
||||
};
|
||||
|
||||
const WidthAwareGridLayout = WidthProvider(ReactGridLayoutBase) as ComponentType<any>;
|
||||
const ResponsiveGridLayout = WidthProvider(Responsive) as ComponentType<any>;
|
||||
|
||||
const BREAKPOINTS = { lg: 1200, md: 900, sm: 600, xs: 0 };
|
||||
const COLS = { lg: 48, md: 24, sm: 12, xs: 6 };
|
||||
|
||||
function clamp(value: number, min: number, max: number): number {
|
||||
return Math.max(min, Math.min(max, value));
|
||||
@@ -21,44 +24,26 @@ function clamp(value: number, min: number, max: number): number {
|
||||
|
||||
function getWidgetMinimumSize(widget: DashboardGridWidget): { minW: number; minH: number } {
|
||||
if (widget.type === "search") {
|
||||
return {
|
||||
minW: 8,
|
||||
minH: 5
|
||||
};
|
||||
return { minW: 8, minH: 5 };
|
||||
}
|
||||
|
||||
if (widget.type === "clock") {
|
||||
return {
|
||||
minW: 4,
|
||||
minH: 4
|
||||
};
|
||||
return { minW: 4, minH: 4 };
|
||||
}
|
||||
|
||||
if (widget.type === "calendar") {
|
||||
return {
|
||||
minW: 8,
|
||||
minH: 6
|
||||
};
|
||||
return { minW: 8, minH: 6 };
|
||||
}
|
||||
|
||||
if (widget.type === "calculator") {
|
||||
return {
|
||||
minW: 8,
|
||||
minH: 6
|
||||
};
|
||||
return { minW: 8, minH: 6 };
|
||||
}
|
||||
|
||||
if (widget.type === "note") {
|
||||
return {
|
||||
minW: 4,
|
||||
minH: 5
|
||||
};
|
||||
return { minW: 4, minH: 5 };
|
||||
}
|
||||
|
||||
return {
|
||||
minW: 4,
|
||||
minH: 4
|
||||
};
|
||||
return { minW: 4, minH: 4 };
|
||||
}
|
||||
|
||||
function widgetsToLayout(widgets: DashboardGridWidget[]): DashboardLayoutItem[] {
|
||||
@@ -104,7 +89,25 @@ export default function DashboardGrid({
|
||||
renderWidget,
|
||||
onLayoutChange
|
||||
}: DashboardGridProps) {
|
||||
const layout = useMemo(() => widgetsToLayout(widgets), [widgets]);
|
||||
const [currentBreakpoint, setCurrentBreakpoint] = useState("lg");
|
||||
const breakpointRef = useRef("lg");
|
||||
|
||||
const isDesktop = currentBreakpoint === "lg";
|
||||
|
||||
const layouts = useMemo(() => ({
|
||||
lg: widgetsToLayout(widgets)
|
||||
}), [widgets]);
|
||||
|
||||
const handleBreakpointChange = useCallback((breakpoint: string) => {
|
||||
breakpointRef.current = breakpoint;
|
||||
setCurrentBreakpoint(breakpoint);
|
||||
}, []);
|
||||
|
||||
const handleLayoutChange = useCallback((layout: DashboardLayoutItem[]) => {
|
||||
if (breakpointRef.current === "lg") {
|
||||
onLayoutChange(normalizeLayout(layout));
|
||||
}
|
||||
}, [onLayoutChange]);
|
||||
|
||||
return (
|
||||
<div className="widgetGridShell">
|
||||
@@ -116,24 +119,26 @@ export default function DashboardGrid({
|
||||
) : null}
|
||||
|
||||
{widgets.length > 0 ? (
|
||||
<WidthAwareGridLayout
|
||||
<ResponsiveGridLayout
|
||||
className="widgetGrid"
|
||||
layout={layout}
|
||||
cols={48}
|
||||
layouts={layouts}
|
||||
breakpoints={BREAKPOINTS}
|
||||
cols={COLS}
|
||||
rowHeight={8}
|
||||
margin={[12, 12]}
|
||||
containerPadding={[0, 0]}
|
||||
compactType={null}
|
||||
preventCollision={true}
|
||||
compactType={isDesktop ? null : "vertical"}
|
||||
preventCollision={isDesktop}
|
||||
isBounded={false}
|
||||
autoSize={true}
|
||||
isDraggable={editMode}
|
||||
isResizable={editMode}
|
||||
isDraggable={editMode && isDesktop}
|
||||
isResizable={editMode && isDesktop}
|
||||
draggableHandle=".widgetDragHandle"
|
||||
draggableCancel=".widgetNoDrag"
|
||||
resizeHandles={["se"]}
|
||||
measureBeforeMount={true}
|
||||
onLayoutChange={(nextLayout: DashboardLayoutItem[]) => onLayoutChange(normalizeLayout(nextLayout))}
|
||||
onBreakpointChange={handleBreakpointChange}
|
||||
onLayoutChange={handleLayoutChange}
|
||||
>
|
||||
{widgets.map((widget) => (
|
||||
<div
|
||||
@@ -143,7 +148,7 @@ export default function DashboardGrid({
|
||||
{renderWidget(widget)}
|
||||
</div>
|
||||
))}
|
||||
</WidthAwareGridLayout>
|
||||
</ResponsiveGridLayout>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user