Skip to content

Commit 241cd44

Browse files
committed
Add docs, make styles configurable
1 parent 078ba46 commit 241cd44

File tree

9 files changed

+400
-79
lines changed

9 files changed

+400
-79
lines changed

docs/InPlaceEditor.md

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
---
2+
layout: default
3+
title: "The InPlaceEditor Component"
4+
---
5+
6+
# `<InPlaceEditor>`
7+
8+
`<InPlaceEditor>` renders a field from the current record. On click, it switches to an editable state, allowing the user to change the value directly.
9+
10+
<video controls autoplay playsinline muted loop>
11+
<source src="./img/InPlaceEditor.mp4" type="video/mp4"/>
12+
Your browser does not support the video tag.
13+
</video>
14+
15+
Use this component to let users edit parts of a record directly in the list or detail view. It is useful for quick edits without navigating to a separate edit page.
16+
17+
The field changes color on hover, to indicate that it is editable. The user can cancel the edit by pressing Escape. The field is saved automatically when the user clicks outside of it or presses Enter. While it is being saved, the field is disabled and a loading spinner is shown. If the save fails, an error message is displayed and the original value is restored.
18+
19+
## Usage
20+
21+
Use `<InPlaceEditor>` inside a `RecordContext` (e.g., under `<List>` or `<Show>`) and pass it a `source` prop to specify which field to edit. The component will render the field with a `<TextField>` and let the user edit it with a `<TextInput>`
22+
23+
{% raw %}
24+
```tsx
25+
import { Show, InPlaceEditor } from 'react-admin';
26+
import { Stack, Box, Typography } from '@mui/material';
27+
28+
const CustomerShow = () => (
29+
<Show>
30+
<Stack direction="row" spacing={2}>
31+
<AvatarField />
32+
<CustomerActions />
33+
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
34+
<Typography>Phone</Typography>
35+
<InPlaceEditor source="phone" />
36+
</Box>
37+
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
38+
<Typography>Email</Typography>
39+
<InPlaceEditor source="email" />
40+
</Box>
41+
...
42+
</Stack>
43+
</Show>
44+
);
45+
```
46+
{% endraw %}
47+
48+
**Note**: `<InPlaceEditor>` creates a `<Form>`, so it cannot be used inside an existing form (e.g., inside a `<SimpleForm>` or `<TabbedForm>`).
49+
50+
Instead of using the `source` prop, you can also specify the component to render in read mode with the `children` prop, and the component to render in edit mode with the `editor` prop. In general, you will need to tweak the styles of both components to make them look good together.
51+
52+
<video controls autoplay playsinline muted loop>
53+
<source src="./img/InPlaceEditorField.mp4" type="video/mp4"/>
54+
Your browser does not support the video tag.
55+
</video>
56+
57+
58+
{% raw %}
59+
```tsx
60+
const choices = [
61+
{ id: 'everyone', name: 'Everyone' },
62+
{ id: 'just_me', name: 'Just me' },
63+
{ id: 'sales', name: 'Sales' },
64+
];
65+
66+
// ...
67+
<InPlaceEditor
68+
source="access"
69+
editor={
70+
<SelectInput
71+
source="access"
72+
choices={choices}
73+
variant="standard"
74+
size="small"
75+
margin="none"
76+
label={false}
77+
helperText={false}
78+
autoFocus
79+
SelectProps={{ defaultOpen: true }}
80+
sx={{
81+
'& .MuiInput-root': { marginTop: 0 },
82+
'& .MuiSelect-select': { textAlign: 'right' },
83+
}}
84+
/>
85+
}
86+
>
87+
<SelectField
88+
source="access"
89+
variant="body1"
90+
choices={choices}
91+
sx={{ display: 'block', marginBottom: '5px' }}
92+
/>
93+
</InPlaceEditor>
94+
```
95+
{% endraw %}
96+
97+
## Props
98+
99+
| Prop | Required | Type | Default | Description |
100+
| ------------ | -------- | --------- | ------- | -------------------------------------------------------------------- |
101+
| `cancelOnBlur` | Optional | `boolean` | `false` | Whether to cancel the edit when the field loses focus. |
102+
| `children` | Optional | `ReactNode` | | The component to render in read mode. |
103+
| `editor` | Optional | `ReactNode` | | The component to render in edit mode. |
104+
| `mutationMode` | Optional | `string` | `pessimistic` | The mutation mode to use when saving the record. |
105+
| `notifyOnSuccess` | Optional | `boolean` | `false` | Whether to show a notification on successful save. |
106+
| `resource` | Optional | `string` | | The name of the resource. |
107+
| `showButtons` | Optional | `boolean` | `false` | Whether to show the save and cancel buttons. |
108+
| `source` | Optional | `string` | | The name of the field to edit. |
109+
| `sx` | Optional | `SxProps` | | The styles to apply to the component. |
110+
111+
## `cancelOnBlur`
112+
113+
By default, when the user clicks outside of the field in edit mode, it saves the current value. If `cancelOnBlur` is set to true, the edit will be canceled instead and the initial value will be restored.
114+
115+
```tsx
116+
<InPlaceEditor source="phone" cancelOnBlur />
117+
```
118+
119+
## `children`
120+
121+
The component to render in read mode. By default, it's a `<TextField>` using the `source` prop.
122+
123+
You can use any [field component](./Fields.md) instead, as it renders in a `RecordContext`.
124+
125+
![InPlaceEditor children](./img/InPlaceEditorChildren.png)
126+
127+
For example, to render a `<SelectField>` in read mode, you can use the following code:
128+
129+
{% raw %}
130+
```tsx
131+
<InPlaceEditor source="leadStatus">
132+
<SelectField
133+
source="leadStatus"
134+
choices={[
135+
{ id: 'customer', name: 'Customer' },
136+
{ id: 'prospect', name: 'Prospect' },
137+
]}
138+
optionText={
139+
<ChipField
140+
size="small"
141+
variant="outlined"
142+
source="name"
143+
color="success"
144+
/>
145+
}
146+
sx={{
147+
display: 'block',
148+
marginBottom: '3px',
149+
marginTop: '2px',
150+
}}
151+
/>
152+
</InPlaceEditor>
153+
```
154+
{% endraw %}
155+
156+
## `editor`
157+
158+
The component to render in edit mode. By default, it's a `<TextInput>` using the `source` prop.
159+
160+
You can use any [input component](./Input.md) instead, as it renders in a `<Form>`. You will probably need to tweak the input variant, margin and style so that it matches the style of the read mode component.
161+
162+
<video controls autoplay playsinline muted loop>
163+
<source src="./img/InPlaceEditorField.mp4" type="video/mp4"/>
164+
Your browser does not support the video tag.
165+
</video>
166+
167+
For example, to use a `<SelectInput>` in edit mode, you can use the following code:
168+
169+
{% raw %}
170+
```tsx
171+
<InPlaceEditor
172+
source="access"
173+
editor={
174+
<SelectInput
175+
source="access"
176+
choices={choices}
177+
variant="standard"
178+
size="small"
179+
margin="none"
180+
label={false}
181+
helperText={false}
182+
autoFocus
183+
SelectProps={{ defaultOpen: true }}
184+
sx={{
185+
'& .MuiInput-root': { marginTop: 0 },
186+
'& .MuiSelect-select': { textAlign: 'right' },
187+
}}
188+
/>
189+
}
190+
>
191+
// ...
192+
</InPlaceEditor>
193+
```
194+
{% endraw %}
195+
196+
## `mutationMode`
197+
198+
The mutation mode to use when saving the record. By default, it is set to `pessimistic`, which means that the record is saved immediately when the user clicks outside of the field or presses Enter.
199+
200+
You can use any of the following values:
201+
202+
- `pessimistic`: On save, the field is dimmed to show the saving state. If the server returns an error, the UI is reverted to the previous state.
203+
- `optimistic`: The UI is updated immediately with the new value, without waiting for the server response. If the server returns an error, the UI is reverted to the previous state.
204+
- `undoable`: The record is saved immediately, but the user can undo the operation by clicking on the undo button in the notification. This must be used in conjunction with the `notifyOnSuccess` prop.
205+
206+
```tsx
207+
<InPlaceEditor source="phone" mutationMode="optimistic" />
208+
```
209+
210+
## `notifyOnSuccess`
211+
212+
By default, the component does not show a notification when the record is saved. If you want to show a notification on successful save, set this prop to `true`.
213+
214+
![InPlaceEditor notifyOnSuccess](./img/InPlaceEditorNotifyOnSuccess.png)
215+
216+
```tsx
217+
<InPlaceEditor source="phone" notifyOnSuccess />
218+
```
219+
220+
## `resource`
221+
222+
The name of the resource. By default, it is set to the current resource in the `ResourceContext`. You can use this prop to override the resource name.
223+
224+
```tsx
225+
<InPlaceEditor source="phone" resource="customers" />
226+
```
227+
228+
## `showButtons`
229+
230+
By default, the component does not show the save and cancel buttons. If you want to show them, set this prop to `true`.
231+
232+
![InPlaceEditor showButtons](./img/InPlaceEditorShowButtons.png)
233+
234+
```tsx
235+
<InPlaceEditor source="phone" showButtons />
236+
```
237+
238+
## `source`
239+
240+
The name of the field to edit. You must set this prop, unless you define the `children` and `editor` props.
241+
242+
```tsx
243+
<InPlaceEditor source="phone" />
244+
```
245+
246+
## `sx`
247+
248+
The styles to apply to the component. Use it to alter the default styles of the reading, editing, and saving modes.
249+
250+
{% raw %}
251+
```tsx
252+
<InPlaceEditor
253+
source="phone"
254+
sx={{
255+
marginTop: '1rem',
256+
marginLeft: '1rem',
257+
'& .RaInPlaceEditor-reading div': {
258+
fontSize: '1.5rem',
259+
fontWeight: 'bold',
260+
color: 'primary.main',
261+
},
262+
'& .RaInPlaceEditor-editing input': {
263+
fontSize: '1.5rem',
264+
fontWeight: 'bold',
265+
color: 'primary.main',
266+
},
267+
'& .RaInPlaceEditor-saving div': {
268+
fontSize: '1.5rem',
269+
fontWeight: 'bold',
270+
color: 'text.disabled',
271+
},
272+
}}
273+
/>
274+
```
275+
{% endraw %}
276+
277+
You can use the `sx` prop to apply styles to the read mode, edit mode and saving mode. The following classes are available:
278+
279+
- `& .RaInPlaceEditor-reading`: The read mode.
280+
- `& .RaInPlaceEditor-editing`: The editing mode.
281+
- `& .RaInPlaceEditor-saving`: The saving mode.

