Skip to content

Commit 2737f75

Browse files
committed
Improve post creation ux
1 parent 39ce253 commit 2737f75

File tree

1 file changed

+181
-88
lines changed

1 file changed

+181
-88
lines changed

examples/simple/src/posts/PostCreate.tsx

Lines changed: 181 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,20 @@ import {
3030
} from 'react-admin';
3131
import { useFormContext, useWatch } from 'react-hook-form';
3232
import {
33-
Alert,
3433
Button,
34+
ButtonGroup,
35+
ClickAwayListener,
3536
Dialog,
3637
DialogActions,
3738
DialogContent,
39+
Grow,
40+
MenuItem,
41+
MenuList,
42+
Paper,
43+
Popper,
44+
Stack,
3845
} from '@mui/material';
39-
import { Link, useSearchParams } from 'react-router-dom';
46+
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
4047

4148
// Client side id generation. We start from 100 to avoid querying the post list to get the next id as we
4249
// may be offline and accessing this page directly (without going through the list page first) which would
@@ -51,78 +58,86 @@ const getNewId = (mutationMode: MutationMode) => {
5158

5259
const PostCreateToolbar = ({
5360
mutationMode,
61+
setMutationMode,
5462
}: {
5563
mutationMode: MutationMode;
64+
setMutationMode: (mutationMode: MutationMode) => void;
5665
}) => {
5766
const notify = useNotify();
5867
const redirect = useRedirect();
5968
const { reset } = useFormContext();
6069

6170
return (
62-
<Toolbar>
63-
<SaveButton label="post.action.save_and_edit" variant="text" />
64-
<SaveButton
65-
label="post.action.save_and_show"
66-
type="button"
67-
variant="text"
68-
mutationOptions={{
69-
onSuccess: data => {
70-
notify('resources.posts.notifications.created', {
71-
type: 'info',
72-
messageArgs: { smart_count: 1 },
73-
undoable: mutationMode === 'undoable',
74-
});
75-
redirect('show', 'posts', data.id);
76-
},
77-
}}
78-
transform={data => ({
79-
...data,
80-
id: getNewId(mutationMode),
81-
average_note: 10,
82-
})}
83-
sx={{ display: { xs: 'none', sm: 'flex' } }}
84-
/>
85-
<SaveButton
86-
label="post.action.save_and_add"
87-
type="button"
88-
variant="text"
89-
mutationOptions={{
90-
onSuccess: () => {
91-
reset();
92-
window.scrollTo(0, 0);
93-
notify('resources.posts.notifications.created', {
94-
type: 'info',
95-
messageArgs: { smart_count: 1 },
96-
undoable: mutationMode === 'undoable',
97-
});
98-
},
99-
}}
100-
transform={data => ({
101-
...data,
102-
id: getNewId(mutationMode),
103-
average_note: 10,
104-
})}
105-
/>
106-
<SaveButton
107-
label="post.action.save_with_average_note"
108-
type="button"
109-
variant="text"
110-
mutationOptions={{
111-
onSuccess: data => {
112-
notify('resources.posts.notifications.created', {
113-
type: 'info',
114-
messageArgs: { smart_count: 1 },
115-
undoable: mutationMode === 'undoable',
116-
});
117-
redirect('show', 'posts', data.id);
118-
},
119-
}}
120-
transform={data => ({
121-
...data,
122-
id: getNewId(mutationMode),
123-
average_note: 10,
124-
})}
125-
sx={{ display: { xs: 'none', sm: 'flex' } }}
71+
<Toolbar sx={{ gap: 1 }}>
72+
<Stack direction="row" spacing={1} sx={{ flexGrow: 1 }}>
73+
<SaveButton label="post.action.save_and_edit" variant="text" />
74+
<SaveButton
75+
label="post.action.save_and_show"
76+
type="button"
77+
variant="text"
78+
mutationOptions={{
79+
onSuccess: data => {
80+
notify('resources.posts.notifications.created', {
81+
type: 'info',
82+
messageArgs: { smart_count: 1 },
83+
undoable: mutationMode === 'undoable',
84+
});
85+
redirect('show', 'posts', data.id);
86+
},
87+
}}
88+
transform={data => ({
89+
...data,
90+
id: getNewId(mutationMode),
91+
average_note: 10,
92+
})}
93+
sx={{ display: { xs: 'none', sm: 'flex' } }}
94+
/>
95+
<SaveButton
96+
label="post.action.save_and_add"
97+
type="button"
98+
variant="text"
99+
mutationOptions={{
100+
onSuccess: () => {
101+
reset();
102+
window.scrollTo(0, 0);
103+
notify('resources.posts.notifications.created', {
104+
type: 'info',
105+
messageArgs: { smart_count: 1 },
106+
undoable: mutationMode === 'undoable',
107+
});
108+
},
109+
}}
110+
transform={data => ({
111+
...data,
112+
id: getNewId(mutationMode),
113+
average_note: 10,
114+
})}
115+
/>
116+
<SaveButton
117+
label="post.action.save_with_average_note"
118+
type="button"
119+
variant="text"
120+
mutationOptions={{
121+
onSuccess: data => {
122+
notify('resources.posts.notifications.created', {
123+
type: 'info',
124+
messageArgs: { smart_count: 1 },
125+
undoable: mutationMode === 'undoable',
126+
});
127+
redirect('show', 'posts', data.id);
128+
},
129+
}}
130+
transform={data => ({
131+
...data,
132+
id: getNewId(mutationMode),
133+
average_note: 10,
134+
})}
135+
sx={{ display: { xs: 'none', sm: 'flex' } }}
136+
/>
137+
</Stack>
138+
<MutationModesSelector
139+
mutationMode={mutationMode}
140+
setMutationMode={setMutationMode}
126141
/>
127142
</Toolbar>
128143
);
@@ -135,43 +150,29 @@ const backlinksDefaultValue = [
135150
},
136151
];
137152

138-
const useMutationMode = (): MutationMode => {
139-
const [searchParams] = useSearchParams();
140-
const mutationMode = searchParams.get('mutationMode') ?? 'pessimistic';
141-
142-
return ['optimistic', 'undoable', 'pessimistic'].includes(mutationMode)
143-
? (mutationMode as MutationMode)
144-
: 'pessimistic';
145-
};
146-
147153
const PostCreate = () => {
148154
const defaultValues = useMemo(
149155
() => ({
150156
average_note: 0,
151157
}),
152158
[]
153159
);
154-
const mutationMode = useMutationMode();
160+
const [mutationMode, setMutationMode] =
161+
React.useState<MutationMode>('pessimistic');
155162
const dateDefaultValue = useMemo(() => new Date(), []);
156163
return (
157164
<Create
158165
redirect="edit"
159166
mutationMode={mutationMode}
160167
transform={data => ({ ...data, id: getNewId(mutationMode) })}
161168
>
162-
<Alert severity="info" role="presentation">
163-
To test offline support, add either{' '}
164-
<Link to="/posts/create?mutationMode=optimistic">
165-
<code>?mutationMode=optimistic</code>
166-
</Link>{' '}
167-
or
168-
<Link to="/posts/create?mutationMode=undoable">
169-
<code>?mutationMode=undoable</code>
170-
</Link>{' '}
171-
to the page search parameters.
172-
</Alert>
173169
<SimpleFormConfigurable
174-
toolbar={<PostCreateToolbar mutationMode={mutationMode} />}
170+
toolbar={
171+
<PostCreateToolbar
172+
mutationMode={mutationMode}
173+
setMutationMode={setMutationMode}
174+
/>
175+
}
175176
defaultValues={defaultValues}
176177
sx={{ maxWidth: { md: 'auto', lg: '30em' } }}
177178
>
@@ -326,3 +327,95 @@ const CreateUser = () => {
326327
</Dialog>
327328
);
328329
};
330+
331+
const MutationModes = ['pessimistic', 'optimistic', 'undoable'] as const;
332+
const MutationModesSelector = (props: {
333+
mutationMode: MutationMode;
334+
setMutationMode: (mode: MutationMode) => void;
335+
}) => {
336+
const { setMutationMode, mutationMode } = props;
337+
const [open, setOpen] = React.useState(false);
338+
const anchorRef = React.useRef<HTMLDivElement>(null);
339+
const buttonRef = React.useRef<HTMLButtonElement>(null);
340+
341+
const handleMenuItemClick = (mutationMode: MutationMode) => {
342+
setOpen(false);
343+
setMutationMode(mutationMode);
344+
};
345+
346+
const handleToggle = () => {
347+
setOpen(prevOpen => !prevOpen);
348+
};
349+
350+
const handleClose = (event: Event) => {
351+
if (
352+
anchorRef.current &&
353+
anchorRef.current.contains(event.target as HTMLElement)
354+
) {
355+
return;
356+
}
357+
358+
setOpen(false);
359+
};
360+
361+
return (
362+
<>
363+
<ButtonGroup
364+
variant="text"
365+
ref={anchorRef}
366+
aria-label="Button group with a nested menu"
367+
>
368+
<Button ref={buttonRef}>{mutationMode}</Button>
369+
<Button
370+
size="small"
371+
aria-controls={open ? 'split-button-menu' : undefined}
372+
aria-expanded={open ? 'true' : undefined}
373+
aria-label="select merge strategy"
374+
aria-haspopup="menu"
375+
onClick={handleToggle}
376+
>
377+
<ArrowDropDownIcon />
378+
</Button>
379+
</ButtonGroup>
380+
<Popper
381+
sx={{ zIndex: 1 }}
382+
open={open}
383+
anchorEl={anchorRef.current}
384+
role={undefined}
385+
transition
386+
disablePortal
387+
>
388+
{({ TransitionProps, placement }) => (
389+
<Grow
390+
{...TransitionProps}
391+
style={{
392+
transformOrigin:
393+
placement === 'bottom'
394+
? 'center top'
395+
: 'center bottom',
396+
}}
397+
>
398+
<Paper>
399+
<ClickAwayListener onClickAway={handleClose}>
400+
<MenuList id="split-button-menu" autoFocusItem>
401+
{MutationModes.map(mutationMode => (
402+
<MenuItem
403+
key={mutationMode}
404+
onClick={() =>
405+
handleMenuItemClick(
406+
mutationMode
407+
)
408+
}
409+
>
410+
{mutationMode}
411+
</MenuItem>
412+
))}
413+
</MenuList>
414+
</ClickAwayListener>
415+
</Paper>
416+
</Grow>
417+
)}
418+
</Popper>
419+
</>
420+
);
421+
};

0 commit comments

Comments
 (0)