Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Theme] Improve the current approach #3340

Merged
merged 1 commit into from
Feb 19, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 10 additions & 14 deletions docs/src/app/components/pages/customization/themes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ import getMuiTheme from 'material-ui/lib/styles/getMuiTheme';

import themesText from './themes.md';

const markdownText = `
## Themes

### Examples

You can use the tabs to change the theme. The changes will be applied to the whole
documentation.
`;

const {
Checkbox,
ClearFix,
Expand Down Expand Up @@ -104,15 +113,6 @@ const ThemesPage = React.createClass({
marginBottom: 32,
overflow: 'hidden',
},
headline: {
fontSize: '24px',
lineHeight: '32px',
paddingTop: '16px',
marginBottom: '12px',
letterSpacing: '0',
fontWeight: Typography.fontWeightNormal,
color: Typography.textDarkBlack,
},
bottomBorderWrapper: {
borderBottom: `1px solid ${borderColor}`,
paddingBottom: '10px',
Expand Down Expand Up @@ -360,19 +360,15 @@ const ThemesPage = React.createClass({
},

render() {

const styles = this.getStyles();

return (
<div>
<Title render={(previousTitle) => `Themes - ${previousTitle}`} />

<h2 style={styles.headline}>Themes</h2>

<MarkdownElement text={markdownText} />
<Paper style={styles.liveExamplePaper}>
<ClearFix style={styles.liveExampleBlock}>{this.getThemeExamples()}</ClearFix>
</Paper>

<div style={styles.bottomBorderWrapper}>
<MarkdownElement text={themesText} />
</div>
Expand Down
164 changes: 87 additions & 77 deletions docs/src/app/components/pages/customization/themes.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
There are now two kinds of themes in Material-UI: **`baseTheme`** and **`muiTheme`**.
The base theme is a plain JS object containing three keys: spacing, palette and fontFamily.
The mui theme, on the other hand, is a much bigger object. It contains a key for every Material-UI
component, and the value corresponding to that key describes the styling of that particular component
under the current base theme. In this sense, the mui theme is *produced* from the base theme.
The base theme acts as a basis for styling components, whereas the mui theme contains specific values
(that are calculated based on the base theme) for styling each component.
### How it works

## Customizing the theme
To achieve the level of customizability that you can see in the example above,
Material-UI is using a single JS object called `muiTheme`.
By default, this `muiTheme` object is based on the
[`lightBaseTheme`](https://github.com/callemall/material-ui/blob/master/src/styles/baseThemes/lightBaseTheme.js).

By default the light base theme is used to calculate the mui theme object. To customize the base theme
or the calculated mui theme you can use `getMuiTheme` to create a theme object and pass it down the context:
This object contains the following keys:
- `spacing`: can be used to change the spacing of components.
- `fontFamily` can be used to change the default font family.
- `palette` can be used to change the color of components.
- `zIndex` can be used to change the level of each component.
- `isRtl` can be used to enable the right to left mode.
- There is also one key for each component so you can use to customize them individually:
- `appBar`
- `avatar`
- ...

### Customizing the theme

To customize the `muiTheme` you must use `getMuiTheme()` to compute a valid `muiTheme`.
Then, you can use `<MuiThemeProvider />` to provide it down the tree to components.

```js
import React from 'react';
Expand All @@ -18,10 +28,17 @@ import MuiThemeProvider from 'material-ui/lib/MuiThemeProvider';
import getMuiTheme from 'material-ui/lib/styles/getMuiTheme';
import AppBar from 'material-ui/lib/app-bar';

// This replaces the textColor value on the palette of
// baseTheme and then calculates the theme based on it.
// This replaces the textColor value on the palette
// and then update the keys for each component that depends on it.
// More on Colors: http://www.material-ui.com/#/customization/colors
const muiTheme = getMuiTheme({palette: {textColor: Colors.cyan500}});
const muiTheme = getMuiTheme({
palette: {
textColor: Colors.cyan500,
},
appBar: {
height: 50,
},
});

class Main extends React.Component {
render() {
Expand All @@ -30,7 +47,7 @@ class Main extends React.Component {
// lazily calculated theme is used instead.
return (
<MuiThemeProvider muiTheme={muiTheme}>
<AppBar title="Hello World!"/>
<AppBar title="My AppBar" />
</MuiThemeProvider>
);
}
Expand All @@ -39,71 +56,72 @@ class Main extends React.Component {
export default Main;
```

Internally, Material-UI components use React's context feature to implement theming. Context is a way
to pass down values through the component hierarchy without having to use props at every level.
In fact, context is very convenient for concepts like theming, which are usually implemented in
a hierarchical manner.

`MuiThemeProvider` uses this feature to pass down the theme to components that need it.
Internally, Material-UI components use React's context feature to implement theming.
Context is a way to pass down values through the component hierarchy without having
to use props at every level.
In fact, context is very convenient for concepts like theming, which are usually
implemented in a hierarchical manner.

## Using the theme
### Predefined themes

In case you wish to access the theme object yourself you can use the `muiThemeable` decorator:
We ship two base themes with Material-UI: light and dark. They are located
under [`material-ui/lib/styles/baseThemes/`](https://github.com/callemall/material-ui/blob/master/src/styles/baseThemes/).
Custom themes may be defined similarly.
The [`lightBaseTheme`](https://github.com/callemall/material-ui/blob/master/src/styles/baseThemes/lightBaseTheme.js)
is the default so you will not need to do anything to use it.
But for the [`darkBaseTheme`](https://github.com/callemall/material-ui/blob/master/src/styles/baseThemes/darkBaseTheme.js) you can use this snippet:

```js
import React from 'react';
import muiThemeable from 'material-ui/lib/muiThemeable';
import darkBaseTheme from 'material-ui/lib/styles/baseThemes/darkBaseTheme';
import MuiThemeProvider from 'material-ui/lib/MuiThemeProvider';
import getMuiTheme from 'material-ui/lib/styles/getMuiTheme';
import AppBar from 'material-ui/lib/app-bar';

class DeepDownTheTree extends React.Component {
const darkMuiTheme = getMuiTheme(darkBaseTheme);

class Main extends React.Component {
render() {
return (
<span style={{color: this.props.muiTheme.baseTheme.palette.textColor}}>
Hello World!
</span>
<MuiThemeProvider muiTheme={darkMuiTheme}>
<AppBar title="My AppBar" />
</MuiThemeProvider>
);
}
}

DeepDownTheTree.propTypes = {
muiTheme: React.PropTypes.object,
};

export default muiThemeable()(DeepDownTheTree);
export default Main;
```

`muiThemeable` gets the theme from context and passes it down as a property.

## Predefined themes

We ship two base themes with Material-UI: light and dark. They are located
under `material-ui/lib/styles/baseThemes/`. Custom themes may be defined similarly.
### Using the theme

The `lightBaseTheme` is the default so you will not need to do anything to use it.
But for the `darkBaseTheme` you can use this snippet:
In case you wish to access the theme object yourself you can use the
`muiThemeable` decorator:

```js
import React from 'react';
import darkBaseTheme from 'material-ui/lib/styles/baseThemes/darkBaseTheme';
import MuiThemeProvider from 'material-ui/lib/MuiThemeProvider';
import getMuiTheme from 'material-ui/lib/styles/getMuiTheme';
import AppBar from 'material-ui/lib/app-bar';

const darkMuiTheme = getMuiTheme(darkBaseTheme);
import muiThemeable from 'material-ui/lib/muiThemeable';

class Main extends React.Component {
class DeepDownTheTree extends React.Component {
render() {
return (
<MuiThemeProvider muiTheme={darkMuiTheme}>
<AppBar title="Hello World!"/>
</MuiThemeProvider>
<span style={{color: this.props.muiTheme.palette.textColor}}>
Hello World!
</span>
);
}
}

export default Main;
DeepDownTheTree.propTypes = {
muiTheme: React.PropTypes.object.isRequired,
};

export default muiThemeable()(DeepDownTheTree);
```

## Using context
`muiThemeable` gets the theme from context and passes it down as a property.

### Using context

The `MuiThemeProvider` component and `muiThemeable` decorator simply use context.
If you prefer using context instead of these you can follow these pattern:
Expand All @@ -122,16 +140,12 @@ class Main extends React.Component {
}

render () {
return (
<div>
<AppBar title="My AppBar"/>
</div>
);
return <AppBar title="My AppBar" />;
}
}

Main.childContextTypes = {
muiTheme: React.PropTypes.object,
muiTheme: React.PropTypes.object.isRequired,
};

export default Main;
Expand All @@ -145,42 +159,43 @@ import React from 'react';
class DeepDownTheTree extends React.Component {
render () {
return (
<span style={{color: this.context.muiTheme.baseTheme.palette.textColor}}>
<span style={{color: this.context.muiTheme.palette.textColor}}>
Hello World!
</span>
);
}
}

DeepDownTheTree.contextTypes = {
muiTheme: React.PropTypes.object,
muiTheme: React.PropTypes.object.isRequired,
};

export default DeepDownTheTree;
```

## API
### API

The items listed below are everything related to how Material-UI's theme work.

### `getMuiTheme(baseTheme, themeOverrides) => muiTheme`
#### `getMuiTheme(muiTheme) => muiTheme`

This function takes in the `baseTheme` merges in onto the default base theme that is the
[lightBaseTheme](https://github.com/callemall/material-ui/blob/master/src/styles/baseThemes/lightBaseTheme.js)
and calculates the muiTheme from it. In other words anything you don't specify on the
`baseTheme` object will be picked up from the `lightBaseTheme`.
This function takes in a `muiTheme`, it will use this parameter to computes the right keys.

Keep in mind, any changes to the theme object must appear as another call to this function.
Keep in mind, any changes to the theme object must appear as another call
to this function.
**Never** directly mutate the theme as the effects will not be reflected in any component
until another render is triggered for that component leaving your application in a moody state.
until another render is triggered for that component leaving your application
in a moody state.

The `baseTheme` object looks like this (these are the defaults):
To see what are the values you can override, use the
[source](https://github.com/callemall/material-ui/blob/master/src/styles/getMuiTheme.js).
The `lightBaseTheme` object looks like this (these are the defaults):

```js
import Colors from 'material-ui/lib/styles/colors';
import ColorManipulator from 'material-ui/lib/utils/color-manipulator';

const baseTheme = {
const lightBaseTheme = {
spacing: {
iconSize: 24,
desktopGutter: 24,
Expand Down Expand Up @@ -214,18 +229,13 @@ const baseTheme = {
};
```

The second argument can be used to override the values calculated from the `baseTheme`.
To see what are the values you can override with this argument. Use the
[source](https://github.com/callemall/material-ui/blob/master/src/styles/getMuiTheme.js#L23-L262)
, Luke...

### `<MuiThemeProvider/>`
#### `<MuiThemeProvider />`

This component takes a theme as a property and passes it down with context.
This should preferably be at the root of your component tree. The first
example demonstrates it's usage.

### `muiThemeable() => ThemeWrapper(Component) => WrappedComponent`
#### `muiThemeable() => ThemeWrapper(Component) => WrappedComponent`

This function creates a wrapper function that you can call providing a component.
The resulting component from calling `ThemeWrapper` is a higher order component (HOC)
Expand Down
28 changes: 19 additions & 9 deletions src/styles/getMuiTheme.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,26 @@ import Typography from '../styles/typography';
* by providing a second argument. The calculated
* theme will be deeply merged with the second argument.
*/
export default function getMuiTheme(baseTheme, muiTheme) {
baseTheme = merge({}, lightBaseTheme, baseTheme);
export default function getMuiTheme(muiTheme, ...more) {
muiTheme = merge({
zIndex,
isRtl: false,
userAgent: undefined,
}, lightBaseTheme, muiTheme, ...more);

const {
spacing,
fontFamily,
palette,
} = muiTheme;

const baseTheme = {
spacing,
} = baseTheme;
fontFamily,
palette,
};

muiTheme = merge({
isRtl: false,
userAgent: undefined,
zIndex,
baseTheme,
rawTheme: baseTheme, // To provide backward compatibility.
appBar: {
color: palette.primary1Color,
textColor: palette.alternateTextColor,
Expand Down Expand Up @@ -274,7 +281,10 @@ export default function getMuiTheme(baseTheme, muiTheme) {
backgroundColor: 'transparent',
borderColor: palette.borderColor,
},
}, muiTheme);
}, muiTheme, {
baseTheme, // To provide backward compatibility.
rawTheme: baseTheme, // To provide backward compatibility.
});

const transformers = [autoprefixer, rtl, callOnce].map((t) => t(muiTheme))
.filter((t) => t);
Expand Down
Loading