Customizing inner components in Material-UI and styled-components - reactjs

Background:
Our app uses #material-ui/core and we're not ready to upgrade to #mui/material.
Our app uses React 16.14.0 and Node 10.15.3, and we're not ready to upgrade.
We use styled-components and have configured Material UI to work with it.
Now that that's out of the way:
I'm having a lot of trouble targeting the inner components. I want to style the outline of a <TextField variant="outline"/>.
MUI's how-to guide says that the inner components should be discoverable in the devtools with classes like MuiInputBase-input. However, my app is giving me MuiInputBase-input-48, and the number is not guaranteed to stay constant between renders.
So I thought I had it with this, but sometimes I'll refresh and lose it because the classnames will change:
const StyledInput = styled(TextField).attrs(p => ({
inputProps: { inputMode: "numeric", pattern: "[0-9]*", color: "white" },
}))`
.MuiOutlinedInput-root-25 .MuiOutlinedInput-notchedOutline-32 {
border-color: ${colors.lightGrey};
}
`;
const SDCurvedInput = (props) => (
<StyledInput
{...props}
id={props.label}
variant="outlined"
/>
);
As a test I tried replacing StyledInput with simply TextField in the markup because I thought maybe styled-components was doing something to add this number, but no luck.

Related

How to change width of Mui DatePicker v5

