diff --git a/packages/ra-tree-ui-materialui/src/DragLayer.js b/packages/ra-tree-ui-materialui/src/DragLayer.js index 446378f734a..ab77817964a 100644 --- a/packages/ra-tree-ui-materialui/src/DragLayer.js +++ b/packages/ra-tree-ui-materialui/src/DragLayer.js @@ -2,13 +2,13 @@ * Custom DragLayer from Alejandro Hernandez * See https://github.com/react-dnd/react-dnd/issues/592#issuecomment-399287474 */ -import React, { Component } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import { DragLayer } from 'react-dnd'; -import compose from 'recompose/compose'; -import { withStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import isEqual from 'lodash/isEqual'; -const styles = { + +const useStyles = makeStyles({ layer: { position: 'fixed', pointerEvents: 'none', @@ -19,56 +19,50 @@ const styles = { height: '100%', }, item: {}, -}; - -class CustomDragLayer extends Component { - static propTypes = { - beingDragged: PropTypes.bool, - classes: PropTypes.object.isRequired, - dragPreviewComponent: PropTypes.oneOfType([ - PropTypes.element, - PropTypes.func, - ]).isRequired, - itemBeingDragged: PropTypes.object, - offset: PropTypes.object, - }; - - shouldComponentUpdate(nextProps) { - return !isEqual(this.props.offset, nextProps.offset); - } +}); - render() { - const { - classes, - beingDragged, - dragPreviewComponent: DragPreview, - itemBeingDragged, - offset, - } = this.props; - if (!beingDragged || !offset) return null; +const CustomDragLayer = ({ + beingDragged, + dragPreviewComponent: DragPreview, + itemBeingDragged, + offset, +}) => { + const classes = useStyles(); + if (!beingDragged || !offset) return null; - return ( -
-
- -
+ return ( +
+
+
- ); - } -} +
+ ); +}; + +CustomDragLayer.propTypes = { + beingDragged: PropTypes.bool, + dragPreviewComponent: PropTypes.oneOfType([ + PropTypes.element, + PropTypes.func, + ]).isRequired, + itemBeingDragged: PropTypes.object, + offset: PropTypes.object, +}; -export default compose( - withStyles(styles), - DragLayer(monitor => ({ - itemBeingDragged: monitor.getItem(), - componentType: monitor.getItemType(), - beingDragged: monitor.isDragging(), - offset: monitor.getSourceClientOffset(), - })) -)(CustomDragLayer); +export default DragLayer(monitor => ({ + itemBeingDragged: monitor.getItem(), + componentType: monitor.getItemType(), + beingDragged: monitor.isDragging(), + offset: monitor.getSourceClientOffset(), +}))( + React.memo( + CustomDragLayer, + (prevProps, nextProps) => !isEqual(prevProps.offset, nextProps.offset) + ) +); diff --git a/packages/ra-tree-ui-materialui/src/DragPreview.js b/packages/ra-tree-ui-materialui/src/DragPreview.js index 9a33376e082..23ce26a67fc 100644 --- a/packages/ra-tree-ui-materialui/src/DragPreview.js +++ b/packages/ra-tree-ui-materialui/src/DragPreview.js @@ -1,10 +1,9 @@ -import React, { Component } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; -import compose from 'recompose/compose'; -import { withStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import { translate } from 'ra-core'; -const styles = theme => ({ +const useStyles = makeStyles(theme => ({ item: { alignItems: 'center', backgroundColor: theme.palette.action.active, @@ -16,45 +15,35 @@ const styles = theme => ({ paddingLeft: theme.spacing(6), paddingRight: theme.spacing(4), }, -}); -class DragPreview extends Component { - shouldComponentUpdate() { - return false; - } - render() { - const { - children, - className, - classes, - node, - style, - translate, - } = this.props; - return ( -
- {children - ? typeof children === 'function' - ? children({ node, translate }) - : children - : translate('ra.tree.drag_preview', { - id: node.id, - smart_count: node.children.length, - })} -
- ); - } -} +})); + +const DragPreview = ({ children, className, node, style, translate }) => { + const classes = useStyles(); + + return ( +
+ {children + ? typeof children === 'function' + ? children({ node, translate }) + : children + : translate('ra.tree.drag_preview', { + id: node.id, + smart_count: node.children.length, + })} +
+ ); +}; DragPreview.propTypes = { children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), className: PropTypes.string, - classes: PropTypes.object, node: PropTypes.object, style: PropTypes.object, translate: PropTypes.func.isRequired, }; -export default compose( - translate, - withStyles(styles) -)(DragPreview); +export default translate()( + React.memo(DragPreview, () => { + return false; + }) +); diff --git a/packages/ra-tree-ui-materialui/src/NodeActions.js b/packages/ra-tree-ui-materialui/src/NodeActions.js index 8e239a1a95f..a1c987fc5b1 100644 --- a/packages/ra-tree-ui-materialui/src/NodeActions.js +++ b/packages/ra-tree-ui-materialui/src/NodeActions.js @@ -1,34 +1,32 @@ -import React, { cloneElement, Children, Component } from 'react'; +import React, { cloneElement, Children } from 'react'; import PropTypes from 'prop-types'; -import { withStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; -const styles = theme => ({ +const useStyles = makeStyles(theme => ({ root: { alignItems: 'center', marginLeft: 'auto', marginRight: theme.spacing(4), }, -}); +})); -export class NodeActions extends Component { - static propTypes = { - classes: PropTypes.object.isRequired, - basePath: PropTypes.string.isRequired, - children: PropTypes.node, - record: PropTypes.object.isRequired, - resource: PropTypes.string.isRequired, - }; +const NodeActions = ({ children, ...props }) => { + const classes = useStyles(); - render() { - const { children, classes, ...props } = this.props; - return ( - - {Children.map(children, action => - action ? cloneElement(action, props) : null - )} - - ); - } -} + return ( + + {Children.map(children, action => + action ? cloneElement(action, props) : null + )} + + ); +}; -export default withStyles(styles)(NodeActions); +NodeActions.propTypes = { + basePath: PropTypes.string.isRequired, + children: PropTypes.node, + record: PropTypes.object.isRequired, + resource: PropTypes.string.isRequired, +}; + +export default NodeActions; diff --git a/packages/ra-tree-ui-materialui/src/NodeForm.js b/packages/ra-tree-ui-materialui/src/NodeForm.js index efd02427123..5b172a78cf3 100644 --- a/packages/ra-tree-ui-materialui/src/NodeForm.js +++ b/packages/ra-tree-ui-materialui/src/NodeForm.js @@ -1,8 +1,8 @@ -import React, { cloneElement, Children, Component } from 'react'; +import React, { cloneElement, Children } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import compose from 'recompose/compose'; -import { withStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import { reduxForm } from 'redux-form'; import { crudUpdate as crudUpdateAction, @@ -11,13 +11,13 @@ import { import NodeFormActions from './NodeFormActions'; -const styles = { +const useStyles = makeStyles({ root: { alignItems: 'center', display: 'flex', flexGrow: 1, }, -}; +}); const sanitizeRestProps = ({ anyTouched, @@ -71,30 +71,26 @@ const sanitizeRestProps = ({ validate, ...props }) => props; -class NodeForm extends Component { - static propTypes = { - actions: PropTypes.node, - basePath: PropTypes.string.isRequired, - cancelDropOnChildren: PropTypes.bool, - children: PropTypes.node, - classes: PropTypes.object, - dispatchCrudUpdate: PropTypes.func.isRequired, - handleSubmit: PropTypes.func.isRequired, - invalid: PropTypes.bool, - node: PropTypes.object.isRequired, - pristine: PropTypes.bool, - resource: PropTypes.string.isRequired, - saving: PropTypes.bool, - startUndoable: PropTypes.func.isRequired, - submitOnEnter: PropTypes.bool, - undoable: PropTypes.bool, - }; - static defaultProps = { - actions: , - }; +const NodeForm = ({ + actions, + basePath, + children, + invalid, + node, + pristine, + resource, + saving, + submitOnEnter = true, + dispatchCrudUpdate, + node: { record }, + startUndoable, + undoable = true, + ...props +}) => { + const classes = useStyles(); - handleClick = event => { + const handleClick = event => { event.persist(); // This ensure clicking on an input or button does not collapse/expand a node // When clicking on the form (empty spaces around inputs) however, it should @@ -104,25 +100,15 @@ class NodeForm extends Component { } }; - handleDrop = event => { + const handleDrop = event => { event.persist(); if (this.props.cancelDropOnChildren) { event.preventDefault(); } }; - handleSubmit = () => { - const { - basePath, - dispatchCrudUpdate, - handleSubmit, - node: { record }, - resource, - startUndoable, - undoable = true, - } = this.props; - - return handleSubmit(values => + const handleSubmit = () => { + return props.handleSubmit(values => undoable ? startUndoable( crudUpdateAction( @@ -145,54 +131,58 @@ class NodeForm extends Component { ); }; - render() { - const { - actions, - basePath, - children, - classes, - handleSubmit, - invalid, - node, - pristine, - resource, - saving, - submitOnEnter = true, - ...props - } = this.props; + return ( +
+ {Children.map(children, field => + field + ? cloneElement(field, { + basePath: field.props.basePath || basePath, + onDrop: handleDrop, + record: node.record, + resource, + }) + : null + )} + {actions && + cloneElement(actions, { + basePath, + record: node.record, + resource, + handleSubmit: handleSubmit, + handleSubmitWithRedirect: handleSubmit, + invalid, + pristine, + saving, + submitOnEnter, + })} +
+ ); +}; + +NodeForm.propTypes = { + actions: PropTypes.node, + basePath: PropTypes.string.isRequired, + cancelDropOnChildren: PropTypes.bool, + children: PropTypes.node, + dispatchCrudUpdate: PropTypes.func.isRequired, + handleSubmit: PropTypes.func.isRequired, + invalid: PropTypes.bool, + node: PropTypes.object.isRequired, + pristine: PropTypes.bool, + resource: PropTypes.string.isRequired, + saving: PropTypes.bool, + startUndoable: PropTypes.func.isRequired, + submitOnEnter: PropTypes.bool, + undoable: PropTypes.bool, +}; - return ( -
- {Children.map(children, field => - field - ? cloneElement(field, { - basePath: field.props.basePath || basePath, - onDrop: this.handleDrop, - record: node.record, - resource, - }) - : null - )} - {actions && - cloneElement(actions, { - basePath, - record: node.record, - resource, - handleSubmit: this.handleSubmit, - handleSubmitWithRedirect: this.handleSubmit, - invalid, - pristine, - saving, - submitOnEnter, - })} -
- ); - } -} +NodeForm.defaultProps = { + actions: , +}; const mapStateToProps = (state, { node }) => ({ form: `tree-node-form-${node.id}`, @@ -211,6 +201,5 @@ export default compose( reduxForm({ enableReinitialize: true, keepDirtyOnReinitialize: true, - }), - withStyles(styles) + }) )(NodeForm); diff --git a/packages/ra-tree-ui-materialui/src/NodeView.js b/packages/ra-tree-ui-materialui/src/NodeView.js index 729ac790005..8838d158bd3 100644 --- a/packages/ra-tree-ui-materialui/src/NodeView.js +++ b/packages/ra-tree-ui-materialui/src/NodeView.js @@ -1,17 +1,17 @@ -import React, { cloneElement, Children, Component } from 'react'; +import React, { cloneElement, Children } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { withStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; const CONTAINER_CLASS = 'treenode-content'; -const styles = { +const useStyles = makeStyles({ root: { alignItems: 'center', display: 'flex', flexGrow: 1, }, -}; +}); const sanitizeRestProps = ({ cancelDropOnChildren, @@ -30,17 +30,17 @@ const sanitizeRestProps = ({ ...rest }) => rest; -export class NodeView extends Component { - static propTypes = { - actions: PropTypes.node, - basePath: PropTypes.string.isRequired, - children: PropTypes.node, - classes: PropTypes.object, - node: PropTypes.object.isRequired, - resource: PropTypes.string.isRequired, - }; +const NodeView = ({ + actions, + basePath, + children, + node, + resource, + ...props +}) => { + const classes = useStyles(); - handleClick = event => { + const handleClick = event => { event.persist(); // This ensure clicking on a button does not collapse/expand a node // When clicking on the form (empty spaces around buttons) however, it should @@ -50,41 +50,37 @@ export class NodeView extends Component { } }; - render() { - const { - actions, - basePath, - children, - classes, - node, - resource, - ...props - } = this.props; + return ( +
+ {Children.map(children, field => + field + ? cloneElement(field, { + basePath: field.props.basePath || basePath, + record: node.record, + resource, + }) + : null + )} + {actions && + cloneElement(actions, { + basePath, + record: node.record, + resource, + })} +
+ ); +}; - return ( -
- {Children.map(children, field => - field - ? cloneElement(field, { - basePath: field.props.basePath || basePath, - record: node.record, - resource, - }) - : null - )} - {actions && - cloneElement(actions, { - basePath, - record: node.record, - resource, - })} -
- ); - } -} +NodeView.propTypes = { + actions: PropTypes.node, + basePath: PropTypes.string.isRequired, + children: PropTypes.node, + node: PropTypes.object.isRequired, + resource: PropTypes.string.isRequired, +}; -export default withStyles(styles)(NodeView); +export default NodeView; diff --git a/packages/ra-tree-ui-materialui/src/RootDropTarget.js b/packages/ra-tree-ui-materialui/src/RootDropTarget.js index 8198ffd82d9..553ac33f149 100644 --- a/packages/ra-tree-ui-materialui/src/RootDropTarget.js +++ b/packages/ra-tree-ui-materialui/src/RootDropTarget.js @@ -1,9 +1,9 @@ -import React, { Component } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import compose from 'recompose/compose'; import classNames from 'classnames'; import { DropTarget } from 'react-dnd'; -import { withStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import Typography from '@material-ui/core/Typography'; import ListItem from '@material-ui/core/ListItem'; import IconGetApp from '@material-ui/icons/GetApp'; @@ -11,7 +11,7 @@ import { translate } from 'ra-core'; import { DROP_TARGET_TYPE } from './constants'; -const styles = theme => ({ +const useStyles = makeStyles(theme => ({ root: { paddingLeft: theme.spacing(6), }, @@ -23,51 +23,41 @@ const styles = theme => ({ hover: { backgroundColor: theme.palette.action.active, }, -}); +})); -class RootDropTarget extends Component { - static propTypes = { - canDrop: PropTypes.bool, - classes: PropTypes.object.isRequired, - connectDropTarget: PropTypes.func.isRequired, - isOverCurrent: PropTypes.bool, - translate: PropTypes.func.isRequired, - }; +const RootDropTarget = ({ + canDrop, + connectDropTarget, + isOverCurrent, + translate, +}) => { + const classes = useStyles(); - shouldComponentUpdate(nextProps) { - return ( - this.props.canDrop !== nextProps.canDrop || - this.props.isOverCurrent !== nextProps.isOverCurrent - ); - } + return ( + + + {connectDropTarget( +
+ + {translate('ra.tree.root_target')} + +
+ )} +
+ ); +}; - render() { - const { - canDrop, - classes, - connectDropTarget, - isOverCurrent, - translate, - } = this.props; - return ( - - - {connectDropTarget( -
- - {translate('ra.tree.root_target')} - -
- )} -
- ); - } -} +RootDropTarget.propTypes = { + canDrop: PropTypes.bool, + connectDropTarget: PropTypes.func.isRequired, + isOverCurrent: PropTypes.bool, + translate: PropTypes.func.isRequired, +}; const dropTargetSpecs = { drop(props, monitor) { @@ -93,6 +83,12 @@ const dropTargetConnect = (connect, monitor) => ({ export default compose( DropTarget(DROP_TARGET_TYPE, dropTargetSpecs, dropTargetConnect), - translate, - withStyles(styles) -)(RootDropTarget); + translate +)( + React.memo(RootDropTarget, (prevProps, nextProps) => { + return ( + prevProps.canDrop !== nextProps.canDrop || + prevProps.isOverCurrent !== nextProps.isOverCurrent + ); + }) +); diff --git a/packages/ra-tree-ui-materialui/src/Tree.js b/packages/ra-tree-ui-materialui/src/Tree.js index 394b5febb61..308a0495768 100644 --- a/packages/ra-tree-ui-materialui/src/Tree.js +++ b/packages/ra-tree-ui-materialui/src/Tree.js @@ -1,6 +1,6 @@ -import React, { Children, Component, Fragment } from 'react'; +import React, { Children, Fragment, useEffect } from 'react'; import PropTypes from 'prop-types'; -import { withStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import List from '@material-ui/core/List'; import { TreeController } from 'ra-tree-core'; import { DragDropContext } from 'react-dnd'; @@ -15,12 +15,12 @@ import DefaultTreeNodeContent from './TreeNodeContent'; import DefaultTreeNodeWithChildren from './TreeNodeWithChildren'; import RootDropTarget from './RootDropTarget'; -export const styles = { +const useStyles = makeStyles({ root: { display: 'flex', flexDirection: 'column', }, -}; +}); const sanitizeRestProps = ({ currentSort, @@ -100,97 +100,94 @@ If you need actions on each node, use the actions prop on either the NodeView or ` ); -export class Tree extends Component { - componentDidMount() { - const childrenCount = Children.count(this.props.children); +const Tree = ({ + allowDropOnRoot, + children, + dragPreviewComponent, + enableDragAndDrop, + parentSource, + treeNodeComponent, + treeNodeWithChildrenComponent, + treeNodeContentComponent, + ...props +}) => { + useEffect(() => { + const childrenCount = Children.count(children); if (childrenCount > 1 && process.env.NODE_ENV !== 'production') { warnAboutChildren(); } - } - - render() { - const { - allowDropOnRoot, - children, - classes, - dragPreviewComponent, - enableDragAndDrop, - parentSource, - treeNodeComponent, - treeNodeWithChildrenComponent, - treeNodeContentComponent, - ...props - } = this.props; - const Container = enableDragAndDrop - ? DragDropContext( - TouchBackend({ - enableKeyboardEvents: true, - enableMouseEvents: true, - enableTouchEvents: true, - }) - )('div') - : Fragment; - - const TreeNode = enableDragAndDrop - ? droppable(treeNodeComponent) - : treeNodeComponent; - - const TreeNodeContent = enableDragAndDrop - ? draggable(treeNodeContentComponent) - : treeNodeContentComponent; - - return ( - - {({ getTreeState, tree, ...controllerProps }) => ( - - {enableDragAndDrop ? ( - + }, [children]); + + const classes = useStyles(); + + const Container = enableDragAndDrop + ? DragDropContext( + TouchBackend({ + enableKeyboardEvents: true, + enableMouseEvents: true, + enableTouchEvents: true, + }) + )('div') + : Fragment; + + const TreeNode = enableDragAndDrop + ? droppable(treeNodeComponent) + : treeNodeComponent; + + const TreeNodeContent = enableDragAndDrop + ? draggable(treeNodeContentComponent) + : treeNodeContentComponent; + + return ( + + {({ getTreeState, tree, ...controllerProps }) => ( + + {enableDragAndDrop ? ( + + ) : null} + + {enableDragAndDrop && allowDropOnRoot ? ( + ) : null} - - {enableDragAndDrop && allowDropOnRoot ? ( - - ) : null} - {tree.map(node => ( - - {children} - - ))} - - - )} - - ); - } -} + {tree.map(node => ( + + {children} + + ))} + + + )} + + ); +}; Tree.propTypes = { allowDropOnRoot: PropTypes.bool, basePath: PropTypes.string.isRequired, children: PropTypes.node, - classes: PropTypes.object, enableDragAndDrop: PropTypes.bool, getTreeFromArray: PropTypes.func, parentSource: PropTypes.string, @@ -211,7 +208,6 @@ Tree.propTypes = { }; Tree.defaultProps = { - classes: {}, parentSource: 'parent_id', dragPreviewComponent: DefaultDragPreview, treeNodeComponent: DefaultTreeNode, @@ -219,4 +215,4 @@ Tree.defaultProps = { treeNodeWithChildrenComponent: DefaultTreeNodeWithChildren, }; -export default withStyles(styles)(Tree); +export default Tree; diff --git a/packages/ra-tree-ui-materialui/src/TreeNode.js b/packages/ra-tree-ui-materialui/src/TreeNode.js index 3abba8d2e72..55840c2c685 100644 --- a/packages/ra-tree-ui-materialui/src/TreeNode.js +++ b/packages/ra-tree-ui-materialui/src/TreeNode.js @@ -1,10 +1,10 @@ -import React, { Fragment, Component } from 'react'; +import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import ListItem from '@material-ui/core/ListItem'; -import { withStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; -const styles = theme => ({ +const useStyles = makeStyles(theme => ({ expandIcon: { margin: 0, left: -theme.spacing(6), @@ -71,132 +71,126 @@ const styles = theme => ({ draggingOver: { background: theme.palette.action.hover, }, -}); +})); -class TreeNode extends Component { - static propTypes = { - basePath: PropTypes.string.isRequired, - canDrop: PropTypes.bool, - children: PropTypes.node, - classes: PropTypes.object, - closeNode: PropTypes.func, - connectDropTarget: PropTypes.func, - expandNode: PropTypes.func, - getIsNodeExpanded: PropTypes.func, - isOver: PropTypes.bool, - isOverCurrent: PropTypes.bool, - itemType: PropTypes.string, - node: PropTypes.object.isRequired, - resource: PropTypes.string.isRequired, - toggleNode: PropTypes.func, - treeNodeComponent: PropTypes.oneOfType([ - PropTypes.element, - PropTypes.func, - ]), - treeNodeContentComponent: PropTypes.oneOfType([ - PropTypes.element, - PropTypes.func, - ]).isRequired, - treeNodeWithChildrenComponent: PropTypes.oneOfType([ - PropTypes.element, - PropTypes.func, - ]), - }; - - static defaultProps = { - connectDropTarget: target => target, - }; +const TreeNode = ({ + basePath, + canDrop, + children, + closeNode, + connectDropTarget, + expandNode, + getIsNodeExpanded, + isOver, + isOverCurrent, + itemType, + node, + resource, + treeNodeComponent, + treeNodeWithChildrenComponent: TreeNodeWithChildren, + treeNodeContentComponent: TreeNodeContent, + toggleNode, + ...props +}) => { + const classes = useStyles(); - handleDrop = event => { + const handleDrop = event => { if (this.props.isOver && this.props.canDrop) { event.persit(); event.preventDefault(); } }; - render() { - const { - basePath, - canDrop, - children, - classes, - closeNode, - connectDropTarget, - expandNode, - getIsNodeExpanded, - isOver, - isOverCurrent, - itemType, - node, - resource, - treeNodeComponent, - treeNodeWithChildrenComponent: TreeNodeWithChildren, - treeNodeContentComponent: TreeNodeContent, - toggleNode, - ...props - } = this.props; - return connectDropTarget( -
- 0, - [classes.leaf]: node.children.length === 0, - [classes.draggingOver]: isOverCurrent, - }), - }} - dense - disableGutters - > - {node.children.length > 0 ? ( - + 0, + [classes.leaf]: node.children.length === 0, + [classes.draggingOver]: isOverCurrent, + }), + }} + dense + disableGutters + > + {node.children.length > 0 ? ( + + {children} + + ) : ( + + {children} - - ) : ( - - - {children} - - - )} - -
- ); - } -} + + + )} + +
+ ); +}; + +TreeNode.propTypes = { + basePath: PropTypes.string.isRequired, + canDrop: PropTypes.bool, + children: PropTypes.node, + closeNode: PropTypes.func, + connectDropTarget: PropTypes.func, + expandNode: PropTypes.func, + getIsNodeExpanded: PropTypes.func, + isOver: PropTypes.bool, + isOverCurrent: PropTypes.bool, + itemType: PropTypes.string, + node: PropTypes.object.isRequired, + resource: PropTypes.string.isRequired, + toggleNode: PropTypes.func, + treeNodeComponent: PropTypes.oneOfType([PropTypes.element, PropTypes.func]), + treeNodeContentComponent: PropTypes.oneOfType([ + PropTypes.element, + PropTypes.func, + ]).isRequired, + treeNodeWithChildrenComponent: PropTypes.oneOfType([ + PropTypes.element, + PropTypes.func, + ]), +}; + +TreeNode.defaultProps = { + connectDropTarget: target => target, +}; -export default withStyles(styles)(TreeNode); +export default TreeNode; diff --git a/packages/ra-tree-ui-materialui/src/TreeNodeContent.js b/packages/ra-tree-ui-materialui/src/TreeNodeContent.js index 20598e4e33e..50ff4d6e604 100644 --- a/packages/ra-tree-ui-materialui/src/TreeNodeContent.js +++ b/packages/ra-tree-ui-materialui/src/TreeNodeContent.js @@ -1,62 +1,57 @@ -import React, { cloneElement, Children, Component, Fragment } from 'react'; +import React, { cloneElement, Children, Fragment } from 'react'; import PropTypes from 'prop-types'; import IconDragHandle from '@material-ui/icons/DragHandle'; -class TreeNodeContent extends Component { - static propTypes = { - basePath: PropTypes.string.isRequired, - cancelDropOnChildren: PropTypes.bool, - connectDragPreview: PropTypes.func, - connectDragSource: PropTypes.func, - containerElement: PropTypes.oneOfType([ - PropTypes.element, - PropTypes.func, - PropTypes.string, - ]), - children: PropTypes.node, - classes: PropTypes.object.isRequired, - expandNode: PropTypes.func, - isLeaf: PropTypes.bool, - node: PropTypes.object.isRequired, - resource: PropTypes.string.isRequired, - submit: PropTypes.func, - }; +const TreeNodeContent = ({ + children, + classes, + connectDragPreview, + connectDragSource, + containerElement: Container, + expandNode, + submit, + isLeaf, + node, + ...props +}) => ( + + {cloneElement(Children.only(children), { node, ...props })} + {connectDragPreview && + connectDragPreview(, { + // IE fallback: specify that we'd rather screenshot the node + // when it already knows it's being dragged so we can hide it with CSS. + captureDraggingState: true, + })} + {connectDragSource && + connectDragSource( +
+ +
+ )} +
+); - static defaultProps = { - containerElement: 'div', - }; +TreeNodeContent.propTypes = { + basePath: PropTypes.string.isRequired, + cancelDropOnChildren: PropTypes.bool, + connectDragPreview: PropTypes.func, + connectDragSource: PropTypes.func, + containerElement: PropTypes.oneOfType([ + PropTypes.element, + PropTypes.func, + PropTypes.string, + ]), + children: PropTypes.node, + classes: PropTypes.object.isRequired, + expandNode: PropTypes.func, + isLeaf: PropTypes.bool, + node: PropTypes.object.isRequired, + resource: PropTypes.string.isRequired, + submit: PropTypes.func, +}; - render() { - const { - children, - classes, - connectDragPreview, - connectDragSource, - containerElement: Container, - expandNode, - submit, - isLeaf, - node, - ...props - } = this.props; - return ( - - {cloneElement(Children.only(children), { node, ...props })} - {connectDragPreview && - connectDragPreview(, { - // IE fallback: specify that we'd rather screenshot the node - // when it already knows it's being dragged so we can hide it with CSS. - captureDraggingState: true, - })} - {connectDragSource && - connectDragSource( -
- -
- )} -
- ); - } -} +TreeNodeContent.defaultProps = { + containerElement: 'div', +}; export default TreeNodeContent; diff --git a/packages/ra-tree-ui-materialui/src/TreeNodeWithChildren.js b/packages/ra-tree-ui-materialui/src/TreeNodeWithChildren.js index ae6df3b2c8c..15bd226d89b 100644 --- a/packages/ra-tree-ui-materialui/src/TreeNodeWithChildren.js +++ b/packages/ra-tree-ui-materialui/src/TreeNodeWithChildren.js @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import List from '@material-ui/core/List'; import ExpansionPanel from '@material-ui/core/ExpansionPanel'; @@ -6,122 +6,115 @@ import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails'; import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary'; import KeyboardArrowDown from '@material-ui/icons/KeyboardArrowDown'; -export class TreeNodeWithChildren extends Component { - static propTypes = { - basePath: PropTypes.string.isRequired, - cancelDropOnChildren: PropTypes.bool, - children: PropTypes.node, - classes: PropTypes.object, - closeNode: PropTypes.func, - expandNode: PropTypes.func, - getIsNodeExpanded: PropTypes.func, - isExpanded: PropTypes.bool, - node: PropTypes.object.isRequired, - resource: PropTypes.string.isRequired, - toggleNode: PropTypes.func, - treeNodeComponent: PropTypes.oneOfType([ - PropTypes.element, - PropTypes.func, - ]), - treeNodeContentComponent: PropTypes.oneOfType([ - PropTypes.element, - PropTypes.func, - ]).isRequired, - treeNodeWithChildrenComponent: PropTypes.oneOfType([ - PropTypes.element, - PropTypes.func, - ]), - }; - - handleChange = () => { +const TreeNodeWithChildren = ({ + basePath, + cancelDropOnChildren, + children, + classes, + closeNode, + expandNode, + getIsNodeExpanded, + isExpanded, + node, + resource, + toggleNode, + treeNodeComponent: TreeNode, + treeNodeWithChildrenComponent, + treeNodeContentComponent: TreeNodeContent, + ...props +}) => { + const handleChange = () => { const { toggleNode, node } = this.props; toggleNode(node.id); }; - render() { - const { - basePath, - cancelDropOnChildren, - children, - classes, - closeNode, - expandNode, - getIsNodeExpanded, - isExpanded, - node, - resource, - toggleNode, - treeNodeComponent: TreeNode, - treeNodeWithChildrenComponent, - treeNodeContentComponent: TreeNodeContent, - ...props - } = this.props; - - return ( - + } > - } - > - - {children} - - - - - {node.children.map(child => ( - - {children} - - ))} - - - - ); - } -} + {children} + + + + + {node.children.map(child => ( + + {children} + + ))} + + + + ); +}; + +TreeNodeWithChildren.propTypes = { + basePath: PropTypes.string.isRequired, + cancelDropOnChildren: PropTypes.bool, + children: PropTypes.node, + classes: PropTypes.object, + closeNode: PropTypes.func, + expandNode: PropTypes.func, + getIsNodeExpanded: PropTypes.func, + isExpanded: PropTypes.bool, + node: PropTypes.object.isRequired, + resource: PropTypes.string.isRequired, + toggleNode: PropTypes.func, + treeNodeComponent: PropTypes.oneOfType([PropTypes.element, PropTypes.func]), + treeNodeContentComponent: PropTypes.oneOfType([ + PropTypes.element, + PropTypes.func, + ]).isRequired, + treeNodeWithChildrenComponent: PropTypes.oneOfType([ + PropTypes.element, + PropTypes.func, + ]), +}; export default TreeNodeWithChildren; diff --git a/packages/ra-ui-materialui/src/Link.tsx b/packages/ra-ui-materialui/src/Link.tsx index 97d573e929c..e026b7c5299 100644 --- a/packages/ra-ui-materialui/src/Link.tsx +++ b/packages/ra-ui-materialui/src/Link.tsx @@ -2,29 +2,36 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { Link as RRLink } from 'react-router-dom'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; -const styles = theme => - createStyles({ - link: { - textDecoration: 'none', - color: theme.palette.primary.main, - }, - }); +const useStyles = makeStyles(theme => ({ + link: { + textDecoration: 'none', + color: theme.palette.primary.main, + }, +})); /** * @deprecated Use react-router-dom's Link instead */ -const Link = ({ to, children, className, classes, ...rest }) => ( - - {children} - -); +const Link = ({ to, children, className, ...rest }) => { + const classes = useStyles({}); + + return ( + + {children} + + ); +}; + Link.propTypes = { className: PropTypes.string, - classes: PropTypes.object, children: PropTypes.node, to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), }; -export default withStyles(styles)(Link); +export default Link; diff --git a/packages/ra-ui-materialui/src/button/CreateButton.js b/packages/ra-ui-materialui/src/button/CreateButton.js index 5327618faff..8c9ab803fff 100644 --- a/packages/ra-ui-materialui/src/button/CreateButton.js +++ b/packages/ra-ui-materialui/src/button/CreateButton.js @@ -2,9 +2,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import onlyUpdateForKeys from 'recompose/onlyUpdateForKeys'; import Fab from '@material-ui/core/Fab'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import ContentAdd from '@material-ui/icons/Add'; -import compose from 'recompose/compose'; import classnames from 'classnames'; import { Link } from 'react-router-dom'; import { useTranslate } from 'ra-core'; @@ -12,32 +11,32 @@ import { useTranslate } from 'ra-core'; import Button from './Button'; import Responsive from '../layout/Responsive'; -const styles = theme => - createStyles({ - floating: { - color: theme.palette.getContrastText(theme.palette.primary.main), - margin: 0, - top: 'auto', - right: 20, - bottom: 60, - left: 'auto', - position: 'fixed', - zIndex: 1000, - }, - floatingLink: { - color: 'inherit', - }, - }); +const useStyles = makeStyles(theme => ({ + floating: { + color: theme.palette.getContrastText(theme.palette.primary.main), + margin: 0, + top: 'auto', + right: 20, + bottom: 60, + left: 'auto', + position: 'fixed', + zIndex: 1000, + }, + floatingLink: { + color: 'inherit', + }, +})); const CreateButton = ({ basePath = '', className, - classes = {}, label = 'ra.action.create', icon = , ...rest }) => { + const classes = useStyles(); const translate = useTranslate(); + return ( (data, field, resource) => }); }); -class ExportButton extends Component { - static propTypes = { - basePath: PropTypes.string, - dispatch: PropTypes.func, - exporter: PropTypes.func, - filter: PropTypes.object, - label: PropTypes.string, - maxResults: PropTypes.number.isRequired, - resource: PropTypes.string.isRequired, - sort: PropTypes.object, - icon: PropTypes.element, - }; - - static defaultProps = { - label: 'ra.action.export', - maxResults: 1000, - icon: , - }; - - handleClick = () => { - const { - dispatch, - exporter, - filter, - maxResults, - sort, - resource, - onClick, - } = this.props; +const ExportButton = ({ + dispatch, + icon, + exporter, + filter, + label, + maxResults, + sort, + resource, + onClick, + ...rest +}) => { + const handleClick = () => { dispatch( crudGetAll( resource, @@ -144,19 +128,33 @@ class ExportButton extends Component { } }; - render() { - const { label, icon, ...rest } = this.props; + return ( + + ); +}; - return ( - - ); - } -} +ExportButton.propTypes = { + basePath: PropTypes.string, + dispatch: PropTypes.func, + exporter: PropTypes.func, + filter: PropTypes.object, + label: PropTypes.string, + maxResults: PropTypes.number.isRequired, + resource: PropTypes.string.isRequired, + sort: PropTypes.object, + icon: PropTypes.element, +}; + +ExportButton.defaultProps = { + label: 'ra.action.export', + maxResults: 1000, + icon: , +}; export default connect()(ExportButton); // inject redux dispatch diff --git a/packages/ra-ui-materialui/src/button/RefreshButton.js b/packages/ra-ui-materialui/src/button/RefreshButton.js index a75ce9e39f8..c8e1df92399 100644 --- a/packages/ra-ui-materialui/src/button/RefreshButton.js +++ b/packages/ra-ui-materialui/src/button/RefreshButton.js @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import NavigationRefresh from '@material-ui/icons/Refresh'; @@ -6,20 +6,8 @@ import { refreshView as refreshViewAction } from 'ra-core'; import Button from './Button'; -class RefreshButton extends Component { - static propTypes = { - label: PropTypes.string, - refreshView: PropTypes.func.isRequired, - icon: PropTypes.element, - }; - - static defaultProps = { - label: 'ra.action.refresh', - icon: , - }; - - handleClick = event => { - const { refreshView, onClick } = this.props; +const RefreshButton = ({ label, refreshView, icon, onClick, ...rest }) => { + const handleClick = event => { event.preventDefault(); refreshView(); @@ -28,16 +16,23 @@ class RefreshButton extends Component { } }; - render() { - const { label, refreshView, icon, ...rest } = this.props; - - return ( - - ); - } -} + return ( + + ); +}; + +RefreshButton.propTypes = { + label: PropTypes.string, + refreshView: PropTypes.func.isRequired, + icon: PropTypes.element, +}; + +RefreshButton.defaultProps = { + label: 'ra.action.refresh', + icon: , +}; const enhance = connect( null, diff --git a/packages/ra-ui-materialui/src/button/RefreshIconButton.js b/packages/ra-ui-materialui/src/button/RefreshIconButton.js index c936bed37e8..24e524715f0 100644 --- a/packages/ra-ui-materialui/src/button/RefreshIconButton.js +++ b/packages/ra-ui-materialui/src/button/RefreshIconButton.js @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import compose from 'recompose/compose'; @@ -7,21 +7,15 @@ import IconButton from '@material-ui/core/IconButton'; import NavigationRefresh from '@material-ui/icons/Refresh'; import { refreshView, translate } from 'ra-core'; -class RefreshButton extends Component { - static propTypes = { - className: PropTypes.string, - label: PropTypes.string, - refreshView: PropTypes.func.isRequired, - translate: PropTypes.func.isRequired, - icon: PropTypes.element, - }; - - static defaultProps = { - label: 'ra.action.refresh', - icon: , - }; - - handleClick = event => { +const RefreshButton = ({ + className, + label, + refreshView, + translate, + icon, + ...rest +}) => { + const handleClick = event => { const { refreshView, onClick } = this.props; event.preventDefault(); refreshView(); @@ -31,31 +25,33 @@ class RefreshButton extends Component { } }; - render() { - const { - className, - label, - refreshView, - translate, - icon, - ...rest - } = this.props; + return ( + + + {icon} + + + ); +}; + +RefreshButton.propTypes = { + className: PropTypes.string, + label: PropTypes.string, + refreshView: PropTypes.func.isRequired, + translate: PropTypes.func.isRequired, + icon: PropTypes.element, +}; - return ( - - - {icon} - - - ); - } -} +RefreshButton.defaultProps = { + label: 'ra.action.refresh', + icon: , +}; const enhance = compose( connect( @@ -64,4 +60,5 @@ const enhance = compose( ), translate ); + export default enhance(RefreshButton); diff --git a/packages/ra-ui-materialui/src/button/SaveButton.js b/packages/ra-ui-materialui/src/button/SaveButton.js index 54dec39f631..e4c10af68bb 100644 --- a/packages/ra-ui-materialui/src/button/SaveButton.js +++ b/packages/ra-ui-materialui/src/button/SaveButton.js @@ -1,26 +1,25 @@ -import React, { Component, cloneElement } from 'react'; +import React, { cloneElement } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import compose from 'recompose/compose'; import Button from '@material-ui/core/Button'; import CircularProgress from '@material-ui/core/CircularProgress'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import ContentSave from '@material-ui/icons/Save'; import classnames from 'classnames'; import { showNotification, translate } from 'ra-core'; -const styles = theme => - createStyles({ - button: { - position: 'relative', - }, - leftIcon: { - marginRight: theme.spacing(1), - }, - icon: { - fontSize: 18, - }, - }); +const useStyles = makeStyles(theme => ({ + button: { + position: 'relative', + }, + leftIcon: { + marginRight: theme.spacing(1), + }, + icon: { + fontSize: 18, + }, +})); const sanitizeRestProps = ({ basePath, @@ -43,42 +42,26 @@ const sanitizeRestProps = ({ ...rest }) => rest; -export class SaveButton extends Component { - static propTypes = { - className: PropTypes.string, - classes: PropTypes.object, - handleSubmitWithRedirect: PropTypes.func, - invalid: PropTypes.bool, - label: PropTypes.string, - pristine: PropTypes.bool, - redirect: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.bool, - PropTypes.func, - ]), - saving: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]), - showNotification: PropTypes.func, - submitOnEnter: PropTypes.bool, - translate: PropTypes.func.isRequired, - variant: PropTypes.oneOf(['text', 'raised', 'flat', 'fab']), - icon: PropTypes.element, - }; - - static defaultProps = { - handleSubmitWithRedirect: () => () => {}, - icon: , - }; - - handleClick = e => { - const { - handleSubmitWithRedirect, - invalid, - redirect, - saving, - showNotification, - onClick, - } = this.props; +const SaveButton = ({ + className, + invalid, + label = 'ra.action.save', + pristine, + redirect, + saving, + submitOnEnter, + translate, + variant = 'contained', + icon, + onClick, + handleSubmitWithRedirect, + showNotification, + ...rest +}) => { + const classes = useStyles(); + const type = submitOnEnter ? 'submit' : 'button'; + const handleClick = e => { if (saving) { // prevent double submission e.preventDefault(); @@ -98,57 +81,61 @@ export class SaveButton extends Component { } }; - render() { - const { - className, - classes = {}, - invalid, - label = 'ra.action.save', - pristine, - redirect, - saving, - submitOnEnter, - translate, - variant = 'contained', - icon, - onClick, - ...rest - } = this.props; + return ( + + ); +}; + +SaveButton.propTypes = { + className: PropTypes.string, + handleSubmitWithRedirect: PropTypes.func, + invalid: PropTypes.bool, + label: PropTypes.string, + pristine: PropTypes.bool, + redirect: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.bool, + PropTypes.func, + ]), + saving: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]), + showNotification: PropTypes.func, + submitOnEnter: PropTypes.bool, + translate: PropTypes.func.isRequired, + variant: PropTypes.oneOf(['text', 'raised', 'flat', 'fab']), + icon: PropTypes.element, +}; - const type = submitOnEnter ? 'submit' : 'button'; - return ( - - ); - } -} +SaveButton.defaultProps = { + handleSubmitWithRedirect: () => () => {}, + icon: , +}; const enhance = compose( translate, connect( undefined, { showNotification } - ), - withStyles(styles) + ) ); export default enhance(SaveButton); diff --git a/packages/ra-ui-materialui/src/button/SaveButton.spec.js b/packages/ra-ui-materialui/src/button/SaveButton.spec.js index ba929cbc3f5..c6ae769e1a0 100644 --- a/packages/ra-ui-materialui/src/button/SaveButton.spec.js +++ b/packages/ra-ui-materialui/src/button/SaveButton.spec.js @@ -10,7 +10,7 @@ describe('', () => { const wrapper = shallow( ); - const ButtonElement = wrapper.find('WithStyles(ForwardRef(Button))'); + const ButtonElement = wrapper.find('ForwardRef(Button)'); expect(ButtonElement.length).toEqual(1); expect(ButtonElement.at(0).prop('raised')).toEqual(true); }); @@ -19,7 +19,7 @@ describe('', () => { const wrapper = shallow( ); - const ButtonElement = wrapper.find('WithStyles(ForwardRef(Button))'); + const ButtonElement = wrapper.find('ForwardRef(Button)'); expect(ButtonElement.length).toEqual(1); expect(ButtonElement.at(0).prop('raised')).toEqual(false); }); diff --git a/packages/ra-ui-materialui/src/detail/Tab.js b/packages/ra-ui-materialui/src/detail/Tab.js index 50d7a15875b..962d9dc979f 100644 --- a/packages/ra-ui-materialui/src/detail/Tab.js +++ b/packages/ra-ui-materialui/src/detail/Tab.js @@ -1,4 +1,4 @@ -import React, { Component, isValidElement } from 'react'; +import React, { isValidElement } from 'react'; import PropTypes from 'prop-types'; import { Link } from 'react-router-dom'; import MuiTab from '@material-ui/core/Tab'; @@ -58,70 +58,74 @@ const sanitizeRestProps = ({ * ); * export default App; */ -class Tab extends Component { - renderHeader = ({ className, label, icon, value, translate, ...rest }) => ( - - ); - renderContent = ({ - contentClassName, - children, - basePath, - record, - resource, - }) => ( - - {React.Children.map(children, field => - field && isValidElement(field) ? ( -
- {field.props.addLabel ? ( - - {field} - - ) : typeof field.type === 'string' ? ( - field - ) : ( - React.cloneElement(field, { - basePath, - record, - resource, - }) - )} -
- ) : null - )} -
- ); +const renderHeader = ({ + className, + label, + icon, + value, + translate, + ...rest +}) => ( + +); + +const renderContent = ({ + contentClassName, + children, + basePath, + record, + resource, +}) => ( + + {React.Children.map(children, field => + field && isValidElement(field) ? ( +
+ {field.props.addLabel ? ( + + {field} + + ) : typeof field.type === 'string' ? ( + field + ) : ( + React.cloneElement(field, { + basePath, + record, + resource, + }) + )} +
+ ) : null + )} +
+); - render() { - const { children, context, ...rest } = this.props; - return context === 'header' - ? this.renderHeader(rest) - : this.renderContent({ children, ...rest }); - } -} +const Tab = ({ children, context, ...rest }) => + context === 'header' + ? renderHeader(rest) + : renderContent({ children, ...rest }); Tab.propTypes = { className: PropTypes.string, diff --git a/packages/ra-ui-materialui/src/detail/TabbedShowLayout.js b/packages/ra-ui-materialui/src/detail/TabbedShowLayout.js index d8dd16205ca..a997a6c8bed 100644 --- a/packages/ra-ui-materialui/src/detail/TabbedShowLayout.js +++ b/packages/ra-ui-materialui/src/detail/TabbedShowLayout.js @@ -1,9 +1,4 @@ -import React, { - Component, - Children, - cloneElement, - isValidElement, -} from 'react'; +import React, { Children, cloneElement, isValidElement } from 'react'; import PropTypes from 'prop-types'; import Divider from '@material-ui/core/Divider'; import { withRouter, Route } from 'react-router-dom'; @@ -71,63 +66,54 @@ const getTabFullPath = (tab, index, baseUrl) => * ); * export default App; */ -export class TabbedShowLayout extends Component { - render() { - const { - basePath, - children, - className, - location, - match, - record, - resource, - translate, - version, - value, - tabs, - ...rest - } = this.props; - return ( -
- {cloneElement( - tabs, - { - // The location pathname will contain the page path including the current tab path - // so we can use it as a way to determine the current tab - value: location.pathname, - match, - }, - children - )} +const TabbedShowLayout = ({ + basePath, + children, + className, + location, + match, + record, + resource, + translate, + version, + value, + tabs, + ...rest +}) => ( +
+ {cloneElement( + tabs, + { + // The location pathname will contain the page path including the current tab path + // so we can use it as a way to determine the current tab + value: location.pathname, + match, + }, + children + )} - - - {Children.map(children, (tab, index) => - tab && isValidElement(tab) ? ( - - cloneElement(tab, { - context: 'content', - resource, - record, - basePath, - }) - } - /> - ) : null - )} - -
- ); - } -} + + + {Children.map(children, (tab, index) => + tab && isValidElement(tab) ? ( + + cloneElement(tab, { + context: 'content', + resource, + record, + basePath, + }) + } + /> + ) : null + )} + +
+); TabbedShowLayout.propTypes = { children: PropTypes.node, diff --git a/packages/ra-ui-materialui/src/field/BooleanField.tsx b/packages/ra-ui-materialui/src/field/BooleanField.tsx index 12d6967b654..563faf64ccf 100644 --- a/packages/ra-ui-materialui/src/field/BooleanField.tsx +++ b/packages/ra-ui-materialui/src/field/BooleanField.tsx @@ -5,14 +5,14 @@ import pure from 'recompose/pure'; import FalseIcon from '@material-ui/icons/Clear'; import TrueIcon from '@material-ui/icons/Done'; import Typography, { TypographyProps } from '@material-ui/core/Typography'; -import { createStyles, withStyles, WithStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import compose from 'recompose/compose'; import { useTranslate } from 'ra-core'; import { FieldProps, InjectedFieldProps, fieldPropTypes } from './types'; import sanitizeRestProps from './sanitizeRestProps'; -const styles = createStyles({ +const useStyles = makeStyles({ label: { // Move the text out of the flow of the container. position: 'absolute', @@ -37,19 +37,17 @@ interface Props extends FieldProps { valueLabelFalse?: string; } -interface EnhancedProps extends WithStyles {} - export const BooleanField: SFC< - Props & InjectedFieldProps & EnhancedProps & TypographyProps + Props & InjectedFieldProps & TypographyProps > = ({ className, - classes, source, record = {}, valueLabelTrue, valueLabelFalse, ...rest }) => { + const classes = useStyles({}); const translate = useTranslate(); const value = get(record, source); let ariaLabel = value ? valueLabelTrue : valueLabelFalse; @@ -101,12 +99,9 @@ export const BooleanField: SFC< }; const EnhancedBooleanField = compose< - Props & InjectedFieldProps & EnhancedProps & TypographyProps, + Props & InjectedFieldProps & TypographyProps, Props & TypographyProps ->( - pure, - withStyles(styles) -)(BooleanField); +>(pure)(BooleanField); EnhancedBooleanField.defaultProps = { addLabel: true, diff --git a/packages/ra-ui-materialui/src/field/ChipField.tsx b/packages/ra-ui-materialui/src/field/ChipField.tsx index 39cb1d1c02d..b7a6556ea8f 100644 --- a/packages/ra-ui-materialui/src/field/ChipField.tsx +++ b/packages/ra-ui-materialui/src/field/ChipField.tsx @@ -3,33 +3,37 @@ import compose from 'recompose/compose'; import get from 'lodash/get'; import pure from 'recompose/pure'; import Chip, { ChipProps } from '@material-ui/core/Chip'; -import { withStyles, WithStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import classnames from 'classnames'; import sanitizeRestProps from './sanitizeRestProps'; import { FieldProps, InjectedFieldProps, fieldPropTypes } from './types'; -const styles = createStyles({ +const useStyles = makeStyles({ chip: { margin: 4 }, }); -export const ChipField: SFC< - FieldProps & InjectedFieldProps & WithStyles & ChipProps -> = ({ className, classes, source, record = {}, ...rest }) => ( - -); +export const ChipField: SFC = ({ + className, + source, + record = {}, + ...rest +}) => { + const classes = useStyles({}); + + return ( + + ); +}; const EnhancedChipField = compose< - FieldProps & InjectedFieldProps & WithStyles & ChipProps, + FieldProps & InjectedFieldProps & ChipProps, FieldProps & ChipProps ->( - withStyles(styles), - pure -)(ChipField); +>(pure)(ChipField); EnhancedChipField.defaultProps = { addLabel: true, diff --git a/packages/ra-ui-materialui/src/field/FileField.tsx b/packages/ra-ui-materialui/src/field/FileField.tsx index 7f021f05299..c27b6ca239d 100644 --- a/packages/ra-ui-materialui/src/field/FileField.tsx +++ b/packages/ra-ui-materialui/src/field/FileField.tsx @@ -1,13 +1,13 @@ import React, { SFC, ComponentType } from 'react'; import PropTypes from 'prop-types'; import get from 'lodash/get'; -import { withStyles, WithStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import classnames from 'classnames'; import sanitizeRestProps from './sanitizeRestProps'; import { FieldProps, InjectedFieldProps, fieldPropTypes } from './types'; -const styles = createStyles({ +const useStyles = makeStyles({ root: { display: 'inline-block' }, }); @@ -17,9 +17,16 @@ interface Props extends FieldProps { target?: string; } -export const FileField: SFC< - Props & InjectedFieldProps & WithStyles -> = ({ classes, className, record, source, title, src, target, ...rest }) => { +export const FileField: SFC = ({ + className, + record, + source, + title, + src, + target, + ...rest +}) => { + const classes = useStyles({}); const sourceValue = get(record, source); if (!sourceValue) { @@ -71,7 +78,7 @@ export const FileField: SFC< ); }; -const EnhancedFileField = withStyles(styles)(FileField) as ComponentType; +const EnhancedFileField = FileField as ComponentType; EnhancedFileField.defaultProps = { addLabel: true, diff --git a/packages/ra-ui-materialui/src/field/ImageField.tsx b/packages/ra-ui-materialui/src/field/ImageField.tsx index 4c7c59ca028..e4d3f20bd0c 100644 --- a/packages/ra-ui-materialui/src/field/ImageField.tsx +++ b/packages/ra-ui-materialui/src/field/ImageField.tsx @@ -1,13 +1,13 @@ import React, { SFC, ComponentType } from 'react'; import PropTypes from 'prop-types'; import get from 'lodash/get'; -import { withStyles, WithStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import classnames from 'classnames'; import sanitizeRestProps from './sanitizeRestProps'; import { FieldProps, InjectedFieldProps, fieldPropTypes } from './types'; -const styles = createStyles({ +const useStyles = makeStyles({ list: { display: 'flex', listStyleType: 'none', @@ -23,9 +23,15 @@ interface Props extends FieldProps { title?: string; } -export const ImageField: SFC< - Props & InjectedFieldProps & WithStyles -> = ({ className, classes, record, source, src, title, ...rest }) => { +export const ImageField: SFC = ({ + className, + record, + source, + src, + title, + ...rest +}) => { + const classes = useStyles({}); const sourceValue = get(record, source); if (!sourceValue) { return
; @@ -73,9 +79,7 @@ export const ImageField: SFC< // wat? TypeScript looses the displayName if we don't set it explicitly ImageField.displayName = 'ImageField'; -const EnhancedImageField = withStyles(styles)(ImageField) as ComponentType< - Props ->; +const EnhancedImageField = ImageField as ComponentType; EnhancedImageField.defaultProps = { addLabel: true, diff --git a/packages/ra-ui-materialui/src/form/FormInput.js b/packages/ra-ui-materialui/src/form/FormInput.js index 8314085935f..6825f4b4b7e 100644 --- a/packages/ra-ui-materialui/src/form/FormInput.js +++ b/packages/ra-ui-materialui/src/form/FormInput.js @@ -1,19 +1,20 @@ import React from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import Labeled from '../input/Labeled'; const sanitizeRestProps = ({ basePath, record, ...rest }) => rest; -const styles = theme => - createStyles({ - input: { width: theme.spacing(32) }, - }); +const useStyles = makeStyles(theme => ({ + input: { width: theme.spacing(32) }, +})); -export const FormInput = ({ classes, input, ...rest }) => - input ? ( +export const FormInput = ({ input, ...rest }) => { + const classes = useStyles(); + + return input ? (
)}
) : null; +}; FormInput.propTypes = { className: PropTypes.string, - classes: PropTypes.object, input: PropTypes.object, }; // wat? TypeScript looses the displayName if we don't set it explicitly FormInput.displayName = 'FormInput'; -export default withStyles(styles)(FormInput); +export default FormInput; diff --git a/packages/ra-ui-materialui/src/form/FormTab.js b/packages/ra-ui-materialui/src/form/FormTab.js index 5431b784635..5d602e7b124 100644 --- a/packages/ra-ui-materialui/src/form/FormTab.js +++ b/packages/ra-ui-materialui/src/form/FormTab.js @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import { Link } from 'react-router-dom'; import MuiTab from '@material-ui/core/Tab'; @@ -18,55 +18,59 @@ const sanitizeRestProps = ({ const hiddenStyle = { display: 'none' }; -class FormTab extends Component { - renderHeader = ({ className, label, icon, value, translate, ...rest }) => { - const to = { pathname: value, search: 'skipFormReset' }; // FIXME use location state when https://github.com/supasate/connected-react-router/issues/301 is fixed - - return ( - - ); - }; +const renderHeader = ({ + className, + label, + icon, + value, + translate, + ...rest +}) => { + const to = { pathname: value, search: 'skipFormReset' }; // FIXME use location state when https://github.com/supasate/connected-react-router/issues/301 is fixed - renderContent = ({ - contentClassName, - children, - hidden, - basePath, - record, - resource, - }) => ( - - {React.Children.map( - children, - input => - input && ( - - ) - )} - + return ( + ); +}; - render() { - const { children, intent, ...rest } = this.props; - return intent === 'header' - ? this.renderHeader(rest) - : this.renderContent({ children, ...rest }); - } -} +const renderContent = ({ + contentClassName, + children, + hidden, + basePath, + record, + resource, +}) => ( + + {React.Children.map( + children, + input => + input && ( + + ) + )} + +); + +const FormTab = ({ children, intent, ...rest }) => { + return intent === 'header' + ? renderHeader(rest) + : renderContent({ children, ...rest }); +}; FormTab.propTypes = { className: PropTypes.string, diff --git a/packages/ra-ui-materialui/src/form/SimpleForm.js b/packages/ra-ui-materialui/src/form/SimpleForm.js index a08653d1ab0..160d5aaf9d5 100644 --- a/packages/ra-ui-materialui/src/form/SimpleForm.js +++ b/packages/ra-ui-materialui/src/form/SimpleForm.js @@ -1,4 +1,4 @@ -import React, { Children, Component } from 'react'; +import React, { Children } from 'react'; import PropTypes from 'prop-types'; import { reduxForm } from 'redux-form'; import { connect } from 'react-redux'; @@ -53,61 +53,59 @@ const sanitizeRestProps = ({ ...props }) => props; -export class SimpleForm extends Component { - handleSubmitWithRedirect = (redirect = this.props.redirect) => - this.props.handleSubmit(values => this.props.save(values, redirect)); - - render() { - const { - basePath, - children, - className, - invalid, - pristine, - record, - redirect, - resource, - saving, - submitOnEnter, - toolbar, - undoable, - version, - ...rest - } = this.props; +const SimpleForm = ({ + basePath, + children, + className, + invalid, + pristine, + record, + redirect, + resource, + saving, + submitOnEnter, + toolbar, + undoable, + version, + save, + handleSubmit, + ...rest +}) => { + const handleSubmitWithRedirect = (redirect = redirect) => + handleSubmit(values => save(values, redirect)); - return ( -
- - {Children.map(children, input => ( - - ))} - - {toolbar && - React.cloneElement(toolbar, { - basePath, - handleSubmitWithRedirect: this.handleSubmitWithRedirect, - handleSubmit: this.props.handleSubmit, - invalid, - pristine, - record, - redirect, - resource, - saving, - submitOnEnter, - undoable, - })} -
- ); - } -} + return ( +
+ + {Children.map(children, input => ( + + ))} + + {toolbar && + React.cloneElement(toolbar, { + basePath, + handleSubmitWithRedirect, + handleSubmit, + invalid, + pristine, + record, + redirect, + resource, + saving, + submitOnEnter, + undoable, + })} +
+ ); +}; SimpleForm.propTypes = { basePath: PropTypes.string, diff --git a/packages/ra-ui-materialui/src/form/SimpleFormIterator.js b/packages/ra-ui-materialui/src/form/SimpleFormIterator.js index 00f25cdbe88..8aabc19af29 100644 --- a/packages/ra-ui-materialui/src/form/SimpleFormIterator.js +++ b/packages/ra-ui-materialui/src/form/SimpleFormIterator.js @@ -1,17 +1,11 @@ -import React, { - Children, - cloneElement, - Component, - isValidElement, -} from 'react'; +import React, { Children, cloneElement, isValidElement } from 'react'; import PropTypes from 'prop-types'; -import compose from 'recompose/compose'; import { CSSTransition, TransitionGroup } from 'react-transition-group'; import get from 'lodash/get'; import Typography from '@material-ui/core/Typography'; import Button from '@material-ui/core/Button'; import FormHelperText from '@material-ui/core/FormHelperText'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import CloseIcon from '@material-ui/icons/RemoveCircleOutline'; import AddIcon from '@material-ui/icons/AddCircleOutline'; import { translate } from 'ra-core'; @@ -19,75 +13,83 @@ import classNames from 'classnames'; import FormInput from '../form/FormInput'; -const styles = theme => - createStyles({ - root: { - padding: 0, - marginBottom: 0, - '& > li:last-child': { - borderBottom: 'none', - }, +const useStyles = makeStyles(theme => ({ + root: { + padding: 0, + marginBottom: 0, + '& > li:last-child': { + borderBottom: 'none', }, - line: { - display: 'flex', - listStyleType: 'none', - borderBottom: `solid 1px ${theme.palette.divider}`, - [theme.breakpoints.down('xs')]: { display: 'block' }, - '&.fade-enter': { - opacity: 0.01, - transform: 'translateX(100vw)', - }, - '&.fade-enter-active': { - opacity: 1, - transform: 'translateX(0)', - transition: 'all 500ms ease-in', - }, - '&.fade-exit': { - opacity: 1, - transform: 'translateX(0)', - }, - '&.fade-exit-active': { - opacity: 0.01, - transform: 'translateX(100vw)', - transition: 'all 500ms ease-in', - }, + }, + line: { + display: 'flex', + listStyleType: 'none', + borderBottom: `solid 1px ${theme.palette.divider}`, + [theme.breakpoints.down('xs')]: { display: 'block' }, + '&.fade-enter': { + opacity: 0.01, + transform: 'translateX(100vw)', }, - index: { - width: '3em', - paddingTop: '1em', - [theme.breakpoints.down('sm')]: { display: 'none' }, + '&.fade-enter-active': { + opacity: 1, + transform: 'translateX(0)', + transition: 'all 500ms ease-in', }, - form: { flex: 2 }, - action: { - paddingTop: '0.5em', + '&.fade-exit': { + opacity: 1, + transform: 'translateX(0)', }, - leftIcon: { - marginRight: theme.spacing(1), + '&.fade-exit-active': { + opacity: 0.01, + transform: 'translateX(100vw)', + transition: 'all 500ms ease-in', }, - }); + }, + index: { + width: '3em', + paddingTop: '1em', + [theme.breakpoints.down('sm')]: { display: 'none' }, + }, + form: { flex: 2 }, + action: { + paddingTop: '0.5em', + }, + leftIcon: { + marginRight: theme.spacing(1), + }, +})); -export class SimpleFormIterator extends Component { - constructor(props) { - super(props); - // we need a unique id for each field for a proper enter/exit animation - // but redux-form doesn't provide one (cf https://github.com/erikras/redux-form/issues/2735) - // so we keep an internal map between the field position and an auto-increment id - this.nextId = props.fields.length - ? props.fields.length - : props.defaultValue - ? props.defaultValue.length - : 0; +const SimpleFormIterator = ({ + basePath, + children, + fields, + meta: { error, submitFailed }, + record, + resource, + source, + translate, + disableAdd, + disableRemove, + defaultValue, +}) => { + const classes = useStyles(); + // we need a unique id for each field for a proper enter/exit animation + // but redux-form doesn't provide one (cf https://github.com/erikras/redux-form/issues/2735) + // so we keep an internal map between the field position and an auto-increment id + let nextId = fields.length + ? fields.length + : defaultValue + ? defaultValue.length + : 0; - // We check whether we have a defaultValue (which must be an array) before checking - // the fields prop which will always be empty for a new record. - // Without it, our ids wouldn't match the default value and we would get key warnings - // on the CssTransition element inside our render method - this.ids = this.nextId > 0 ? Array.from(Array(this.nextId).keys()) : []; - } + // We check whether we have a defaultValue (which must be an array) before checking + // the fields prop which will always be empty for a new record. + // Without it, our ids wouldn't match the default value and we would get key warnings + // on the CssTransition element inside our render method + const ids = nextId > 0 ? Array.from(Array(nextId).keys()) : []; - removeField = index => () => { - const { fields } = this.props; - this.ids.splice(index, 1); + const removeField = index => () => { + ids.splice(index, 1); fields.remove(index); }; @@ -95,129 +97,111 @@ export class SimpleFormIterator extends Component { // If disableRemove is a function, then call the function with the current record to // determining if the button should be disabled. Otherwise, use a boolean property that // enables or disables the button for all of the fields. - disableRemoveField = (record, disableRemove) => { + const disableRemoveField = (record, disableRemove) => { if (typeof disableRemove === 'boolean') { return disableRemove; } return disableRemove && disableRemove(record); }; - addField = () => { - const { fields } = this.props; - this.ids.push(this.nextId++); + const addField = () => { + ids.push(nextId++); fields.push({}); }; - render() { - const { - basePath, - classes = {}, - children, - fields, - meta: { error, submitFailed }, - record, - resource, - source, - translate, - disableAdd, - disableRemove, - } = this.props; - const records = get(record, source); - return fields ? ( -
    - {submitFailed && error && ( - {error} - )} - - {fields.map((member, index) => ( - -
  • - - {index + 1} - -
    - {Children.map(children, (input, index2) => - isValidElement(input) ? ( - - ) : null - )} -
    - {!this.disableRemoveField( - (records && records[index]) || {}, - disableRemove - ) && ( - - - - )} -
  • -
    - ))} -
    - {!disableAdd && ( -
  • - - - -
  • - )} -
- ) : null; - } -} + {index + 1} + +
+ {Children.map(children, (input, index2) => + isValidElement(input) ? ( + + ) : null + )} +
+ {!disableRemoveField( + (records && records[index]) || {}, + disableRemove + ) && ( + + + + )} + + + ))} + + {!disableAdd && ( +
  • + + + +
  • + )} + + ) : null; +}; SimpleFormIterator.defaultProps = { disableAdd: false, @@ -228,7 +212,6 @@ SimpleFormIterator.propTypes = { defaultValue: PropTypes.any, basePath: PropTypes.string, children: PropTypes.node, - classes: PropTypes.object, className: PropTypes.string, fields: PropTypes.object, meta: PropTypes.object, @@ -240,7 +223,4 @@ SimpleFormIterator.propTypes = { disableRemove: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]), }; -export default compose( - translate, - withStyles(styles) -)(SimpleFormIterator); +export default translate()(SimpleFormIterator); diff --git a/packages/ra-ui-materialui/src/form/TabbedForm.js b/packages/ra-ui-materialui/src/form/TabbedForm.js index 8762d2bdaf0..2923fbad342 100644 --- a/packages/ra-ui-materialui/src/form/TabbedForm.js +++ b/packages/ra-ui-materialui/src/form/TabbedForm.js @@ -1,4 +1,4 @@ -import React, { Children, Component, isValidElement } from 'react'; +import React, { Children, isValidElement } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import { @@ -11,17 +11,16 @@ import { connect } from 'react-redux'; import { withRouter, Route } from 'react-router-dom'; import compose from 'recompose/compose'; import Divider from '@material-ui/core/Divider'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import { getDefaultValues, translate, REDUX_FORM_NAME } from 'ra-core'; import Toolbar from './Toolbar'; import CardContentInner from '../layout/CardContentInner'; import TabbedFormTabs from './TabbedFormTabs'; -const styles = theme => - createStyles({ - errorTabButton: { color: theme.palette.error.main }, - }); +const useStyles = makeStyles(theme => ({ + errorTabButton: { color: theme.palette.error.main }, +})); const sanitizeRestProps = ({ anyTouched, @@ -72,119 +71,114 @@ export const getTabFullPath = (tab, index, baseUrl) => tab.props.path ? `/${tab.props.path}` : index > 0 ? `/${index}` : '' }`; -export class TabbedForm extends Component { - handleSubmitWithRedirect = (redirect = this.props.redirect) => - this.props.handleSubmit(values => this.props.save(values, redirect)); +const TabbedForm = ({ + basePath, + children, + className, + invalid, + location, + match, + pristine, + record, + redirect, + resource, + saving, + submitOnEnter, + tabs, + tabsWithErrors, + toolbar, + translate, + undoable, + value, + version, + save, + handleSubmit, + ...rest +}) => { + const classes = useStyles(); + const handleSubmitWithRedirect = (redirect = redirect) => + handleSubmit(values => save(values, redirect)); - render() { - const { - basePath, - children, - className, - classes = {}, - invalid, - location, - match, - pristine, - record, - redirect, - resource, - saving, - submitOnEnter, - tabs, - tabsWithErrors, - toolbar, - translate, - undoable, - value, - version, - ...rest - } = this.props; + const url = match ? match.url : location.pathname; - const url = match ? match.url : location.pathname; - return ( -
    - {React.cloneElement( - tabs, - { - classes, - currentLocationPath: location.pathname, - url, - tabsWithErrors, - }, - children + return ( + + {React.cloneElement( + tabs, + { + classes, + currentLocationPath: location.pathname, + url, + tabsWithErrors, + }, + children + )} + + + {/* All tabs are rendered (not only the one in focus), to allow validation + on tabs not in focus. The tabs receive a `hidden` property, which they'll + use to hide the tab using CSS if it's not the one in focus. + See https://github.com/marmelab/react-admin/issues/1866 */} + {Children.map( + children, + (tab, index) => + tab && ( + + {routeProps => + isValidElement(tab) + ? React.cloneElement(tab, { + intent: 'content', + resource, + record, + basePath, + hidden: !routeProps.match, + /** + * Force redraw when the tab becomes active + * + * This is because the fields, decorated by redux-form and connect, + * aren't redrawn by default when the tab becomes active. + * Unfortunately, some material-ui fields (like multiline TextField) + * compute their size based on the scrollHeight of a dummy DOM element, + * and scrollHeight is 0 in a hidden div. So they must be redrawn + * once the tab becomes active. + * + * @ref https://github.com/marmelab/react-admin/issues/1956 + */ + key: `${index}_${!routeProps.match}`, + }) + : null + } + + ) )} - - - {/* All tabs are rendered (not only the one in focus), to allow validation - on tabs not in focus. The tabs receive a `hidden` property, which they'll - use to hide the tab using CSS if it's not the one in focus. - See https://github.com/marmelab/react-admin/issues/1866 */} - {Children.map( - children, - (tab, index) => - tab && ( - - {routeProps => - isValidElement(tab) - ? React.cloneElement(tab, { - intent: 'content', - resource, - record, - basePath, - hidden: !routeProps.match, - /** - * Force redraw when the tab becomes active - * - * This is because the fields, decorated by redux-form and connect, - * aren't redrawn by default when the tab becomes active. - * Unfortunately, some material-ui fields (like multiline TextField) - * compute their size based on the scrollHeight of a dummy DOM element, - * and scrollHeight is 0 in a hidden div. So they must be redrawn - * once the tab becomes active. - * - * @ref https://github.com/marmelab/react-admin/issues/1956 - */ - key: `${index}_${!routeProps.match}`, - }) - : null - } - - ) - )} - - {toolbar && - React.cloneElement(toolbar, { - basePath, - className: 'toolbar', - handleSubmitWithRedirect: this.handleSubmitWithRedirect, - handleSubmit: this.props.handleSubmit, - invalid, - pristine, - record, - redirect, - resource, - saving, - submitOnEnter, - undoable, - })} - - ); - } -} + + {toolbar && + React.cloneElement(toolbar, { + basePath, + className: 'toolbar', + handleSubmitWithRedirect, + handleSubmit, + invalid, + pristine, + record, + redirect, + resource, + saving, + submitOnEnter, + undoable, + })} + + ); +}; TabbedForm.propTypes = { basePath: PropTypes.string, children: PropTypes.node, className: PropTypes.string, - classes: PropTypes.object, defaultValue: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), handleSubmit: PropTypes.func, // passed by redux-form invalid: PropTypes.bool, @@ -283,8 +277,7 @@ const enhance = compose( destroyOnUnmount: false, enableReinitialize: true, keepDirtyOnReinitialize: true, - }), - withStyles(styles) + }) ); export default enhance(TabbedForm); diff --git a/packages/ra-ui-materialui/src/form/Toolbar.js b/packages/ra-ui-materialui/src/form/Toolbar.js index e7c7bbb7e7d..119cf658d7c 100644 --- a/packages/ra-ui-materialui/src/form/Toolbar.js +++ b/packages/ra-ui-materialui/src/form/Toolbar.js @@ -1,46 +1,44 @@ import React, { Children, Fragment, isValidElement } from 'react'; import PropTypes from 'prop-types'; -import compose from 'recompose/compose'; import MuiToolbar from '@material-ui/core/Toolbar'; import withWidth from '@material-ui/core/withWidth'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import classnames from 'classnames'; import { SaveButton, DeleteButton } from '../button'; -const styles = theme => - createStyles({ - toolbar: { - backgroundColor: - theme.palette.type === 'light' - ? theme.palette.grey[100] - : theme.palette.grey[900], +const useStyles = makeStyles(theme => ({ + toolbar: { + backgroundColor: + theme.palette.type === 'light' + ? theme.palette.grey[100] + : theme.palette.grey[900], + }, + desktopToolbar: { + marginTop: theme.spacing(2), + }, + mobileToolbar: { + position: 'fixed', + bottom: 0, + left: 0, + right: 0, + padding: '16px', + width: '100%', + boxSizing: 'border-box', + flexShrink: 0, + zIndex: 2, + }, + defaultToolbar: { + flex: 1, + display: 'flex', + justifyContent: 'space-between', + }, + spacer: { + [theme.breakpoints.down('xs')]: { + height: '5em', }, - desktopToolbar: { - marginTop: theme.spacing(2), - }, - mobileToolbar: { - position: 'fixed', - bottom: 0, - left: 0, - right: 0, - padding: '16px', - width: '100%', - boxSizing: 'border-box', - flexShrink: 0, - zIndex: 2, - }, - defaultToolbar: { - flex: 1, - display: 'flex', - justifyContent: 'space-between', - }, - spacer: { - [theme.breakpoints.down('xs')]: { - height: '5em', - }, - }, - }); + }, +})); const valueOrDefault = (value, defaultValue) => typeof value === 'undefined' ? defaultValue : value; @@ -48,7 +46,6 @@ const valueOrDefault = (value, defaultValue) => const Toolbar = ({ basePath, children, - classes, className, handleSubmit, handleSubmitWithRedirect, @@ -62,77 +59,80 @@ const Toolbar = ({ undoable, width, ...rest -}) => ( - - - {Children.count(children) === 0 ? ( -
    - - {record && typeof record.id !== 'undefined' && ( - { + const classes = useStyles(); + + return ( + + + {Children.count(children) === 0 ? ( +
    + - )} -
    - ) : ( - Children.map(children, button => - button && isValidElement(button) - ? React.cloneElement(button, { - basePath, - handleSubmit: valueOrDefault( - button.props.handleSubmit, - handleSubmit - ), - handleSubmitWithRedirect: valueOrDefault( - button.props.handleSubmitWithRedirect, - handleSubmitWithRedirect - ), - invalid, - pristine, - record, - resource, - saving, - submitOnEnter: valueOrDefault( - button.props.submitOnEnter, - submitOnEnter - ), - undoable: valueOrDefault( - button.props.undoable, - undoable - ), - }) - : null - ) - )} -
    -
    - -); + {record && typeof record.id !== 'undefined' && ( + + )} +
    + ) : ( + Children.map(children, button => + button && isValidElement(button) + ? React.cloneElement(button, { + basePath, + handleSubmit: valueOrDefault( + button.props.handleSubmit, + handleSubmit + ), + handleSubmitWithRedirect: valueOrDefault( + button.props.handleSubmitWithRedirect, + handleSubmitWithRedirect + ), + invalid, + pristine, + record, + resource, + saving, + submitOnEnter: valueOrDefault( + button.props.submitOnEnter, + submitOnEnter + ), + undoable: valueOrDefault( + button.props.undoable, + undoable + ), + }) + : null + ) + )} + +
    + + ); +}; Toolbar.propTypes = { basePath: PropTypes.string, children: PropTypes.node, - classes: PropTypes.object, className: PropTypes.string, handleSubmit: PropTypes.func, handleSubmitWithRedirect: PropTypes.func, @@ -155,8 +155,4 @@ Toolbar.defaultProps = { submitOnEnter: true, }; -const enhance = compose( - withWidth(), - withStyles(styles) -); -export default enhance(Toolbar); +export default withWidth()(Toolbar); diff --git a/packages/ra-ui-materialui/src/input/AutocompleteArrayInputChip.js b/packages/ra-ui-materialui/src/input/AutocompleteArrayInputChip.js index 178f0a47b09..75d15fdd439 100644 --- a/packages/ra-ui-materialui/src/input/AutocompleteArrayInputChip.js +++ b/packages/ra-ui-materialui/src/input/AutocompleteArrayInputChip.js @@ -1,8 +1,8 @@ -import React from 'react'; +import { makeStyles } from '@material-ui/core/styles'; import ChipInput from 'material-ui-chip-input'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import React from 'react'; -const chipInputStyles = createStyles({ +const useStyles = makeStyles({ label: { top: 18, }, @@ -21,6 +21,10 @@ const chipInputStyles = createStyles({ }, }); -const AutocompleteArrayInputChip = props => ; +const AutocompleteArrayInputChip = props => { + const classes = useStyles(); + + return ; +}; -export default withStyles(chipInputStyles)(AutocompleteArrayInputChip); +export default AutocompleteArrayInputChip; diff --git a/packages/ra-ui-materialui/src/input/CheckboxGroupInput.js b/packages/ra-ui-materialui/src/input/CheckboxGroupInput.js index 87867b7cb69..63253996e9b 100644 --- a/packages/ra-ui-materialui/src/input/CheckboxGroupInput.js +++ b/packages/ra-ui-materialui/src/input/CheckboxGroupInput.js @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import get from 'lodash/get'; import FormLabel from '@material-ui/core/FormLabel'; @@ -7,7 +7,7 @@ import FormGroup from '@material-ui/core/FormGroup'; import FormControlLabel from '@material-ui/core/FormControlLabel'; import FormHelperText from '@material-ui/core/FormHelperText'; import Checkbox from '@material-ui/core/Checkbox'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import compose from 'recompose/compose'; import { addField, translate, FieldTitle } from 'ra-core'; @@ -15,19 +15,16 @@ import defaultSanitizeRestProps from './sanitizeRestProps'; const sanitizeRestProps = ({ setFilter, setPagination, setSort, ...rest }) => defaultSanitizeRestProps(rest); -const styles = theme => - createStyles({ - root: {}, - label: { - transform: 'translate(0, 1.5px) scale(0.75)', - transformOrigin: `top ${ - theme.direction === 'ltr' ? 'left' : 'right' - }`, - }, - checkbox: { - height: 32, - }, - }); +const useStyles = makeStyles(theme => ({ + root: {}, + label: { + transform: 'translate(0, 1.5px) scale(0.75)', + transformOrigin: `top ${theme.direction === 'ltr' ? 'left' : 'right'}`, + }, + checkbox: { + height: 32, + }, +})); /** * An Input component for a checkbox group, using an array of objects for the options @@ -91,11 +88,35 @@ const styles = theme => * * The object passed as `options` props is passed to the material-ui components */ -export class CheckboxGroupInput extends Component { - handleCheck = (event, isChecked) => { - const { - input: { value, onChange }, - } = this.props; + +const CheckboxGroupInput = ({ + choices, + className, + isRequired, + label, + meta, + resource, + source, + input, + id, + input: { value, onChange }, + optionText, + optionValue, + options, + translate, + translateChoice, + ...rest +}) => { + if (typeof meta === 'undefined') { + throw new Error( + "The CheckboxGroupInput component wasn't called within a redux-form . Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/react-admin/Inputs.html#writing-your-own-input-component for details." + ); + } + + const classes = useStyles(); + const { touched, error, helperText = false } = meta; + + const handleCheck = (event, isChecked) => { let newValue; try { // try to convert string value to number, e.g. '123' @@ -111,18 +132,7 @@ export class CheckboxGroupInput extends Component { } }; - renderCheckbox = choice => { - const { - id, - input: { value }, - optionText, - optionValue, - options, - translate, - translateChoice, - classes, - } = this.props; - + const renderCheckbox = choice => { const choiceName = React.isValidElement(optionText) ? React.cloneElement(optionText, { record: choice }) : typeof optionText === 'function' @@ -139,7 +149,7 @@ export class CheckboxGroupInput extends Component { undefined : false } - onChange={this.handleCheck} + onChange={handleCheck} value={String(get(choice, optionValue))} control={ . Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/react-admin/Inputs.html#writing-your-own-input-component for details." - ); - } - - const { touched, error, helperText = false } = meta; - - return ( - - - - - {choices.map(this.renderCheckbox)} - {touched && error && ( - {error} - )} - {helperText && {helperText}} - - ); - } -} + return ( + + + + + {choices.map(renderCheckbox)} + {touched && error && {error}} + {helperText && {helperText}} + + ); +}; CheckboxGroupInput.propTypes = { choices: PropTypes.arrayOf(PropTypes.object), - classes: PropTypes.object, className: PropTypes.string, label: PropTypes.string, source: PropTypes.string, @@ -230,7 +215,6 @@ CheckboxGroupInput.propTypes = { CheckboxGroupInput.defaultProps = { choices: [], - classes: {}, options: {}, optionText: 'name', optionValue: 'id', @@ -239,8 +223,7 @@ CheckboxGroupInput.defaultProps = { const EnhancedCheckboxGroupInput = compose( addField, - translate, - withStyles(styles) + translate )(CheckboxGroupInput); EnhancedCheckboxGroupInput.defaultProps = { diff --git a/packages/ra-ui-materialui/src/input/FileInputPreview.js b/packages/ra-ui-materialui/src/input/FileInputPreview.js index 3b9d1b247ff..44f694f2ddc 100644 --- a/packages/ra-ui-materialui/src/input/FileInputPreview.js +++ b/packages/ra-ui-materialui/src/input/FileInputPreview.js @@ -1,72 +1,65 @@ -import React, { Component } from 'react'; -import compose from 'recompose/compose'; +import React, { useEffect } from 'react'; import PropTypes from 'prop-types'; import IconButton from '@material-ui/core/IconButton'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import RemoveCircle from '@material-ui/icons/RemoveCircle'; import { translate } from 'ra-core'; -const styles = theme => - createStyles({ - removeButton: {}, - removeIcon: { - color: theme.palette.accent1Color, - }, - }); +const useStyles = makeStyles(theme => ({ + removeButton: {}, + removeIcon: { + color: theme.palette.accent1Color, + }, +})); -export class FileInputPreview extends Component { - static propTypes = { - children: PropTypes.element.isRequired, - classes: PropTypes.object, - className: PropTypes.string, - file: PropTypes.object, - onRemove: PropTypes.func.isRequired, - revokeObjectURL: PropTypes.func, - }; +const FileInputPreview = ({ + children, + className, + onRemove, + revokeObjectURL, + file, + translate, + ...rest +}) => { + useEffect(() => { + return () => { + const { file, revokeObjectURL } = this.props; - static defaultProps = { - file: undefined, - translate: id => id, - }; + if (file.preview) { + revokeObjectURL + ? revokeObjectURL(file.preview) + : window.URL.revokeObjectURL(file.preview); + } + }; + }, []); - componentWillUnmount() { - const { file, revokeObjectURL } = this.props; + const classes = useStyles(); - if (file.preview) { - revokeObjectURL - ? revokeObjectURL(file.preview) - : window.URL.revokeObjectURL(file.preview); - } - } + return ( +
    + + + + {children} +
    + ); +}; - render() { - const { - children, - classes = {}, - className, - onRemove, - revokeObjectURL, - file, - translate, - ...rest - } = this.props; +FileInputPreview.propTypes = { + children: PropTypes.element.isRequired, + className: PropTypes.string, + file: PropTypes.object, + onRemove: PropTypes.func.isRequired, + revokeObjectURL: PropTypes.func, +}; - return ( -
    - - - - {children} -
    - ); - } -} +FileInputPreview.defaultProps = { + file: undefined, + translate: id => id, +}; -export default compose( - withStyles(styles), - translate -)(FileInputPreview); +export default translate()(FileInputPreview); diff --git a/packages/ra-ui-materialui/src/input/Labeled.js b/packages/ra-ui-materialui/src/input/Labeled.js index 130d925fffd..ad9a14383b3 100644 --- a/packages/ra-ui-materialui/src/input/Labeled.js +++ b/packages/ra-ui-materialui/src/input/Labeled.js @@ -2,27 +2,26 @@ import React from 'react'; import PropTypes from 'prop-types'; import InputLabel from '@material-ui/core/InputLabel'; import FormControl from '@material-ui/core/FormControl'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import { FieldTitle } from 'ra-core'; -const styles = theme => - createStyles({ - label: { - position: 'relative', - }, - value: { - fontFamily: theme.typography.fontFamily, - color: 'currentColor', - padding: `${theme.spacing(1)}px 0 ${theme.spacing(1) / 2}px`, - border: 0, - boxSizing: 'content-box', - verticalAlign: 'middle', - background: 'none', - margin: 0, // Reset for Safari - display: 'block', - width: '100%', - }, - }); +const useStyles = makeStyles(theme => ({ + label: { + position: 'relative', + }, + value: { + fontFamily: theme.typography.fontFamily, + color: 'currentColor', + padding: `${theme.spacing(1)}px 0 ${theme.spacing(1) / 2}px`, + border: 0, + boxSizing: 'content-box', + verticalAlign: 'middle', + background: 'none', + margin: 0, // Reset for Safari + display: 'block', + width: '100%', + }, +})); /** * Use any component as read-only Input, labeled just like other Inputs. @@ -41,7 +40,6 @@ const styles = theme => */ export const Labeled = ({ children, - classes, className, fullWidth, id, @@ -53,6 +51,7 @@ export const Labeled = ({ source, ...rest }) => { + const classes = useStyles(); if (!label && !source) { throw new Error( `Cannot create label for component <${children && @@ -94,7 +93,6 @@ export const Labeled = ({ Labeled.propTypes = { basePath: PropTypes.string, children: PropTypes.element, - classes: PropTypes.object, className: PropTypes.string, fullWidth: PropTypes.bool, id: PropTypes.string, @@ -109,4 +107,4 @@ Labeled.propTypes = { labelStyle: PropTypes.object, }; -export default withStyles(styles)(Labeled); +export default Labeled; diff --git a/packages/ra-ui-materialui/src/input/RadioButtonGroupInput.js b/packages/ra-ui-materialui/src/input/RadioButtonGroupInput.js index 389318108e4..ff399734f87 100644 --- a/packages/ra-ui-materialui/src/input/RadioButtonGroupInput.js +++ b/packages/ra-ui-materialui/src/input/RadioButtonGroupInput.js @@ -7,14 +7,12 @@ import FormHelperText from '@material-ui/core/FormHelperText'; import InputLabel from '@material-ui/core/InputLabel'; import RadioGroup from '@material-ui/core/RadioGroup'; import Radio from '@material-ui/core/Radio'; -import { withStyles, createStyles } from '@material-ui/core/styles'; -import compose from 'recompose/compose'; +import { makeStyles } from '@material-ui/core/styles'; import { addField, FieldTitle, useTranslate } from 'ra-core'; - import sanitizeRestProps from './sanitizeRestProps'; import InputHelperText from './InputHelperText'; -const styles = createStyles({ +const useStyles = makeStyles({ label: { position: 'relative', }, @@ -78,7 +76,6 @@ const styles = createStyles({ * The object passed as `options` props is passed to the material-ui component */ export const RadioButtonGroupInput = ({ - classes, className, label, resource, @@ -94,6 +91,7 @@ export const RadioButtonGroupInput = ({ translateChoice, ...rest }) => { + const classes = useStyles(); const translate = useTranslate(); const handleChange = useCallback( @@ -177,7 +175,6 @@ export const RadioButtonGroupInput = ({ RadioButtonGroupInput.propTypes = { choices: PropTypes.arrayOf(PropTypes.object), - classes: PropTypes.object, className: PropTypes.string, input: PropTypes.object, isRequired: PropTypes.bool, @@ -197,7 +194,6 @@ RadioButtonGroupInput.propTypes = { }; RadioButtonGroupInput.defaultProps = { - classes: {}, choices: [], options: {}, optionText: 'name', @@ -205,7 +201,4 @@ RadioButtonGroupInput.defaultProps = { translateChoice: true, }; -export default compose( - addField, - withStyles(styles) -)(RadioButtonGroupInput); +export default addField()(RadioButtonGroupInput); diff --git a/packages/ra-ui-materialui/src/input/ReferenceArrayInput.spec.js b/packages/ra-ui-materialui/src/input/ReferenceArrayInput.spec.js index cd4e4473b28..7fa3b96a341 100644 --- a/packages/ra-ui-materialui/src/input/ReferenceArrayInput.spec.js +++ b/packages/ra-ui-materialui/src/input/ReferenceArrayInput.spec.js @@ -29,9 +29,7 @@ describe('', () => { ); const MyComponentElement = wrapper.find('MyComponent'); assert.equal(MyComponentElement.length, 0); - const LinearProgressElement = wrapper.find( - 'WithStyles(LinearProgress)' - ); + const LinearProgressElement = wrapper.find('LinearProgress'); assert.equal(LinearProgressElement.length, 1); }); @@ -130,9 +128,7 @@ describe('', () => { ); - const LinearProgressElement = wrapper.find( - 'WithStyles(LinearProgress)' - ); + const LinearProgressElement = wrapper.find('LinearProgress'); assert.equal(LinearProgressElement.length, 0); const ErrorElement = wrapper.find('ReferenceError'); assert.equal(ErrorElement.length, 0); diff --git a/packages/ra-ui-materialui/src/input/ReferenceInput.spec.js b/packages/ra-ui-materialui/src/input/ReferenceInput.spec.js index d79c59911e8..3a0e3623a72 100644 --- a/packages/ra-ui-materialui/src/input/ReferenceInput.spec.js +++ b/packages/ra-ui-materialui/src/input/ReferenceInput.spec.js @@ -29,9 +29,7 @@ describe('', () => { ); const MyComponentElement = wrapper.find('MyComponent'); assert.equal(MyComponentElement.length, 0); - const LinearProgressElement = wrapper.find( - 'WithStyles(LinearProgress)' - ); + const LinearProgressElement = wrapper.find('LinearProgress'); assert.equal(LinearProgressElement.length, 1); }); @@ -47,9 +45,7 @@ describe('', () => { ); - const LinearProgressElement = wrapper.find( - 'WithStyles(LinearProgress)' - ); + const LinearProgressElement = wrapper.find('LinearProgress'); assert.equal(LinearProgressElement.length, 0); const MyComponentElement = wrapper.find('MyComponent'); assert.equal(MyComponentElement.length, 1); diff --git a/packages/ra-ui-materialui/src/input/ResettableTextField.js b/packages/ra-ui-materialui/src/input/ResettableTextField.js index 78b9fe1f6e0..8f00a3c27a7 100644 --- a/packages/ra-ui-materialui/src/input/ResettableTextField.js +++ b/packages/ra-ui-materialui/src/input/ResettableTextField.js @@ -1,16 +1,15 @@ -import React, { Component } from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; -import compose from 'recompose/compose'; import classNames from 'classnames'; import InputAdornment from '@material-ui/core/InputAdornment'; import IconButton from '@material-ui/core/IconButton'; import MuiTextField from '@material-ui/core/TextField'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import ClearIcon from '@material-ui/icons/Clear'; import { translate } from 'ra-core'; -const styles = createStyles({ +const useStyles = makeStyles({ clearIcon: { height: 16, width: 0, @@ -30,103 +29,99 @@ const styles = createStyles({ /** * An override of the default Material-UI TextField which is resettable */ -class ResettableTextField extends Component { - static propTypes = { - classes: PropTypes.object.isRequired, - clearAlwaysVisible: PropTypes.bool, - disabled: PropTypes.bool, - InputProps: PropTypes.object, - onBlur: PropTypes.func, - onChange: PropTypes.func.isRequired, - onFocus: PropTypes.func, - resettable: PropTypes.bool, - translate: PropTypes.func.isRequired, - value: PropTypes.any.isRequired, - }; - state = { showClear: false }; +const ResettableTextField = ({ + translate, + clearAlwaysVisible, + InputProps, + value, + resettable, + disabled, + onBlur, + onChange, + onFocus, + ...props +}) => { + const classes = useStyles(); + const [showClear, setShowClear] = useState(false); + + const { + clearButton, + clearIcon, + visibleClearButton, + visibleClearIcon, + ...restClasses + } = classes; - handleClickClearButton = event => { + const handleClickClearButton = event => { event.preventDefault(); - this.props.onChange(''); + onChange(''); }; - handleMouseDownClearButton = event => { + const handleMouseDownClearButton = event => { event.preventDefault(); }; - handleFocus = event => { - this.setState({ showClear: true }); - this.props.onFocus && this.props.onFocus(event); + const handleFocus = event => { + setShowClear(true); + onFocus && onFocus(event); }; - handleBlur = event => { - this.setState({ showClear: false }); - this.props.onBlur && this.props.onBlur(event); + const handleBlur = event => { + setShowClear(false); + onBlur && onBlur(event); }; - render() { - const { - translate, - classes, - clearAlwaysVisible, - InputProps, - value, - resettable, - disabled, - ...props - } = this.props; - const { showClear } = this.state; - const { - clearButton, - clearIcon, - visibleClearButton, - visibleClearIcon, - ...restClasses - } = classes; - - return ( - - + + - - - - ), - ...InputProps, - }} - disabled={disabled} - {...props} - onFocus={this.handleFocus} - onBlur={this.handleBlur} - /> - ); - } -} + /> + + + ), + ...InputProps, + }} + disabled={disabled} + {...props} + onFocus={handleFocus} + onBlur={handleBlur} + /> + ); +}; -export default compose( - translate, - withStyles(styles) -)(ResettableTextField); +ResettableTextField.propTypes = { + clearAlwaysVisible: PropTypes.bool, + disabled: PropTypes.bool, + InputProps: PropTypes.object, + onBlur: PropTypes.func, + onChange: PropTypes.func.isRequired, + onFocus: PropTypes.func, + resettable: PropTypes.bool, + translate: PropTypes.func.isRequired, + value: PropTypes.any.isRequired, +}; + +export default translate()(ResettableTextField); diff --git a/packages/ra-ui-materialui/src/input/SearchInput.js b/packages/ra-ui-materialui/src/input/SearchInput.js index 0aaa2103687..0b935023a5c 100644 --- a/packages/ra-ui-materialui/src/input/SearchInput.js +++ b/packages/ra-ui-materialui/src/input/SearchInput.js @@ -1,20 +1,21 @@ import React from 'react'; -import PropTypes from 'prop-types'; import SearchIcon from '@material-ui/icons/Search'; import InputAdornment from '@material-ui/core/InputAdornment'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import { useTranslate } from 'ra-core'; import TextInput from './TextInput'; -const searchFilterStyles = createStyles({ +const useStyles = makeStyles({ input: { marginTop: 32, }, }); -const SearchInput = ({ classes, ...props }) => { +const SearchInput = props => { + const classes = useStyles(); const translate = useTranslate(); + return ( { ); }; -SearchInput.propTypes = { - classes: PropTypes.object, -}; - -export default withStyles(searchFilterStyles)(SearchInput); +export default SearchInput; diff --git a/packages/ra-ui-materialui/src/input/SelectInput.js b/packages/ra-ui-materialui/src/input/SelectInput.js index 75d23396986..a7864da7524 100644 --- a/packages/ra-ui-materialui/src/input/SelectInput.js +++ b/packages/ra-ui-materialui/src/input/SelectInput.js @@ -2,7 +2,7 @@ import React, { useState, useCallback, useEffect } from 'react'; import PropTypes from 'prop-types'; import get from 'lodash/get'; import MenuItem from '@material-ui/core/MenuItem'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import compose from 'recompose/compose'; import { addField, translate, FieldTitle, useTranslate } from 'ra-core'; @@ -50,12 +50,11 @@ const sanitizeRestProps = ({ ...rest }) => rest; -const styles = theme => - createStyles({ - input: { - minWidth: theme.spacing(20), - }, - }); +const useStyles = makeStyles(theme => ({ + input: { + minWidth: theme.spacing(20), + }, +})); /** * An Input component for a select box, using an array of objects for the options @@ -134,7 +133,6 @@ const styles = theme => export const SelectInput = ({ allowEmpty, choices, - classes, className, disableValue, emptyValue, @@ -156,6 +154,7 @@ export const SelectInput = ({ * @see https://github.com/erikras/redux-form/issues/2456 */ const [value, setValue] = useState(input.value); + const classes = useStyles(); const translate = useTranslate(); useEffect(() => { @@ -249,7 +248,6 @@ SelectInput.propTypes = { allowEmpty: PropTypes.bool.isRequired, emptyValue: PropTypes.any, choices: PropTypes.arrayOf(PropTypes.object), - classes: PropTypes.object, className: PropTypes.string, input: PropTypes.object, isRequired: PropTypes.bool, @@ -272,7 +270,6 @@ SelectInput.propTypes = { SelectInput.defaultProps = { allowEmpty: false, emptyValue: '', - classes: {}, choices: [], options: {}, optionText: 'name', @@ -283,6 +280,5 @@ SelectInput.defaultProps = { export default compose( addField, - translate, - withStyles(styles) + translate )(SelectInput); diff --git a/packages/ra-ui-materialui/src/layout/AppBar.js b/packages/ra-ui-materialui/src/layout/AppBar.js index 4a567497fc2..c05104d31c1 100644 --- a/packages/ra-ui-materialui/src/layout/AppBar.js +++ b/packages/ra-ui-materialui/src/layout/AppBar.js @@ -6,7 +6,7 @@ import MuiAppBar from '@material-ui/core/AppBar'; import Toolbar from '@material-ui/core/Toolbar'; import IconButton from '@material-ui/core/IconButton'; import Typography from '@material-ui/core/Typography'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import MenuIcon from '@material-ui/icons/Menu'; import withWidth from '@material-ui/core/withWidth'; import compose from 'recompose/compose'; @@ -16,40 +16,38 @@ import LoadingIndicator from './LoadingIndicator'; import DefaultUserMenu from './UserMenu'; import HideOnScroll from './HideOnScroll'; -const styles = theme => - createStyles({ - toolbar: { - paddingRight: 24, - }, - menuButton: { - marginLeft: '0.5em', - marginRight: '0.5em', - }, - menuButtonIconClosed: { - transition: theme.transitions.create(['transform'], { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.leavingScreen, - }), - transform: 'rotate(0deg)', - }, - menuButtonIconOpen: { - transition: theme.transitions.create(['transform'], { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.leavingScreen, - }), - transform: 'rotate(180deg)', - }, - title: { - flex: 1, - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - overflow: 'hidden', - }, - }); +const useStyles = makeStyles(theme => ({ + toolbar: { + paddingRight: 24, + }, + menuButton: { + marginLeft: '0.5em', + marginRight: '0.5em', + }, + menuButtonIconClosed: { + transition: theme.transitions.create(['transform'], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + transform: 'rotate(0deg)', + }, + menuButtonIconOpen: { + transition: theme.transitions.create(['transform'], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + transform: 'rotate(180deg)', + }, + title: { + flex: 1, + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + overflow: 'hidden', + }, +})); const AppBar = ({ children, - classes, className, logo, logout, @@ -59,48 +57,51 @@ const AppBar = ({ userMenu, width, ...rest -}) => ( - - - - { + const classes = useStyles(); + + return ( + + + - - - {Children.count(children) === 0 ? ( - - ) : ( - children - )} - - {cloneElement(userMenu, { logout })} - - - -); + aria-label="open drawer" + onClick={toggleSidebar} + className={classNames(classes.menuButton)} + > + + + {Children.count(children) === 0 ? ( + + ) : ( + children + )} + + {cloneElement(userMenu, { logout })} + + + + ); +}; AppBar.propTypes = { children: PropTypes.node, - classes: PropTypes.object, className: PropTypes.string, logout: PropTypes.element, open: PropTypes.bool, @@ -122,7 +123,6 @@ const enhance = compose( toggleSidebar: toggleSidebarAction, } ), - withStyles(styles), withWidth() ); diff --git a/packages/ra-ui-materialui/src/layout/CardContentInner.js b/packages/ra-ui-materialui/src/layout/CardContentInner.js index ba345c9881f..893aa7f3fab 100644 --- a/packages/ra-ui-materialui/src/layout/CardContentInner.js +++ b/packages/ra-ui-materialui/src/layout/CardContentInner.js @@ -2,24 +2,23 @@ import React from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import CardContent from '@material-ui/core/CardContent'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; -var styles = theme => - createStyles({ - root: { - paddingTop: 0, - paddingBottom: 0, - '&:first-child': { - paddingTop: 16, - }, - '&:last-child': { - paddingBottom: 16, - [theme.breakpoints.only('xs')]: { - paddingBottom: 70, - }, +var useStyles = makeStyles(theme => ({ + root: { + paddingTop: 0, + paddingBottom: 0, + '&:first-child': { + paddingTop: 16, + }, + '&:last-child': { + paddingBottom: 16, + [theme.breakpoints.only('xs')]: { + paddingBottom: 70, }, }, - }); + }, +})); /** * Overrides material-ui CardContent to allow inner content @@ -28,16 +27,19 @@ var styles = theme => * padding double the spacing between each CardContent, leading to too much * wasted space. Use this component as a CardContent alternative. */ -const CardContentInner = ({ classes, className, children }) => ( - - {children} - -); +const CardContentInner = ({ className, children }) => { + const classes = useStyles(); + + return ( + + {children} + + ); +}; CardContentInner.propTypes = { className: PropTypes.string, - classes: PropTypes.object.isRequired, children: PropTypes.node, }; -export default withStyles(styles)(CardContentInner); +export default CardContentInner; diff --git a/packages/ra-ui-materialui/src/layout/Confirm.js b/packages/ra-ui-materialui/src/layout/Confirm.js index b9bfbb7ec66..d28ce7d0b83 100644 --- a/packages/ra-ui-materialui/src/layout/Confirm.js +++ b/packages/ra-ui-materialui/src/layout/Confirm.js @@ -6,35 +6,34 @@ import DialogContent from '@material-ui/core/DialogContent'; import DialogContentText from '@material-ui/core/DialogContentText'; import DialogTitle from '@material-ui/core/DialogTitle'; import Button from '@material-ui/core/Button'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import { fade } from '@material-ui/core/styles/colorManipulator'; import ActionCheck from '@material-ui/icons/CheckCircle'; import AlertError from '@material-ui/icons/ErrorOutline'; import classnames from 'classnames'; import { useTranslate } from 'ra-core'; -const styles = theme => - createStyles({ - contentText: { - minWidth: 400, - }, - confirmPrimary: { - color: theme.palette.primary.main, - }, - confirmWarning: { - color: theme.palette.error.main, - '&:hover': { - backgroundColor: fade(theme.palette.error.main, 0.12), - // Reset on mouse devices - '@media (hover: none)': { - backgroundColor: 'transparent', - }, +const useStyles = makeStyles(theme => ({ + contentText: { + minWidth: 400, + }, + confirmPrimary: { + color: theme.palette.primary.main, + }, + confirmWarning: { + color: theme.palette.error.main, + '&:hover': { + backgroundColor: fade(theme.palette.error.main, 0.12), + // Reset on mouse devices + '@media (hover: none)': { + backgroundColor: 'transparent', }, }, - iconPaddingStyle: { - paddingRight: '0.5em', - }, - }); + }, + iconPaddingStyle: { + paddingRight: '0.5em', + }, +})); /** * Confirmation dialog @@ -60,10 +59,10 @@ const Confirm = ({ confirmColor, onClose, onConfirm, - classes, translateOptions = {}, }) => { const [loading, setLoading] = useState(false); + const classes = useStyles(); const translate = useTranslate(); const handleConfirm = useCallback( @@ -128,10 +127,9 @@ Confirm.propTypes = { Confirm.defaultProps = { cancel: 'ra.action.cancel', - classes: {}, confirm: 'ra.action.confirm', confirmColor: 'primary', isOpen: false, }; -export default withStyles(styles)(Confirm); +export default Confirm; diff --git a/packages/ra-ui-materialui/src/layout/Error.js b/packages/ra-ui-materialui/src/layout/Error.js index 44d65650ea4..3c3ba0ae638 100644 --- a/packages/ra-ui-materialui/src/layout/Error.js +++ b/packages/ra-ui-materialui/src/layout/Error.js @@ -5,7 +5,7 @@ import Button from '@material-ui/core/Button'; import ExpansionPanel from '@material-ui/core/ExpansionPanel'; import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails'; import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import ErrorIcon from '@material-ui/icons/Report'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import History from '@material-ui/icons/History'; @@ -13,45 +13,46 @@ import History from '@material-ui/icons/History'; import Title, { TitlePropType } from './Title'; import { useTranslate } from 'ra-core'; -const styles = theme => - createStyles({ - container: { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', - [theme.breakpoints.down('sm')]: { - padding: '1em', - }, - fontFamily: 'Roboto, sans-serif', - opacity: 0.5, +const useStyles = makeStyles(theme => ({ + container: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + [theme.breakpoints.down('sm')]: { + padding: '1em', }, - title: { - display: 'flex', - alignItems: 'center', - }, - icon: { - width: '2em', - height: '2em', - marginRight: '0.5em', - }, - panel: { - marginTop: '1em', - }, - panelDetails: { - whiteSpace: 'pre-wrap', - }, - toolbar: { - marginTop: '2em', - }, - }); + fontFamily: 'Roboto, sans-serif', + opacity: 0.5, + }, + title: { + display: 'flex', + alignItems: 'center', + }, + icon: { + width: '2em', + height: '2em', + marginRight: '0.5em', + }, + panel: { + marginTop: '1em', + }, + panelDetails: { + whiteSpace: 'pre-wrap', + }, + toolbar: { + marginTop: '2em', + }, +})); function goBack() { window.history.go(-1); } -const Error = ({ error, errorInfo, classes, className, title, ...rest }) => { +const Error = ({ error, errorInfo, className, title, ...rest }) => { + const classes = useStyles(); const translate = useTranslate(); + return ( @@ -89,11 +90,10 @@ const Error = ({ error, errorInfo, classes, className, title, ...rest }) => { }; Error.propTypes = { - classes: PropTypes.object, className: PropTypes.string, error: PropTypes.object.isRequired, errorInfo: PropTypes.object, title: TitlePropType, }; -export default withStyles(styles)(Error); +export default Error; diff --git a/packages/ra-ui-materialui/src/layout/LinearProgress.js b/packages/ra-ui-materialui/src/layout/LinearProgress.js index 6b9f999fe2d..f9f86314abe 100644 --- a/packages/ra-ui-materialui/src/layout/LinearProgress.js +++ b/packages/ra-ui-materialui/src/layout/LinearProgress.js @@ -1,16 +1,15 @@ import React from 'react'; import Progress from '@material-ui/core/LinearProgress'; import PropTypes from 'prop-types'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import classnames from 'classnames'; -const styles = theme => - createStyles({ - root: { - margin: `${theme.spacing(1)}px 0`, - width: `${theme.spacing(20)}px`, - }, - }); +const useStyles = makeStyles(theme => ({ + root: { + margin: `${theme.spacing(1)}px 0`, + width: `${theme.spacing(20)}px`, + }, +})); /** * Progress bar formatted to replace an input or a field in a form layout @@ -22,14 +21,19 @@ const styles = theme => * * @param {object} classes CSS class names injected by withStyles */ -export const LinearProgress = ({ classes, className, ...rest }) => ( - <Progress className={classnames(classes.root, className)} {...rest} /> -); +export const LinearProgress = ({ className, ...rest }) => { + const classes = useStyles(); + + return ( + <Progress className={classnames(classes.root, className)} {...rest} /> + ); +}; + LinearProgress.propTypes = { - classes: PropTypes.object, className: PropTypes.string, }; + // wat? TypeScript looses the displayName if we don't set it explicitly LinearProgress.displayName = 'LinearProgress'; -export default withStyles(styles)(LinearProgress); +export default LinearProgress; diff --git a/packages/ra-ui-materialui/src/layout/Loading.js b/packages/ra-ui-materialui/src/layout/Loading.js index d2ccb2209d8..4c79f1a6399 100644 --- a/packages/ra-ui-materialui/src/layout/Loading.js +++ b/packages/ra-ui-materialui/src/layout/Loading.js @@ -1,43 +1,43 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import classnames from 'classnames'; import CircularProgress from '@material-ui/core/CircularProgress'; import { useTranslate } from 'ra-core'; -const styles = theme => - createStyles({ - container: { - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - [theme.breakpoints.up('md')]: { - height: '100%', - }, - [theme.breakpoints.down('sm')]: { - height: '100vh', - marginTop: '-3em', - }, +const useStyles = makeStyles(theme => ({ + container: { + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + [theme.breakpoints.up('md')]: { + height: '100%', }, - icon: { - width: '9em', - height: '9em', + [theme.breakpoints.down('sm')]: { + height: '100vh', + marginTop: '-3em', }, - message: { - textAlign: 'center', - fontFamily: 'Roboto, sans-serif', - opacity: 0.5, - margin: '0 1em', - }, - }); + }, + icon: { + width: '9em', + height: '9em', + }, + message: { + textAlign: 'center', + fontFamily: 'Roboto, sans-serif', + opacity: 0.5, + margin: '0 1em', + }, +})); const Loading = ({ - classes, className, loadingPrimary = 'ra.page.loading', loadingSecondary = 'ra.message.loading', }) => { + const classes = useStyles(); const translate = useTranslate(); + return ( <div className={classnames(classes.container, className)}> <div className={classes.message}> @@ -50,7 +50,6 @@ const Loading = ({ }; Loading.propTypes = { - classes: PropTypes.object, className: PropTypes.string, loadingPrimary: PropTypes.string, loadingSecondary: PropTypes.string, @@ -61,4 +60,4 @@ Loading.defaultProps = { loadingSecondary: 'ra.message.loading', }; -export default withStyles(styles)(Loading); +export default Loading; diff --git a/packages/ra-ui-materialui/src/layout/LoadingIndicator.js b/packages/ra-ui-materialui/src/layout/LoadingIndicator.js index a2628c6db45..8fb1b9af981 100644 --- a/packages/ra-ui-materialui/src/layout/LoadingIndicator.js +++ b/packages/ra-ui-materialui/src/layout/LoadingIndicator.js @@ -2,20 +2,21 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { connect } from 'react-redux'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import CircularProgress from '@material-ui/core/CircularProgress'; -import compose from 'recompose/compose'; import RefreshIconButton from '../button/RefreshIconButton'; -const styles = createStyles({ +const useStyles = makeStyles({ loader: { margin: 14, }, }); -export const LoadingIndicator = ({ classes, className, isLoading, ...rest }) => - isLoading ? ( +export const LoadingIndicator = ({ className, isLoading, ...rest }) => { + const classes = useStyles(); + + return isLoading ? ( <CircularProgress className={classNames('app-loader', classes.loader, className)} color="inherit" @@ -26,9 +27,9 @@ export const LoadingIndicator = ({ classes, className, isLoading, ...rest }) => ) : ( <RefreshIconButton /> ); +}; LoadingIndicator.propTypes = { - classes: PropTypes.object, className: PropTypes.string, isLoading: PropTypes.bool, width: PropTypes.string, @@ -38,10 +39,7 @@ const mapStateToProps = state => ({ isLoading: state.admin.loading > 0, }); -export default compose( - connect( - mapStateToProps, - {} // Avoid connect passing dispatch in props - ), - withStyles(styles) +export default connect( + mapStateToProps, + {} // Avoid connect passing dispatch in props )(LoadingIndicator); diff --git a/packages/ra-ui-materialui/src/layout/Menu.js b/packages/ra-ui-materialui/src/layout/Menu.js index eb35fadcc78..4eb3101c553 100644 --- a/packages/ra-ui-materialui/src/layout/Menu.js +++ b/packages/ra-ui-materialui/src/layout/Menu.js @@ -2,8 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import inflection from 'inflection'; -import compose from 'recompose/compose'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import classnames from 'classnames'; import { getResources, useTranslate } from 'ra-core'; import DefaultIcon from '@material-ui/icons/ViewList'; @@ -12,7 +11,7 @@ import DashboardMenuItem from './DashboardMenuItem'; import MenuItemLink from './MenuItemLink'; import Responsive from '../layout/Responsive'; -const styles = createStyles({ +const useStyles = makeStyles({ main: { display: 'flex', flexDirection: 'column', @@ -33,7 +32,6 @@ const translatedResourceName = (resource, translate) => }); const Menu = ({ - classes, className, dense, hasDashboard, @@ -44,7 +42,9 @@ const Menu = ({ logout, ...rest }) => { + const classes = useStyles(); const translate = useTranslate(); + return ( <div className={classnames(classes.main, className)} {...rest}> {hasDashboard && ( @@ -74,7 +74,6 @@ const Menu = ({ }; Menu.propTypes = { - classes: PropTypes.object, className: PropTypes.string, dense: PropTypes.bool, hasDashboard: PropTypes.bool, @@ -95,21 +94,16 @@ const mapStateToProps = state => ({ pathname: state.router.location.pathname, // used to force redraw on navigation }); -const enhance = compose( - connect( - mapStateToProps, - {}, // Avoid connect passing dispatch in props, - null, - { - areStatePropsEqual: (prev, next) => - prev.resources.every( - (value, index) => value === next.resources[index] // shallow compare resources - ) && - prev.pathname === next.pathname && - prev.open === next.open, - } - ), - withStyles(styles) -); - -export default enhance(Menu); +export default connect( + mapStateToProps, + {}, // Avoid connect passing dispatch in props, + null, + { + areStatePropsEqual: (prev, next) => + prev.resources.every( + (value, index) => value === next.resources[index] // shallow compare resources + ) && + prev.pathname === next.pathname && + prev.open === next.open, + } +)(Menu); diff --git a/packages/ra-ui-materialui/src/layout/MenuItemLink.js b/packages/ra-ui-materialui/src/layout/MenuItemLink.js index f3f94c53c5a..c4d66ce37a3 100644 --- a/packages/ra-ui-materialui/src/layout/MenuItemLink.js +++ b/packages/ra-ui-materialui/src/layout/MenuItemLink.js @@ -1,61 +1,49 @@ -import React, { Component, cloneElement } from 'react'; +import React, { cloneElement } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import { NavLink } from 'react-router-dom'; import MenuItem from '@material-ui/core/MenuItem'; import ListItemIcon from '@material-ui/core/ListItemIcon'; import Tooltip from '@material-ui/core/Tooltip'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; const NavLinkRef = React.forwardRef((props, ref) => ( <NavLink innerRef={ref} {...props} /> )); -const styles = theme => - createStyles({ - root: { - color: theme.palette.text.secondary, - }, - active: { - color: theme.palette.text.primary, - }, - icon: { minWidth: theme.spacing(5) }, - }); +const useStyles = makeStyles(theme => ({ + root: { + color: theme.palette.text.secondary, + }, + active: { + color: theme.palette.text.primary, + }, + icon: { minWidth: theme.spacing(5) }, +})); -export class MenuItemLink extends Component { - static propTypes = { - classes: PropTypes.object.isRequired, - className: PropTypes.string, - leftIcon: PropTypes.element, - onClick: PropTypes.func, - primaryText: PropTypes.node, - staticContext: PropTypes.object, - to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]) - .isRequired, - }; +const MenuItemLink = ({ + className, + primaryText, + leftIcon, + staticContext, + sidebarIsOpen, + onClick, + ...props +}) => { + const classes = useStyles(); - handleMenuTap = e => { - this.props.onClick && this.props.onClick(e); + const handleMenuTap = e => { + onClick && onClick(e); }; - renderMenuItem() { - const { - classes, - className, - primaryText, - leftIcon, - staticContext, - sidebarIsOpen, - ...props - } = this.props; - + const renderMenuItem = () => { return ( <MenuItem className={classnames(classes.root, className)} activeClassName={classes.active} component={NavLinkRef} {...props} - onClick={this.handleMenuTap} + onClick={handleMenuTap} > {leftIcon && ( <ListItemIcon className={classes.icon}> @@ -65,21 +53,24 @@ export class MenuItemLink extends Component { {primaryText} </MenuItem> ); + }; + if (sidebarIsOpen) { + return renderMenuItem(); } + return ( + <Tooltip title={primaryText} placement="right"> + {renderMenuItem()} + </Tooltip> + ); +}; - render() { - const { sidebarIsOpen, primaryText } = this.props; - - if (sidebarIsOpen) { - return this.renderMenuItem(); - } - - return ( - <Tooltip title={primaryText} placement="right"> - {this.renderMenuItem()} - </Tooltip> - ); - } -} +MenuItemLink.propTypes = { + className: PropTypes.string, + leftIcon: PropTypes.element, + onClick: PropTypes.func, + primaryText: PropTypes.node, + staticContext: PropTypes.object, + to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired, +}; -export default withStyles(styles)(MenuItemLink); +export default MenuItemLink; diff --git a/packages/ra-ui-materialui/src/layout/NotFound.js b/packages/ra-ui-materialui/src/layout/NotFound.js index 0377de21ff9..7e80406238e 100644 --- a/packages/ra-ui-materialui/src/layout/NotFound.js +++ b/packages/ra-ui-materialui/src/layout/NotFound.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import Button from '@material-ui/core/Button'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import HotTub from '@material-ui/icons/HotTub'; import History from '@material-ui/icons/History'; import classnames from 'classnames'; @@ -9,42 +9,43 @@ import classnames from 'classnames'; import { useTranslate, Authenticated } from 'ra-core'; import Title from './Title'; -const styles = theme => - createStyles({ - container: { - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - [theme.breakpoints.up('md')]: { - height: '100%', - }, - [theme.breakpoints.down('sm')]: { - height: '100vh', - marginTop: '-3em', - }, +const useStyles = makeStyles(theme => ({ + container: { + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + [theme.breakpoints.up('md')]: { + height: '100%', }, - icon: { - width: '9em', - height: '9em', + [theme.breakpoints.down('sm')]: { + height: '100vh', + marginTop: '-3em', }, - message: { - textAlign: 'center', - fontFamily: 'Roboto, sans-serif', - opacity: 0.5, - margin: '0 1em', - }, - toolbar: { - textAlign: 'center', - marginTop: '2em', - }, - }); + }, + icon: { + width: '9em', + height: '9em', + }, + message: { + textAlign: 'center', + fontFamily: 'Roboto, sans-serif', + opacity: 0.5, + margin: '0 1em', + }, + toolbar: { + textAlign: 'center', + marginTop: '2em', + }, +})); function goBack() { window.history.go(-1); } -const NotFound = ({ classes, className, title, location, ...rest }) => { +const NotFound = ({ className, title, location, ...rest }) => { + const classes = useStyles(); const translate = useTranslate(); + return ( <Authenticated location={location}> <div className={classnames(classes.container, className)} {...rest}> @@ -69,10 +70,9 @@ const NotFound = ({ classes, className, title, location, ...rest }) => { }; NotFound.propTypes = { - classes: PropTypes.object, className: PropTypes.string, title: PropTypes.string, location: PropTypes.object, }; -export default withStyles(styles)(NotFound); +export default NotFound; diff --git a/packages/ra-ui-materialui/src/layout/Sidebar.js b/packages/ra-ui-materialui/src/layout/Sidebar.js index ce44d7f845c..c903db7ed01 100644 --- a/packages/ra-ui-materialui/src/layout/Sidebar.js +++ b/packages/ra-ui-materialui/src/layout/Sidebar.js @@ -1,9 +1,9 @@ -import React, { PureComponent, Children, cloneElement } from 'react'; +import React, { Children, cloneElement, useLayoutEffect } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import compose from 'recompose/compose'; import Drawer from '@material-ui/core/Drawer'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import withWidth from '@material-ui/core/withWidth'; import { setSidebarVisibility } from 'ra-core'; import lodashGet from 'lodash/get'; @@ -12,118 +12,99 @@ import Responsive from './Responsive'; export const DRAWER_WIDTH = 240; export const CLOSED_DRAWER_WIDTH = 55; -const styles = theme => - createStyles({ - drawerPaper: { - position: 'relative', - height: 'auto', - overflowX: 'hidden', - width: props => - props.open - ? lodashGet(theme, 'sidebar.width', DRAWER_WIDTH) - : lodashGet( - theme, - 'sidebar.closedWidth', - CLOSED_DRAWER_WIDTH - ), - transition: theme.transitions.create('width', { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.leavingScreen, - }), - backgroundColor: 'transparent', - marginTop: '0.5em', - borderRight: 'none', - [theme.breakpoints.only('xs')]: { - marginTop: 0, - height: '100vh', - position: 'inherit', - backgroundColor: theme.palette.background.default, - }, - [theme.breakpoints.up('md')]: { - border: 'none', - marginTop: '1.5em', - }, +const useStyles = makeStyles(theme => ({ + drawerPaper: { + position: 'relative', + height: 'auto', + overflowX: 'hidden', + width: props => + props.open + ? lodashGet(theme, 'sidebar.width', DRAWER_WIDTH) + : lodashGet(theme, 'sidebar.closedWidth', CLOSED_DRAWER_WIDTH), + transition: theme.transitions.create('width', { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + backgroundColor: 'transparent', + marginTop: '0.5em', + borderRight: 'none', + [theme.breakpoints.only('xs')]: { + marginTop: 0, + height: '100vh', + position: 'inherit', + backgroundColor: theme.palette.background.default, }, - }); + [theme.breakpoints.up('md')]: { + border: 'none', + marginTop: '1.5em', + }, + }, +})); + +const Sidebar = ({ width, setSidebarVisibility, open, children, ...rest }) => { + const classes = useStyles(); -// We shouldn't need PureComponent here as it's connected -// but for some reason it keeps rendering even though mapStateToProps returns the same object -class Sidebar extends PureComponent { - componentWillMount() { - const { width, setSidebarVisibility } = this.props; + useLayoutEffect(() => { if (width !== 'xs' && width !== 'sm') { setSidebarVisibility(true); } - } - - handleClose = () => this.props.setSidebarVisibility(false); + }, [setSidebarVisibility, width]); - toggleSidebar = () => this.props.setSidebarVisibility(!this.props.open); + const handleClose = () => setSidebarVisibility(false); + const toggleSidebar = () => setSidebarVisibility(!open); - render() { - const { - children, - classes, - open, - setSidebarVisibility, - width, - ...rest - } = this.props; - - return ( - <Responsive - xsmall={ - <Drawer - variant="temporary" - open={open} - PaperProps={{ - className: classes.drawerPaper, - }} - onClose={this.toggleSidebar} - {...rest} - > - {cloneElement(Children.only(children), { - onMenuClick: this.handleClose, - })} - </Drawer> - } - small={ - <Drawer - variant="permanent" - open={open} - PaperProps={{ - className: classes.drawerPaper, - }} - onClose={this.toggleSidebar} - {...rest} - > - {cloneElement(Children.only(children), { - dense: true, - onMenuClick: this.handleClose, - })} - </Drawer> - } - medium={ - <Drawer - variant="permanent" - open={open} - PaperProps={{ - className: classes.drawerPaper, - }} - onClose={this.toggleSidebar} - {...rest} - > - {cloneElement(Children.only(children), { dense: true })} - </Drawer> - } - /> - ); - } -} + return ( + <Responsive + xsmall={ + <Drawer + variant="temporary" + open={open} + PaperProps={{ + className: classes.drawerPaper, + }} + onClose={toggleSidebar} + {...rest} + > + {cloneElement(Children.only(children), { + onMenuClick: handleClose, + })} + </Drawer> + } + small={ + <Drawer + variant="permanent" + open={open} + PaperProps={{ + className: classes.drawerPaper, + }} + onClose={toggleSidebar} + {...rest} + > + {cloneElement(Children.only(children), { + dense: true, + onMenuClick: handleClose, + })} + </Drawer> + } + medium={ + <Drawer + variant="permanent" + open={open} + PaperProps={{ + className: classes.drawerPaper, + }} + onClose={toggleSidebar} + {...rest} + > + {cloneElement(Children.only(children), { dense: true })} + </Drawer> + } + /> + ); +}; Sidebar.propTypes = { children: PropTypes.node.isRequired, - classes: PropTypes.object, open: PropTypes.bool.isRequired, setSidebarVisibility: PropTypes.func.isRequired, width: PropTypes.string, @@ -139,6 +120,5 @@ export default compose( mapStateToProps, { setSidebarVisibility } ), - withStyles(styles), withWidth({ resizeInterval: Infinity }) // used to initialize the visibility on first render )(Sidebar); diff --git a/packages/ra-ui-materialui/src/list/BulkActionsToolbar.js b/packages/ra-ui-materialui/src/list/BulkActionsToolbar.js index 03a66b26024..bae8c781e20 100644 --- a/packages/ra-ui-materialui/src/list/BulkActionsToolbar.js +++ b/packages/ra-ui-materialui/src/list/BulkActionsToolbar.js @@ -3,43 +3,41 @@ import PropTypes from 'prop-types'; import classnames from 'classnames'; import Toolbar from '@material-ui/core/Toolbar'; import Typography from '@material-ui/core/Typography'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import { lighten } from '@material-ui/core/styles/colorManipulator'; import { useTranslate, sanitizeListRestProps } from 'ra-core'; import TopToolbar from '../layout/TopToolbar'; -const styles = theme => - createStyles({ - toolbar: { - zIndex: 3, - color: - theme.palette.type === 'light' - ? theme.palette.primary.main - : theme.palette.text.primary, - justifyContent: 'space-between', - backgroundColor: - theme.palette.type === 'light' - ? lighten(theme.palette.primary.light, 0.85) - : theme.palette.primary.dark, - minHeight: theme.spacing(8), - height: theme.spacing(8), - transition: `${theme.transitions.create( - 'height' - )}, ${theme.transitions.create('min-height')}`, - }, - collapsed: { - minHeight: 0, - height: 0, - overflowY: 'hidden', - }, - title: { - flex: '0 0 auto', - }, - }); +const useStyles = makeStyles(theme => ({ + toolbar: { + zIndex: 3, + color: + theme.palette.type === 'light' + ? theme.palette.primary.main + : theme.palette.text.primary, + justifyContent: 'space-between', + backgroundColor: + theme.palette.type === 'light' + ? lighten(theme.palette.primary.light, 0.85) + : theme.palette.primary.dark, + minHeight: theme.spacing(8), + height: theme.spacing(8), + transition: `${theme.transitions.create( + 'height' + )}, ${theme.transitions.create('min-height')}`, + }, + collapsed: { + minHeight: 0, + height: 0, + overflowY: 'hidden', + }, + title: { + flex: '0 0 auto', + }, +})); const BulkActionsToolbar = ({ - classes, basePath, filterValues, label, @@ -48,6 +46,7 @@ const BulkActionsToolbar = ({ children, ...rest }) => { + const classes = useStyles(); const translate = useTranslate(); return ( @@ -82,7 +81,6 @@ const BulkActionsToolbar = ({ BulkActionsToolbar.propTypes = { children: PropTypes.node, - classes: PropTypes.object, basePath: PropTypes.string, filterValues: PropTypes.object, label: PropTypes.string, @@ -94,4 +92,4 @@ BulkActionsToolbar.defaultProps = { label: 'ra.action.bulk_actions', }; -export default withStyles(styles)(BulkActionsToolbar); +export default BulkActionsToolbar; diff --git a/packages/ra-ui-materialui/src/list/Datagrid.js b/packages/ra-ui-materialui/src/list/Datagrid.js index 9e92efa306b..b37382614a2 100644 --- a/packages/ra-ui-materialui/src/list/Datagrid.js +++ b/packages/ra-ui-materialui/src/list/Datagrid.js @@ -1,12 +1,7 @@ -import React, { - Component, - isValidElement, - Children, - cloneElement, -} from 'react'; +import React, { isValidElement, Children, cloneElement } from 'react'; import PropTypes from 'prop-types'; import { sanitizeListRestProps } from 'ra-core'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import Table from '@material-ui/core/Table'; import TableCell from '@material-ui/core/TableCell'; import TableHead from '@material-ui/core/TableHead'; @@ -18,52 +13,51 @@ import DatagridHeaderCell from './DatagridHeaderCell'; import DatagridBody from './DatagridBody'; import DatagridLoading from './DatagridLoading'; -const styles = theme => - createStyles({ - table: { - tableLayout: 'auto', - }, - thead: {}, - tbody: { - height: 'inherit', - }, - headerRow: {}, - headerCell: { +const useStyles = makeStyles(theme => ({ + table: { + tableLayout: 'auto', + }, + thead: {}, + tbody: { + height: 'inherit', + }, + headerRow: {}, + headerCell: { + padding: '0 12px', + '&:last-child': { padding: '0 12px', - '&:last-child': { - padding: '0 12px', - }, - }, - checkbox: {}, - row: {}, - clickableRow: { - cursor: 'pointer', }, - rowEven: {}, - rowOdd: {}, - rowCell: { + }, + checkbox: {}, + row: {}, + clickableRow: { + cursor: 'pointer', + }, + rowEven: {}, + rowOdd: {}, + rowCell: { + padding: '0 12px', + '&:last-child': { padding: '0 12px', - '&:last-child': { - padding: '0 12px', - }, - }, - expandHeader: { - padding: 0, - width: 48, - }, - expandIconCell: { - width: 48, }, - expandIcon: { - transform: 'rotate(-90deg)', - transition: theme.transitions.create('transform', { - duration: theme.transitions.duration.shortest, - }), - }, - expanded: { - transform: 'rotate(0deg)', - }, - }); + }, + expandHeader: { + padding: 0, + width: 48, + }, + expandIconCell: { + width: 48, + }, + expandIcon: { + transform: 'rotate(-90deg)', + transition: theme.transitions.create('transform', { + duration: theme.transitions.duration.shortest, + }), + }, + expanded: { + transform: 'rotate(0deg)', + }, +})); /** * The Datagrid component renders a list of records as a table. @@ -97,14 +91,39 @@ const styles = theme => * </Datagrid> * </ReferenceManyField> */ -class Datagrid extends Component { - updateSort = event => { + +const Datagrid = ({ + basePath, + body, + children, + className, + currentSort, + data, + expand, + hasBulkActions, + hover, + ids, + isLoading, + loaded, + onSelect, + onToggleItem, + resource, + rowClick, + rowStyle, + selectedIds, + setSort, + total, + version, + ...rest +}) => { + const classes = useStyles(); + + const updateSort = event => { event.stopPropagation(); - this.props.setSort(event.currentTarget.dataset.sort); + setSort(event.currentTarget.dataset.sort); }; - handleSelectAll = event => { - const { onSelect, ids, selectedIds } = this.props; + const handleSelectAll = event => { if (event.target.checked) { onSelect( ids.reduce( @@ -119,142 +138,110 @@ class Datagrid extends Component { } }; - render() { - const { - basePath, - body, - children, - classes, - className, - currentSort, - data, - expand, - hasBulkActions, - hover, - ids, - isLoading, - loaded, - onSelect, - onToggleItem, - resource, - rowClick, - rowStyle, - selectedIds, - setSort, - total, - version, - ...rest - } = this.props; - - /** - * if loaded is false, the list displays for the first time, and the dataProvider hasn't answered yet - * if loaded is true, the data for the list has at least been returned once by the dataProvider - * if loaded is undefined, the Datagrid parent doesn't track loading state (e.g. ReferenceArrayField) - */ - if (loaded === false) { - return ( - <DatagridLoading - classes={classes} - className={className} - expand={expand} - hasBulkActions={hasBulkActions} - nbChildren={React.Children.count(children)} - /> - ); - } - - /** - * Once loaded, the data for the list may be empty. Instead of - * displaying the table header with zero data rows, - * the datagrid displays nothing in this case. - */ - if (!isLoading && (ids.length === 0 || total === 0)) { - return null; - } - - /** - * After the initial load, if the data for the list isn't empty, - * and even if the data is refreshing (e.g. after a filter change), - * the datagrid displays the current data. - */ + /** + * if loaded is false, the list displays for the first time, and the dataProvider hasn't answered yet + * if loaded is true, the data for the list has at least been returned once by the dataProvider + * if loaded is undefined, the Datagrid parent doesn't track loading state (e.g. ReferenceArrayField) + */ + if (loaded === false) { return ( - <Table - className={classnames(classes.table, className)} - {...sanitizeListRestProps(rest)} - > - <TableHead className={classes.thead}> - <TableRow - className={classnames(classes.row, classes.headerRow)} - > - {expand && ( - <TableCell className={classes.expandHeader} /> - )} - {hasBulkActions && ( - <TableCell padding="none"> - <Checkbox - className="select-all" - color="primary" - checked={ - selectedIds.length > 0 && - ids.length > 0 && - !ids.find( - it => selectedIds.indexOf(it) === -1 - ) - } - onChange={this.handleSelectAll} - /> - </TableCell> - )} - {Children.map(children, (field, index) => - isValidElement(field) ? ( - <DatagridHeaderCell - className={classes.headerCell} - currentSort={currentSort} - field={field} - isSorting={ - currentSort.field === - (field.props.sortBy || - field.props.source) - } - key={field.props.source || index} - resource={resource} - updateSort={this.updateSort} - /> - ) : null - )} - </TableRow> - </TableHead> - {cloneElement( - body, - { - basePath, - className: classes.tbody, - classes, - expand, - rowClick, - data, - hasBulkActions, - hover, - ids, - isLoading, - onToggleItem, - resource, - rowStyle, - selectedIds, - version, - }, - children - )} - </Table> + <DatagridLoading + classes={classes} + className={className} + expand={expand} + hasBulkActions={hasBulkActions} + nbChildren={React.Children.count(children)} + /> ); } -} + + /** + * Once loaded, the data for the list may be empty. Instead of + * displaying the table header with zero data rows, + * the datagrid displays nothing in this case. + */ + if (!isLoading && (ids.length === 0 || total === 0)) { + return null; + } + + /** + * After the initial load, if the data for the list isn't empty, + * and even if the data is refreshing (e.g. after a filter change), + * the datagrid displays the current data. + */ + return ( + <Table + className={classnames(classes.table, className)} + {...sanitizeListRestProps(rest)} + > + <TableHead className={classes.thead}> + <TableRow + className={classnames(classes.row, classes.headerRow)} + > + {expand && <TableCell className={classes.expandHeader} />} + {hasBulkActions && ( + <TableCell padding="none"> + <Checkbox + className="select-all" + color="primary" + checked={ + selectedIds.length > 0 && + ids.length > 0 && + !ids.find( + it => selectedIds.indexOf(it) === -1 + ) + } + onChange={handleSelectAll} + /> + </TableCell> + )} + {Children.map(children, (field, index) => + isValidElement(field) ? ( + <DatagridHeaderCell + className={classes.headerCell} + currentSort={currentSort} + field={field} + isSorting={ + currentSort.field === + (field.props.sortBy || field.props.source) + } + key={field.props.source || index} + resource={resource} + updateSort={updateSort} + /> + ) : null + )} + </TableRow> + </TableHead> + {cloneElement( + body, + { + basePath, + className: classes.tbody, + classes, + expand, + rowClick, + data, + hasBulkActions, + hover, + ids, + isLoading, + onToggleItem, + resource, + rowStyle, + selectedIds, + version, + }, + children + )} + </Table> + ); +}; Datagrid.propTypes = { basePath: PropTypes.string, body: PropTypes.element, children: PropTypes.node.isRequired, - classes: PropTypes.object, className: PropTypes.string, currentSort: PropTypes.shape({ field: PropTypes.string, @@ -285,4 +272,4 @@ Datagrid.defaultProps = { body: <DatagridBody />, }; -export default withStyles(styles)(Datagrid); +export default Datagrid; diff --git a/packages/ra-ui-materialui/src/list/DatagridHeaderCell.js b/packages/ra-ui-materialui/src/list/DatagridHeaderCell.js index 1af4af489b5..68916d6b598 100644 --- a/packages/ra-ui-materialui/src/list/DatagridHeaderCell.js +++ b/packages/ra-ui-materialui/src/list/DatagridHeaderCell.js @@ -2,15 +2,14 @@ import React from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import shouldUpdate from 'recompose/shouldUpdate'; -import compose from 'recompose/compose'; import TableCell from '@material-ui/core/TableCell'; import TableSortLabel from '@material-ui/core/TableSortLabel'; import Tooltip from '@material-ui/core/Tooltip'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import { FieldTitle, useTranslate } from 'ra-core'; // remove the sort icons when not active -const styles = createStyles({ +const useStyles = makeStyles({ icon: { display: 'none', }, @@ -22,7 +21,6 @@ const styles = createStyles({ }); export const DatagridHeaderCell = ({ - classes, className, field, currentSort, @@ -31,7 +29,9 @@ export const DatagridHeaderCell = ({ isSorting, ...rest }) => { + const classes = useStyles(); const translate = useTranslate(); + return ( <TableCell className={classnames(className, field.props.headerClassName)} @@ -80,7 +80,6 @@ export const DatagridHeaderCell = ({ }; DatagridHeaderCell.propTypes = { - classes: PropTypes.object, className: PropTypes.string, field: PropTypes.element, currentSort: PropTypes.shape({ @@ -93,14 +92,9 @@ DatagridHeaderCell.propTypes = { updateSort: PropTypes.func.isRequired, }; -const enhance = compose( - shouldUpdate( - (props, nextProps) => - props.isSorting !== nextProps.isSorting || - (nextProps.isSorting && - props.currentSort.order !== nextProps.currentSort.order) - ), - withStyles(styles) -); - -export default enhance(DatagridHeaderCell); +export default shouldUpdate( + (props, nextProps) => + props.isSorting !== nextProps.isSorting || + (nextProps.isSorting && + props.currentSort.order !== nextProps.currentSort.order) +)(DatagridHeaderCell); diff --git a/packages/ra-ui-materialui/src/list/DatagridLoading.js b/packages/ra-ui-materialui/src/list/DatagridLoading.js index f2f3b0fdcfd..95540538e76 100644 --- a/packages/ra-ui-materialui/src/list/DatagridLoading.js +++ b/packages/ra-ui-materialui/src/list/DatagridLoading.js @@ -8,21 +8,20 @@ import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import IconButton from '@material-ui/core/IconButton'; import Checkbox from '@material-ui/core/Checkbox'; import classnames from 'classnames'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; -const RawPlaceholder = ({ classes }) => ( - <div className={classes.root}> </div> -); +const useStyles = makeStyles(theme => ({ + root: { + backgroundColor: theme.palette.grey[300], + display: 'flex', + }, +})); -const styles = theme => - createStyles({ - root: { - backgroundColor: theme.palette.grey[300], - display: 'flex', - }, - }); +const Placeholder = () => { + const classes = useStyles(); -const Placeholder = withStyles(styles)(RawPlaceholder); + return <div className={classes.root}> </div>; +}; const times = (nbChildren, fn) => Array.from({ length: nbChildren }, (_, key) => fn(key)); diff --git a/packages/ra-ui-materialui/src/list/Filter.js b/packages/ra-ui-materialui/src/list/Filter.js index 3d8773b9e43..95666381941 100644 --- a/packages/ra-ui-materialui/src/list/Filter.js +++ b/packages/ra-ui-materialui/src/list/Filter.js @@ -1,32 +1,32 @@ -import React, { Component } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; -import { withStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import { sanitizeListRestProps } from 'ra-core'; import FilterForm from './FilterForm'; import FilterButton from './FilterButton'; -const styles = theme => ({ +const useStyles = makeStyles(theme => ({ button: {}, form: { marginBottom: theme.spacing(1), }, -}); +})); -export class Filter extends Component { - renderButton() { - const { - classes = {}, - context, - resource, - children, - showFilter, - hideFilter, - displayedFilters, - filterValues, - ...rest - } = this.props; +const Filter = ({ + context, + resource, + children, + showFilter, + hideFilter, + displayedFilters, + filterValues, + setFilters, + ...rest +}) => { + const classes = useStyles(); + const renderButton = () => { return ( <FilterButton className={classes.button} @@ -38,22 +38,9 @@ export class Filter extends Component { {...sanitizeListRestProps(rest)} /> ); - } - - renderForm() { - const { - classes = {}, - context, - resource, - children, - hideFilter, - displayedFilters, - showFilter, - filterValues, - setFilters, - ...rest - } = this.props; + }; + const renderForm = () => { return ( <FilterForm className={classes.form} @@ -66,18 +53,13 @@ export class Filter extends Component { {...sanitizeListRestProps(rest)} /> ); - } + }; - render() { - return this.props.context === 'button' - ? this.renderButton() - : this.renderForm(); - } -} + return context === 'button' ? renderButton() : renderForm(); +}; Filter.propTypes = { children: PropTypes.node, - classes: PropTypes.object, context: PropTypes.oneOf(['form', 'button']), displayedFilters: PropTypes.object, filterValues: PropTypes.object, @@ -87,4 +69,4 @@ Filter.propTypes = { resource: PropTypes.string.isRequired, }; -export default withStyles(styles)(Filter); +export default Filter; diff --git a/packages/ra-ui-materialui/src/list/FilterButton.js b/packages/ra-ui-materialui/src/list/FilterButton.js index 457222d39cd..04497cc7bce 100644 --- a/packages/ra-ui-materialui/src/list/FilterButton.js +++ b/packages/ra-ui-materialui/src/list/FilterButton.js @@ -1,34 +1,34 @@ -import React, { Component } from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; import Menu from '@material-ui/core/Menu'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import ContentFilter from '@material-ui/icons/FilterList'; import classnames from 'classnames'; -import compose from 'recompose/compose'; import { translate } from 'ra-core'; import lodashGet from 'lodash/get'; import FilterButtonMenuItem from './FilterButtonMenuItem'; import Button from '../button/Button'; -const styles = createStyles({ +const useStyles = makeStyles({ root: { display: 'inline-block' }, }); -export class FilterButton extends Component { - constructor(props) { - super(props); - this.state = { - open: false, - anchorEl: null, - }; - this.handleClickButton = this.handleClickButton.bind(this); - this.handleRequestClose = this.handleRequestClose.bind(this); - this.handleShow = this.handleShow.bind(this); - } +const FilterButton = ({ + className, + resource, + showFilter, + displayedFilters, + filterValues, + translate, + filters, + ...rest +}) => { + const classes = useStyles(); + const [open, setOpen] = useState(false); + const [anchorEl, setAnchorEl] = useState(null); - getHiddenFilters() { - const { filters, displayedFilters, filterValues } = this.props; + const getHiddenFilters = () => { return filters.filter( filterElement => !filterElement.props.alwaysOn && @@ -36,74 +36,54 @@ export class FilterButton extends Component { typeof lodashGet(filterValues, filterElement.props.source) === 'undefined' ); - } + }; - handleClickButton(event) { + const handleClickButton = event => { // This prevents ghost click. event.preventDefault(); + setOpen(true); + setAnchorEl(event.currentTarget); + }; - this.setState({ - open: true, - anchorEl: event.currentTarget, - }); - } - - handleRequestClose() { - this.setState({ - open: false, - }); - } + const handleRequestClose = () => { + setOpen(false); + }; - handleShow({ source, defaultValue }) { - this.props.showFilter(source, defaultValue); - this.setState({ - open: false, - }); - } + const handleShow = ({ source, defaultValue }) => { + showFilter(source, defaultValue); + setOpen(false); + }; - render() { - const hiddenFilters = this.getHiddenFilters(); - const { - classes = {}, - className, - resource, - showFilter, - displayedFilters, - filterValues, - translate, - ...rest - } = this.props; - const { open, anchorEl } = this.state; + const hiddenFilters = getHiddenFilters(); - return ( - hiddenFilters.length > 0 && ( - <div className={classnames(classes.root, className)} {...rest}> - <Button - className="add-filter" - label="ra.action.add_filter" - onClick={this.handleClickButton} - > - <ContentFilter /> - </Button> - <Menu - open={open} - anchorEl={anchorEl} - onClose={this.handleRequestClose} - > - {hiddenFilters.map(filterElement => ( - <FilterButtonMenuItem - key={filterElement.props.source} - filter={filterElement.props} - resource={resource} - onShow={this.handleShow} - /> - ))} - </Menu> - </div> - ) - ); - } -} + return ( + hiddenFilters.length > 0 && ( + <div className={classnames(classes.root, className)} {...rest}> + <Button + className="add-filter" + label="ra.action.add_filter" + onClick={handleClickButton} + > + <ContentFilter /> + </Button> + <Menu + open={open} + anchorEl={anchorEl} + onClose={handleRequestClose} + > + {hiddenFilters.map(filterElement => ( + <FilterButtonMenuItem + key={filterElement.props.source} + filter={filterElement.props} + resource={resource} + onShow={handleShow} + /> + ))} + </Menu> + </div> + ) + ); +}; FilterButton.propTypes = { resource: PropTypes.string.isRequired, @@ -112,11 +92,7 @@ FilterButton.propTypes = { filterValues: PropTypes.object.isRequired, showFilter: PropTypes.func.isRequired, translate: PropTypes.func.isRequired, - classes: PropTypes.object, className: PropTypes.string, }; -export default compose( - translate, - withStyles(styles) -)(FilterButton); +export default translate()(FilterButton); diff --git a/packages/ra-ui-materialui/src/list/FilterForm.js b/packages/ra-ui-materialui/src/list/FilterForm.js index abdee9bf29e..99a809ddbf7 100644 --- a/packages/ra-ui-materialui/src/list/FilterForm.js +++ b/packages/ra-ui-materialui/src/list/FilterForm.js @@ -1,8 +1,8 @@ -import React, { Component } from 'react'; +import React, { useEffect } from 'react'; import PropTypes from 'prop-types'; import { reduxForm } from 'redux-form'; import classnames from 'classnames'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import compose from 'recompose/compose'; import withProps from 'recompose/withProps'; import lodashSet from 'lodash/set'; @@ -10,17 +10,16 @@ import lodashGet from 'lodash/get'; import FilterFormInput from './FilterFormInput'; -const styles = theme => - createStyles({ - form: { - marginTop: '-10px', - paddingTop: 0, - display: 'flex', - alignItems: 'flex-end', - flexWrap: 'wrap', - }, - clearFix: { clear: 'right' }, - }); +const useStyles = makeStyles({ + form: { + marginTop: '-10px', + paddingTop: 0, + display: 'flex', + alignItems: 'flex-end', + flexWrap: 'wrap', + }, + clearFix: { clear: 'right' }, +}); const sanitizeRestProps = ({ anyTouched, @@ -65,20 +64,28 @@ const sanitizeRestProps = ({ ...props }) => props; -export class FilterForm extends Component { - componentDidMount() { - this.props.filters.forEach(filter => { +const FilterForm = ({ + className, + resource, + hideFilter, + filters, + displayedFilters, + initialValues, + ...rest +}) => { + const classes = useStyles(); + + useEffect(() => { + filters.forEach(filter => { if (filter.props.alwaysOn && filter.props.defaultValue) { throw new Error( 'Cannot use alwaysOn and defaultValue on a filter input. Please set the filterDefaultValues props on the <List> element instead.' ); } }); - } - - getShownFilters() { - const { filters, displayedFilters, initialValues } = this.props; + }, [filters]); + const getShownFilters = () => { return filters.filter( filterElement => filterElement.props.alwaysOn || @@ -86,32 +93,27 @@ export class FilterForm extends Component { typeof lodashGet(initialValues, filterElement.props.source) !== 'undefined' ); - } - - handleHide = event => - this.props.hideFilter(event.currentTarget.dataset.key); + }; - render() { - const { classes = {}, className, resource, ...rest } = this.props; + const handleHide = event => hideFilter(event.currentTarget.dataset.key); - return ( - <div - className={classnames(className, classes.form)} - {...sanitizeRestProps(rest)} - > - {this.getShownFilters().map(filterElement => ( - <FilterFormInput - key={filterElement.props.source} - filterElement={filterElement} - handleHide={this.handleHide} - resource={resource} - /> - ))} - <div className={classes.clearFix} /> - </div> - ); - } -} + return ( + <div + className={classnames(className, classes.form)} + {...sanitizeRestProps(rest)} + > + {getShownFilters().map(filterElement => ( + <FilterFormInput + key={filterElement.props.source} + filterElement={filterElement} + handleHide={handleHide} + resource={resource} + /> + ))} + <div className={classes.clearFix} /> + </div> + ); +}; FilterForm.propTypes = { resource: PropTypes.string.isRequired, @@ -119,7 +121,6 @@ FilterForm.propTypes = { displayedFilters: PropTypes.object.isRequired, hideFilter: PropTypes.func.isRequired, initialValues: PropTypes.object, - classes: PropTypes.object, className: PropTypes.string, }; @@ -148,7 +149,6 @@ export const mergeInitialValuesWithDefaultValues = ({ }); const enhance = compose( - withStyles(styles), withProps(mergeInitialValuesWithDefaultValues), reduxForm({ form: 'filterForm', diff --git a/packages/ra-ui-materialui/src/list/PaginationActions.js b/packages/ra-ui-materialui/src/list/PaginationActions.js index 5bce6b34ce9..9c2d69d8774 100644 --- a/packages/ra-ui-materialui/src/list/PaginationActions.js +++ b/packages/ra-ui-materialui/src/list/PaginationActions.js @@ -1,29 +1,34 @@ -import React, { Component } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import pure from 'recompose/pure'; import Button from '@material-ui/core/Button'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import ChevronLeft from '@material-ui/icons/ChevronLeft'; import ChevronRight from '@material-ui/icons/ChevronRight'; import compose from 'recompose/compose'; import { translate } from 'ra-core'; -const styles = theme => - createStyles({ - actions: { - flexShrink: 0, - color: theme.palette.text.secondary, - marginLeft: 20, - }, - hellip: { padding: '1.2em' }, - }); +const useStyles = makeStyles(theme => ({ + actions: { + flexShrink: 0, + color: theme.palette.text.secondary, + marginLeft: 20, + }, + hellip: { padding: '1.2em' }, +})); -export class PaginationActions extends Component { +const PaginationActions = ({ + page, + rowsPerPage, + count, + onChangePage, + translate, +}) => { + const classes = useStyles(); /** * Warning: material-ui's page is 0-based */ - range() { - const { page, rowsPerPage, count } = this.props; + const range = () => { const nbPages = Math.ceil(count / rowsPerPage) || 1; if (isNaN(page) || nbPages === 1) { return []; @@ -57,45 +62,38 @@ export class PaginationActions extends Component { } return input; - } + }; - getNbPages = () => - Math.ceil(this.props.count / this.props.rowsPerPage) || 1; + const getNbPages = () => Math.ceil(count / rowsPerPage) || 1; - prevPage = event => { - if (this.props.page === 0) { - throw new Error( - this.props.translate('ra.navigation.page_out_from_begin') - ); + const prevPage = event => { + if (page === 0) { + throw new Error(translate('ra.navigation.page_out_from_begin')); } - this.props.onChangePage(event, this.props.page - 1); + onChangePage(event, page - 1); }; - nextPage = event => { - if (this.props.page > this.getNbPages() - 1) { - throw new Error( - this.props.translate('ra.navigation.page_out_from_end') - ); + const nextPage = event => { + if (page > getNbPages() - 1) { + throw new Error(translate('ra.navigation.page_out_from_end')); } - this.props.onChangePage(event, this.props.page + 1); + onChangePage(event, page + 1); }; - gotoPage = event => { + const gotoPage = event => { const page = parseInt(event.currentTarget.dataset.page, 10); - if (page < 0 || page > this.getNbPages() - 1) { + if (page < 0 || page > getNbPages() - 1) { throw new Error( - this.props.translate('ra.navigation.page_out_of_boundaries', { + translate('ra.navigation.page_out_of_boundaries', { page: page + 1, }) ); } - this.props.onChangePage(event, page); + onChangePage(event, page); }; - renderPageNums() { - const { classes = {} } = this.props; - - return this.range().map((pageNum, index) => + const renderPageNums = () => { + return range().map((pageNum, index) => pageNum === '.' ? ( <span key={`hyphen_${index}`} className={classes.hellip}> … @@ -103,56 +101,52 @@ export class PaginationActions extends Component { ) : ( <Button className="page-number" - color={ - pageNum === this.props.page + 1 ? 'default' : 'primary' - } + color={pageNum === page + 1 ? 'default' : 'primary'} key={pageNum} data-page={pageNum - 1} - onClick={this.gotoPage} + onClick={gotoPage} size="small" > {pageNum} </Button> ) ); - } + }; - render() { - const { classes = {}, page, translate } = this.props; + const nbPages = getNbPages(); - const nbPages = this.getNbPages(); - if (nbPages === 1) return <div className={classes.actions} />; - return ( - <div className={classes.actions}> - {page > 0 && ( - <Button - color="primary" - key="prev" - onClick={this.prevPage} - className="previous-page" - size="small" - > - <ChevronLeft /> - {translate('ra.navigation.prev')} - </Button> - )} - {this.renderPageNums()} - {page !== nbPages - 1 && ( - <Button - color="primary" - key="next" - onClick={this.nextPage} - className="next-page" - size="small" - > - {translate('ra.navigation.next')} - <ChevronRight /> - </Button> - )} - </div> - ); - } -} + if (nbPages === 1) return <div className={classes.actions} />; + + return ( + <div className={classes.actions}> + {page > 0 && ( + <Button + color="primary" + key="prev" + onClick={prevPage} + className="previous-page" + size="small" + > + <ChevronLeft /> + {translate('ra.navigation.prev')} + </Button> + )} + {renderPageNums()} + {page !== nbPages - 1 && ( + <Button + color="primary" + key="next" + onClick={nextPage} + className="next-page" + size="small" + > + {translate('ra.navigation.next')} + <ChevronRight /> + </Button> + )} + </div> + ); +}; /** * PaginationActions propTypes are copied over from material-ui’s @@ -171,8 +165,7 @@ PaginationActions.propTypes = { const enhance = compose( pure, - translate, - withStyles(styles) + translate ); export default enhance(PaginationActions); diff --git a/packages/ra-ui-materialui/src/list/SimpleList.js b/packages/ra-ui-materialui/src/list/SimpleList.js index cd83fc24149..db77b9edb54 100644 --- a/packages/ra-ui-materialui/src/list/SimpleList.js +++ b/packages/ra-ui-materialui/src/list/SimpleList.js @@ -7,11 +7,11 @@ import ListItemAvatar from '@material-ui/core/ListItemAvatar'; import ListItemIcon from '@material-ui/core/ListItemIcon'; import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'; import ListItemText from '@material-ui/core/ListItemText'; -import { withStyles, createStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import { Link } from 'react-router-dom'; import { linkToRecord, sanitizeListRestProps } from 'ra-core'; -const styles = createStyles({ +const useStyles = makeStyles({ link: { textDecoration: 'none', color: 'inherit', @@ -19,27 +19,27 @@ const styles = createStyles({ tertiary: { float: 'right', opacity: 0.541176 }, }); -const LinkOrNot = withStyles(styles)( - ({ classes, linkType, basePath, id, children }) => - linkType === 'edit' || linkType === true ? ( - <Link to={linkToRecord(basePath, id)} className={classes.link}> - {children} - </Link> - ) : linkType === 'show' ? ( - <Link - to={`${linkToRecord(basePath, id)}/show`} - className={classes.link} - > - {children} - </Link> - ) : ( - <span>{children}</span> - ) -); +const LinkOrNot = ({ linkType, basePath, id, children }) => { + const classes = useStyles(); + + return linkType === 'edit' || linkType === true ? ( + <Link to={linkToRecord(basePath, id)} className={classes.link}> + {children} + </Link> + ) : linkType === 'show' ? ( + <Link + to={`${linkToRecord(basePath, id)}/show`} + className={classes.link} + > + {children} + </Link> + ) : ( + <span>{children}</span> + ); +}; const SimpleList = ({ basePath, - classes = {}, className, data, hasBulkActions, @@ -57,63 +57,69 @@ const SimpleList = ({ tertiaryText, total, ...rest -}) => - (isLoading || total > 0) && ( - <List className={className} {...sanitizeListRestProps(rest)}> - {ids.map(id => ( - <LinkOrNot - linkType={linkType} - basePath={basePath} - id={id} - key={id} - > - <ListItem button> - {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> +}) => { + const classes = useStyles(); + + return ( + (isLoading || total > 0) && ( + <List className={className} {...sanitizeListRestProps(rest)}> + {ids.map(id => ( + <LinkOrNot + linkType={linkType} + basePath={basePath} + id={id} + key={id} + > + <ListItem button> + {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> )} - </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> + </ListItemSecondaryAction> + )} + </ListItem> + </LinkOrNot> + ))} + </List> + ) ); +}; SimpleList.propTypes = { basePath: PropTypes.string, - classes: PropTypes.object, className: PropTypes.string, data: PropTypes.object, hasBulkActions: PropTypes.bool.isRequired, @@ -137,4 +143,4 @@ SimpleList.defaultProps = { selectedIds: [], }; -export default withStyles(styles)(SimpleList); +export default SimpleList; diff --git a/packages/ra-ui-materialui/src/list/SingleFieldList.js b/packages/ra-ui-materialui/src/list/SingleFieldList.js index b1ef9e8fff2..11d91051bf4 100644 --- a/packages/ra-ui-materialui/src/list/SingleFieldList.js +++ b/packages/ra-ui-materialui/src/list/SingleFieldList.js @@ -1,13 +1,13 @@ -import React, { cloneElement, Component, Children } from 'react'; +import React, { cloneElement, Children } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import LinearProgress from '@material-ui/core/LinearProgress'; -import { createStyles, withStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import { linkToRecord } from 'ra-core'; import Link from '../Link'; -const styles = createStyles({ +const useStyles = makeStyles({ root: { display: 'flex', flexWrap: 'wrap' }, }); @@ -54,74 +54,70 @@ const sanitizeRestProps = ({ * </SingleFieldList> * </ReferenceManyField> */ -export class SingleFieldList extends Component { + +const SingleFieldList = ({ + className, + ids, + data, + loaded, + resource, + basePath, + children, + linkType, + ...rest +}) => { + const classes = useStyles(); // Our handleClick does nothing as we wrap the children inside a Link but it is // required fo ChipField which uses a Chip from material-ui. // The material-ui Chip requires an onClick handler to behave like a clickable element - handleClick = () => {}; - render() { - const { - classes = {}, - className, - ids, - data, - loaded, - resource, - basePath, - children, - linkType, - ...rest - } = this.props; - - if (loaded === false) { - return <LinearProgress />; - } + const handleClick = () => {}; - return ( - <div - className={classnames(classes.root, className)} - {...sanitizeRestProps(rest)} - > - {ids.map(id => { - const resourceLinkPath = !linkType - ? false - : linkToRecord(basePath, id, linkType); + if (loaded === false) { + return <LinearProgress />; + } + return ( + <div + className={classnames(classes.root, className)} + {...sanitizeRestProps(rest)} + > + {ids.map(id => { + const resourceLinkPath = !linkType + ? false + : linkToRecord(basePath, id, linkType); - if (resourceLinkPath) { - return ( - <Link - className={classes.link} - key={id} - to={resourceLinkPath} - onClick={stopPropagation} - > - {cloneElement(Children.only(children), { - record: data[id], - resource, - basePath, - // Workaround to force ChipField to be clickable - onClick: this.handleClick, - })} - </Link> - ); - } + if (resourceLinkPath) { + return ( + <Link + className={classes.link} + key={id} + to={resourceLinkPath} + onClick={stopPropagation} + > + {cloneElement(Children.only(children), { + record: data[id], + resource, + basePath, + // Workaround to force ChipField to be clickable + onClick: handleClick, + })} + </Link> + ); + } - return cloneElement(Children.only(children), { - key: id, - record: data[id], - resource, - basePath, - }); - })} - </div> - ); - } -} + return cloneElement(Children.only(children), { + key: id, + record: data[id], + resource, + basePath, + }); + })} + </div> + ); +}; SingleFieldList.propTypes = { basePath: PropTypes.string, children: PropTypes.element.isRequired, - classes: PropTypes.object, className: PropTypes.string, data: PropTypes.object, ids: PropTypes.array, @@ -131,8 +127,7 @@ SingleFieldList.propTypes = { }; SingleFieldList.defaultProps = { - classes: {}, linkType: 'edit', }; -export default withStyles(styles)(SingleFieldList); +export default SingleFieldList; diff --git a/packages/ra-ui-materialui/src/list/SingleFieldList.spec.js b/packages/ra-ui-materialui/src/list/SingleFieldList.spec.js index 6479a893b6a..53d451737f2 100644 --- a/packages/ra-ui-materialui/src/list/SingleFieldList.spec.js +++ b/packages/ra-ui-materialui/src/list/SingleFieldList.spec.js @@ -20,7 +20,7 @@ describe('<SingleFieldList />', () => { <ChipField source="title" /> </SingleFieldList> ); - const linkElements = wrapper.find('WithStyles(Link)'); + const linkElements = wrapper.find('Link'); assert.equal(linkElements.length, 2); assert.deepEqual(linkElements.map(link => link.prop('to')), [ '/posts/1', @@ -42,7 +42,7 @@ describe('<SingleFieldList />', () => { <ChipField source="title" /> </SingleFieldList> ); - const linkElements = wrapper.find('WithStyles(Link)'); + const linkElements = wrapper.find('Link'); assert.equal(linkElements.length, 2); assert.deepEqual(linkElements.map(link => link.prop('to')), [ '/posts/1', @@ -64,7 +64,7 @@ describe('<SingleFieldList />', () => { <ChipField source="title" /> </SingleFieldList> ); - let linkElements = wrapper.find('WithStyles(Link)'); + let linkElements = wrapper.find('Link'); assert.equal(linkElements.length, 2); assert.deepEqual(linkElements.map(link => link.prop('to')), [ '/edit/1', @@ -84,7 +84,7 @@ describe('<SingleFieldList />', () => { <ChipField source="title" /> </SingleFieldList> ); - linkElements = wrapper.find('WithStyles(Link)'); + linkElements = wrapper.find('Link'); assert.equal(linkElements.length, 2); assert.deepEqual(linkElements.map(link => link.prop('to')), [ '/show/1', @@ -108,7 +108,7 @@ describe('<SingleFieldList />', () => { </SingleFieldList> ); - const linkElements = wrapper.find('WithStyles(Link)'); + const linkElements = wrapper.find('Link'); assert.equal(linkElements.length, 2); assert.deepEqual(linkElements.map(link => link.prop('to')), [ '/prefix/bar/1/show', @@ -131,7 +131,7 @@ describe('<SingleFieldList />', () => { <ChipField source="title" /> </SingleFieldList> ); - let linkElements = wrapper.find('WithStyles(Link)'); + let linkElements = wrapper.find('Link'); assert.equal(linkElements.length, 2); assert.deepEqual(linkElements.map(link => link.prop('to')), [ '/edit/1/show', @@ -152,7 +152,7 @@ describe('<SingleFieldList />', () => { <ChipField source="title" /> </SingleFieldList> ); - linkElements = wrapper.find('WithStyles(Link)'); + linkElements = wrapper.find('Link'); assert.equal(linkElements.length, 2); assert.deepEqual(linkElements.map(link => link.prop('to')), [ '/show/1/show', @@ -176,7 +176,7 @@ describe('<SingleFieldList />', () => { </SingleFieldList> ); - const linkElements = wrapper.find('WithStyles(Link)'); + const linkElements = wrapper.find('Link'); assert.equal(linkElements.length, 0); const chipElements = wrapper.find('EnhancedChipField'); assert.equal(chipElements.length, 2);