Skip to content

Commit

Permalink
Merge pull request #5905 from marmelab/scrollToTop
Browse files Browse the repository at this point in the history
Add scrollToTop on key navigation links
  • Loading branch information
djhi authored Feb 15, 2021
2 parents c171a0a + 0e0a64f commit 7304d11
Show file tree
Hide file tree
Showing 29 changed files with 559 additions and 37 deletions.
308 changes: 308 additions & 0 deletions docs/Buttons.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
---
layout: default
title: "Buttons"
---

# Buttons

React-Admin provides button components for all the common uses.

## Navigation Buttons

These buttons allow users to navigate between the various react-admin views.

### `<EditButton>`

Opens the Edit view of a given record:

```js
import { EditButton } from 'react-admin';

const CommentEditButton = ({ record }) => (
<EditButton basePath="/comments" label="Edit comment" record={record} />
);
```

![Edit button](./img/edit-button.png)

`<EditButton>` is based on react-admin's base `<Button>`, so it's responsive, accessible, and the label is translatable.

| Prop | Required | Type | Default | Description |
| ------------- | -------- | --------------- | ---------------- | ------------------------------------------------ |
| `basePath` | Required | `string` | - | Base path to resource, e.g. '/posts' |
| `record` | Required | `Object` | - | Record to link to, e.g. `{ id: 12, foo: 'bar' }` |
| `label` | Optional | `string` | 'ra.action.edit' | Label or translation message to use |
| `icon` | Optional | `React.element` | - | Icon element, e.g. `<CommentIcon />` |
| `scrollToTop` | Optional | `boolean` | `true` | Scroll to top after link |

