如何使用简单列表启用批量操作
How to enable bulk actions with Simple List
我需要为列表创建不同的视图,以便可以在移动设备上查看它。有人建议我使用 SimpleList,但我仍然希望用户能够 select 列表中的多个项目并完成批量操作。有没有办法做到这一点? React Admin 文档中没有太多关于这种情况的文档。
我没有足够的声誉来投票,但是,使用当前的 SimpleList,我制作了一个适合我的目的的可选(批量操作)版本。
它可能对你也有用。
它使用的是常规 JS,而不是原始的 TypeScript。
要使用它,只需导入文件,例如:
import SelectSimpleList from './SelectSimpleList'
并以与原始 SimpleList 完全相同的方式使用它。
SelectSimpleList.js:
import * as React from 'react';
import PropTypes from 'prop-types';
import {
Avatar,
List,
//ListProps,
ListItem,
ListItemAvatar,
ListItemIcon,
ListItemSecondaryAction,
ListItemText,
} from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
import { Link } from 'react-router-dom';
import {
linkToRecord,
sanitizeListRestProps,
useListContext,
//Record,
//RecordMap,
//Identifier,
} from 'ra-core';
import Checkbox from '@material-ui/core/Checkbox';
import { useTimeout } from 'ra-core';
import classnames from 'classnames';
const useStylesPlaceholder = makeStyles(
theme => ({
root: {
backgroundColor: theme.palette.grey[300],
display: 'flex',
},
}),
{ name: 'RaPlaceholder' }
);
const Placeholder = props => {
const classes = useStylesPlaceholder(props);
return (
<span className={classnames(classes.root, props.className)}>
</span>
);
};
const useStylesLoading = makeStyles(
theme => ({
primary: {
width: '30vw',
display: 'inline-block',
marginBottom: theme.spacing(),
},
tertiary: { float: 'right', opacity: 0.541176, minWidth: '10vw' },
}),
{ name: 'RaSimpleListLoading' },
);
const times = (nbChildren, fn) =>
Array.from({ length: nbChildren }, (_, key) => fn(key));
const SimpleListLoading = props => {
const {
classes: classesOverride,
className,
hasLeftAvatarOrIcon,
hasRightAvatarOrIcon,
hasSecondaryText,
hasTertiaryText,
nbFakeLines = 5,
...rest
} = props;
const classes = useStylesLoading(props);
const oneSecondHasPassed = useTimeout(1000);
return oneSecondHasPassed ? (
<List className={className} {...rest}>
{times(nbFakeLines, key => (
<ListItem key={key}>
{hasLeftAvatarOrIcon && (
<ListItemAvatar>
<Avatar> </Avatar>
</ListItemAvatar>
)}
<ListItemText
primary={
<div>
<Placeholder className={classes.primary} />
{hasTertiaryText && (
<span className={classes.tertiary}>
<Placeholder />
</span>
)}
</div>
}
secondary={
hasSecondaryText ? <Placeholder /> : undefined
}
/>
{hasRightAvatarOrIcon && (
<ListItemSecondaryAction>
<Avatar> </Avatar>
</ListItemSecondaryAction>
)}
</ListItem>
))}
</List>
) : null;
};
SimpleListLoading.propTypes = {
className: PropTypes.string,
hasLeftAvatarOrIcon: PropTypes.bool,
hasRightAvatarOrIcon: PropTypes.bool,
hasSecondaryText: PropTypes.bool,
hasTertiaryText: PropTypes.bool,
nbFakeLines: PropTypes.number,
};
const useStyles = makeStyles(
{
tertiary: { float: 'right', opacity: 0.541176 },
},
{ name: 'RaSimpleList' }
);
/**
* The <SimpleList> component renders a list of records as a material-ui <List>.
* It is usually used as a child of react-admin's <List> and <ReferenceManyField> components.
*
* Also widely used on Mobile.
*
* Props:
* - primaryText: function returning a React element (or some text) based on the record
* - secondaryText: same
* - tertiaryText: same
* - leftAvatar: function returning a React element based on the record
* - leftIcon: same
* - rightAvatar: same
* - rightIcon: same
* - linkType: 'edit' or 'show', or a function returning 'edit' or 'show' based on the record
* - rowStyle: function returning a style object based on (record, index)
*
* @example // Display all posts as a List
* const postRowStyle = (record, index) => ({
* backgroundColor: record.views >= 500 ? '#efe' : 'white',
* });
* export const PostList = (props) => (
* <List {...props}>
* <SimpleList
* primaryText={record => record.title}
* secondaryText={record => `${record.views} views`}
* tertiaryText={record =>
* new Date(record.published_at).toLocaleDateString()
* }
* rowStyle={postRowStyle}
* />
* </List>
* );
*/
const SelectSimpleList = props => {
const {
className,
classes: classesOverride,
hasBulkActions,
leftAvatar,
leftIcon,
linkType = 'edit',
primaryText,
rightAvatar,
rightIcon,
secondaryText,
tertiaryText,
rowStyle,
...rest
} = props;
const { basePath, data, ids, loaded, total, onToggleItem, selectedIds } = useListContext(props);
const classes = useStyles(props);
if (loaded === false) {
return (
<SimpleListLoading
classes={classes}
className={className}
hasLeftAvatarOrIcon={!!leftIcon || !!leftAvatar}
hasRightAvatarOrIcon={!!rightIcon || !!rightAvatar}
hasSecondaryText={!!secondaryText}
hasTertiaryText={!!tertiaryText}
/>
);
}
const isSelected = id => {
if (selectedIds.includes(id)){
return true;
}
return false;
}
return (
total > 0 && (
<List className={className} {...sanitizeListRestProps(rest)}>
{ids.map((id, rowIndex) => (
<LinkOrNot
linkType={linkType}
basePath={basePath}
id={id}
key={id}
record={data[id]}
>
<ListItem
//onClick={() => {onToggleItem(id)}}
button={!!linkType}
style={
rowStyle
? rowStyle(data[id], rowIndex)
: undefined
}
>
<Checkbox
checked={isSelected(id)}
onChange={() => onToggleItem(id)}
color="primary"
onClick={(e) => e.stopPropagation()}
inputProps={{ 'aria-label': 'primary checkbox' }}
/>
{leftIcon && (
<ListItemIcon>
{leftIcon(data[id], id)}
</ListItemIcon>
)}
{leftAvatar && (
<ListItemAvatar>
<Avatar>{leftAvatar(data[id], id)}</Avatar>
</ListItemAvatar>
)}
<ListItemText
primary={
<div>
{primaryText(data[id], id)}
{tertiaryText && (
<span className={classes.tertiary}>
{tertiaryText(data[id], id)}
</span>
)}
</div>
}
secondary={
secondaryText && secondaryText(data[id], id)
}
/>
{(rightAvatar || rightIcon) && (
<ListItemSecondaryAction>
{rightAvatar && (
<Avatar>
{rightAvatar(data[id], id)}
</Avatar>
)}
{rightIcon && (
<ListItemIcon>
{rightIcon(data[id], id)}
</ListItemIcon>
)}
</ListItemSecondaryAction>
)}
</ListItem>
</LinkOrNot>
))}
</List>
)
);
};
SelectSimpleList.propTypes = {
className: PropTypes.string,
classes: PropTypes.object,
leftAvatar: PropTypes.func,
leftIcon: PropTypes.func,
linkType: PropTypes.oneOfType([
PropTypes.string,
PropTypes.bool,
PropTypes.func,
]),
primaryText: PropTypes.func,
rightAvatar: PropTypes.func,
rightIcon: PropTypes.func,
secondaryText: PropTypes.func,
tertiaryText: PropTypes.func,
rowStyle: PropTypes.func,
};
const useLinkOrNotStyles = makeStyles(
{
link: {
textDecoration: 'none',
color: 'inherit',
},
},
{ name: 'RaLinkOrNot' }
);
const LinkOrNot = ({
classes: classesOverride,
linkType,
basePath,
id,
children,
record,
}) => {
const classes = useLinkOrNotStyles({ classes: classesOverride });
const link =
typeof linkType === 'function' ? linkType(record, id) : linkType;
return link === 'edit' || link === true ? (
<Link to={linkToRecord(basePath, id)} className={classes.link}>
{children}
</Link>
) : link === 'show' ? (
<Link
to={`${linkToRecord(basePath, id)}/show`}
className={classes.link}
>
{children}
</Link>
) : (
<span>{children}</span>
);
};
export default SelectSimpleList;
React-Admin v4.
的答案
由于我决定使用 tss-react/mui 更新我所有的 makeStyle 代码,您需要在使用此版本的 SelectSimpleList 之前安装它。 (npm i tss-react/mui)
使用此更新版本,无需更改代码 'should' 即可正常运行。还添加了 bulkActionButtons 并且应该起作用。
import * as React from 'react';
import PropTypes from 'prop-types';
import { isValidElement } from 'react';
import {
Avatar,
List,
ListItem,
ListItemAvatar,
ListItemIcon,
ListItemSecondaryAction,
ListItemText
} from '@mui/material';
import { makeStyles } from 'tss-react/mui';
import { Link } from 'react-router-dom';
import {
useCreatePath,
sanitizeListRestProps,
useListContext,
useResourceContext,
RecordContextProvider,
} from 'ra-core';
import Checkbox from '@mui/material/Checkbox';
import { useTimeout } from 'ra-core';
import classnames from 'classnames';
import { BulkActionsToolbar } from 'react-admin';
import { BulkDeleteButton } from 'react-admin';
const defaultBulkActionButtons = <BulkDeleteButton />;
const useStylesPlaceholder = makeStyles()((theme) =>{
return {
root: {
backgroundColor: theme.palette.grey[300],
display: 'flex',
}
}
});
const Placeholder = props => {
const { classes } = useStylesPlaceholder(props);
return (
<span className={classnames(classes.root, props.className)}>
</span>
);
};
const useStylesLoading = makeStyles()((theme) => {
return {
primary: {
width: '30vw',
display: 'inline-block',
marginBottom: theme.spacing(),
},
tertiary: { float: 'right', opacity: 0.541176, minWidth: '10vw' }
}
})
const times = (nbChildren, fn) =>
Array.from({ length: nbChildren }, (_, key) => fn(key));
const SimpleListLoading = props => {
const {
classes: classesOverride,
className,
hasLeftAvatarOrIcon,
hasRightAvatarOrIcon,
hasSecondaryText,
hasTertiaryText,
nbFakeLines = 5,
...rest
} = props;
const { classes } = useStylesLoading(props);
const oneSecondHasPassed = useTimeout(1000);
return oneSecondHasPassed ? (
<List className={className} {...rest}>
{times(nbFakeLines, key => (
<ListItem key={key}>
{hasLeftAvatarOrIcon && (
<ListItemAvatar>
<Avatar> </Avatar>
</ListItemAvatar>
)}
<ListItemText
primary={
<div>
<Placeholder className={classes.primary} />
{hasTertiaryText && (
<span className={classes.tertiary}>
<Placeholder />
</span>
)}
</div>
}
secondary={
hasSecondaryText ? <Placeholder /> : undefined
}
/>
{hasRightAvatarOrIcon && (
<ListItemSecondaryAction>
<Avatar> </Avatar>
</ListItemSecondaryAction>
)}
</ListItem>
))}
</List>
) : null;
};
SimpleListLoading.propTypes = {
className: PropTypes.string,
hasLeftAvatarOrIcon: PropTypes.bool,
hasRightAvatarOrIcon: PropTypes.bool,
hasSecondaryText: PropTypes.bool,
hasTertiaryText: PropTypes.bool,
nbFakeLines: PropTypes.number,
};
const useStyles = makeStyles()((theme) => {
return {
tertiary: { float: 'right', opacity: 0.541176 },
}
})
/**
* The <SimpleList> component renders a list of records as a material-ui <List>.
* It is usually used as a child of react-admin's <List> and <ReferenceManyField> components.
*
* Also widely used on Mobile.
*
* Props:
* - primaryText: function returning a React element (or some text) based on the record
* - secondaryText: same
* - tertiaryText: same
* - leftAvatar: function returning a React element based on the record
* - leftIcon: same
* - rightAvatar: same
* - rightIcon: same
* - linkType: 'edit' or 'show', or a function returning 'edit' or 'show' based on the record
* - rowStyle: function returning a style object based on (record, index)
*
* @example // Display all posts as a List
* const postRowStyle = (record, index) => ({
* backgroundColor: record.views >= 500 ? '#efe' : 'white',
* });
* export const PostList = (props) => (
* <List {...props}>
* <SimpleList
* primaryText={record => record.title}
* secondaryText={record => `${record.views} views`}
* tertiaryText={record =>
* new Date(record.published_at).toLocaleDateString()
* }
* rowStyle={postRowStyle}
* />
* </List>
* );
*/
const SelectSimpleList = props => {
const {
className,
classes: classesOverride,
bulkActionButtons = defaultBulkActionButtons,
leftAvatar,
leftIcon,
linkType = 'edit',
primaryText,
rightAvatar,
rightIcon,
secondaryText,
tertiaryText,
rowStyle,
isRowSelectable,
...rest
} = props;
const hasBulkActions = !!bulkActionButtons !== false;
const resource = useResourceContext(props);
const { data, isLoading, total, onToggleItem, selectedIds } = useListContext(props);
const { classes } = useStyles(props);
if (isLoading === true) {
return (
<SimpleListLoading
classes={classes}
className={className}
hasBulkActions={hasBulkActions}
hasLeftAvatarOrIcon={!!leftIcon || !!leftAvatar}
hasRightAvatarOrIcon={!!rightIcon || !!rightAvatar}
hasSecondaryText={!!secondaryText}
hasTertiaryText={!!tertiaryText}
/>
);
}
const isSelected = id => {
if (selectedIds.includes(id)){
return true;
}
return false;
}
return (
total > 0 && (
<>
{bulkActionButtons !== false ? (
<BulkActionsToolbar selectedIds={selectedIds}>
{isValidElement(bulkActionButtons)
? bulkActionButtons
: defaultBulkActionButtons}
</BulkActionsToolbar>
) : null}
<List className={className} {...sanitizeListRestProps(rest)}>
{data.map((record, rowIndex) => (
<RecordContextProvider key={record.id} value={record}>
<LinkOrNot
linkType={linkType}
resource={resource}
id={record.id}
key={record.id}
record={record}
style={
rowStyle
? rowStyle(record, rowIndex)
: undefined
}
>
{
!!isRowSelectable ? (
<>
{
!!isRowSelectable(record) ? (
<Checkbox
checked={isSelected(record.id)}
onChange={() => onToggleItem(record.id)}
color="primary"
onClick={(e) => e.stopPropagation()}
inputProps={{ 'aria-label': 'selected checkbox' }}
/>
) : (
<div style={{width: '46px'}} />
)
}
</>
) : (
<Checkbox
checked={isSelected(record.id)}
onChange={() => onToggleItem(record.id)}
color="primary"
onClick={(e) => e.stopPropagation()}
inputProps={{ 'aria-label': 'selected checkbox' }}
/>
)
}
{leftIcon && (
<ListItemIcon>
{leftIcon(record, record.id)}
</ListItemIcon>
)}
{leftAvatar && (
<ListItemAvatar>
<Avatar>{leftAvatar(record, record.id)}</Avatar>
</ListItemAvatar>
)}
<ListItemText
primary={
<div>
{primaryText(record, record.id)}
{tertiaryText && (
<span className={classes.tertiary}>
{tertiaryText(record, record.id)}
</span>
)}
</div>
}
secondary={
secondaryText && secondaryText(record, record.id)
}
/>
{(rightAvatar || rightIcon) && (
<ListItemSecondaryAction>
{rightAvatar && (
<Avatar>
{rightAvatar(record, record.id)}
</Avatar>
)}
{rightIcon && (
<ListItemIcon>
{rightIcon(record, record.id)}
</ListItemIcon>
)}
</ListItemSecondaryAction>
)}
</LinkOrNot>
</RecordContextProvider>
))}
</List>
</>
)
);
};
SelectSimpleList.propTypes = {
className: PropTypes.string,
classes: PropTypes.object,
leftAvatar: PropTypes.func,
leftIcon: PropTypes.func,
linkType: PropTypes.oneOfType([
PropTypes.string,
PropTypes.bool,
PropTypes.func,
]),
primaryText: PropTypes.func,
rightAvatar: PropTypes.func,
rightIcon: PropTypes.func,
secondaryText: PropTypes.func,
tertiaryText: PropTypes.func,
rowStyle: PropTypes.func,
};
const useLinkOrNotStyles = makeStyles()((theme) => {
return {
link: {
textDecoration: 'none',
color: 'inherit',
}
}
})
const LinkOrNot = ({
classes: classesOverride,
linkType,
resource,
id,
children,
record,
...rest
}) => {
const { classes } = useLinkOrNotStyles({ classes: classesOverride });
const createPath = useCreatePath();
const type =
typeof linkType === 'function' ? linkType(record, id) : linkType;
return type === false ? (
<ListItem
// @ts-ignore
component="div"
className={classes.link}
{...rest}
>
{children}
</ListItem>
) : (
// @ts-ignore
<ListItem
component={Link}
button={true}
to={createPath({ resource, id, type })}
className={classes.link}
{...rest}
>
{children}
</ListItem>
);
};
export default SelectSimpleList;
我需要为列表创建不同的视图,以便可以在移动设备上查看它。有人建议我使用 SimpleList,但我仍然希望用户能够 select 列表中的多个项目并完成批量操作。有没有办法做到这一点? React Admin 文档中没有太多关于这种情况的文档。
我没有足够的声誉来投票,但是,使用当前的 SimpleList,我制作了一个适合我的目的的可选(批量操作)版本。
它可能对你也有用。
它使用的是常规 JS,而不是原始的 TypeScript。
要使用它,只需导入文件,例如:
import SelectSimpleList from './SelectSimpleList'
并以与原始 SimpleList 完全相同的方式使用它。
SelectSimpleList.js:
import * as React from 'react';
import PropTypes from 'prop-types';
import {
Avatar,
List,
//ListProps,
ListItem,
ListItemAvatar,
ListItemIcon,
ListItemSecondaryAction,
ListItemText,
} from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
import { Link } from 'react-router-dom';
import {
linkToRecord,
sanitizeListRestProps,
useListContext,
//Record,
//RecordMap,
//Identifier,
} from 'ra-core';
import Checkbox from '@material-ui/core/Checkbox';
import { useTimeout } from 'ra-core';
import classnames from 'classnames';
const useStylesPlaceholder = makeStyles(
theme => ({
root: {
backgroundColor: theme.palette.grey[300],
display: 'flex',
},
}),
{ name: 'RaPlaceholder' }
);
const Placeholder = props => {
const classes = useStylesPlaceholder(props);
return (
<span className={classnames(classes.root, props.className)}>
</span>
);
};
const useStylesLoading = makeStyles(
theme => ({
primary: {
width: '30vw',
display: 'inline-block',
marginBottom: theme.spacing(),
},
tertiary: { float: 'right', opacity: 0.541176, minWidth: '10vw' },
}),
{ name: 'RaSimpleListLoading' },
);
const times = (nbChildren, fn) =>
Array.from({ length: nbChildren }, (_, key) => fn(key));
const SimpleListLoading = props => {
const {
classes: classesOverride,
className,
hasLeftAvatarOrIcon,
hasRightAvatarOrIcon,
hasSecondaryText,
hasTertiaryText,
nbFakeLines = 5,
...rest
} = props;
const classes = useStylesLoading(props);
const oneSecondHasPassed = useTimeout(1000);
return oneSecondHasPassed ? (
<List className={className} {...rest}>
{times(nbFakeLines, key => (
<ListItem key={key}>
{hasLeftAvatarOrIcon && (
<ListItemAvatar>
<Avatar> </Avatar>
</ListItemAvatar>
)}
<ListItemText
primary={
<div>
<Placeholder className={classes.primary} />
{hasTertiaryText && (
<span className={classes.tertiary}>
<Placeholder />
</span>
)}
</div>
}
secondary={
hasSecondaryText ? <Placeholder /> : undefined
}
/>
{hasRightAvatarOrIcon && (
<ListItemSecondaryAction>
<Avatar> </Avatar>
</ListItemSecondaryAction>
)}
</ListItem>
))}
</List>
) : null;
};
SimpleListLoading.propTypes = {
className: PropTypes.string,
hasLeftAvatarOrIcon: PropTypes.bool,
hasRightAvatarOrIcon: PropTypes.bool,
hasSecondaryText: PropTypes.bool,
hasTertiaryText: PropTypes.bool,
nbFakeLines: PropTypes.number,
};
const useStyles = makeStyles(
{
tertiary: { float: 'right', opacity: 0.541176 },
},
{ name: 'RaSimpleList' }
);
/**
* The <SimpleList> component renders a list of records as a material-ui <List>.
* It is usually used as a child of react-admin's <List> and <ReferenceManyField> components.
*
* Also widely used on Mobile.
*
* Props:
* - primaryText: function returning a React element (or some text) based on the record
* - secondaryText: same
* - tertiaryText: same
* - leftAvatar: function returning a React element based on the record
* - leftIcon: same
* - rightAvatar: same
* - rightIcon: same
* - linkType: 'edit' or 'show', or a function returning 'edit' or 'show' based on the record
* - rowStyle: function returning a style object based on (record, index)
*
* @example // Display all posts as a List
* const postRowStyle = (record, index) => ({
* backgroundColor: record.views >= 500 ? '#efe' : 'white',
* });
* export const PostList = (props) => (
* <List {...props}>
* <SimpleList
* primaryText={record => record.title}
* secondaryText={record => `${record.views} views`}
* tertiaryText={record =>
* new Date(record.published_at).toLocaleDateString()
* }
* rowStyle={postRowStyle}
* />
* </List>
* );
*/
const SelectSimpleList = props => {
const {
className,
classes: classesOverride,
hasBulkActions,
leftAvatar,
leftIcon,
linkType = 'edit',
primaryText,
rightAvatar,
rightIcon,
secondaryText,
tertiaryText,
rowStyle,
...rest
} = props;
const { basePath, data, ids, loaded, total, onToggleItem, selectedIds } = useListContext(props);
const classes = useStyles(props);
if (loaded === false) {
return (
<SimpleListLoading
classes={classes}
className={className}
hasLeftAvatarOrIcon={!!leftIcon || !!leftAvatar}
hasRightAvatarOrIcon={!!rightIcon || !!rightAvatar}
hasSecondaryText={!!secondaryText}
hasTertiaryText={!!tertiaryText}
/>
);
}
const isSelected = id => {
if (selectedIds.includes(id)){
return true;
}
return false;
}
return (
total > 0 && (
<List className={className} {...sanitizeListRestProps(rest)}>
{ids.map((id, rowIndex) => (
<LinkOrNot
linkType={linkType}
basePath={basePath}
id={id}
key={id}
record={data[id]}
>
<ListItem
//onClick={() => {onToggleItem(id)}}
button={!!linkType}
style={
rowStyle
? rowStyle(data[id], rowIndex)
: undefined
}
>
<Checkbox
checked={isSelected(id)}
onChange={() => onToggleItem(id)}
color="primary"
onClick={(e) => e.stopPropagation()}
inputProps={{ 'aria-label': 'primary checkbox' }}
/>
{leftIcon && (
<ListItemIcon>
{leftIcon(data[id], id)}
</ListItemIcon>
)}
{leftAvatar && (
<ListItemAvatar>
<Avatar>{leftAvatar(data[id], id)}</Avatar>
</ListItemAvatar>
)}
<ListItemText
primary={
<div>
{primaryText(data[id], id)}
{tertiaryText && (
<span className={classes.tertiary}>
{tertiaryText(data[id], id)}
</span>
)}
</div>
}
secondary={
secondaryText && secondaryText(data[id], id)
}
/>
{(rightAvatar || rightIcon) && (
<ListItemSecondaryAction>
{rightAvatar && (
<Avatar>
{rightAvatar(data[id], id)}
</Avatar>
)}
{rightIcon && (
<ListItemIcon>
{rightIcon(data[id], id)}
</ListItemIcon>
)}
</ListItemSecondaryAction>
)}
</ListItem>
</LinkOrNot>
))}
</List>
)
);
};
SelectSimpleList.propTypes = {
className: PropTypes.string,
classes: PropTypes.object,
leftAvatar: PropTypes.func,
leftIcon: PropTypes.func,
linkType: PropTypes.oneOfType([
PropTypes.string,
PropTypes.bool,
PropTypes.func,
]),
primaryText: PropTypes.func,
rightAvatar: PropTypes.func,
rightIcon: PropTypes.func,
secondaryText: PropTypes.func,
tertiaryText: PropTypes.func,
rowStyle: PropTypes.func,
};
const useLinkOrNotStyles = makeStyles(
{
link: {
textDecoration: 'none',
color: 'inherit',
},
},
{ name: 'RaLinkOrNot' }
);
const LinkOrNot = ({
classes: classesOverride,
linkType,
basePath,
id,
children,
record,
}) => {
const classes = useLinkOrNotStyles({ classes: classesOverride });
const link =
typeof linkType === 'function' ? linkType(record, id) : linkType;
return link === 'edit' || link === true ? (
<Link to={linkToRecord(basePath, id)} className={classes.link}>
{children}
</Link>
) : link === 'show' ? (
<Link
to={`${linkToRecord(basePath, id)}/show`}
className={classes.link}
>
{children}
</Link>
) : (
<span>{children}</span>
);
};
export default SelectSimpleList;
React-Admin v4.
的答案由于我决定使用 tss-react/mui 更新我所有的 makeStyle 代码,您需要在使用此版本的 SelectSimpleList 之前安装它。 (npm i tss-react/mui)
使用此更新版本,无需更改代码 'should' 即可正常运行。还添加了 bulkActionButtons 并且应该起作用。
import * as React from 'react';
import PropTypes from 'prop-types';
import { isValidElement } from 'react';
import {
Avatar,
List,
ListItem,
ListItemAvatar,
ListItemIcon,
ListItemSecondaryAction,
ListItemText
} from '@mui/material';
import { makeStyles } from 'tss-react/mui';
import { Link } from 'react-router-dom';
import {
useCreatePath,
sanitizeListRestProps,
useListContext,
useResourceContext,
RecordContextProvider,
} from 'ra-core';
import Checkbox from '@mui/material/Checkbox';
import { useTimeout } from 'ra-core';
import classnames from 'classnames';
import { BulkActionsToolbar } from 'react-admin';
import { BulkDeleteButton } from 'react-admin';
const defaultBulkActionButtons = <BulkDeleteButton />;
const useStylesPlaceholder = makeStyles()((theme) =>{
return {
root: {
backgroundColor: theme.palette.grey[300],
display: 'flex',
}
}
});
const Placeholder = props => {
const { classes } = useStylesPlaceholder(props);
return (
<span className={classnames(classes.root, props.className)}>
</span>
);
};
const useStylesLoading = makeStyles()((theme) => {
return {
primary: {
width: '30vw',
display: 'inline-block',
marginBottom: theme.spacing(),
},
tertiary: { float: 'right', opacity: 0.541176, minWidth: '10vw' }
}
})
const times = (nbChildren, fn) =>
Array.from({ length: nbChildren }, (_, key) => fn(key));
const SimpleListLoading = props => {
const {
classes: classesOverride,
className,
hasLeftAvatarOrIcon,
hasRightAvatarOrIcon,
hasSecondaryText,
hasTertiaryText,
nbFakeLines = 5,
...rest
} = props;
const { classes } = useStylesLoading(props);
const oneSecondHasPassed = useTimeout(1000);
return oneSecondHasPassed ? (
<List className={className} {...rest}>
{times(nbFakeLines, key => (
<ListItem key={key}>
{hasLeftAvatarOrIcon && (
<ListItemAvatar>
<Avatar> </Avatar>
</ListItemAvatar>
)}
<ListItemText
primary={
<div>
<Placeholder className={classes.primary} />
{hasTertiaryText && (
<span className={classes.tertiary}>
<Placeholder />
</span>
)}
</div>
}
secondary={
hasSecondaryText ? <Placeholder /> : undefined
}
/>
{hasRightAvatarOrIcon && (
<ListItemSecondaryAction>
<Avatar> </Avatar>
</ListItemSecondaryAction>
)}
</ListItem>
))}
</List>
) : null;
};
SimpleListLoading.propTypes = {
className: PropTypes.string,
hasLeftAvatarOrIcon: PropTypes.bool,
hasRightAvatarOrIcon: PropTypes.bool,
hasSecondaryText: PropTypes.bool,
hasTertiaryText: PropTypes.bool,
nbFakeLines: PropTypes.number,
};
const useStyles = makeStyles()((theme) => {
return {
tertiary: { float: 'right', opacity: 0.541176 },
}
})
/**
* The <SimpleList> component renders a list of records as a material-ui <List>.
* It is usually used as a child of react-admin's <List> and <ReferenceManyField> components.
*
* Also widely used on Mobile.
*
* Props:
* - primaryText: function returning a React element (or some text) based on the record
* - secondaryText: same
* - tertiaryText: same
* - leftAvatar: function returning a React element based on the record
* - leftIcon: same
* - rightAvatar: same
* - rightIcon: same
* - linkType: 'edit' or 'show', or a function returning 'edit' or 'show' based on the record
* - rowStyle: function returning a style object based on (record, index)
*
* @example // Display all posts as a List
* const postRowStyle = (record, index) => ({
* backgroundColor: record.views >= 500 ? '#efe' : 'white',
* });
* export const PostList = (props) => (
* <List {...props}>
* <SimpleList
* primaryText={record => record.title}
* secondaryText={record => `${record.views} views`}
* tertiaryText={record =>
* new Date(record.published_at).toLocaleDateString()
* }
* rowStyle={postRowStyle}
* />
* </List>
* );
*/
const SelectSimpleList = props => {
const {
className,
classes: classesOverride,
bulkActionButtons = defaultBulkActionButtons,
leftAvatar,
leftIcon,
linkType = 'edit',
primaryText,
rightAvatar,
rightIcon,
secondaryText,
tertiaryText,
rowStyle,
isRowSelectable,
...rest
} = props;
const hasBulkActions = !!bulkActionButtons !== false;
const resource = useResourceContext(props);
const { data, isLoading, total, onToggleItem, selectedIds } = useListContext(props);
const { classes } = useStyles(props);
if (isLoading === true) {
return (
<SimpleListLoading
classes={classes}
className={className}
hasBulkActions={hasBulkActions}
hasLeftAvatarOrIcon={!!leftIcon || !!leftAvatar}
hasRightAvatarOrIcon={!!rightIcon || !!rightAvatar}
hasSecondaryText={!!secondaryText}
hasTertiaryText={!!tertiaryText}
/>
);
}
const isSelected = id => {
if (selectedIds.includes(id)){
return true;
}
return false;
}
return (
total > 0 && (
<>
{bulkActionButtons !== false ? (
<BulkActionsToolbar selectedIds={selectedIds}>
{isValidElement(bulkActionButtons)
? bulkActionButtons
: defaultBulkActionButtons}
</BulkActionsToolbar>
) : null}
<List className={className} {...sanitizeListRestProps(rest)}>
{data.map((record, rowIndex) => (
<RecordContextProvider key={record.id} value={record}>
<LinkOrNot
linkType={linkType}
resource={resource}
id={record.id}
key={record.id}
record={record}
style={
rowStyle
? rowStyle(record, rowIndex)
: undefined
}
>
{
!!isRowSelectable ? (
<>
{
!!isRowSelectable(record) ? (
<Checkbox
checked={isSelected(record.id)}
onChange={() => onToggleItem(record.id)}
color="primary"
onClick={(e) => e.stopPropagation()}
inputProps={{ 'aria-label': 'selected checkbox' }}
/>
) : (
<div style={{width: '46px'}} />
)
}
</>
) : (
<Checkbox
checked={isSelected(record.id)}
onChange={() => onToggleItem(record.id)}
color="primary"
onClick={(e) => e.stopPropagation()}
inputProps={{ 'aria-label': 'selected checkbox' }}
/>
)
}
{leftIcon && (
<ListItemIcon>
{leftIcon(record, record.id)}
</ListItemIcon>
)}
{leftAvatar && (
<ListItemAvatar>
<Avatar>{leftAvatar(record, record.id)}</Avatar>
</ListItemAvatar>
)}
<ListItemText
primary={
<div>
{primaryText(record, record.id)}
{tertiaryText && (
<span className={classes.tertiary}>
{tertiaryText(record, record.id)}
</span>
)}
</div>
}
secondary={
secondaryText && secondaryText(record, record.id)
}
/>
{(rightAvatar || rightIcon) && (
<ListItemSecondaryAction>
{rightAvatar && (
<Avatar>
{rightAvatar(record, record.id)}
</Avatar>
)}
{rightIcon && (
<ListItemIcon>
{rightIcon(record, record.id)}
</ListItemIcon>
)}
</ListItemSecondaryAction>
)}
</LinkOrNot>
</RecordContextProvider>
))}
</List>
</>
)
);
};
SelectSimpleList.propTypes = {
className: PropTypes.string,
classes: PropTypes.object,
leftAvatar: PropTypes.func,
leftIcon: PropTypes.func,
linkType: PropTypes.oneOfType([
PropTypes.string,
PropTypes.bool,
PropTypes.func,
]),
primaryText: PropTypes.func,
rightAvatar: PropTypes.func,
rightIcon: PropTypes.func,
secondaryText: PropTypes.func,
tertiaryText: PropTypes.func,
rowStyle: PropTypes.func,
};
const useLinkOrNotStyles = makeStyles()((theme) => {
return {
link: {
textDecoration: 'none',
color: 'inherit',
}
}
})
const LinkOrNot = ({
classes: classesOverride,
linkType,
resource,
id,
children,
record,
...rest
}) => {
const { classes } = useLinkOrNotStyles({ classes: classesOverride });
const createPath = useCreatePath();
const type =
typeof linkType === 'function' ? linkType(record, id) : linkType;
return type === false ? (
<ListItem
// @ts-ignore
component="div"
className={classes.link}
{...rest}
>
{children}
</ListItem>
) : (
// @ts-ignore
<ListItem
component={Link}
button={true}
to={createPath({ resource, id, type })}
className={classes.link}
{...rest}
>
{children}
</ListItem>
);
};
export default SelectSimpleList;