All posts

Migrating to VCC UI 1.x

Published

Today, we're happy to introduce the first major version RC of VCC UI! Apart from improvements, new components and features, we primarily have been focusing on theming capabilities.

Theming

In previous versions, VCC UI exported a couple of constants such as COLORS which where hard-coded and Volvo specific. Now that it slowly becomes a multi-brand component library, we had to introduce a new, more flexible, concept.

For example, Polestar obviously does not want to use Volvo-branded logos, fonts and colors. That's why VCC UI 1.0.0 introduces a new single source of truth for all design language tokens: brand themes.

Below, we're going through all the crucial parts that have changed with the new theming concept.

Passing a theme

The theme is passed by wrapping your app with the ThemeProvider from VCC UI. It accepts a theme prop which contains an object of theme properties such as breakpoints, colors or fonts. VCC UI provides default themes for each brand.

import { ThemeProvider } from 'vcc-ui';
import volvoTheme from 'vcc-ui/lib/themes/volvo';
export default () => (
<ThemeProvider theme={volvoTheme}>
<App />
</ThemeProvider>
);

One can also extend or modify the theme e.g. adding new breakpoints:

import { getTheme } from 'vcc-ui';
const volvoTheme = getTheme({ variant: 'light' });
const customTheme = {
...volvoTheme,
breakpoints: {
...volvoTheme.breakpoints,
megaLarge: '@media (min-width: 2400px)',
},
};

Using theme values

Now all of the VCC UI components inside our app will use values from the passed theme. But, apart from using those, we often need to extend component styles which requires e.g. breakpoints or colors from the theme.

There are two ways to get values from the current theme depending on the type of component we're using it.

Hooks

Within functional components, we can use the useTheme hook to access theme values.

import { useTheme, Button } from 'vcc-ui';
function CustomButton() {
const theme = useTheme();
return (
<Button
extend={{
[theme.breakpoints.fromL]: {
color: theme.colors.primaryLight,
},
}}
>
Click me
</Button>
);
}

Render-props

Within class components, we can't use the useTheme, but have a render-prop component Theme instead.

import { Theme, Button } from 'vcc-ui';
function CustomButton() {
return (
<Theme>
{(theme) => (
<Button
extend={{
[theme.breakpoints.fromL]: {
color: theme.colors.primaryLight,
},
}}
>
Click me
</Button>
)}
</Theme>
);
}

Fonts

Since fonts are also included in the theme, the way how fonts are loaded has changed as well. We don't export custom font loading methods anymore, but rather include a plugin that automatically renders fonts on demand. Therefore we introduced 3 new theme properties: fonts, fontsPath and fontTypes.

How font loading works:

As soon as we detect a fontFamily that contains a font name included in the fonts list, we load and render that specific font using the fontsPath prefix. To safely use the fonts, they're also provided as constant values within fontTypes.

const theme = {
fontsPath: '/static/vcc-ui/fonts/',
fontTypes: {
NOVUM: 'Volvo Novum, Arial',
},
fonts: [
{
fontFamily: 'Volvo Novum',
fontWeight: 400,
src: [
'volvo-novum/volvo-novum-regular.woff2',
'volvo-novum/volvo-novum-regular.woff',
'volvo-novum/volvo-novum-regular.eot',
'volvo-novum/volvo-novum-regular.svg',
],
},
],
};

Using the theme within your style:

const style = ({ theme }) => ({
fontFamily: theme.fontTypes.NOVUM,
});

Similar to fonts, the logos are now also part of the theme and are configured using 3 different theme properties: logoImages, logoImagesPath, logoTypes. The logos are referenced similar to the fonts. Images with a 2x-suffix are automatically treated as retina substitutes and loaded respectively.

const theme = {
logoImages: {
square: 'volvo-logo.png',
square2x: 'volvo-logo-2x.png',
wordmark: 'volvo-wordmark.svg',
},
logoImagesPath: '/static/vcc-ui/images/',
logoTypes: {
WORDMARK: 'wordmark',
SQUARE: 'square',
},
};
import { useTheme, Logo } from 'vcc-ui';
export function SquareLogo() {
const { theme } = useTheme();
return <Logo type={theme.logoTypes.SQUARE} />;
}

Right to Left rendering (RTL)

Another common use case is RTL styling for languages like Arabic or Hebrew. In the past this was achieved using the ConfigContext.Provider setting the isRtl property. Making our theme the single source of truth, the direction is now also part of the theme.

By default, every theme sets direction to ltr which is still the more common use case. To enable rtl flow, one can wrap the components with another ThemeProvider.

Note: We don't have to spread the full theme again, as the ThemeProvider automatically merges with outer themes.

import { ThemeProvider } from 'vcc-ui'
export default () => (
<ThemeProvider theme={{ direction: 'rtl' }}>
<RTLApp />
<ThemeProvider>
)

The great thing is that we can enable rtl for small subtrees only without affecting the rest of the app.

Components

Apart from the new theming concept, we also changed some components and even added new ones.

Block & Inline

Probably the most commonly used components are the base components Block and Inline that are used to create layouts. In the past, they're just rendering div and span elements which to some extent is fine, but we wanted to make sure the correct display style is also applied. In vcc-ui 1.0.0, Block renders a div with display: block while Inline renders a span with display: inline. This might cause some layout changes in existing apps.

Box

Apart from Block and Inline, we're happy to introduce another layout component: Box. It renders a div with display: flex and also implements a default flexbox set that's heavily inspired from React Native:

display: flex;
flex-direction: column;
flex-grow: 0;
flex-shrink: 1;
flex-basis: auto;
align-self: stretch;

LoadingBar

LoadingBar is a component similar to the existing Spinner but in a linear fashion. It implements a common loading style known from Safari and a couple of websites.


Deprecated Props

Button

  • fullWidth: We deprecated the fullWidth property in favor of a more mobile-first approach. We believe that our components should always adapt to their container instead of setting their size and boundaries themselve. Further reading: https://weser.io/blog/positioning-ui-components.

Libraries

We also updated most of our dependencies.

Especially React and Fela have been updated to the newest versions (react@16.8.x and fela@10.2.x) in order to use features such as hooks and the new Context API. Thus, we recommend everyone to do the same.