UNPKG

@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
"use strict"; '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";