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.

145 lines (143 loc) 5.64 kB
"use strict"; 'use client'; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default; Object.defineProperty(exports, "__esModule", { value: true }); exports.TabsIndicator = void 0; var React = _interopRequireWildcard(require("react")); var _generateId = require("@base-ui-components/utils/generateId"); var _useForcedRerendering = require("@base-ui-components/utils/useForcedRerendering"); var _useOnMount = require("@base-ui-components/utils/useOnMount"); var _useRenderElement = require("../../utils/useRenderElement"); var _DirectionContext = require("../../direction-provider/DirectionContext"); var _TabsRootContext = require("../root/TabsRootContext"); var _styleHooks = require("../root/styleHooks"); var _TabsListContext = require("../list/TabsListContext"); var _prehydrationScript = require("./prehydrationScript.min"); var _TabsIndicatorCssVars = require("./TabsIndicatorCssVars"); var _jsxRuntime = require("react/jsx-runtime"); const customStyleHookMapping = { ..._styleHooks.tabsStyleHookMapping, selectedTabPosition: () => null, selectedTabSize: () => null }; /** * A visual indicator that can be styled to match the position of the currently active tab. * Renders a `<span>` element. * * Documentation: [Base UI Tabs](https://base-ui.com/react/components/tabs) */ const TabsIndicator = exports.TabsIndicator = /*#__PURE__*/React.forwardRef(function TabIndicator(componentProps, forwardedRef) { const { className, render, renderBeforeHydration = false, ...elementProps } = componentProps; const { getTabElementBySelectedValue, orientation, tabActivationDirection, value } = (0, _TabsRootContext.useTabsRootContext)(); const { tabsListRef } = (0, _TabsListContext.useTabsListContext)(); const [instanceId] = React.useState(() => (0, _generateId.generateId)('tab')); const [isMounted, setIsMounted] = React.useState(false); const { value: activeTabValue } = (0, _TabsRootContext.useTabsRootContext)(); const direction = (0, _DirectionContext.useDirection)(); (0, _useOnMount.useOnMount)(() => setIsMounted(true)); const rerender = (0, _useForcedRerendering.useForcedRerendering)(); React.useEffect(() => { if (value != null && tabsListRef.current != null && typeof ResizeObserver !== 'undefined') { const resizeObserver = new ResizeObserver(() => { rerender(); }); resizeObserver.observe(tabsListRef.current); return () => { resizeObserver.disconnect(); }; } return undefined; }, [value, tabsListRef, rerender]); let left = 0; let right = 0; let top = 0; let bottom = 0; let width = 0; let height = 0; let isTabSelected = false; if (value != null && tabsListRef.current != null) { const selectedTab = getTabElementBySelectedValue(value); const tabsList = tabsListRef.current; isTabSelected = true; if (selectedTab != null) { left = selectedTab.offsetLeft - tabsList.clientLeft; right = direction === 'ltr' ? tabsList.scrollWidth - selectedTab.offsetLeft - selectedTab.offsetWidth - tabsList.clientLeft : selectedTab.offsetLeft - tabsList.clientLeft; top = selectedTab.offsetTop - tabsList.clientTop; bottom = tabsList.scrollHeight - selectedTab.offsetTop - selectedTab.offsetHeight - tabsList.clientTop; width = selectedTab.offsetWidth; height = selectedTab.offsetHeight; } } const selectedTabPosition = React.useMemo(() => isTabSelected ? { left, right, top, bottom } : null, [left, right, top, bottom, isTabSelected]); const selectedTabSize = React.useMemo(() => isTabSelected ? { width, height } : null, [width, height, isTabSelected]); const style = React.useMemo(() => { if (!isTabSelected) { return undefined; } return { [_TabsIndicatorCssVars.TabsIndicatorCssVars.activeTabLeft]: `${left}px`, [_TabsIndicatorCssVars.TabsIndicatorCssVars.activeTabRight]: `${right}px`, [_TabsIndicatorCssVars.TabsIndicatorCssVars.activeTabTop]: `${top}px`, [_TabsIndicatorCssVars.TabsIndicatorCssVars.activeTabBottom]: `${bottom}px`, [_TabsIndicatorCssVars.TabsIndicatorCssVars.activeTabWidth]: `${width}px`, [_TabsIndicatorCssVars.TabsIndicatorCssVars.activeTabHeight]: `${height}px` }; }, [left, right, top, bottom, width, height, isTabSelected]); const displayIndicator = isTabSelected && width > 0 && height > 0; const state = React.useMemo(() => ({ orientation, selectedTabPosition, selectedTabSize, tabActivationDirection }), [orientation, selectedTabPosition, selectedTabSize, tabActivationDirection]); const element = (0, _useRenderElement.useRenderElement)('span', componentProps, { state, ref: forwardedRef, props: [{ role: 'presentation', style, hidden: !displayIndicator // do not display the indicator before the layout is settled }, elementProps, { ['data-instance-id']: !(isMounted && renderBeforeHydration) ? instanceId : undefined, suppressHydrationWarning: true }], customStyleHookMapping }); if (activeTabValue == null) { return null; } return /*#__PURE__*/(0, _jsxRuntime.jsxs)(React.Fragment, { children: [element, !isMounted && renderBeforeHydration && /*#__PURE__*/(0, _jsxRuntime.jsx)("script", { // eslint-disable-next-line react/no-danger dangerouslySetInnerHTML: { __html: _prehydrationScript.script }, suppressHydrationWarning: true })] }); }); if (process.env.NODE_ENV !== "production") TabsIndicator.displayName = "TabsIndicator";