@base-ui/react
Version:
Base UI is a library of headless ('unstyled') React components and low-level hooks. You gain complete control over your app's CSS and accessibility features.
122 lines (120 loc) • 4.24 kB
JavaScript
'use client';
import * as React from 'react';
import { useStableCallback } from '@base-ui/utils/useStableCallback';
import { EMPTY_ARRAY } from '@base-ui/utils/empty';
import { CompositeRoot } from "../../internals/composite/root/CompositeRoot.js";
import { tabsStateAttributesMapping } from "../root/stateAttributesMapping.js";
import { useTabsRootContext } from "../root/TabsRootContext.js";
import { TabsListContext } from "./TabsListContext.js";
/**
* Groups the individual tab buttons.
* Renders a `<div>` element.
*
* Documentation: [Base UI Tabs](https://base-ui.com/react/components/tabs)
*/
import { jsx as _jsx } from "react/jsx-runtime";
export const TabsList = /*#__PURE__*/React.forwardRef(function TabsList(componentProps, forwardedRef) {
const {
activateOnFocus = false,
className,
loopFocus = true,
render,
style,
...elementProps
} = componentProps;
const {
onValueChange,
orientation,
value,
setTabMap,
tabActivationDirection
} = useTabsRootContext();
const [highlightedTabIndex, setHighlightedTabIndex] = React.useState(0);
const [tabsListElement, setTabsListElement] = React.useState(null);
const indicatorUpdateListenersRef = React.useRef(new Set());
const tabResizeObserverElementsRef = React.useRef(new Set());
const resizeObserverRef = React.useRef(null);
const notifyIndicatorUpdateListeners = useStableCallback(() => {
indicatorUpdateListenersRef.current.forEach(listener => {
listener();
});
});
React.useEffect(() => {
if (typeof ResizeObserver === 'undefined') {
return undefined;
}
const resizeObserver = new ResizeObserver(() => {
if (!indicatorUpdateListenersRef.current.size) {
return;
}
notifyIndicatorUpdateListeners();
});
resizeObserverRef.current = resizeObserver;
if (tabsListElement) {
resizeObserver.observe(tabsListElement);
}
tabResizeObserverElementsRef.current.forEach(element => {
resizeObserver.observe(element);
});
return () => {
resizeObserver.disconnect();
resizeObserverRef.current = null;
};
}, [tabsListElement, notifyIndicatorUpdateListeners]);
const registerIndicatorUpdateListener = useStableCallback(listener => {
indicatorUpdateListenersRef.current.add(listener);
return () => {
indicatorUpdateListenersRef.current.delete(listener);
};
});
const registerTabResizeObserverElement = useStableCallback(element => {
tabResizeObserverElementsRef.current.add(element);
resizeObserverRef.current?.observe(element);
return () => {
tabResizeObserverElementsRef.current.delete(element);
resizeObserverRef.current?.unobserve(element);
};
});
const onTabActivation = useStableCallback((newValue, eventDetails) => {
if (newValue !== value) {
onValueChange(newValue, eventDetails);
}
});
const state = {
orientation,
tabActivationDirection
};
const defaultProps = {
'aria-orientation': orientation === 'vertical' ? 'vertical' : undefined,
role: 'tablist'
};
const tabsListContextValue = React.useMemo(() => ({
activateOnFocus,
highlightedTabIndex,
registerIndicatorUpdateListener,
registerTabResizeObserverElement,
onTabActivation,
setHighlightedTabIndex,
tabsListElement
}), [activateOnFocus, highlightedTabIndex, registerIndicatorUpdateListener, registerTabResizeObserverElement, onTabActivation, setHighlightedTabIndex, tabsListElement]);
return /*#__PURE__*/_jsx(TabsListContext.Provider, {
value: tabsListContextValue,
children: /*#__PURE__*/_jsx(CompositeRoot, {
render: render,
className: className,
style: style,
state: state,
refs: [forwardedRef, setTabsListElement],
props: [defaultProps, elementProps],
stateAttributesMapping: tabsStateAttributesMapping,
highlightedIndex: highlightedTabIndex,
enableHomeAndEndKeys: true,
loopFocus: loopFocus,
orientation: orientation,
onHighlightedIndexChange: setHighlightedTabIndex,
onMapChange: setTabMap,
disabledIndices: EMPTY_ARRAY
})
});
});
if (process.env.NODE_ENV !== "production") TabsList.displayName = "TabsList";