I've been struggling with a styling issue for a while, but the basic issue is that I want to style every instance of <StyledButton> differently after the first. To do this, I'm targeting the wrapping element (attributeset-row className) and the remove-btn className (for <StyledButton>) like so:
const StyledHorizontalAttributesTable = styled(StyledHorizontalAttributes)`
& .attributeset-row:not(:first-child) .remove-btn {
background-color: lightblue;
}
`;
I have discovered the issue is that the CSS styles are not being applied to the components, due to my targeting of classNames - as you can see below I am passing in classNames to the relevant components (and they are showing in the browser), but along with a lot of other what looks to be jargon:
Can anyone explain where I might be going wrong with this in terms of applying specific CSS styles to StyledComponents (usually this type of styling isn't needed) but I need to style all <StyledButton>'s after the first differently.
Here's my code:
const StyledButton = styled(Button)`
margin-top: 14px;
`;
const StyledHorizontalAttributesTable = styled(StyledHorizontalAttributes)`
& .attributeset-row:not(:first-child) .remove-btn {
background-color: lightblue;
}
`;
return (
<div className={className}>
{objects.map((enteredObject, index) => (
<RepeatableAttributeSetContextProvider
form={form}
object={enteredObject}
key={`${enteredObject.key}-${enteredObject.repeatIndex}`}
>
<StyledHorizontalAttributesTable className="attributeset-row">
{enteredObject.attributeCollection.questions
.filter(filterRepeatAttributes)
.map((attribute) => (
<Fragment key={attribute.key}>
{renderAttribute(enteredObject, attribute, formLayout)}
</Fragment>
))}
<StyledButton
className="remove-btn"
type="link"
buttonStyle="LINK"
name="delete"
dataId={`delete-${enteredObject.key}-${index}`}
isOberonIcon
isIconButton
icon="bin"
onClick={() => onRemove(enteredObject)}
>
<Message id="Form.Button.Remove" defaultMessage="Remove" />
</StyledButton>
</StyledHorizontalAttributesTable>
</RepeatableAttributeSetContextProvider>
))}
</div>
);
I would use the adjacent sibling combinator. This will target all .attributeset-rows but the first one.
const StyledHorizontalAttributesTable = styled(StyledHorizontalAttributes)`
& + & {
.remove-btn {
background-color: lightblue;
}
}
`;
Here's a simple version of this example over at CodeSandbox. https://codesandbox.io/s/serene-swanson-frcb4?file=/src/App.js
Related
Currently I have the following code:
FormInput.jsx:
import './FormInput.scss';
const FormInput = ({ label, ...otherProps} ) => {
const { value } = otherProps;
return (
<div className="group">
<input className="formInput" {...otherProps} />
{label && (
<label
className={`${value.length ? 'shrink' : ''} formInput-label`}
>{label}</label>
)}
</div>
)
};
export default FormInput;
In FormInput.scss:
.formInput-label {
transition: 300ms ease all;
position: absolute;
font-size: 18px;
left: 5px;
top: 10px;
&.shrink {
top: -14px;
}
}
What I'm confused about is that the targeted class of the label in FormInput.jsx should compile to:
.shrink .formInput-label
But the one in the SASS file will compile to:
.formInput-label.shrink
My question is how does the correct styling still get applied if the class targeting in the JSX and SASS files aren't the same? The JSX has the classes in reverse order and a space in between.
The class attribute (or className in JSX) defines a list of classes. The syntax for classes is different in HTML and in CSS, but the order of classes on the same element does not matter in either.
JSX renders to HTML, so it renders as class="shrink formInput-label", which defines the list of classes ["shrink", "formInput-label"] and is compatible with the CSS selector .shrink.formInput-label.
Also, a CSS selector such as .shrink would match the element as well (just with a lower specifity rating), because it matches one of the classes in the list.
Im trying to learn and convert my project from css to styled component(https://styled-components.com/), at the moment i have converted all my other components except one component where i am stuck, checked others examples from stackoverflow but it was not same kind.
I have conditional class names
My question is how to convert InfoBox component to use styled component ? do i need to inject the styles through some kind of styled-component wrapper or thats not needed ?
english is not my mother language so could be mistaked
my code:
import React from 'react'
import "./InfoBox.css"
function InfoBox({ isRed, active, activetored, ...props }) {
return (
<div onClick={props.onClick}
className={`infoBox ${active && "infoBox--selected"}
${activetored && "infoBox--selectedtored"}
${isRed && "infoBox--red"} `} >
</div>
)
}
export default InfoBox
<div className="app__stats">
<InfoBox
isRed
active={typeofCase === "cases"}
onClick={(e) => setTypeofCase('cases')}
/>
<InfoBox
isGreen
active={typeofCase === "recovered"}
onClick={(e) => setTypeofCase('recovered')}
/>
<InfoBox
isRed
activetored={typeofCase === "deaths"}
onClick={(e) => setTypeofCase('deaths')}
/>
</div>
css is like this (you can put whatever):
. infoBox--selected {
border-top: 10px solid greenyellow;
}
. infoBox--selectedtored {
border-top: 10px solid red;
}
. infoBox--red {
border-color: darkblue;
}
One of the ideas behind styled-component is to avoid classnames.
Instead of setting the css by class, you have few options. the easiest one will probably be to use your props inside the css code, and change the style by it:
const InfoBox = styeld.div`
border-color: ${props => props.isRed ? 'darkblue' : 'black'};
border-top: ${props => props.active ? '10px solid greenyellow' : 'red'};
...
`;
this way, you don't need classnames (although it can be done with it too, obviously).
Now, instead of the div inside the component, use the InfoBox styled component we just wrote and you good to go.
I have a button which need to be styled using styled component. As I am new to react I need your help in getting button color change to green on success and blue on failure. Also I have class card-preview which need to have box shadow when I hover on it while using styled component .The below code will give you an idea what I am trying to do.
import styled from "styled-components";
const Button = styled.button`
border: none;
border-radius: 10px;
padding: 7px 10px;
font-size: 1.3rem;
background: ${(props) => (props.success ? "green" : "blue")};
color: white;
`;
const Cardlist = ({ cards, title }) => {
return (
<div className="card-list">
<h2>{title}</h2>
{cards.map((card) => (
<div className="card-preview" key={card.id}>
<h2>{card.name}</h2>
if({card.status}==='success')
<Button success>{card.status}</Button>
else
<Button pending>{card.status}</Button>
</div>
))}
</div>
);
};
export default Cardlist;
For the button and CSS I think what you have should be working, though it could be more DRY. The if-else isn't valid in JSX anyway. I'm also not sure what you do with the pending prop, but it appears to be the antithesis of the success "state".
const Button = styled.button`
border: none;
border-radius: 10px;
padding: 7px 10px;
font-size: 1.3rem;
background: ${(props) => (props.success ? "green" : "blue")};
color: white;
`;
...
{cards.map((card) => (
<div className="card-preview" key={card.id}>
<h2>{card.name}</h2>
<Button
success={card.status === 'success'}
pending={card.status !== 'success'} // <-- may not be necessary
>
{card.status}
</Button>
</div>
))}
To apply some style to the card-preview div you just need to convert that to a styled-component as well.
const CardPreview = styled.div.attrs(() => ({
className: "card-preview",
}))`
:hover { // <-- sometimes you need &:hover to get the parent node
// apply box-shadow and any other CSS rules here
}
`;
Then replace the div with CardPreview component.
{cards.map((card) => (
<CardPreview key={card.id}>
<h2>{card.name}</h2>
<Button success={card.status === 'success'} >
{card.status}
</Button>
</CardPreview>
))}
See Pseudoselectors & Nesting
Update: Reagarding .attrs
This is a chainable method that attaches some props to a styled
component.
It simply allows you to specify prop values when the component is created versus when it's being used. The className prop looks to be a static "card-preview" so may as well set it once and forget it (in this specific case).
I'm using Input from Semantic UI in order to create a search input:
import React from 'react';
import { Input } from 'semantic-ui-react';
export default ({ placeholder, onChange }) => {
return (
<Input
icon="search"
icon={<img src={searchIcon} />}
iconPosition="left"
placeholder={placeholder}
onChange={onChange}
/>
);
};
It works and looks good.
The problem is that I need to change its icon with an svg image. So the svg is imported in the file and used like this:
import React from 'react';
import { Input } from 'semantic-ui-react';
import searchIcon from '../../assets/icons/searchIcon.svg';
export default ({ placeholder, onChange }) => {
return (
<Input
icon={<img src={searchIcon} />}
iconPosition="left"
placeholder={placeholder}
onChange={onChange}
/>
);
};
The problem is that it puts the icon outside of the input and on the right side of it.
It should be inside the input and on the left part.
There were no styling changes after the svg was added, why isn't it in the same position as the original icon?
Most likely semantic-ui adding special styles when we add some icon by attribute "icon". Semantic-ui-react doesn't support custom icons. :,(
In the type declaration we can read:
/** Optional Icon to display inside the Input. */icon?: any | SemanticShorthandItem<InputProps>
My proposition: add some styles to CSS, like me in the sandbox
.input {
position: relative;
width: fit-content;
display: flex;
justify-content: center;
align-items: center;
}
img {
position: absolute;
right: 5px;
width: 10px;
}
I got it working by passing a custom component where the svg image is wrapped by an i tag that has a an icon class:
const CustomIcon = (
<i className="icon">
<img width={38} height={38} src={searchIcon} />
</i>
);
const App = () => {
return (
<Input icon={CustomIcon} iconPosition="left" placeholder="placeholder" />
);
};
The benefit to this approach is that you can change the iconPosition without it breaking the styling with this approach.
To give more context the icon getting displayed at the right position is due to the styles applied to this selector: .ui.icon.input>i.icon. Because it expects an i tag the styles won't be applied if you don't wrap the image between i tags.
Using styled-components, I am trying to style a nested <Input /> component that I have created, which is being used within a different component that has a dropdown appear when typing. I need to add padding-left: 3rem to this nested input but I cannot access it from the component <Dropdown />.
<Dropdown
options={options}
/>
The above is imported where I need it. I need to access the below input from the above <Dropdown />.
<div>
<Input {...props}/> // I need to edit the padding in this component
// rendered input unique to this new component would go here
</div>
The above <Input /> is imported from another component which is used in all instances where I require an input.
export const Dropdown = styled(DropDown)`
padding-left: 3rem !important;
`;
The component works fine but this fails to affect the inner padding of the Input that I need to target.
What do I do?
From what you've said, I'd suggest that the dependency of padding the Input component is with your Dropdown (which you seem to realise already).
Therefore you'd be better off having that "unqiue" styling coupled with your Dropdown component via a wrapping styled component within it.
The following example is crude (and by no means complete or working), but hopefully it illustrates how the ownership of the padding-left should be within the Dropdown and not a sporadic styled component floating some where else in your code base.
./Input/Input.jsx
const Input = ({ value }) => (
<input value={value} />
);
./Dropdown/styled.js
const InputWrapper = styled.div`
position: relative;
padding-left: 3rem !important; /* Your padding */
`;
const Icon = styled.div`
position: absolute;
top: 0;
left: 0;
width: 3rem;
height: 3rem;
background: blue;
`;
const Menu = styled.ul`/* whatever */`;
./Dropdown/Dropdown.jsx
import Input from '...';
import { InputWrapper, Icon, Menu } from './styled';
const Dropdown = ({ options }) => (
<div>
<InputWrapper>
<Icon />
<Input value={'bleh'} />
</InputWrapper>
<Menu>{options}</Menu>
</div>
);
This setup will promote reusable self-contained components.
Figured out the solution below:
export const StyledInput = styled.div`
&& #id-for-input { // specifically on the <Input />
padding-left: 3rem !important;
}
`;
<StyledInput>
<Dropdown />
</StyledInput>