@bydata/react-supertabs
v1.2.9
Published
A customizable React super tabs component.
Downloads
726
Readme
SuperTabs
SuperTabs is a production-ready React navigation library for managing parent tabs and nested sub-tabs in complex applications.
It provides a robust tab lifecycle with route integration, privilege-aware module access, and extensible UI controls for module-level workflows.
Highlights
- Parent tab and sub-tab navigation with URL synchronization
- Drag-and-drop tab reordering
- Configurable module actions (
showAddButtonandcustomAddButtons) - Pluggable handlers for open, close, reorder, and fetch operations
1) Setup
Wrap your app with TabProvider.
import { TabProvider } from "components/SuperTabs/TabContext";
import { alertService } from "super-notifier";
<TabProvider
SITE_PREFIX={SITE_PREFIX}
SITE_PAGES={SITE_PAGES}
homeUrl="/app"
getUserDetails={getUser}
hasPrivilege={(privilege) => checkPrivilege(getUser().privileges, privilege)}
getTabs={getTabs}
handleOpenTab={handleOpenTab}
handleCloseTab={handleCloseTab}
handleReorderTabs={handleReorderTabs}
isDefaultExpanded={true}
alertService={alertService}
>
<Main />
</TabProvider>;Render tabs in header:
import SuperTabs from "components/SuperTabs/SuperTabs";
<header>
<SuperTabs />
</header>;2) TabProvider props
Required
SITE_PREFIX: stringSITE_PAGES: Array<SitePage>
SitePage is typically:
{
id: 2,
title: "Recruiter",
url: "/app/recruiter/f-home",
privilege: "RECRUITER",
containsSubTabs: true,
showAddButton: true, // boolean or privilege array
customAddButtons: [
{ id: 1, name: "Data Grid", privilege: "RECRUITER" },
{ id: 2, name: "Trend Master", privilege: "RECRUITER" }
],
showInMobile: true, // optional
isExternal: false // optional
}Optional
homeUrl(default:/)entityIdentifierKey(default:organization_id)persistTabsAfterLogin(default:false)preventHomeRedirect(default:false)- When
true, clicking home opens module menu instead of redirecting.
- When
retainTabsAfterEntityChange(default:false)shouldRedirectToSubTab(default:true)isMobileView(default:false)isDefaultExpanded(default:false)getUserDetails(function)hasPrivilege(function)getTabs(async function)handleOpenTab(async function)handleCloseTab(async function)handleReorderTabs(async function)alertServiceobject with optional methods:success,warning,error,info,alert
3) API callback contracts
getTabs({ payload, controller })
Expected shape:
{
status: 1,
tabs: [], // flat backend tabs with tab_info
current_tab: {} | null, // current tab from backend
hasNoTabs: false // true when backend has no open tabs
}handleOpenTab(payload[, controller])
Expected shape:
{
status: 1,
tabId: 123,
children: [] // optional child tabs
}handleCloseTab(tabId)
Expected shape:
{
status: 1,
current_tab_id: 456 // optional
}handleReorderTabs(payload)
Expected shape:
{
status: 1;
}4) Opening tabs
Option 1: SuperLink
import SuperLink from "components/SuperTabs/SuperLink";
<SuperLink tab={page} isSubTab={false}>
{page.title}
</SuperLink>;Open external links:
<SuperLink tab={{ ...page, isExternal: true }} isSubTab={false} isExternal>
{page.title}
</SuperLink>Option 2: openSuperTabOnRowClick
import { useTabContext } from "components/SuperTabs/TabContext";
const { openSuperTabOnRowClick } = useTabContext();
function handleRowClick(row) {
openSuperTabOnRowClick({
tab: {
id: row.id,
name: row.name,
url: `/app/recruiter/f-${row.id}`,
},
isSubTab: true, // defaults to true
});
}5) Tab object notes
Common fields:
idurl- one of
name/title
Optional:
showAvatar,firstName,lastName,imgSrcorimg_urltooltipfor hover text; falls back to tab title/name when emptyisExternal- any extra metadata your module uses
{
id: 100,
url: "/app/profile/100",
tooltip: "Profile: John Doe",
firstName: "John",
lastName: "Doe",
showAvatar: true,
imgSrc: "https://cdn.example.com/u100.png"
}6) Add button callbacks
Bind module add callbacks using emitter:
import { useCallback, useEffect } from "react";
import { emitter } from "components/SuperTabs/SuperTabs";
import { useTabContext } from "components/SuperTabs/TabContext";
const { openSuperTabOnRowClick } = useTabContext();
const handleTabAddBtn = useCallback(
(customButtonData) => {
// customButtonData is passed when using customAddButtons
openSuperTabOnRowClick({
tab: {
id: 123,
name: "New Funnel",
url: "/app/recruiter/f-123",
},
isSubTab: true,
});
},
[openSuperTabOnRowClick],
);
useEffect(() => {
const unsub = emitter.emit("bindCallback", {
id: 2, // same parent module id from SITE_PAGES
callback: handleTabAddBtn,
});
return unsub;
}, [handleTabAddBtn]);7) Close cleanup event
import { useEffect } from "react";
import { emitter } from "components/SuperTabs/SuperTabs";
useEffect(() => {
const unsub = emitter.subscribe(
"superTabClose",
({ appId, isSubTab, tab }) => {
if (appId === 2 && isSubTab) {
// cleanup here
}
},
);
return unsub;
}, []);8) Not Available page
import NotAvailable from "components/SuperTabs/NotAvailable";
<NotAvailable subTabName="Profile" />;9) Keep-alive (optional)
Use shared keep-alive utility:
import { withKeepAlive } from "components/KeepAlive";
const Component = () => <div>Component</div>;
export default withKeepAlive(Component, 2); // module idCleanup keep-alive cache on parent tab close:
import { useEffect } from "react";
import { useAliveScope } from "components/KeepAlive";
import { emitter } from "components/SuperTabs/SuperTabs";
const { drop } = useAliveScope();
useEffect(() => {
const unsub = emitter.subscribe("superTabClose", ({ appId, isSubTab }) => {
if (appId && !isSubTab) {
drop(appId);
}
});
return unsub;
}, [drop]);10) Styling note
Home tab logo can be styled with .brand-logo.
