Themes Migration

For any questions or help with this, you have friends at the #dev-frontend Slack channel

1. Update the design library

TODO: Add info about older versions.

Update the design library to the latest version by running

yarn add @bulb/design

At this point you'll notice your linter is gonna go nuts, ignore it for now and follow this guide, it is the easiest and fastest way to migrate (trust me, I've done this for a couple of repos now).

2. Adding the theme provider

You should wrap your application with a theme provider this will make the theme object available for styled components and also within any React components through a React Context.

There are two theme providers available, one for client-side rendered applications and another one for server-side rendered applications (e.g. Gatsby or Next.js).

Client-side rendered applications

Go to the file where all of your providers are and add ThemeProvider to the mix

import { ThemeProvider } from '@bulb/design/modules/ThemeProvider';
...
export const App: React.FC = () => (
<SomeProvider>
<ThemeProvider>
<GlobalStyles />
<MainComponent />
</ThemeProvider>
</SomeProvider>
);

Make sure that <GlobalStyles /> is a child of theme provider!

Server-side rendered applications

For SSR apps, you should do the same as above, but instead, import SSRThemeProvider!

import { SSRThemeProvider } from '@bulb/design/modules/ThemeProvider';

The SSR theme provider will make sure that:

  1. The app is server-side rendered with CSS variables
  2. The app is rehydrated correctly and fallbacks are applied for browsers that do not support CSS variables

3. Updating the theme type for styled-components

In order to make the theme type available for every usage of styled, you should change styled-components's DefaultTheme to be the one exported by the design library.

For that you should create this file:

src/@types/styled-components/index.d.ts

This should be the content:

import { Theme } from '@bulb/design/theme';
declare module 'styled-components' {
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface DefaultTheme extends Theme {}
}

4. Removing all imports that have been deprecated

Okay, now we're gonna start some real code change.

A lot of imports have been deprecated, the first thing we'll do is to get rid of those imports.

Use the global search tool (⌘+⇧+F on VS Code) to find each of these lines, and replace them with an empty string.

Attention: For all of the next steps, change the "files to include" option to .ts, .tsx

Pro tip: Add a newline (⇧+Return) to the end of each these, that way you'll end up remove the entire line

  1. import { responsive } from '@bulb/design/utils/responsive';
  2. import { bulbTokens } from '@bulb/design/utils/scaleProperties';
  3. import { palette } from '@bulb/design/styles/palette';
  4. import { media } from '@bulb/design/utils/styleUtils';
  5. import { type } from '@bulb/design/utils/modularScale';

This will remove the majority of the deprecated imports, the rest will be removed ad-hoc in the next steps

5. Getting rid of responsive

This one will blow your mind if you haven't used regex search and replace before.

First perform a global search with this query (click the .* button to activate regex search)

\$\{responsive`([\n\S\s]*?)`\}

Then simply replace all with $1;.

For now, ignore all of other errors you see. e.g. Cannot find name 'bulbTokens'.

Something like this:

${responsive`
font-size: ${bulbTokens.type.mouse};
margin-bottom: ${bulbTokens.spacing.moon};
`}

Should now look like this:

font-size: ${bulbTokens.type.mouse};
margin-bottom: ${bulbTokens.spacing.moon};

6. Convert all uses of bulbTokens to use the theme instead

