material-ui overwrite theme with useStyles / jss - javascript

How can you override the Material-UI theme using styles without using !important?
const theme = createMuiTheme({
overrides: {
MuiInputBase: {
input: {
background: '#dd7711',
padding: 10,
},
},
},
},
})
export default makeStyles(theme => ({
hutber: {
background: '#000',
color: '#fff',
},
}))
function SpacingGrid() {
const classes = useStyles()
return <MuiThemeProvider theme={theme}><Input label="Outlined" variant="outlined" className={classes.hutber} /></MuiThemeProvider>
}
Output:
As you can see, the only way to override the styles are be creating another theme :O I would like to know if styles

The reason the override was not working was because specifying the className prop is equivalent to specifying the root CSS class for Input, but your theme overrides are on the input CSS class which is applied to a different element (the root element is a div, the input element is an <input> element within that div).
In my example below, you can see two different approaches for targeting the <input> element. The first approach uses a nested selector to target .MuiInputBase-input. The second approach uses the classes prop (instead of className) and provides the overrides as the input CSS class.
import React from "react";
import ReactDOM from "react-dom";
import {
createMuiTheme,
MuiThemeProvider,
makeStyles
} from "#material-ui/core/styles";
import Input from "#material-ui/core/Input";
const theme = createMuiTheme({
overrides: {
MuiInputBase: {
input: {
background: "#dd7711",
padding: 10
}
}
}
});
const useStyles = makeStyles(theme => ({
hutber: {
"& .MuiInputBase-input": {
background: "#000",
color: "#fff"
}
},
alternateApproach: {
background: "#000",
color: "#fff"
}
}));
function App() {
const classes = useStyles();
return (
<MuiThemeProvider theme={theme}>
<Input defaultValue="Without overrides" variant="outlined" />
<br />
<br />
<Input
defaultValue="With overrides"
variant="outlined"
className={classes.hutber}
/>
<br />
<br />
<Input
defaultValue="Alternate approach"
variant="outlined"
classes={{ input: classes.alternateApproach }}
/>
</MuiThemeProvider>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Related

using theme.spacing inside theme definition

I have the following theme setup:
export const themeDefault = createTheme({
themeName: 'Default (Mortgage Hub)',
spacing: 4,
...typography,
palette,
components: {
MuiButton: {
styleOverrides: {
root: {
paddingLeft: theme.spacing(12),
paddingRight: theme.spacing(4),
border: '10px',
},
},
},
},
})
And I would like to use theme.spacing inside however, of course. Theme is not defined yet. I have tried to use makeStyles & useTheme however these are hooks. So of course they will not work.
Are you able to supply styleOverrides once the theme has been indicated?
The styleOverrides feature of the theme is extremely powerful and versatile. In the Overrides based on props portion of the documentation, you can find an example of specifying a callback within the styleOverrides which then gives you access to the component's props and state (via ownerState) and the theme.
Below is an example showing how the callback syntax gives you access to the theme as well as showing how you can leverage ownerState to do conditional styles based on the props passed to a component (e.g. based on whether or not startIcon was specified or which variant was used or any combination of interest).
import * as React from "react";
import Stack from "#mui/material/Stack";
import Button from "#mui/material/Button";
import { createTheme, ThemeProvider } from "#mui/material/styles";
import SaveIcon from "#mui/icons-material/Save";
const theme = createTheme({
themeName: "Default (Mortgage Hub)",
spacing: 4,
components: {
MuiButton: {
styleOverrides: {
root: ({ ownerState, theme }) => {
const conditionalStyles = {};
if (ownerState.startIcon !== undefined) {
conditionalStyles.paddingLeft = theme.spacing(6);
if (ownerState.variant === "outlined") {
conditionalStyles.border = "5px solid black";
conditionalStyles["&:hover"] = { border: "5px solid grey" };
}
} else {
conditionalStyles.paddingLeft = theme.spacing(4);
}
return {
...conditionalStyles,
paddingRight: theme.spacing(4)
};
}
}
}
}
});
export default function BasicButtons() {
return (
<ThemeProvider theme={theme}>
<Stack spacing={2} direction="row">
<Button variant="text">Text</Button>
<Button variant="contained">Contained</Button>
<Button variant="outlined">Outlined</Button>
<Button startIcon={<SaveIcon />} variant="contained">
Contained with startIcon
</Button>
<Button startIcon={<SaveIcon />} variant="outlined">
Outlined with startIcon
</Button>
</Stack>
</ThemeProvider>
);
}

How to pass styles via props with styled components without having to create the component inside the component

I cannot find another way to give my props dynamic styles without creating the styled-component inside the Text component. For performance I'd like to create the component outside of the Text component.
Styled Component
import React, { ReactChildren } from 'react'
import styled from 'styled-components'
export const Text = ({ as = 'p', children, styles = {} }) => {
const newStyles = Object.entries(styles).reduce((acc, [key, value]) => {
return `${acc}${key}: ${value};`
}, '')
const Component = styled.p`
${newStyles}
color: #000;
`
return <Component as={as}>{children}</Component>
}
Desired Usage
<Text styles={{ height: "10px" }} />
Output HTML
<p styles="height: 10px" />
The above code is confusing in that you want to apply styles to the DOM style property, but you're also applying them as CSS styles to a classname. There's no need to create this composed component because styled-components already handles as, children, and style properties without composing:
Example 1:
import React from "react";
import styled, { Interpolation } from "styled-components";
export type TextProps = {
as?: string | React.ComponentType<any>;
children: React.ReactNode;
styles?: Interpolation<React.CSSProperties>;
};
const Component = styled.p<TextProps>`
${({ styles }) => styles}
color: #000;
`;
export const Text = ({
as,
children,
styles
}: TextProps): React.ReactElement => (
<Component styles={styles} as={as}>
{children}
</Component>
);
export default Text;
Example 2:
import styled from "styled-components";
const Text = styled.p`
color: #000;
`;
export default Text;
Example 3:
import * as React from "react";
import styled from "styled-components";
export type TextComponentProps = {
className?: string;
children: React.ReactNode;
styles?: React.CSSProperties;
};
const TextComponent = ({
children,
className,
styles
}: TextComponentProps): React.ReactElement => (
<p className={className} style={styles}>
{children}
</p>
);
const Text = styled(TextComponent)`
color: #000;
`;
export default Text;
Usage:
import * as React from "react";
import Box from "./Box";
import Text from "./Text";
import Text2 from "./Text2";
import Text3 from "./Text3";
import "./styles.css";
const App = (): React.ReactElement => (
<div className="app">
<h1>Example 1 - "styles" as CSS Styles</h1>
<Box>
<Text styles={{ height: "10px" }}>Hello</Text>
<Text as="h1">Goodbye</Text>
</Box>
<hr />
<h1>Example 2 - "style" as DOM styles</h1>
<Box>
<Text2 style={{ height: "10px" }}>Hello</Text2>
<Text2 as="h1">Goodbye</Text2>
</Box>
<hr />
<h1>Example 3 - "styles" as DOM styles</h1>
<Box>
<Text3 styles={{ height: "10px" }}>Hello</Text3>
<Text3 as="h1">Goodbye</Text3>
</Box>
</div>
);
export default App;
Output:
On that note, it sounds like you might be trying to do something I did with composabled-styled-components, although I wouldn't recommend it because it doesn't work with SSR apps.
You could create a ref on the styled component in order to set the style in an effect like this:
const Component = styled.p`
background-color: red;
`;
const Text = ({ as = 'p', children, styles = {} }) => {
const newStyles = Object.entries(styles).reduce((acc, [key, value]) => {
return `${acc}${key}: ${value};`
}, '')
const ref = React.createRef();
React.useEffect(() => {
ref.current.style = newStyles;
}, []);
return <Component ref={ref} as={as}>{children}</Component>
}
ReactDOM.render(<Text styles={{ height: "10px" }}>This paragraph is 10px high</Text>, document.getElementById('root'));
<script crossorigin src="https://unpkg.com/react#17/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.production.min.js"></script></script>
<script src="https://unpkg.com/react-is#17/umd/react-is.production.min.js"></script>
<script src="https://unpkg.com/styled-components/dist/styled-components.min.js"></script>
<div id="root"></div>

React How to conditionally override TextField error color in Material-UI?

I'm using React Material-UI library and I want to conditionally override the error color of a TextField.
I need to change the helperText, border, text and required marker color to yellow when the error is of a certain type. Something like that :
Otherwise, I want to keep the default color(red) for every other type of error.
I tried to follow the same principle used in this codesandbox but I couldn't get a grip of all the components that I needed to change and I had to use the important keyword almost every time to see a difference.
I have managed to conditionally change the color of the helperText like so :
<TextField
label="Name"
className={formClasses.textField}
margin="normal"
variant="outlined"
required
error={!!errors}
helperText={errors && "Incorrect entry."}
FormHelperTextProps={{classes: {root: getColorType(AnErrorType)}}}
/>
The getColorType will return a CSS object with the property color set to the one that corresponds the given error type. ex:
hardRequiredHintText: {
color: `${theme.palette.warning.light} !important`
},
Is there an easier way to override MUI error color and to see it reflected in all the component that uses it?
For each type of validation, display a different color, we can pass params to makeStyles
import { makeStyles } from "#material-ui/core/styles";
const useStyles = params =>
makeStyles(theme => ({
root: {
}
}));
const Component = () => {
const classes = useStyles(someParams)();
Full code:
import React from "react";
import "./styles.css";
import { TextField } from "#material-ui/core";
import { makeStyles } from "#material-ui/core/styles";
const useStyles = value =>
makeStyles(theme => ({
root: {
"& .Mui-error": {
color: acquireValidationColor(value)
},
"& .MuiFormHelperText-root": {
color: acquireValidationColor(value)
}
}
}));
const acquireValidationColor = message => {
switch (message) {
case "Incorrect entry":
return "green";
case "Please input":
return "orange";
default:
return "black";
}
};
const ValidationTextField = ({ helperText }) => {
const classes = useStyles(helperText)();
return (
<TextField
label="Name"
margin="normal"
variant="outlined"
required
error={helperText !== ""}
helperText={helperText}
className={classes.root}
/>
);
};
export default function App() {
const data = ["Incorrect entry", "Please input", ""];
return (
<div className="App">
{data.map((x, idx) => (
<ValidationTextField helperText={x} key={idx} />
))}
</div>
);
}
For Class Based Components
import React from "react";
import { TextField } from "#material-ui/core";
import { withStyles, createStyles } from "#material-ui/core/styles";
const commonStyles = (theme) =>
createStyles({
root: {},
warningStyles: {
"& .MuiFormLabel-root.Mui-error": {
color: "orange !important"
},
"& .MuiInput-underline.Mui-error:after": {
borderBottomColor: "orange !important"
},
"& .MuiFormHelperText-root.Mui-error": {
color: "orange !important"
}
}
});
class DemoComponent extends React.Component {
render() {
const { classes } = this.props;
const _text1HasWarning = false;
const _text2HasWarning = true;
const _text3HasWarning = false;
return (
<>
<TextField
error={false}
className={_text1HasWarning ? classes.warningStyles : null}
value="Valid Value"
variant="standard"
label="Valid label"
helperText="Valid helper text"
/>
<br />
<br />
<br />
<TextField
error={true}
className={_text2HasWarning ? classes.warningStyles : null}
value="warning value"
variant="standard"
label="warning label"
helperText="warning helper text"
/>
<br />
<br />
<br />
<TextField
error={true}
className={_text3HasWarning ? classes.warningStyles : null}
value="error value"
variant="standard"
helperText="error helper text"
label="error label"
/>
</>
);
}
}
export default withStyles(commonStyles)(DemoComponent);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Output
You can accomplish this by overriding your Material-UI theme default styles and then wrapping your text field or your component inside of myTheme
import { createMuiTheme } from 'material-ui/styles';
const myTheme = createMuiTheme({
overrides:{
MuiInput: {
underline: {
'&:after': {
backgroundColor: 'any_color_value_in_hex',
}
},
},
}
});
export default myTheme;
and then import it into your component and use:
import {MuiThemeProvider} from 'material-ui/styles';
import myTheme from './components/myTheme'
<MuiThemeProvider theme = {myTheme}>
<TextField />
</MuiThemeProvider>
I hope it helps you.

How to disable primary color for FormLabel when radio button group is focused?

When form control is focused then FormLabel is highlighted.How to disable primary color for FormLabel when radio button group is focused and use black instead?
const styles = {
formLabel: {
color: "#000"
},
formLabelFocused: {
color: "#000"
}
};
function App({ classes }) {
return (
<FormControl>
<FormLabel
classes={{ root: classes.formLabel, focused: classes.formLabelFocused }}
>
Options
</FormLabel>
<RadioGroup>
{options.map(option => {
const { value, label } = option;
return (
<FormControlLabel
control={<Radio />}
key={value}
value={value}
label={label}
/>
);
})}
</RadioGroup>
</FormControl>
);
}
example https://codesandbox.io/s/st-of-radio-31o2x
When an attempt to override Material-UI's default styles doesn't work, the next step is to look at how the default styles are defined.
Below is an excerpt from FormLabel.js showing how the focused styling is defined:
export const styles = theme => ({
/* Styles applied to the root element. */
root: {
color: theme.palette.text.secondary,
'&$focused': {
color: theme.palette.primary.main,
},
},
/* Pseudo-class applied to the root element if `focused={true}`. */
focused: {},
});
The effect of this is for the focused color to be specified in a CSS rule like:
.MuiFormLabel-root.Mui-focused {
color: #3f51b5;
}
The effect of your override attempt would be more like:
.Mui-focused {
color: #000;
}
The default styling uses .Mui-focused along with .MuiFormLabel-root in order to ensure that the focused styling has higher CSS specificity than the non-focused styling. Your override, however, has lower specificity than the default focused styling.
Here is a modified version of your sandbox that works:
import React from "react";
import {
FormControl,
FormLabel,
RadioGroup,
Radio,
FormControlLabel,
withStyles
} from "#material-ui/core";
const options = [...Array(4).keys()].map(item => {
return { value: `value ${item}`, label: `label ${item}` };
});
const styles = {
formLabel: {
color: "#000",
"&.Mui-focused": {
color: "#000"
}
}
};
function App({ classes }) {
return (
<FormControl>
<FormLabel classes={{ root: classes.formLabel }}>Options</FormLabel>
<RadioGroup>
{options.map(option => {
const { value, label } = option;
return (
<FormControlLabel
control={<Radio />}
key={value}
value={value}
label={label}
/>
);
})}
</RadioGroup>
</FormControl>
);
}
export default withStyles(styles)(App);
Related references:
https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity
https://github.com/mui-org/material-ui/blob/v4.8.3/packages/material-ui/src/FormLabel/FormLabel.js#L17
https://cssinjs.org/jss-plugin-nested/?v=v10.0.3#use--to-reference-selector-of-the-parent-rule
you can add !important.
const styles = {
formLabel: {
color: "#000 !important"
},
formLabelFocused: {
color: "#000"
}
};

How can I change the label size of a material ui TextField?

I have a TextField defined as follows:
<TextField
id="standard-with-placeholder"
label="First Name"
className={classes.textField}
margin="normal"
/>
And it looks like this:
But I want it look like this:
The "First Name" text is larger. How can I adjust the label text size? Currently my styles object is empty. I think that should be where the CSS to do this should go, but I'm unsure what the css would look like for the label text.
Thanks
Here's an example of how to modify the label font size in v4 of Material-UI (v5 example further down). This example also modifies the font-size of the input on the assumption that you would want those sizes to be in sync, but you can play around with this in the sandbox to see the effects of changing one or the other.
import React from "react";
import ReactDOM from "react-dom";
import TextField from "#material-ui/core/TextField";
import { withStyles } from "#material-ui/core/styles";
const styles = {
inputRoot: {
fontSize: 30
},
labelRoot: {
fontSize: 30,
color: "red",
"&$labelFocused": {
color: "purple"
}
},
labelFocused: {}
};
function App({ classes }) {
return (
<div className="App">
<TextField
id="standard-with-placeholder"
label="First Name"
InputProps={{ classes: { root: classes.inputRoot } }}
InputLabelProps={{
classes: {
root: classes.labelRoot,
focused: classes.labelFocused
}
}}
margin="normal"
/>
</div>
);
}
const StyledApp = withStyles(styles)(App);
const rootElement = document.getElementById("root");
ReactDOM.render(<StyledApp />, rootElement);
Below is an equivalent example for v5 of Material-UI using styled instead of withStyles:
import React from "react";
import ReactDOM from "react-dom";
import MuiTextField from "#material-ui/core/TextField";
import { inputClasses } from "#material-ui/core/Input";
import { inputLabelClasses } from "#material-ui/core/InputLabel";
import { styled } from "#material-ui/core/styles";
const TextField = styled(MuiTextField)(`
.${inputClasses.root} {
font-size: 30px;
}
.${inputLabelClasses.root} {
font-size: 30px;
color: red;
&.${inputLabelClasses.focused} {
color: purple;
}
}
`);
function App() {
return (
<div className="App">
<TextField
id="standard-with-placeholder"
label="First Name"
margin="normal"
variant="standard"
/>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Here are the relevant parts of the documentation:
https://material-ui.com/api/text-field/
https://material-ui.com/api/input/
https://material-ui.com/api/input-label/
https://material-ui.com/api/form-label/
You can target the textfield label like this:
.MuiFormLabel-root {
font-size: 20px !important;
}

Categories