It also supports [all the other `<Button>` props](#button).

**Tip**: You can use it as `<Datagrid>` child with no props, since `<Datagrid>` injects `record` and `basePath` to its children. However, you should use the `<Datagrid rowClick="edit">` prop instead to avoid using one column for the Edit button.

**Tip**: If you want to link to the Edit view manually, use the `/{resource}/{record.id}` location.

### `<ShowButton>`

Opens the Show view of a given record:

```js
import { ShowButton } from 'react-admin';

const CommentShowButton = ({ record }) => (
<ShowButton basePath="/comments" label="Show comment" record={record} />
);
```

![Show button](./img/show-button.png)

`<ShowButton>` is based on react-admin's base `<Button>`, so it's responsive, accessible, and the label is translatable.

| Prop | Required | Type | Default | Description |
| ------------- | -------- | --------------- | ---------------- | ------------------------------------------------ |
| `basePath` | Required | `string` | - | Base path to resource, e.g. '/posts' |
| `record` | Required | `Object` | - | Record to link to, e.g. `{ id: 12, foo: 'bar' }` |
| `label` | Optional | `string` | 'ra.action.show' | Label or translation message to use |
| `icon` | Optional | `React.element` | - | Icon element, e.g. `<CommentIcon />` |
| `scrollToTop` | Optional | `boolean` | `true` | Scroll to top after link |

It also supports [all the other `<Button>` props](#button).

**Tip**: You can use it as `<Datagrid>` child with no props, since `<Datagrid>` injects `record` and `basePath` to its children. However, you should use the `<Datagrid rowClick="show">` prop instead to avoid using one column for the Edit button.

**Tip**: If you want to link to the Show view manually, use the `/{resource}/{record.id}/show` location.

### `<CreateButton>`

Opens the Create view of a given resource:

```js
import { CreateButton } from 'react-admin';

const CommentCreateButton = () => (
<CreateButton basePath="/comments" label="Create comment" />
);
```

![Create button](./img/create-button.png)

`<CreateButton>` is based on react-admin's base `<Button>`, so it's responsive, accessible, and the label is translatable. On mobile, it turns into a "Floating Action Button".

![Create button FAB](./img/create-button-fab.png)

| Prop | Required | Type | Default | Description |
| ------------- | -------- | --------------- | ------------------ | -------------------------------------------- |
| `basePath` | Required | `string` | - | base path to resource, e.g. '/posts' |
| `label` | Optional | `string` | 'ra.action.create' | label or translation message to use |
| `icon` | Optional | `React.element` | - | iconElement, e.g. `<CommentIcon />` |
| `scrollToTop` | Optional | `boolean` | `true` | Scroll to top after link |

It also supports [all the other `<Button>` props](#button).

**Tip**: If you want to link to the Create view manually, use the `/{resource}/create` location.

### `<ListButton>`

Opens the List view of a given resource:

```js
import { ListButton } from 'react-admin';

const CommentListButton = () => (
<ListButton basePath="/comments" label="Comments" />
);
```

![List button](./img/list-button.png)

`<ListButton>` is based on react-admin's base `<Button>`, so it's responsive, accessible, and the label is translatable.

By default, react-admin doesn't display a `<ListButton>` in Edit and Show views action toolbar. This saves visual clutter, and users can always use the back button. You can add it by specifying your own `actions`:

```jsx
// linking back to the list from the Edit view
import { TopToolbar, ListButton, ShowButton, Edit } from 'react-admin';

const PostEditActions = ({ basePath, record, resource }) => (
<TopToolbar>
<ListButton basePath={basePath} />
<ShowButton basePath={basePath} record={record} />
</TopToolbar>
);

export const PostEdit = (props) => (
<Edit actions={<PostEditActions />} {...props}>
...
</Edit>
);
```

| Prop | Required | Type | Default | Description |
| ---------- | -------- | --------------- | ---------------- | -------------------------------------------- |
| `basePath` | Required | `string` | - | base path to resource, e.g. '/posts' |
| `label` | Optional | `string` | 'ra.action.list' | label or translation message to use |
| `icon` | Optional | `React.element` | - | iconElement, e.g. `<CommentIcon />` |

It also supports [all the other `<Button>` props](#button).

**Tip**: If you want to link to the List view manually, use the `/{resource}` location.

## List Buttons

The following buttons are designed to be used in List views.

### `<ExportButton>`

Exports the current list, with filters applied, but without pagination. It relies on [the `exporter` function](./List.md#exporter) passed to the `<List>` component, via the `ListContext`. It's disabled for empty lists.

By default, the `<ExportButton>` is included in the List actions.

```jsx
import { CreateButton, ExportButton, TopToolbar } from 'react-admin';

const PostListActions = ({ basePath }) => (
<TopToolbar>
<PostFilter context="button" />
<CreateButton basePath={basePath} />
<ExportButton />
</TopToolbar>
);

export const PostList = (props) => (
<List actions={<PostListActions />} {...props}>
...
</List>
);
```

![Export button](./img/export-button.png)

| Prop | Required | Type | Default | Description |
| ------------ | -------- | --------------- | ------------------ | ----------------------------------- |
| `maxResults` | Optional | `number` | 1000 | Maximum number of records to export |
| `label` | Optional | `string` | 'ra.action.export' | label or translation message to use |
| `icon` | Optional | `React.element` | `<DownloadIcon>` | iconElement, e.g. `<CommentIcon />` |
| `exporter` | Optional | `function` | - | Override the List exporter function |

### `<BulkExportButton>`

Same as `<ExportButton>`, except it only exports the selected rows instead of the entire list. To be used inside [the `<List bulkActionButtons>` prop](./List.md#bulkactionbuttons).

```jsx
import * as React from 'react';
import { Fragment } from 'react';
import { BulkDeleteButton, BulkExportButton } from 'react-admin';

const PostBulkActionButtons = ({ basePath }) => (
<Fragment>
<BulkExportButton />
<BulkDeleteButton basePath={basePath} />
</Fragment>
);

export const PostList = (props) => (
<List {...props} bulkActionButtons={<PostBulkActionButtons />}>
...
</List>
);
```

![Bulk Export button](./img/bulk-export-button.png)

| Prop | Required | Type | Default | Description |
| ------------ | -------- | --------------- | ------------------ | ----------------------------------- |
| `label` | Optional | `string` | 'ra.action.export' | label or translation message to use |
| `icon` | Optional | `React.element` | `<DownloadIcon>` | iconElement, e.g. `<CommentIcon />` |
| `exporter` | Optional | `function` | - | Override the List exporter function |

### `<BulkDeleteButton>`

Deletes the selected rows. To be used inside [the `<List bulkActionButtons>` prop](./List.md#bulkactionbuttons) (where it's enabled by default).

```jsx
import * as React from 'react';
import { Fragment } from 'react';
import { BulkDeleteButton, BulkExportButton } from 'react-admin';

const PostBulkActionButtons = ({ basePath }) => (
<Fragment>
<BulkExportButton />
<BulkDeleteButton basePath={basePath} />
</Fragment>
);

export const PostList = (props) => (
<List {...props} bulkActionButtons={<PostBulkActionButtons />}>
...
</List>
);
```

![Bulk Delete button](./img/bulk-delete-button.png)

| Prop | Required | Type | Default | Description |
| ------------ | -------- | --------------- | ------------------ | ----------------------------------- |
| `label` | Optional | `string` | 'ra.action.delete' | label or translation message to use |
| `icon` | Optional | `React.element` | `<DeleteIcon>` | iconElement, e.g. `<CommentIcon />` |
| `exporter` | Optional | `function` | - | Override the List exporter function |
| `undoable` | Optional | `boolean` | true | Allow users to cancel the deletion |

### `<FilterButton>`

This button is an internal component used by react-admin in [the `<Filter>` button/form combo](./List.md#the-filter-buttonform-combo).

![List Filters](./img/list_filter.gif)

### `<SortButton>`

Some List views don't have a natural UI for sorting - e.g. the `<SimpleList>`, or a list of images, don't have column headers like the `<Datagrid>`. For these cases, react-admin offers the `<SortButton>`, which displays a dropdown list of fields that the user can choose to sort on.

![Sort Button](./img/sort-button.gif)

`<SortButton>` expects one prop: `fields`, the list of fields it should allow to sort on. For instance, here is how to offer a button to sort on the `reference`, `sales`, and `stock` fields:

```jsx
import * as React from 'react';
import { TopToolbar, SortButton, CreateButton, ExportButton } from 'react-admin';

const ListActions = () => (
<TopToolbar>
<SortButton fields={['reference', 'sales', 'stock']} />
<CreateButton basePath="/products" />
<ExportButton />
</TopToolbar>
);
```

| Prop | Required | Type | Default | Description |
| ------------ | -------- | --------------- | ------------------ | ----------------------------------- |
| `fields` | Required | `string[]` | - | List of fields to offer sort on |
| `icon` | Optional | `React.element` | `<DeleteIcon>` | iconElement, e.g. `<CommentIcon />` |
| `label` | Optional | `string` | 'ra.action.delete' | label or translation message to use |

## Record Buttons

### `<DeleteButton>`
### `<CloneButton>`
### `<SaveButton>`

## Miscellaneous

### `<Button>`

Base component for most react-admin buttons. Responsive (displays only the icon with a tooltip on mobile) and accessible.

| Prop | Required | Type | Default | Description |
| ------------ | -------- | ------------------------------ | ------- | ---------------------------------------- |
| `alignIcon` | Optional | `'left' | 'right` | `'left'` | Icon position relative to the label |
| `children` | Optional | `React.element` | - | icon to use |
| `className` | Optional | `string` | - | path to link to, e.g. '/posts' |
| `color` | Optional | `'default' | 'inherit'| 'primary' | 'secondary'` | `'primary'` | Label and icon color |
| `disabled` | Optional | `boolean` | `false` | If `true`, the button will be disabled |
| `size` | Optional | `'large' | 'medium' | 'small'` | `'small'` | Button size |

Other props are passed down to [the underlying material-ui `<Button>`](https://material-ui.com/api/button/).

### `<RefreshButton>`
### `<SkipNavigationButton>`
### `<MenuItemLink>`
### `<UserMenu>`
Binary file added docs/img/bulk-delete-button.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/bulk-export-button.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/create-button-fab.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/create-button.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/edit-button.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/export-button.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/list-button.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/show-button.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions docs/navigation.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
<li {% if page.path contains 'Inputs.md' %} class="active" {% endif %}><a href="./Inputs.html"><code>&lt;Input&gt;</code> Components</a></li>
</ul>

<ul><div>Other UI components</div>
<li {% if page.path contains 'Buttons.md' %} class="active" {% endif %}><a href="./Buttons.html">Buttons</a></li>
</ul>

<ul><div>Recipes</div>
<li {% if page.path contains 'Actions.md' %} class="active" {% endif %}><a href="./Actions.html">Querying the API</a></li>
<li {% if page.path contains 'Theming.md' %} class="active" {% endif %}><a href="./Theming.html">Theming</a></li>
Expand Down
4 changes: 4 additions & 0 deletions packages/ra-core/src/controller/useListParams.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ describe('useListParams', () => {
page: 1,
perPage: 10,
}),
state: { _scrollToTop: false },
});
});
});
Expand Down Expand Up @@ -260,6 +261,7 @@ describe('useListParams', () => {
page: 1,
perPage: 10,
}),
state: { _scrollToTop: false },
});
});

