@base-ui-components/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.
179 lines (175 loc) • 6.04 kB
JavaScript
;
'use client';
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.TabsTab = void 0;
var React = _interopRequireWildcard(require("react"));
var _owner = require("@base-ui-components/utils/owner");
var _useIsoLayoutEffect = require("@base-ui-components/utils/useIsoLayoutEffect");
var _useBaseUiId = require("../../utils/useBaseUiId");
var _useRenderElement = require("../../utils/useRenderElement");
var _useButton = require("../../use-button");
var _constants = require("../../composite/constants");
var _useCompositeItem = require("../../composite/item/useCompositeItem");
var _TabsRootContext = require("../root/TabsRootContext");
var _TabsListContext = require("../list/TabsListContext");
var _createBaseUIEventDetails = require("../../utils/createBaseUIEventDetails");
var _reasons = require("../../utils/reasons");
var _utils = require("../../floating-ui-react/utils");
/**
* An individual interactive tab button that toggles the corresponding panel.
* Renders a `<button>` element.
*
* Documentation: [Base UI Tabs](https://base-ui.com/react/components/tabs)
*/
const TabsTab = exports.TabsTab = /*#__PURE__*/React.forwardRef(function TabsTab(componentProps, forwardedRef) {
const {
className,
disabled = false,
render,
value: valueProp,
id: idProp,
nativeButton = true,
...elementProps
} = componentProps;
const {
value: activeTabValue,
getTabPanelIdByTabValueOrIndex,
orientation
} = (0, _TabsRootContext.useTabsRootContext)();
const {
activateOnFocus,
highlightedTabIndex,
onTabActivation,
setHighlightedTabIndex,
tabsListElement
} = (0, _TabsListContext.useTabsListContext)();
const id = (0, _useBaseUiId.useBaseUiId)(idProp);
const tabMetadata = React.useMemo(() => ({
disabled,
id,
value: valueProp
}), [disabled, id, valueProp]);
const {
compositeProps,
compositeRef,
index
// hook is used instead of the CompositeItem component
// because the index is needed for Tab internals
} = (0, _useCompositeItem.useCompositeItem)({
metadata: tabMetadata
});
const tabValue = valueProp ?? index;
// the `active` state isn't set on the server (it relies on effects to be calculated),
// so we fall back to checking the `value` param with the activeTabValue from the TabsContext
const active = React.useMemo(() => {
if (valueProp === undefined) {
return index < 0 ? false : index === activeTabValue;
}
return valueProp === activeTabValue;
}, [index, activeTabValue, valueProp]);
const isNavigatingRef = React.useRef(false);
// Keep the highlighted item in sync with the currently active tab
// when the value prop changes externally (controlled mode)
(0, _useIsoLayoutEffect.useIsoLayoutEffect)(() => {
if (isNavigatingRef.current) {
isNavigatingRef.current = false;
return;
}
if (!(active && index > -1 && highlightedTabIndex !== index)) {
return;
}
// If focus is currently within the tabs list, don't override the roving
// focus highlight. This keeps keyboard navigation relative to the focused
// item after an external/asynchronous selection change.
const listElement = tabsListElement;
if (listElement != null) {
const activeEl = (0, _utils.activeElement)((0, _owner.ownerDocument)(listElement));
if (activeEl && (0, _utils.contains)(listElement, activeEl)) {
return;
}
}
setHighlightedTabIndex(index);
}, [active, index, highlightedTabIndex, setHighlightedTabIndex, disabled, tabsListElement]);
const {
getButtonProps,
buttonRef
} = (0, _useButton.useButton)({
disabled,
native: nativeButton,
focusableWhenDisabled: true
});
const tabPanelId = index > -1 ? getTabPanelIdByTabValueOrIndex(valueProp, index) : undefined;
const isPressingRef = React.useRef(false);
const isMainButtonRef = React.useRef(false);
function onClick(event) {
if (active || disabled) {
return;
}
onTabActivation(tabValue, (0, _createBaseUIEventDetails.createChangeEventDetails)(_reasons.REASONS.none, event.nativeEvent, undefined, {
activationDirection: 'none'
}));
}
function onFocus(event) {
if (active) {
return;
}
if (index > -1) {
setHighlightedTabIndex(index);
}
if (disabled) {
return;
}
if (activateOnFocus && (!isPressingRef.current ||
// keyboard or touch focus
isPressingRef.current && isMainButtonRef.current) // mouse focus
) {
onTabActivation(tabValue, (0, _createBaseUIEventDetails.createChangeEventDetails)(_reasons.REASONS.none, event.nativeEvent, undefined, {
activationDirection: 'none'
}));
}
}
function onPointerDown(event) {
if (active || disabled) {
return;
}
isPressingRef.current = true;
function handlePointerUp() {
isPressingRef.current = false;
isMainButtonRef.current = false;
}
if (!event.button || event.button === 0) {
isMainButtonRef.current = true;
const doc = (0, _owner.ownerDocument)(event.currentTarget);
doc.addEventListener('pointerup', handlePointerUp, {
once: true
});
}
}
const state = React.useMemo(() => ({
disabled,
active,
orientation
}), [disabled, active, orientation]);
const element = (0, _useRenderElement.useRenderElement)('button', componentProps, {
state,
ref: [forwardedRef, buttonRef, compositeRef],
props: [compositeProps, {
role: 'tab',
'aria-controls': tabPanelId,
'aria-selected': active,
id,
onClick,
onFocus,
onPointerDown,
[_constants.ACTIVE_COMPOSITE_ITEM]: active ? '' : undefined,
onKeyDownCapture() {
isNavigatingRef.current = true;
}
}, elementProps, getButtonProps]
});
return element;
});
if (process.env.NODE_ENV !== "production") TabsTab.displayName = "TabsTab";