Re-use themed style blocks within a Material-UI components styles - material-ui

Thinking of the concept of a SASS mixin, what would be the best approach to enable the re-use of style blocks that incorporate the current theme.
For example I have two components that import their respective styles:
Component 1
index
styles
Component 2
index
styles
Each component has a text block
Component 1
import { styles } from './styles'
....
<div className={classes.richText}>...</div>
Component 2
import { styles } from './styles'
....
<div className={classes.richText}>...</div>
The styles for richText are the same but I would rather not duplicate them in the imported styles file.
I would rather have a single file that exposes reusable CSS properties based on the theme.
The only way I can currently do this is by returning an object that I have passed the theme to e.g.
const RichText = (theme) => {
return {
fontWeight: 100,
color: theme.typography.body1.color
}
}
Then import this into the styles
Component 1
styles.js
import { RichText } from '../mixins/'
const styles = theme => ({
richText: {
...RichText(theme),
fontSize: '1rem'
}
Component 2
styles.js
import { RichText } from '../mixins/'
const styles = theme => ({
richText: {
...RichText(theme),
fontSize: '1.2rem'
}
Feels like there has to be a better way: utilising withTheme() maybe?

Let's say you want to custom a checkbox as below:
import { makeStyles } from '#material-ui/core/styles';
import CheckBox from '#material-ui/core/CheckBox';
const useStyles = makeStyles((theme) => ({
root: {
color: theme.status.danger,
'&$checked': {
color: theme.status.danger,
},
},
checked: {},
}));
function CustomCheckbox() {
const classes = useStyles();
return (
<Checkbox
defaultChecked
classes={{
root: classes.root,
checked: classes.checked,
}}
/>
);
}
And you want to reuse its style as many components as you can.
You have two options: ThemeProvider or export your custom style.
Changing the component's theme
Themes let you apply a consistent tone to your app. It allows you to customize all design aspects of your project in order to meet the specific needs of your business or brand.
import { createMuiTheme, ThemeProvider } from '#material-ui/core/styles';
import orange from '#material-ui/core/colors/orange';
const myTheme = createMuiTheme({
status: {
danger: orange[500],
},
});
export default function CustomStyles() {
return (
<ThemeProvider theme={myTheme}>
<CustomCheckbox />
</ThemeProvider>
);
}
Exporting your custom styles
Separate your components in a proper folder, i.e: ./assets/styles:
import { makeStyles } from '#material-ui/core/styles';
export const useCheckBoxStyles = makeStyles((theme) => ({
root: {
color: theme.status.danger,
'&$checked': {
color: theme.status.danger,
},
},
checked: {},
}));
And your components tree ./components:
import { useCheckBoxStyles } from './assets/styles';
function CustomCheckbox() {
const classes = useCheckBoxStyles();
return (
<Checkbox
defaultChecked
classes={{
root: classes.root,
checked: classes.checked,
}}
/>
);
}
References
https://material-ui.com/customization/theming

Related

MUI Theme provider not working in tsdx package development

Tring to create a custom button component with mui button using theme provider.
Its working with react app and story book when using it directly.
in my case, I need to publish custom button component as npm package and I'm using tsdx for developing and publishing it. once package has published I was was trying to consume it my another react application button getting rendered but custom themes not getting applied which we provided during package development.
button package
import Button from '#mui/material/Button';
// import { deepmerge } from '#mui/utils';
import {btnProperties} from './properties'
import { createTheme, ThemeProvider, StyledEngineProvider } from '#mui/material/styles';
export interface Props {
/** Name of the button will be displayed */
label : string;
/** optional needs to be string --test*/
variant? : string;
/** Custom styles object */
theme? : object
}
const themeProperties = (inputTokenz : any) =>{
let combinedProps = { ...btnProperties , ...inputTokenz}
console.log("combinedProps",combinedProps)
return createTheme({
components: {
MuiButton: {
styleOverrides: {
root: {
fontSize: combinedProps.btnFontSize,
color: combinedProps.btnColor,
backgroundColor: combinedProps.btnBackgroundColor,
fontWeight: combinedProps.btnfontWeight,
textTransform : 'none',
'&:hover' : {
backgroundColor: combinedProps.btnHover,
}
},
},
},
},
});
}
/**
* A custom MUI button component.
*/
export const EnbdButton: FC<Props> = ({ label,theme }) => {
return <div>
{/* <Button variant='contained'>{label}</Button> */}
<StyledEngineProvider injectFirst>
{/* <ThemeProvider theme={deepmerge(baseTheme, getDeepMergeObj(theme))}> */}
<ThemeProvider theme={themeProperties(theme)}>
<Button variant="contained">{label}</Button>
</ThemeProvider>
</StyledEngineProvider>
</div>;
};```

How to change the font family in Next JS when used with Material UI?

I am recently getting started with Next JS. for styling, I am using Material UI. one issue I am facing is with the fonts. I couldn't able to change the font family to a different font. as per the below example (Github link), I created a _document.js page inside my pages folder
https://github.com/mui-org/material-ui/tree/master/examples/nextjs/pages
_document.js
in the below code I tried changing Roboto with Quicksand
import React from "react";
import Document, { Html, Head, Main, NextScript } from "next/document";
import { ServerStyleSheets } from "#material-ui/core/styles";
// import theme from "../components/Theme.js";
export default class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head>
{/* PWA primary color */}
{/* <meta name="theme-color" content={theme.palette.primary.main} /> */}
<link
rel="stylesheet"
// href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
href="https://fonts.googleapis.com/css?family=Quicksand:300,400,500,700&display=swap"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
// `getInitialProps` belongs to `_document` (instead of `_app`),
// it's compatible with server-side generation (SSG).
MyDocument.getInitialProps = async (ctx) => {
const sheets = new ServerStyleSheets();
const originalRenderPage = ctx.renderPage;
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
// Styles fragment is rendered after the app and page rendering finish.
styles: [
...React.Children.toArray(initialProps.styles),
sheets.getStyleElement(),
],
};
};
Also, I customized my MUI theme object as below
const theme = createMuiTheme({
palette: {
type: "dark",
background: {
default: "#212121",
},
typography: {
fontFamily: "Quicksand",
},
},
});
Layout.js
import { createMuiTheme } from "#material-ui/core/styles";
import { ThemeProvider } from "#material-ui/styles";
import { makeStyles } from "#material-ui/core/styles";
import CssBaseline from "#material-ui/core/CssBaseline";
import Container from "#material-ui/core/Container";
import Divider from "#material-ui/core/Divider";
import NavBar from "./NavBar.js";
import Footer from "./Footer.js";
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
flexDirection: "column",
minHeight: "100vh",
},
main: {
marginTop: theme.spacing(0),
marginBottom: theme.spacing(10),
},
}));
const theme = createMuiTheme({
palette: {
type: "dark",
background: {
default: "#212121",
},
typography: {
fontFamily: "Quicksand",
},
},
});
function Layout({ children }) {
const classes = useStyles();
return (
<div className={classes.root}>
<ThemeProvider theme={theme}>
<CssBaseline />
<Container component="main" className={classes.main} maxWidth="md">
<NavBar />
<Divider />
{children}
<Divider />
<Footer />
</Container>
</ThemeProvider>
</div>
);
}
export default Layout;
But no luck. can anyone please advise?
Thanks In Advance
Venk
You can wrap your in _app.js with a ThemeProvider.
Example _app.js:
import '../styles/globals.scss'
import { motion, AnimatePresence } from 'framer-motion'
import { useRouter } from 'next/router'
import Header from '../components/Header'
import { AuthProvider } from '../contexts/AuthContext'
import { CartProvider } from '../contexts/CartContext'
import { ThemeProvider } from '#material-ui/core'
import theme from '../styles/theme'
export default function App({ Component, pageProps }) {
const router = useRouter()
return(
<AnimatePresence exitBeforeEnter>
<CartProvider>
<AuthProvider>
<ThemeProvider theme={theme}>
<Header />
<motion.div key={router.pathname} className="main">
<Component { ...pageProps } />
</motion.div>
</ThemeProvider>
</AuthProvider>
</CartProvider>
</AnimatePresence>
)
}
Example theme file:
import { createTheme, responsiveFontSizes } from "#material-ui/core"
let theme = createTheme({
palette: {
primary: {
main: '#0277bd',
},
secondary: {
main: '#b2ff59',
},
darkGray: {
main: '#333333'
},
},
})
theme = responsiveFontSizes(theme)
export default theme
I'm not sure how you understand the Mui theme concept. Let's just talk about NextJs first.
NextJs is no different from React. To set the global font-family you just have to put font-family: your desired font into the html, body {} block inside the global.css which is already imported at the top level of _app.js. Every component will use that global font unless it has custom styles. Example:
html, body {
padding: 0;
margin: 0;
font-family: Quicksand, cursive;
}
Now, with Mui, components use a designed style system which conclude default font option as well. To inject your custom style into every single Mui's component you need to wrap all of them inside a top/root level <ThemeProvider theme={yourCutomTheme}> component to override the defaults, reference: Custom Theming from Mui official doc.
Things are not that complicated tho.
In Nextjs V10+ with material-ui
_document.js
Don't forget to add this link tag before your fonts for improving the performance
<link rel="preconnect" href="https://fonts.gstatic.com" />
_app.js
add the override object to make your font a global font in your app instead of Roboto the default
const theme = responsiveFontSizes(
createMuiTheme({
// your custom UI Config ,
typography: {
fontFamily: "Quicksand",
},
overrides: {
MuiCssBaseline: {
"#global": {
"#font-face": [
{
fontFamily: "Quicksand",
fontStyle: "normal",
fontDisplay: "swap",
fontWeight: // your font weight,
},
],
},
},
},
})
)
Here is what worked for me with Google Fonts, MUI v5, NextJs v13 (TS)
https://nextjs.org/docs/api-reference/next/font
https://mui.com/material-ui/customization/typography/#adding-amp-disabling-variants
Add primary and secondary variant to theme
import { Barlow, Cormorant } from "#next/font/google";
export const barlow = Barlow({
weight: ["200", "400", "500", "600", "700"],
display: "swap",
fallback: ["Helvetica", "Arial", "sans-serif"],
});
export const cormorant = Cormorant({
weight: ["300", "400", "500", "600", "700"],
display: "swap",
fallback: ["Times New Roman", "Times", "serif"],
});
declare module "#mui/material/styles" {
interface TypographyVariants {
primary: React.CSSProperties;
secondary: React.CSSProperties;
}
interface TypographyVariantsOptions {
primary?: React.CSSProperties;
secondary?: React.CSSProperties;
}
}
declare module "#mui/material/Typography" {
interface TypographyPropsVariantOverrides {
primary: true;
secondary: true;
}
}
let theme = createTheme({
typography: {
fontFamily: barlow.style.fontFamily,
body1: {
fontFamily: barlow.style.fontFamily,
},
primary: {
fontFamily: barlow.style.fontFamily,
},
secondary: {
fontFamily: cormorant.style.fontFamily,
},
},
});
in component as sx prop
<Typography
sx={{
fontFamily: theme.typography.secondary.fontFamily,
}}
>
More About Us:
</Typography>
cormorant overides the default
or props
<Typography
variant="secondary"
>
More About Us:
</Typography>

How to set DialogTitle to Typography Variant h5?

I currently have lots of dialogs and want to change DialogTitle to have Typography h5 heading. Currently this works:
<DialogTitle>
<Typography variant="h5">Create Exercise</Typography>
</DialogTitle>
But I want this to be applied to all dialogs without having to add use the typography component. I also have tried the following with createTheme, but this does not change the fontSize.
const theme = createTheme({
components: {
MuiDialogTitle: {
styleOverrides: {
root: {
// Set this to h5
fontSize: "1.5rem",
},
},
},
},
});
Modify the DialogTitle component as mention below 👇
import MuiDialogTitle from "#material-ui/core/DialogTitle";
import Typography from "#material-ui/core/Typography";
import { withStyles } from "#material-ui/core/styles";
const styles = (theme) => ({
root: {
margin: 0,
padding: theme.spacing(2),
},
});
const DialogTitle = withStyles(styles)((props) => {
const { children, classes, ...other } = props;
return (
<MuiDialogTitle disableTypography className={classes.root} {...other}>
<Typography variant="h5">{children}</Typography>
</MuiDialogTitle>
);
});
export default DialogTitle;
use this component as DialogTitle, even you can modify the style and also able to add extra component into DialogTitle, like closing icon or some secondary title.

How do you change the primary colour on a resource basis in react-admin?

I have various resource components that sit inside the react-admin Admin component. The admin component has a custom layout. Each of the resource screens has a different primary colour though, to represent the section that you are in and the content type. How is it possible to pass a specific theme object variable of primary colour to each resource that is in then used inside my custom Layout?
<Admin
authProvider={AuthProvider}
dashboard={Dashboard}
dataProvider={graphQLProvider}
history={history}
layout={Layout}
title="Home"
<Resource name="User" options={{label: 'Administrators'}} list={UserList} show={UserShow} edit={UserEdit}
create={UserCreate} icon={UserIcon}/>
Should I be using the className prop which is available on 'every react-admin component' or is there a better way?
React-admin doesn't support this feature, but you can achieve it using a custom layout (see Replacing the appbar) and a listener on the history location to get the resource from the URL. Something like:
import React, { useEffect, useState } from 'react';
import { useHistory } from 'react-router-dom';
import { AppBar } from 'react-admin';
import { makeStyles } from '#material-ui/core/styles';
const useStyles = makeStyles({
posts: {
backgroundColor: 'red',
},
comments: {
backgroundColor: 'blue',
}
});
const MyAppBar = ({ children }) => {
const [resource, setResource] = useState();
const history = useHistory();
const classes = useStyles();
useEffect(() => {
const newResource = extractResourceFromPathname(history.location.pathname); // left as an exercise to the reader
if (newResource !== resource) {
setResource(newResource);
}
}, [history.location.pathname]);
return <AppBar className={classes[resource]} {...props} />
}

Correct way to style a section of a material-ui document

I have a header and want to style all of it's buttons differently than my global theme. I have tried using a child theme like:
<ThemeProvider
theme={(outerTheme) =>
_.merge(outerTheme, {
overrides: {
MuiButton: {
label: {
color: "#fff",
},
},
},
})
}
>
However, while I had expected this to override only MuiButton's in the child theme, it overrode them in them globally.
I know I can use makeStyles but, then, as far as I know, I have to reference it in all the child components which want to use the style. I'd like to wrap a higher level component and have all child components pick up the style. How is this done?
You can do this:
import Button from '#material-ui/core/Button';
import { purple } from '#material-ui/core/colors';
import { makeStyles } from '#material-ui/core/styles';
const useStyles = makeStyles((theme) => ({
header: {
"& button": {
marginRight: theme.spacing(1),
color: theme.palette.getContrastText(purple[500]),
backgroundColor: purple[500],
"&:hover": {
backgroundColor: purple[700],
},
},
},
});
function CustomHeader(props) {
const classes = useStyles();
return (
<div className={classes.header}>
<Button>Button 1</Button>
<Button>Button 2</Button>
<Button disabled>Button 3</Button>
</div>
)
};