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.
173 lines (146 loc) • 4.48 kB
JavaScript
import React from 'react';
import { useFela } from 'react-fela';
import { objectMap } from '../../object-map';
import { renameKeys } from '../../rename-keys';
import PropTypes from 'prop-types';
export const SUB_STYLES = ['standard', 'inline-link', 'emphasis', 'overline'];
export const convertBaselineValues = objectMap((value, key) => {
if (key === 'top') {
return `${value}px 0 0`;
} else if (key === 'bottom') {
return `0 0 ${value}px`;
}
if (typeof value === 'object') {
return convertBaselineValues(value);
}
return value;
});
export const renameBaselineKeys = renameKeys({
top: 'padding',
bottom: 'margin',
});
export const convertBaselineKeys = obj =>
renameBaselineKeys(
objectMap(value => {
if (typeof value === 'object') {
return renameBaselineKeys(value);
}
return value;
})(obj)
);
/**
Baseline adjustments allow us to fine tune each type scale
so it sits on the 4px baseline. It only needs to be applied to
the root element. We use a combination of margin and padding
to prevent margin collapsing.
*/
export const createBaselineAdjustments = ({
baselineAdjustments = {},
isRootElement,
shouldAdjustBaseline,
}) => {
if (!shouldAdjustBaseline || !isRootElement) {
return { margin: 0, padding: 0 };
}
return convertBaselineKeys(convertBaselineValues(baselineAdjustments));
};
const TextContext = React.createContext();
export const Text = React.forwardRef(
(
{
extend,
variant: userVariant,
subStyle,
alignBaseline: shouldAdjustBaseline = true,
as,
className,
children,
...props
},
ref
) => {
const { css, theme } = useFela(props);
/**
Context is used for two reasons:
1. Allows us to derive the type scale from the context, letting us omit
the variant name for nested styles, eg:
<Text variant="columbus">Hello <Text subStyle="bold">World</Text></Text>
2. Lets us know if the element is the 'root' element, and apply baseline
adjustments if required.
*/
return (
<TextContext.Consumer>
{({
parentVariant: variant = userVariant,
isRootElement = true,
} = {}) => {
const typeScaleStyle = theme.typeScale[variant];
const {
element: preferredElement,
styles,
baselineAdjustments,
} = typeScaleStyle[subStyle];
const baselineAdjustmentStyles = createBaselineAdjustments({
baselineAdjustments,
isRootElement,
shouldAdjustBaseline,
});
const displayModifier = isRootElement ? { display: 'block' } : {};
const Element = as || preferredElement || 'p';
return (
<Element
ref={ref}
{...props}
className={
css(
{
color: theme.color.foreground.primary,
},
displayModifier,
styles,
baselineAdjustmentStyles,
extend
) + (className ? ' ' + className : '')
}
>
<TextContext.Provider
value={{ parentVariant: variant, isRootElement: false }}
>
{children}
</TextContext.Provider>
</Element>
);
}}
</TextContext.Consumer>
);
}
);
Text.displayName = 'Text';
Text.defaultProps = {
variant: 'columbus',
subStyle: 'standard',
};
Text.propTypes = {
/** A JSX node */
children: PropTypes.node,
/** Any valid React element, function, or a string specifying a name for an HTML element */
as: PropTypes.oneOfType([
PropTypes.string,
PropTypes.element,
PropTypes.func,
]),
/** One of the available type scale styles */
variant: PropTypes.string,
/** One of the available sub-styles for a given type scale */
subStyle: PropTypes.string,
/** An object containing valid CSS style declarations */
extend: PropTypes.oneOfType([
PropTypes.object,
PropTypes.func,
PropTypes.array,
]),
/** Add an additional custom className to element. Warning: make sure it doesn't collide with the classNames being generated for the atomic CSS */
className: PropTypes.string,
/** Adjust padding-top/margin-bottom to the text to keep it aligned with the micro grid. */
alignBaseline: PropTypes.bool,
};