@carbon/react
Version:
React components for the Carbon Design System
786 lines (784 loc) • 32 kB
JavaScript
/**
* Copyright IBM Corp. 2016, 2026
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
const require_runtime = require("../../_virtual/_rolldown/runtime.js");
const require_usePrefix = require("../../internal/usePrefix.js");
const require_Text = require("../Text/Text.js");
const require_keys = require("../../internal/keyboard/keys.js");
const require_match = require("../../internal/keyboard/match.js");
const require_useIsomorphicEffect = require("../../internal/useIsomorphicEffect.js");
const require_useId = require("../../internal/useId.js");
const require_deprecate = require("../../prop-types/deprecate.js");
const require_utils = require("../../internal/utils.js");
const require_useMergedRefs = require("../../internal/useMergedRefs.js");
const require_useEvent = require("../../internal/useEvent.js");
const require_Tooltip = require("../Tooltip/Tooltip.js");
const require_index = require("../BadgeIndicator/index.js");
const require_useControllableState = require("../../internal/useControllableState.js");
const require_Grid = require("../Grid/Grid.js");
const require_useMatchMedia = require("../../internal/useMatchMedia.js");
const require_usePressable = require("./usePressable.js");
let classnames = require("classnames");
classnames = require_runtime.__toESM(classnames);
let react = require("react");
react = require_runtime.__toESM(react);
let prop_types = require("prop-types");
prop_types = require_runtime.__toESM(prop_types);
let react_jsx_runtime = require("react/jsx-runtime");
let _carbon_icons_react = require("@carbon/icons-react");
let es_toolkit_compat = require("es-toolkit/compat");
let _carbon_layout = require("@carbon/layout");
//#region src/components/Tabs/Tabs.tsx
/**
* Copyright IBM Corp. 2016, 2026
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
const buttonWidth = 44;
const verticalTabHeight = 64;
const TabsContext = react.default.createContext({
baseId: "",
activeIndex: 0,
defaultSelectedIndex: 0,
dismissable: false,
onTabCloseRequest() {},
setActiveIndex() {},
selectedIndex: 0,
setSelectedIndex() {}
});
const TabContext = react.default.createContext({
index: 0,
hasSecondaryLabel: false
});
const lgMediaQuery = `(min-width: ${_carbon_layout.breakpoints.lg.width})`;
const smMediaQuery = `(max-width: ${_carbon_layout.breakpoints.md.width})`;
const TabPanelContext = react.default.createContext(0);
function Tabs({ children, defaultSelectedIndex = 0, onChange, selectedIndex: controlledSelectedIndex, dismissable, onTabCloseRequest }) {
const baseId = require_useId.useId("ccs");
if (dismissable && !onTabCloseRequest) console.error("dismissable property specified without also providing an onTabCloseRequest property.");
const [activeIndex, setActiveIndex] = (0, react.useState)(defaultSelectedIndex);
const [selectedIndex, setSelectedIndex] = require_useControllableState.useControllableState({
value: controlledSelectedIndex,
defaultValue: defaultSelectedIndex,
onChange: (value) => onChange?.({ selectedIndex: value })
});
const value = {
baseId,
activeIndex,
defaultSelectedIndex,
dismissable,
onTabCloseRequest,
setActiveIndex,
selectedIndex,
setSelectedIndex
};
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TabsContext.Provider, {
value,
children
});
}
Tabs.propTypes = {
children: prop_types.default.node,
defaultSelectedIndex: prop_types.default.number,
dismissable: prop_types.default.bool,
onChange: prop_types.default.func,
onTabCloseRequest: (props) => {
if (props.dismissable && !props.onTabCloseRequest) return /* @__PURE__ */ new Error("dismissable property specified without also providing an onTabCloseRequest property.");
},
selectedIndex: prop_types.default.number
};
function TabsVertical({ children, height, defaultSelectedIndex = 0, onChange, selectedIndex: controlledSelectedIndex, ...rest }) {
const [selectedIndex, setSelectedIndex] = require_useControllableState.useControllableState({
value: controlledSelectedIndex,
defaultValue: defaultSelectedIndex,
onChange: (value) => onChange?.({ selectedIndex: value })
});
const props = {
...rest,
selectedIndex,
onChange: ({ selectedIndex }) => setSelectedIndex(selectedIndex)
};
if (!require_useMatchMedia.useMatchMedia(smMediaQuery)) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_Grid.GridAsGridComponent, {
style: { height },
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Tabs, {
...props,
children
})
});
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Tabs, {
...props,
children
});
}
TabsVertical.propTypes = {
children: prop_types.default.node,
defaultSelectedIndex: prop_types.default.number,
height: prop_types.default.string,
onChange: prop_types.default.func,
selectedIndex: prop_types.default.number
};
/**
* Get the next index for a given keyboard event
* given a count of the total items and the current index
*/
function getNextIndex(event, total, index) {
switch (true) {
case require_match.match(event, require_keys.ArrowRight): return (index + 1) % total;
case require_match.match(event, require_keys.ArrowLeft): return (total + index - 1) % total;
case require_match.match(event, require_keys.Home): return 0;
case require_match.match(event, require_keys.End): return total - 1;
default: return index;
}
}
/**
* Get the next index for a given keyboard event
* given a count of the total items and the current index
*/
function getNextIndexVertical(event, total, index) {
switch (true) {
case require_match.match(event, require_keys.ArrowDown): return (index + 1) % total;
case require_match.match(event, require_keys.ArrowUp): return (total + index - 1) % total;
case require_match.match(event, require_keys.Home): return 0;
case require_match.match(event, require_keys.End): return total - 1;
default: return index;
}
}
function TabList({ activation = "automatic", "aria-label": label, children, className: customClassName, contained = false, fullWidth = false, iconSize, leftOverflowButtonProps, light, rightOverflowButtonProps, scrollDebounceWait = 200, scrollIntoView, ...rest }) {
const { activeIndex, selectedIndex, setSelectedIndex, setActiveIndex, dismissable } = react.default.useContext(TabsContext);
const prefix = require_usePrefix.usePrefix();
const ref = (0, react.useRef)(null);
const previousButton = (0, react.useRef)(null);
const nextButton = (0, react.useRef)(null);
const [isScrollable, setIsScrollable] = (0, react.useState)(false);
const [scrollLeft, setScrollLeft] = (0, react.useState)(0);
const hasSecondaryLabelTabs = contained && react.Children.toArray(children).some((child) => require_utils.isComponentElement(child, Tab) && typeof child.props.secondaryLabel !== "undefined");
const isLg = require_useMatchMedia.useMatchMedia(lgMediaQuery);
const distributeWidth = fullWidth && contained && isLg && react.default.Children.toArray(children).length < 9;
const className = (0, classnames.default)(`${prefix}--tabs`, {
[`${prefix}--tabs--contained`]: contained,
[`${prefix}--tabs--light`]: light,
[`${prefix}--tabs__icon--default`]: iconSize === "default",
[`${prefix}--tabs__icon--lg`]: iconSize === "lg",
[`${prefix}--layout--size-lg`]: iconSize === "lg",
[`${prefix}--tabs--tall`]: hasSecondaryLabelTabs,
[`${prefix}--tabs--full-width`]: distributeWidth,
[`${prefix}--tabs--dismissable`]: dismissable
}, customClassName);
const [isNextButtonVisible, setIsNextButtonVisible] = (0, react.useState)(ref.current ? scrollLeft + buttonWidth + ref.current.clientWidth < ref.current.scrollWidth : false);
const isPreviousButtonVisible = ref.current ? isScrollable && scrollLeft > 0 : false;
const previousButtonClasses = (0, classnames.default)(`${prefix}--tab--overflow-nav-button`, `${prefix}--tab--overflow-nav-button--previous`, { [`${prefix}--tab--overflow-nav-button--hidden`]: !isPreviousButtonVisible });
const nextButtonClasses = (0, classnames.default)(`${prefix}--tab--overflow-nav-button`, `${prefix}--tab--overflow-nav-button--next`, { [`${prefix}--tab--overflow-nav-button--hidden`]: !isNextButtonVisible });
const tabs = (0, react.useRef)([]);
const debouncedOnScroll = (0, react.useCallback)(() => {
(0, es_toolkit_compat.debounce)(() => {
if (ref.current) setScrollLeft(ref.current.scrollLeft);
}, scrollDebounceWait)();
}, [scrollDebounceWait]);
function onKeyDown(event) {
if (require_match.matches(event, [
require_keys.ArrowRight,
require_keys.ArrowLeft,
require_keys.Home,
require_keys.End
])) {
event.preventDefault();
const activeTabs = tabs.current.filter((tab) => tab !== null).filter((tab) => !tab.disabled);
const currentIndex = activeTabs.indexOf(tabs.current[activation === "automatic" ? selectedIndex : activeIndex]);
const nextIndex = tabs.current.indexOf(activeTabs[getNextIndex(event, activeTabs.length, currentIndex)]);
if (activation === "automatic") setSelectedIndex(nextIndex);
else if (activation === "manual") setActiveIndex(nextIndex);
tabs.current[nextIndex]?.focus();
}
}
function handleBlur({ relatedTarget: currentActiveNode }) {
if (ref.current?.contains(currentActiveNode)) return;
if (activation === "manual") setActiveIndex(selectedIndex);
}
/**
* Scroll the tab into view if it is not already visible
* @param tab - The tab to scroll into view
* @returns {void}
*/
function scrollTabIntoView(tab) {
if (!isScrollable || !ref.current) return;
if (tab) {
const { width: tabWidth } = tab.getBoundingClientRect();
const start = tab.offsetLeft;
const end = tab.offsetLeft + tabWidth;
const visibleStart = ref.current.scrollLeft + buttonWidth;
const visibleEnd = ref.current.scrollLeft + ref.current.clientWidth - buttonWidth;
if (start < visibleStart) setScrollLeft(start - buttonWidth);
if (end > visibleEnd) setScrollLeft(end + buttonWidth - ref.current.clientWidth);
}
}
(0, react.useEffect)(() => {
const tab = tabs.current[selectedIndex];
if (scrollIntoView && tab) tab.scrollIntoView({
block: "nearest",
inline: "nearest"
});
}, []);
(0, react.useEffect)(() => {
setIsNextButtonVisible(ref.current ? scrollLeft + buttonWidth + ref.current.clientWidth + 1 < ref.current.scrollWidth : false);
if (dismissable && ref.current) setIsScrollable(ref.current.scrollWidth > ref.current.clientWidth + 1);
}, [
children,
dismissable,
scrollLeft
]);
(0, react.useEffect)(() => {
if (tabs.current[selectedIndex]?.disabled) {
const activeTabs = tabs.current.filter((tab) => {
return !tab.disabled;
});
if (activeTabs.length > 0) {
const tab = activeTabs[0];
setSelectedIndex(tabs.current.indexOf(tab));
}
}
}, []);
require_useIsomorphicEffect.default(() => {
if (ref.current) setIsScrollable(ref.current.scrollWidth > ref.current.clientWidth + 1);
function handler() {
if (ref.current) setIsScrollable(ref.current.scrollWidth > ref.current.clientWidth + 1);
}
const debouncedHandler = (0, es_toolkit_compat.debounce)(handler, 200);
window.addEventListener("resize", debouncedHandler);
return () => {
debouncedHandler.cancel();
window.removeEventListener("resize", debouncedHandler);
};
}, []);
require_useIsomorphicEffect.default(() => {
if (scrollLeft !== null && ref.current) ref.current.scrollLeft = scrollLeft;
}, [scrollLeft]);
require_useIsomorphicEffect.default(() => {
scrollTabIntoView(activation === "manual" ? tabs.current[activeIndex] : tabs.current[selectedIndex]);
}, [activation, activeIndex]);
require_useIsomorphicEffect.default(() => {
const tab = tabs.current[selectedIndex];
scrollTabIntoView(tab);
}, [
selectedIndex,
isScrollable,
children
]);
require_usePressable.usePressable(previousButton, {
onPress({ longPress }) {
if (!longPress && ref.current) setScrollLeft(Math.max(scrollLeft - ref.current.scrollWidth / tabs.current.length * 1.5, 0));
},
onLongPress() {
return createLongPressBehavior(ref, "backward", setScrollLeft);
}
});
require_usePressable.usePressable(nextButton, {
onPress({ longPress }) {
if (!longPress && ref.current) setScrollLeft(Math.min(scrollLeft + ref.current.scrollWidth / tabs.current.length * 1.5, ref.current.scrollWidth - ref.current.clientWidth));
},
onLongPress() {
return createLongPressBehavior(ref, "forward", setScrollLeft);
}
});
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
className,
children: [
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
"aria-hidden": "true",
tabIndex: -1,
"aria-label": "Scroll left",
ref: previousButton,
className: previousButtonClasses,
type: "button",
...leftOverflowButtonProps,
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_carbon_icons_react.ChevronLeft, {})
}),
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
...rest,
"aria-label": label,
ref,
role: "tablist",
className: `${prefix}--tab--list`,
onScroll: debouncedOnScroll,
onKeyDown,
onBlur: handleBlur,
children: react.Children.map(children, (child, index) => {
return !(0, react.isValidElement)(child) ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TabContext.Provider, {
value: {
index,
hasSecondaryLabel: hasSecondaryLabelTabs,
contained
},
children: (0, react.cloneElement)(child, { ref: (node) => {
if (!node) return;
tabs.current[index] = node;
} })
});
})
}),
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
"aria-hidden": "true",
tabIndex: -1,
"aria-label": "Scroll right",
ref: nextButton,
className: nextButtonClasses,
type: "button",
...rightOverflowButtonProps,
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_carbon_icons_react.ChevronRight, {})
})
]
});
}
TabList.propTypes = {
activation: prop_types.default.oneOf(["automatic", "manual"]),
"aria-label": prop_types.default.string,
children: prop_types.default.node,
className: prop_types.default.string,
contained: prop_types.default.bool,
fullWidth: prop_types.default.bool,
iconSize: prop_types.default.oneOf(["default", "lg"]),
leftOverflowButtonProps: prop_types.default.object,
light: require_deprecate.deprecate(prop_types.default.bool, "The `light` prop for `TabList` has been deprecated in favor of the new `Layer` component. It will be removed in the next major release."),
rightOverflowButtonProps: prop_types.default.object,
scrollDebounceWait: prop_types.default.number,
scrollIntoView: prop_types.default.bool
};
function TabListVertical({ activation = "automatic", "aria-label": label, children, className: customClassName, scrollIntoView, ...rest }) {
const { activeIndex, selectedIndex, setSelectedIndex, setActiveIndex } = react.default.useContext(TabsContext);
const prefix = require_usePrefix.usePrefix();
const ref = (0, react.useRef)(null);
const [isOverflowingBottom, setIsOverflowingBottom] = (0, react.useState)(false);
const [isOverflowingTop, setIsOverflowingTop] = (0, react.useState)(false);
const isSm = require_useMatchMedia.useMatchMedia(smMediaQuery);
const className = (0, classnames.default)(`${prefix}--tabs`, `${prefix}--tabs--vertical`, `${prefix}--tabs--contained`, customClassName);
const tabs = (0, react.useRef)([]);
function onKeyDown(event) {
if (require_match.matches(event, [
require_keys.ArrowDown,
require_keys.ArrowUp,
require_keys.Home,
require_keys.End
])) {
event.preventDefault();
const activeTabs = tabs.current.filter((tab) => tab !== null).filter((tab) => !tab.disabled);
const currentIndex = activeTabs.indexOf(tabs.current[activation === "automatic" ? selectedIndex : activeIndex]);
const nextIndex = tabs.current.indexOf(activeTabs[getNextIndexVertical(event, activeTabs.length, currentIndex)]);
if (activation === "automatic") setSelectedIndex(nextIndex);
else if (activation === "manual") setActiveIndex(nextIndex);
tabs.current[nextIndex]?.focus();
}
}
function handleBlur({ relatedTarget: currentActiveNode }) {
if (ref.current?.contains(currentActiveNode)) return;
if (activation === "manual") setActiveIndex(selectedIndex);
}
(0, react.useEffect)(() => {
if (tabs.current[selectedIndex]?.disabled) {
const activeTabs = tabs.current.filter((tab) => {
return !tab.disabled;
});
if (activeTabs.length > 0) {
const tab = activeTabs[0];
setSelectedIndex(tabs.current.indexOf(tab));
}
}
}, []);
(0, react.useEffect)(() => {
function handler() {
const containerHeight = ref.current?.offsetHeight;
const containerTop = ref.current?.getBoundingClientRect().top;
const selectedPositionTop = tabs.current[selectedIndex]?.getBoundingClientRect().top;
const halfTabHeight = verticalTabHeight / 2;
if (containerTop && containerHeight) {
if (selectedPositionTop - halfTabHeight < containerTop || selectedPositionTop - containerTop + verticalTabHeight + halfTabHeight > containerHeight) ref.current?.scrollTo({
top: (selectedIndex - 1) * verticalTabHeight,
behavior: "smooth"
});
}
}
window.addEventListener("resize", handler);
handler();
return () => {
window.removeEventListener("resize", handler);
};
}, [selectedIndex, scrollIntoView]);
(0, react.useEffect)(() => {
const element = ref.current;
if (!element) return;
const handler = () => {
const halfTabHeight = verticalTabHeight / 2;
setIsOverflowingBottom(element.scrollTop + element.clientHeight + halfTabHeight <= element.scrollHeight);
setIsOverflowingTop(element.scrollTop > halfTabHeight);
};
const resizeObserver = new ResizeObserver(() => handler());
resizeObserver.observe(element);
element.addEventListener("scroll", handler);
return () => {
resizeObserver.disconnect();
element.removeEventListener("scroll", handler);
};
});
if (isSm) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TabList, {
...rest,
"aria-label": label,
contained: true,
children
});
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
className,
children: [
isOverflowingTop && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: `${prefix}--tab--list-gradient_top` }),
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
...rest,
"aria-label": label,
ref,
role: "tablist",
className: `${prefix}--tab--list`,
onKeyDown,
onBlur: handleBlur,
children: react.Children.map(children, (child, index) => {
return !(0, react.isValidElement)(child) ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TabContext.Provider, {
value: {
index,
hasSecondaryLabel: false
},
children: (0, react.cloneElement)(child, { ref: (node) => {
if (!node) return;
tabs.current[index] = node;
} })
});
})
}),
isOverflowingBottom && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: `${prefix}--tab--list-gradient_bottom` })
]
});
}
TabListVertical.propTypes = {
activation: prop_types.default.oneOf(["automatic", "manual"]),
"aria-label": prop_types.default.string,
children: prop_types.default.node,
className: prop_types.default.string
};
/**
* Helper function to set up the behavior when a button is "long pressed".
* This function will take a ref to the tablist, a direction, and a setter
* for scrollLeft and will update the scroll position within a requestAnimationFrame.
*
* It returns a cleanup function to be run
* when the long press is deactivated
*/
function createLongPressBehavior(ref, direction, setScrollLeft) {
const node = ref.current;
if (!node) return () => {};
const defaultScrollBehavior = node?.style["scroll-behavior"];
node.style["scroll-behavior"] = "auto";
const scrollDelta = direction === "forward" ? 5 : -5;
let frameId = null;
function tick() {
if (!node) return;
node.scrollLeft = node.scrollLeft + scrollDelta;
frameId = requestAnimationFrame(tick);
}
frameId = requestAnimationFrame(tick);
return () => {
node.style["scroll-behavior"] = defaultScrollBehavior;
setScrollLeft(node.scrollLeft);
if (frameId) cancelAnimationFrame(frameId);
};
}
const Tab = (0, react.forwardRef)(({ as = "button", children, className: customClassName, disabled, onClick, onKeyDown, secondaryLabel, renderIcon: Icon, ...rest }, forwardRef) => {
const prefix = require_usePrefix.usePrefix();
const { selectedIndex, setSelectedIndex, baseId, dismissable, onTabCloseRequest } = react.default.useContext(TabsContext);
const { index, hasSecondaryLabel, contained } = react.default.useContext(TabContext);
const { badgeIndicator } = react.default.useContext(IconTabContext) || {};
const dismissIconRef = (0, react.useRef)(null);
const tabRef = (0, react.useRef)(null);
const ref = require_useMergedRefs.useMergedRefs([forwardRef, tabRef]);
const [ignoreHover, setIgnoreHover] = (0, react.useState)(false);
const id = `${baseId}-tab-${index}`;
const panelId = `${baseId}-tabpanel-${index}`;
const [isEllipsisApplied, setIsEllipsisApplied] = (0, react.useState)(false);
const isEllipsisActive = (element) => {
setIsEllipsisApplied(element.offsetHeight < element.scrollHeight);
return element.offsetHeight < element.scrollHeight;
};
const className = (0, classnames.default)(`${prefix}--tabs__nav-item`, `${prefix}--tabs__nav-link`, {
[`${prefix}--tabs__nav-item--selected`]: selectedIndex === index,
[`${prefix}--tabs__nav-item--disabled`]: disabled,
[`${prefix}--tabs__nav-item--hover-off`]: ignoreHover
}, customClassName);
const BaseComponent = as;
const onDismissIconMouseEnter = (evt) => {
if (contained && tabRef.current) {
evt.stopPropagation();
setIgnoreHover(true);
tabRef.current.classList.add(`${prefix}--tabs__nav-item--hover-off`);
}
};
const onDismissIconMouseLeave = () => {
if (contained && tabRef.current) {
tabRef.current.classList.remove(`${prefix}--tabs__nav-item--hover-off`);
setIgnoreHover(false);
}
};
require_useEvent.useEvent(dismissIconRef, "mouseover", onDismissIconMouseEnter);
require_useEvent.useEvent(dismissIconRef, "mouseleave", onDismissIconMouseLeave);
require_useIsomorphicEffect.default(() => {
function handler() {
const elementTabId = document.getElementById(`${id}`) || tabRef.current;
if (elementTabId?.closest(`.${prefix}--tabs--vertical`)) {
const newElement = elementTabId?.getElementsByClassName(`${prefix}--tabs__nav-item-label`)[0];
isEllipsisActive(newElement);
}
}
handler();
window.addEventListener("resize", handler);
return () => {
window.removeEventListener("resize", handler);
};
}, [prefix, id]);
const handleClose = (evt) => {
evt.stopPropagation();
onTabCloseRequest?.(index);
if (tabRef.current && tabRef.current.parentElement) {
const tabCount = Array.from(tabRef.current.parentElement.childNodes).filter((node) => {
if (!(node instanceof HTMLElement)) return false;
return node.classList.contains(`${prefix}--tabs__nav-link`) && !node.classList.contains(`${prefix}--tabs__nav-item--disabled`);
}).length;
if (tabRef.current && index + 1 !== tabCount) tabRef.current.focus();
else {
const prevTabIndex = (tabCount - 2) * 2;
const previousTab = tabRef.current.parentElement.childNodes[prevTabIndex];
if (previousTab instanceof HTMLElement) previousTab.focus();
}
}
};
const handleKeyDown = (event) => {
if (dismissable && require_match.match(event, require_keys.Delete)) handleClose(event);
onKeyDown?.(event);
};
const DismissIcon = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
className: (0, classnames.default)({
[`${prefix}--tabs__nav-item--close`]: dismissable,
[`${prefix}--tabs__nav-item--close--hidden`]: !dismissable
}),
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
type: "button",
tabIndex: -1,
"aria-disabled": disabled,
"aria-hidden": selectedIndex === index && dismissable ? "false" : "true",
disabled,
className: (0, classnames.default)({
[`${prefix}--tabs__nav-item--close-icon`]: dismissable,
[`${prefix}--visually-hidden`]: !dismissable,
[`${prefix}--tabs__nav-item--close-icon--selected`]: selectedIndex === index,
[`${prefix}--tabs__nav-item--close-icon--disabled`]: disabled
}),
onClick: handleClose,
title: `Remove ${typeof children === "string" ? children : ""} tab`,
ref: dismissIconRef,
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_carbon_icons_react.Close, {
"aria-hidden": selectedIndex === index && dismissable ? "false" : "true",
"aria-label": `Press delete to remove ${typeof children === "string" ? children : ""} tab`
})
})
});
const hasIcon = Icon ?? dismissable;
if (isEllipsisApplied) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_Tooltip.Tooltip, {
label: children,
align: "top",
leaveDelayMs: 0,
autoAlign: true,
onMouseEnter: () => false,
closeOnActivation: true,
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(BaseComponent, {
...rest,
"aria-controls": panelId,
"aria-disabled": disabled,
"aria-selected": selectedIndex === index,
ref,
id,
role: "tab",
className,
disabled,
title: children,
onClick: (evt) => {
if (disabled) return;
setSelectedIndex(index);
onClick?.(evt);
},
onKeyDown: handleKeyDown,
tabIndex: selectedIndex === index ? "0" : "-1",
type: "button",
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
className: `${prefix}--tabs__nav-item-label-wrapper`,
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_Text.Text, {
className: `${prefix}--tabs__nav-item-label`,
children
})
}), hasSecondaryLabel && secondaryLabel && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_Text.Text, {
as: "div",
className: `${prefix}--tabs__nav-item-secondary-label`,
title: secondaryLabel,
children: secondaryLabel
})]
})
});
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(BaseComponent, {
...rest,
"aria-controls": panelId,
"aria-disabled": disabled,
"aria-selected": selectedIndex === index,
ref,
id,
role: "tab",
className,
disabled,
onClick: (evt) => {
if (disabled) return;
setSelectedIndex(index);
onClick?.(evt);
},
onKeyDown: handleKeyDown,
tabIndex: selectedIndex === index ? "0" : "-1",
type: "button",
children: [
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
className: `${prefix}--tabs__nav-item-label-wrapper`,
children: [
dismissable && Icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
className: `${prefix}--tabs__nav-item--icon-left`,
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { size: 16 })
}),
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_Text.Text, {
className: `${prefix}--tabs__nav-item-label`,
children
}),
!dismissable && Icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
className: (0, classnames.default)(`${prefix}--tabs__nav-item--icon`, { [`${prefix}--visually-hidden`]: !hasIcon }),
children: !dismissable && Icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { size: 16 })
})
]
}),
hasSecondaryLabel && secondaryLabel && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_Text.Text, {
as: "div",
className: `${prefix}--tabs__nav-item-secondary-label`,
title: secondaryLabel,
children: secondaryLabel
}),
!disabled && badgeIndicator && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_index.default, {})
]
}), DismissIcon] });
});
Tab.propTypes = {
as: prop_types.default.oneOfType([prop_types.default.string, prop_types.default.elementType]),
children: prop_types.default.node,
className: prop_types.default.string,
disabled: prop_types.default.bool,
onClick: prop_types.default.func,
onKeyDown: prop_types.default.func,
renderButton: prop_types.default.func,
renderIcon: prop_types.default.oneOfType([prop_types.default.func, prop_types.default.object]),
secondaryLabel: prop_types.default.string
};
/**
* IconTab
*/
const IconTabContext = (0, react.createContext)(false);
const IconTab = react.default.forwardRef(({ badgeIndicator, children, className: customClassName, defaultOpen = false, enterDelayMs, leaveDelayMs, label, ...rest }, ref) => {
const prefix = require_usePrefix.usePrefix();
const value = (0, react.useMemo)(() => ({ badgeIndicator }), [badgeIndicator]);
const hasSize20 = (0, react.isValidElement)(children) && (children.props.size === 20 || children.props.size === "20");
const classNames = (0, classnames.default)(`${prefix}--tabs__nav-item--icon-only`, customClassName, { [`${prefix}--tabs__nav-item--icon-only__20`]: hasSize20 });
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconTabContext.Provider, {
value,
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_Tooltip.Tooltip, {
align: "bottom",
defaultOpen,
className: `${prefix}--icon-tooltip`,
enterDelayMs,
label,
leaveDelayMs,
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Tab, {
className: classNames,
ref,
...rest,
children
})
})
});
});
IconTab.propTypes = {
badgeIndicator: prop_types.default.bool,
children: prop_types.default.node,
className: prop_types.default.string,
defaultOpen: prop_types.default.bool,
enterDelayMs: prop_types.default.number,
label: prop_types.default.node.isRequired,
leaveDelayMs: prop_types.default.number
};
const TabPanel = react.default.forwardRef(({ children, className: customClassName, ...rest }, forwardRef) => {
const prefix = require_usePrefix.usePrefix();
const { selectedIndex, baseId } = react.default.useContext(TabsContext);
const index = react.default.useContext(TabPanelContext);
const id = `${baseId}-tabpanel-${index}`;
const tabId = `${baseId}-tab-${index}`;
const className = (0, classnames.default)(`${prefix}--tab-content`, customClassName);
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
...rest,
"aria-labelledby": tabId,
id,
className,
ref: forwardRef,
role: "tabpanel",
hidden: selectedIndex !== index,
children
});
});
TabPanel.propTypes = {
children: prop_types.default.node,
className: prop_types.default.string
};
function TabPanels({ children }) {
const prefix = require_usePrefix.usePrefix();
const refs = (0, react.useRef)([]);
const hiddenStates = (0, react.useRef)([]);
require_useIsomorphicEffect.default(() => {
const tabContainer = refs.current[0]?.previousElementSibling;
const isVertical = tabContainer?.classList.contains(`${prefix}--tabs--vertical`);
const parentHasHeight = tabContainer?.parentElement?.style.height;
if (isVertical && !parentHasHeight) {
hiddenStates.current = refs.current.map((ref) => ref?.hidden || false);
refs.current.forEach((ref) => {
if (ref) ref.hidden = false;
});
const heights = refs.current.map((ref) => ref?.offsetHeight || 0);
const max = Math.max(...heights);
if (tabContainer instanceof HTMLElement) tabContainer.style.height = max + "px";
refs.current.forEach((ref, index) => {
if (ref) ref.hidden = hiddenStates.current[index];
});
}
});
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: react.Children.map(children, (child, index) => {
return !(0, react.isValidElement)(child) ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TabPanelContext.Provider, {
value: index,
children: (0, react.cloneElement)(child, { ref: (element) => {
refs.current[index] = element;
} })
});
}) });
}
TabPanels.propTypes = { children: prop_types.default.node };
//#endregion
exports.IconTab = IconTab;
exports.Tab = Tab;
exports.TabList = TabList;
exports.TabListVertical = TabListVertical;
exports.TabPanel = TabPanel;
exports.TabPanels = TabPanels;
exports.Tabs = Tabs;
exports.TabsVertical = TabsVertical;