This one is easy. Just two easy steps.

  1. Perform a global search for ${bulbTokens.spacing and replace them for ${({ theme }) => theme.space.
  2. perform a global search for ${bulbTokens.type and replace them for ${({ theme }) => theme.fontSizes.

7. Convert all uses of type to use the theme instead

Some repos don't use type, which is great, in that case you can skip this step

Another easy one with regex search.

Perform a global search for \$\{type\('(.+?)'\)\} (tick the regex option ".*") and replace it with ${({ theme }) => theme.fontSizes.$1}

Cool, eh?

11. Replace media with themes

This task requires attention

Here's a list. The first line is the global search query, the second line is what you should replace them with:

media.mobileOnly`
({ theme }) => theme.media.small.andBelow} {
media.tabletAndDown`
({ theme }) => theme.media.medium.andBelow} {
media.tabletAndUp`
({ theme }) => theme.media.medium.andAbove} {
media.desktopAndUp`
({ theme }) => theme.media.large.andAbove} {
media.singleColumn`
({ theme }) => theme.media.extended.below1024} {
media.multiColumn`
({ theme }) => theme.media.extended.above1024} {
media.smallAndDown`
({ theme }) => theme.media.extended.below1200} {
media.mediumAndUp`
({ theme }) => theme.media.extended.above1200} {

You might have noticed that this has broken everything that use these media queries, so now we're gonna fix it with regex search again.

Perform a global search query for:

theme\.media([\n\S\s]*?)`\}

Replace the results with:

theme.media$1};

Most (if not all) of the problems should be fixed now.

11. Replace palette with theme in styled components

This another quick and easy one. Simple perform this search:

${palette

and replace it with:

${({ theme }) => theme.colours

7.5 - Fixing leftovers responsive and media

TODO

  • Also theme.dolphin[0]

  • media.something containing ({ theme })

8. Fixing negative values

Since the new theme.space and theme.fontSizes values are based on CSS variables, they don't place nicely with negative values. It's an easy fix, though.

Perform a search for -${({ theme.

If you find any results fix them to this format:

margin-left: calc(-1 * ${({ theme }) => theme.space.earth});

9. Fixing ugly code

The find and replace above can cause some ugly convertions, though. Let's fix those:

Perform a global search for } ${({ theme.

If you find anything that looks like this:

margin: ${({ theme }) => theme.space.earth} ${({ theme }) => theme.space.mars};

Replace it for something a bit more elegant, like this:

margin: ${({ theme }) => `${theme.space.earth} ${theme.space.mars}`};

12. Replacing palette within components that are not styled

For regular React components, you should be able to get the theme from the context by:

import { useTheme } from 'styled-components';
const MyComponent: React.FC = () => {
const { colours } = useTheme();
return <Section backgroundColor={colours.brand.blue} />;
};

This one is a bit tedious because you'll need to search for the remaining uses of palette and manually add useTheme to the components

13. Replacing palette in tests

If you have tests that use palette, you can import and use testTheme instead.

import { testTheme } from '@bulb/design/utils/testUtils/testTheme';
describe('some test', () => {
it('renders a pink text', () => {
const PinkText = <Text colour={testTheme.colours.brand.pink}>Hello</Text>;
});
});

⚠️ ATTENTION: Do not use testTheme for client facing code, as we need to the colours to come from the theme (e.g. Imagine some day there is a dark theme)

14. Replacing palette in places that are not inside components

This one is tricky. Some times we have constants that contain colours. e.g.

const colourMap = {
new: palette.ui.orange,
open: palette.ui.red,
pending: palette.extended.blue.tintish,
hold: palette.extended.blue.shady,
solved: palette.greyScale.darkGrey,
closed: palette.greyScale.grey,
};

The best option is to turn these into functions that take the theme as argument and then call it inside of the component that's gonna use those colours:

import { DefaultTheme, useTheme } from 'styled-components';
const getColourMap = (theme: DefaultTheme) => {
new: theme.colours.ui.orange,
open: theme.colours.ui.red,
pending: theme.colours.extended.blue.tintish,
hold: theme.colours.extended.blue.shady,
solved: theme.colours.greyScale.darkGrey,
closed: theme.colours.greyScale.grey,
};
const MyComponent: React.FC = (props) => {
const theme = useTheme();
const colourMap = getColourMap(theme);
return <Icon backgroundColor={colourMap[props.status]} />;
}

15. Last changes

By now, if you run yarn lint there shouldn't be many errors in your console.

As a rule of thumb, any import that came from @bulb/design/styles is now available from themes.

Other changes will be described in the changelog. You can fix each of them by taking a look at the design library changelog.

16. Tests

For unit tests, if you were using @testing-library/react, there is a new util function that replaces render:

import { renderWithThemeProvider } from '@bulb/design/utils/testUtils/renderWithThemeProvider';

For end-to-end tests, make sure that you wrap you page component with <ThemeProvider>

17. Checking everything works

Run your application and check if everything is working fine!

Also, ask your QA to do a thorough regression test!