React & Typescript, styled components & children - reactjs

I've tried so many different combinations to get this to work, but it's not applying the styles I've written for the StyledLines component, by them selves they work fine! Using the StyledLines component as a child, it doesn't work. The Target component styles work as expected.
import React, { Fragment, FunctionComponent } from 'react';
import styled from '#emotion/styled';
interface Crosshair {
size: number;
thickness: number;
}
const Target = styled.div<Crosshair>`
position:absolute;
&:before {
content: '';
display:block;
position:absolute;
top:50%;
left:50%;
background-color:transparent;
border-color:#2fd5d5;
margin-left:-${({size}) => size / 4}px;
margin-top:-${({thickness}) => thickness / 4}px;
width:${({size}) => size / 2}px;
height:${({thickness}) => thickness / 2}px;
}
`;
const Lines: FunctionComponent = ({children}) => <div className="line">{children}</div>;
const StyledLines = styled(Lines)<Crosshair>`
position:absolute;
&:nth-of-type(1) {
top:0;
left:0;
}
&:nth-of-type(2) {
top:0;
right:0;
}
&:nth-of-type(3) {
bottom:0;
right:0;
}
&:nth-of-type(4) {
bottom:0;
left:0;
}
&:after, &:before {
content: '';
display:block;
position:absolute;
top:50%;
left:50%;
background:#2fd5d5;
margin-left:-${({size = 2}) => size / 2}px;
margin-top:-${({thickness = 24}) => thickness / 2}px;
width:${({size = 2}) => size}px;
height:${({thickness = 24}) => thickness}px;
}
&:before {
transform: rotateZ(90deg);
}
`;
export default function Crosshairs() {
return <Fragment>
<div>
{[0,1,2,3].map(i => <StyledLines key={i} size={24} thickness={2}>
<Target size={24} thickness={2} />
</StyledLines>)}
</div>
</Fragment>;
}

Lines is a plain React component, not a styled component, so you have to pass the className prop to the DOM part you want to style:
const Lines: FunctionComponent = ({children, className}) => <div className={`line ${className}`}>{children}</div>;

Related

React - Typescript - How to function as prop to another component

There are three components Toggle, ToggleMenu, Wrapper
The toggle should be universal, and used for different functions
The Wrapper should only change background color when toggle is on
The question is how to pass the function that responcible for changing color to togglemenu, that it executs when switch toggle
App
import React from 'react';
import { Wrapper } from './containers/wrapper';
import { Button } from './components/buttons/button';
import { ToggleMenu } from './components/settings/settings';
const App = () => {
return (
<>
<Wrapper>
<Button />
<ToggleMenu />
</Wrapper>
</>
)
}
export default App;
ToggleMenu
import styled from "styled-components";
import { Toggle } from "../buttons/toggle";
import { WrapperProps } from "../../containers/wrapper";
import { test } from "./test"
const SettingsContainer = styled.div`
margin: auto;
width: 50%;
height: 50%;
display: flex;
align-items: center;
justify-content: center;
background-color: white;
`;
const Container = styled.div`
height: 50%;
width: 50%;
display: flex;
justify-content: center;
flex-direction: column;
background-color: lime;
`;
const TogCon = styled.div`
margin: 0.5em;
`;
export const ToggleMenu = (props: WrapperProps) => {
return (
<SettingsContainer>
<Container>
<TogCon>
<Toggle toggleText={"Dark mode"} onChange={props.handleTheme}/>
</TogCon>
<TogCon>
<Toggle toggleText={"Sth"} onChange={() => {console.log("Sth")}}/>
</TogCon>
</Container>
</SettingsContainer>
);
};
Wrapper
import React, { useState } from "react";
import styled from "styled-components";
export type WrapperProps = {
children?: React.ReactNode;
color?: string;
handleTheme?: () => void;
};
const Container = styled.div<WrapperProps>`
width: 100%;
height: 100%;
top: 0;
left: 0;
position: fixed;
background-color: ${props => props.color }
`
export const Wrapper = ({ children }: WrapperProps) => {
const [theme, setTheme] = useState("black")
const handleTheme = () => {
theme === "black" ? setTheme("white"): setTheme("black")
}
return(
<Container
color={theme}
handleTheme={handleTheme}
> { children }
</Container>
);
}
Was solved with React.cloneElement
export const Wrapper = (props: WrapperProps) => {
const [theme, setTheme] = useState("black")
const handleTheme = () => {
theme === "black" ? setTheme("white"): setTheme("black")
}
const childrenWithProps = React.Children.map(props.children, child => {
if (React.isValidElement(child)) {
return React.cloneElement(child, { handleTheme });
}
return child;
});
return(
<Container color={theme} >
{ childrenWithProps }
</Container>
);
}

