Proper way to use Material UI in reactjs project - reactjs

I've recently started using Material UI and I'm not sure what is the proper way to use it. There are some times when I want to customize the styles of the Material UIcomponents. But for that I have to work in the ".js" file, which I think isn't appropriate.
Is there a way where one can store multiple custom styles/components of Material UI components in a different file and then import the styles/components from there?
Thanks!

There is no problem in separating the styles from the components.
I for one even import the makeStyles hook inside the styles.js.
// styles.js
import { makeStyles } from '#material-ui/core/styles';
const styles = makeStyles(theme => ({
main: {
padding: theme.spacing(2),
},
}));
export default styles;
You can import the useStyles in the component if you like that better
// Component.jsx
import React from 'react';
import styles from './styles';
const Component = () => {
const classes = styles();
return (
<WhatEverComponent classsName={classes.main}/>
);
}
LE: makeStyles in Component
// styles.js
const styles = theme => ({
main: {
padding: theme.spacing(2),
},
});
export default styles;
// Component.jsx
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import stylesObj from './styles';
const styles = makeStyles(stylesObj);
const Component = () => {
const classes = styles();
return (
<WhatEverComponent classsName={classes.main}/>
);
}
I've made also a sandbox here. You can check how both work.
I would suggest to use the first variant.

Related

Published styled-components UI library does not have access to extended theme types on the consumer side

