diff --git a/packages/ra-ui-materialui/src/defaultTheme.ts b/packages/ra-ui-materialui/src/defaultTheme.ts index dc9a3ed0aa4..c3601ce9659 100644 --- a/packages/ra-ui-materialui/src/defaultTheme.ts +++ b/packages/ra-ui-materialui/src/defaultTheme.ts @@ -1,4 +1,24 @@ -export default { +import { ThemeOptions } from '@material-ui/core/styles/createMuiTheme'; +import { CSSProperties } from 'react'; + +type Width = CSSProperties['width']; + +declare module '@material-ui/core/styles/createMuiTheme' { + interface Theme { + sidebar?: { + width: Width; + closedWidth: Width; + }; + } + interface ThemeOptions { + sidebar?: { + width: Width; + closedWidth: Width; + }; + } +} + +const defaultTheme: ThemeOptions = { palette: { secondary: { light: '#6ec6ff', @@ -7,11 +27,6 @@ export default { contrastText: '#fff', }, }, - typography: { - title: { - fontWeight: 400, - }, - }, sidebar: { width: 240, closedWidth: 55, @@ -27,3 +42,5 @@ export default { }, }, }; + +export default defaultTheme; diff --git a/packages/ra-ui-materialui/src/layout/CardContentInner.js b/packages/ra-ui-materialui/src/layout/CardContentInner.tsx similarity index 82% rename from packages/ra-ui-materialui/src/layout/CardContentInner.js rename to packages/ra-ui-materialui/src/layout/CardContentInner.tsx index 573c85cab2b..9cce9a49b80 100644 --- a/packages/ra-ui-materialui/src/layout/CardContentInner.js +++ b/packages/ra-ui-materialui/src/layout/CardContentInner.tsx @@ -1,7 +1,7 @@ -import React from 'react'; +import React, { FC, ReactNode } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; -import CardContent from '@material-ui/core/CardContent'; +import CardContent, { CardContentProps } from '@material-ui/core/CardContent'; import { makeStyles } from '@material-ui/core/styles'; const useStyles = makeStyles( @@ -30,7 +30,7 @@ const useStyles = makeStyles( * padding double the spacing between each CardContent, leading to too much * wasted space. Use this component as a CardContent alternative. */ -const CardContentInner = props => { +const CardContentInner: FC = props => { const { className, children } = props; const classes = useStyles(props); return ( @@ -40,6 +40,12 @@ const CardContentInner = props => { ); }; +interface Props { + classes?: object; + className?: string; + children: ReactNode; +} + CardContentInner.propTypes = { className: PropTypes.string, classes: PropTypes.object, diff --git a/packages/ra-ui-materialui/src/layout/Confirm.tsx b/packages/ra-ui-materialui/src/layout/Confirm.tsx index 8f8fd29c62e..689b8f4e00f 100644 --- a/packages/ra-ui-materialui/src/layout/Confirm.tsx +++ b/packages/ra-ui-materialui/src/layout/Confirm.tsx @@ -104,8 +104,13 @@ const Confirm: FC = props => { - diff --git a/packages/ra-ui-materialui/src/layout/DeviceTestWrapper.js b/packages/ra-ui-materialui/src/layout/DeviceTestWrapper.tsx similarity index 82% rename from packages/ra-ui-materialui/src/layout/DeviceTestWrapper.js rename to packages/ra-ui-materialui/src/layout/DeviceTestWrapper.tsx index a8e0abd50b7..12db47b4185 100644 --- a/packages/ra-ui-materialui/src/layout/DeviceTestWrapper.js +++ b/packages/ra-ui-materialui/src/layout/DeviceTestWrapper.tsx @@ -1,7 +1,12 @@ -import React from 'react'; +import React, { ReactNode, FC } from 'react'; import mediaQuery from 'css-mediaquery'; import { ThemeProvider } from '@material-ui/styles'; import { createMuiTheme } from '@material-ui/core/styles'; +import { Breakpoint } from '@material-ui/core/styles/createBreakpoints'; + +interface Props { + width: Breakpoint; +} /** * Test utility to simulate a device form factor for server-side mediaQueries @@ -14,7 +19,7 @@ import { createMuiTheme } from '@material-ui/core/styles'; * * */ -const DeviceTestWrapper = ({ width = 'md', children }) => { +const DeviceTestWrapper: FC = ({ width = 'md', children }) => { const theme = createMuiTheme(); // Use https://github.com/ericf/css-mediaquery as ponyfill. diff --git a/packages/ra-ui-materialui/src/layout/Error.js b/packages/ra-ui-materialui/src/layout/Error.tsx similarity index 88% rename from packages/ra-ui-materialui/src/layout/Error.js rename to packages/ra-ui-materialui/src/layout/Error.tsx index 6f3c3301a9b..99fcb6555c5 100644 --- a/packages/ra-ui-materialui/src/layout/Error.js +++ b/packages/ra-ui-materialui/src/layout/Error.tsx @@ -1,4 +1,4 @@ -import React, { Fragment } from 'react'; +import React, { Fragment, FC, ErrorInfo } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import Button from '@material-ui/core/Button'; @@ -10,7 +10,7 @@ import ErrorIcon from '@material-ui/icons/Report'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import History from '@material-ui/icons/History'; -import Title, { TitlePropType } from './Title'; +import Title, { TitlePropType, TitleProps } from './Title'; import { useTranslate } from 'ra-core'; const useStyles = makeStyles( @@ -52,7 +52,7 @@ function goBack() { window.history.go(-1); } -const Error = props => { +const Error: FC = props => { const { error, errorInfo, @@ -88,8 +88,8 @@ const Error = props => {
@@ -99,11 +99,19 @@ const Error = props => { ); }; +export interface ErrorProps { + className?: string; + error: Error; + errorInfo: ErrorInfo; + title: TitleProps['defaultTitle']; + classes?: object; +} + Error.propTypes = { classes: PropTypes.object, className: PropTypes.string, - error: PropTypes.object.isRequired, - errorInfo: PropTypes.object, + error: PropTypes.any.isRequired, + errorInfo: PropTypes.any, title: TitlePropType, }; diff --git a/packages/ra-ui-materialui/src/layout/ErrorBoundary.tsx b/packages/ra-ui-materialui/src/layout/ErrorBoundary.tsx new file mode 100644 index 00000000000..edcf4d14c41 --- /dev/null +++ b/packages/ra-ui-materialui/src/layout/ErrorBoundary.tsx @@ -0,0 +1,63 @@ +import { Component, ComponentType, createElement, ErrorInfo } from 'react'; +import PropTypes from 'prop-types'; +import { ComponentPropType, TitleComponent } from 'ra-core'; +import { withRouter, RouteComponentProps } from 'react-router'; + +import { ErrorProps } from './Error'; + +class _ErrorBoundary extends Component< + ErrorBoundaryProps & RouteComponentProps, + ErrorBoundaryState +> { + static propTypes = { + children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), + error: ComponentPropType, + title: PropTypes.element.isRequired, + }; + + state = { hasError: false, errorMessage: null, errorInfo: null }; + + constructor(props) { + super(props); + /** + * Reset the error state upon navigation + * + * @see https://stackoverflow.com/questions/48121750/browser-navigation-broken-by-use-of-react-error-boundaries + */ + props.history.listen(() => { + if (this.state.hasError) { + this.setState({ hasError: false }); + } + }); + } + + componentDidCatch(errorMessage, errorInfo) { + this.setState({ hasError: true, errorMessage, errorInfo }); + } + + render() { + const { hasError, errorMessage, errorInfo } = this.state; + const { children, error, title } = this.props; + + return hasError + ? createElement(error, { + error: errorMessage, + errorInfo, + title, + }) + : children; + } +} + +export const ErrorBoundary = withRouter(_ErrorBoundary); + +export interface ErrorBoundaryProps { + error: ComponentType; + title?: TitleComponent; +} + +interface ErrorBoundaryState { + hasError?: boolean; + errorMessage: Error | null; + errorInfo: ErrorInfo | null; +} diff --git a/packages/ra-ui-materialui/src/layout/Layout.js b/packages/ra-ui-materialui/src/layout/Layout.js deleted file mode 100644 index b55a6152089..00000000000 --- a/packages/ra-ui-materialui/src/layout/Layout.js +++ /dev/null @@ -1,213 +0,0 @@ -import React, { - Component, - createElement, - useEffect, - useRef, - useState, -} from 'react'; -import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import classnames from 'classnames'; -import { withRouter } from 'react-router-dom'; -import { - createMuiTheme, - withStyles, - createStyles, -} from '@material-ui/core/styles'; -import { ThemeProvider } from '@material-ui/styles'; -import compose from 'recompose/compose'; - -import DefaultAppBar from './AppBar'; -import DefaultSidebar from './Sidebar'; -import DefaultMenu from './Menu'; -import DefaultNotification from './Notification'; -import DefaultError from './Error'; -import defaultTheme from '../defaultTheme'; -import { ComponentPropType } from 'ra-core'; - -const styles = theme => - createStyles({ - root: { - display: 'flex', - flexDirection: 'column', - zIndex: 1, - minHeight: '100vh', - backgroundColor: theme.palette.background.default, - position: 'relative', - minWidth: 'fit-content', - width: '100%', - }, - appFrame: { - display: 'flex', - flexDirection: 'column', - [theme.breakpoints.up('xs')]: { - marginTop: theme.spacing(6), - }, - [theme.breakpoints.down('xs')]: { - marginTop: theme.spacing(7), - }, - }, - contentWithSidebar: { - display: 'flex', - flexGrow: 1, - }, - content: { - display: 'flex', - flexDirection: 'column', - flexGrow: 1, - flexBasis: 0, - padding: theme.spacing(3), - paddingTop: theme.spacing(1), - paddingLeft: 0, - [theme.breakpoints.up('xs')]: { - paddingLeft: 5, - }, - [theme.breakpoints.down('sm')]: { - padding: 0, - }, - }, - }); - -const sanitizeRestProps = ({ - staticContext, - history, - location, - match, - ...props -}) => props; - -class Layout extends Component { - state = { hasError: false, errorMessage: null, errorInfo: null }; - - constructor(props) { - super(props); - /** - * Reset the error state upon navigation - * - * @see https://stackoverflow.com/questions/48121750/browser-navigation-broken-by-use-of-react-error-boundaries - */ - props.history.listen(() => { - if (this.state.hasError) { - this.setState({ hasError: false }); - } - }); - } - - componentDidCatch(errorMessage, errorInfo) { - this.setState({ hasError: true, errorMessage, errorInfo }); - } - - render() { - const { - appBar, - children, - classes, - className, - customRoutes, - error, - dashboard, - logout, - menu, - notification, - open, - sidebar, - title, - ...props - } = this.props; - const { hasError, errorMessage, errorInfo } = this.state; - return ( -
-
- {createElement(appBar, { title, open, logout })} -
- {createElement(sidebar, { - children: createElement(menu, { - logout, - hasDashboard: !!dashboard, - }), - })} -
- {hasError - ? createElement(error, { - error: errorMessage, - errorInfo, - title, - }) - : children} -
-
- {createElement(notification)} -
-
- ); - } -} - -Layout.propTypes = { - appBar: ComponentPropType, - children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), - classes: PropTypes.object, - className: PropTypes.string, - customRoutes: PropTypes.array, - dashboard: ComponentPropType, - error: ComponentPropType, - history: PropTypes.object.isRequired, - logout: PropTypes.element, - menu: ComponentPropType, - notification: ComponentPropType, - open: PropTypes.bool, - sidebar: ComponentPropType, - title: PropTypes.node.isRequired, -}; - -Layout.defaultProps = { - appBar: DefaultAppBar, - error: DefaultError, - menu: DefaultMenu, - notification: DefaultNotification, - sidebar: DefaultSidebar, -}; - -const mapStateToProps = state => ({ - open: state.admin.ui.sidebarOpen, -}); - -const EnhancedLayout = compose( - connect( - mapStateToProps, - {} // Avoid connect passing dispatch in props - ), - withRouter, - withStyles(styles, { name: 'RaLayout' }) -)(Layout); - -const LayoutWithTheme = ({ theme: themeOverride, ...props }) => { - const themeProp = useRef(themeOverride); - const [theme, setTheme] = useState(createMuiTheme(themeOverride)); - - useEffect(() => { - if (themeProp.current !== themeOverride) { - themeProp.current = themeOverride; - setTheme(createMuiTheme(themeOverride)); - } - }, [themeOverride, themeProp, theme, setTheme]); - - return ( - - - - ); -}; - -LayoutWithTheme.propTypes = { - theme: PropTypes.object, -}; - -LayoutWithTheme.defaultProps = { - theme: defaultTheme, -}; - -export default LayoutWithTheme; diff --git a/packages/ra-ui-materialui/src/layout/Layout.tsx b/packages/ra-ui-materialui/src/layout/Layout.tsx new file mode 100644 index 00000000000..57c2789d499 --- /dev/null +++ b/packages/ra-ui-materialui/src/layout/Layout.tsx @@ -0,0 +1,202 @@ +import React, { + ComponentType, + createElement, + FC, + ReactElement, + ReactNode, + useEffect, + useRef, + useState, +} from 'react'; +import PropTypes from 'prop-types'; +import { DrawerProps, makeStyles } from '@material-ui/core'; +import { createMuiTheme } from '@material-ui/core/styles'; +import { ThemeOptions } from '@material-ui/core/styles/createMuiTheme'; +import { ThemeProvider } from '@material-ui/styles'; +import classnames from 'classnames'; +import { ComponentPropType, DashboardComponent, ReduxState } from 'ra-core'; +import { useSelector } from 'react-redux'; +import { RouteComponentProps } from 'react-router-dom'; + +import DefaultAppBar from './AppBar'; +import DefaultSidebar from './Sidebar'; +import DefaultMenu, { MenuProps } from './Menu'; +import DefaultNotification, { NotificationProps } from './Notification'; +import DefaultError from './Error'; +import defaultTheme from '../defaultTheme'; +import { ErrorBoundary, ErrorBoundaryProps } from './ErrorBoundary'; + +const useStyles = makeStyles( + theme => ({ + root: { + display: 'flex', + flexDirection: 'column', + zIndex: 1, + minHeight: '100vh', + backgroundColor: theme.palette.background.default, + position: 'relative', + minWidth: 'fit-content', + width: '100%', + }, + appFrame: { + display: 'flex', + flexDirection: 'column', + [theme.breakpoints.up('xs')]: { + marginTop: theme.spacing(6), + }, + [theme.breakpoints.down('xs')]: { + marginTop: theme.spacing(7), + }, + }, + contentWithSidebar: { + display: 'flex', + flexGrow: 1, + }, + content: { + display: 'flex', + flexDirection: 'column', + flexGrow: 1, + flexBasis: 0, + padding: theme.spacing(3), + paddingTop: theme.spacing(1), + paddingLeft: 0, + [theme.breakpoints.up('xs')]: { + paddingLeft: 5, + }, + [theme.breakpoints.down('sm')]: { + padding: 0, + }, + }, + }), + { name: 'RaLayout' } +); + +const sanitizeRestProps = ({ + staticContext, + history, + location, + match, + ...props +}: Partial): Omit< + Partial, + keyof RouteComponentProps | 'title' +> => props; + +export const Layout: FC = props => { + const { + appBar, + children, + classes: classesOverride, + className, + customRoutes, + error, + dashboard, + logout, + menu, + notification, + sidebar, + title, + ...rest + } = props; + + const open = useSelector((state: ReduxState) => state.admin.ui.sidebarOpen); + const classes = useStyles(); + + return ( +
+
+ {createElement(appBar, { title, open, logout })} +
+ {createElement(sidebar, { + children: createElement(menu, { + logout, + hasDashboard: !!dashboard, + }), + })} +
+ + {children} + +
+
+ {createElement(notification)} +
+
+ ); +}; + +Layout.propTypes = { + appBar: ComponentPropType, + children: PropTypes.element, + classes: PropTypes.object, + className: PropTypes.string, + customRoutes: PropTypes.array, + dashboard: ComponentPropType, + error: ComponentPropType, + logout: PropTypes.element, + menu: ComponentPropType, + notification: ComponentPropType, + open: PropTypes.bool, + sidebar: ComponentPropType, + title: PropTypes.element.isRequired, +}; + +Layout.defaultProps = { + appBar: DefaultAppBar, + error: DefaultError, + menu: DefaultMenu, + notification: DefaultNotification, + sidebar: DefaultSidebar, +}; + +interface LayoutWithThemeProps extends LayoutProps { + theme?: ThemeOptions; +} + +const LayoutWithTheme: FC = ({ + theme: themeOverride, + ...props +}) => { + const themeProp = useRef(themeOverride); + const [theme, setTheme] = useState(createMuiTheme(themeOverride)); + + useEffect(() => { + if (themeProp.current !== themeOverride) { + themeProp.current = themeOverride; + setTheme(createMuiTheme(themeOverride)); + } + }, [themeOverride, themeProp, theme, setTheme]); + + return ( + + + + ); +}; + +interface LayoutProps extends RouteComponentProps, ErrorBoundaryProps { + appBar?: ComponentType; + classes?: object; + children: ReactElement; + className?: string; + customRoutes?: any[]; + dashboard?: DashboardComponent; + logout?: ReactNode; + menu?: ComponentType; + notification?: ComponentType; + open?: boolean; + sidebar?: ComponentType; +} + +LayoutWithTheme.propTypes = { + theme: PropTypes.any, +}; + +LayoutWithTheme.defaultProps = { + theme: defaultTheme, +}; + +export default LayoutWithTheme; diff --git a/packages/ra-ui-materialui/src/layout/LinearProgress.js b/packages/ra-ui-materialui/src/layout/LinearProgress.tsx similarity index 85% rename from packages/ra-ui-materialui/src/layout/LinearProgress.js rename to packages/ra-ui-materialui/src/layout/LinearProgress.tsx index 2822b0883e0..e58be311037 100644 --- a/packages/ra-ui-materialui/src/layout/LinearProgress.js +++ b/packages/ra-ui-materialui/src/layout/LinearProgress.tsx @@ -1,5 +1,7 @@ -import React from 'react'; -import Progress from '@material-ui/core/LinearProgress'; +import React, { FC } from 'react'; +import Progress, { + LinearProgressProps, +} from '@material-ui/core/LinearProgress'; import PropTypes from 'prop-types'; import { makeStyles } from '@material-ui/core/styles'; import classnames from 'classnames'; @@ -24,7 +26,7 @@ const useStyles = makeStyles( * * @param {Object} classes CSS class names */ -const LinearProgress = props => { +const LinearProgress: FC = props => { const { classes: classesOverride, className, ...rest } = props; const classes = useStyles(props); return ( diff --git a/packages/ra-ui-materialui/src/layout/Loading.js b/packages/ra-ui-materialui/src/layout/Loading.tsx similarity index 89% rename from packages/ra-ui-materialui/src/layout/Loading.js rename to packages/ra-ui-materialui/src/layout/Loading.tsx index ac71d81dd3e..96e2b9a232b 100644 --- a/packages/ra-ui-materialui/src/layout/Loading.js +++ b/packages/ra-ui-materialui/src/layout/Loading.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { FC } from 'react'; import PropTypes from 'prop-types'; import { makeStyles } from '@material-ui/core/styles'; import classnames from 'classnames'; @@ -33,7 +33,7 @@ const useStyles = makeStyles( { name: 'RaLoading' } ); -const Loading = props => { +const Loading: FC = props => { const { className, loadingPrimary = 'ra.page.loading', @@ -52,6 +52,13 @@ const Loading = props => { ); }; +interface Props { + classes?: object; + className?: string; + loadingPrimary?: string; + loadingSecondary?: string; +} + Loading.propTypes = { classes: PropTypes.object, className: PropTypes.string, diff --git a/packages/ra-ui-materialui/src/layout/LoadingIndicator.js b/packages/ra-ui-materialui/src/layout/LoadingIndicator.tsx similarity index 71% rename from packages/ra-ui-materialui/src/layout/LoadingIndicator.js rename to packages/ra-ui-materialui/src/layout/LoadingIndicator.tsx index 0d999801e5c..02d1e5a21cb 100644 --- a/packages/ra-ui-materialui/src/layout/LoadingIndicator.js +++ b/packages/ra-ui-materialui/src/layout/LoadingIndicator.tsx @@ -1,10 +1,12 @@ -import React from 'react'; +import React, { FC } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { useSelector } from 'react-redux'; import { makeStyles } from '@material-ui/core/styles'; -import CircularProgress from '@material-ui/core/CircularProgress'; -import { useRefreshWhenVisible } from 'ra-core'; +import CircularProgress, { + CircularProgressProps, +} from '@material-ui/core/CircularProgress'; +import { useRefreshWhenVisible, ReduxState } from 'ra-core'; import RefreshIconButton from '../button/RefreshIconButton'; @@ -17,10 +19,10 @@ const useStyles = makeStyles( { name: 'RaLoadingIndicator' } ); -const LoadingIndicator = props => { +const LoadingIndicator: FC = props => { const { classes: classesOverride, className, ...rest } = props; useRefreshWhenVisible(); - const loading = useSelector(state => state.admin.loading > 0); + const loading = useSelector((state: ReduxState) => state.admin.loading > 0); const classes = useStyles(props); return loading ? ( { LoadingIndicator.propTypes = { classes: PropTypes.object, className: PropTypes.string, - width: PropTypes.string, + size: PropTypes.string, }; export default LoadingIndicator; diff --git a/packages/ra-ui-materialui/src/layout/Menu.tsx b/packages/ra-ui-materialui/src/layout/Menu.tsx index 7f7207f75f9..24bb6e17385 100644 --- a/packages/ra-ui-materialui/src/layout/Menu.tsx +++ b/packages/ra-ui-materialui/src/layout/Menu.tsx @@ -1,4 +1,4 @@ -import React, { FC, ReactElement } from 'react'; +import React, { FC, ReactNode } from 'react'; import PropTypes from 'prop-types'; import { shallowEqual, useSelector } from 'react-redux'; // @ts-ignore @@ -92,7 +92,7 @@ export interface MenuProps { className?: string; dense?: boolean; hasDashboard: boolean; - logout?: ReactElement; + logout?: ReactNode; onMenuClick?: () => void; } diff --git a/packages/ra-ui-materialui/src/layout/NotFound.js b/packages/ra-ui-materialui/src/layout/NotFound.tsx similarity index 73% rename from packages/ra-ui-materialui/src/layout/NotFound.js rename to packages/ra-ui-materialui/src/layout/NotFound.tsx index a3b5caccfb6..1f3de0c8054 100644 --- a/packages/ra-ui-materialui/src/layout/NotFound.js +++ b/packages/ra-ui-materialui/src/layout/NotFound.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { FC } from 'react'; import PropTypes from 'prop-types'; import Button from '@material-ui/core/Button'; import { makeStyles } from '@material-ui/core/styles'; @@ -7,7 +7,8 @@ import History from '@material-ui/icons/History'; import classnames from 'classnames'; import { useAuthenticated, useTranslate } from 'ra-core'; -import Title from './Title'; +import Title, { TitleProps } from './Title'; +import { RouteComponentProps } from 'react-router-dom'; const useStyles = makeStyles( theme => ({ @@ -45,8 +46,8 @@ function goBack() { window.history.go(-1); } -const NotFound = props => { - const { className, classes: classesOverride, title, ...rest } = props; +const NotFound: FC = props => { + const { className, title, classes: classesOverride, ...rest } = props; const classes = useStyles(props); const translate = useTranslate(); useAuthenticated(); @@ -62,7 +63,11 @@ const NotFound = props => {
{translate('ra.message.not_found')}.
-
@@ -76,13 +81,21 @@ const sanitizeRestProps = ({ location, match, ...rest -}) => rest; +}: Partial): Omit< + Partial, + keyof RouteComponentProps | 'className' | 'title' +> => rest; + +export interface NotFoundProps extends RouteComponentProps { + className: string; + title: TitleProps['defaultTitle']; + classes?: ReturnType; +} NotFound.propTypes = { className: PropTypes.string, - classes: PropTypes.object, + classes: PropTypes.any, title: PropTypes.string, - location: PropTypes.object, }; export default NotFound; diff --git a/packages/ra-ui-materialui/src/layout/Notification.tsx b/packages/ra-ui-materialui/src/layout/Notification.tsx index 3e8aeb2d0c5..551be4bc664 100644 --- a/packages/ra-ui-materialui/src/layout/Notification.tsx +++ b/packages/ra-ui-materialui/src/layout/Notification.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useState, useEffect, useCallback, FC } from 'react'; import PropTypes from 'prop-types'; import { useSelector, useDispatch } from 'react-redux'; import Snackbar, { SnackbarProps } from '@material-ui/core/Snackbar'; @@ -15,10 +15,6 @@ import { useTranslate, } from 'ra-core'; -interface Props { - type?: string; -} - const useStyles = makeStyles( (theme: Theme) => ({ error: { @@ -36,9 +32,7 @@ const useStyles = makeStyles( { name: 'RaNotification' } ); -const Notification: React.FunctionComponent< - Props & Omit -> = props => { +const Notification: FC = props => { const { classes: classesOverride, type, @@ -111,6 +105,10 @@ const Notification: React.FunctionComponent< ); }; +export interface NotificationProps extends Omit { + type?: string; +} + Notification.propTypes = { type: PropTypes.string, }; diff --git a/packages/ra-ui-materialui/src/layout/Sidebar.js b/packages/ra-ui-materialui/src/layout/Sidebar.tsx similarity index 77% rename from packages/ra-ui-materialui/src/layout/Sidebar.js rename to packages/ra-ui-materialui/src/layout/Sidebar.tsx index 9898de49f5a..e5d2f2bb7b1 100644 --- a/packages/ra-ui-materialui/src/layout/Sidebar.js +++ b/packages/ra-ui-materialui/src/layout/Sidebar.tsx @@ -1,14 +1,24 @@ -import React, { Children, cloneElement } from 'react'; +import React, { Children, cloneElement, FC, ReactElement } from 'react'; import PropTypes from 'prop-types'; import { useDispatch, useSelector } from 'react-redux'; -import { Drawer, makeStyles, useMediaQuery } from '@material-ui/core'; +import { + Drawer, + makeStyles, + useMediaQuery, + Theme, + DrawerProps, +} from '@material-ui/core'; import lodashGet from 'lodash/get'; -import { setSidebarVisibility } from 'ra-core'; +import { setSidebarVisibility, ReduxState } from 'ra-core'; export const DRAWER_WIDTH = 240; export const CLOSED_DRAWER_WIDTH = 55; -const useStyles = makeStyles( +interface StyleProps { + open: boolean; +} + +const useStyles = makeStyles( theme => ({ drawerPaper: { position: 'relative', @@ -45,19 +55,14 @@ const useStyles = makeStyles( { name: 'RaSidebar' } ); -const Sidebar = props => { - const { - children, - closedSize, - size, - classes: classesOverride, - ...rest - } = props; +const Sidebar: FC = props => { + const { children, ...rest } = props; const dispatch = useDispatch(); - const isXSmall = useMediaQuery(theme => theme.breakpoints.down('xs')); - const isSmall = useMediaQuery(theme => theme.breakpoints.down('sm')); - const open = useSelector(state => state.admin.ui.sidebarOpen); - useSelector(state => state.locale); // force redraw on locale change + const isXSmall = useMediaQuery(theme => + theme.breakpoints.down('xs') + ); + const isSmall = useMediaQuery(theme => theme.breakpoints.down('sm')); + const open = useSelector((state: ReduxState) => state.admin.ui.sidebarOpen); const handleClose = () => dispatch(setSidebarVisibility(false)); const toggleSidebar = () => dispatch(setSidebarVisibility(!open)); const classes = useStyles({ ...props, open }); @@ -105,8 +110,12 @@ const Sidebar = props => { ); }; +interface SidebarProps extends DrawerProps { + children: ReactElement; +} + Sidebar.propTypes = { - children: PropTypes.node.isRequired, + children: PropTypes.element.isRequired, }; export default Sidebar; diff --git a/packages/ra-ui-materialui/src/layout/Title.js b/packages/ra-ui-materialui/src/layout/Title.tsx similarity index 73% rename from packages/ra-ui-materialui/src/layout/Title.js rename to packages/ra-ui-materialui/src/layout/Title.tsx index edecd818cf6..485d9e2d12c 100644 --- a/packages/ra-ui-materialui/src/layout/Title.js +++ b/packages/ra-ui-materialui/src/layout/Title.tsx @@ -1,9 +1,15 @@ -import React, { cloneElement } from 'react'; +import React, { cloneElement, FC, ReactElement } from 'react'; import { createPortal } from 'react-dom'; import PropTypes from 'prop-types'; -import { useTranslate, warning } from 'ra-core'; +import { useTranslate, warning, TitleComponent } from 'ra-core'; -const Title = ({ className, defaultTitle, locale, record, title, ...rest }) => { +const Title: FC = ({ + className, + defaultTitle, + record, + title, + ...rest +}) => { const translate = useTranslate(); const container = typeof document !== 'undefined' @@ -26,6 +32,13 @@ const Title = ({ className, defaultTitle, locale, record, title, ...rest }) => { return createPortal(titleElement, container); }; +export interface TitleProps { + defaultTitle: TitleComponent; + className?: string; + record?: any; + title?: TitleComponent; +} + export const TitlePropType = PropTypes.oneOfType([ PropTypes.string, PropTypes.element, @@ -34,7 +47,6 @@ export const TitlePropType = PropTypes.oneOfType([ Title.propTypes = { defaultTitle: PropTypes.string, className: PropTypes.string, - locale: PropTypes.string, record: PropTypes.object, title: TitlePropType, }; diff --git a/packages/ra-ui-materialui/src/layout/TitleForRecord.js b/packages/ra-ui-materialui/src/layout/TitleForRecord.js deleted file mode 100644 index 96f265c76a9..00000000000 --- a/packages/ra-ui-materialui/src/layout/TitleForRecord.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import Title, { TitlePropType } from './Title'; - -const TitleForRecord = ({ defaultTitle, record, title }) => - record ? ( - - ) : ( - '' - ); - -TitleForRecord.propTypes = { - defaultTitle: PropTypes.any, - record: PropTypes.object, - title: TitlePropType, -}; - -export default TitleForRecord; diff --git a/packages/ra-ui-materialui/src/layout/TitleForRecord.tsx b/packages/ra-ui-materialui/src/layout/TitleForRecord.tsx new file mode 100644 index 00000000000..5793cee6ed8 --- /dev/null +++ b/packages/ra-ui-materialui/src/layout/TitleForRecord.tsx @@ -0,0 +1,30 @@ +import React, { FC } from 'react'; +import PropTypes from 'prop-types'; +import Title, { TitlePropType, TitleProps } from './Title'; + +const TitleForRecord: FC<Props> = ({ + defaultTitle, + record, + title, + className, +}) => + record ? ( + <Title + title={title} + record={record} + defaultTitle={defaultTitle} + className={className} + /> + ) : null; + +interface Props extends TitleProps { + record: TitleProps['record']; +} + +TitleForRecord.propTypes = { + defaultTitle: PropTypes.any, + record: PropTypes.object, + title: TitlePropType, +}; + +export default TitleForRecord; diff --git a/packages/ra-ui-materialui/src/layout/TopToolbar.js b/packages/ra-ui-materialui/src/layout/TopToolbar.tsx similarity index 89% rename from packages/ra-ui-materialui/src/layout/TopToolbar.js rename to packages/ra-ui-materialui/src/layout/TopToolbar.tsx index 66f5a96c9a5..f52124e72e8 100644 --- a/packages/ra-ui-materialui/src/layout/TopToolbar.js +++ b/packages/ra-ui-materialui/src/layout/TopToolbar.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import React, { FC } from 'react'; import PropTypes from 'prop-types'; -import Toolbar from '@material-ui/core/Toolbar'; +import Toolbar, { ToolbarProps } from '@material-ui/core/Toolbar'; import { makeStyles } from '@material-ui/core/styles'; import classnames from 'classnames'; @@ -29,7 +29,7 @@ const useStyles = makeStyles( { name: 'RaTopToolbar' } ); -const TopToolbar = props => { +const TopToolbar: FC<ToolbarProps> = props => { const { className, children, ...rest } = props; const classes = useStyles(props); return ( diff --git a/packages/ra-ui-materialui/src/layout/UserMenu.js b/packages/ra-ui-materialui/src/layout/UserMenu.tsx similarity index 87% rename from packages/ra-ui-materialui/src/layout/UserMenu.js rename to packages/ra-ui-materialui/src/layout/UserMenu.tsx index 25a39d38e1f..e5c26723db7 100644 --- a/packages/ra-ui-materialui/src/layout/UserMenu.js +++ b/packages/ra-ui-materialui/src/layout/UserMenu.tsx @@ -1,12 +1,18 @@ -import React, { Children, cloneElement, isValidElement, useState } from 'react'; +import React, { + Children, + cloneElement, + isValidElement, + useState, + FC, + ReactNode, +} from 'react'; import PropTypes from 'prop-types'; import { useTranslate } from 'ra-core'; import Tooltip from '@material-ui/core/Tooltip'; import IconButton from '@material-ui/core/IconButton'; import Menu from '@material-ui/core/Menu'; import AccountCircle from '@material-ui/icons/AccountCircle'; - -const UserMenu = props => { +const UserMenu: FC<Props> = props => { const [anchorEl, setAnchorEl] = useState(null); const translate = useTranslate(); @@ -57,9 +63,16 @@ const UserMenu = props => { ); }; +interface Props { + label?: string; + logout?: ReactNode; + icon?: ReactNode; + children?: ReactNode; +} + UserMenu.propTypes = { children: PropTypes.node, - label: PropTypes.string.isRequired, + label: PropTypes.string, logout: PropTypes.element, icon: PropTypes.node, }; diff --git a/packages/ra-ui-materialui/src/layout/index.js b/packages/ra-ui-materialui/src/layout/index.ts similarity index 100% rename from packages/ra-ui-materialui/src/layout/index.js rename to packages/ra-ui-materialui/src/layout/index.ts