I want to create a react Portal component

We are using react.
I want to create a Portal component and enclose a Drawer component in the Portal component.
I have created a Poratl component, but the following error occurs.
I don't know how to solve this problem.
errorMesssage
_react.default.createPortal is not a function
code
import ReactDOM from "react";
import createPortal from "react-dom";
export const Portal = ({ children }) => {
const el = document.getElementById("portal");
return ReactDOM.createPortal(children, el);
};
import React from "react";
import styled from "styled-components";
import { Portal } from "./Portal";
type Props = {
isOpen: boolean;
children: React.ReactNode;
onClose: () => void;
};
export const Drawer = (props: Props) => {
return (
<Portal>
<Overlay isOpen={props.isOpen} onClick={props.onClose} />
<DrawerModal {...props}>{props.children}</DrawerModal>
</Portal>
);
};
const DrawerModal = styled.div<Props>`
position: absolute;
overflow: scroll;
top: 0;
right: 0;
height: 100vh;
width: ${({ isOpen }: Props) => (isOpen ? 400 : 0)}px;
background: #ffffff;
box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.25);
transition: 200ms cubic-bezier(0.25, 0.1, 0.24, 1);
`;
const Overlay = styled.div<{ isOpen: boolean }>`
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: ${(props: { isOpen: boolean }) => (props.isOpen ? 0 : -1)};
`;
Try import ReactDOM from 'react-dom'; instead of import ReactDOM from 'react';
import ReactDOM from 'react-dom'; // Update this line
export const Portal = ({ children }) => {
const el = document.getElementById("portal");
return ReactDOM.createPortal(children, el);
};

Animation not working in styled-component using {keyframes}

