admin-on-rest-fr05t1k
Version:
A frontend Framework for building admin applications on top of REST services, using ES6, React and Material UI
308 lines (254 loc) • 10.7 kB
Markdown
---
layout: default
title: "Theming"
---
# Theming
Whether you need to adjust a CSS rule for a single component, or change the color of the labels in the entire app, you're covered!
## Overriding A Component Style
Most admin-on-rest components support two style props to set inline styles:
* `style`: A style object to customize the look and feel of the component container (e.g. the `<td>` in a datagrid). Most of the time, that's where you'll want to put your custom styles.
* `elStyle`: A style object to customize the look and feel of the component element itself, usually a material ui component. Use this prop when you want to fine tune the display of a material ui component, according to [their styling documentation]((http://www.material-ui.com/#/customization/styles)).
These props accept a style object:
{% raw %}
```js
import { EmailField } from 'admin-on-rest/mui';
<EmailField source="email" style={{ backgroundColor: 'lightgrey' }} elStyle={{ textDecoration: 'none' }} />
// renders in the datagrid as
<td style="background-color:lightgrey">
<a style="text-decoration:none" href="mailto:foo@example.com">
foo.com
</a>
</td>
```
{% endraw %}
Some components support additional props to style their own elements. For instance, when using a `<Datagrid>`, you can specify how a `<Field>` renders headers with the `headerStyle` prop. Here is how to make a column right aligned:
{% raw %}
```js
export const ProductList = (props) => (
<List {...props}>
<Datagrid>
<TextField source="sku" />
<TextField
source="price"
style={{ textAlign: 'right' }}
headerStyle={{ textAlign: 'right' }}
/>
<EditButton />
</Datagrid>
</List>
);
```
{% endraw %}
Refer to each component documentation for a list of supported style props.
If you need more control over the HTML code, you can also create your own [Field](./Fields.html#writing-your-own-field-component) and [Input](./Inputs.html#writing-your-own-input-component) components.
## Conditional Formatting
Sometimes you want the format to depend on the value. Admin-on-rest doesn't provide any special way to do it, because React already has all that's necessary - in particular, Higher-Order Components (HOCs).
For instance, if you want to highlight a `<TextField>` in red if the value is higher than 100, just wrap the field into a HOC:
{% raw %}
```js
const colored = WrappedComponent => props => props.record[props.source] > 100 ?
<span style={{ color: 'red' }}><WrappedComponent {...props} /></span> :
<WrappedComponent {...props} />;
const ColoredTextField = colored(TextField);
export const PostList = (props) => (
<List {...props}>
<Datagrid>
<TextField source="id" />
...
<ColoredTextField source="nb_views" />
<EditButton />
</Datagrid>
</List>
);
```
{% endraw %}
If you want to read more about higher-order components, check out this SitePoint tutorial: [Higher Order Components: A React Application Design Pattern](https://www.sitepoint.com/react-higher-order-components/)
## Responsive Utility
To provide an optimized experience on mobile, tablet, and desktop devices, you often need to display different components depending on the screen size. That's the purpose of the `<Responsive>` component, which offers a declarative approach to responsive web design.
It expects element props named `small`, `medium`, and `large`. It displays the element that matches the screen size (with breakpoints at 768 and 992 pixels):
```js
// in src/posts.js
import React from 'react';
import { List, Responsive, SimpleList, Datagrid, TextField, ReferenceField, EditButton } from 'admin-on-rest/lib/mui';
export const PostList = (props) => (
<List {...props}>
<Responsive
small={
<SimpleList
primaryText={record => record.title}
secondaryText={record => `${record.views} views`}
tertiaryText={record => new Date(record.published_at).toLocaleDateString()}
/>
}
medium={
<Datagrid>
<TextField source="id" />
<ReferenceField label="User" source="userId" reference="users">
<TextField source="name" />
</ReferenceField>
<TextField source="title" />
<TextField source="body" />
<EditButton />
</Datagrid>
}
/>
</List>
);
```
**Tip**: If you only provide `small` and `medium`, the `medium` element will also be used on large screens. The same kind of smart default exists for when you omit `small` or `medium`.
**Tip**: You can also use [material-ui's `withWith()` higher order component](https://github.com/callemall/material-ui/blob/master/src/utils/withWidth.js) to have the `with` prop injected in your own components.
## Using a Predefined Theme
Material UI also supports [complete theming](http://www.material-ui.com/#/customization/themes) out of the box. Material UI ships two base themes: light and dark. Admin-on-rest uses the light one by default. To use the dark one, pass it to the `<Admin>` component, in the `theme` prop (along with `getMuiTheme()`).
```js
import darkBaseTheme from 'material-ui/styles/baseThemes/darkBaseTheme';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
const App = () => (
<Admin theme={getMuiTheme(darkBaseTheme)} restClient={simpleRestClient('http://path.to.my.api')}>
// ...
</Admin>
);
```

## Writing a Custom Theme
If you need more fine tuning, you'll need to write your own `theme` object, following [Material UI themes documentation](http://www.material-ui.com/#/customization/themes). Material UI merges custom theme objects with the `light` theme.
```js
import {
cyan500, cyan700,
pinkA200,
grey100, grey300, grey400, grey500,
white, darkBlack, fullBlack,
} from 'material-ui/styles/colors';
import { fade } from 'material-ui/utils/colorManipulator';
import spacing from 'material-ui/styles/spacing';
const myTheme = {
spacing: spacing,
fontFamily: 'Roboto, sans-serif',
palette: {
primary1Color: cyan500,
primary2Color: cyan700,
primary3Color: grey400,
accent1Color: pinkA200,
accent2Color: grey100,
accent3Color: grey500,
textColor: darkBlack,
alternateTextColor: white,
canvasColor: white,
borderColor: grey300,
disabledColor: fade(darkBlack, 0.3),
pickerHeaderColor: cyan500,
clockCircleColor: fade(darkBlack, 0.07),
shadowColor: fullBlack,
},
};
```
The `muiTheme` 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`
* ...
**Tip**: Check [Material UI custom colors documentation](http://www.material-ui.com/#/customization/colors) to see pre-defined colors available for customizing your theme.
Once your theme is defined, pass it to the `<Admin>` component, in the `theme` prop (along with `getMuiTheme()`).
```js
const App = () => (
<Admin theme={getMuiTheme(myTheme)} restClient={simpleRestClient('http://path.to.my.api')}>
// ...
</Admin>
);
```
## Using a Custom Layout
Instead of the default layout, you can use your own component as the admin layout. Just use the `appLayout` prop of the `<Admin>` component:
```js
// in src/App.js
import MyLayout from './MyLayout';
const App = () => (
<Admin appLayout={MyLayout} restClient={simpleRestClient('http://path.to.my.api')}>
// ...
</Admin>
);
```
Use the [default layout](https://github.com/marmelab/admin-on-rest/blob/master/src/mui/layout/Layout.js) as a starting point for your custom layout. Here is a simplified version (with a fixed sidebar, and no responsive support):
```js
// in src/MyLayout.js
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import CircularProgress from 'material-ui/CircularProgress';
import injectTapEventPlugin from 'react-tap-event-plugin';
import { AppBar, Sidebar, Notification } from 'admin-on-rest/lib/mui';
import { setSidebarVisibility as setSidebarVisibilityAction } from 'admin-on-rest';
injectTapEventPlugin();
const styles = {
main: {
display: 'flex',
flexDirection: 'column',
minHeight: '100vh',
},
body: {
backgroundColor: '#edecec',
display: 'flex',
flex: 1,
overflow: 'hidden',
},
content: {
flex: 1,
padding: '2em',
},
loader: {
position: 'absolute',
top: 0,
right: 0,
margin: 16,
zIndex: 1200,
},
};
class MyLayout extends Component {
componentWillMount() {
this.props.setSidebarVisibility(true);
}
render() {
const { children, isLoading, menu, title } = this.props;
return (
<MuiThemeProvider>
<div style={styles.main}>
<AppBar title={title} />
<div className="body" style={styles.body}>
<div style={styles.content}>
{children}
</div>
<Sidebar theme={theme}>
{menu}
</Sidebar>
</div>
<Notification />
{isLoading && <CircularProgress
color="#fff"
size={width === 1 ? 20 : 30}
thickness={2}
style={styles.loader}
/>}
</div>
</MuiThemeProvider>
);
}
}
MyLayout.propTypes = {
isLoading: PropTypes.bool.isRequired,
children: PropTypes.node,
menu: PropTypes.element,
setSidebarVisibility: PropTypes.func.isRequired,
title: PropTypes.string.isRequired,
};
function mapStateToProps(state) {
return {
isLoading: state.admin.loading > 0,
};
}
export default connect(mapStateToProps, {
setSidebarVisibility: setSidebarVisibilityAction,
})(MyLayout);
```