| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148 |
- "use client";
- import type { DragEndEvent } from "@dnd-kit/core";
- import {
- closestCenter,
- DndContext,
- KeyboardSensor,
- PointerSensor,
- useSensor,
- useSensors,
- } from "@dnd-kit/core";
- import {
- horizontalListSortingStrategy,
- SortableContext,
- sortableKeyboardCoordinates,
- } from "@dnd-kit/sortable";
- import { theme } from "antd";
- import { usePathname } from "next/navigation";
- import type React from "react";
- import { useEffect, useRef } from "react";
- import { useMenuStore } from "@/stores/menuStore";
- import { useTabStore } from "@/stores/tabStore";
- import Tab from "./Tab";
- const TabBar: React.FC = () => {
- const pathname = usePathname();
- const tabs = useTabStore((state) => state.tabs);
- const activeTab = useTabStore((state) => state.activeTab);
- const addTab = useTabStore((state) => state.addTab);
- const setActiveTab = useTabStore((state) => state.setActiveTab);
- const getTabByPath = useTabStore((state) => state.getTabByPath);
- const getBreadcrumb = useMenuStore((state) => state.getBreadcrumb);
- const reorderTabs = useTabStore((state) => state.reorderTabs);
- const {
- token: { colorBorder },
- } = theme.useToken();
- const containerRef = useRef<HTMLDivElement>(null);
- // Setup sensors for drag and drop
- const sensors = useSensors(
- useSensor(PointerSensor, {
- activationConstraint: {
- distance: 8, // 8px movement required before drag starts
- },
- }),
- useSensor(KeyboardSensor, {
- coordinateGetter: sortableKeyboardCoordinates,
- }),
- );
- // Handle drag end
- const handleDragEnd = (event: DragEndEvent) => {
- const { active, over } = event;
- if (over && active.id !== over.id) {
- reorderTabs(active.id as string, over.id as string);
- }
- };
- // Sync tabs with route changes
- useEffect(() => {
- if (!pathname || pathname === "/") return;
- const existingTab = getTabByPath(pathname);
- if (existingTab) {
- // Tab exists, just activate it
- setActiveTab(existingTab.key);
- } else {
- // Create new tab
- const breadcrumb = getBreadcrumb(pathname);
- const label = breadcrumb[breadcrumb.length - 1] || "未命名";
- // Generate key from path
- const key = pathname.replace(/\//g, "-").slice(1) || "home";
- addTab({
- key,
- label,
- path: pathname,
- closable: pathname !== "/home",
- });
- }
- }, [pathname, getTabByPath, setActiveTab, addTab, getBreadcrumb]);
- // Auto scroll to active tab
- useEffect(() => {
- if (!activeTab || !containerRef.current) return;
- const container = containerRef.current;
- const activeElement = container.querySelector(
- ".tab-item.active",
- ) as HTMLElement;
- if (activeElement) {
- const containerRect = container.getBoundingClientRect();
- const elementRect = activeElement.getBoundingClientRect();
- // Check if element is outside viewport
- if (
- elementRect.left < containerRect.left ||
- elementRect.right > containerRect.right
- ) {
- activeElement.scrollIntoView({
- behavior: "smooth",
- block: "nearest",
- inline: "center",
- });
- }
- }
- }, [activeTab]);
- if (tabs.length === 0) {
- return null;
- }
- return (
- <DndContext
- sensors={sensors}
- collisionDetection={closestCenter}
- onDragEnd={handleDragEnd}
- >
- <SortableContext
- items={tabs.map((tab) => tab.key)}
- strategy={horizontalListSortingStrategy}
- >
- <div
- ref={containerRef}
- className="tab-bar flex items-center p-2 px-4"
- style={{
- borderBottom: `1px solid ${colorBorder}`,
- overflowX: "auto",
- overflowY: "hidden",
- whiteSpace: "nowrap",
- scrollBehavior: "smooth",
- }}
- >
- {tabs.map((tab) => (
- <Tab key={tab.key} tab={tab} active={activeTab?.key === tab.key} />
- ))}
- </div>
- </SortableContext>
- </DndContext>
- );
- };
- export default TabBar;
|