UNPKG

@spark-web/tabs

Version:

--- title: Tabs storybookPath: page-layout-tabs--default isExperimentalPackage: true ---

335 lines (314 loc) 12.7 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var react = require('react'); var _slicedToArray = require('@babel/runtime/helpers/slicedToArray'); var _objectSpread = require('@babel/runtime/helpers/objectSpread2'); var react$1 = require('@emotion/react'); var reactTabs = require('@radix-ui/react-tabs'); var box = require('@spark-web/box'); var button = require('@spark-web/button'); var divider = require('@spark-web/divider'); var stack = require('@spark-web/stack'); var text = require('@spark-web/text'); var theme = require('@spark-web/theme'); var utils = require('@spark-web/utils'); var internal = require('@spark-web/utils/internal'); var useMeasure = require('react-use-measure'); var jsxRuntime = require('@emotion/react/jsx-runtime'); function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; } var useMeasure__default = /*#__PURE__*/_interopDefault(useMeasure); /** @todo handle fragments */ var IndexContext = /*#__PURE__*/react.createContext(0); var IndexProvider = IndexContext.Provider; function useIndexContext() { var index = react.useContext(IndexContext); // Radix UI requires that the `value` prop for Tab and TabPanel be a string return String(index); } var Tabs = /*#__PURE__*/react.forwardRef(function (_ref, forwardedRef) { var activationMode = _ref.activationMode, data = _ref.data, children = _ref.children, defaultIndex = _ref.defaultIndex; var defaultValue = typeof defaultIndex === 'undefined' ? String(0) : String(defaultIndex); return jsxRuntime.jsx(reactTabs.Root, _objectSpread(_objectSpread({}, data ? internal.buildDataAttributes(data) : undefined), {}, { activationMode: activationMode, defaultValue: defaultValue, ref: forwardedRef, children: children })); }); Tabs.displayName = 'Tabs'; //////////////////////////////////////////////////////////////////////////////// /** * TabList * * The parent component of the tabs. */ function TabList(_ref2) { var children = _ref2.children, data = _ref2.data; var theme$1 = theme.useTheme(); return jsxRuntime.jsx(reactTabs.List, { asChild: true, children: jsxRuntime.jsx(box.Box, { data: data, display: "flex", flexWrap: "nowrap", css: { gap: theme$1.spacing.small, overflowX: 'auto', // overflow-x: auto forces overflow-y to clip, which hides the active // tab indicator (::after) that extends below the tab via translateY. // paddingBottom creates room for it within the container's paint area; // the negative marginBottom cancels the extra space so the divider // below stays flush. paddingBottom: theme$1.border.width.large, marginBottom: -theme$1.border.width.large, scrollbarWidth: 'none', '&::-webkit-scrollbar': { display: 'none' } }, children: resolveTabListChildren(children) }) }); } //////////////////////////////////////////////////////////////////////////////// /** * Tab * * The interactive element that changes the selected panel. */ var Tab = /*#__PURE__*/react.forwardRef(function (_ref3, forwardedRef) { var children = _ref3.children, data = _ref3.data, disabled = _ref3.disabled, paddingY = _ref3.paddingY; var _useTabStyles = useTabStyles(paddingY), _useTabStyles2 = _slicedToArray(_useTabStyles, 3), boxProps = _useTabStyles2[0], textSize = _useTabStyles2[1], tabStyles = _useTabStyles2[2]; /** * The font-weight of the tab changes when the button is active. * This causes the button to get slightly wider (which we don't want). * To avoid this, we measure the initial width of the button (after the * first paint) and give it fixed width. * We're using the style prop for this so that Emotion doesn't need to * generate a completely new hash for each tab (as the width will vary * for each tab based on the length of the text). */ var _useMeasure = useMeasure__default["default"](), _useMeasure2 = _slicedToArray(_useMeasure, 2), internalRef = _useMeasure2[0], bounds = _useMeasure2[1]; var composedRef = utils.useComposedRefs(internalRef, forwardedRef); var widthRef = react.useRef(undefined); react.useEffect(function () { if (bounds.width && !widthRef.current) { widthRef.current = bounds.width; } }, [bounds.width]); var index = useIndexContext(); return jsxRuntime.jsx(stack.Stack, { position: "relative", paddingY: paddingY, children: jsxRuntime.jsx(reactTabs.Trigger, { asChild: true, disabled: disabled, value: index, children: jsxRuntime.jsx(button.BaseButton, _objectSpread(_objectSpread({}, boxProps), {}, { ref: composedRef, data: data, css: react$1.css(tabStyles), style: { width: widthRef.current }, children: jsxRuntime.jsx(text.DefaultTextPropsProvider, { size: textSize, tone: "muted", children: jsxRuntime.jsx(Content, { children: children }) }) })) }) }); }); var TAB_NAME = 'Tab'; Tab.displayName = TAB_NAME; //////////////////////////////////////////////////////////////////////////////// /** * TabPanels * * The parent component of the panels. */ function TabPanels(_ref4) { var children = _ref4.children, data = _ref4.data, _ref4$showDivider = _ref4.showDivider, showDivider = _ref4$showDivider === void 0 ? true : _ref4$showDivider; return jsxRuntime.jsxs(stack.Stack, { data: data, children: [showDivider && jsxRuntime.jsx(divider.Divider, { width: "large" }), resolveTabPanelsChildren(children)] }); } //////////////////////////////////////////////////////////////////////////////// /** * TabPanel * * The panel that displays when it's corresponding tab is active. */ var TabPanel = /*#__PURE__*/react.forwardRef(function (_ref5, forwardedRef) { var children = _ref5.children, data = _ref5.data; var index = useIndexContext(); return jsxRuntime.jsx(reactTabs.Content, _objectSpread(_objectSpread({}, data ? internal.buildDataAttributes(data) : undefined), {}, { forceMount: true, ref: forwardedRef, value: index, css: react$1.css({ '&[data-state=inactive]': { display: 'none' } }), children: children })); }); var TAB_PANEL_NAME = 'TabPanel'; TabPanel.displayName = TAB_PANEL_NAME; //////////////////////////////////////////////////////////////////////////////// /** * Helpers */ /** * Custom hook to encapsulate styles for the Tab component */ function useTabStyles(paddingY) { var _theme$components$tab, _theme$components$tab2, _theme$components$tab3, _theme$components$tab4, _theme$components$tab5, _theme$components$tab6, _theme$components$tab7, _theme$components$tab8, _theme$components$tab9, _theme$components$tab0, _theme$components$tab1, _theme$components$tab10, _theme$components$tab11, _theme$components$tab12; var theme$1 = theme.useTheme(); var tone = 'primary'; var _useButtonStyles = button.useButtonStyles({ iconOnly: false, prominence: 'none', size: 'medium', tone: tone, rounded: false }), _useButtonStyles2 = _slicedToArray(_useButtonStyles, 2), boxProps = _useButtonStyles2[0], buttonStyles = _useButtonStyles2[1]; var textSize = (_theme$components$tab = (_theme$components$tab2 = theme$1.components.tabs) === null || _theme$components$tab2 === void 0 ? void 0 : _theme$components$tab2.textSize) !== null && _theme$components$tab !== void 0 ? _theme$components$tab : 'xsmall'; var textTransform = (_theme$components$tab3 = (_theme$components$tab4 = theme$1.components.tabs) === null || _theme$components$tab4 === void 0 ? void 0 : _theme$components$tab4.textTransform) !== null && _theme$components$tab3 !== void 0 ? _theme$components$tab3 : 'uppercase'; var letterSpacing = (_theme$components$tab5 = (_theme$components$tab6 = theme$1.components.tabs) === null || _theme$components$tab6 === void 0 ? void 0 : _theme$components$tab6.letterSpacing) !== null && _theme$components$tab5 !== void 0 ? _theme$components$tab5 : '0.05em'; return [_objectSpread(_objectSpread({}, boxProps), {}, { paddingX: 'large', borderRadius: (_theme$components$tab7 = (_theme$components$tab8 = theme$1.components.tabs) === null || _theme$components$tab8 === void 0 || (_theme$components$tab8 = _theme$components$tab8.active) === null || _theme$components$tab8 === void 0 ? void 0 : _theme$components$tab8.borderRadius) !== null && _theme$components$tab7 !== void 0 ? _theme$components$tab7 : boxProps['borderRadius'] }), textSize, _objectSpread(_objectSpread({}, buttonStyles), {}, { textTransform: textTransform, letterSpacing: letterSpacing, '&:not([aria-disabled=true])': _objectSpread(_objectSpread({}, buttonStyles['&:not([aria-disabled=true])']), {}, { 'body:not([data-brighte-focus-visible]) &:focus': { outline: 'none', boxShadow: 'none' }, ':hover': _objectSpread(_objectSpread({}, buttonStyles['&:not([aria-disabled=true])'][':hover']), {}, { backgroundColor: (_theme$components$tab9 = (_theme$components$tab0 = theme$1.components.tabs) === null || _theme$components$tab0 === void 0 || (_theme$components$tab0 = _theme$components$tab0.hover) === null || _theme$components$tab0 === void 0 ? void 0 : _theme$components$tab0.background) !== null && _theme$components$tab9 !== void 0 ? _theme$components$tab9 : theme$1.backgroundInteractions["".concat(tone, "LowHover")] }) }), '&[data-state=active]': { '&:not([aria-disabled=true])': { ':hover': (_theme$components$tab1 = theme$1.components.tabs) === null || _theme$components$tab1 === void 0 ? void 0 : _theme$components$tab1.hover, '*': { color: (_theme$components$tab10 = theme$1.components.tabs) === null || _theme$components$tab10 === void 0 ? void 0 : _theme$components$tab10.color, fontWeight: (_theme$components$tab11 = theme$1.components.tabs) === null || _theme$components$tab11 === void 0 ? void 0 : _theme$components$tab11.fontWeight } }, ':active': { transform: 'none' }, // Pseudo border '::after': _objectSpread({ content: '""', position: 'absolute', background: theme$1.color.foreground.primaryActive, bottom: paddingY ? -theme$1.spacing[paddingY] : 0, left: 0, right: 0, height: theme$1.border.width.large, width: '100%', transform: 'translateY(100%)' }, (_theme$components$tab12 = theme$1.components.tabs) === null || _theme$components$tab12 === void 0 ? void 0 : _theme$components$tab12.pseudoBorder) } })]; } /** * Provides base typographic styles when the type of children is `string` or * `number`. * Otherwise children are wrapped in a `Fragment` in order to provide custom * styles. */ function Content(_ref6) { var children = _ref6.children; if (typeof children === 'string' || typeof children === 'number') { return jsxRuntime.jsx(text.Text, { as: "span", baseline: false, overflowStrategy: "nowrap", children: children }); } return jsxRuntime.jsx(react.Fragment, { children: children }); } /** * Throws an error if children are not `Tab` components */ function resolveTabListChildren(children) { return react.Children.map(children, function (child, index) { if (child.type.displayName !== TAB_NAME) { throw new Error('All children of `TabList` must be `Tab` components'); } return childWithIndexProvider({ child: child, index: index }); }); } /** * Throws an error if children are not `TabPanel` components */ function resolveTabPanelsChildren(children) { return react.Children.map(children, function (child, index) { if (child.type.displayName !== TAB_PANEL_NAME) { throw new Error('All children of `TabPanels` must be `TabPanel` components'); } return childWithIndexProvider({ child: child, index: index }); }); } /** * Ensures that the children are valid `ReactElements` before wrapping them with * the IndexProvider. */ function childWithIndexProvider(_ref7) { var child = _ref7.child, index = _ref7.index; return /*#__PURE__*/react.isValidElement(child) && jsxRuntime.jsx(IndexProvider, { value: index, children: child }); } exports.IndexProvider = IndexProvider; exports.Tab = Tab; exports.TabList = TabList; exports.TabPanel = TabPanel; exports.TabPanels = TabPanels; exports.Tabs = Tabs; exports.useIndexContext = useIndexContext;