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

[Doc] Update RBAC OSS Doc #9435

Merged
merged 13 commits into from
Nov 17, 2023
109 changes: 80 additions & 29 deletions docs/TabbedForm.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ title: "TabbedForm"

## Usage

`<TabbedForm>` reads the `record` from the `RecordContext`, uses it to initialize the defaultValues of a `<Form>`, renders its children in a Material UI `<Stack>`, and renders a toolbar with a `<SaveButton>` that calls the `save` callback prepared by the edit or the create controller when pressed.
`<TabbedForm>` reads the `record` from the `RecordContext`, uses it to initialize the defaultValues of a `<Form>`, renders its children in a Material UI `<Stack>`, and renders a toolbar with a `<SaveButton>` that calls the `save` callback prepared by the edit or the create controller when pressed.

`<TabbedForm>` is often used as child of `<Create>` or `<Edit>`. It accepts `<TabbedForm.Tab>` elements as children. It relies on [react-hook-form](https://react-hook-form.com/) for form handling. It requires no prop by default.

Expand All @@ -31,7 +31,7 @@ import {
DateField,
TextInput,
ReferenceManyField,
NumberInput,
NumberInput,
DateInput,
BooleanInput,
EditButton
Expand Down Expand Up @@ -72,7 +72,7 @@ export const PostEdit = () => (

`<TabbedForm>` calls react-hook-form's `useForm` hook, and places the result in a `FormProvider` component. This means you can take advantage of the [`useFormContext`](https://react-hook-form.com/docs/useformcontext) and [`useFormState`](https://react-hook-form.com/docs/useformstate) hooks to access the form state.

React-admin highlights the tabs containing validation errors to help users locate incorrect input values.
React-admin highlights the tabs containing validation errors to help users locate incorrect input values.

## Props

Expand Down Expand Up @@ -333,14 +333,14 @@ export const PostEdit = () => (
The solution here is to set a max width on one of the following components:

* the `<Edit>` or `<Create>`
* the `<TabbedForm>`
* the `<TabbedForm>`

## `toolbar`

By default, `<TabbedForm>` renders a toolbar at the bottom of the form, containing:

- a submit button on Creation pages,
- a submit button and a delete button on Edition pages.
- a submit button and a delete button on Edition pages.

If you want to tweak the look and feel of that toolbar, add or remove buttons, pass yout own toolbar component to the form using the `toolbar` prop.

Expand Down Expand Up @@ -485,7 +485,7 @@ export const UserCreate = () => (

## `warnWhenUnsavedChanges`

React-admin keeps track of the form state, so it can detect when the user leaves an `Edit` or `Create` page with unsaved changes. To avoid data loss, you can use this ability to ask the user to confirm before leaving a page with unsaved changes.
React-admin keeps track of the form state, so it can detect when the user leaves an `Edit` or `Create` page with unsaved changes. To avoid data loss, you can use this ability to ask the user to confirm before leaving a page with unsaved changes.

![Warn About Unsaved Changes](./img/warn_when_unsaved_changes.png)

Expand Down Expand Up @@ -668,12 +668,12 @@ const ProductEditDetails = () => (
## Subscribing To Form Changes

`<TabbedForm>` relies on [react-hook-form's `useForm`](https://react-hook-form.com/docs/useform) to manage the form state and validation. You can subscribe to form changes using the [`useFormContext`](https://react-hook-form.com/docs/useformcontext) and [`useFormState`](https://react-hook-form.com/docs/useformstate) hooks.

**Reminder:** [react-hook-form's `formState` is wrapped with a Proxy](https://react-hook-form.com/docs/useformstate/#rules) to improve render performance and skip extra computation if specific state is not subscribed. So, make sure you deconstruct or read the `formState` before render in order to enable the subscription.

```js
const { isDirty } = useFormState(); // ✅
const formState = useFormState(); // ❌ should deconstruct the formState
const formState = useFormState(); // ❌ should deconstruct the formState
```

## Dynamic Tab Label
Expand Down Expand Up @@ -824,55 +824,54 @@ Check [the `<AutoSave>` component](./AutoSave.md) documentation for more details

## Role-Based Access Control (RBAC)

Fine-grained permissions control can be added by using the [`<TabbedForm>`](./AuthRBAC.md#tabbedform) and [`<FormTab>`](./AuthRBAC.md#tabbedform) components provided by the `@react-admin/ra-rbac` package.
Fine-grained permissions control can be added by using the [`<TabbedForm>`](./AuthRBAC.md#tabbedform) component provided by the `@react-admin/ra-rbac` package.

Use in conjunction with ra-rbac's [`<TabbedForm.Tab>`](#tabbedformtab-1) and add a `name` prop to the `Tab` to define the resource on which the user needs to have the 'write' permissions for.
erwanMarmelab marked this conversation as resolved.
Show resolved Hide resolved

**Tip:** ra-rbac's [`<TabbedForm.Tab>`](#tabbedformtab-1) also allows to only render the child inputs for which the user has the 'write' permissions.

{% raw %}
```jsx
import { Edit, TextInput } from 'react-admin';
import { TabbedForm, FormTab } from '@react-admin/ra-rbac';
import { TabbedForm } from '@react-admin/ra-rbac';

const authProvider = {
checkAuth: () => Promise.resolve(),
login: () => Promise.resolve(),
logout: () => Promise.resolve(),
checkError: () => Promise.resolve(),
getPermissions: () =>Promise.resolve({
permissions: [
// 'delete' is missing
getPermissions: () =>
Promise.resolve([
// action 'delete' is missing
{ action: ['list', 'edit'], resource: 'products' },
{ action: 'write', resource: 'products.reference' },
{ action: 'write', resource: 'products.width' },
{ action: 'write', resource: 'products.height' },
// 'products.description' is missing
{ action: 'write', resource: 'products.thumbnail' },
// 'products.image' is missing
{ action: 'write', resource: 'products.tab.description' },
// tab 'stock' is missing
{ action: 'write', resource: 'products.tab.images' },
// 'products.tab.stock' is missing
],
}),
]),
};

const ProductEdit = () => (
<Edit>
<TabbedForm>
<FormTab label="Description" name="description">
<TabbedForm.Tab label="Description" name="description">
<TextInput source="reference" />
<TextInput source="width" />
<TextInput source="height" />
{/* not displayed */}
<TextInput source="description" />
</FormTab>
<FormTab label="Images" name="images">
{/* not displayed */}
</TabbedForm.Tab>
{/* the "Stock" tab is not displayed */}
<TabbedForm.Tab label="Stock" name="stock">
<TextInput source="stock" />
</TabbedForm.Tab>
<TabbedForm.Tab label="Images" name="images">
<TextInput source="image" />
<TextInput source="thumbnail" />
</FormTab>
{/* not displayed */}
<FormTab label="Stock" name="stock">
<TextInput source="stock" />
</FormTab>
{/*} delete button not displayed */}
</TabbedForm.Tab>
{/* the "Delete" button is not displayed */}
</TabbedForm>
</Edit>
);
Expand All @@ -881,6 +880,58 @@ const ProductEdit = () => (

Check [the RBAC `<TabbedForm>` component](./AuthRBAC.md#tabbedform) documentation for more details.


To use the `<TabbedForm>` component, you will work with the `<TabbedForm.tab>` component. Since `<TabbedForm>` is provided by the `@react-admin/ra-rbac` package, then `<TabbedForm.tab>` too. This component will only renders a tab and its content if the user has the right permissions.
erwanMarmelab marked this conversation as resolved.
Show resolved Hide resolved

Add a `name` prop to the `Tab` to define the resource on which the user needs to have the 'write' permissions for.

`<TabbedForm.Tab>` also only renders the child inputs for which the user has the 'write' permissions.

```tsx
import { Edit, TextInput } from 'react-admin';
import { TabbedForm } from '@react-admin/ra-rbac';

const authProvider = {
// ...
getPermissions: () =>
Promise.resolve([
{ action: ['list', 'edit'], resource: 'products' },
{ action: 'write', resource: 'products.reference' },
{ action: 'write', resource: 'products.width' },
{ action: 'write', resource: 'products.height' },
// 'products.description' is missing
{ action: 'write', resource: 'products.thumbnail' },
// 'products.image' is missing
{ action: 'write', resource: 'products.tab.description' },
// 'products.tab.stock' is missing
{ action: 'write', resource: 'products.tab.images' },
]),
};

const ProductEdit = () => (
<Edit>
<TabbedForm>
<TabbedForm.Tab label="Description" name="description">
<TextInput source="reference" />
<TextInput source="width" />
<TextInput source="height" />
{/* Input Description is not displayed */}
<TextInput source="description" />
</TabbedForm.Tab>
{/* Input Stock is not displayed */}
<TabbedForm.Tab label="Stock" name="stock">
<TextInput source="stock" />
</TabbedForm.Tab>
<TabbedForm.Tab label="Images" name="images">
{/* Input Image is not displayed */}
<TextInput source="image" />
<TextInput source="thumbnail" />
</TabbedForm.Tab>
</TabbedForm>
</Edit>
);
```

## Linking Two Inputs

Edition forms often contain linked inputs, e.g. country and city (the choices of the latter depending on the value of the former).
Expand Down
104 changes: 102 additions & 2 deletions docs/TabbedShowLayout.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ title: "TabbedShowLayout"

Switching tabs will update the current url. By default, it uses the tabs indexes and the first tab will be displayed at the root url. You can customize the path by providing a `path` prop to each `<TabbedShowLayout.Tab>` component. If you'd like the first one to act as an index page, just omit the `path` prop.

## Usage
## Usage

Use `<TabbedShowLayout>` as descendant of a `<Show>` component (or any component creating a `<RecordContext>`), define the tabs via `<TabbedShowLayout.Tab>` children, and set the fields to be displayed as children of each tab:

Expand Down Expand Up @@ -345,9 +345,109 @@ const StaticPostShow = () => (

When passed a `record`, `<TabbedShowLayout>` creates a `RecordContext` with the given record.

## Role-Based Access Control (RBAC)

Fine-grained permissions control can be added by using the `<TabbedShowLayout>` component provided by the `@react-admin/ra-rbac` package.

Use it in conjunction with ra-rbac's [`<TabbedShowLayout.Tab>`](#tabbedshowlayouttab-1) and add a `name` prop to the `Tab` to define the resource on which the user needs to have the 'read' permissions for.

**Tip:** ra-rbac's [`<TabbedShowLayout.Tab>`](#tabbedshowlayouttab-1) also allows to only render the child fields for which the user has the 'read' permissions.

```tsx
import { Show, TextField } from 'react-admin';
import { TabbedShowLayout } from '@react-admin/ra-rbac';

const authProvider = {
// ...
getPermissions: () =>
Promise.resolve([
{ action: ['list', 'show'], resource: 'products' },
{ action: 'read', resource: 'products.reference' },
{ action: 'read', resource: 'products.width' },
{ action: 'read', resource: 'products.height' },
{ action: 'read', resource: 'products.thumbnail' },
{ action: 'read', resource: 'products.tab.description' },
// 'products.tab.stock' is missing
{ action: 'read', resource: 'products.tab.images' },
]),
};

const ProductShow = () => (
<Show>
<TabbedShowLayout>
<TabbedShowLayout.Tab label="Description" name="description">
<TextField source="reference" />
<TextField source="width" />
<TextField source="height" />
<TextField source="description" />
</TabbedShowLayout.Tab>
{/* Tab Stock is not displayed */}
<TabbedShowLayout.Tab label="Stock" name="stock">
<TextField source="stock" />
</TabbedShowLayout.Tab>
<TabbedShowLayout.Tab label="Images" name="images">
<TextField source="image" />
<TextField source="thumbnail" />
</TabbedShowLayout.Tab>
</TabbedShowLayout>
</Show>
);
```

To use the `<TabbedShowLayout>` component, you will work with the `<TabbedShowLayout.tab>` component. Since `<TabbedShowLayout>` is provided by the `@react-admin/ra-rbac` package, then `<TabbedShowLayout.tab>` too. This component will only renders a tab and its content if the user has the right permissions.
erwanMarmelab marked this conversation as resolved.
Show resolved Hide resolved

Add a `name` prop to the `Tab` to define the resource on which the user needs to have the 'read' permissions for.

`<TabbedShowLayout.Tab>` also only renders the child fields for which the user has the 'read' permissions.

```tsx
import { Show, TextField } from 'react-admin';
import { TabbedShowLayout } from '@react-admin/ra-rbac';

const authProvider = {
// ...
getPermissions: () =>
Promise.resolve([
{ action: ['list', 'show'], resource: 'products' },
{ action: 'read', resource: 'products.reference' },
{ action: 'read', resource: 'products.width' },
{ action: 'read', resource: 'products.height' },
// 'products.description' is missing
{ action: 'read', resource: 'products.thumbnail' },
// 'products.image' is missing
{ action: 'read', resource: 'products.tab.description' },
// 'products.tab.stock' is missing
{ action: 'read', resource: 'products.tab.images' },
]),
};

const ProductShow = () => (
<Show>
<TabbedShowLayout>
<TabbedShowLayout.Tab label="Description" name="description">
<TextField source="reference" />
<TextField source="width" />
<TextField source="height" />
{/* Field Description is not displayed */}
<TextField source="description" />
</TabbedShowLayout.Tab>
{/* Tab Stock is not displayed */}
<TabbedShowLayout.Tab label="Stock" name="stock">
<TextField source="stock" />
</TabbedShowLayout.Tab>
<TabbedShowLayout.Tab label="Images" name="images">
{/* Field Image is not displayed */}
<TextField source="image" />
<TextField source="thumbnail" />
</TabbedShowLayout.Tab>
</TabbedShowLayout>
</Show>
);
```


## See Also

* [Field components](./Fields.md)
* [Show Guesser](./ShowGuesser.md) guesses the fields based on the record type
* [SimpleShowLayout](./TabbedShowLayout.md) provides a simpler layout with no tabs

Loading