vcc-ui
Version:
VCC UI is a collection of React UI Components that can be used for developing front-end applications at Volvo Car Corporation.
216 lines (187 loc) • 8.36 kB
JavaScript
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
import React, { useRef, useContext, useEffect } from 'react';
import { useFela } from 'react-fela';
import PropTypes from 'prop-types';
import { Click } from '../click';
import { TabNavContext } from '../tab-nav';
import { getThemeStyle } from '../../get-theme-style';
var style = function style(_ref) {
var reverseOut = _ref.reverseOut,
isActiveTab = _ref.isActiveTab,
duration = _ref.duration,
theme = _ref.theme;
return {
minWidth: 50,
alignSelf: 'initial',
justifyContent: 'center',
flexGrow: 0,
flexShrink: 0,
flexBasis: 'auto',
whiteSpace: 'nowrap',
paddingTop: theme.baselineGrid * 2,
paddingBottom: 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: 200,
fontSize: 16,
boxSizing: 'border-box',
borderBottomWidth: duration === 0 ? 3 : 0,
borderBottomStyle: 'solid',
borderBottomColor: 'transparent',
':focus': {
outline: 0,
color: reverseOut ? theme.color.primitive.white : theme.color.ornament.highlight
},
':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) {
var currentOffset = currentItem ? currentItem.offsetLeft : 0;
var currentWidth = currentItem ? currentItem.offsetWidth : 0;
var newWidth = newItem.offsetWidth;
var newOffset = newItem.offsetLeft;
var currentDifference = !isRTL ? currentOffset - scrollItem.offsetLeft : scrollItem.offsetWidth - (currentOffset - scrollItem.offsetLeft) - newWidth;
var newDifference = !isRTL ? newOffset - scrollItem.offsetLeft : scrollItem.offsetWidth - (newOffset - scrollItem.offsetLeft) - newWidth;
return {
currentOffset: currentOffset,
currentWidth: currentWidth,
currentDifference: currentDifference,
newWidth: newWidth,
newOffset: newOffset,
newDifference: newDifference
};
}
function activateTab(setActive, currentItem, newItem, scrollItem, borderItem, duration, scrollTolerance, isRTL) {
var _calculatePosition = calculatePosition(currentItem, newItem, scrollItem, isRTL),
currentOffset = _calculatePosition.currentOffset,
currentWidth = _calculatePosition.currentWidth,
currentDifference = _calculatePosition.currentDifference,
newOffset = _calculatePosition.newOffset,
newWidth = _calculatePosition.newWidth,
newDifference = _calculatePosition.newDifference;
var property = isRTL ? 'marginRight' : 'marginLeft';
var toLeft = currentOffset > newOffset; // just a small helper to conditionally apply styles to the border element
function setStyle(style, value) {
var 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) {
var newScrollLeft = newItem.offsetLeft - scrollItem.offsetLeft - scrollItem.scrollLeft;
var 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(function () {
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(function () {
setStyle('width', newWidth);
setStyle(property, newDifference, !isRTL);
}, duration * 3 / 2);
}
}
export var TabNavItem = React.forwardRef(function (_ref2, _ref3) {
var children = _ref2.children,
isActive = _ref2.isActive,
onClick = _ref2.onClick,
props = _objectWithoutProperties(_ref2, ["children", "isActive", "onClick"]);
var _useContext = useContext(TabNavContext),
active = _useContext.active,
setActive = _useContext.setActive,
scrollRef = _useContext.scrollRef,
borderRef = _useContext.borderRef,
reverseOut = _useContext.reverseOut,
_useContext$duration = _useContext.duration,
duration = _useContext$duration === void 0 ? 0 : _useContext$duration;
var _useFela = useFela(),
theme = _useFela.theme;
var itemRef = useRef();
var isRTL = theme.direction !== 'ltr';
var activate = function activate() {
return activateTab(setActive, active, itemRef.current, scrollRef.current, borderRef.current, duration, theme.baselineGrid * 2.5, isRTL);
};
useEffect(function () {
if (isActive && setActive) {
activate();
}
}, // also update the active state if isActive is manually updated
[isActive, isRTL]);
var styleProps = {
isActiveTab: active ? active === itemRef.current : isActive,
reverseOut: reverseOut,
duration: duration,
theme: theme
};
var onClickHandler = function onClickHandler(e) {
if (setActive) {
activate();
}
if (onClick) {
onClick(e);
}
};
return React.createElement(Click, _extends({}, props, {
ref: function ref(el) {
if (itemRef) itemRef.current = el;
if (_ref3) _ref3.current = el;
},
onClick: onClickHandler,
extend: [style(styleProps), getThemeStyle('tabNavItem', theme, 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
};