I started rewriting CSS into React Components but i've encountered a problem, I do know how to do this (using styled-components btw ) :
You have 5 ways to styling component in React.
Every approach have pros & cons(personally I use 4-th ways.)
1.Inline Css
const divStyle = {
color: 'blue',
backgroundImage: 'url(' + imgUrl + ')',
};
function HelloWorldComponent() {
return <div style={divStyle}>Hello World!</div>;
2. CSS in JS
const Button = (props) => (
<button className={ 'large' in props && 'large' }>
{ props.children }
<style jsx>{`
button {
padding: 20px;
background: #eee;
color: #999
}
.large {
padding: 50px
}
`}</style>
</button>
)
/* Creates a regular button */
<Button>Hi</Button>
/* Creates a large button */
<Button large>Big</Button>
3. Styled Components
const Wrapper = styled.section`
padding: 4em;
background: papayawhip;
`;
render(
<Wrapper>
<Title>
Hello World!
</Title>
</Wrapper>
);
4. Css Modules (scss || sass)
#width: 10px;
#height: #width + 10px;
#header {
width: #width;
height: #height;
}
5. Stylable - https://github.com/wix/stylable
import React from 'react';
import styled, {css} from 'styled-components';
const Info = styled.div`
display: flex;
// etc.
${props => props.title && css`font-size: 15px`}
`
const Box = styled.div`
&:first-of-type {
// some more css
}
`
// usage
<Info title>some info</Info>
I recommend you follow the official docs as stated in comments by #Mikkel
Related
I noticed that my styles on a component look right in storybook but not in the running application. The component code is at the bottom of this post.
All the components are from MUI, and looking at the "Computed" tab of the Chrome inspector, I can see that the Avatar components are getting their styles differently in the two environments -- the priorities of the classes are swapped:
In the app:
In the storybook
Wrapping the styles in && fixes the problem, and I've used this solution before for MUI style issues, but I've never come across the case where it behaves differently in the app vs storybook.
Here's my main decorator in preview.tsx
export const decorators: ComponentStoryObj<React.FC>['decorators'] = [
story => {
return (
<>
<header>
<link
href='https://fonts.googleapis.com/css?family=Roboto:100,200,300,400,500,600,700'
rel='stylesheet'
/>
<link
href='https://fonts.googleapis.com/css?family=Roboto+Condensed:400,500,600,700'
rel='stylesheet'
/>
</header>
<AppWrapperWithUser>{story()}</AppWrapperWithUser>
</>
);
},
];
Here's the AppWrapper used above, which also wraps the application itself (so that the styles match)
import { ThemeProvider as MuiThemeProvider } from '#mui/material/styles';
import { ThemeProvider as StyledThemeProvider } from 'styled-components';
import { globalStyles, theme } from './theme';
type Props = {
store?: ReturnType<typeof createStore>;
initialState?: RootState;
};
export const AppWrapper: React.FC<Props> = ({ children, store, initialState }) => {
const appStore = store || createStore(initialState);
return (
<MuiThemeProvider theme={theme}>
<StyledThemeProvider theme={theme}>
{globalStyles}
<Provider store={appStore}>{children}</Provider>
</StyledThemeProvider>
</MuiThemeProvider>
);
};
And the component in question:
const S = {
Wrapper: styled(Paper)`
padding: 32px;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;Ø
overflow: hidden;
`,
Pane: styled.div<{ centered?: boolean }>`
display: flex;
flex-direction: column;
gap: 4px;
align-items: ${p => p.centered && 'center'};
`,
Subtitle: styled(Typography).attrs({ variant: 'body2' })`
font-size: 14px;
text-transform: uppercase;
`,
Title: styled(Typography)`
&& {
font-size: 28px;
font-weight: 700;
}
`,
Avatar: styled(Avatar)`
height: 84px;
width: 84px;
box-sizing: content-box;
border: 2px solid rgba(0, 0, 0, 0.1);
`,
TeamLogoWrapper: styled.div`
display: flex;
align-items: center;
height: 100%;
width: 222px;
`,
TeamLogo: styled(Avatar)`
height: 222px;
width: 222px;
box-sizing: content-box;
opacity: 0.3;
border: 2px solid rgba(0, 0, 0, 0.1);
position: absolute;
`,
};
const PlayerDetailHeader = ({ player }: { player: ProcessedPropUIPlayer }) => {
const { ... } = player;
const infoStr = [teamName, `#${uniformNumber}`, position].join(' | ');
return (
<S.Wrapper data-testid={`PlayerDetailHeader-${playerId}`}>
<S.Avatar alt={playerName} src={playerImg} />
<S.Pane>
<S.Title>{playerName}</S.Title>
<S.Subtitle>{infoStr}</S.Subtitle>
</S.Pane>
<S.Pane centered>
<S.Title>{openProps}</S.Title>
<S.Subtitle>Open Props</S.Subtitle>
</S.Pane>
<S.Pane centered>
<S.Title>{dollarFormatter(openAction)}</S.Title>
<S.Subtitle>Open Action</S.Subtitle>
</S.Pane>
<S.TeamLogoWrapper>
<S.TeamLogo alt={teamName} src={teamImg} />
</S.TeamLogoWrapper>
</S.Wrapper>
);
};
My problem is about props. I want to use nested components with props in styled-components. For example:
const MenuItem = ({ item }) => {
const router = useRouter();
const [isOpen, setIsOpen] = useState(false);
const isActive = router?.asPath === item?.path;
return (
<MenuItemWrapper key={item?.slug} onClick={() => setIsOpen(!isOpen)}>
<Link href={item?.path}>
<InnerMenuItem isActive={isActive}>
{item?.prefix && <Prefix>{item?.prefix}</Prefix>}
{item?.label}
{item?.children && <RightArrow isOpen={isOpen} />}
</InnerMenuItem>
</Link>
<Children>
{
item?.children?.map((child) => <MenuItem item={child} />)
}
</Children>
</MenuItemWrapper>
);
};
export default MenuItem;
this is MenuItem component. I use MenuItem component as a recursion component.
in styled-component i tried this but it doesnt work. I want to apply different style in Children > InnerMenuItem but it not working
export const Children = styled.div`
display: flex;
flex-direction: column;
margin-left: 65px;
${MenuItemWrapper} {
font-size: 16px;
padding: 9px 0;
&:not(:first-child) {
border-top:none;
}
}
${InnerMenuItem} {
${({ isActive }) => // HOW CAN I USE THIS PROPS HERE
isActive &&
css`
color: orange
`};
}
`;
from styled components official documentations:
"If the styled target is a simple element (e.g. styled.div), styled-components passes through any known HTML attribute to the DOM. If it is a custom React component (e.g. styled(MyComponent)), styled-components passes through all props."
example :
const Input = styled.input`
color: ${props => props.inputColor || "palevioletred"};
`;
return(
<Input inputColor="rebeccapurple" />
)
another way is by Extending Styles, example :
const Button = styled.button`
color: palevioletred;
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
const TomatoButton = styled(Button)`
color: tomato;
border-color: tomato;
`;
return(
<div>
<Button>Normal Button</Button>
<TomatoButton>Tomato Button</TomatoButton>
</div>
);
more about styled-components read here
have you tried
${InnerMenuItem} {
color: ${({isActive})=> isActive ? 'orange' : undefined}
};
I have 6 div elements that I want to be able to click one of and have the className change for just the div I've clicked on. Currently, when I click on one div, ALL the div classNames change. This idea came from a vanilla JS concept that I'm trying to convert into a React component. I'm not sure where/what is going wrong, if anyone can tell me how to prevent the sibling divs' onClicks from being fired or if what I have is wrong fundamentally, I would be eternally grateful. This is what I have so far:
import React, { useState} from "react";
import { Panels } from "../../components/index";
import { data } from "../../constants/index";
import "./gallery.css";
const Gallery = () => {
const [isOpen, setIsOpen] = useState(false);
const toggleOpen = () => {
setIsOpen(!isOpen);
};
return (
<div className="panels">
{data.restaurants.map((restaurant, index) => (
<div
className={`panel panel${index} ${isOpen ? "open open-active" : ""}`}
onClick={toggleOpen}
>
<Panels
key={restaurant.name + index}
description={restaurant.description}
name={restaurant.name}
website={restaurant.website}
/>
</div>
))}
</div>
);
};
export default Gallery;
This is my Panels Component code:
import React from "react";
const Panels = ({ name, description, website }) => {
return (
<div className="panel_text">
<p>{description}</p>
<p>{name}</p>
<a href={website}>
<p>Visit {name}</p>
</a>
</div>
)};
export default Panels;
Aaaand this is my CSS code:
*, *:before, *:after {
box-sizing: inherit;
}
.panels {
min-height: 100vh;
overflow: hidden;
display: flex;
}
.panel, .panel_text {
background: '#fff';
box-shadow: inse 0 0 0 5px rgba(255, 255, 255, 0.1);
color: var(--color-golden);
text-align: center;
align-items: center;
transition:
font-size 0.7s cubic-bezier(0.61, -0.19, 0.7, -0.11),
flex 0.7s cubic-bezier(0.61, -0.19, 0.7, -0.11);
font-size: 20px;
background-size: cover;
background-position: center;
flex: 1;
justify-content: center;
display: flex;
flex-direction: column;
}
.panel0 {background-image: url(../../assets/defuegocarousel.jpg);}
.panel1 {background-image: url(../../assets/HaydenslakeSz.jpg);}
.panel2 {background-image: url(../../assets/stonecliffSz.jpg);}
.panel3 {background-image: url(../../assets/shigezooutside.png);}
.panel4 {background-image: url(../../assets/southparkSz.jpeg);}
.panel5 {background-image: url(../../assets/lechonoutside.jpg);}
.panel > * {
margin: 0;
width: 100%;
transition: transform 0.5s;
flex: 1 0 auto;
display: flex;
justify-content: center;
align-items: center;
}
.panel > *:first-child {transform: translateY(-100%);}
.panel.open-active > *:first-child {transform: translateY(0); }
.panel > *:last-child { transform: translateY(100%); }
.panel.open-active > *:last-child {transform: translateY(0); }
.panel_text p, a {
text-transform: uppercase;
}
.panel p:nth-child(2) {
font-size: 4rem;
}
.panel.open {
flex: 5;
font-size: 40px;
}
#media only screen and (max-width: 600px) {
.panel p {
font-size: 1rem;
}
}
You're saving a boolean value as a div element is open or not. So this value is considered for all div element's because there is no identifier which div element is open. You need to save a div element value to identify the open div element.
So you can use a div element's index instead of a boolean value. For example, try the below code.
import React, { useState} from "react";
import { Panels } from "../../components/index";
import { data } from "../../constants/index";
import "./gallery.css";
const Gallery = () => {
const [isOpen, setIsOpen] = useState(null);
return (
<div className="panels">
{data.restaurants.map((restaurant, index) => (
<div
className={`panel panel${index} ${isOpen === index ? "open open-active" : ""}`}
onClick={() => setIsOpen(index)}
>
<Panels
key={restaurant.name + index}
description={restaurant.description}
name={restaurant.name}
website={restaurant.website}
/>
</div>
))}
</div>
);
};
export default Gallery;
your isOpen state is common between all your div's
you should specify a unique value for isOpen of each div
you can change your isOpen state to an object like this :
import React, { useState} from "react";
import { Panels } from "../../components/index";
import { data } from "../../constants/index";
import "./gallery.css";
const Gallery = () => {
const [isOpen, setIsOpen] = useState({});
const toggleOpen = (index) => {
setIsOpen(prevState => {...prevState ,[index]:!(!!prevState[index]) });
};
return (
<div className="panels">
{data.restaurants.map((restaurant, index) => (
<div
className={`panel panel${index} ${isOpen[index] ? "open open-active" : ""}`}
onClick={()=>toggleOpen(index)}
>
<Panels
key={restaurant.name + index}
description={restaurant.description}
name={restaurant.name}
website={restaurant.website}
/>
</div>
))}
</div>
);
};
export default Gallery;
I am trying to build this simple component which takes title and Icon Component as props and renders them. The icons I use here are third party components like the ones from Material UI.
option.component.jsx
import { Wrapper } from './option.styles';
function Option({ title, Icon }) {
return (
<Wrapper>
{Icon && <Icon />}
<h4>{title}</h4>
</Wrapper>
);
}
option.styles.js
import styled from 'styled-components';
export const Wrapper = styled.div`
display: flex;
color: grey;
&:hover {
color: white;
}
`;
// export const Icon =
I've organized all my styles in a separate file and I intend to keep it that way.
I want to style <Icon /> , but I don't want to do it inside Option Component like this.
import styled from 'styled-components';
import { Wrapper } from './option.styles';
function Option({ title, Icon }) {
const IconStyled = styled(Icon)`
margin-right: 10px;
`;
return (
<Wrapper>
{Icon && <IconStyled />}
<h4>{title}</h4>
</Wrapper>
);
}
What is the best way to style a component passed as a prop while maintaining this file organization?
I've looked through the documentation and I wasn't able find anything related to this. Any help would be appreciated.
You can do this in 2 ways:
1. As a SVG Icon (svg-icon):
option.styles.js as:
import styled from "styled-components";
import SvgIcon from "#material-ui/core/SvgIcon";
export const Wrapper = styled.div`
display: flex;
color: grey;
&:hover {
color: black;
}
`;
export const IconStyled = styled(SvgIcon)`
margin-right: 10px;
`;
And in your component, do like that:
import { Wrapper, IconStyled } from "./option.styles";
function Option({ title, Icon }) {
return (
<Wrapper>
{Icon && <IconStyled component={Icon} />}
<h4>{title}</h4>
</Wrapper>
);
}
const App = () => {
return (
<>
<Option title="title" Icon={HomeIcon}></Option>
<Option title="title" Icon={AccessAlarmIcon}></Option>
</>
);
};
2. As a Font Icon (font-icons):
Import material icons in <head>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
option.styles.js as:
import styled from "styled-components";
import Icon from "#material-ui/core/Icon";
export const Wrapper = styled.div`
display: flex;
color: grey;
&:hover {
color: black;
}
`;
export const IconStyled = styled(Icon)`
margin-right: 10px;
`;
And in your component, do like that:
import { Wrapper, IconStyled } from "./option.styles";
function Option({ title, icon }) {
return (
<Wrapper>
{icon && <IconStyled>{icon}</IconStyled>}
<h4>{title}</h4>
</Wrapper>
);
}
const App = () => {
return (
<>
<Option title="title" icon='star'></Option>
<Option title="title" icon='home'></Option>
</>
);
};
I'm using styled-system with styled components and have a basic case like this:
const buttonFont = {
fontFamily: "Chilanka"
};
// style a boilerplate for text
const Text = styled.div`
${typography}
${color}
`;
// button blueprint
const Button = ({ children, ...rest }) => {
return (
<Text as="button" {...buttonFont } {...rest}>
{children}
</Text>
);
};
// styled button using button
const StyledButton = styled(Button)`
color: white;
background-color: gray;
padding: 10px 20px;
border: 2px solid black;
`;
// When using "as" this component does not includes buttonFont styles
const StyledLabel = styled(StyledButton).attrs({
as: "label"
})``;
I want to create a StyledLabel which will inherit all styles from StyledButton, but change tag to label. But StyledLabel does not get the buttonFont styles when using "as" attribute.
Please see live example here: demo
I'm not sure what your end goal is, but these 2 examples worked in terms of inheritance. However, they might not help with your plan for composition:
import React from "react";
import styled, {css} from "styled-components";
import { typography, color } from "styled-system";
import ReactDOM from "react-dom";
import "./styles.css";
const buttonFont = {
fontFamily: "Chilanka"
};
const Text = styled.div`
${typography}
${color}
margin: 24px;
`;
const StyledButton = styled(Text)`
color: white;
background-color: gray;
padding: 10px 20px;
border: 2px solid black;
`;
const StyledLabel = styled(StyledButton)`
color: yellow;
`;
const __Text = styled.div`
${typography(buttonFont)}
${color}
margin: 24px;
`;
const __StyledButton = styled(__Text)`
color: white;
background-color: gray;
padding: 10px 20px;
border: 2px solid black;
`;
const __StyledLabel = styled(__StyledButton)`
color: yellow;
`;
function App() {
return (
<div className="App">
<StyledButton as="button" {...buttonFont}>styled button</StyledButton>
<StyledLabel as="label" {...buttonFont}>Does inherit styled font with "as"</StyledLabel>
<br />
<br />
<br />
<__StyledButton as="button">styled button</__StyledButton>
<__StyledLabel as="label">Does inherit styled font with "as"</__StyledLabel>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);