Material UI Customized styled element with theme - material-ui

I'm starting to use Material UI in my project.
I have created a theme with some basic definitions, using my css variables:
import { createTheme } from '#mui/material/styles';
import theme from './_my-variables.scss';
const muiTheme = createTheme({
palette: {
primary: {
main: theme['color-blue'], // 'color-blue' is my css variable
},
},
typography: {
fontFamily: theme['brand-font'], // 'brand-font' is my css variable
},
}
I also used ThemeProvider in my App layout:
export const App = (props) => {
return (<Provider store={store}>
<ThemeProvider theme={muiTheme}>
<ConnectedRouter history={historyRef.history}>
<Layout {...props} />
</ConnectedRouter>
</ThemeProvider>
</Provider>);
};
I have a page with MUI elements, and it works fine and takes the new primary color and the new font.
<Box>
<Paper>
<Checkbox color="primary" /> <!-- I can see my new color in the page! Yay! -->
some text
</Paper>
</Box>
Now I want to create a custom element - like <MyCustomizeBox> that will have a different properties, using my css variables (for example - a specific width, defined in my variables).
How do I define them in my theme and how to use it to customize a new element?
I prefer not to user classes () because I want to create a generic elements for reusing. Also I don't want to change all Boxes in my app - only the customized ones.
I tries to use "withStyles" but I was getting the default theme instead of my customized theme and I saw on Google that I'm not support to use both "withStyles" and theme together.

At last, found the answer:
import { styled } from '#mui/system';
const MyCustomizedPaper = styled(Paper)(({ theme }) => ({
width: theme.custom.myCustomWidthCssVar
}));
<MyCustomizedPaper ></MyCustomizedPaper >

Related

Is there a way to "inject" pure CSS into MUI v5

I have a custom spinner that is currently using keyframes like so:
import { keyframes } from "#mui/system";
...
const keyframeSpinner = keyframes`
0%{transform:rotate(0deg);}
100%{transform:rotate(360deg);}
`;
...
<Box
sx={{
animation: `${keyframeSpinner} 1s linear infinite`,
}}
/>
...
I don't want to import #mui/system and I don't want to use styled components.
So, I'm trying to find a solution where I can uses pure css or another solution that I'm unaware of.
You can easily apply in-line CSS styles to components using emotion, which is also used by MUI.
For example, here is the css prop from emotion being used to customize background-color and hover on a div. The code you write in the css prop can be pure CSS.
import { css, jsx } from '#emotion/react'
const color = 'darkgreen'
const customCss = css`
background-color: hotpink
&:hover { color: ${color} }
`
render(
<div css = {customCss}>
This div has a hotpink background.
</div>
)

Custom Components in a Custom Layout in react-admin e.g. appbar, sidebar doesnt' don't show the correct theme colours for all page types

What you were expecting:
When you are dynamically pick a theme object in a custom layout based on the URL/history in react-admin resource, the correct theme is used on the every page type e.g. List, Show, Create and Edit.
What happened instead:
When you go to the resource listing page, the correct theme is used. But when you then navigate to any of the create/show or edit pages, the theme seems to default back to the base / primary one.
Steps to reproduce:
Create Custom Layout file. Overwrite the Appbar and List component (with Listbase) with custom ones too. Although I'm not sure the appbar/listbase steps are necessary.
Use the custom Layout in the Admin component.
Create some resources.
Dynamically pick a theme based on the name of the Resouce (using history).
Navigate to the listing page of the resources. They display the specific theme correctly.
Navigate to the create/show/edit pages of the resources. These, for some reason, default back to the primary theme (or primary default colours).
Related code:
At the root level code..
<Admin
authProvider={AuthProvider({ awsConfig })}
dashboard={Dashboard}
dataProvider={graphQLProvider}
history={history}
layout={Layout}
title="Home"
loginPage={CustomLogin}
// logoutButton={LogoutButton}
>
<LMSResource projection={["details"]} name="Assigment" options={{label: 'Assignments'}} create={AssignmentCreate} list={AssignmentList}
title="Generic" show={AssignmentShow} edit={AssignmentEdit} icon={AssignmentIcon}/>
In the custom Layout file (I have an array of themes to choose from that is imported from another file)
const Layout = ({
children,
dashboard,
logout,
}) => {
const [resource, setResource] = useState('');
const history = useHistory();
useEffect(() => {
const newResource = history.location.pathname.replace(/^\/|\/$/g, '').toLowerCase();
if(newResource===''){
setResource('primary');
} else if (newResource !== resource) {
setResource(newResource);
}
}, [history.location.pathname]);
function getTheme(themes){
let result = ''
var i=0;
for(i = 0; i < themes.length; i++){
if(Object.keys(themes[i])[0] === resource){
result = themes[i][resource];
}
}
return (!result ? themes[0].primary : result );
}
const theme = createMuiTheme(getTheme(themes));
const useStyles = makeStyles(() => ({
...
}));
const classes = useStyles(theme);
const dispatch = useDispatch();
const open = useSelector(state => state.admin.ui.sidebarOpen);
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<Grid container>
<Box className={classes.sidebar} color="grey.200" borderRight={1} item md={3} lg={2} component={Grid} display={{ xs: 'none', md: 'block' }}>
<Grid>
<Logo/>
<SchoolText color='textPrimary'/>
</Grid>
<TextField className={[classes.search,classes['search' + resource]]} id="outlined-basic" label="Search" variant="outlined" />
<Sidebar open={open} className={classes.sidebar}>
<Menu logout={logout} hasDashboard={!!dashboard} />
</Sidebar>
</Box>
<Box item component={Grid} xs={12} md={9} lg={10}>
<AppBar title={resource} logout={logout} />
<Box component={Grid} className={classes.breadcrumb} display={{ xs: 'none', md: 'block' }} borderTop={1}>
<Breadcrumbs/>
</Box>
<main>
<div className={classes.content}>
{children}
</div>
</main>
</Box>
<Notification />
</Grid>
</ThemeProvider>
);
}; export default Layout
I can post the List, Show, Create and Edit components if necessary, but I presume if one of resource components is receiving the right theme variables, then they all should?
To be clear, I mean the layout colours outside of the List, Edit, Create and Show components and in the actual Layout components i.e. the appbar background is the wrong colour, as is the sidebar etc. So how can they be right for List but wrong for Edit, Create and Show pages when it uses the same layout component for each in the resource? I don't think I have any way of controlling this?
Environment
React-admin version: 3.7.1
Last version that did not exhibit the issue (if applicable): n/a
React version: 17.0.2
Browser: all
Stack trace (in case of a JS error): n/a
Sooo. Don't mess with Regexs kids, they're dangerous. The issue was related to the different URL structure of create/edit/show vs list and therefore the theme matching didn't work..

How to extend material-ui component style to another react component from differently library?

For example:
import { CssBaseline, ThemeProvider, Typography } from "#material-ui/core"; // MUI **Typography** component
import { Text, RichText } from "#some-library/core"; // some-library's **Text** component
const theme = createMuiTheme({
overrides: {
typography: {
h1: {
fontFamily: Garamond,
fontSize: pxToRem(56),
lineHeight: pxToRem(64),
letterSpacing: pxToRem(-0.5),
[breakpoints.up("sm")]: {
fontSize: pxToRem(72),
lineHeight: pxToRem(76)
},
[breakpoints.up("md")]: {
fontSize: pxToRem(104),
lineHeight: pxToRem(112)
},
[breakpoints.up("lg")]: {
fontSize: pxToRem(120),
lineHeight: pxToRem(128),
letterSpacing: pxToRem(-0.75)
}
}
}
}
});
function App() {
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<Typography variant="h1">MUI Typography Heading</Typography>
<Text tag="h1">Library B text component</Text>
</ThemeProvider>
);
}
All I want is to apply the same styles of the <Typography> for the <Text> component.
I know can I can add className to the other component like <Text className={"MuiTypography-root MuiTypography-h1"} tag="h1">. Is that the right approach? And if I do like this would the props of components also be used in the component? Like <Text variant="h2"> and would it take all the responsiveness of Typography too?
Can the styles of MUI component be extended to another react component with all the other functionality (props etc) using any other way?
I am trying to create a shared (MUI components + some-library components) component library and using MUI as the base library for components and styles. And I have created custom theme for the MUI components but not able to extend these styles to the components of another library which too has few basic components (text field, buttons, form elements etc).
Please let know if the question makes sense as I am very new to react and MUI. Thanks much.
Ciao, for what I know, the answer should be no: you cannot extend material-ui component style to another component from different library. Because a component from another library would not accepts the same Material UI props (in your example, variant="h1" would be not recognized as valid props in other component except Material UI components).
Of course, as you said, you could assign the same Material UI class to another component and that component will follow the same style:
<Text className={"MuiTypography-root MuiTypography-h1"} tag="h1"> // this work
Or you could extend a Material UI component. Suppose that you want to make your own Text, you could extend Typography component using withStyles like:
import { withStyles } from "#material-ui/core/styles";
const MyText = withStyles((theme) => ({
root: {
color: "green" // in this case, MyText will have a green text color
}
}))((props) => <Typography {...props} />);
But, as you can see, I extended a Typography component, not a component coming from another library. Here a codesandbox example.
As I said, this is what I know. Maybe there is a way to achieve what you asked (even if I have some doubts because how to map Material UI props to be accepted by another component that has different props?)

How do I globally override variant, color, style, etc. for Material-UI components?

Instead of doing this everywhere:
<Button variant="contained" color="primary"
style={{textTransform: "none"}}
>
Description
</Button>
I just want to write:
<Button>
Description
</Button>
Can I use theme overrides to do this and what would that look like?
Note that I'm trying to override both Material-UI properties and CSS styles. I want to do this globally (i.e. not using withStyles() stuff everywhere).
Or can this only be done by defining some kind of new AppButton component?
Currently using material-ui 3.2.2
You can do this with global overrides for your theme.
Documentation is here https://material-ui.com/customization/themes/#customizing-all-instances-of-a-component-type
Doing it this way will still allow you override the variant on a per component basis as well.
const theme = createMuiTheme({
props: {
// Name of the component ⚛️
MuiButton: {
// The properties to apply
variant: 'contained'
},
},
});
Here's an alternate way to do this, without defining a new component.
Custom components can be awkward when used with Material-UI's JSS styling solution with Typescript. I've found it difficult to define WithStyle types when combining style types from the shared component and the thing using it.
Instead of defining components, it's possible to define sets of default properties that you then apply with the spread operator.
Define and export a standard set of shared props somewhere out in your app:
import {LinearProgressProps} from "#material-ui/core/LinearProgress";
export const linearProps: LinearProgressProps = {
variant:"indeterminate",
color:"primary",
style:{height:"2px"},
};
Then use those props in your app:
<LinearProgress {...linearProps} />
This is then easy to override with custom properties, custom inline styles or JSS generated styles:
<LinearProgress {...linearProps} className={classes.customProgress}
color="secondary" style={{...linearProps.style, width: "100%"}} />
For anyone finding this question, assuming there is no Material-UI way to do this, here's my custom button component.
import * as React from "react";
import {Button} from "#material-ui/core";
import {ButtonProps} from "#material-ui/core/Button";
export class AppButton extends React.Component<ButtonProps, {}>{
render(){
let {style, ...props} = this.props;
return <Button {...props} variant="contained" color="primary"
style={{...style, textTransform: "none"}}
>
{this.props.children}
</Button>
}
}
AppButton.muiName = 'Button';

How do you set the ripple color of a material-ui ListItem?

