Material ui emotion/styled how to combine multiple snippets of css classes? - javascript

In the old way material ui (version 4 and earlier) styled components you could use className property to "Select" which styles are active, ie a component could be styled like:
const styles = (theme: ThemeTy) => ({
root: {
width: '100%',
},
rootLight: {
color: theme.palette.getContrastText(theme.palette.primary.main),
},
})
const useStyles = makeStyles(styles);
function MyComp() {
const classes = useLocalStyles();
return <Input
className=classNames(classes.root, highContrast ? classes.rootLight : undefined)}
value={100}
/>
}
However in the new api one would define the classes outside the component similar to styled-components does it:
const StyledInput = styled(Input)(({theme}) => `
width: '100%',
`);
function MyComp() {
return <StyledInput
value={100}
/>
}
However how would I add the conditional styling? Would I have to use the sx element? And thus use conditional styling everywhere?

You can pass any props to your StyledInput and then style the component based on them:
const StyledInput = styled(Input)(({ theme, showborders }) => ({
width: "100%",
border: showborders ? "3px solid red" : "none"
}));
...
const [showBorders, setShowBorders] = React.useState(false);
return (
<Box>
<StyledInput showBorders={showborders ? 1 : 0} value={100} />
</Box>
);
Demo

Related

How to extend styling of component without using wrapper element?

I'm working with styled-components in atomic system.
I have my <Atom title={title} onClick={onClick}/>
And I have molecule that extends that atom, by just adding some functionality to it without wrapping element:
const Molecule = ({}) => {
const [title, setTitle] = useState('Base title')
const onClick = () => {
// some actions
}
useEffect(()=>{
setTitle('New title')
},[conditions changed])
return(
<Atom title={title} onClick={onClick}/>
)
}
I have base styling in <Atom/> but I would like to add some more to it in <Molecule/>. Is it possible to do it without creating extra wrapper?
It is possible, but the question is if it's worthy the effort - the most anticipated way would be to do it as the documentation says - to wrap the styled component and extend the styles (but this is what you want to avoid). So either:
you could assign a className to Atom, so you can adjust/overwrite the styles with CSS
pass the extraStyles props to Atom and then pass to the styled component and just use inside after the previous, default styles to overwrite them
or either pass some extraStyles as CSSProperties object and just use them as inline styling.
https://codesandbox.io/s/withered-leftpad-znip6b?file=/src/App.js:64-545
/* styles.css */
.extraClass {
color: green;
}
const AtomStyled = styled.div`
font-size: 17px;
color: blue;
font-weight: 600;
`;
const Atom = ({ children, className, extraStyles }) => {
return (
<AtomStyled style={extraStyles} className={className}>
{children}
</AtomStyled>
);
};
const Molecule = () => {
return (
<Atom className={'extraClass'} extraStyles={{ fontSize: 27 }}>
Atom
</Atom>
);
};

findByType function not finding property?

I'm writing tests for components I have main component Icon
const Icon: React.FC<IIconPropTypes> = ({ size, color, icon, onClick, blockEvents }) => (
<IconWrapper onClick={onClick} blockEvents={blockEvents}>
<IconSvg color={color} hasClick={!!onClick} width={size}>
<path d={ICONS_PATH[icon]} />
</IconSvg>
</IconWrapper>
);
and IconSvg which I am testing
const IconSvg = styled.svg<IIconSvgProps>`
cursor: ${(props) => (props.hasClick ? 'pointer' : 'default')};
display: inline-block;
vertical-align: middle;
width: ${(props) => (props.width ? `${props.width}px` : '100%')};
path {
fill: ${(props) => (props.color ? props.color : '#edcd7d')};
transition: all 0.3s;
}
`;
This is how my test looks like
it('matches snapshot and renders without color prop', () => {
const [hasClick, width] = [true, 100];
const component = renderer.create(<IconSvg hasClick={hasClick} width={width}/>);
const tree = component.toJSON() as ReactTestRendererJSON;
expect(tree).toMatchSnapshot();
expect(component.root.findByType('path')).toHaveStyleRule('fill', '#edcd7d');
})
But findByType cannot find the path, and therefore the fill property, can anyone know what the error is and how to reach it?
You are testing your IconSvg component here:
let component = renderer.create(<IconSvg hasClick={hasClick} width={width}/>);
According to your code IconSvg is just a styled svg. You need to test Icon component which has a path element in it:
let component = renderer.create(<Icon size={} color={} icon={} onClick={} blockEvents={} } />);

Changing the styling in MUI Table