I have a base component - Emoji.jsx
import styled from 'styled-components';
const StyledEmoji = styled.div`
font-size: 6rem;
cursor: pointer;
&:not(:last-child) {
margin-right: 3rem;
}
`;
function Emoji({ content, handleClick }) {
return (
<StyledEmoji onClick={() => handleClick(content)}>{content}</StyledEmoji>
);
}
export default Emoji;
I am extending this component and applying anmimation to it in EmojiBubble.jsx
import Emoji from './Emoji';
import styled, { keyframes } from 'styled-components';
const Bubble = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
`;
const EmojiBubble = styled(Emoji)`
animation: ${Bubble} 6s ease-in-out;
`;
export default EmojiBubble;
But, the animation is not working when I am using EmojiBubble component
<EmojiBubble content={emoji} /> //Emoji not rotating
What the issue here?
function Emoji({ content, handleClick })... doesn't pass the class name down to StyledEmoji, that's why styles created by EmojiBubble are not applied. All you need to do: pass the class name:
function Emoji({ content, handleClick, className }) {
return (
<StyledEmoji className={className} onClick={() => handleClick(content)}>
{content}
</StyledEmoji>
);
}
Working example

React Storybook Checkbox change handler not working

I am creating a checkbox component using React, Typescript, Storybook and Styled-Components, after following this tutorial: building-a-checkbox-component-with-react-and-styled-components. I have had to adjust the code as I am using a FunctionComponent, but I am facing an issue with the change handler. I cannot check or uncheck the Checkbox which seems to be readonly, and I'm not sure where I am going wrong. Here is my code:
Checkbox.tsx
import React from 'react';
import styled from 'styled-components';
import { FunctionComponent } from 'react';
type CheckboxProps = {
checked?: boolean;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
};
const CheckboxContainer = styled.div`
display: inline-block;
vertical-align: middle;
`;
const Icon = styled.svg`
fill: none;
stroke: white;
stroke-width: 2px;
`;
const HiddenCheckbox = styled.input.attrs({ type: 'checkbox' })`
border: 0;
clip: rect(0 0 0 0);
clippath: inset(50%);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
white-space: nowrap;
width: 1px;
`;
const StyledCheckbox = styled.div`
display: inline-block;
width: 16px;
height: 16px;
background: ${(props: CheckboxProps) => (props.checked ? 'green' : 'white')};
border-color: 'dark gray';
border-width: 1px;
border-style: solid;
border-radius: 2px;
${HiddenCheckbox}:focus + & {
box-shadow: 0 0 0 3px grey;
}
${Icon} {
visibility: ${(props: CheckboxProps) => (props.checked ? 'visible' : 'hidden')};
}
`;
const Checkbox: FunctionComponent<CheckboxProps> = ({ onChange, checked, ...props }) => {
return (
<CheckboxContainer>
<HiddenCheckbox checked={checked} {...props} onChange={onChange} />
<StyledCheckbox data-testid="styledcheckbox" checked={checked} {...props} onChange={onChange}>
<Icon viewBox="0 0 24 24">
<polyline points="20 6 9 17 4 12" />
</Icon>
</StyledCheckbox>
</CheckboxContainer>
);
};
export default Checkbox;
Checkbox.stories.js
// Checkbox.stories.js
import React, { useState, useRef } from 'react';
import Checkbox from '#components/Checkbox/Checkbox';
import { action } from '#storybook/addon-actions';
import { storiesOf } from '#storybook/react';
// eslint-disable-next-line #typescript-eslint/explicit-function-return-type
const CheckboxStateful = props => {
const [value, setValue] = useState(props);
const valRef = useRef(value);
// eslint-disable-next-line #typescript-eslint/explicit-function-return-type
const onChange = event => {
setValue(event.target.value);
valRef.current = event.target.value;
};
return (
<Checkbox
value={value}
onChange={event => {
onChange(event);
}}
></Checkbox>
);
};
storiesOf('Checkbox', module)
.add('with checked', () => {
const value = true;
// eslint-disable-next-line #typescript-eslint/explicit-function-return-type
const onChange = event => setValue(event.target.value);
return <CheckboxStateful value={value} onChange={onChange}></CheckboxStateful>;
})
.add('with unchecked', () => {
const value = false;
// eslint-disable-next-line #typescript-eslint/explicit-function-return-type
const onChange = event => setValue(event.target.value);
return <CheckboxStateful value={value} onChange={onChange}></CheckboxStateful>;
});
As I am a novice at both React and Storybook I may require some expert input into whether my change handler code is correct or not. I have looked at other examples, but none of them use Typescript. Any help would be appreciated.
Solution is to change onChange to onClick in CheckboxProps in Checkbox.tsx, as follows:
onClick: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
and to simplify CheckboxStateful in Checkbox.stories.js as follows:
const CheckboxStateful = () => {
const [value, setValue] = useState(false);
return <Checkbox checked={value} onClick={() => setValue(!value)}></Checkbox>;
};
The complete updated code is now as follows:
Checkbox.tsx
import * as React from 'react';
import styled from 'styled-components';
import { FunctionComponent } from 'react';
type CheckboxProps = {
checked?: boolean;
onClick: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
};
const CheckboxContainer = styled.div`
display: inline-block;
vertical-align: middle;
`;
const Icon = styled.svg`
fill: none;
stroke: white;
stroke-width: 2px;
`;
const HiddenCheckbox = styled.input.attrs({ type: 'checkbox' })`
border: 0;
clip: rect(0 0 0 0);
clippath: inset(50%);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
white-space: nowrap;
width: 1px;
`;
const StyledCheckbox = styled.div`
display: inline-block;
width: 16px;
height: 16px;
background: ${(props: CheckboxProps) => (props.checked ? 'green' : 'white')};
border-color: 'dark gray';
border-width: 1px;
border-style: solid;
border-radius: 2px;
${HiddenCheckbox}:focus + & {
box-shadow: 0 0 0 3px grey;
}
${Icon} {
visibility: ${(props: CheckboxProps) => (props.checked ? 'visible' : 'hidden')};
}
`;
const Checkbox: FunctionComponent<CheckboxProps> = ({ onClick, checked, ...props }) => {
return (
<CheckboxContainer>
<HiddenCheckbox checked={checked} {...props} />
<StyledCheckbox checked={checked} {...props} onClick={onClick} data-testid="styledcheckbox">
<Icon viewBox="0 0 24 24">
<polyline points="20 6 9 17 4 12" />
</Icon>
</StyledCheckbox>
</CheckboxContainer>
);
};
export default Checkbox;
Checkbox.stories.js
/* eslint-disable #typescript-eslint/explicit-function-return-type */
// Checkbox.stories.js
import * as React from 'react';
import { useState } from 'react';
import Checkbox from '#components/Checkbox/Checkbox';
import { action } from '#storybook/addon-actions';
import { storiesOf } from '#storybook/react';
export default {
title: 'Checkbox',
component: Checkbox,
};
const CheckboxStateful = () => {
const [value, setValue] = useState(false);
return <Checkbox checked={value} onClick={() => setValue(!value)}></Checkbox>;
};
storiesOf('Checkbox', module)
.add('with checked', () => {
const value = true;
return <Checkbox checked={value} onClick={action('checked')}></Checkbox>;
})
.add('with unchecked', () => {
const value = false;
return <Checkbox checked={value} onClick={action('checked')}></Checkbox>;
})
.add('stateful', () => {
return <CheckboxStateful />;
});
Once you create the React project and add Storybook, just run yarn storybook and the checkboxes will show up correctly.
Instead of creating a new Stateful component a much better approach is to use state in the components template.
An example:
const Template: ComponentStory<typeof MyComponent> = (args) => {
const [state, setState] = useState('');
return (
<MyComponent
prop1={args.prop1}
prop2={args.prop2}
onChange={() => setState('Change')}
/>
);
};
Here's a link to the original answer I got this approach from:
https://stackoverflow.com/a/64722559/10641401

React testing toHaveStyleRule property not found in style rules

I am trying to test that ThemeProvider is providing theme to my component. It is properly providing default & custom theme to the base class and passing tests, but when I test for the condition class it does not find any of the styles. This is even while passing through the class directly. This is my first venture into Styled-Components and testing as a whole.
I have tested using the optional { css } import from styled-components, tried passing the class directly, and removing the default class entirely. I also tried setting a default style directly in the styled-component. The toBeTruthy() does pass, so it's at least seeing it with the class I would think?
// This is being called globally
export const mountWithTheme = (Component, customTheme) => {
const theme = customTheme || defaultTheme
return mount(<ThemeProvider theme={theme}>{Component}</ThemeProvider>)
}
import React from 'react';
import { shallow, mount } from 'enzyme';
import { MemoryRouter, Route, Link } from 'react-router-dom';
import { css } from 'styled-components';
import 'jest-styled-components';
import HeaderLinkA from './HeaderLinkA.jsx';
describe('HeaderLinkA', () => {
it('renders color /w prop', () => {
const wrapper = mount(
<MemoryRouter initialEntries={['/about']} >
<Route component={props => <HeaderLinkA {...props} name='about' test='about' theme={{ primarycolor: 'white', secondarycolor: 'black' }} /> } path='/about' />
</MemoryRouter>
)
expect(wrapper.find('Link')).toHaveStyleRule('color', 'white');
expect(wrapper.find('Link')).toHaveStyleRule('color', 'white', {
modifier: `:hover`,
});
});
it('is themed with default styles, when theme is missing', () => {
const wrapper = global.StyledComponents.mountWithTheme(
<MemoryRouter initialEntries={['/about']} >
<React.Fragment>
<HeaderLinkA name='about' testclass='section-link-active' />,
</React.Fragment>
</MemoryRouter>
)
expect(wrapper.find('Link')).toHaveStyleRule('color', '#FFF')
expect(wrapper.find('Link')).toHaveStyleRule('color', '#FFF', {
modifier: `:hover`
});
expect(wrapper.find('Link.section-link-active')).toBeTruthy();
expect(wrapper.find('Link')).toHaveStyleRule('border-bottom', '1px solid #95d5d2');
});
});
import React from 'react';
import { Link } from 'react-router-dom';
import styled from 'styled-components';
const StyledHeaderLink = styled(Link)`
text-decoration: none;
color: ${ props => props.theme.primarycolor };
padding-bottom: 2px;
overflow-x: hidden;
position: relative;
display: inline-flex;
&:active,
&:hover {
color: ${ props => props.theme.primarycolor };
}
&.section-link {
&:after {
content: '';
position: absolute;
bottom: 0;
left: 0;
height: 1px;
background: ${ props => props.theme.secondarycolor };
width: 100%;
transform: translate3d(-110%, 0, 0);
-webkit-transform: translate3d(-110%, 0, 0);
transition: transform .3s ease-in-out;
-webkit-transition: transform .3s ease-in-out;
}
&:hover:after {
transform: translate3d(0%, 0, 0);
-webkit-transform: translate3d(0%, 0, 0);
}
}
&.section-link-active {
border-bottom: 1px solid ${ props => props.theme.secondarycolor || '#95d5d2' };
}
`;
const HeaderLinkA = ( props ) => {
const page = props.name.toLowerCase();
return (
<StyledHeaderLink {...props } to={`/${ page }`}
className={props.testclass || window.location.pathname === `/${ page }` ? // refactor this to be controlled by HeaderO and pass down the prop.
'section-link-active'
: 'section-link'} >
{props.name}
</StyledHeaderLink>
)
}
export default HeaderLinkA;
All of the tests pass up until the final one which I'm stuck on.
"Property 'border-bottom' is not found in style rules"
Expected:
"border-bottom: 1px solid #95d5d2"
Received:
"border-bottom: undefined"

Resources