It's another week and we're going to, once again, be looking at MaterialSwitch
, hopefully it's not getting boring yet. I'm going to add some themeing, using styled-components, that allows the app to pull from an object of presets but also make one off changes when they're needed.
Here's what we're going to be making. I've switched over to codesandbox, from jsFiddle, in order to have multiple files and have it closer to a real dev experience.
Theme file
In the theme file, called interface/theme.js
we have a couple of functions and the theme object.
The theme object is very simple for this project but it can get more and more complicated as you expand your theme. This is our theme object.
export const theme = {
toggle: {
active: "#00897B",
inactive: "#bdbdbd",
shadow: "0 0 8px rgba(0, 0, 0, 0.2), 0 0 2px rgba(0, 0, 0, 0.4)"
},
general: {
typography: {
fontFamily: '"Open Sans", "Arial"'
},
timingFunction: {
easeInOut: "cubic-bezier(0.4, 0, 0.2, 1)"
}
}
};
There are two functions both are used for modifying the theme and making that process as easy as possible. Only one is exported for use though. Let's take a look.
// loop through all levels of an object and update theme accordingly
function _deepSetObject(newTheme, originalTheme) {
let combinedTheme = Object.assign({}, originalTheme);
for (let key of Object.keys(newTheme)) {
if (typeof newTheme[key] === "object" && !Array.isArray(newTheme[key])) {
combinedTheme[key] = _deepSetObject(newTheme[key], combinedTheme[key]);
} else {
combinedTheme[key] = newTheme[key];
}
}
return combinedTheme;
}
// Function to get full theme with modifications
const themeModify = newTheme => {
if (!newTheme || typeof newTheme !== "object" || Array.isArray(newTheme))
return theme;
return _deepSetObject(newTheme, theme);
};
export default themeModify;
_deepSetObject
This function simply goes through our object and updates the theme accordingly. This way we can send only the parts of the theme object we want to change.
themeModify
This function takes a new theme object and uses _deepSetObject
to generate a theme object to return. If it does not get fed an object to start with it will return the original theme with no modifications.
Changes to MaterialSwitch
We're going to have to make some changes to MaterialSwitch
now that we're using styled-components, for instance all our CSS is now in the JS file.
Imports
Our imports now include styled
and ThemeProvider
from styled-components
, I'll show you how they're used soon, and also our themeModify
, which we exported from our theme.
import React from "react";
import styled, { ThemeProvider } from "styled-components";
import themeModify from "./theme";
styled
styled
lets us create a standard HTMLElement
and attach some CSS to it. Generally it's a good idea to create a wrapper div or, as in our case, a different wrapper element. As we already had a label as our outer most element I used styled
to remake that.
const Label = styled.label`
/* styles go here */
`
As you may have noticed we saved the output of styled
to Label
and that is now out replacement label element. Like so,
<Label>
<!-- HTML goes here -->
<Label>
The styles that we write inside styled
are like Sass which is quite nice as it allows us to write our CSS in a more modern manor.
Now let's look at how we use items from our theme object in styled
const Label = styled.label`
display: inline-flex;
font-family: ${props => props.theme.general.typography.fontFamily};
align-items: center;
margin: 5px 0;
& span {
position: relative;
cursor: pointer;
/* rest of styles */
}
`
Thank to template literals we simply have to select the layer of our object. For toggle active colour we'd have a slightly different path but it's the same method.
${props => props.theme.toggle.active};
JSX
Let's look at the HTML
like part of the component. Not much has changed from the last iteration we've added a ThemeProvider
element as a wrapper around the whole thing and we've changed our label
component to our new styled
versions called Label
.
<ThemeProvider theme={themeModify(props.theme)}>
<Label>
<input
readOnly={readOnly}
disabled={disabled}
defaultChecked={defaultChecked}
onChange={changeHandler}
type="checkbox"
/>
<span />
{children}
</Label>
</ThemeProvider>
You'll notice our themeModify
function is now being used, we use it to feed out theme, modified or otherwise, into the ThemeProvider
.
Result
That was a lot of code but I think it's worth it in the long run. Let's look at how we can used this code now.
export default function App() {
const magentaTheme = {
toggle: {
active: "#FF00FF",
inactive: "#bb9cbb"
}
};
return (
<div className="App">
<MaterialSwitch>Default theme</MaterialSwitch>
<MaterialSwitch theme={magentaTheme}>Custom theme</MaterialSwitch>
</div>
);
}
This is the code for the screen you saw in the demo, at the top of the post. Two elements, one using the default theme and one with a slightly variance. We can attach a theme object with the same structure as our main theme, all the parts missing are filled in, to the theme property.
This is very powerful as a theme object can contain every aspect of your program. Using this method it is even possible to load themes from a database, update a theme based on a local text input and a whole host of other things. It's very exciting.
Signing off
Thank you for reading I hope you got something out of it, I certainly did. Feel free to leave question, corrections or anything else in the comments down below.
Thanks again 🦄🦄💕❤️🧡💛💚🤓🧠