如何从商店获取主题并在 react-admin 上切换应用主题?

How to get theme from store and switch the app theme on react-admin?

react-admin 版本:3.8.4

我有一个 react-admin 应用程序,我正在尝试在浅色和深色主题之间切换。

您可以在下方看到 Theme.js,我在其中导出了两个对象,其中包含文档中描述的默认主题覆盖。 (https://material-ui.com/pt/customization/default-theme/)

export const darkTheme = {
palette: {
    type: 'dark'
},
overrides: {
    MuiAppBar: {
        colorSecondary: {
            backgroundColor: '#424242', //'#1e4c9a',
            color: '#fff'
        },
    },
    MuiButton: {
        textPrimary: {
            color: '#fff',
        }
    },
    MuiTypography: {
        colorPrimary: {
            color: '#fff'
        }
    },
    MuiDrawer: {
        paper: {
            paddingTop: '20px'
        }
    },
    MuiFilledInput: {
        underline: {
            '&:after': {
                borderBottomColor: '#bf9f00'
            }
        }
    },
    MuiFormLabel: {
        root: {
            '&$focused': {
                color: '#bf9f00'
            }
        }
    },
}
}

export const lightTheme = {
    palette: {
        type: 'light'
    },
    overrides: {
        MuiAppBar: {
            colorSecondary: {
                backgroundColor: '#2196f3',
                color: '#fff'
            },
        },
    },
}

我也做了一个 customReducer,如下所示,称为 ThemeReducer.js

import { createMuiTheme } from '@material-ui/core/styles';

import { darkTheme } from '../../layout/Theme'
const Theme = createMuiTheme(darkTheme)

export default (previousState = Theme, action) => {
  if (action.type === 'SWITCH_THEME') {
    return action.theme;
  }
  return previousState;
}

还有一个动作(ThemeAction.js)来调度状态:

export const switchTheme = (theme) => {
  return {
    type: 'SWITCH_THEME',
    theme
  }
}

要设置自定义 Reducer,如文档中所述,我在 <Admin> 属性自定义 Reducer 上设置如下:

import Theme from './store/reducers/themeReducer'


const App = () => {

  return (
    <div className="admin-body">

      <Admin
        customReducers={{ theme: Theme }}
        layout={Layout}
        authProvider={login}
        dataProvider={dataProvider}
        loginPage={LoginPage}
        locale="pt"
        customRoutes={[
          <Route
            key="configuration"
            path="/configuration"
            title="Configurações"
            component={Configuration}
          />
        ]}
      > ...

要在主题之间切换并设置到商店,我正在使用此配置页面:

import React from 'react'
import { useSelector, useDispatch } from 'react-redux';
import Button from '@material-ui/core/Button';
import { switchTheme } from '../../store/actions/themeAction';
import { darkTheme, lightTheme } from '../../layout/Theme'
import { createMuiTheme } from '@material-ui/core/styles';

import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import { Title } from 'react-admin';

import './styles.css'

const Configuration = () => {
  const dispatch = useDispatch();
  const theme = useSelector(state => state.theme);

  const mainClass = 'configPage'

  return (
    <Card>
      <Title title="Configurações" />
      <CardContent className={mainClass}>
        <div className="theme-label">
          <p className="text" >Selecione seu Tema: </p>
          <Button
            variant="contained"
            className={theme.palette.type === 'light' ? 'active' : ''}
            onClick={() => dispatch(switchTheme(createMuiTheme(lightTheme)))}
          >
            Claro
        </Button>
          <Button
            variant="contained"
            className={theme.palette.type === 'dark' ? 'active' : ''}
            onClick={() => dispatch(switchTheme(createMuiTheme(darkTheme)))}
          >
            Escuro
        </Button>
        </div>
      </CardContent>
    </Card>

  )
}

export default Configuration;

所有这些结构都工作正常,我的意思是全局状态主题正在正确更新,所以 reducer 正在获取点击的主题并切换状态。

问题是,如文档中所述,要更改默认主题,我们必须将新主题作为属性传递给标签 <Admin>,例如:<Admin theme={theme}> </admin> 但是标签 <Admin> 被设置到 App.js 中,并且 redux 上下文不在 App 之上,因此无法将全局主题状态放入其中。

那么我的问题是如何使用我创建的全局主题状态作为应用程序主题传递。

我已经尝试过作为 Layout 的父级传递,如下所示,但主题并未反映在应用程序中。

Layout.js

const MyLayout = (props) => {
    const theme = useSelector(state => state.theme)
    return (
        <ThemeProvider theme={theme}>
            <Layout
                {...props}
                appBar={AppBar}
                sidebar={Sidebar}
                menu={Menu}
                notification={Notification}
            />
        </ThemeProvider>
    )
};

App.js

...
    import Layout from './layout'
    
    const App = () => {
    
      return (
        <div className="admin-body">
    
          <Admin
            customReducers={{ theme: Theme }}
            layout={Layout}
            authProvider={login}
            dataProvider={dataProvider}
            loginPage={LoginPage}
            locale="pt"
            customRoutes={[
              <Route
                key="configuration"
                path="/configuration"
                title="Configurações"
                component={Configuration}
              />
            ]}
          >
...

感谢任何帮助。 此致

不知道你的要求,也许主题没有必要存储在redux中?您可以使用反应上下文 api 的解决方案,如下所示:

type ThemeState = {
    theme: 'light' | 'dark',
    setTheme(theme: 'light' | 'dark'): void,
};

const StateContext = React.createContext<ThemeState>({
    theme: 'light',
    setTheme: () => {}
});

export const ThemeStateProvider = (props: { children: React.ReactNode }) => {

    const [theme, setTheme] = React.useState('light');

    return (

        <StateContext.Provider value={{
            theme,
            setTheme
        }}>
            {props.children}
        </StateContext.Provider>
    );
};

export const useThemeState = () => useContext(StateContext);

然后像这样使用它:

// new root compontent that wraps App
const Root = () => (
    <ThemeStateProvider>
       <App />
    </ThemeStateProvider>
)
const App = () => {
    const {theme} = useThemeState();
   
    return <Admin theme={theme === 'light' ? lightTheme : darkTheme} ... />
}
const Configuration = () => {
    const {setTheme} = useThemeState();
   
    [...]

    return (
        [...]
        <Button onClick={() => setTheme('light')} />
        [...]
    );
}

希望对你有用!