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

[RFR] Add aside support in List, Edit, and Show views #2304

Merged
merged 7 commits into from
Sep 13, 2018
Merged
Show file tree
Hide file tree
Changes from 3 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
34 changes: 34 additions & 0 deletions docs/CreateEdit.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Here are all the props accepted by the `<Create>` and `<Edit>` components:

* [`title`](#page-title)
* [`actions`](#actions)
* [`aside`](#aside-component)

Here is the minimal code necessary to display a form to create and edit comments:

Expand Down Expand Up @@ -136,6 +137,39 @@ export const PostEdit = (props) => (

Using a custom `EditActions` component also allow to remove the `<DeleteButton>` if you want to prevent deletions from the admin.

### Aside component

You may want to display additional information on the side of the form. Use the `aside` prop for that, passing the component of your choice:

```jsx
const Aside = () => (
<div style={{ width: 200, margin: '1em' }}>
<Typography variant="title">Post details</Typography>
<Typography variant="body1">
Posts will only be published one an editor approves them
</Typography>
</div>
);

const PostEdit = props => (
<Edit aside={<Aside />} {...props}>
...
</Edit>
```

The `aside` component receives the same props as the `Edit` child component: `basePath`, `record`, `resource`, and `version`. That means you can display non-editable details of the current record in the aside component:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we add something about Create too ?


```jsx
const Aside = ({ record }) => (
<div style={{ width: 200, margin: '1em' }}>
<Typography variant="title">Post details</Typography>
<Typography variant="body1">
Creation date: {record.createdAt}
</Typography>
</div>
);
```

## Prefilling a `<Create>` Record

You may need to prepopulate a record based on another one. For that use case, use the `<CloneButton>` component. It expects a `record` and a `basePath` (usually injected to children of `<Datagrid>`, `<SimpleForm>`, `<SimpleShowLayout>`, etc.), so it's as simple to use as a regulat field or input.
Expand Down
49 changes: 49 additions & 0 deletions docs/List.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Here are all the props accepted by the `<List>` component:
* [`filter`](#permanent-filter) (the permanent filter used in the REST request)
* [`filterDefaultValues`](#filter-default-values) (the default values for `alwaysOn` filters)
* [`pagination`](#pagination)
* [`aside`](#aside-component)

Here is the minimal code necessary to display a list of posts:

Expand Down Expand Up @@ -580,6 +581,54 @@ export const PostList = (props) => (
);
```

### Aside component

You may want to display additional information on the side of the list. Use the `aside` prop for that, passing the component of your choice:

```jsx
const Aside = () => (
<div style={{ width: 200, margin: '1em' }}>
<Typography variant="title">Post details</Typography>
<Typography variant="body1">
Posts will only be published one an editor approves them
</Typography>
</div>
);

const PostList = props => (
<List aside={<Aside />} {...props}>
...
</List>
```

The `aside` component receives the same props as the `List` child component, including the following:

* `basePath`,
* `currentSort`,
* `data`,
* `defaultTitle`,
* `filterValues`,
* `ids`,
* `page`,
* `perPage`,
* `resource`,
* `selectedIds`,
* `total`,
* `version`,

That means you can display additional details of the current list in the aside component:

```jsx
const Aside = ({ data, ids }) => (
<div style={{ width: 200, margin: '1em' }}>
<Typography variant="title">Posts stats</Typography>
<Typography variant="body1">
Total views: {ids.map(id => data[id]).reduce((sum, post) => sum + post.views)}
</Typography>
</div>
);
```

### CSS API

The `List` component accepts the usual `className` prop but you can override many class names injected to the inner components by React-admin thanks to the `classes` property (as most Material UI components, see their [documentation about it](https://material-ui.com/customization/overrides/#overriding-with-classes)). This property accepts the following keys:
Expand Down
34 changes: 34 additions & 0 deletions docs/Show.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Here are all the props accepted by the `<Show>` component:

* [`title`](#page-title)
* [`actions`](#actions)
* [`aside`](#aside-component)

Here is the minimal code necessary to display a view to show a post:

Expand Down Expand Up @@ -115,6 +116,39 @@ export const PostShow = (props) => (
);
```

### Aside component

You may want to display additional information on the side of the resource detail. Use the `aside` prop for that, passing the component of your choice:

```jsx
const Aside = () => (
<div style={{ width: 200, margin: '1em' }}>
<Typography variant="title">Post details</Typography>
<Typography variant="body1">
Posts will only be published one an editor approves them
</Typography>
</div>
);

const PostShow = props => (
<Show aside={<Aside />} {...props}>
...
</Show>
```

The `aside` component receives the same props as the `Show` child component: `basePath`, `record`, `resource`, and `version`. That means you can display secondary details of the current record in the aside component:

```jsx
const Aside = ({ record }) => (
<div style={{ width: 200, margin: '1em' }}>
<Typography variant="title">Post details</Typography>
<Typography variant="body1">
Creation date: {record.createdAt}
</Typography>
</div>
);
```

## The `<SimpleShowLayout>` component

The `<SimpleShowLayout>` component receives the `record` as prop from its parent component. It is responsible for rendering the actual view.
Expand Down
13 changes: 13 additions & 0 deletions examples/simple/src/users/Aside.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';
import Typography from '@material-ui/core/Typography';

const Aside = () => (
<div style={{ width: 200, margin: '1em' }}>
<Typography variant="title">App Users</Typography>
<Typography variant="body1">
Eiusmod adipisicing tempor duis qui. Ullamco aliqua tempor incididunt aliquip aliquip qui ad minim aliqua. Aute et magna quis pariatur irure sunt. Aliquip velit consequat dolore ullamco laborum voluptate cupidatat. Proident minim reprehenderit id dolore elit sit occaecat ad amet tempor esse occaecat enim. Laborum aliqua excepteur qui ipsum in dolor et cillum est.
</Typography>
</div>
);

export default Aside;
4 changes: 3 additions & 1 deletion examples/simple/src/users/UserCreate.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
required,
} from 'react-admin';

import Aside from './Aside';

const UserEditToolbar = ({ permissions, ...props }) => (
<Toolbar {...props}>
<SaveButton
Expand All @@ -30,7 +32,7 @@ const UserEditToolbar = ({ permissions, ...props }) => (
);

const UserCreate = ({ permissions, ...props }) => (
<Create {...props}>
<Create {...props} aside={<Aside />}>
<TabbedForm toolbar={<UserEditToolbar permissions={permissions} />}>
<FormTab label="user.form.summary" path="">
<TextInput
Expand Down
4 changes: 3 additions & 1 deletion examples/simple/src/users/UserEdit.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ import {
TextInput,
required,
} from 'react-admin';

import UserTitle from './UserTitle';
import Aside from './Aside';

const UserEdit = ({ permissions, ...props }) => (
<Edit title={<UserTitle />} {...props}>
<Edit title={<UserTitle />} aside={<Aside />} {...props}>
<TabbedForm defaultValue={{ role: 'user' }}>
<FormTab label="user.form.summary" path="">
{permissions === 'admin' && <DisabledInput source="id" />}
Expand Down
3 changes: 3 additions & 0 deletions examples/simple/src/users/UserList.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {
} from 'react-admin';
export const UserIcon = PeopleIcon;

import Aside from './Aside';

const UserFilter = ({ permissions, ...props }) => (
<Filter {...props}>
<SearchInput source="q" alwaysOn />
Expand All @@ -31,6 +33,7 @@ const UserList = ({ permissions, ...props }) => (
filters={<UserFilter permissions={permissions} />}
filterDefaultValues={{ role: 'user' }}
sort={{ field: 'name', order: 'ASC' }}
aside={<Aside />}
>
<Responsive
small={
Expand Down
4 changes: 3 additions & 1 deletion examples/simple/src/users/UserShow.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Show, Tab, TabbedShowLayout, TextField } from 'react-admin'; // eslint-disable-line import/no-unresolved

import UserTitle from './UserTitle';
import Aside from './Aside';

const UserShow = ({ permissions, ...props }) => (
<Show title={<UserTitle />} {...props}>
<Show title={<UserTitle />} aside={<Aside />} {...props}>
<TabbedShowLayout>
<Tab label="user.form.summary">
<TextField source="id" />
Expand Down
33 changes: 30 additions & 3 deletions packages/ra-ui-materialui/src/detail/Create.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import React from 'react';
import PropTypes from 'prop-types';
import Card from '@material-ui/core/Card';
import { withStyles } from '@material-ui/core/styles';
import classnames from 'classnames';
import { CreateController } from 'ra-core';

import TitleForRecord from '../layout/TitleForRecord';
import CardContentInner from '../layout/CardContentInner';

const styles = {
root: {
display: 'flex',
},
card: {
flex: '1 1 auto',
},
};

const sanitizeRestProps = ({
actions,
children,
Expand All @@ -31,8 +41,10 @@ const sanitizeRestProps = ({

export const CreateView = ({
actions,
aside,
basePath,
children,
classes,
className,
defaultTitle,
hasList,
Expand All @@ -45,15 +57,15 @@ export const CreateView = ({
...rest
}) => (
<div
className={classnames('create-page', className)}
className={classnames('create-page', classes.root, className)}
{...sanitizeRestProps(rest)}
>
<TitleForRecord
title={title}
record={record}
defaultTitle={defaultTitle}
/>
<Card>
<Card className={classes.card}>
{actions && (
<CardContentInner>
{React.cloneElement(actions, {
Expand All @@ -74,13 +86,22 @@ export const CreateView = ({
save,
})}
</Card>
{aside &&
React.cloneElement(aside, {
basePath,
record,
resource,
save,
})}
</div>
);

CreateView.propTypes = {
actions: PropTypes.element,
aside: PropTypes.node,
basePath: PropTypes.string,
children: PropTypes.element,
classes: PropTypes.object,
className: PropTypes.string,
defaultTitle: PropTypes.any,
hasList: PropTypes.bool,
Expand All @@ -92,6 +113,10 @@ CreateView.propTypes = {
title: PropTypes.any,
};

CreateView.defaultProps = {
classes: {},
};

/**
* Page component for the Create view
*
Expand Down Expand Up @@ -141,7 +166,9 @@ const Create = props => (

Create.propTypes = {
actions: PropTypes.element,
aside: PropTypes.node,
children: PropTypes.element,
classes: PropTypes.object,
className: PropTypes.string,
hasCreate: PropTypes.bool,
hasEdit: PropTypes.bool,
Expand All @@ -152,4 +179,4 @@ Create.propTypes = {
hasList: PropTypes.bool,
};

export default Create;
export default withStyles(styles)(Create);
Loading