move project tabs to sidebar, remove top tab bar
Made-with: Cursor
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
import { VIBNSidebar } from "./vibn-sidebar";
|
import { VIBNSidebar } from "./vibn-sidebar";
|
||||||
@@ -61,61 +60,20 @@ export function ProjectShell({
|
|||||||
<style>{`
|
<style>{`
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.vibn-left-sidebar { display: none !important; }
|
.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); }
|
.vibn-page-content { padding-bottom: env(safe-area-inset-bottom); }
|
||||||
}
|
}
|
||||||
@media (max-width: 480px) {
|
|
||||||
.vibn-tab-bar a { padding: 10px 10px !important; }
|
|
||||||
}
|
|
||||||
`}</style>
|
`}</style>
|
||||||
|
|
||||||
<div style={{ display: "flex", height: "100dvh", background: "#f6f4f0", overflow: "hidden" }}>
|
<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" }}>
|
<div className="vibn-left-sidebar" style={{ display: "flex" }}>
|
||||||
<VIBNSidebar workspace={workspace} />
|
<VIBNSidebar workspace={workspace} tabs={TABS} activeTab={activeTab} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Main column — full width */}
|
{/* Page content — extends to the very top */}
|
||||||
<div style={{ flex: 1, display: "flex", flexDirection: "column", minWidth: 0 }}>
|
<div className="vibn-page-content" style={{ flex: 1, overflow: "auto", minWidth: 0 }}>
|
||||||
|
{children}
|
||||||
{/* 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" }}>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,16 @@ import Link from "next/link";
|
|||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { signOut, useSession } from "next-auth/react";
|
import { signOut, useSession } from "next-auth/react";
|
||||||
|
|
||||||
|
interface TabItem {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface VIBNSidebarProps {
|
interface VIBNSidebarProps {
|
||||||
workspace: string;
|
workspace: string;
|
||||||
|
tabs?: TabItem[];
|
||||||
|
activeTab?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ProjectData {
|
interface ProjectData {
|
||||||
@@ -23,7 +31,7 @@ const COLLAPSED_KEY = "vibn_sidebar_collapsed";
|
|||||||
const COLLAPSED_W = 52;
|
const COLLAPSED_W = 52;
|
||||||
const EXPANDED_W = 216;
|
const EXPANDED_W = 216;
|
||||||
|
|
||||||
export function VIBNSidebar({ workspace }: VIBNSidebarProps) {
|
export function VIBNSidebar({ workspace, tabs, activeTab }: VIBNSidebarProps) {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
|
|
||||||
@@ -179,34 +187,85 @@ export function VIBNSidebar({ workspace }: VIBNSidebarProps) {
|
|||||||
<div style={{ flex: 1, overflow: "auto", paddingBottom: 8 }}>
|
<div style={{ flex: 1, overflow: "auto", paddingBottom: 8 }}>
|
||||||
|
|
||||||
{activeProjectId && project ? (
|
{activeProjectId && project ? (
|
||||||
/* ── PROJECT VIEW: name + status only ── */
|
/* ── PROJECT VIEW: name + status + section tabs ── */
|
||||||
<>
|
<>
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
<div style={{ padding: "6px 12px 8px" }}>
|
<>
|
||||||
<div style={{ fontSize: "0.82rem", fontWeight: 700, color: "#1a1a1a", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
<div style={{ padding: "6px 12px 8px" }}>
|
||||||
{project.productName || project.name || "Project"}
|
<div style={{ fontSize: "0.82rem", fontWeight: 700, color: "#1a1a1a", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
||||||
|
{project.productName || project.name || "Project"}
|
||||||
|
</div>
|
||||||
|
<div style={{ display: "flex", alignItems: "center", gap: 5, marginTop: 3 }}>
|
||||||
|
<span style={{
|
||||||
|
width: 6, height: 6, borderRadius: "50%", flexShrink: 0, display: "inline-block",
|
||||||
|
background: project.status === "live" ? "#2e7d32"
|
||||||
|
: project.status === "building" ? "#3d5afe"
|
||||||
|
: "#d4a04a",
|
||||||
|
}} />
|
||||||
|
<span style={{ fontSize: "0.68rem", color: "#8a8478" }}>
|
||||||
|
{project.status === "live" ? "Live" : project.status === "building" ? "Building" : "Defining"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: "flex", alignItems: "center", gap: 5, marginTop: 3 }}>
|
|
||||||
<span style={{
|
{tabs && tabs.length > 0 && (
|
||||||
width: 6, height: 6, borderRadius: "50%", flexShrink: 0, display: "inline-block",
|
<div style={{ padding: "2px 8px" }}>
|
||||||
background: project.status === "live" ? "#2e7d32"
|
{tabs.map(t => {
|
||||||
: project.status === "building" ? "#3d5afe"
|
const isActive = activeTab === t.id;
|
||||||
: "#d4a04a",
|
return (
|
||||||
}} />
|
<Link
|
||||||
<span style={{ fontSize: "0.68rem", color: "#8a8478" }}>
|
key={t.id}
|
||||||
{project.status === "live" ? "Live" : project.status === "building" ? "Building" : "Defining"}
|
href={`/${workspace}/project/${activeProjectId}/${t.path}`}
|
||||||
</span>
|
style={{
|
||||||
</div>
|
width: "100%", display: "flex", alignItems: "center",
|
||||||
</div>
|
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 && (
|
{collapsed && (
|
||||||
<div style={{ display: "flex", justifyContent: "center", paddingTop: 8 }}>
|
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", paddingTop: 8, gap: 6 }}>
|
||||||
<span style={{
|
<span style={{
|
||||||
width: 7, height: 7, borderRadius: "50%", display: "inline-block",
|
width: 7, height: 7, borderRadius: "50%", display: "inline-block",
|
||||||
background: project.status === "live" ? "#2e7d32"
|
background: project.status === "live" ? "#2e7d32"
|
||||||
: project.status === "building" ? "#3d5afe"
|
: project.status === "building" ? "#3d5afe"
|
||||||
: "#d4a04a",
|
: "#d4a04a",
|
||||||
}} title={project.productName || project.name} />
|
}} 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>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user