74 lines
2.5 KiB
TypeScript
74 lines
2.5 KiB
TypeScript
import { useEffect } from "react";
|
|
import { useLocation, useNavigate } from "react-router";
|
|
import { X } from "lucide-react";
|
|
import { cn } from "@/lib/utils";
|
|
import { useProjectTabsStore } from "@/store/project-tabs-store";
|
|
|
|
export function ProjectTabsBar() {
|
|
const tabs = useProjectTabsStore((state) => state.tabs);
|
|
const activeTabId = useProjectTabsStore((state) => state.activeTabId);
|
|
const activateTab = useProjectTabsStore((state) => state.activateTab);
|
|
const activateHome = useProjectTabsStore((state) => state.activateHome);
|
|
const closeTab = useProjectTabsStore((state) => state.closeTab);
|
|
const location = useLocation();
|
|
const navigate = useNavigate();
|
|
|
|
useEffect(() => {
|
|
const active = tabs.find((tab) => tab.id === activeTabId);
|
|
if (!active) return;
|
|
const targetPath = active.kind === "home" ? "/" : `/editor/${active.projectId}`;
|
|
if (location.pathname === targetPath) return;
|
|
navigate(targetPath);
|
|
}, [activeTabId, tabs, location.pathname, navigate]);
|
|
|
|
function handleSelect(tabId: string, kind: "home" | "project") {
|
|
if (kind === "home") activateHome();
|
|
else activateTab(tabId);
|
|
}
|
|
|
|
function handleClose(tabId: string) {
|
|
closeTab(tabId);
|
|
}
|
|
|
|
return (
|
|
<div className="relative bg-muted px-2 pt-2">
|
|
<div className="flex h-8 items-stretch gap-1 overflow-x-auto">
|
|
{tabs.map((tab) => {
|
|
const isActive = tab.id === activeTabId;
|
|
|
|
return (
|
|
<button
|
|
key={tab.id}
|
|
type="button"
|
|
className={cn(
|
|
"group relative flex min-w-[140px] max-w-[200px] shrink-0 items-center gap-2 rounded-t-md border px-3 text-sm transition border-b-0",
|
|
isActive
|
|
? "border-border bg-background text-foreground z-1"
|
|
: "border-transparent bg-transparent text-muted-foreground hover:border-border/60 hover:bg-background/80 hover:text-foreground",
|
|
)}
|
|
title={tab.label}
|
|
onClick={() => handleSelect(tab.id, tab.kind)}
|
|
>
|
|
<span className="truncate">{tab.label}</span>
|
|
{tab.closable && (
|
|
<button
|
|
type="button"
|
|
className="ml-auto rounded-sm text-muted-foreground/80 transition hover:bg-muted hover:text-foreground"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
handleClose(tab.id);
|
|
}}
|
|
aria-label="关闭标签"
|
|
>
|
|
<X className="size-4" />
|
|
</button>
|
|
)}
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
<span className="block absolute bottom-0 left-0 right-0 h-2 border-b border-border/70"></span>
|
|
</div>
|
|
);
|
|
}
|