docs/img/InPlaceEditor.mp4

122 KB
Binary file not shown.

docs/img/InPlaceEditorChildren.png

8.44 KB
Loading

docs/img/InPlaceEditorField.mp4

88.5 KB
Binary file not shown.
39.7 KB
Loading
8.37 KB
Loading

packages/ra-ui-materialui/src/input/InPlaceEditor/InPlaceEditor.Card.stories.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ export const Complex = () => {
8787
flexDirection: 'column',
8888
px: 2,
8989
py: 3,
90-
m: 2,
90+
my: 2,
91+
mx: 20,
9192
width: 300,
9293
}}
9394
>
@@ -118,8 +119,11 @@ export const Complex = () => {
118119
<InPlaceEditor
119120
source="name"
120121
sx={{
121-
fontSize: '1.2rem',
122-
'& input': {
122+
'& .RaInPlaceEditor-reading div, & .RaInPlaceEditor-saving div, & .RaInPlaceEditor-editing input':
123+
{
124+
fontSize: '1.2rem',
125+
},
126+
'& .RaInPlaceEditor-editing input': {
123127
textAlign: 'center',
124128
},
125129
}}

packages/ra-ui-materialui/src/input/InPlaceEditor/InPlaceEditor.stories.tsx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,18 +115,31 @@ export const SX = () => {
115115
{
116116
users: [{ id: 1, name: 'John Doe', age: 25 }],
117117
},
118-
process.env.NODE_ENV !== 'test'
118+
process.env.NODE_ENV !== 'test',
119+
500
119120
);
120121
return (
121122
<Wrapper dataProvider={dataProvider}>
122123
<InPlaceEditor
123124
source="name"
124125
sx={{
125-
fontSize: '1.5rem',
126-
fontWeight: 'bold',
127-
color: 'primary.main',
128126
marginTop: '1rem',
129127
marginLeft: '1rem',
128+
'& .RaInPlaceEditor-reading div': {
129+
fontSize: '1.5rem',
130+
fontWeight: 'bold',
131+
color: 'primary.main',
132+
},
133+
'& .RaInPlaceEditor-saving div': {
134+
fontSize: '1.5rem',
135+
fontWeight: 'bold',
136+
color: 'text.disabled',
137+
},
138+
'& .RaInPlaceEditor-editing input': {
139+
fontSize: '1.5rem',
140+
fontWeight: 'bold',
141+
color: 'primary.main',
142+
},
130143
}}
131144
/>
132145
</Wrapper>

0 commit comments

Comments
 (0)