Skip to content

Commit b0f9974

Browse files
authored
Merge pull request #6594 from marmelab/simplelist-custom-url-support
SimpleList: Add support for custom url
2 parents 73d779e + cf3c51c commit b0f9974

File tree

2 files changed

+186
-78
lines changed

2 files changed

+186
-78
lines changed

packages/ra-ui-materialui/src/list/SimpleList.spec.tsx

+86
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,90 @@ describe('<SimpleList />', () => {
4949
).not.toBeNull();
5050
});
5151
});
52+
53+
it.each([
54+
[
55+
'edit',
56+
'edit',
57+
['http://localhost/posts/1', 'http://localhost/posts/2'],
58+
],
59+
[
60+
'show',
61+
'show',
62+
['http://localhost/posts/1/show', 'http://localhost/posts/2/show'],
63+
],
64+
[
65+
'custom',
66+
(record, id) => `/posts/${id}/custom`,
67+
[
68+
'http://localhost/posts/1/custom',
69+
'http://localhost/posts/2/custom',
70+
],
71+
],
72+
])(
73+
'should render %s links for each item',
74+
async (_, link, expectedUrls) => {
75+
const { getByText } = renderWithRouter(
76+
<ListContext.Provider
77+
value={{
78+
loaded: true,
79+
loading: false,
80+
ids: [1, 2],
81+
data: {
82+
1: { id: 1, title: 'foo' },
83+
2: { id: 2, title: 'bar' },
84+
},
85+
total: 2,
86+
resource: 'posts',
87+
basePath: '/posts',
88+
}}
89+
>
90+
<SimpleList
91+
linkType={link}
92+
primaryText={record => record.id.toString()}
93+
secondaryText={<TextField source="title" />}
94+
/>
95+
</ListContext.Provider>
96+
);
97+
98+
await waitFor(() => {
99+
expect(getByText('1').closest('a').href).toEqual(
100+
expectedUrls[0]
101+
);
102+
expect(getByText('2').closest('a').href).toEqual(
103+
expectedUrls[1]
104+
);
105+
});
106+
}
107+
);
108+
109+
it('should not render links if linkType is false', async () => {
110+
const { getByText } = renderWithRouter(
111+
<ListContext.Provider
112+
value={{
113+
loaded: true,
114+
loading: false,
115+
ids: [1, 2],
116+
data: {
117+
1: { id: 1, title: 'foo' },
118+
2: { id: 2, title: 'bar' },
119+
},
120+
total: 2,
121+
resource: 'posts',
122+
basePath: '/posts',
123+
}}
124+
>
125+
<SimpleList
126+
linkType={false}
127+
primaryText={record => record.id.toString()}
128+
secondaryText={<TextField source="title" />}
129+
/>
130+
</ListContext.Provider>
131+
);
132+
133+
await waitFor(() => {
134+
expect(getByText('1').closest('a')).toBeNull();
135+
expect(getByText('2').closest('a')).toBeNull();
136+
});
137+
});
52138
});

packages/ra-ui-materialui/src/list/SimpleList.tsx