I am creating UI library using styled-components. I am extending the DefaultTheme type to support custom themes that are needed for our use case. Both theme and theme definition is coming from a different internal package that I use. When working on the UI library components, it works correctly. The issue begins when we tried to use the theming on the consumer side.
The problem is that, it seems that types are not really extended to the consumer correctly, so without adding styled.d.ts file on the client side, it doesn't seem to understand the types of the theme. Theme get a type anyand it should be read asDefaultTheme type.
I wonder if there is any way to expose the extended types to consumer so they don't have to add this additional file on their side?
Is there anyone out there who had similar problem? Maybe you could share your findings?
Here is my setup:
Design System project:
// theme.ts
import tokens from 'my-external-package/variables.json';
import type { ThemeProps } from 'styled-components';
import type { MyThemeType } from 'my-external-package//theme';
const { light, dark } = tokens.color.theme;
export const lightTheme = {
color: light,
};
export const darkTheme = {
color: dark,
};
export const defaultTheme = lightTheme;
// styled.d.ts
import {} from 'styled-components';
import type { MyThemeType } from 'my-external-package//theme';
// extend theme
declare module 'styled-components' {
// eslint-disable-next-line #typescript-eslint/no-empty-interface
export interface DefaultTheme extends MyThemeType {}
}
// CustomThemeProvider.tsx
import React, { createContext, useState, ReactNode, useContext } from 'react';
import { ThemeProvider } from 'styled-components';
import { lightTheme, darkTheme } from './theme';
const themes = {
light: lightTheme,
dark: darkTheme,
};
type CustomThemeProviderProps = {
children: ReactNode;
defaultTheme?: keyof typeof themes;
};
const themeContext = createContext({ toggleTheme: () => {} });
const { Provider } = themeContext;
export const CustomThemeProvider = ({
children,
defaultTheme = 'light',
}: CustomThemeProviderProps) => {
const [currentTheme, setCurrentTheme] = useState(defaultTheme);
return (
<Provider
value={{
toggleTheme: () =>
setCurrentTheme((current) => current === 'light' ? 'dark' : 'light'),
}}
>
<ThemeProvider theme={themes[currentTheme]}>{children}</ThemeProvider>
</Provider>
);
};
// I also export hook over here so I can use it on the client side
export const useToggleTheme = () => {
const { toggleTheme } = useContext(themeContext);
return toggleTheme;
};
App consumer NextJs
//_app.tsx
import type { AppProps } from 'next/app';
import { CustomThemeProvider } from 'my-library-package/theming';
function MyApp({ Component, pageProps }: AppProps) {
return (
<CustomThemeProvider defaultTheme='light'>
<Component {...pageProps} />
</CustomThemeProvider>
);
}
export default MyApp;
// consumer_page.tsx
import type { NextPage } from 'next';
import { useCallback, useState } from 'react';
import styled from 'styled-components';
import tokens from 'my-external-package/variables.json';
import { useToggleTheme, Switch } from 'my-library-package';
const CustomComponent = styled.p`
color: ${({ theme }) => theme.color.feedback.success.foreground};
`;
const MyPage: NextPage = () => {
const toggleTheme = useToggleTheme();
return (
<>
<Switch onChange={toggleTheme}/>
<CustomComponent>This component have access to theme</CustomComponent>
</>
)
}
export default MyPage;
We are considering re-export utilities from styled-components with the right DefaultTheme and instruct consumers not to install styled-components
Instruct design-system consumers to create a styled.d.ts file to get the theme correctly populated.
Both of those seems rather painful. :(

Material UI - Custom Select icon with custom themes

I'm creating a custom Material UI theme for my React application :
import {createTheme, Theme, ThemeOptions, ThemeProvider, useMediaQuery} from "#mui/material";
import {Components} from '#mui/material/styles/components';
import {FunctionComponent, PropsWithChildren, useMemo} from 'react';
import {select} from './select';
import {darkColors, lightColors} from './colors';
const components: Components<Theme> = {
MuiSelect: select
}
const lightThemeOptions: ThemeOptions = {
palette: lightColors,
components
};
export const lightTheme = createTheme(lightThemeOptions);
const darkThemeOptions: ThemeOptions = {
palette: darkColors,
components
};
export const darkTheme = createTheme(darkThemeOptions);
// eslint-disable-next-line #typescript-eslint/no-empty-interface
export interface SystemThemeProps {
}
export const SystemThemeProvider: FunctionComponent<PropsWithChildren<SystemThemeProps>> = ({children}) => {
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
const systemTheme = useMemo(() => prefersDarkMode ? darkTheme : lightTheme, [prefersDarkMode]);
return (
<ThemeProvider theme={systemTheme}>{children}</ThemeProvider>
);
};
And here is my select file :
import {Theme} from '#mui/material'
import {ComponentsOverrides} from '#mui/material/styles/overrides';
import {ComponentsProps} from '#mui/material/styles/props';
import {ComponentsVariants} from '#mui/material/styles/variants';
type MuiSelect = {
defaultProps?: ComponentsProps['MuiSelect'];
styleOverrides?: ComponentsOverrides<Theme>['MuiSelect'];
variants?: ComponentsVariants['MuiSelect'];
}
export const select: MuiSelect = {
styleOverrides: {
icon: ({ownerState, theme}) => ({
color: 'red'
})
}
}
So with that code, I can see the select native icon displayed with red color. So I think I've targeted the right classes to change.
However I have no idea how I can say that I don't want to use the native css but instead I want to load a JPEG file.
I guess I'll have one more class to target for the "open" state of the select.
Can you help ?

Material UI: how do I use Theme values with withStyles?

I am building a component with Material UI. I am using the default theme file (as per here https://material-ui.com/customization/default-theme/).
I know I can bring in certain values from the theme with makeStyles as such:
import { makeStyles, Theme } from '#material-ui/styles';
const useStyles = makeStyles((theme: Theme) => ({
something: {
color: theme.palette.common.black,
},
}));
That works fine.
But how do I use those same values with styled Material UI components? eg:
import withStyles from '#material-ui/core/styles';
const StyledBadge = withStyles({
badge: {
color: theme.palette.common.black,
},
})(Badge);
I tried replicating the above, eg:
const StyledBadge = withStyles((theme: Theme) => ({
but this doesn't work.
Would anyone know the correct way of doing this?
I tried creating Component withStyles as below and it is working perfectly.
import { withStyles } from "#material-ui/core";
export const ExpansionPanelDetails = withStyles(theme => ({
root: {
padding: theme.spacing(1),
},
}))(MuiExpansionPanelDetails);

material-ui-next - Dynamically set palette color

I am using "material-ui": "^1.0.0-beta.33" for my project.
What I want to do is set primary palette color dynamically inside a react component (color will be fetched from some api).
Basically I want to override below:
const theme = createMuiTheme({
palette: {
primary: "some color from api"
},
})
Is there a way to set this in componentDidMount function of any component?
Reference: https://material-ui-next.com/
I created a component that uses MuiThemeProvider and wrap my entire app around that component. Below is the structure of the component.
import React, {Component} from "react";
import {connect} from "react-redux";
import {withStyles} from 'material-ui/styles';
import * as colors from 'material-ui/colors';
import { MuiThemeProvider, createMuiTheme } from 'material-ui/styles';
import { withRouter } from 'react-router-dom';
export class ThemeWrapperComponent extends Component {
constructor(props){
super(props);
}
render(){
return (
<MuiThemeProvider theme={createMuiTheme(
{
palette: {
primary: { main: **colorFromApi** },
}
)}>
<div>
{ this.props.children }
</div>
</MuiThemeProvider>
)
}
}
export const ThemeWrapper = withRouter(connect(mapStateToProps)(ThemeWrapperComponent));
Below is how I wrapped my app around this component:
<ThemeWrapper>
<div>
<Routes/>
</div>
</ThemeWrapper>
Now, whatever colour you are sending from the api gets applied to the whole theme. More customisation can be done based on requirement.
I'm doing exactly this. Even got it working with WebMIDI using MIDI controller sliders and knobs just for fun.
The basic strategy is to use createMuiTheme and ThemeProvider and to store the theme in your application store (context, state, redux), etc.
class ThemeManager extends React.Component {
getThemeJson = () => this.props.context.themeJson || defaultThemeJson
componentDidMount () {
const themeJson = this.getThemeJson()
const theme = createMuiTheme(themeJson)
this.props.setContext({ theme, themeJson })
}
render () {
const { children, context } = this.props
const theme = context.theme
return theme
? <ThemeProvider theme={theme}>{children}</ThemeProvider>
: children
}
}
https://github.com/platform9/pf9-ui-plugin/blob/master/src/app/ThemeManager.js
and then you simply update your application's state.
handleImport = themeStr => {
const themeJson = JSON.parse(themeStr)
const theme = createMuiTheme(themeJson)
this.props.setContext({ theme, themeJson })
}
https://github.com/platform9/pf9-ui-plugin/blob/master/src/app/plugins/theme/components/ImportExportPanel.js#L17

material-ui : Extract color from theme

I want to use a color from my material-ui theme inside a component like that :
const MyComponent = props => (
<UsersIcon color={currentTheme.primary1Color} />
)
So, my need is to extract a value from the current provided theme.
I found a working solution to solve this case, using context to retrieve the current theme :
const MyComponent = (props, {muiTheme}) => (
<UsersIcon color={muiTheme.palette.primary1Color} />
)
contextTypes = {
muiTheme: PropTypes.object.isRequired,
}
The React context is used "under the hood" by material-ui, so my solution is not future proof – the implementation of MUI can change –, is there any way to solve this in a proper (or recommended) way ?
You can access the theme variables with react hook or with higher-order component.
Example with hook:
//...
import { useTheme } from '#material-ui/core/styles';
const MyComponent = () => {
const theme = useTheme();
return <UsersIcon color={theme.palette.primary.main} />
}
Example with HOC:
//...
import { withTheme } from '#material-ui/core/styles';
const MyComponent = ({theme, ...other}) => {
return <UsersIcon color={theme.palette.primary.main} />
}
export default withTheme(MyComponent)
Don't forget to wrap root application component with ThemeProvider
Another method to mention is makeStyles for CSS-in-JS styling:
//...
import { makeStyles } from '#material-ui/core/styles'
const useStyles = makeStyles(theme => ({
icon: {
color: theme.palette.primary.main
}
}))
const MyComponent = () => {
const classes = useStyles()
return <UsersIcon className={classes.icon} />
}
Yes you have! using muiThemeable..
import muiThemeable from 'material-ui/styles/muiThemeable';
const MyComponent = props => (
<UsersIcon color={props.muiTheme.palette.primary1Color} />
)
export default muiThemeable()(MyComponent )
from material-ui docs
If your colors don't change at runtime, you can store these constants in a global object that gets used to initialize the theme as well as used in your custom components. This would allow you to not depend on context while keeping your code dry.

Resources