feat: deep-link sidebar Layouts to specific design surface

- Sidebar Layouts items now link to /design?surface=<surfaceId>
- Design page reads ?surface= param and opens that surface directly
- DesignPage split into DesignPageInner + Suspense wrapper so
  useSearchParams works in the Next.js static build

Made-with: Cursor
This commit is contained in:
2026-03-06 14:12:29 -08:00
parent 812645cae8
commit 39167dbe45
2 changed files with 21 additions and 6 deletions

View File

@@ -1,6 +1,7 @@
"use client"; "use client";
import { use, useState, useEffect } from "react"; import { use, useState, useEffect, Suspense } from "react";
import { useSearchParams } from "next/navigation";
import { toast } from "sonner"; import { toast } from "sonner";
import { import {
SCAFFOLD_REGISTRY, THEME_REGISTRY, SCAFFOLD_REGISTRY, THEME_REGISTRY,
@@ -958,8 +959,9 @@ function SurfacePicker({
// Page // Page
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export default function DesignPage({ params }: { params: Promise<{ workspace: string; projectId: string }> }) { function DesignPageInner({ projectId }: { projectId: string }) {
const { projectId } = use(params); const searchParams = useSearchParams();
const requestedSurface = searchParams.get("surface");
const [surfaces, setSurfaces] = useState<string[]>([]); const [surfaces, setSurfaces] = useState<string[]>([]);
const [surfaceThemes, setSurfaceThemes] = useState<Record<string, string>>({}); const [surfaceThemes, setSurfaceThemes] = useState<Record<string, string>>({});
@@ -979,7 +981,11 @@ export default function DesignPage({ params }: { params: Promise<{ workspace: st
setSurfaces(loaded); setSurfaces(loaded);
setSurfaceThemes(d.surfaceThemes ?? {}); setSurfaceThemes(d.surfaceThemes ?? {});
setSelectedThemes(d.surfaceThemes ?? {}); setSelectedThemes(d.surfaceThemes ?? {});
if (loaded.length > 0) setActiveSurfaceId(loaded[0]); // Honour ?surface= param if valid, otherwise default to first
const initial = requestedSurface && loaded.includes(requestedSurface)
? requestedSurface
: loaded[0] ?? null;
setActiveSurfaceId(initial);
return loaded; return loaded;
}); });
@@ -1137,3 +1143,12 @@ export default function DesignPage({ params }: { params: Promise<{ workspace: st
</div> </div>
); );
} }
export default function DesignPage({ params }: { params: Promise<{ workspace: string; projectId: string }> }) {
const { projectId } = use(params);
return (
<Suspense fallback={<div style={{ display: "flex", height: "100%", alignItems: "center", justifyContent: "center", color: "#a09a90", fontFamily: "Outfit, sans-serif", fontSize: "0.85rem" }}>Loading</div>}>
<DesignPageInner projectId={projectId} />
</Suspense>
);
}

View File

@@ -338,12 +338,12 @@ export function VIBNSidebar({ workspace }: VIBNSidebarProps) {
key={s} key={s}
icon={SURFACE_ICONS[s] ?? "◌"} icon={SURFACE_ICONS[s] ?? "◌"}
label={SURFACE_LABELS[s] ?? s} label={SURFACE_LABELS[s] ?? s}
href={`${base}/design`} href={`${base}/design?surface=${encodeURIComponent(s)}`}
collapsed={collapsed} collapsed={collapsed}
/> />
)) ))
) : ( ) : (
<SectionRow icon="◌" label="Not configured" dim collapsed={collapsed} /> <SectionRow icon="◌" label="Not configured" dim href={`${base}/design`} collapsed={collapsed} />
)} )}
<SectionDivider /> <SectionDivider />