added full width button

This commit is contained in:
Oliver Kieselbach
2026-05-07 12:19:11 +02:00
parent b91b6c6d52
commit b61e327752
2 changed files with 74 additions and 4 deletions
@@ -86,6 +86,8 @@ interface SessionTableProps {
onColumnFiltersChange: (filters: Record<string, Set<string>>) => void;
onDeleteSession: (sessionId: string, tenantId: string, deviceName?: string) => void;
onBlockDevice: (serialNumber: string, tenantId: string, deviceName?: string) => void;
fullWidth: boolean;
onToggleFullWidth: () => void;
}
export function SessionTable({
@@ -123,6 +125,8 @@ export function SessionTable({
onColumnFiltersChange,
onDeleteSession,
onBlockDevice,
fullWidth,
onToggleFullWidth,
}: SessionTableProps) {
const router = useRouter();
const [visibleColumns, setVisibleColumns] = useState<Set<string>>(getInitialVisibleColumns);
@@ -328,6 +332,31 @@ export function SessionTable({
))}
</select>
{/* Full-width toggle */}
<button
onClick={onToggleFullWidth}
className={`inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium border transition-colors ${
fullWidth
? "text-blue-700 bg-blue-50 border-blue-200 hover:bg-blue-100"
: "text-gray-600 bg-gray-100 hover:bg-gray-200 border-gray-200"
}`}
title={fullWidth ? "Switch to default width" : "Expand to full width"}
aria-pressed={fullWidth}
>
{fullWidth ? (
// collapse: arrows pointing inward (>-<)
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 12h18M3 8l4 4-4 4M21 8l-4 4 4 4" />
</svg>
) : (
// expand: arrows pointing outward (<-->)
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 12h18M7 8l-4 4 4 4M17 8l4 4-4 4" />
</svg>
)}
<span className="hidden sm:inline">{fullWidth ? "Default" : "Full width"}</span>
</button>
{/* Column Selector */}
<div className="relative" ref={columnSelectorRef}>
<button
@@ -1,6 +1,6 @@
"use client";
import { Suspense, useEffect, useRef, useState } from "react";
import { Suspense, useCallback, useEffect, useRef, useState } from "react";
import Link from "next/link";
import { useRouter, useSearchParams } from "next/navigation";
import { ProtectedRoute } from "../../components/ProtectedRoute";
@@ -30,12 +30,51 @@ export default function Home() {
);
}
const FULL_WIDTH_STORAGE_KEY = "dashboard_fullWidth";
function HomeContent() {
const router = useRouter();
const searchParams = useSearchParams();
// Opt-in full-width layout via ?span=full (also accepts wide/max) — drops the max-w-7xl cap.
const spanParam = searchParams?.get("span")?.toLowerCase();
const fullWidth = spanParam === "full" || spanParam === "wide" || spanParam === "max";
// Full-width layout: URL `?span=full|wide|max` overrides + persists; `?span=default|normal|off` clears.
// Without a URL override, falls back to the last user choice from localStorage.
const [fullWidth, setFullWidth] = useState<boolean>(() => {
const span = searchParams?.get("span")?.toLowerCase();
if (span === "full" || span === "wide" || span === "max") return true;
if (span === "default" || span === "normal" || span === "off") return false;
if (typeof window !== "undefined") {
try {
return localStorage.getItem(FULL_WIDTH_STORAGE_KEY) === "1";
} catch { /* ignore */ }
}
return false;
});
// Persist any URL-driven override on first mount so it survives subsequent visits without the param.
useEffect(() => {
const span = searchParams?.get("span")?.toLowerCase();
if (span === "full" || span === "wide" || span === "max") {
try { localStorage.setItem(FULL_WIDTH_STORAGE_KEY, "1"); } catch { /* ignore */ }
} else if (span === "default" || span === "normal" || span === "off") {
try { localStorage.setItem(FULL_WIDTH_STORAGE_KEY, "0"); } catch { /* ignore */ }
}
// Run once on mount; URL re-sync happens via toggleFullWidth.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const toggleFullWidth = useCallback(() => {
setFullWidth((prev) => {
const next = !prev;
try { localStorage.setItem(FULL_WIDTH_STORAGE_KEY, next ? "1" : "0"); } catch { /* ignore */ }
if (typeof window !== "undefined") {
const url = new URL(window.location.href);
if (next) url.searchParams.set("span", "full");
else url.searchParams.delete("span");
window.history.replaceState(null, "", url.toString());
}
return next;
});
}, []);
const mainClassName = fullWidth
? "w-full px-4 sm:px-6 lg:px-8 py-4"
: "max-w-7xl mx-auto py-4 sm:px-6 lg:px-8";
@@ -309,6 +348,8 @@ function HomeContent() {
onColumnFiltersChange={setColumnFilters}
onDeleteSession={deleteSession}
onBlockDevice={blockDevice}
fullWidth={fullWidth}
onToggleFullWidth={toggleFullWidth}
/>
)}
</div>