feat(layout): implement cookie-based state management for playground sidebar visibility

This commit is contained in:
Anish Sarkar 2026-07-14 03:39:02 +05:30
parent 2c6724206e
commit 79c7d1ecdf
4 changed files with 38 additions and 23 deletions

View file

@ -20,9 +20,11 @@ import { useElectronAPI } from "@/hooks/use-platform";
export function DashboardClientLayout({
children,
workspaceId,
initialPlaygroundSidebarCollapsed,
}: {
children: React.ReactNode;
workspaceId: string;
initialPlaygroundSidebarCollapsed: boolean;
}) {
const t = useTranslations("dashboard");
const router = useRouter();
@ -162,7 +164,10 @@ export function DashboardClientLayout({
return (
<DocumentUploadDialogProvider>
<OnboardingTour />
<LayoutDataProvider workspaceId={workspaceId}>
<LayoutDataProvider
workspaceId={workspaceId}
initialPlaygroundSidebarCollapsed={initialPlaygroundSidebarCollapsed}
>
{children}
<ConnectorIndicator showTrigger={false} />
</LayoutDataProvider>

View file

@ -1,16 +1,27 @@
// Server component
import type React from "react";
import { use } from "react";
import { cookies } from "next/headers";
import { DashboardClientLayout } from "./client-layout";
export default function DashboardLayout({
const PLAYGROUND_SIDEBAR_COLLAPSED_COOKIE = "surfsense_playground_sidebar_collapsed";
export default async function DashboardLayout({
params,
children,
}: {
params: Promise<{ workspace_id: string }>;
children: React.ReactNode;
}) {
const { workspace_id } = use(params);
const [{ workspace_id }, cookieStore] = await Promise.all([params, cookies()]);
const initialPlaygroundSidebarCollapsed =
cookieStore.get(PLAYGROUND_SIDEBAR_COLLAPSED_COOKIE)?.value === "true";
return <DashboardClientLayout workspaceId={workspace_id}>{children}</DashboardClientLayout>;
return (
<DashboardClientLayout
workspaceId={workspace_id}
initialPlaygroundSidebarCollapsed={initialPlaygroundSidebarCollapsed}
>
{children}
</DashboardClientLayout>
);
}

View file

@ -56,10 +56,15 @@ import { LayoutShell } from "../ui/shell";
interface LayoutDataProviderProps {
workspaceId: string;
initialPlaygroundSidebarCollapsed: boolean;
children: React.ReactNode;
}
export function LayoutDataProvider({ workspaceId, children }: LayoutDataProviderProps) {
export function LayoutDataProvider({
workspaceId,
initialPlaygroundSidebarCollapsed,
children,
}: LayoutDataProviderProps) {
const t = useTranslations("dashboard");
const tCommon = useTranslations("common");
const tSidebar = useTranslations("sidebar");
@ -720,6 +725,7 @@ export function LayoutDataProvider({ workspaceId, children }: LayoutDataProvider
onTabSwitch={handleTabSwitch}
onTabPrefetch={handleTabPrefetch}
playgroundSidebar={<PlaygroundSidebar workspaceId={workspaceId} />}
initialPlaygroundSidebarCollapsed={initialPlaygroundSidebarCollapsed}
>
<Fragment key={chatResetKey}>{children}</Fragment>
</LayoutShell>

View file

@ -2,7 +2,7 @@
import { useAtomValue } from "jotai";
import dynamic from "next/dynamic";
import { useEffect, useMemo, useState } from "react";
import { useMemo, useState } from "react";
import { activeTabAtom, type Tab } from "@/atoms/tabs/tabs.atom";
import { Logo } from "@/components/Logo";
import { Spinner } from "@/components/ui/spinner";
@ -41,7 +41,8 @@ const DocumentTabContent = dynamic(
}
);
const PLAYGROUND_SIDEBAR_COLLAPSED_STORAGE_KEY = "surfsense:layout:v1:playground-sidebar-collapsed";
const PLAYGROUND_SIDEBAR_COLLAPSED_COOKIE = "surfsense_playground_sidebar_collapsed";
const PLAYGROUND_SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 365;
function MacDesktopTitleBar({
isSidebarCollapsed,
@ -115,6 +116,7 @@ interface LayoutShellProps {
onTabSwitch?: (tab: Tab) => void;
onTabPrefetch?: (tab: Tab) => void;
playgroundSidebar?: React.ReactNode;
initialPlaygroundSidebarCollapsed?: boolean;
}
function MainContentPanel({
@ -216,12 +218,15 @@ export function LayoutShell({
onTabSwitch,
onTabPrefetch,
playgroundSidebar,
initialPlaygroundSidebarCollapsed = false,
}: LayoutShellProps) {
const isMobile = useIsMobile();
const electronAPI = useElectronAPI();
const isMacDesktop = electronAPI?.versions.platform === "darwin";
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [isPlaygroundSidebarCollapsed, setIsPlaygroundSidebarCollapsed] = useState(false);
const [isPlaygroundSidebarCollapsed, setIsPlaygroundSidebarCollapsed] = useState(
initialPlaygroundSidebarCollapsed
);
const { isCollapsed, setIsCollapsed, toggleCollapsed } = useSidebarState(defaultCollapsed);
const {
sidebarWidth,
@ -234,23 +239,11 @@ export function LayoutShell({
() => ({ isCollapsed, setIsCollapsed, toggleCollapsed }),
[isCollapsed, setIsCollapsed, toggleCollapsed]
);
useEffect(() => {
try {
setIsPlaygroundSidebarCollapsed(
window.localStorage.getItem(PLAYGROUND_SIDEBAR_COLLAPSED_STORAGE_KEY) === "true"
);
} catch {}
}, []);
const handlePlaygroundSidebarToggle = () => {
setIsPlaygroundSidebarCollapsed((collapsed) => {
const nextCollapsed = !collapsed;
try {
window.localStorage.setItem(
PLAYGROUND_SIDEBAR_COLLAPSED_STORAGE_KEY,
String(nextCollapsed)
);
} catch {}
const secureAttribute = window.location.protocol === "https:" ? "; Secure" : "";
document.cookie = `${PLAYGROUND_SIDEBAR_COLLAPSED_COOKIE}=${nextCollapsed}; Path=/; Max-Age=${PLAYGROUND_SIDEBAR_COOKIE_MAX_AGE}; SameSite=Lax${secureAttribute}`;
return nextCollapsed;
});
};