Expand Down Expand Up @@ -295,6 +297,7 @@ describe('useListParams', () => {
page: 1,
perPage: 10,
}),
state: { _scrollToTop: false },
});
});

Expand Down Expand Up @@ -330,6 +333,7 @@ describe('useListParams', () => {
page: 1,
perPage: 10,
}),
state: { _scrollToTop: false },
});
});
});
Expand Down
1 change: 1 addition & 0 deletions packages/ra-core/src/controller/useListParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ const useListParams = ({
newParams.displayedFilters
),
})}`,
state: { _scrollToTop: action.type === SET_PAGE },
});
dispatch(changeListParams(resource, newParams));
} else {
Expand Down
2 changes: 2 additions & 0 deletions packages/ra-core/src/core/CoreAdminRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Route, Switch } from 'react-router-dom';
import RoutesWithLayout from './RoutesWithLayout';
import { useLogout, useGetPermissions, useAuthState } from '../auth';
import { useTimeout, useSafeSetState } from '../util';
import { useScrollToTop } from './useScrollToTop';
import {
AdminChildren,
CustomRoutes,
Expand Down Expand Up @@ -41,6 +42,7 @@ const CoreAdminRouter: FunctionComponent<AdminRouterProps> = props => {
const { authenticated } = useAuthState();
const oneSecondHasPassed = useTimeout(1000);
const [computedChildren, setComputedChildren] = useSafeSetState<State>([]);
useScrollToTop();
useEffect(() => {
if (typeof props.children === 'function') {
initializeResources();
Expand Down
1 change: 1 addition & 0 deletions packages/ra-core/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export * from './dataFetchActions';
export * from './components';
export * from './ResourceContext';
export * from './ResourceContextProvider';
export * from './useScrollToTop';
export * from './useResourceContext';
export * from './useResourceDefinition';
// there seems to be a bug in TypeScript: this only works if the exports are in this order.
Expand Down
Loading

0 comments on commit 7304d11

Please sign in to comment.