vcc-ui
Version:
A React library for building user interfaces at Volvo Cars
218 lines (212 loc) • 7.85 kB
JavaScript
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.TabNavItem = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _react = _interopRequireWildcard(require("react"));
var _reactFela = require("react-fela");
var _click = require("../click");
var _nav = require("../nav");
var _tabNav = require("../tab-nav");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
// TODO: Fix eslint issues the next time this file is edited.
/* eslint-disable react-hooks/exhaustive-deps */
const style = _ref => {
let {
reverseOut,
isActiveTab,
duration,
theme
} = _ref;
return {
minWidth: 50,
alignSelf: 'initial',
justifyContent: 'center',
flexGrow: 0,
flexShrink: 0,
flexBasis: 'auto',
whiteSpace: 'nowrap',
paddingTop: theme.baselineGrid * 2,
paddingBottom: theme.baselineGrid * 2,
paddingRight: theme.baselineGrid * 2,
paddingLeft: theme.baselineGrid * 2,
display: 'flex',
marginRight: theme.baselineGrid * 2.5,
transitionProperty: 'color',
transitionTimingFunction: 'ease-out',
transitionDuration: duration + 'ms',
color: reverseOut ? theme.color.primitive.white : theme.color.foreground.primary,
textAlign: 'left',
letterSpacing: 0.3,
fontWeight: 300,
fontSize: 16,
lineHeight: 1.3,
fontFamily: theme.fontTypes.NOVUM,
boxSizing: 'border-box',
borderBottomWidth: duration === 0 ? 3 : 0,
borderBottomStyle: 'solid',
borderBottomColor: 'transparent',
':hover': {
color: reverseOut ? theme.color.primitive.white : theme.color.ornament.highlight
},
extend: [{
condition: isActiveTab,
style: {
borderBottomColor: reverseOut ? theme.color.primitive.white : theme.color.ornament.highlight,
color: reverseOut ? theme.color.primitive.white : theme.color.ornament.highlight
}
}, {
condition: duration === 0,
style: {
':hover': {
borderBottomColor: reverseOut ? theme.color.primitive.white : theme.color.ornament.highlight
}
}
}]
};
};
function calculatePosition(currentItem, newItem, scrollItem, isRTL) {
const currentOffset = currentItem ? currentItem.offsetLeft : 0;
const currentWidth = currentItem ? currentItem.offsetWidth : 0;
const newWidth = newItem.offsetWidth;
const newOffset = newItem.offsetLeft;
const currentDifference = !isRTL ? currentOffset - scrollItem.offsetLeft : scrollItem.offsetWidth - (currentOffset - scrollItem.offsetLeft) - newWidth;
const newDifference = !isRTL ? newOffset - scrollItem.offsetLeft : scrollItem.offsetWidth - (newOffset - scrollItem.offsetLeft) - newWidth;
return {
currentOffset,
currentWidth,
currentDifference,
newWidth,
newOffset,
newDifference
};
}
function activateTab(setActive, currentItem, newItem, scrollItem, borderItem, duration, scrollTolerance, isRTL) {
const {
currentOffset,
currentWidth,
currentDifference,
newOffset,
newWidth,
newDifference
} = calculatePosition(currentItem, newItem, scrollItem, isRTL);
const property = isRTL ? 'marginRight' : 'marginLeft';
const toLeft = currentOffset > newOffset;
// just a small helper to conditionally apply styles to the border element
function setStyle(style, value) {
let condition = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
if (condition) {
borderItem.style[style] = value + 'px';
}
}
// scroll into view if scrollItem is actually collapsed
if (scrollItem.offsetWidth < scrollItem.scrollWidth && scrollItem.offsetWidth > newWidth) {
const newScrollLeft = newItem.offsetLeft - scrollItem.offsetLeft - scrollItem.scrollLeft;
const spaceRight = scrollItem.offsetWidth - newWidth - newScrollLeft;
if (spaceRight - scrollTolerance < 0 || scrollItem.offsetWidth - (spaceRight + scrollTolerance) < newWidth) {
scrollItem.scrollLeft = newScrollLeft;
}
}
// set the new active item first to make sure calculation is always correct
setActive(newItem);
// initial load
if (!currentItem) {
setStyle('width', newWidth);
setStyle(property, newDifference);
return;
}
/**
* Depending on the direction, it transforms the width and the x-offset
* It uses a helper div (borderRef, see tab-nav/TabNavItemBorder) that is usually hidden
* It removes the current TabNavItem border and adds it back once the animation is done
*/
if (toLeft) {
setStyle('width', currentOffset - newOffset + currentWidth);
setStyle(property, newDifference, !isRTL);
setTimeout(() => {
setStyle(property, newDifference, isRTL);
setStyle('width', newWidth);
}, duration * 3 / 2);
} else {
setStyle(property, newDifference, isRTL);
setStyle('width', newWidth + currentDifference - newDifference, isRTL);
setStyle('width', newWidth + newDifference - currentDifference, !isRTL);
setTimeout(() => {
setStyle('width', newWidth);
setStyle(property, newDifference, !isRTL);
}, duration * 3 / 2);
}
}
/**
* @deprecated
*/
const TabNavItem = exports.TabNavItem = /*#__PURE__*/_react.default.forwardRef((_ref2, ref) => {
let {
children,
isActive,
onClick,
...props
} = _ref2;
const {
active,
setActive,
scrollRef,
borderRef,
reverseOut,
duration = 0
} = (0, _react.useContext)(_tabNav.TabNavContext);
const {
theme
} = (0, _reactFela.useFela)();
const keyboardNavigationProps = (0, _nav.useKeyboardNavigation)({
ref,
onKeyDown: props.onKeyDown
});
const itemRef = keyboardNavigationProps.ref;
const isRTL = theme.direction !== 'ltr';
const activate = () => activateTab(setActive, active, itemRef.current, scrollRef.current, borderRef.current, duration, theme.baselineGrid * 2.5, isRTL);
(0, _react.useEffect)(() => {
if (isActive && setActive) {
activate();
}
},
// also update the active state if isActive is manually updated
[isActive, isRTL]);
const isActiveTab = active ? active === itemRef.current : isActive;
const styleProps = {
isActiveTab,
reverseOut,
duration,
theme
};
const onClickHandler = e => {
if (setActive) {
activate();
}
if (onClick) {
onClick(e);
}
};
return /*#__PURE__*/_react.default.createElement(_click.Click, (0, _extends2.default)({
role: "tab",
"aria-selected": isActiveTab
}, props, {
onClick: onClickHandler
}, keyboardNavigationProps, {
extend: [style(styleProps), {}]
}), children);
});
TabNavItem.displayName = 'TabNavItem';
TabNavItem.propTypes = {
children: _propTypes.default.node,
/** Indicate if the TabNavItem will open a dropdown menu */
isDropdown: _propTypes.default.bool,
/** Indicate if the TabNavItem is in an active state */
isActive: _propTypes.default.bool,
/** Onclick function */
onClick: _propTypes.default.func
};
;