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:
Claude
2026-06-18 10:32:59 +02:00
parent a4051ae132
commit 91e5902020
5 changed files with 1890 additions and 39 deletions
+6
View File
@@ -0,0 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/dev/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
+1783
View File
File diff suppressed because it is too large Load Diff
+56
View File
@@ -1255,6 +1255,62 @@ button:disabled {
left: 0;
width: 230px;
}
.adminCardGrid {
grid-template-columns: 1fr;
}
}
@media (max-width: 600px) {
.topBar {
gap: 10px;
padding: 8px 12px;
}
.brandLogoFrame {
width: 36px;
height: 36px;
}
.title {
font-size: 15px;
}
.subtitle {
font-size: 11px;
}
.profileButton {
width: 38px;
height: 38px;
font-size: 12px;
}
.dashboardWorkspace {
padding: 10px;
}
.topEditModeButton {
width: 36px;
height: 36px;
}
.topBar .topEditModeButton {
right: 54px;
}
.dashboardTabsBar {
padding: 4px 12px !important;
}
.editToolbar {
padding: 10px 12px;
font-size: 13px;
}
.editToolbar p {
font-size: 12px;
}
}
.singleNoteWidget {
+42 -37
View File
@@ -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>
);
+3 -2
View File
@@ -15,7 +15,7 @@
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
@@ -32,7 +32,8 @@
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": [
"node_modules",