styled-components with custom React components: passing props - reactjs

I have a custom component that takes in a property icon and sets the button's inner image to the corresponding icon as follows:
<custom-button-icon ref={showIcon} slot="right" icon="tier1:visibility-show" onClick={() => isPasswordVisible(false)} />
I would like to apply custom styling to a couple of instances of this component using styled-components. How can I pass the icon prop in so that it still functions (gets passed in as a pop of custom-button-icon)? This is what I have so far but it returns an empty button (with no icon):
export const d2lButtonIcon = ({ icon }) => {
const buttonIcon = styled(`d2l-button-icon`)`
display: flex;
align-items: center;
justify-content: center;
padding: 100px;
margin: 1px;
`
return <buttonIcon icon={icon} />
}
Thanks!

This is a really unusually case because this d2l-button-icon that you are dealing with a custom HTML element rather than a React component.
styled-components can style any React component and any built-in DOM element (a, div, etc.), but I don't think it knows how to deal with a custom DOM element because that's just not the way that we build things in React.
At first I tried passing the style to the d2l-button-icon element, and I got the icon prop to pass through but the style was ignored.
The way that I got this to work is to apply the styles to a div around the element and pass the other props to the d2l-button-icon element itself.
// function component adds styles around the icon button
const UnstyledD2L = ({ className, ...props }) => {
return (
<div className={className}>
<d2l-button-icon {...props} />
</div>
);
};
// can use styled-component to style this wrapper component
const StyledButtonIcon = styled(UnstyledD2L)`
display: flex;
align-items: center;
justify-content: center;
padding: 100px;
margin: 1px;
`;
// example usage with props
export default () => <StyledButtonIcon text="My Button" icon="tier1:gear" />;
However if you aren't super attached to this Brightspace package, I would recommend switching to a UI library that is designed for React.

Related

React styled component specificity issue for modal calling custom styled modal

I am working with styled component in React. I want to style a modal with the following style inside my component,
const StyledModal = styled(Modal)`
.modal-container-header {
display: none;
}
.modal-container-footer {
display: none ;
}
.modal-container-close {
display: none;
}
`;
That is, I want to omit the global footer, header for my custom modal. It is working fine for one case but not working for another.
Here is the entire component,
interface LoaderInterface {
loading: boolean;
loadingText?: string;
}
const StyledModal = styled(Modal)`
.modal-container-header {
display: none;
}
.modal-container-footer {
display: none ;
}
.modal-container-close {
display: none;
}
`;
const Loader: FC<LoaderInterface> = ({ loading, loadingText }) => {
console.log("this modal is working");
return (
<StyledModal onClose={() => loading === false} open={loading}>
<div>
<Loading size="large" />
<Heading type="h4">{loadingText}</Heading>
</div>
</StyledModal>
);
};
export default Loader;
It is working when a general React functional component is calling the custom styled modal
but it is not working when a modal calling this custom modal. When the modal is triggering the custom modal then the style of the calling modal is getting effected. But I closed the calling modal before calling this custom modal.
In DEV tool I'm always seeing, for example, for footer,
.hPyTDi modal-container-footer {
border-top: 1px solid rgba(0,0,0,0.06);
padding: 16px 24px;
text-align: right;
}
That is the global style. Not my custom style. when I apply display: none inside the above code in the dev tool obviously it is working.
But this issue doesn't persist when my custom modal is called by a React functional component. Only this issue appears when a modal (which is also a React component) calls this custom modal.
I also tried '&' to increase specificity but it doesn't work.
I did,
&.modal-container-footer {
display: none;
}
It doesn't work. If I move the custom modal style in a global place it works. But I don't want that as then this is applied for all modal where I want the header/footer to be visible.
Really struggling with this. Any help is much appreciated.
I've run into so many issues with the way Styled Components selectors, and come to realise that it's always 'safer' to pass props into them. This is because of the way SC dynamically assigns class names. Might this help?
const StyledModal = styled(Modal)`
${ ({ hidden }) => hidden && `
.modal-container-header, .modal-container-footer, .modal-container-close {
display: none;
}
`}
`;
const Example = () => (
<StyledModal hidden={()=>someFunction} />
)
For TypeScript, the syntax is similar:
const StyledModal = styled.div<{hidden: boolean}>(...)

Styled-Components Pass Props For Condition

I am trying to make a conditional icon with styled-components. But I need to pass props like function params. Because I need to change the icon with the condition.
export const RadioCheck = ({checked}) => styled(checked ? FaCheckCircle : FaRegCircle)`
display: flex;
align-self: center;
margin: 0 2em;
font-size: 1em;
color: ${props => props.checked ? primary : `transparent`}
`
Is there any way for this or I need to create two different components and use them?
Seems to me like you should use the condition where you return the icon to decide which component to use.
Something like
return(
<div>
{checked ?
<FaCheckCircle
//... your props
/>
:
<FaRegCircle
//... props
/>
}
</div>
)
If it's helpful to you, it is possible to style a styled component, such as
const FirstComponent = styled.div`
//some styling
`
const SecondComponent = styled(FirstComponent)`
//some more styling
`