I'm trying to change the width of the calendar popup on the Mui DatePicker but can't seem to figure it out. I have changed the width of the input using:
renderInput={(params) => <TextField {...params} sx={{ ...formStyles }} />}
But I want to change the width of the calendar as well. I'm new to working with any component library, so maybe I'm missing something. The docs said you could override the theme with the name MuiDatePicker, but when I try:
const theme = createTheme({
components: {
MuiDatePicker: {},
},
});
I'm getting a typescript error saying that MuiDatePicker isn't assignable to Components
Are you trying to change the width of the input TextField?
I am doing it like so, and it seems to do the trick everytime:
Wrap your DatePicker in a div
<div className={classes.datePicker}>
<DatePicker format="dd/mm/yyyy">
</div>
Override MUI styles of the div's TextField like so:
datePicker: {
"& .MuiTextField-root": {
width: 200,
},
Not sure if that's the best way to do it, but it works for me, also in MUI v5.

Regular ClassName with Material UI

I've read some answers to similar questions but did not fully understand because of materialUI update where #mui/styles are depricated.
According to mui docs we can create custom styling using sx prop, smth like this
<Typography variant="h5" sx={{backgroundColor: 'red' }}>Header</Typography>
But when I use regular classNames in v5 it works well too:
//css
.makeRed {
backgroundColor: red
}
// jsx
<Typography variant="h5" className="makeRed">Header</Typography>
The question is - does using regular classes has pitfalls and I need to rewrite mui classes?
(have started my mUI jorney yesterday)
I've been brushing up on Material for a new job and I've found that if you want to make things more easily changeable and run faster you'll work through the framework. The purpose of Material is for enhanced reusability, so you'll want to work with that purpose and align your style in everything you do within the app towards everything being the same.
If anything your best bet is to do:
const useStyles = makeStyles({
makeRed: {
background-color: 'red'
}
})
export default useStyles;
Then in the actual component just have your classes const as.
const classes = useStyles();
<Typography className = {classes.makeRed} />

Incorrect classnames in Material UI with styled components inside react frame

I am using react-frame-component to load a custom component I created which is using material ui and styled-components.
The custom component I created is irrelevant but contains the following relevant code:
const StyledCardHeader = styled(({ isRTL, ...rest }) => (
<CardHeader {...rest} />
))`
${({ theme, isRTL }) => `
& .MuiCardHeader-title {
margin-right:${isRTL ? 0 : theme.spacing(2)}px;
margin-left: ${isRTL ? theme.spacing(2) : 0}px;
}
`};
`;
When it renders, the actual classname becomes something else than I expect: MuiCardHeader-title-34 (it adds the 34 as suffix - random number).
Therefore, my custom styled is not being applied.
Here's a sandbox of the above:
https://codesandbox.io/s/sparkling-field-ui82v
You can look into overriding the MUI CardHeader CSS based off of the docs.
<CardHeader
{...rest}
classes={{
title: makeStyles({
customTitleMargin: {
marginRight: `${isRTL ? 0 : theme.spacing(2)}px`,
marginLeft: `${isRTL ? theme.spacing(2) : 0}px`,
}
})().customTitleMargin
}}
/>
I did not want to mess with your code too much so in my example, I just plugged in the makeStyles export from #material-ui/styles, but the logic of future implementations is similar in that you should just override the MUI component itself.
CodeSandBox: https://codesandbox.io/s/lucid-joliot-5mf8n?file=/src/Card.js

Altering multiple component roots for MUI Textfield

According to the MUI Texfield API here, Textfield is a simple abstraction on top of the following components
FormControl
Input
InputLabel
FilledInput
OutlinedInput
Input
FormHelperText
And therefore, to change the styling of the Textfield for any of the components above, like for example notchedOutline class, which is a class for OutlinedInput, i can just do the following
import { TextField } from '#material-ui/core';
const style = theme => ({
notchOutline: { /*style in here*/ }
});
<TextField
inputProps={{ notchedOutline : classes.notchedOutline }}
>
</TextField>
All of this can be achieved if that subcomponent classes are unique for that component only.
My question is, how can i style for the more common naming class, like if say i wanna modify the root classes of OutlinedInput, InputLabel, FormHelperText or more subcomponents inside the TextField all at once? I dont think this will work right?
<TextField
FormControlProps={{ root: classes.root }}
OutlinedInputProps={{ root: classes.root, notchedOutline : classes.notchedOutline }}
>
</TextField>
or
<TextField
inputProps={{
root: classes.OutlinedInputRoot,
root : classes.FormHelperTextRoot
}}
>
</TextField>
Need help on how to aim the specific root of a subcomponent of a TextField, without needing to touch on the global MUI theming, or not using the provided TextField at all, instead building the textfield component using those subcomponents on it.
Below is an example showing how to target each of these.
Targeting TextField root is equivalent to targeting FormControl, since FormControl is the "root" component rendered by TextField.
There is no difference in how to target Input, FilledInput, or OutlinedInput -- they are all reached via InputProps.
As a side note, using the className prop for a given component is also equivalent to classes.root.
import React from "react";
import ReactDOM from "react-dom";
import TextField from "#material-ui/core/TextField";
import { makeStyles } from "#material-ui/core/styles";
const useStyles = makeStyles({
formControlRoot: {
border: "2px solid lightgreen",
padding: 2,
marginTop: 10
},
inputRoot: {
border: "2px solid blue"
},
inputLabelRoot: {
border: "2px solid pink"
},
formHelperTextRoot: {
border: "2px solid red"
}
});
function App() {
const classes = useStyles();
const [variant, setVariant] = React.useState("standard");
return (
<div>
<TextField
variant={variant}
label={`My Label (${variant})`}
helperText="My Helper Text"
classes={{ root: classes.formControlRoot }}
InputProps={{ classes: { root: classes.inputRoot } }}
InputLabelProps={{ classes: { root: classes.inputLabelRoot } }}
FormHelperTextProps={{ classes: { root: classes.formHelperTextRoot } }}
/>
<br />
<br />
<button onClick={() => setVariant("standard")}>Standard</button>
<button onClick={() => setVariant("outlined")}>Outlined</button>
<button onClick={() => setVariant("filled")}>Filled</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Relevant documentation: https://material-ui.com/api/text-field/#props
answering this will depend specifically on the material ui version you are using, but i'm going to assume you are using version > 3.
All the following should be working in version 3.9 as i have used them myself, but they are also supposed to work fine with version > 4;
input props are used to pass props to the underlying regular html input element directly, you can pass things like style, max, value, onchange. Things that are native to the html input element.
If you want to pass classes to the underlying material ui input you need to pass a classes object to the InputProps.
Here is how
<TextField
variant="outlined"
// this passes props to the html element, root for example here does not mean anything
inputProps={{
style: { textAlign: 'center' },
}
// this passes props to the wrapper material input, can be one of the following: Input, FilledInput, OutlinedInput
// You can pass here anything that the underlying material component uses but we are only interested in two things classes and className, because other props like value and onChange you can pass directly to TextField - thats why they said TextField is an Abstraction over theses Components
InputProps={{
className: styles.slider_filter_input, // usually you dont need className, classes will be sufficient, but wanted to show that you can also use it
classes: {
focused: classes.focused
// the list of keys you pass here depend on your variant, if for example you used variant="outlined" then you need to check the css api of the OutlinedInput
}
}}
/>
Finally here is a working codesandbox showing the ideas above https://codesandbox.io/s/material-ui-drawer-8p6wv

Set a style property in reactjs for all components of same type

I am getting started with ReactJS and MaterialUI and I am struggling to understand how styling is so different than using CSS.
I mean, in CSS you can define a global rule for all input or select elements, but in ReactJS looks like you cannot do it and you have to pass the class to all elements one by one. I am sure I am missing something, but I haven't found what.
For example, if I have 10 TextField (in fact I am using TextInput from react-admin) in a Form, I would like the 10 of them have the same width without having to declare a style object, pass it using withStyles(style) HOC and set className={classes.input} one by one.
The short answer is - there isn't an easy way to do what you want here.
You have a few options:
Just add the class to each of your components
const MyForm = ({classes}) => (
<Form>
<TextInput className = {classes.textInput} />
<TextInput className = {classes.textInput}/>
<TextInput className = {classes.textInput}/>
</Form>
)
const styles = {
textInput: {
color: "red"
}
}
The downside of this is that it is repetitive.
Style the dom element input directly using a nested selector
const MyForm = ({classes}) => (
<Form className={classes.root}>
<TextInput/>
<TextInput/>
<TextInput/>
</Form>
)
const styles = {
root: {
"& input" {
color: "red",
}
}
}
This here will style the input dom element directly - not the React component itself. The downside of this is that you might end up styling inputs that you didn't intend to. However, I've found this the best way to do things like styling tables etc.
Create your own library of higher order components to wrap the Material-UI components
See this Software Engineering question of mine.
const MyTextInput = ({classes, ...rest}) => (
<TextInput className = {classes.root} ...rest/>
)
const styles = {
root: {
color: "red",
}
}
const MyForm = ({classes}) => (
<Form>
<MyTextInput/>
<MyTextInput/>
<MyTextInput/>
</Form>
);
This does seem like unnecessary boilerplate etc, but I think in the long run - this is the best approach for a project that is going to be around for a while - in terms of maintaining a consistent design schema across the whole application.

Resources