UNPKG

@carbon/react

Version:

React components for the Carbon Design System

786 lines (784 loc) 32 kB
/** * 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;