I'm working on a React application, and I need to implement tab navigation similar to SAPUI5's IconTabBar component with an overflow behavior.
In the SAPUI5 example (link below), when there are too many tabs to fit in the available space, the excess tabs are moved to a dropdown/select list.
Example Link: SAPUI5 IconTabBar Overflow Example
How can I implement tabs that will show in a row, and if there are too many to fit, an overflow menu (select list) will appear to allow users to select the remaining tabs.
Are there any existing libraries or components I can use that implement this behavior easily?
I found one UI library which has the functionality close to what you need. Check it here: React Fluent UI.
But if you are already using other UI frameworks, you can easily implement this with simple logic. I've shown an example below:
To do this you need to measure the container width and display the tabs that could only be fit into the container and add the remaining tabs to the dropdown.
This works well if you have equal width tabs. If your tabs are varying width then you need to measure each tab's width and only add them if they can be displayed without overflowing.
Here is a draft implementation of this:
import { useState, useRef, useLayoutEffect } from "react";
import Dropdown from "react-dropdown";
export default function Tabs({ tabs }) {
const [activeTab, setActiveTab] = useState(tabs[0]);
const [dropdownTabs, setDropDownTabs] = useState([]);
const [displayedTabs, setDisplayedTabs] = useState([]);
const tabsRef = useRef(null);
useLayoutEffect(() => {
const handleResize = () => {
let divWidth = tabsRef.current.offsetWidth;
let count = Math.floor(divWidth / 65) - 1;
let newDisplayedTabs = tabs.slice(0, count);
let newDropDownTabs = tabs.slice(count);
if (newDropDownTabs.includes(activeTab)) {
newDropDownTabs = newDropDownTabs.filter((tab) => tab !== activeTab);
newDropDownTabs.unshift(newDisplayedTabs[newDisplayedTabs.length - 1]);
delete newDisplayedTabs[newDisplayedTabs.length - 1];
newDisplayedTabs.push(activeTab);
}
setDisplayedTabs(newDisplayedTabs);
setDropDownTabs(newDropDownTabs);
};
handleResize();
window.addEventListener("resize", handleResize);
}, [tabs, activeTab]);
const tabsList = displayedTabs?.map((tab) => (
<div
className={"tab " + (tab === activeTab ? "active" : "")}
onClick={() => setActiveTab(tab)}
>
{tab}
</div>
));
return (
<>
<div className="tabs-list" ref={tabsRef}>
{tabsList}
<Dropdown
options={dropdownTabs}
onChange={(e) => setActiveTab(e.value)}
value={dropdownTabs[0]}
placeholder="More"
arrowClosed={<img src="/dropdown-arrow.svg" />}
arrowOpen={<img src="/dropdown-arrow.svg" />}
/>
</div>
</>
);
}
Note: In the above code, I've also taken care of window resize event.
And on resizing the screen it looks like this: