MUI: customize button component with tailwind classes - reactjs

I'm trying to customise the look of MUI's <Button /> component. As i want to do it by using tailwind classes i tried to use defaultProps instead of styleOverrides.
const lightTheme = createTheme({
components: {
MuiButton: { defaultProps: { classes: { root: 'p-8' } } },
},
});
It is working fine except that i'm not able to add tailwind classes when i use the <Button /> component as this will overwrite the changes i made above.
<Button variant="contained" classes={{ root: 'p-12' }}>
Ok
</Button>
Is there a way to extend the definition of the classes prop rather than overwriting it?

Related

How do I remove the shadows for all Buttons in MUI

I want to remove the shadows for all contained buttons in my custom MUI theme but I don't know how to go about it so far. How do I do so? I have tried setting the boxShadow property for each individual Button to Zero but that's not working.
You could use a Button prop named disableElevation (which is false by default)
You could use it per component or set globally in theme setting
In a single component
<Button variant="contained" disableElevation={true}>
a button
</Button>
Globally via theme setting
const theme = createTheme({
components: {
MuiButton: {
defaultProps: {
disableElevation: true
}
}
}
});
Codesandbox demo

MUI 5 using styled components with child component props

I'm converting from MUI 4 to 5. Working on converting from makeStyles() to styled components. Is there an example somewhere using the styled() method with a component that has child component props? For example, <ListItemText /> has primaryTypographyProps and secondaryTypographyProps, both of which I'm setting the inner className property for custom styles.
How does something like this...
<ListItemText
{...props}
primaryTypographyProps={{
variant: 'body2',
className: classes.primary,
}}
secondaryTypographyProps={{
variant: 'body1',
className: classes.secondary,
}}
/>
...convert to something like this?
const StyledListItemText = styled(ListItemText)(({ theme }) => ({
...???...
}));
[Edit] This is the closest I've been able to find, but it's not quite there. What I'm trying to do is pass it through a props object, rather than whole components.
I haven't had a chance to verify if this works, but I'm assuming this is the direction I need to go with this:
const StyledListItemText = styled(ListItemText)(() => ({
'MuiListItemText-primary': { ... },
'MuiListItemText-secondary': { ... },
}));
You are very close!
import listItemTextClasses from '#mui/material';
...
const StyledListItemText = styled(ListItemText)(() => ({
[`& .${listItemTextClasses.primary}`]: {...}
[`& .${listItemTextClasses.secondary}`]: {...}
}));
https://mui.com/base/getting-started/customization/#applying-custom-css-rules

Material UI react testing custom styles

I'm struggling to come up with a sensible way of asserting that a given style will be added to a Material-UI component when a given prop is specified
import makeStyles from "#material-ui/core/styles/makeStyles";
const useStyles = makeStyles({
myRoot: props => props.inverse && {
border: "2px solid red",
},
})
const MyTextInput = ({ inverse, ...otherProps }) => {
const classes = useStyles({ inverse });
return (
<TextField
label="Standard secondary"
color="secondary"
classes={{
root: classes.myRoot,
}}
/>
);
}
// with inverse
<MyTextInput
label="Standard secondary"
color="secondary"
inverse={true}
/>
// without inverse prop
<MyTextInput
label="Standard secondary"
color="secondary"
/>
Is there a sensible way to test that the correct class has been applied?
I notice that when I render the component multiple times, the className + a number will be added to all instances e.g. makeStyles-myRoot-*.
Is there way of confirming that the correct style has been applied?
I'm using react-testing-library if that helps.

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

react-rte (Rich Text Editor): how to implement inline style for custom components?

react-rte is a Rich Text Editor based on draft-js. My goal is to customize the toolbar components with, e.g., material ui react components. Reading through the react-rte docs, I think that there are two styling hooks:
toolbarConfig for CSS (link); and
customControls for completely overriding components (as seen in demo).
I believe that my use case calls for customControls, but from the provided demo (see below) I am not able to understand how to hook the custom components back into rte's functionality. For example, if I render a custom button component for BOLD, how does this button get the default functionality that would have gone to the default button provided by toolbarConfig?
editor demo with customControls:
<RichTextEditor
value={value}
onChange={this._onChange}
className="react-rte-demo"
placeholder="Tell a story"
toolbarClassName="demo-toolbar"
editorClassName="demo-editor"
readOnly={this.state.readOnly}
customControls={[
// eslint-disable-next-line no-unused-vars
(setValue, getValue, editorState) => {
let choices = new Map([
['1', {label: '1'}],
['2', {label: '2'}],
['3', {label: '3'}],
]);
return (
<ButtonGroup key={1}>
<Dropdown
choices={choices}
selectedKey={getValue('my-control-name')}
onChange={(value) => setValue('my-control-name', value)}
/>
</ButtonGroup>
);
},
<ButtonGroup key={2}>
<IconButton
label="Remove Link"
iconName="remove-link"
focusOnClick={false}
onClick={() => console.log('You pressed a button')}
/>
</ButtonGroup>,
]}
/>
my currently invalid implementation:
<RichTextEditor
value={this.state.value}
onChange={this.onChange}
customControls={rteCustomControls}
/>
...
const inlineStyleButtonControls = [
{ label: "format_bold", style: "BOLD", component: FormatBoldIcon },
{ label: "format_italic", style: "ITALIC", component: FormatItalicIcon },
{
label: "format_underlined",
style: "UNDERLINE",
component: FormatUnderlinedIcon,
},
];
const rteCustomControls = [
(setValue, getValue, editorState) => {
return inlineStyleButtonControls.map((button, i) => (
<IconButton
key={i}
color="inherit"
aria-label={button.label}
selectedKey={getValue(button.style)}
onClick={value => setValue(button.style, value)}
>
<button.component />
</IconButton>
));
},
];
If your goal is purely to modify the display, you should be able to target the buttons via CSS to change their displays, just like you would target any other DOM element.
Change element display via CSS.
RichTextEditor button:nth-child(1){
background-image: url('/icon1.svg');
}
RichTextEditor button:nth-child(2){
background-image: url('/icon2.svg');
}
Or, change element display via javascript
Array.from(document.querySelectorsAll('RichTextEditor button')).forEach((el)=>{
// Modify element here
})
If you also want to modify the button's functionality, you can look at the code where they are defined, and potentially add them in as custom controls.

Resources