84cbf01706
Tabs-Komponente bekommt optionalen tabHrefBuilder(tabId)-Prop. Wenn gesetzt, erscheint neben jedem Tab-Label ein kleines ExternalLink-Icon, das den Tab via ?tab=<id> in einem neuen Browser-Tab öffnet. CustomerDetail übergibt den Builder. URL-Param wird eh schon für den Tab-Sync genutzt – Anhängen reicht. Click-stopPropagation verhindert, dass der Klick auf das Icon gleichzeitig den Tab-Wechsel auslöst.
83 lines
2.4 KiB
TypeScript
83 lines
2.4 KiB
TypeScript
import { ReactNode, useState, useEffect } from 'react';
|
|
import { ExternalLink } from 'lucide-react';
|
|
|
|
interface Tab {
|
|
id: string;
|
|
label: string;
|
|
content: ReactNode;
|
|
}
|
|
|
|
interface TabsProps {
|
|
tabs: Tab[];
|
|
defaultTab?: string;
|
|
activeTab?: string;
|
|
onTabChange?: (tabId: string) => void;
|
|
/**
|
|
* Optional: liefert die URL, unter der ein einzelner Tab in einem
|
|
* neuen Tab geöffnet werden kann. Wenn gesetzt, erscheint neben jedem
|
|
* Tab-Label ein kleines „im neuen Tab öffnen"-Icon.
|
|
*/
|
|
tabHrefBuilder?: (tabId: string) => string;
|
|
}
|
|
|
|
export default function Tabs({
|
|
tabs,
|
|
defaultTab,
|
|
activeTab: controlledTab,
|
|
onTabChange,
|
|
tabHrefBuilder,
|
|
}: TabsProps) {
|
|
const [internalTab, setInternalTab] = useState(defaultTab || tabs[0]?.id);
|
|
const activeTab = controlledTab ?? internalTab;
|
|
|
|
useEffect(() => {
|
|
if (controlledTab !== undefined) {
|
|
setInternalTab(controlledTab);
|
|
}
|
|
}, [controlledTab]);
|
|
|
|
const handleTabChange = (tabId: string) => {
|
|
setInternalTab(tabId);
|
|
onTabChange?.(tabId);
|
|
};
|
|
|
|
return (
|
|
<div>
|
|
<div className="border-b border-gray-200">
|
|
<nav className="flex -mb-px space-x-6">
|
|
{tabs.map((tab) => (
|
|
<div key={tab.id} className="flex items-center gap-1">
|
|
<button
|
|
onClick={() => handleTabChange(tab.id)}
|
|
className={`py-4 px-1 border-b-2 font-medium text-sm whitespace-nowrap ${
|
|
activeTab === tab.id
|
|
? 'border-blue-500 text-blue-600'
|
|
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
|
}`}
|
|
>
|
|
{tab.label}
|
|
</button>
|
|
{tabHrefBuilder && (
|
|
<a
|
|
href={tabHrefBuilder(tab.id)}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
onClick={(e) => e.stopPropagation()}
|
|
className="text-gray-400 hover:text-blue-600 p-0.5"
|
|
title={`${tab.label} in neuem Tab öffnen`}
|
|
aria-label={`${tab.label} in neuem Tab öffnen`}
|
|
>
|
|
<ExternalLink className="w-3.5 h-3.5" />
|
|
</a>
|
|
)}
|
|
</div>
|
|
))}
|
|
</nav>
|
|
</div>
|
|
<div className="mt-4">
|
|
{tabs.find((tab) => tab.id === activeTab)?.content}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|