Question about mixing styled-components with material-ui

Hello I am a react newbie and new to styling as well :)
I am trying to style the material-ui Button component with styled components
I am doing this by overriding global class names, I know material-ui introduced global class names like MuiButton-root etc
I am not clear on the use of "&" in parent selector, for example:
const StyledButton = styled(Button)`
&.MuiButton-root {
width: 500px;
}
.MuiButton-label {
color: ${props => props.labelColor};
justify-content: center;
}
`;
The above code works and can achieve the following:
The Button has a width of 500px
The label has a color of red (labelColor is passed in as a prop)
Please see sandbox below for full code
Question:
Why do I need "&" selector for the MuiButton-root, whereas for the MuiButton-label I don't?
Also is this the best way to style material-ui with styled components?
Please see the following sandbox: https://codesandbox.io/embed/practical-field-sfxcu
The '&' selector is used to target the classes and neighbouring elements/classes. Take a look at cssinjs. Its the underlying system behind MUI's styling.
But in short to answer your question;
const StyledButton = styled(Button)`
&.MuiButton-root { //this targets any class that is added to the main component [1]
width: 500px;
}
.MuiButton-label { //in css-in-js this is effectively '& .MuiButton-label [2]
color: ${props => props.labelColor};
justify-content: center;
}
[1] Targets main classes on component
<StyledButton className='test bob'> << this element is targeted
</StyledButton>
[2] Targets child elements either through class or element type. Note the space between & and the actual class.
<StyledButton className='test bob'>
<span className='MuiButton-label'>test</span> << this element is targeted instead
</StyledButton>
You can also use the element tag directly
span {
color: ${props => props.labelColor};
justify-content: center;
}

Add styling to child element using Styled Components?

Say I want to create a styled component called <NoPrint /> that adds the following CSS to any element passed as child:
#media print
{
display: none !important;
}
How can I do that?
I want to be able to write a styled component like this:
<NoPrint><p>Some paragraph</p></NoPrint>
<NoPrint><Some>Some React component</Some></NoPrint>
I know that I can write:
const NoPrint = styled.div`
#media print
{
display: none !important;
}
`
render(
<NoPrint>
<Something />
</NoPrint>
)
But this component creates a wrapping div, which I do not want.
This is because you have to pass in the component you want to style, IE:
Style a component without wrapper
const NoPrint = styled(myComponent)`
#media print
{
display: none !important;
}
`
this will apply a custom style directly to an element without a wrapper.
Polymorphic prop
Another way is to use a "as" polymorphic prop
const Component = styled.div`
#media print
{
display: none !important;
}
`;
render(
<Component
as="button"
onClick={() => alert('It works!')}
>
Hello World!
</Component>
)
The only way I can see to figure something without having a div would be to have your media query in a css stylesheet with a classname, and pass that classname to your elements.
something like
#media print
.noPrint{
display: none !important;
}
<p class="noPrint" >Some paragraph</p>
To make an element without a div, there is React.Fragment available, but you can't style those...
I don't know if
Realistically you cant escape the wrapping div. You will have to either wrap it somewhere or, create a static styled component that can be included inside other styled components by referencing it.
Example
You can put this in like a style helper file
export const NoPrint = styled.div`
#media print
{
display: none !important;
}
then you can include it in other styled components. But it will not be wrappable around other components without an associated element like a span or div.

Styled Components & React: Click event

I am trying to create an onClick for a Styled Components Component, but it is not working. It is not logging hello in the console.
<Component onClick={this.handleClick} />
The handleClick:
handleClick(e) {
console.log("hello");
}
This is also in the constructor:
this.handleClick = this.handleClick.bind(this);
The component:
const Component = styled.span`
/* Regular CSS */
${css};
`;
Thanks in advance!
EDIT:
The component is nested in a Button component. When I add the onClick to the Button Component hello is shown in the console. However I want the child to have this function not the parent.
EDIT 2:
Ok so when I change the CSS it works. I have no idea why. This is my css:
top: 0;
left: 0;
width: 10px;
height: 10px;
display: block;
z-index: 0;
position: absolute;
overflow: hidden;
border-radius: inherit;
We probably need more code. Anyway the cleanest way to bind is by arrow functions:
handleClick = () => console.log('click')
If your Button is INSIDE you have to use another props as onClick is binded to onClick event.
Try
// In Parent
<Parent action={this.handleClick} />
// In Child
<Child onClick={this.props.action} />
Your span is rendered with a css value of display: inline. If there is no actual content in it to expand, it will be 0 pixels in width, and therefore you are not actually clicking on it.
I ran into the same issues and in Styled Components I couldn't make it work with a forced display: block. You should use a clickable item, like styled.button...
You might want to try this:
handleClick = e => {
console.log('hello');
}
...
<Component onClick={(e) => this.handleClick(e)} />
or
<Component onClick={this.handleClick.bind(e)}/>

Resources