ReactJS and Less, set dynamic class - reactjs

I have a component like this:
import '../MLExtraIcon.less';
const MLExtraIcon = ({ text, color }) => (
<span className={`ml-extra-icon ml-extra-icon-color-${color}`}>
{text}
</span>
);
So, I call <MLExtraIcon> components with 2 props: text and color
But currenly, I have hardcoded the "color" as a class in a less file which is:
// MLExtraIcon.less
#import "../colors";
.ml-extra-icon {
&.ml-extra-icon-color-passion {
background-color: #passion;
}
}
And in the imported colors file I have a bunch of variables like:
// colors.less
#opportunities: #F47521;
#innovation: #1976BD;
#passion: #CB198A;
// etc...
How could I select dynamically the variable to use in the .less file? I mean, my problem is here:
.ml-extra-icon {
&.ml-extra-icon-color-passion {. <--- that's hardcoded, I would like to know if
background-color: #passion;
}
}
...if there's a way to give the variable name as a parameter, for instance:
.ml-extra-icon {
&.ml-extra-icon-color-$color {
background-color: $color;
}
}
(I'm inventing that syntax, in order to explain what I want to achieve). I don't want to create different css clases for each already declared less variable (like the hardcoded example)
Any tip?

If you want to do it dynamic you can use styled-components. Example codesandbox
import React from "react";
import styled from "styled-components";
export default function App() {
const ColorWrapper = styled.span`
color: ${(props) => props.color};
`;
const MLExtraIcon = ({ text, color }) => (
<ColorWrapper color={color}>{text}</ColorWrapper>
);
return (
<div className="App">
{MLExtraIcon({ text: "Your passion", color: "blue" })}
</div>
);
}
Also you can create global theme file instead of local const theme, to use it throughout the project.

Related

React Styled components, how to get props type of component wchich returns different elements

I decided to change the styling in the project from css modules to styled components.
I had a problem with one component, which, depending on the props, returns different elements - a button or a link.
here is code of it:
//type
export type ButtonProps = UIComponentProps &
React.ComponentPropsWithoutRef<"button"> & {
type?: "button";
};
export type AnchorProps = UIComponentProps &
React.ComponentPropsWithoutRef<"a"> & {
type: "link";
external?: boolean;
};
export type CustomButtonProps = ButtonProps | AnchorProps;
//component
import Link from "next/link";
import { CustomButtonProps } from "../Button";
const Button = (props: CustomButtonProps) => {
if (props.type === "link") {
// eslint-disable-next-line #typescript-eslint/no-unused-vars
const { type: _, external, ...anchorProps } = props;
const { children, disabled, href } = anchorProps;
if (disabled) {
return <button disabled={true}>{children}</button>;
} else {
return external ? (
<a {...anchorProps}>{children}</a>
) : (
<Link {...anchorProps} href={href || "#"}>
{children}
</Link>
);
}
}
const { children } = props;
return <button {...props}>{children}</button>;
};
export default Button;
When I used css modules, to change the styles, I just sent the className which became at the end of the list of classes and overlapped the previous styles
<Button className={styles.heroButton}>{HeaderText.heroButton}</Button>
// if pass 'type={"link"}' it become link el
Now i use styled components extend functionality
export const HeroButton = styled(Button)`
width: 60px;
height: 60px;
#media screen and (max-width: 425px) {
width: 50px;
height: 50px;
}
`;
If i extend a component that returns a single element all is working fine
But with Button multi component typescript not getting types
Is there a way to get types and not seperate multi component to two different?

Styled-components add styles to custom component

I want to add styles to the custom component with a help of the styled-components library. I found some similar questions on StackOverflow with answers but it seems not to work for me. I do something like this:
Here is my custom component
const ComponentStyledBox = styled.div`
color: red;
`;
const ComponentStyled = () => (
<ComponentStyledBox>Component With StyledComponents</ComponentStyledBox>
);
And here I try to use my custom component and add some styles to it
const ComponentStyledWrapper = styled(ComponentStyled)`
background-color: green;
`;
export default function App() {
return (
<div>
<ComponentStyledWrapper />
</div>
);
}
As the result, I don't have a green background. Only red text. The same will be if I use instead of the styled custom component just a component with styles added via CSS in a common way. What do I do wrong?
For your convenience here is a CodeSandbox
Thanks in advance!
The problem is caused by this:
const ComponentStyled = () => (
<ComponentStyledBox>Component With StyledComponents</ComponentStyledBox>
);
You've defined a new component called ComponentStyled which is not style-able. Try this instead (in App.styles.tsx):
import { ComponentStyledBox } from "./components/ComponentStyled/ComponentStyled.styles";
export const ComponentStyledWrapper = styled(ComponentStyledBox)`
background-color: green;
`;
ComponentStyledBox is style-able, so you'll get the green background.
Edit (response to #zakharov.arthur's comment): If really do want a custom component with hard-coded children (are you sure this is what you want?), you can make your custom component style-able by exposing ComponentStyledBox's props:
// I haven't actually tried this but it should work.
const ComponentStyled = (props: Omit<Parameters<ComponentStyledBox>[0], 'children'>) =>
<ComponentStyledBox {...props}>Component With StyledComponents</ComponentStyledBox>

Next.js: How to get applied styles from element?

Let's say I have global.css
.test {
background-color: black;
width: 50px;
height: 50px;
}
For some reason, I need to get the styles data from applied element. I tried refs but it always return empty string.
import { useState, useEffect, useRef } from "react";
const IndexPage = () => {
const divEl = useRef<HTMLDivElement | null>(null);
const [divStyle, setDivStyle] = useState({} as CSSStyleDeclaration);
useEffect(() => {
if (divEl.current) {
setDivStyle(divEl.current.style);
}
}, [divEl.current]);
return (
<div>
<div ref={divEl} className="test"></div>
<pre>{JSON.stringify(divStyle, undefined, 2)}</pre>
</div>
);
};
export default IndexPage;
Is it because next.js SSR or should I add something to dependency array?
code sandbox here
You can use computed styles to get what you need, although it won't be a "simple" object with properties. You'll need to query each property individually:
if (divEl.current) {
setDivStyle(getComputedStyle(divEl.current));
}
and then you can do something like:
divStyle.getPropertyValue("background-color")
Here's an example sandbox (forked from yours) that demostrates it.

How to keep styled-components styles separate in different higher-order components on partially static site?

I'm using higher order components to reuse React/Next.js components but with different styles. However, this works only for vanilla React where pages are not generated either server-side or statically. If some pages are static and others dynamic, it won't work.
Here's an example component. We'll focus on TabStyles.Main in a little while:
const Tabs: NextPage<Props> = ({ ... }: Props) => {
...
return (
<TabStyles.Main>
...
</TabStyles.Main>
);
};
export default Tabs;
Here're its styles:
import styled from "styled-components";
const Main = styled.div`
display: flex;
flex-direction: column;
`;
...
export const TabStyles = {
Main,
...
};
Now let's implement two HOCs.
In red.tsx, rendered at path /red, color is red:
// Import Tabs and TabStyles
const TabsFontRed = <P extends {}>(Component: NextPage<P>) => {
TabStyles.Main = styled(TabStyles.Main)`
color: red;
`;
const NewComp = (props: P) => <Component {...props} />;
return NewComp;
};
const RedHOC = TabsFontRed(Tabs);
In blue.tsx, rendered at path /blue, color is blue.
// Import Tabs and TabStyles
const TabsFontBlue = <P extends {}>(Component: NextPage<P>) => {
TabStyles.Main = styled(TabStyles.Main)`
color: blue;
`;
const NewComp = (props: P) => <Component {...props} />;
return NewComp;
};
const BlueHOC = TabsFontBlue(Tabs);
This works if all pages are dynamic because TabStyles.Main = styled(TabStyles.Main)... is called every time we load the page, thus changing color to the correct one.
However, it seems if /red is static but /blue isn't, as we might do with Next.js, the behaviour will be different:
Go to /red first, font is red. Good.
Go to /blue, font is blue. Also good.
Go back to /red, font is now blue. Oh no!
Is there a way to augment this higher order component plus styled-components pattern so that styling will remain different when some pages are static and some are not?
Edit, 1 April 2021:
React complains in the console if we do this. I lost the notice now but it makes me think this is a bad idea. Probably, overriding styles is not good use of higher order components. Now I think HOCs should be used for adding logic, not styles.
Override styled-components in conventional fashion like this:
const NewStyledComp = styled(OlderStyledComp)`
background-color: ...;
`
Or like this:
const NewStyledComp = styled.div`
${OlderStyledComp} {
background-color: ...;
}
`

React Styled-components TypeScript extending styles with props on base style gets TS2769 error

I am trying to extend a base styled to other styled components and I get a typescript error. I have been able to do this in just pure javascript, am not able to do the same in typescript
base file
_Modal.ts
import styled from 'styled-components/macro'
interface I_Modal {
'background-color': string
'min-height': string
'min-width': string
}
const _modal = styled.div<I_Modal>`
background-color: ${props => props['background-color'] ? props['background-color'] : props.theme.colors.baseColor};
min-height: ${props => props['min-height'] ? props['min-height'] : '300px'};
min-width: ${props => props['min-width'] ? props['min-width'] : '200px'}
`
export default _modal
file i am trying to extend the styles to
registerModal.ts
import styled from 'styled-components/macro'
import _modal from './_modal'
export const RegisterModal = styled(_modal)`
background-color: purple;
height: 300px;
width: 200px;
`
everything in VSCode says its good, its just not compiling properly
image of error
Tips for using styled-components in TypeScript
If the parent is not a specific react component, it is recommended to write it simply as follows.
const Button = styled.button<{primary: boolean}>`
color: ${({primary}) => primary ? 'skyblue' };
`
Picking can reduce maintenance costs if the component is complex or references the parent props
interface Props {
primary
secondary
}
function MyComponent(props: Props) {
return (
<div>
<Button secondary={props.secondary} primary={props.primary}>{props.children}</Button>
</div>
)
}
const Button = styled.button<Pick<Props, 'primary' | 'secondary'>>`
${({primary, secondary}) => primary ? css`` : secondary ? css`` : css``}
`
Type a constant
It is convenient to type constants such as color and size so that you can see which px is applied when you use them.
const FONT = {
XXXLARGE: 32,
XXLARGE: 24,
XLARGE: 18,
LARGE: 16,
MEDIUM: 14,
BASE: 12,
SMALL: 11,
XSMALL: 10,
TINY: 8,
} as const
const FONT_WEIGHT = {
NORMAL: 400,
BOLD: 600,
} as const
const BORDER_RADIUS = 4 as 4
export default {
FONT,
FONT_WEIGHT,
BORDER_RADIUS
}
By doing this, you can check which constant has which px with the suggestion function of the editor.
When using css prop
Babel-plugin and css prop enabled by babel macro are extensions of jsx, so if you do nothing, TS will get angry.
You can solve it by writing the following in index.d.ts of type root. (Styled-components / cssprop is an extension of react)
import {} from 'styled-components/cssprop'
When using attrs
This has to be a bit cumbersome to write, so you may not use attrs a lot in the first place.
const StyledImg = styled.img.attrs<{ logoSrc: logoSrc }>(
({logoSrc}) => ({
src: logoSrc,
})
)<{ logoSrc: logoSrc }>

Resources