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:
Vendored
+6
@@ -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.
|
||||
Generated
+1783
File diff suppressed because it is too large
Load Diff
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user