move project tabs to sidebar, remove top tab bar
Made-with: Cursor
This commit is contained in:
@@ -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" />
|
||||
</>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user