I can seem to find anywhere in the documentation how to go about setting the ripple color on a material-ui ListItem. I have the ListItem wrapped in a MuiThemeProvider with my overridden theme like this:
const muiTheme = getMuiTheme({
palette: {
hoverColor: 'red',
},
});
<MuiThemeProvider muiTheme={muiTheme}>
<ListItem>
...
</ListItem>
</MuiThemeProvider>
What palette color property should I set to change the ripple color?
The ripple effect comes from a child component called TouchRipple. Specifically, the ripple color comes from the background-color of an element which is selectable using the MuiTouchRipple-child class. The ripple color is currentColor by default, but can be overridden easily.
Note that this works for any button-based component, not just ListItem.
Examples:
Styled Components API:
const MyListItem = styled(ListItem)`
.MuiTouchRipple-child {
background-color: red;
}
`;
Hook API:
const useStyles = makeStyles({
root: {
'.MuiTouchRipple-child': {
backgroundColor: 'red';
}
}
});
const MyListItem = () {
const classes = useStyles();
return <ListItem button className={classes.root}>Hook</ListItem>;
}
Global CSS:
.MuiListItem-root .MuiTouchRipple-child {
background-color: red;
}
I got here working on a similar issue on the Button, but it appears to be consistent across ripple effect, so perhaps this will help someone in the future.
In Material-UI next/v1, the rippleColor is linked explicitly to the label color of the element. If you want the ripple and label to be different colors, you have to override the label color separately.
import MUIButton from 'material-ui/Button';
import {withStyles} from 'material-ui/styles';
const Button = (props) => {
return <MUIButton className={props.classes.button}>Hat</MUIButton>
const styles = {
button: {color: 'rebeccapurple'}
};
export default withStyles(styles)(Button);
This should get you an overridden ripple color.
This is how you can globally change ripple color to red.
import React from "react";
import { createMuiTheme, ThemeProvider } from "#material-ui/core/styles";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemText from "#material-ui/core/ListItemText";
const theme = createMuiTheme({
overrides: {
// Style sheet name
MuiTouchRipple: {
// Name of the rule
child: {
// Some CSS
backgroundColor: "red"
}
}
}
});
const App = () => {
return (
<ThemeProvider theme={theme}>
<List>
<ListItem button>
<ListItemText primary="Item One" />
</ListItem>
<ListItem button>
<ListItemText primary="Item Two" />
</ListItem>
{/* <ListItem button> ... </ListItem> */}
</List>
</ThemeProvider>
);
};
export default App;
Play with the code in CodeSandBox.
Useful links:
Here's what the theme object looks like with the default values.
The overrides key enables you to customize the appearance of all instances of a component type.
You're on the right track! To change the ripple color, your theme should be:
const muiTheme = getMuiTheme({
ripple: {
color: 'red',
},
});
...however, that changes the ripple color for most of the material-ui components, not just ListItem. You can change the ripple color directly on the ListItem with the focusRippleColor and touchRippleColor properties:
<ListItem focusRippleColor="darkRed" touchRippleColor="red" primaryText="Hello" />
If you want to change the color of the ripple effect it can be done through the theme like you tried to do.
In the theme you can change the TouchRippleProps's classes and define your color in the CSS style you have.
import React from 'react';
import { createMuiTheme, ThemeProvider, Button } from '#material-ui/core';
const theme= createMuiTheme({
props:{
MuiButtonBase: {
TouchRippleProps: {
classes: {
root: 'CustomizeTouchRipple'
}
}
}
}
});
export default function App() {
return (
<ThemeProvider theme={theme}>
<Button>Click</Button>
</ThemeProvider>
);
}
And in the CSS style file:
.CustomizeTouchRipple {
color: red;
}
Simple as that.
Update 1:
Instead of using a CSS class style you can directly put style: {color: red[500]}.
It seems like in the current version I need to use the sx prop on <ListItem />.
<ListItem
sx={(theme) => ({
"& .MuiTouchRipple-child": {
backgroundColor: `${theme.palette.primary.main} !important`,
},
})}
>
Content
</ListItem>
this worked for me:
.mat-radio-button.mat-accent .mat-radio-inner-circle,
.mat-radio-button.mat-accent .mat-radio-ripple .mat-ripple-element,
.mat-radio-button.mat-accent .mat-radio-ripple .mat-ripple-element:not(.mat-radio-persistent-ripple),
.mat-radio-button.mat-accent:active .mat-radio-persistent-ripple {
background-color: mat-color($my-gray, 500);
}