How to create deterministic styles when using createStyles() - reactjs

I have create a component that needs to have custom styles, so I used createStyles({}). This seems to have worked (almost) as I want it to. I have also used createGenerateClassName({}) to indicate I need deterministic style names. However, the two do not seem to be working together. While the standard MUI components no longer have the hash number as part of the class name, the custom styles do. What need to change to support deterministic styles for every class name?
Here is the code I have:
import {Component, ComponentMeta, ComponentProps, SizeObject} from '#xyz/abc' // real name removed here due to restrictions
import {Button, Paper} from '#material-ui/core'
import {createGenerateClassName, createStyles, MuiThemeProvider, Theme, withStyles} from '#material-ui/core/styles'
import JssProvider from 'react-jss/lib/JssProvider'
const theme = createMuiTheme({
palette: {
primary: {
main: 'blue',
},
secondary: {
main: 'green',
},
error: {
main: 'red',
},
},
typography: {
useNextVariants: true,
},
})
const styles = ({palette, spacing}: Theme) =>
createStyles({
button: {
backgroundColor: '#2196f3',
},
buttonDark: {
backgroundColor: '#880e4f',
},
buttonLight: {
backgroundColor: '#e1bee7',
},
})
const generateClassName = createGenerateClassName({
dangerouslyUseGlobalCSS: true,
})
class AnalysisSelector extends Component<ComponentProps, any> {
render() {
const {classes} = this.props
return (
<MuiThemeProvider theme={theme}>
<JssProvider generateClassName={generateClassName}>
<Paper {...this.props.emit()} className={'paperContainer'}>
<Button className={classes.button}>Primary Light</Button>
<Button className={classes.buttonLight}>Primary</Button>
<Button className={classes.buttonDark}>Primary Dark</Button>
</Paper>
</JssProvider>
</MuiThemeProvider>
)
}
}
export const MNOAnalysisSelector = withStyles(styles, {name: 'mnoButton'})(AnalysisSelector)
Finally here is the rendered HTML:
<button class="MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-flat mnoButton-button-1" tabindex="0" type="button">
<span class="MuiButton-label">Primary Light</span>
<span class="MuiTouchRipple-root"></span>
</button>
<button class="MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-flat mnoButton-buttonLight-3" tabindex="0" type="button">
<span class="MuiButton-label">Primary</span>
<span class="MuiTouchRipple-root"></span>
</button>
<button class="MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-flat mnoButton-buttonDark-2" tabindex="0" type="button">
<span class="MuiButton-label">Primary Dark</span>
<span class="MuiTouchRipple-root"></span>
</button>
</div>
I am fine with the class names being mnoButton-button, mnoButton-buttonLight, and mnoButton-buttonDark, I just need the ending hash removed.
Thanks for any suggestions / assistance.

You can use global class names as documented in v4 here: https://next.material-ui.com/styles/advanced/#jss-plugin-global
jss-plugin-global is included in v3 as well, so the same approach will work with it.
The only way for the other syntax to create global names is if the name passed to withStyles starts with "Mui" (which I wouldn't recommend doing).
I've shown both approaches in the code below.
import React from "react";
import { withStyles } from "#material-ui/core/styles";
import Button from "#material-ui/core/Button";
const styles = theme => ({
"#global": {
".mnoButton-button": {
backgroundColor: "#2196f3"
},
".mnoButton-buttonDark": {
backgroundColor: "#880e4f"
},
".mnoButton-buttonLight": {
backgroundColor: "#e1bee7"
}
},
button: {
backgroundColor: "purple",
color: "white"
}
});
const MyButtons = ({ classes }) => {
return (
<>
<Button className="mnoButton-button">Hello World</Button>
<Button className="mnoButton-buttonDark">Hello World</Button>
<Button className="mnoButton-buttonLight">Hello World</Button>
<Button className={classes.button}>Hello World</Button>
</>
);
};
export default withStyles(styles, { name: "Mui-mnoButton" })(MyButtons);

Source: github
import { StylesProvider, createGenerateClassName } from '#material-ui/core/styles';
// other imports
const generateClassName = (rule, styleSheet) =>
`${styleSheet.options.classNamePrefix}-${rule.key}`;
test(deterministic classnames, () => {
render(
<StylesProvider generateClassName={generateClassName}>
<App />
</StylesProvider>
);
});

Related

React Material-UI and color: warning

I am new to React and MUI and maybe I am just missing something.
I am trying to make a button with color='warning' that is defined in my palette like this (the theme works and I can use primary and secondary colors):
const theme = createMuiTheme({
palette: {
primary: {
main: '#70B657'
},
secondary: {
light: '#2face3',
main: '#4199D8',
contrastText: '#ffcc00'
},
warning: {
main: '#BC211D'
}
}
});
I noticed in the documentation that the <Button> color prop only takes default|inherit|primary|secondary so it is not possible to use it like that.
So what is the CORRECT or best practice to use warning colored button in Material-UI? I think this is a basic thing and should be pretty easy to achieve..??
Preferably a solution that does not involve making several different Themes and importing them when needed.
Thanks!
Usage:
const useStyles = makeStyles(theme => ({
root: {
color: theme.palette.warning.main
}
}));
Full code:
import React from "react";
import "./styles.css";
import { Button } from "#material-ui/core";
import { createMuiTheme, ThemeProvider } from "#material-ui/core/styles";
import { makeStyles } from "#material-ui/core/styles";
const useStyles = makeStyles(theme => ({
root: {
color: theme.palette.warning.main
}
}));
function YourComponent() {
const classes = useStyles();
return (
<div className="App">
<Button variant="contained" classes={{ root: classes.root }}>
Secondary
</Button>
</div>
);
}
const theme = createMuiTheme({
palette: {
warning: { main: "#FFFFFF" }
}
});
export default function App() {
return (
<ThemeProvider theme={theme}>
<YourComponent />
</ThemeProvider>
);
}
Update
Pass props to makeStyles
import React from "react";
import "./styles.css";
import { makeStyles } from "#material-ui/core/styles";
const useStyles = props =>
makeStyles(theme => ({
root: {
color: props.value === "111" ? "red" : "blue"
}
}));
const Comp = props => {
const classes = useStyles(props)();
return <input defaultValue={props.value} className={classes.root} />;
};
export default function App() {
return (
<div className="App">
<div>
<Comp value={"111"} />
</div>
<div>
<Comp value={"222"} />
</div>
</div>
);
}
yeah I don't understand why the first example would work and the second dont.
app component
const theme = createMuiTheme({
palette: {
primary: {
main: '#bed000',
},
secondary: {
main: '#110b36',
},
error: {
main: '#B33A3A',
},
},
})
<MuiThemeProvider theme={theme}>
<Route exact path="/" component={LoginView} />
</MuiThemeProvider>
<LoginView>
<TextField
autoFocus
label="ContraseƱa"
name="Password"
type="Password"
value={values.Password}
onChange={handleChange}
onBlur={handleBlur}
fullWidth
color={touched.Password && errors.Password ? "primary" : "secondary"}
/>
<TextField
autoFocus
label="ContraseƱa"
name="Password"
type="Password"
value={values.Password}
onChange={handleChange}
onBlur={handleBlur}
fullWidth
color={touched.Password && errors.Password ? "error" : "secondary"}
/>
</LoginView>

Icon className doesn't update on state change

I am trying simplest ever theme toggle in react with context and don't seem to be able to get the icon re-render when context changes. Everything else works just fine: colors, background image... It renders either of icons depending on initial state, but icon doesn't update when theme is toggled.
import React, { useContext } from "react"
import { ThemeContext } from "../../contexts/ThemeContext"
const ThemeToggle = () => {
const { isDarkMode, dark, light, toggleTheme } = useContext(ThemeContext)
const theme = isDarkMode ? dark : light
return (
<li
style={{ background: theme.bgPrimary, color: theme.text }}
onClick={toggleTheme}
>
<i className={theme.ico} />
</li>
)
}
export default ThemeToggle
Context:
import React, { Component, createContext } from "react"
export const ThemeContext = createContext()
class ThemeContexProvider extends Component {
state = {
isDarkMode: false,
light: {
text: "#333",
bgPrimary: "#eee",
bgSecondary: "#333",
ico: "fas fa-moon"
},
dark: {
text: "#ddd",
bgPrimary: "#000003",
bgSecondary: "#bbb",
ico: "fas fa-sun"
}
}
toggleTheme = () => {
this.setState({ isDarkMode: !this.state.isDarkMode })
}
render() {
return (
<ThemeContext.Provider
value={{ ...this.state, toggleTheme: this.toggleTheme }}
>
{this.props.children}
</ThemeContext.Provider>
)
}
}
export default ThemeContexProvider
I fixed this installing dedicated react fa package, still don't know why above doesn't work. This works though:
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome"
import { faMoon } from "#fortawesome/free-solid-svg-icons"
import { faSun } from "#fortawesome/free-solid-svg-icons"
//.....
return (
<li
style={{ background: theme.bgPrimary, color: theme.text }}
onClick={toggleTheme}
>
{isDarkMode ? (
<FontAwesomeIcon icon={faSun} />
) : (
<FontAwesomeIcon icon={faMoon} />
)}
</li>
I suspect the spaces in your ico property could be the issue. Normally this isn't an issue for state/props. Context is possibly to blame for this. This might fix it:
<i className={`fas ${theme.ico}`} />
Replace your context ico properties with just the class that changes fa-moon and fa-sun
You could have used key props in the icon tag. Keys help React identify which items have changed. So it can rerender your icon.
{isDarkMode ? (
<i key={1} icon={faSun} />
) : (
<i key={2} icon={faMoon} />
)}

Material UI Palette not updating

I'm new to React and Material UI, i'm trying to add some custom styles to the MUI buttons by using the createMuiTheme.
I've followed the docs and pretty much copied the example but it's having no effect and no errors are being thrown in the console.
I've been banging my head against this for a while now and I can't see what the problem is, what am I missing?
import React from 'react';
import { createMuiTheme } from '#material-ui/core/styles';
import { ThemeProvider } from '#material-ui/styles';
import Button from '#material-ui/core/Button';
const mytheme = createMuiTheme({
palette: {
primary: { main: '#1565C0'},
secondary: { main: '#11cb5f' },
},
});
export const PrimaryButton = (props) => {
return (
<ThemeProvider theme={mytheme}>
<a href={props.buttonLink}>
<Button
style={{ ...props.styles}}
onClick={props.handleClick}
variant='contained' color='primary' size='large'
>
{props.buttonText}
</Button>
</a>
</ThemeProvider>
);
};
export const SecondaryButton = (props) => {
return (
<ThemeProvider theme={mytheme}>
<Button
style={{...props.styles }}
value={props.value || ''}
onClick={props.handleClick}
variant='outlined' color='secondary' size='large'
>
{props.buttonText}
</Button>
</ThemeProvider>
)
}
It is working. Try to change this:
variant='outlined'
by
variant='contained'

React Material Theme Chooser

So I've been working with react material on my application. Everything until now it's fine but I want to make application in which themes can be stored on the back-end and I can load them based on user choice.
So until now, I know I can create multiple themes and store them as stated here
but I want to store them to the back-end and I don't have Idea how that would work
So I need help for an idea or some kind of tutorial which can point me in the right direction?
You just need to store the attributes that you are allowing the user to change/specify. For instance, you might only allow them to choose a primary and secondary color. You would then save those two pieces of information in your DB and then recreate the theme using createMuiTheme.
Here's some sample code demonstrating this:
import React from "react";
import ReactDOM from "react-dom";
import CssBaseline from "#material-ui/core/CssBaseline";
import AppBar from "#material-ui/core/AppBar";
import Button from "#material-ui/core/Button";
import { createMuiTheme, MuiThemeProvider } from "#material-ui/core/styles";
const themeDB = {
a: {
primaryColor: "#0f0",
secondaryColor: "#f0f"
},
b: {
primaryColor: "#ff0",
secondaryColor: "#0ff"
}
};
const createThemeFromThemeDBEntry = themeDBEntry => {
return createMuiTheme({
palette: {
primary: {
main: themeDBEntry.primaryColor
},
secondary: {
main: themeDBEntry.secondaryColor
}
}
});
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = { currentTheme: createMuiTheme() };
}
switchToThemeA = () => {
const themeA = createThemeFromThemeDBEntry(themeDB.a);
this.setState({ currentTheme: themeA });
};
switchToThemeB = () => {
const themeB = createThemeFromThemeDBEntry(themeDB.b);
this.setState({ currentTheme: themeB });
};
useDefaultTheme = () => {
this.setState({ currentTheme: createMuiTheme() });
};
render() {
return (
<>
<CssBaseline />
<MuiThemeProvider theme={this.state.currentTheme}>
<AppBar position="static">AppBar using Primary Color</AppBar>
<AppBar position="static" color="secondary">
AppBar using Secondary Color
</AppBar>
<br />
<Button
onClick={this.switchToThemeA}
variant="contained"
color="primary"
>
Use Theme A
</Button>
<Button
onClick={this.switchToThemeB}
variant="contained"
color="secondary"
>
Use Theme B
</Button>
<Button onClick={this.useDefaultTheme} color="secondary">
Use Default Theme
</Button>
</MuiThemeProvider>
</>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Cannot create a custom login page with Admin-On-Rest

Been trying for a while to figure out how to set a custom login page in admin-on-rest v-1.0.0
Need to enable email based login. For this I changed the default admin-on-rest login page code slightly (below)
Replaced the UserName with email and changed the authClient to loopbackRestClient
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { propTypes, reduxForm, Field } from 'redux-form';
import { connect } from 'react-redux';
import compose from 'recompose/compose';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import { Card, CardActions } from 'material-ui/Card';
import Avatar from 'material-ui/Avatar';
import RaisedButton from 'material-ui/RaisedButton';
import TextField from 'material-ui/TextField';
import CircularProgress from 'material-ui/CircularProgress';
import LockIcon from 'material-ui/svg-icons/action/lock-outline';
import { cyan500, pinkA200 } from 'material-ui/styles/colors';
import defaultTheme from 'admin-on-rest';
import { userLogin as userLoginAction } from 'admin-on-rest';
import Notification from 'admin-on-rest';
const styles = {
main: {
display: 'flex',
flexDirection: 'column',
minHeight: '100vh',
alignItems: 'center',
justifyContent: 'center',
},
card: {
minWidth: 300,
},
avatar: {
margin: '1em',
textAlign: 'center ',
},
form: {
padding: '0 1em 1em 1em',
},
input: {
display: 'flex',
},
};
function getColorsFromTheme(theme) {
if (!theme) return { primary1Color: cyan500, accent1Color: pinkA200 };
const {
palette: {
primary1Color,
accent1Color,
},
} = theme;
return { primary1Color, accent1Color };
}
// see http://redux-form.com/6.4.3/examples/material-ui/
const renderInput = ({ meta: { touched, error } = {}, input: { ...inputProps }, ...props }) =>
<TextField
errorText={touched && error}
{...inputProps}
{...props}
fullWidth
/>;
class EmailLogin extends Component {
login = (auth) => this.props.userLogin(auth, this.props.location.state ? this.props.location.state.nextPathname : '/');
render() {
const { handleSubmit, submitting, theme } = this.props;
const muiTheme = getMuiTheme(theme);
const { primary1Color, accent1Color } = getColorsFromTheme(muiTheme);
console.log(styles.form)
return (
<MuiThemeProvider muiTheme={muiTheme}>
<div style={{ ...styles.main, backgroundColor: primary1Color }}>
<Card style={styles.card}>
<div style={styles.avatar}>
<Avatar backgroundColor={accent1Color} icon={<LockIcon />} size={60} />
</div>
<form onSubmit={handleSubmit(this.login)}>
<div style={styles.form}>
<div style={styles.input} >
<Field
name="email"
floatingLabelText={"email"}
component={renderInput}
disabled={submitting}
placeholder={"email"}
/>
</div>
<div style={styles.input}>
<Field
name="password"
component={renderInput}
type="password"
disabled={submitting}
/>
</div>
</div>
<CardActions>
<RaisedButton
type="submit"
primary
disabled={submitting}
icon={submitting && <CircularProgress size={25} thickness={2} />}
fullWidth
/>
</CardActions>
</form>
</Card>
<Notification />
</div>
</MuiThemeProvider>
);
}
}
EmailLogin.propTypes = {
...propTypes,
authClient: PropTypes.func,
previousRoute: PropTypes.string,
theme: PropTypes.object,
userLogin: PropTypes.func.isRequired,
};
EmailLogin.defaultProps = {
theme: defaultTheme,
};
const enhance = compose(
reduxForm({
form: 'signIn',
validate: (values, props) => {
const errors = {};
return errors;
},
}),
connect(null, { userLogin: userLoginAction }),
);
export default enhance( EmailLogin );
I have added the above to the loginPage prop on Admin in my app.js
However admin-on-rest seems to be showing the default page.
I copied the BtcLoginPage from this question
Is there any examples of how to create a custom login page?
But the admin is still showing the default page (the one with UserName) only.
Please do advise. How can I create a custom page using Admin-On-Rest.
Thanks
There is a working example in the admin-on-rest-demo repository:
The custom Login component:
https://github.com/marmelab/admin-on-rest-demo/blob/master/src/Login.js
It's integration into the Admin:
https://github.com/marmelab/admin-on-rest-demo/blob/master/src/App.js#L45
Which version of aor are you using ?
Seems I have been doing something wrong.
My custom loginPage (above) had a silent error. The line
import Notification from 'admin-on-rest';
Was failing it needed to be
import { Notification } from 'admin-on-rest';
However.
Admin-on-rest was implicitly substituting the default admin page for my custom page. I was ascribing the error to my limited knowledge of Redux and React-Redux. But the solution was simpler.

Resources