I'm creating a Material UI table what I believe is the default way, and I'm getting huge padding internally.
I have tried using withStyles and passing the resulting class into through the component property, like this:
const StyledPaper = withStyles(theme => ({
root: {
padding: "0",
},
}), Paper);
...
<Table component={StyledPaper}>
I have tried making classes and passing them in:
const useStyles = makeStyles(theme => ({
root: {
padding: "0",
},
}));
...
const classes = useStyles();
...
<Table classes={classes}>
I have futzed around endlessly and I'm not having any effect at all.
Any suggestions?
If you look at the DOM element class name, you would find out that it starts with MuiPaper-root under the MuiGrid-root element.
Perhaps use nesting selector is a good approach in this situation for customized styles
import { withStyles } from "#material-ui/core/styles";
const styles = {
root: {
"& .MuiPaper-root": {
padding: 0
}
}
};
...
const { classes } = this.props;
...
export default withStyles(styles)(App);
usage
Notice it's not inside the Table so you may want to add the padding for Grid
<Grid container>
<Grid item className={classes.root} // ...>
// ...
</Grid>
</Grid>
Similar online demo and related QA:
How to change material-ui Textfield label styles in react

How to Change a css property based on a state of another component

I'm building a web page with gatsby which is based in react, and I need my nav component changes his sticky position to relative or auto, every time that I open the modal of the gallery component..but I don't know how to approach and solve the problem. The nav component belongs to the layout component which is Gallery's parent component...Here are the components involved:
nav component:
import React, { Component } from 'react'
import { Location } from '#reach/router'
import { Link } from 'gatsby'
import { Menu, X } from 'react-feather'
import Logo from './Logo'
import './Nav.css'
export class Navigation extends Component {
state = {
active: false,
activeSubNav: false,
currentPath: false
}
componentDidMount = () =>
this.setState({ currentPath: this.props.location.pathname })
handleMenuToggle = () => this.setState({ active: !this.state.active })
// Only close nav if it is open
handleLinkClick = () => this.state.active && this.handleMenuToggle()
toggleSubNav = subNav =>
this.setState({
activeSubNav: this.state.activeSubNav === subNav ? false : subNav
})
render() {
const { active } = this.state,
{ subNav } = this.props,
NavLink = ({ to, className, children, ...props }) => (
<Link
to={to}
className={`NavLink ${
to === this.state.currentPath ? 'active' : ''
} ${className}`}
onClick={this.handleLinkClick}
{...props}
>
{children}
</Link>
)
return (
<nav className={`Nav ${active ? 'Nav-active' : ''}`}>
<div className="Nav--Container container">
<Link to="/" onClick={this.handleLinkClick}>
<div style={{ width: `40px`, margin:`0 20px`}}>
<Logo />
</div>
</Link>
<div className="Nav--Links">
<NavLink to="/">Home</NavLink>
<NavLink to="/contact/">Contacto</NavLink>
<div className={`Nav--Group ${this.state.activeSubNav === 'about' ? 'active' : '' }`} >
<span className={`NavLink Nav--GroupParent ${
this.props.location.pathname.includes('about') ||
this.props.location.pathname.includes('team') ||
this.props.location.pathname.includes('news')
? 'active'
: ''
}`}
onClick={() => this.toggleSubNav('about')}
>
Nosotros
</span>
<div className="Nav--GroupLinks">
{subNav.map( (link, index)=> (
<NavLink
to={link.link}
key={'posts-subnav-link-' + index}
className="Nav--GroupLink">{link.name}</NavLink>
))}
</div>
</div>
</div>
<button
className="Button-blank Nav--MenuButton"
onClick={this.handleMenuToggle}
>
{active ? <X /> : <Menu />}
</button>
</div>
</nav>
)
}
}
export default ({ subNav }) => (
<Location>{route => <Navigation subNav={subNav} {...route} />}</Location>
)
the default position property is set to sticky in the nav.css file I want remove that and change it
dynamically depending of the modal gallery state, open or close.
this is my gallery component:
import React, { useState, useCallback } from "react";
import Gallery from "react-photo-gallery";
import Carousel, { Modal, ModalGateway } from "react-images";
const PhotoGallery = ({photos}) => {
const [currentImage, setCurrentImage] = useState(0);
const [viewerIsOpen, setViewerIsOpen] = useState(false);
const openLightbox = useCallback((event, { photo, index }) => {
setCurrentImage(index);
setViewerIsOpen(true);
}, []);
const closeLightbox = () => {
setCurrentImage(0);
setViewerIsOpen(false);
};
return(
<div>
<Gallery photos={photos} onClick={openLightbox} />
<ModalGateway>
{viewerIsOpen ? (
<Modal onClose={closeLightbox}>
<Carousel
currentIndex={currentImage}
views={photos.map(x => ({
...x,
srcset: x.srcSet,
caption: x.title
}))}
/>
</Modal>
) : null}
</ModalGateway>
</div>
)
}
export default PhotoGallery
the problem is that when the modal is open the nav still sticky and does not allow me access to the modal controls, like close and expand...and I need to change that.
There are a few approaches to this.
Old school classname toggling
Pass a prop down to the child component that reflects the state. On the child, use that prop to conditionally render one or more classes that represent the desired presentation.
Assign styles via style prop
This is similar to #1, but eliminates a layer of abstraction. Instead of assembling a class list you just assemble the CSS styles you'd like to apply as an object.
const Component = ({ someState }) =>
<div style={someState ? { border: "5px solid red" } : { color: "#999" }}>
Some Text
</div>
Use a CSS-in-JS library
The downside of the above approach is that you wind up duplicating styles for each instance of your element on the page. CSS-in-JS libraries solve this by extracting your styles into an automatically generated class and applying the class to your component instead. I prefer Emotion, but there are others.
Using Emotion you're able to accept a className prop from the parent that override the defaults set by the child. This inversion-of-control is really powerful and solves many of the shortcomings with early CSS-in-JS approaches.
const ParentComponent = () => {
const [someState] = useState(false)
return <ChildComponent css={{ color: someState ? "blue" : "red" }} />
}
const ChildComponent = ({ className }) =>
<div
css={{
color: "#000",
border: "4px solid currentColor"
}}
className={className}
>
Some Text
</div>
In the above example, className is assigned by Emotion using the generated class name assigned based on the css prop passed to ChildComponent inside of ParentComponent. The result of this would be a div with a blue border and blue text when someState is false (default). When someState is switched to true, the border and text will be red. This is because the styles passed in via className will override the styles assigned directly via css in Emotion.

