move project tabs to sidebar, remove top tab bar

Made-with: Cursor
This commit is contained in:
2026-03-08 13:00:54 -07:00
parent fc59333383
commit 231aeb4402
2 changed files with 82 additions and 65 deletions

View File

@@ -1,6 +1,5 @@
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { ReactNode } from "react";
import { VIBNSidebar } from "./vibn-sidebar";
@@ -61,63 +60,22 @@ export function ProjectShell({
<style>{`
@media (max-width: 768px) {
.vibn-left-sidebar { display: none !important; }
.vibn-tab-bar { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.vibn-tab-bar a { padding: 10px 14px !important; font-size: 0.75rem !important; }
.vibn-page-content { padding-bottom: env(safe-area-inset-bottom); }
}
@media (max-width: 480px) {
.vibn-tab-bar a { padding: 10px 10px !important; }
}
`}</style>
<div style={{ display: "flex", height: "100dvh", background: "#f6f4f0", overflow: "hidden" }}>
{/* Left sidebar */}
{/* Left sidebar — includes project tabs */}
<div className="vibn-left-sidebar" style={{ display: "flex" }}>
<VIBNSidebar workspace={workspace} />
<VIBNSidebar workspace={workspace} tabs={TABS} activeTab={activeTab} />
</div>
{/* Main column — full width */}
<div style={{ flex: 1, display: "flex", flexDirection: "column", minWidth: 0 }}>
{/* Tab bar — sits at the top, no header above it */}
<div className="vibn-tab-bar" style={{
padding: "0 24px",
borderBottom: "1px solid #e8e4dc",
display: "flex",
background: "#fff",
flexShrink: 0,
}}>
{TABS.map(t => (
<Link
key={t.id}
href={`/${workspace}/project/${projectId}/${t.path}`}
style={{
padding: "13px 16px",
fontSize: "0.8rem",
fontWeight: 500,
color: activeTab === t.id ? "#1a1a1a" : "#a09a90",
borderBottom: activeTab === t.id ? "2px solid #1a1a1a" : "2px solid transparent",
transition: "color 0.12s",
fontFamily: "Outfit, sans-serif",
textDecoration: "none",
display: "block",
whiteSpace: "nowrap",
}}
onMouseEnter={e => { if (activeTab !== t.id) (e.currentTarget as HTMLElement).style.color = "#6b6560"; }}
onMouseLeave={e => { if (activeTab !== t.id) (e.currentTarget as HTMLElement).style.color = "#a09a90"; }}
>
{t.label}
</Link>
))}
</div>
{/* Page content — full width, each page manages its own layout */}
<div className="vibn-page-content" style={{ flex: 1, overflow: "auto" }}>
{/* Page content — extends to the very top */}
<div className="vibn-page-content" style={{ flex: 1, overflow: "auto", minWidth: 0 }}>
{children}
</div>
</div>
</div>
<Toaster position="top-center" />
</>

View File

@@ -5,8 +5,16 @@ import Link from "next/link";
import { usePathname } from "next/navigation";
import { signOut, useSession } from "next-auth/react";
interface TabItem {
id: string;
label: string;
path: string;
}
interface VIBNSidebarProps {
workspace: string;
tabs?: TabItem[];
activeTab?: string;
}
interface ProjectData {
@@ -23,7 +31,7 @@ const COLLAPSED_KEY = "vibn_sidebar_collapsed";
const COLLAPSED_W = 52;
const EXPANDED_W = 216;
export function VIBNSidebar({ workspace }: VIBNSidebarProps) {
export function VIBNSidebar({ workspace, tabs, activeTab }: VIBNSidebarProps) {
const pathname = usePathname();
const { data: session } = useSession();
@@ -179,9 +187,10 @@ export function VIBNSidebar({ workspace }: VIBNSidebarProps) {
<div style={{ flex: 1, overflow: "auto", paddingBottom: 8 }}>
{activeProjectId && project ? (
/* ── PROJECT VIEW: name + status only ── */
/* ── PROJECT VIEW: name + status + section tabs ── */
<>
{!collapsed && (
<>
<div style={{ padding: "6px 12px 8px" }}>
<div style={{ fontSize: "0.82rem", fontWeight: 700, color: "#1a1a1a", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
{project.productName || project.name || "Project"}
@@ -198,15 +207,65 @@ export function VIBNSidebar({ workspace }: VIBNSidebarProps) {
</span>
</div>
</div>
{tabs && tabs.length > 0 && (
<div style={{ padding: "2px 8px" }}>
{tabs.map(t => {
const isActive = activeTab === t.id;
return (
<Link
key={t.id}
href={`/${workspace}/project/${activeProjectId}/${t.path}`}
style={{
width: "100%", display: "flex", alignItems: "center",
padding: "7px 10px", borderRadius: 6,
background: isActive ? "#f6f4f0" : "transparent",
color: isActive ? "#1a1a1a" : "#6b6560",
fontSize: "0.8rem", fontWeight: isActive ? 600 : 500,
transition: "background 0.12s", textDecoration: "none",
}}
onMouseEnter={e => { if (!isActive) (e.currentTarget as HTMLElement).style.background = "#f6f4f0"; }}
onMouseLeave={e => { if (!isActive) (e.currentTarget as HTMLElement).style.background = "transparent"; }}
>
{t.label}
</Link>
);
})}
</div>
)}
</>
)}
{collapsed && (
<div style={{ display: "flex", justifyContent: "center", paddingTop: 8 }}>
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", paddingTop: 8, gap: 6 }}>
<span style={{
width: 7, height: 7, borderRadius: "50%", display: "inline-block",
background: project.status === "live" ? "#2e7d32"
: project.status === "building" ? "#3d5afe"
: "#d4a04a",
}} title={project.productName || project.name} />
{tabs && tabs.map(t => {
const isActive = activeTab === t.id;
return (
<Link
key={t.id}
href={`/${workspace}/project/${activeProjectId}/${t.path}`}
title={t.label}
style={{
width: 28, height: 28, borderRadius: 6, display: "flex",
alignItems: "center", justifyContent: "center",
background: isActive ? "#f6f4f0" : "transparent",
color: isActive ? "#1a1a1a" : "#a09a90",
fontSize: "0.6rem", fontWeight: 700, textDecoration: "none",
textTransform: "uppercase", letterSpacing: "0.02em",
transition: "background 0.12s",
}}
onMouseEnter={e => { if (!isActive) (e.currentTarget as HTMLElement).style.background = "#f6f4f0"; }}
onMouseLeave={e => { if (!isActive) (e.currentTarget as HTMLElement).style.background = "transparent"; }}
>
{t.label.slice(0, 2)}
</Link>
);
})}
</div>
)}
</>