The React Native components style is usually defined as a static variable along with the component itself. This makes it easy to build self-contained components that always look and behave the same way. On the other hand, it complicates building customizable (or skinnable) components that could have multiple styles which could be customized without touching the component’s source code.
One of our main goals was to add support for themes to components with as little changes as possible to the components themselves. To add support for themes to your component you only need to make two minor changes to it.
@shoutem/theme
is available on npm:
$ npm install --save @shoutem/theme
The main thing you need to change is to start using the style rules from the props.style
property, instead of using the static variable defined alongside the component. You can define the default style of the component statically (the same way as before) but you shouldn’t use that property to get the actual style in runtime. This allows us to merge the default style with any theme style that may be active in the app, and provide the final style to components.
We will now demonstrate how simple it is to make an existing component customizable for an example. Let’s start by implementing a simple component that has a static style:
import React, { Component } from 'react';
import { StyleSheet, Text, View, Image } from 'react-native';
export default class AvatarItem extends Component {
render() {
return (
<View style={styles.container}>
<Image style={styles.avatarImage} source={{ uri: 'https://shoutem.github.io/img/ui-toolkit/examples/image-9.png' }} />
<Text style={styles.title}>John Doe</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
height: 50,
flexDirection: 'row',
justifyContent: 'center',
},
avatarImage: {
width: 36,
height: 36,
borderRadius: 18,
},
title: {
flex: 1,
fontSize: 19,
fontWeight: 'bold',
},
});
In order to support themes, we need to:
styles
with this.props.style
import React, { Component } from 'react';
import { Text, View, Image } from 'react-native';
import { connectStyle } from '@shoutem/theme';
class AvatarItem extends Component {
// connect styles to props.style defined by the theme
const styles = this.props.style;
render() {
return (
<View style={styles.container}>
<Image style={styles.avatarImage} source={{ uri: 'https://shoutem.github.io/img/ui-toolkit/examples/image-9.png' }} />
<Text style={styles.title}>John Doe</Text>
</View>
);
}
}
const styles = {
container: {
flex: 1,
height: 50,
flexDirection: 'row',
justifyContent: 'center',
},
avatarImage: {
width: 36,
height: 36,
borderRadius: 18,
},
title: {
flex: 1,
fontSize: 19,
fontWeight: 'bold',
},
};
// connect the component to the theme
export default connectStyle('com.example.AvatarItem', styles)(AvatarItem);
The connectStyle
function receives two arguments. The first one represents the fully qualified name that component will be referenced by in the theme, and the second one is the default component style. Fully qualified name of the component needs to have namespace
prefix, separated with .
from the component name.
Any styles defined in the theme will be merged with the default style, and theme rules will override the rules from the default style. The style that is sent to connectStyle
shouldn’t be created using the StyleSheet.create
. Style sheet will be created by the connectStyle
function at at appropriate time.
With those simple changes, we have a component that can receive styles from the outside. The only thing left to do is to initialize the style provider within the app, so that theme styles are correctly distributed to components. To do this, we need to initialize the StyleProvider
component, and render any customizable components within it:
import React, { Component } from 'react';
import { StyleProvider } from '@shoutem/theme';
class App extends Component {
render() {
return (
<StyleProvider style={theme}>
// any app components
</StyleProvider>
);
}
}
// Define a theme
const theme = {
'com.example.AvatarItem': {
// overrides AvatarItem component style...
},
};
All styles defined as a part of the theme may be regular React Native styles, but there are several new types of style rules that are supported in themes as well. We will explain all those rules on the Card
component from the UI toolkit:
<Card>
<Image styleName="banner" source={{ uri: 'https://shoutem.github.io/img/ui-toolkit/examples/image-10.png' }} />
<View styleName="card-content">
<Subtitle numberOfLines={4}>Choosing The Right Boutique Hotel For You</Subtitle>
<Divider styleName="empty" />
<Caption>21 hours ago</Caption>
</View>
</Card>
const theme = {
'shoutem.ui.Card': {
// card component variants
'.dark': {
backgroundColor: '#000'
},
'.light': {
backgroundColor: '#fff'
},
// style variant available to child components of any type
'*.card-content': {
padding: 15
},
// style that will be applied to all child image components
'shoutem.ui.Image': {
flex: 1,
resizeMode: 'cover',
},
// style variant available to child image comoponents
'shoutem.ui.Image.banner': {
height: 85
},
// default card style, we usually place these rules at the bottom
backgroundColor: '#fff',
borderRadius: 2,
// card shadow style
shadowColor: 'black',
shadowRadius: 9,
shadowOpacity: 0.3,
shadowOffset: { width: 5, height: 7 }
}
}
The default component style is at the bottom of the style object. This style will always be applied as a base style to all cards. After that, any theme style will be merged with that style, i.e., the theme style rules will override the base component rules. In the end, any style specified through the style
prop directly on the component will be merged on top of the styles mentioned above to get the final component style.
Rules above the default component style are the new rule types that are specific to theme styles.
The .dark
, and .light
rules are card variants that can be activated by using the styleName
prop on the card component. For example, a card with a dark variant would look like this:
<Card styleName="dark">
...
</Card>
The rest of the rules in the style object are rules that will be applied to child components of a card. Each of those rules has two components, the component type, and the optional style name. The rule *.card-content
will be available to child components of a card of any type. This rule can be applied to a child component by using the styleName
prop, for example:
<Card>
<View styleName="card-content">
...
</View>
</Card>
The remaining two rules will be applied only to images. shoutem.ui.Image
will be applied to all Image
components added to a card, and the rule shoutem.ui.Image.banner
will be applied to all images with a styleName="banner"
prop.