how to do Storybook React Material UI custom styling - reactjs

Hello I'm new to stroybook and and would liek to use custom colors on my component but i keep on getting errors.
This is the code below, could sb help me to fix this so I can see how to use storybook material ui elements in storybook..
the code:
import "bootstrap/dist/css/bootstrap.min.css";
import React from "react";
import { deepOrange, deepPurple } from '#material-ui/core/colors';
import { makeStyles } from '#material-ui/core/styles';
import Avatar from '#material-ui/core/Avatar';
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
'& > *': {
margin: theme.spacing(1),
},
},
orange: {
color: theme.palette.getContrastText(deepOrange[500]),
backgroundColor: deepOrange[500],
},
purple: {
color: theme.palette.getContrastText(deepPurple[500]),
backgroundColor: deepPurple[500],
},
}));
And this is the error.
export default {
const classes = useStyles();
title: "Components/Avatar/Number",
component: Avatar
}
export const _Avatar = () => (
<>
<Avatar>H</Avatar>
<Avatar className={classes.orange}>N</Avatar>
<Avatar className={classes.purple}>OP</Avatar>
</>
);
ERROR in
src\stories\Avatars\avatar.stories.js
Parsing error: Unexpected keyword 'const'.
export default {
const classes = useStyles();
^
title: "Components/Avatar/Number",
component: Avatar
}

It is syntactically incorrect to use the const keyword and the = operator inside your default export object. Try the following code instead:
export default {
classes: useStyles(),
title: "Components/Avatar/Number",
component: Avatar
}

Related

TypeError: theme.spacing is not a function

I'm working on react app and using material-ui v5 and I'm stuck on this error
theme.spacing is not working.
import { makeStyles } from "#material-ui/styles";
import React from "react";
import Drawer from "#mui/material/Drawer";
import Typography from "#mui/material/Typography";
import List from "#mui/material/List";
import ListItem from "#mui/material/ListItem";
import ListItemIcon from "#mui/material/ListItemIcon";
import ListItemText from "#mui/material/ListItemText";
import { AddCircleOutlineOutlined, SubjectOutlined } from "#mui/icons-material";
import { useHistory, useLocation } from "react-router";
const drawerWidth = 240;
const useStyles = makeStyles((theme) => {
return {
page: {
background: "#f9f9f9",
width: "100%",
padding: theme.spacing(3),
},
drawer: {
width: drawerWidth,
},
drawerPaper: {
width: drawerWidth,
},
root: {
display: "flex",
},
active: {
background: "#f4f4f4 !important",
},
};
});
const Layout = ({ children }) => {
const classes = useStyles();
const history = useHistory();
const location = useLocation();
const menuItems = [
{
text: "My Notes",
icon: <SubjectOutlined color="secondary" />,
path: "/",
},
{
text: "Create Note",
icon: <AddCircleOutlineOutlined color="secondary" />,
path: "/create",
},
];
return (
<div className={classes.root}>
<div className={classes.page}>{children}</div>
</div>
);
};
export default Layout;
on line no. 19 that is "padding: theme.spacing(3)" it is showing error on my browser
TypeError: theme.spacing is not a function
This is not working because you have to type your Theme
import the Theme
import { Theme } from '#mui/material'
Now type it:
const useStyles=makeStyles((theme:Theme)=>({
root:{
background: 'black',
padding:theme.spacing(3),
}
}))
I was facing the exact same problem and this worked for me.
How do you create the theme, did you made with createTheme? You need to pass the provider in the root of your application. Try import like this.
import { createTheme } from '#mui/material/styles
Then define all your global styles for your application and then make, for example, this.
import { ThemeProvider } from "#mui/material/styles";
<ThemeProvider theme={yourtheme}><App/></ThemeProvider>
Maybe your import is wrong ? Try this
import {makeStyles} from "#material-ui/core/styles";

Using multiple CSS rule names with the styled API in Material UI

I'm using Material UI and I want to style a component using multiple rule names with the styled API.
Let's say I want to style the FormLabel Component in blue with the asterisk (required) in red.
With the Hook API I would do something like that:
import React from 'react'
import { makeStyles } from '#material-ui/core/styles'
import MuiFormLabel from '#material-ui/core/FormLabel'
const useStyle = makeStyles({
root: {
color: 'blue'
},
asterisk: {
color: 'red'
},
})
const FormLabel = ({ children }) => {
const classes = useStyle()
return (
<MuiFormLabel
classes={{
root: classes.root,
asterisk: classes.asterisk
}}
>
{children}
</MuiFormLabel>
)
}
Can I pass root AND asterisk to my component using the styled API?
I tried this but it doesn't work
import React from 'react'
import { styled } from '#material-ui/core/styles'
import MuiFormLabel from '#material-ui/core/FormLabel'
const StyledFormLabel = styled(MuiFormLabel)({
'.MuiFormLabel-root': {
color: 'blue'
},
'.MuiFormLabel-asterisk': {
color: 'red'
},
})
const FormLabel = ({ children }) => (
<StyledFormLabel>{children}</StyledFormLabel>
)
Below is an example of the correct syntax. By default, the top-level keys in the object passed to styled are assumed to be CSS property names. By adding & at the beginning of the key, it lets styled know that you are defining a nested rule. .MuiFormLabel-root is unnecessary since the root level is where properties will be applied by default (e.g. color: "blue" in the example below). The & is a reference to the root-level class, so & .MuiFormLabel-asterisk targets descendant elements with the MuiFormLabel-asterisk class.
import React from "react";
import { styled } from "#material-ui/core/styles";
import MuiFormLabel from "#material-ui/core/FormLabel";
const StyledFormLabel = styled(MuiFormLabel)({
color: "blue",
"&.Mui-error": {
color: "purple"
},
"& .MuiFormLabel-asterisk": {
color: "green"
},
"& .MuiFormLabel-asterisk.Mui-error": {
color: "red"
}
});
const FormLabel = ({ children }) => (
<>
<StyledFormLabel required>{children}</StyledFormLabel>
<br />
<StyledFormLabel error required>
{children}
</StyledFormLabel>
</>
);
export default FormLabel;

Global Styles with React and MUI

I'm new to React and MUI but I have to write an enterprise application with a nice styling. I would like to use some kind of global styles for my application (to be able to change it later on
) with functional components in react (maybe I will later add redux).
What's the best practice approach for global styles with react and material (latest versions)?
What about this one (ThemeProvider): https://material-ui.com/styles/advanced/?
I read about MuiThemeProvider but could not find it in the material version 4 documentation. Is it obsolete? What's the difference between MuiThemeProvider and ThemeProvider?
React (client side rendering) & Material (latest versions)
Backend: Node
In Material-UI v5, you can use GlobalStyles to do exactly that. From what I know, GlobalStyles is just a wrapper of emotion's Global component. The usage is pretty straightforward:
import GlobalStyles from "#mui/material/GlobalStyles";
<GlobalStyles
styles={{
h1: { color: "red" },
h2: { color: "green" },
body: { backgroundColor: "lightpink" }
}}
/>
Note that you don't even have to put it inside ThemeProvider, GlobalStyles uses the defaultTheme if not provided any:
return (
<>
<GlobalStyles
styles={(theme) => ({
h1: { color: theme.palette.primary.main },
h2: { color: "green" },
body: { backgroundColor: "lightpink" }
})}
/>
<h1>This is a h1 element</h1>
<h2>This is a h2 element</h2>
</>
);
Live Demo
You can actually write global styles with material UI:
const useStyles = makeStyles((theme) => ({
'#global': {
'.MuiPickersSlideTransition-transitionContainer.MuiPickersCalendarHeader-transitionContainer': {
order: -1,
},
'.MuiTypography-root.MuiTypography-body1.MuiTypography-alignCenter': {
fontWeight: 'bold',
}
}
}));
Global Styles with Material UI & React
// 1. GlobalStyles.js
import { createStyles, makeStyles } from '#material-ui/core';
const useStyles = makeStyles(() =>
createStyles({
'#global': {
html: {
'-webkit-font-smoothing': 'antialiased',
'-moz-osx-font-smoothing': 'grayscale',
height: '100%',
width: '100%'
},
'*, *::before, *::after': {
boxSizing: 'inherit'
},
body: {
height: '100%',
width: '100%'
},
'#root': {
height: '100%',
width: '100%'
}
}
})
);
const GlobalStyles = () => {
useStyles();
return null;
};
export default GlobalStyles;
** Then Use it in App.js like below**
// 2. App.js
import React from 'react';
import { MuiThemeProvider, createMuiTheme } from '#material-ui/core/styles';
import { Router } from 'react-router-dom';
import { NavBar, Routes, GlobalStyles, Routes } from '../';
const theme = createMuiTheme({
palette: {
primary: {
main: 'blue'
}
}
});
const App = () => {
return (
<MuiThemeProvider theme={theme}>
<Router>
<NavBar />
<GlobalStyles />
<Routes />
</Router>
</MuiThemeProvider>
);
};
export default App;
This Works for me with my react project.
For global styles you can use it like shown below.
This is the best implementation that has worked for me.
const theme = createMuiTheme({
overrides: {
MuiCssBaseline: {
'#global': {
html: {
WebkitFontSmoothing: 'auto',
},
},
},
},
});
// ...
return (
<ThemeProvider theme={theme}>
<CssBaseline />
{children}
</ThemeProvider>
);
For more reference: Global CSS
In my experience, using MuiThemeProvider and createMuiTheme have worked wonderfully. However, I am using Material-UI version 3.9.2.
MuiThemeProvider should wrap around your entire application. All you need to do in all of your components would be to instead of passing your styles object to with styles, pass a function that passes in the theme.
Ex:
import React from 'react';
import { MuiThemeProvider, createMuiTheme } from '#material-ui/core/styles';
import {NavBar, Routes} from '../'
const theme = createMuiTheme({
palette: {
primary: {
main: 'red'
},
},
/* whatever else you want to add here */
});
class App extends Component {
render() {
return (
<MuiThemeProvider theme={theme}>
<NavBar />
<Routes />
</MuiThemeProvider>
)
}
then in navbar let's say:
import React from 'react';
import { withStyles } from '#material-ui/core';
const styles = theme => ({
root: {
color: theme.palette.primary.main,,
}
})
const NavBar = ({classes}) => {
return <div className={classes.root}>navigation</div>
}
export default withStyles(styles)(NavBar);
Hope that helps, works very well for me!

React: Slider with custom hook not working properly

I am trying to create a custom hook with a slider ui element. My goal is to be able to access the slider value from the parent element so as to update some other ui parts.
However, it seems that the slider values do not update correctly: when the user tries to drag the slider tooltip it only moves one step. It seems like the mouse events stop being tracked after useEffect gets called.
What can I do to fix this and have a smooth dragging behaviour?
Here is my code (sandbox):
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Demo from './demo';
ReactDOM.render(<Demo />, document.querySelector('#root'));
demo.js
import React, { useEffect } from "react";
import useSlider from "./slider";
function CustomizedSlider() {
const [CustomSlider, sliderValue] = useSlider("Slider", 50);
useEffect(() => {
console.log("Slider value: " + sliderValue);
}, [sliderValue]);
return <CustomSlider />;
}
export default CustomizedSlider;
slider.js
import React, { useState } from "react";
import { withStyles, makeStyles } from "#material-ui/core/styles";
import Paper from "#material-ui/core/Paper";
import Slider from "#material-ui/core/Slider";
import Typography from "#material-ui/core/Typography";
const useStyles = makeStyles(theme => ({...
}));
const PrettoSlider = withStyles({...
})(Slider);
export default function useSlider(label, defaultState) {
const classes = useStyles();
const [state, setState] = useState(defaultState);
const CustomSlider = () => {
return (
<Paper className={classes.root}>
<div className={classes.margin} />
<Typography gutterBottom>{label}</Typography>
<PrettoSlider
valueLabelDisplay="auto"
aria-label="pretto slider"
defaultValue={50}
value={state}
onChange={(event, v) => {
setState(v);
}}
/>
</Paper>
);
};
return [CustomSlider, state];
}
Thanks a lot for your help!
The issue is that your CustomSlider is a new component type for each render due to it being a unique function each time. This causes it to unmount/remount with each render rather than just re-render which will cause all sorts of issues (as you've seen).
Rather than a custom hook, I think you really just want a custom component. Below is one way you could structure it with only minimal changes to your initial code.
demo.js
import React, { useEffect } from "react";
import CustomSlider from "./CustomSlider";
function CustomizedSlider() {
const [value, setValue] = React.useState(50);
useEffect(() => {
console.log("Slider value: " + value);
}, [value]);
return <CustomSlider label="Slider" value={value} setValue={setValue} />;
}
export default CustomizedSlider;
CustomSlider.js
import React from "react";
import { withStyles, makeStyles } from "#material-ui/core/styles";
import Paper from "#material-ui/core/Paper";
import Slider from "#material-ui/core/Slider";
import Typography from "#material-ui/core/Typography";
const useStyles = makeStyles(theme => ({
root: {
width: 300 + 24 * 2,
padding: 24
},
margin: {
height: theme.spacing(1)
}
}));
const PrettoSlider = withStyles({
root: {
color: "#a2df77",
height: 8
},
thumb: {
height: 24,
width: 24,
backgroundColor: "#fff",
border: "2px solid currentColor",
marginTop: -8,
marginLeft: -12,
"&:focus,&:hover,&$active": {
boxShadow: "inherit"
}
},
active: {},
valueLabel: {
left: "calc(-50% + 4px)"
},
track: {
height: 8,
borderRadius: 4
},
rail: {
height: 8,
borderRadius: 4
}
})(Slider);
const CustomSlider = ({ label, value, setValue }) => {
const classes = useStyles();
return (
<Paper className={classes.root}>
<div className={classes.margin} />
<Typography gutterBottom>{label}</Typography>
<PrettoSlider
valueLabelDisplay="auto"
aria-label="pretto slider"
defaultValue={50}
value={value}
onChange={(event, v) => {
setValue(v);
}}
/>
</Paper>
);
};
export default CustomSlider;

How to use 'theme' and 'props' in makeStyles?

How do I write makeStyles() so that it allows me to use both theme variables and props?
I've tried this:
const useStyles = makeStyles(theme => ({
appbar: props => ({
boxShadow: "none",
background: "transparent",
marginTop: theme.spacing(2),
marginBottom: theme.spacing(2),
color: theme.palette.getContrastText(props)
}),
}));
And called it in the component with:
const classes = useStyles(backgroundColor);
Where backgroundColor is a prop on the component with type CSSProperties["backgroundColor"]
But I'm getting the error:
TypeError: Cannot read property 'rules' of undefined
at RuleList.onUpdate (C:\Users\...\node_modules\jss\dist\jss.cjs.js:944:14)
at RuleList.update (C:\Users\...\node_modules\jss\dist\jss.cjs.js:923:12)
at StyleSheet.update (C:\Users\...\node_modules\jss\dist\jss.cjs.js:1178:39)
at attach (C:\Users\...\node_modules\#material-ui\styles\makeStyles\makeStyles.js:141:18)
at C:\Users\...\node_modules\#material-ui\styles\makeStyles\makeStyles.js:262:7
at useSynchronousEffect (C:\Users\...\node_modules\#material-ui\styles\makeStyles\makeStyles.js:207:14)
at C:\Users\...\node_modules\#material-ui\styles\makeStyles\makeStyles.js:254:5
at Layout (C:\Users\...\.next\server\static\development\pages\index.js:1698:17)
at processChild (C:\Users\...\node_modules\react-dom\cjs\react-dom-server.node.development.js:2888:14)
at resolve (C:\Users\...\node_modules\react-dom\cjs\react-dom-server.node.development.js:2812:5)
at ReactDOMServerRenderer.render (C:\Users\...\node_modules\react-dom\cjs\react-dom-server.node.development.js:3202:22)
at ReactDOMServerRenderer.read (C:\Users\...\node_modules\react-dom\cjs\react-dom-server.node.development.js:3161:29)
at renderToString (C:\Users\...\node_modules\react-dom\cjs\react-dom-server.node.development.js:3646:27)
at render (C:\Users\...\node_modules\next-server\dist\server\render.js:86:16)
at renderPage (C:\Users\...\node_modules\next-server\dist\server\render.js:211:20)
at ctx.renderPage (C:\Users\...\.next\server\static\development\pages\_document.js:2404:22)
100 | handleSignUpClick,
101 | backgroundColor
102 | }) => {
> 103 | const classes = useStyles(backgroundColor);
104 | return (
105 | <AppBar className={classes.appbar}>
106 | <Container maxWidth="lg">
edit: I'm using version 4.0.0-beta.1 of material core and styles
Tested with:
"#material-ui/core": "^4.0.0-beta.1",
"#material-ui/styles": "^4.0.0-rc.0",
JavaScript example:
my-component.js
import React from 'react';
import { makeStyles } from '#material-ui/styles';
import { useStyles } from './my-component.styles.js';
const myComponent = (props) => {
const styleProps = { width: '100px', height: '100px' };
const classes = useStyles(styleProps);
return (
<div className={classes.mySelector}></div> // with 100px and height 100px will be applied
)
}
my-component.styles.js
export const useStyles = makeStyles(theme => ({
mySelector: props => ({
display: 'block',
width: props.width,
height: props.height,
}),
}));
TypeScript example:
my-component.ts
import React, { FC } from 'react';
import { makeStyles } from '#material-ui/styles';
import { useStyles } from './my-component.styles.ts';
import { MyComponentProps, StylesProps } from './my-component.interfaces.ts';
const myComponent: FC<MyComponentProps> = (props) => {
const styleProps: StylesProps = { width: '100px', height: '100px' };
const classes = useStyles(styleProps);
return (
<div className={classes.mySelector}></div> // with 100px and height 100px will be applied
)
}
my-component.interfaces.ts
export interface StyleProps {
width: string;
height: string;
}
export interface MyComponentProps {
}
my-component.styles.ts
import { Theme } from '#material-ui/core';
import { makeStyles } from '#material-ui/styles';
import { StyleProps } from './my-components.interfaces.ts';
export const useStyles = makeStyles<Theme, StyleProps>((theme: Theme) => ({
mySelector: props => ({ // props = { width: string; height: string }
display: 'block',
width: props.width,
height: props.height,
}),
}));
Update
Re-tested with
"#material-ui/core": "^4.12.X"
You need to pass an object to useStyles rather than a string.
So instead of:
const classes = useStyles(backgroundColor);
you should have:
const classes = useStyles(props);
or
const classes = useStyles({backgroundColor});
Then you can get at backgroundColor using:
color: theme.palette.getContrastText(props.backgroundColor).
Here's a working example:
https://codesandbox.io/s/o7xryjnmly
You can do this: (i dont know if is the best way but works... also can access to the theme ang globals provider (using themeProvider))
import { makeStyles }from '#material-ui/core/styles'
import styles from './styles';
const useStyles = makeStyles(theme => (styles(theme))); // here call styles function imported from styles.js
const SideNav = ({drawerState, toggleDrawer}) => {
const classes = useStyles();
return (
<Box className={classes.root}>
<Drawer className="drawer" anchor="left" open={drawerState} onClose={() => toggleDrawer(false)}>
<NavList></NavList>
</Drawer>
</Box>
);
export default SideNav;
and in styles.js
const styles = (theme) => {
return ({
root: {
'& .drawer': {
backgroundColor:'red'
}
}
});
}
export default styles;
makeStyles get the theme by params
so you can create a styles.js for every component with a arrow function inside and
calling from makeStyles that can access to the theme provider.
We have 2 modes in general, your prop variable is imported to the component or not.
If your prop variable is imported, it is a global variable, so it is valid in makeStyles():
import {prop} from ...
const useStyles = makeStyles((theme) => ({
className:{
// use prop
}
})
define component...
If your prop variable is defined in the component (such as a state), you have 2 choices:
You can pass the prop variable to makeStyles():
const useStyles = makeStyles((theme,prop) => ({
className:{
// use prop
}
})
define component...
You can use arrow function without passing any argument (except theme) to makeStyles():
const useStyles = makeStyles((theme) => ({
className:(prop)=>({
//use prop
})
})
define component...
Here is an example of how you can use only props or props and theme both with makeStyles() just like styled-components
component.js
import { tableCellStyling } from './component.styled.js';
const RenderRow = (props) => {
const { noPaddingTopBottom } = tableCellStyling(props);
return(
<StyledTableRow>
{data.map( (row,i) => (
<StyledTableCell className={noPaddingTopBottom}>
{row.data}
</StyledTableCell>
)}
</StyledTableRow>
)
};
Assuming my props object which is being passed by RenderRow Component to tableCellStyling has { color: 'grey', thinRow: true } in it
component.styled.js
import { makeStyles } from '#material-ui/core/styles';
export const tableCellStyling = makeStyles(theme => ({
noPaddingTopBottom: {
borderBottom: ({ color }) => color ? `2px solid ${color}` : '2px solid red',
paddingBottom: props => props.hasActions && 0,
paddingTop: props => props.hasActions && 0,
backgroundColor: theme.palette.common.white,
},
}));
The way you can do it is like this:
import { makeStyles, createStyles, Theme } from "#material-ui/core/styles";
...
...
const classes = useStyles();
...
...
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: propName => ({
border: "none",
boxShadow: "none",
cursor: propName ? "pointer" : "auto",
width: "100%",
backgroundColor: "#fff",
padding: "15px 15px"
}),
updated: {
marginTop: 12,
fontWeight: 400,
color: "#939393"
}
})
);
You can use the prop name inside the element you are styling by making it an arrow function that returns the styling. In addition, you can also style other elements inside the createStyles hook. This took me some time, I hope anyone finds it useful. ✨🔥

Resources