+100-78
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
ListItem,
99
ListItemAvatar,
1010
ListItemIcon,
11+
ListItemProps,
1112
ListItemSecondaryAction,
1213
ListItemText,
1314
} from '@material-ui/core';
@@ -129,76 +130,69 @@ const SimpleList = <RecordType extends Record = Record>(
129130
basePath={basePath}
130131
id={id}
131132
record={data[id]}
133+
style={
134+
rowStyle
135+
? rowStyle(data[id], rowIndex)
136+
: undefined
137+
}
132138
>
133-
<ListItem
134-
button={!!linkType as any}
135-
style={
136-
rowStyle
137-
? rowStyle(data[id], rowIndex)
138-
: undefined
139-
}
140-
>
141-
{leftIcon && (
142-
<ListItemIcon>
143-
{leftIcon(data[id], id)}
144-
</ListItemIcon>
145-
)}
146-
{leftAvatar && (
147-
<ListItemAvatar>
148-
{renderAvatar(id, leftAvatar)}
149-
</ListItemAvatar>
150-
)}
151-
<ListItemText
152-
primary={
153-
<div>
154-
{isValidElement(primaryText)
155-
? primaryText
156-
: primaryText(data[id], id)}
139+
{leftIcon && (
140+
<ListItemIcon>
141+
{leftIcon(data[id], id)}
142+
</ListItemIcon>
143+
)}
144+
{leftAvatar && (
145+
<ListItemAvatar>
146+
{renderAvatar(id, leftAvatar)}
147+
</ListItemAvatar>
148+
)}
149+
<ListItemText
150+
primary={
151+
<div>
152+
{isValidElement(primaryText)
153+
? primaryText
154+
: primaryText(data[id], id)}
157155

158-
{!!tertiaryText &&
159-
(isValidElement(
160-
tertiaryText
161-
) ? (
162-
tertiaryText
163-
) : (
164-
<span
165-
className={
166-
classes.tertiary
167-
}
168-
>
169-
{tertiaryText(
170-
data[id],
171-
id
172-
)}
173-
</span>
174-
))}
175-
</div>
176-
}
177-
secondary={
178-
!!secondaryText &&
179-
(isValidElement(secondaryText)
180-
? secondaryText
181-
: secondaryText(data[id], id))
182-
}
183-
/>
184-
{(rightAvatar || rightIcon) && (
185-
<ListItemSecondaryAction>
186-
{rightAvatar && (
187-
<Avatar>
188-
{renderAvatar(
189-
id,
190-
rightAvatar
191-
)}
192-
</Avatar>
193-
)}
194-
{rightIcon && (
195-
<ListItemIcon>
196-
{rightIcon(data[id], id)}
197-
</ListItemIcon>
198-
)}
199-
</ListItemSecondaryAction>
200-
)}
201-
</ListItem>
156+
{!!tertiaryText &&
157+
(isValidElement(
158+
tertiaryText
159+
) ? (
160+
tertiaryText
161+
) : (
162+
<span
163+
className={
164+
classes.tertiary
165+
}
166+
>
167+
{tertiaryText(
168+
data[id],
169+
id
170+
)}
171+
</span>
172+
))}
173+
</div>
174+
}
175+
secondary={
176+
!!secondaryText &&
177+
(isValidElement(secondaryText)
178+
? secondaryText
179+
: secondaryText(data[id], id))
180+
}
181+
/>
182+
{(rightAvatar || rightIcon) && (
183+
<ListItemSecondaryAction>
184+
{rightAvatar && (
185+
<Avatar>
186+
{renderAvatar(id, rightAvatar)}
187+
</Avatar>
188+
)}
189+
{rightIcon && (
190+
<ListItemIcon>
191+
{rightIcon(data[id], id)}
192+
</ListItemIcon>
193+
)}
194+
</ListItemSecondaryAction>
195+
)}
202196
</LinkOrNot>
203197
</li>
204198
</RecordContextProvider>
@@ -255,40 +249,68 @@ export interface SimpleListProps<RecordType extends Record = Record>
255249

256250
const useLinkOrNotStyles = makeStyles(
257251
{
258-
link: {
259-
textDecoration: 'none',
260-
color: 'inherit',
261-
},
252+
link: {},
262253
},
263254
{ name: 'RaLinkOrNot' }
264255
);
265256

266-
const LinkOrNot = (props: LinkOrNotProps) => {
257+
const LinkOrNot = (
258+
props: LinkOrNotProps & Omit<ListItemProps, 'button' | 'component' | 'id'>
259+
) => {
267260
const {
268261
classes: classesOverride,
269262
linkType,
270263
basePath,
271264
id,
272265
children,
273266
record,
267+
...rest
274268
} = props;
275269
const classes = useLinkOrNotStyles({ classes: classesOverride });
276270
const link =
277271
typeof linkType === 'function' ? linkType(record, id) : linkType;
278272

279273
return link === 'edit' || link === true ? (
280-
<Link to={linkToRecord(basePath, id)} className={classes.link}>
274+
<ListItem
275+
button
276+
// @ts-ignore
277+
component={Link}
278+
to={linkToRecord(basePath, id)}
279+
className={classes.link}
280+
{...rest}
281+
>
281282
{children}
282-
</Link>
283+
</ListItem>
283284
) : link === 'show' ? (
284-
<Link
285+
<ListItem
286+
button
287+
// @ts-ignore
288+
component={Link}
285289
to={`${linkToRecord(basePath, id)}/show`}
286290
className={classes.link}
291+
{...rest}
292+
>
293+
{children}
294+
</ListItem>
295+
) : link !== false ? (
296+
<ListItem
297+
button
298+
// @ts-ignore
299+
component={Link}
300+
to={link}
301+
className={classes.link}
302+
{...rest}
287303
>
288304
{children}
289-
</Link>
305+
</ListItem>
290306
) : (
291-
<span>{children}</span>
307+
<ListItem
308+
// @ts-ignore
309+
component="div"
310+
{...rest}
311+
>
312+
{children}
313+
</ListItem>
292314
);
293315
};
294316

0 commit comments

Comments
 (0)