vcc-ui
Version:
A React library for building user interfaces at Volvo Cars
208 lines (204 loc) • 6.68 kB
JavaScript
import _extends from "@babel/runtime/helpers/extends";
import PropTypes from 'prop-types';
// TODO: Fix eslint issues the next time this file is edited.
/* eslint-disable react-hooks/exhaustive-deps */
import React, { useContext, useEffect } from 'react';
import { useFela } from 'react-fela';
import { Click } from '../click';
import { useKeyboardNavigation } from '../nav';
import { TabNavContext } from '../tab-nav';
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
*/
export const TabNavItem = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
let {
children,
isActive,
onClick,
...props
} = _ref2;
const {
active,
setActive,
scrollRef,
borderRef,
reverseOut,
duration = 0
} = useContext(TabNavContext);
const {
theme
} = useFela();
const keyboardNavigationProps = 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);
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.createElement(Click, _extends({
role: "tab",
"aria-selected": isActiveTab
}, props, {
onClick: onClickHandler
}, keyboardNavigationProps, {
extend: [style(styleProps), {}]
}), children);
});
TabNavItem.displayName = 'TabNavItem';
TabNavItem.propTypes = {
children: PropTypes.node,
/** Indicate if the TabNavItem will open a dropdown menu */
isDropdown: PropTypes.bool,
/** Indicate if the TabNavItem is in an active state */
isActive: PropTypes.bool,
/** Onclick function */
onClick: PropTypes.func
};