How can i extract and combine inline css style properties

I read a couple of answers and all of them seems to assume you have a single object containing CSS instead of single properties, I tried the answers and I couldn't make it work, I was wondering what's wrong and what's the best way to do this, here is what I have done so far :
import React from 'react';
import FormLabel from '#material-ui/core/FormLabel';
const label = (props) => {
// <label onClick={props.onClick} className={props.className + ' ' + props.gridClass} style={props.inlineStyle} >{props.label}</label>
let divStyleArray = [];
if (typeof props.inlineStyle.background !== 'undefined') {
divStyleArray.push(props.inlineStyle.background)
delete props.inlineStyle.background
}
if (typeof props.inlineStyle.textAlign !== 'undefined') {
divStyleArray.push(props.inlineStyle.textAlign)
delete props.inlineStyle.textAlign
}
const customStyle = {
width: '100%'
}
const divStyle = Object.assign({}, ...divStyleArray);
return (
<div className={props.gridClass} style={{divStyle}}>
<FormLabel component="label" onClick={props.onClick} style={{ ...customStyle, ...props.inlineStyle }}>{props.label}</FormLabel>
</div>
)
}
export default label;
My goal is to extract a couple of CSS property, give it to the div and then give the rest to whats inside the div
Update 01:
I tried the answered given but it doesn't seem to work properly, here is what i did:
import React from 'react';
import FormLabel from '#material-ui/core/FormLabel';
const label = (props) => {
let inlineStyle = {
...props.inlineStyle
}
const divStyle = {
background: inlineStyle.background,
textAlign: inlineStyle.textAlign,
}
delete inlineStyle.background;
delete inlineStyle.textAlign;
const customStyle = {
width: '100%'
}
return (
<div className={props.gridClass} style={divStyle}>
<FormLabel component="label" onClick={props.onClick} style={{ ...customStyle, ...inlineStyle }}>{props.label}</FormLabel>
</div>
)
}
export default label;
First of all deleting stuff from the props object would be anti-pattern, or at least bad practice as far as I know.
If you only need the two properties you use there you could use this code:
const label = (props) => {
let divStyle = {
background: props.background,
textAlign: props.textAlign,
};
const customStyle = {
width: '100%'
}
return (
<div className={props.gridClass} style={{divStyle}}>
<FormLabel
component="label"
onClick={props.onClick}
style={{
...customStyle,
...props.inlineStyle,
background: undefined,
textAlign: undefined,
}}
>{props.label}</FormLabel>
</div>
)
}

Categories