UNPKG

braid-design-system

Version:
195 lines (194 loc) • 7.07 kB
import { jsx, jsxs } from "react/jsx-runtime"; import assert from "assert"; import { assignInlineVars } from "@vanilla-extract/dynamic"; import { useContext, useRef, useState, useEffect, useCallback } from "react"; import { negativeMargin } from "../../css/negativeMargin/negativeMargin.mjs"; import { useIsomorphicLayoutEffect } from "../../hooks/useIsomorphicLayoutEffect.mjs"; import { flattenChildren } from "../../utils/flattenChildren.mjs"; import { Box } from "../Box/Box.mjs"; import { useBraidTheme } from "../BraidProvider/BraidThemeContext.mjs"; import { Divider } from "../Divider/Divider.mjs"; import { buildDataAttributes } from "../private/buildDataAttributes.mjs"; import { dividerSpacingForSize } from "./Tab.mjs"; import { TabListContext } from "./TabListContext.mjs"; import { TAB_LIST_UPDATED } from "./Tabs.actions.mjs"; import { TabsContext } from "./TabsProvider.mjs"; import { scroll, nowrap, mask, marginAuto, divider, tabUnderline, tabUnderlineActiveDarkMode, underlineWidth, underlineLeft } from "./Tabs.css.mjs"; const tabLinePositionDefault = { left: 0, width: 0 }; const getActiveTabLinePosition = (button) => { if (!button) { return tabLinePositionDefault; } const computedStyle = getComputedStyle(button); const elWidth = button.getBoundingClientRect().width; const paddingLeft = parseFloat(computedStyle.paddingLeft); const paddingRight = parseFloat(computedStyle.paddingRight); const width = elWidth - paddingLeft - paddingRight; return { left: button.offsetLeft + paddingLeft, width }; }; const Tabs = (props) => { const tabsContext = useContext(TabsContext); const tabsRef = useRef(null); const [activeTabPosition, setActiveTabPosition] = useState( tabLinePositionDefault ); const { children, label, data, align = "left", size = "standard", gutter, reserveHitArea = false, divider: divider$1 = "minimal", ...restProps } = props; assert( tabsContext !== null, "Tabs must be rendered as a child of TabsProvider. See the documentation for correct usage: https://seek-oss.github.io/braid-design-system/components/Tabs" ); if (!tabsContext) { throw new Error("Tabs rendered outside TabsProvider"); } const { dispatch, a11y, selectedIndex, selectedItem } = tabsContext; const tabItems = []; const childTabs = flattenChildren(children); const tabs = childTabs.map((tab, index) => { assert( // @ts-expect-error typeof tab === "object" && tab.type.__isTab__, "Only Tab elements can be direct children of a Tabs" ); tabItems.push(tab.props.item ?? index); return /* @__PURE__ */ jsx( TabListContext.Provider, { value: { tabListItemIndex: index, scrollContainer: tabsRef.current, isLast: childTabs.length === index + 1, size }, children: tab }, index ); }); useEffect(() => { dispatch({ type: TAB_LIST_UPDATED, tabItems }); }, [tabItems.join(), dispatch]); const { space: { grid, space } } = useBraidTheme(); const [showMask, setShowMask] = useState(true); const updateMask = useCallback(() => { if (!tabsRef.current) { return; } setShowMask( tabsRef.current.scrollWidth - tabsRef.current.offsetWidth - tabsRef.current.scrollLeft > grid * space.small ); }, [tabsRef, setShowMask, grid, space]); useIsomorphicLayoutEffect(() => { updateMask(); window.addEventListener("resize", updateMask); return () => window.removeEventListener("resize", updateMask); }, [updateMask]); const selectedTabIndex = typeof selectedItem !== "undefined" ? tabItems.indexOf(selectedItem) : selectedIndex; const selectedTabButtonEl = tabsContext.tabButtonElements[selectedTabIndex.toString()]; useIsomorphicLayoutEffect(() => { setActiveTabPosition(getActiveTabLinePosition(selectedTabButtonEl)); }, [selectedTabButtonEl, size]); return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx( Box, { className: reserveHitArea ? void 0 : negativeMargin("top", dividerSpacingForSize[size]), children: /* @__PURE__ */ jsx(Box, { position: "relative", children: /* @__PURE__ */ jsxs( Box, { ref: tabsRef, className: [ scroll, nowrap, showMask ? mask : null ], display: "flex", onScroll: updateMask, flexWrap: "nowrap", children: [ /* @__PURE__ */ jsx( Box, { display: "flex", className: align === "center" ? marginAuto : void 0, paddingX: gutter, flexWrap: "nowrap", position: "relative", zIndex: 1, children: /* @__PURE__ */ jsxs( Box, { ...a11y.tabListProps({ label }), display: "flex", ...buildDataAttributes({ data, validateRestProps: restProps }), flexWrap: "nowrap", position: "relative", children: [ tabs, divider$1 === "minimal" ? /* @__PURE__ */ jsx( Box, { position: "absolute", bottom: 0, left: 0, right: 0, className: divider, children: /* @__PURE__ */ jsx(Divider, {}) } ) : null, selectedTabButtonEl ? /* @__PURE__ */ jsx( Box, { component: "span", position: "absolute", display: "block", left: 0, right: 0, bottom: 0, background: "formAccent", pointerEvents: "none", className: [ tabUnderline, tabUnderlineActiveDarkMode ], style: assignInlineVars({ [underlineLeft]: activeTabPosition.left.toString(), [underlineWidth]: activeTabPosition.width.toString() }) } ) : null ] } ) } ), divider$1 === "full" ? /* @__PURE__ */ jsx( Box, { position: "absolute", bottom: 0, left: 0, right: 0, className: divider, children: /* @__PURE__ */ jsx(Divider, {}) } ) : null ] } ) }) } ) }); }; export { Tabs };