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.
275 lines (251 loc) • 7.16 kB
JavaScript
import React, { createContext, useState, useRef } from 'react';
import { useFela } from 'react-fela';
import PropTypes from 'prop-types';
import { Block } from '../block';
import { Click } from '../click';
import { Arrow } from '../arrow';
import { Inline } from '../inline';
import { Flex } from '../flex';
import { getThemeStyle } from '../../get-theme-style';
import { deprecate } from '../../deprecate';
export const TabNavContext = createContext({});
const styles = {
nav: ({ theme, reverseOut }) => ({
flexDirection: 'row',
overflow: 'hidden',
borderBottomStyle: 'solid',
borderBottomWidth: 1,
borderBottomColor: reverseOut
? theme.color.primitive.grey200
: theme.color.ornament.divider,
backgroundColor: reverseOut
? theme.color.primitive.grey200
: theme.color.background.secondary,
}),
scrollContainer: {
flex: '0 1 auto',
overflowY: 'hidden',
overflowX: 'auto',
msOverflowY: 'hidden',
msOverflowX: 'auto',
msOverflowStyle: 'none',
'::-webkit-scrollbar': {
display: 'none',
},
},
itemContainer: {
flexDirection: 'row',
padding: '0 0 0 19px',
},
stickyLeft: ({ isCentered }) => ({
flexShrink: 0,
flexBasis: 0,
flexGrow: isCentered ? 1 : 0,
msFlex: '1 0 auto',
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
}),
stickyRight: {
flexShrink: 0,
flexBasis: 0,
flexGrow: 1,
msFlex: '1 0 auto',
alignItems: 'center',
justifyContent: 'flex-end',
flexDirection: 'row',
},
border: ({ reverseOut, animatedMargin, duration, theme }) => ({
width: 0,
height: duration === 0 ? 0 : 3,
marginRight: '0 !important',
backgroundColor: reverseOut
? theme.color.primitive.white
: theme.color.ornament.highlight,
transitionProperty: `margin-${animatedMargin}, width`,
transitionTimingFunction: 'ease-out',
transitionDuration: duration + 'ms',
}),
backButton: ({ reverseOut, showBackButtonOn = [], theme }) => ({
fontSize: 15,
padding: '16px 10px 19px',
verticalAlign: 'middle',
fontWeight: 200,
whiteSpace: 'nowrap',
flexShrink: '0',
color: reverseOut
? theme.color.primitive.white
: theme.color.foreground.primary,
display: showBackButtonOn.some(size => size === 's') ? 'inline' : 'none',
'> svg': {
marginRight: 8,
},
':hover > svg': {
transform: 'translateX(-5px)',
},
[theme.breakpoints.onlyM]: {
display: showBackButtonOn.some(size => size === 'm') ? 'inline' : 'none',
},
[theme.breakpoints.fromL]: {
display: showBackButtonOn.some(size => size === 'l') ? 'inline' : 'none',
},
}),
backButtonText: ({ theme }) => ({
[theme.breakpoints.onlyS]: {
display: 'none',
},
}),
};
function BackButton({ backButtonConfig = {}, showBackButtonOn, reverseOut }) {
const { theme } = useFela();
const { href, clickHandler = () => {}, text } = backButtonConfig;
const styleProps = {
theme,
reverseOut,
showBackButtonOn,
};
return (
<Click
onClick={clickHandler}
href={href}
extend={[
styles.backButton(styleProps),
getThemeStyle('tabNavBackButton', theme, styleProps),
]}
>
<Arrow
direction="left"
size={10}
color={
reverseOut
? theme.color.primitive.white
: theme.color.ornament.highlight
}
/>
<Inline extend={styles.backButtonText}>{text || ''}</Inline>
</Click>
);
}
function TabNavBorder() {
const { borderRef, reverseOut, duration } = React.useContext(TabNavContext);
const { theme } = useFela();
const animatedMargin = theme.direction === 'rtl' ? 'right' : 'left';
return (
<Block
ref={borderRef}
extend={styles.border({
animatedMargin,
duration,
reverseOut,
theme,
})}
/>
);
}
export const TabNav = React.forwardRef(
(
{
children,
duration = 160,
enableLineTransition = false,
textAlign = 'center',
// TODO: remove variant in 2.x
// eslint-disable-next-line
variant,
stickyChildrenLeft = null,
stickyChildrenRight = null,
showBackButtonOn = ['s', 'm', 'l'],
backButton: backButtonConfig = {},
},
ref
) => {
const [active, setActive] = useState(null);
const scrollRef = useRef();
const borderRef = useRef();
const { theme } = useFela();
// TODO: remove in 2.x
deprecate(
'Variant for TabNav is deprecated, please use the volvo dark theme instead',
variant
);
const reverseOut = variant === 'dark';
const isCentered = textAlign === 'center';
const backButtonVisible = !!(
backButtonConfig.clickHandler || backButtonConfig.href
);
const styleProps = {
theme,
reverseOut,
isCentered,
};
const context = {
active,
setActive,
scrollRef,
borderRef,
reverseOut,
duration: enableLineTransition ? duration : 0,
};
return (
<Flex
as="nav"
ref={ref}
extend={[
styles.nav(styleProps),
getThemeStyle('tabNav', theme, styleProps),
]}
>
<Flex extend={styles.stickyLeft(styleProps)}>
{backButtonVisible ? (
<BackButton
backButtonConfig={backButtonConfig}
showBackButtonOn={showBackButtonOn}
reverseOut={reverseOut}
/>
) : null}
<TabNavContext.Provider value={{ reverseOut }}>
{stickyChildrenLeft}
</TabNavContext.Provider>
</Flex>
<TabNavContext.Provider value={context}>
<Flex ref={scrollRef} extend={styles.scrollContainer}>
<Flex extend={styles.itemContainer}>
<Flex extend={{ paddingRight: 1 }} />
{children}
<Flex extend={{ paddingRight: 1 }} />
</Flex>
<TabNavBorder />
</Flex>
</TabNavContext.Provider>
<Flex extend={styles.stickyRight}>
<TabNavContext.Provider value={{ reverseOut }}>
{stickyChildrenRight}
</TabNavContext.Provider>
</Flex>
</Flex>
);
}
);
TabNav.displayName = 'TabNav';
TabNav.propTypes = {
/** Text-align: left or center */
textAlign: PropTypes.oneOf(['center', 'left']),
/** Back button text, href, click handler */
backButton: PropTypes.shape({
text: PropTypes.string,
href: PropTypes.string,
clickHandler: PropTypes.func,
}),
/** A JSX node */
children: PropTypes.node,
/** A JSX node sticky to the left */
stickyChildrenLeft: PropTypes.node,
/** A JSX node sticky to the right */
stickyChildrenRight: PropTypes.node,
/** Which viewports to show the back button on */
showBackButtonOn: PropTypes.arrayOf(PropTypes.oneOf(['s', 'm', 'l'])),
/** Enable fancy transition when changing active tabs */
enableLineTransition: PropTypes.bool,
/** Set duration of lineTransition animation */
duration: PropTypes.number,
};