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

Add scrollToTop on key navigation links #5905

Merged
merged 14 commits into from Feb 15, 2021
308 changes: 308 additions & 0 deletions docs/Buttons.md
@@ -0,0 +1,308 @@
---
layout: default
title: "Buttons"
---

# Buttons

React provides button components for all the common uses.
fzaninotto marked this conversation as resolved.
Show resolved Hide resolved

## 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 filter 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.
fzaninotto marked this conversation as resolved.
Show resolved Hide resolved

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>`
fzaninotto marked this conversation as resolved.
Show resolved Hide resolved
### `<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>`
fzaninotto marked this conversation as resolved.
Show resolved Hide resolved
### `<SkipNavigationButton>`
### `<MenuItemLink>`
### `<UserMenu>`
Binary file added docs/img/bulk-delete-button.png
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
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
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
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
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
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
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
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
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
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
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
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
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