import React , { Component , createElement , useEffect , useRef , useState , ErrorInfo , ReactElement , ComponentType , HtmlHTMLAttributes , } from 'react' ; import PropTypes from 'prop-types' ; import { connect } from 'react-redux' ; import classnames from 'classnames' ; import { RouteComponentProps , withRouter } from 'react-router-dom' ; import { withStyles , createStyles } from '@material-ui/core/styles' ; import { ThemeProvider } from '@material-ui/styles' ; import { ThemeOptions } from '@material-ui/core' ; import { ComponentPropType , CoreLayoutProps } from 'ra-core' ; import compose from 'lodash/flowRight' ; import DefaultAppBar , { AppBarProps } from './AppBar' ; import DefaultSidebar from './Sidebar' ; import DefaultMenu , { MenuProps } from './Menu' ; import DefaultNotification from './Notification' ; import DefaultError from './Error' ; import defaultTheme from '../defaultTheme' ; import SkipNavigationButton from '../button/SkipNavigationButton' ; import { createMuiTheme } from './createMuiTheme' ; 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%' , color : theme . palette . getContrastText ( theme . palette . background . default ) , } , appFrame : { display : 'flex' , flexDirection : 'column' , flexGrow : 1 , [ theme . breakpoints . up ( 'xs' ) ] : { marginTop : theme . spacing ( 6 ) , } , [ theme . breakpoints . down ( 'xs' ) ] : { marginTop : theme . spacing ( 7 ) , } , } , contentWithSidebar : { display : 'flex' , flexGrow : 1 , } , content : { backgroundColor : theme . palette . background . default , zIndex : 2 , 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 , } , } , } ) ; class LayoutWithoutTheme extends Component < LayoutWithoutThemeProps , LayoutState > { state : LayoutState = { hasError : false , error : null , errorInfo : null } ; constructor ( props ) { super ( props ) ; see https props . history . listen ( ( ) => { if ( this . state . hasError ) { this . setState ( { hasError : false } ) ; } } ) ; } componentDidCatch ( error , errorInfo ) { this . setState ( { hasError : true , error , errorInfo } ) ; } render ( ) { const { appBar , children , classes , className , error : ErrorComponent , dashboard , logout , menu , notification , open , sidebar , title , match , location , history , staticContext , ... props } = this . props ; const { hasError , error , errorInfo } = this . state ; return ( < > < div className = { classnames ( 'layout' , classes . root , className ) } { ... props } > < SkipNavigationButton /> < div className = { classes . appFrame } > { createElement ( appBar , { title , open , logout } ) } < main className = { classes . contentWithSidebar } > { createElement ( sidebar , { children : createElement ( menu , { logout , hasDashboard : ! ! dashboard , } ) , } ) } < div id = "main-content" className = { classes . content } > { hasError ? ( < ErrorComponent error = { error } errorInfo = { errorInfo } title = { title } / > ) : ( children ) } < /div> < /main> < /div> < /div> { createElement ( notification ) } < />); } static propTypes = { appBar : ComponentPropType , children : PropTypes . oneOfType ( [ PropTypes . func , PropTypes . node ] ) , classes : PropTypes . object , className : PropTypes . string , dashboard : ComponentPropType , error : ComponentPropType , history : PropTypes . object . isRequired , logout : PropTypes . element , menu : ComponentPropType , notification : ComponentPropType , open : PropTypes . bool , sidebar : ComponentPropType , title : PropTypes . node . isRequired , } ; static defaultProps = { appBar : DefaultAppBar , error : DefaultError , menu : DefaultMenu , notification : DefaultNotification , sidebar : DefaultSidebar , } ; } export interface LayoutProps extends CoreLayoutProps , Omit < HtmlHTMLAttributes < HTMLDivElement > , 'title' > { appBar ? : ComponentType < AppBarProps > ; classes ? : any ; className ? : string ; error ? : ComponentType < { error ? : Error ; errorInfo ? : ErrorInfo ; title ? : string | ReactElement < any > ; } > ; menu ? : ComponentType < MenuProps > ; notification ? : ComponentType ; sidebar ? : ComponentType < { children : JSX . Element ; } > ; theme ? : ThemeOptions ; } export interface LayoutState { hasError : boolean ; error ? : Error ; errorInfo ? : ErrorInfo ; } interface LayoutWithoutThemeProps extends RouteComponentProps , Omit < LayoutProps , 'theme' > { open ? : boolean ; } const mapStateToProps = state => ( { open : state . admin . ui . sidebarOpen , } ) ; const EnhancedLayout = compose ( connect ( mapStateToProps , { } ) , withRouter , withStyles ( styles , { name : 'RaLayout' } ) ) ( LayoutWithoutTheme ) ; const Layout = ( { theme : themeOverride , ... props } ) : JSX . Element => { 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 ( < ThemeProvider theme = { theme } > < EnhancedLayout { ... props } / > < /ThemeProvider>); } ; Layout . propTypes = { theme : PropTypes . object , } ; Layout . defaultProps = { theme : defaultTheme , } ; export default Layout ;