@spark-web/tabs
Version:
--- title: Tabs storybookPath: page-layout-tabs--default isExperimentalPackage: true ---
300 lines (278 loc) • 9.1 kB
JavaScript
'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 css = require('@emotion/css');
var reactTabs = require('@radix-ui/react-tabs');
var button = require('@spark-web/button');
var divider = require('@spark-web/divider');
var inline = require('@spark-web/inline');
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('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 /*#__PURE__*/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;
return /*#__PURE__*/jsxRuntime.jsx(reactTabs.List, {
asChild: true,
children: /*#__PURE__*/jsxRuntime.jsx(inline.Inline, {
data: data,
gap: "small",
children: resolveTabListChildren(children)
})
});
}
////////////////////////////////////////////////////////////////////////////////
/**
* Tab
*
* The interactive element that changes the selected panel.
*/
var GAP = 'small'; // Space between the interactive element and the pseudo border
var Tab = /*#__PURE__*/react.forwardRef(function (_ref3, forwardedRef) {
var children = _ref3.children,
data = _ref3.data,
disabled = _ref3.disabled;
var _useTabStyles = useTabStyles(),
_useTabStyles2 = _slicedToArray(_useTabStyles, 2),
boxProps = _useTabStyles2[0],
tabStyles = _useTabStyles2[1];
/**
* 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 /*#__PURE__*/jsxRuntime.jsx(stack.Stack, {
position: "relative",
paddingY: GAP,
children: /*#__PURE__*/jsxRuntime.jsx(reactTabs.Trigger, {
asChild: true,
disabled: disabled,
value: index,
children: /*#__PURE__*/jsxRuntime.jsx(button.BaseButton, _objectSpread(_objectSpread({}, boxProps), {}, {
ref: composedRef,
data: data,
className: css.css(tabStyles),
style: {
width: widthRef.current
},
children: /*#__PURE__*/jsxRuntime.jsx(text.DefaultTextPropsProvider, {
size: "small",
tone: "muted",
children: /*#__PURE__*/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;
return /*#__PURE__*/jsxRuntime.jsxs(stack.Stack, {
data: data,
children: [/*#__PURE__*/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 /*#__PURE__*/jsxRuntime.jsx(reactTabs.Content, _objectSpread(_objectSpread({}, data ? internal.buildDataAttributes(data) : undefined), {}, {
forceMount: true,
ref: forwardedRef,
value: index,
className: css.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() {
var theme$1 = theme.useTheme();
var _useButtonStyles = button.useButtonStyles({
iconOnly: false,
prominence: 'none',
size: 'medium',
tone: 'primary'
}),
_useButtonStyles2 = _slicedToArray(_useButtonStyles, 2),
boxProps = _useButtonStyles2[0],
buttonStyles = _useButtonStyles2[1];
return [_objectSpread(_objectSpread({}, boxProps), {}, {
paddingX: 'xlarge'
}), _objectSpread(_objectSpread({}, buttonStyles), {}, {
'&[data-state=active]': {
'&:not([aria-disabled=true])': {
':hover': {
background: theme$1.color.background.primaryMuted
},
'*': {
color: theme$1.color.foreground.primaryActive,
fontWeight: theme$1.typography.fontWeight.semibold
}
},
':active': {
transform: 'none'
},
// Pseudo border
'::after': {
content: '""',
position: 'absolute',
background: theme$1.color.foreground.primaryActive,
bottom: -theme$1.spacing[GAP],
left: 0,
right: 0,
height: theme$1.border.width.large,
width: '100%',
transform: 'translateY(100%)'
}
}
})];
}
/**
* 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 /*#__PURE__*/jsxRuntime.jsx(text.Text, {
as: "span",
baseline: false,
overflowStrategy: "nowrap",
children: children
});
}
return /*#__PURE__*/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) && /*#__PURE__*/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;