How to add a text line to a React styled-component - reactjs

I am learning Styled-component: I would like my code to render a text line (Your personal trainer) but I do not want to have it in App.js file. How can I do that?
import styled from 'styled-components';
const StyledTextLine = styled('YOUR PERSONAL TRAINER')`
color:#333333 100%;
font-size: 17px;
font-family: NeuropoliticalRg-Regular;
word-spacing:0px;
`;
export default StyledTextLine;

Assuming you might want a span:
const StyledTextLine = styled.span`
color:#333333 100%;
font-size: 17px;
font-family: NeuropoliticalRg-Regular;
word-spacing:0px;
`;
const TextLine = () => {
return <StyledTextLine>YOUR PERSONAL TRAINER</StyledTextLine>
}
If you need more help with styled-component feel free to drop on their docs
If you want to re-use the component pass a prop for a message like this:
const TextLine = ({ message }) => {
return <StyledTextLine>{message}</StyledTextLine>
}

Related

Not able to dynamically style component - TypeScript , Styled-component

I have a struggle here and I fear I do not understand what is happening.
I am following the documentation of styled-components
and everything seems to be like they described it, but it seems that it is not working on my side.
I am trying to render a Card/div or basically to expand it based on props coming from the parent
...
const [open, setOpen] = useState<boolean>(false)
...
return (
<CardWrapper
open={open}
... />
...
)
And here I use my state inside the styled component
import { keyframes, css } from '#emotion/react'
import styled from '#emotion/styled/macro'
const expandAnimation = keyframes`
0% {
height: 180px;
}
100% {
height: 240px;
}
`
const unexpandAnimation = keyframes`
0% {
height: 240px;
}
100% {
height: 180px;
}
`
...
export const CardWrapper = styled.div<{open: boolean}>`
animation: ${(open) => (open
? css`${expandAnimation} 1s ease-in-out ;`
: css`${unexpandAnimation} 1s ease-in-out;`)} ;
`
Gave it a try also with
export const CardWrapper = styled.div<{open: boolean}>`
${({ open }) => open && css`
animation-name: ${expandAnimation }
`}
`
But it seems I am not doing something right. I am pretty new to styled-components, but if someone can tell me what am I doing wrong.
UPDATE
In order for us to display some animation we need to provide a prop of ease and a prop of direction. In my case i use forwards and backawrds
export const CardWrapper = styled.div<{open: boolean}>`
${({ open }) => (open
? css`animation: ${expandAnimation} 0.7s ease forwards`
: css`animation: ${unexpandAnimation}1s ease backwards `)
}
`

React/TypeScript - Extending component with generic type and reuse generic type within component

I have the following component that takes a generic node and patchCurrentNode props.
import { css } from '#emotion/react';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import React, {
PropsWithChildren,
useState,
} from 'react';
import BaseNodeData from '../../types/BaseNodeData';
import { PatchCurrentNode } from '../../types/BaseNodeProps';
type Props<NodeData extends BaseNodeData = BaseNodeData> = {
node: NodeData;
patchCurrentNode: PatchCurrentNode<NodeData>;
};
/**
* Input that stores the name of the node's selected value.
*
* Some nodes ask for an input (text, choice, etc.), the selected value will be stored and be indexed using the selected variable's name.
*/
export const VariableNameInput: <NodeData extends BaseNodeData = BaseNodeData>(p: PropsWithChildren<Props<NodeData>>) => React.ReactElement = (props) => {
const {
node,
patchCurrentNode,
} = props;
const {
width = 200,
} = node;
console.log('node', node);
const [variableName, setVariableName] = useState<string | undefined>(node?.data?.variableName);
console.log('VariableNameInput variableName', variableName);
const onChange = (event: any) => {
console.log('onChange variableName', event, event?.target?.value);
setVariableName(event?.target?.value);
};
const onSubmit = () => {
console.log('onSubmit variableName', variableName);
patchCurrentNode({
data: {
variableName: variableName,
},
});
};
return (
<div
className={'variable-name-container'}
css={css`
position: absolute;
bottom: 0;
background-color: black;
width: ${width}px;
height: 50px;
margin-left: -15px;
margin-bottom: -15px;
padding-left: 15px;
border-radius: 5px;
.variable-name {
width: ${width - 50}px;
margin-top: 12px;
margin-left: -5px;
padding-left: 5px;
background-color: black;
color: ${variableName?.length ? 'white' : '#6E6E6E'}; // Change different color between placeholder and actual value
border: 1px solid #6E6E6E;
}
.submit {
color: #6E6E6E;
margin-left: 10px;
cursor: pointer;
}
`}
>
<input
className={'variable-name'}
placeholder={'Variable name'}
value={variableName}
onChange={onChange}
/>
<FontAwesomeIcon
className={'submit'}
icon={['fas', 'paper-plane']}
onClick={onSubmit}
/>
</div>
);
};
export default VariableNameInput;
It is used like this:
<VariableNameInput
node={node}
patchCurrentNode={patchCurrentNode}
/>
With the following types:
import BaseNodeData from '../BaseNodeData';
import { QuestionNodeAdditionalData } from './QuestionNodeAdditionalData';
export type QuestionNodeData = BaseNodeData<QuestionNodeAdditionalData>;
---
import BaseNodeAdditionalData from '../BaseNodeAdditionalData';
import { QuestionChoiceType } from './QuestionChoiceType';
export type QuestionNodeAdditionalData = BaseNodeAdditionalData & {
text?: string;
questionType?: QuestionChoiceType;
variableName?: string;
};
The implementation of the generic in the component works. But, I still have TS errors:
How can I reuse the generic NodeData type in the component's implementation?
I was thinking I could do something like node?.data?.variableName as NodeData, but NodeData isn't known there.
Edit: Solution
You aren't getting access to the generic NodeData inside of the component body due to how you have defined the component's type signature separately from it's implementation.
export const VariableNameInput: <NodeData extends BaseNodeData = BaseNodeData>(
p: PropsWithChildren<Props<NodeData>>
) => React.ReactElement = (props) => {
// ...
};
Instead of applying the type annotations to the VariableNameInput variable, apply them directly to the function arguments and return type.
export const VariableNameInput = <NodeData extends BaseNodeData = BaseNodeData>(
props: PropsWithChildren<Props<NodeData>>
): React.ReactElement => {
// ...
};
You are now able to access NodeData inside the component.
useState<string | undefined>((node as NodeData).data?.variableName);
But you don't get any additional information by asserting this since node is a required prop and it is always type NodeData.
It seems more likely that you need to define variableName as an optional string prop on the BaseNodeAdditionalData type. If you edit your post to include the types for BaseNodeData and PatchCurrentNode (and their dependencies) then perhaps I can help you fix the underlying issues.
It's the callback patchCurrentNode that is going to give you the most trouble because NodeData could extend BaseNodeData by requiring additional properties or more specific values. You can assert as Partial<NodeData>, but you want to be confident that any assertion you make is actually correct.
Typescript Playground Link

Semantic UI styled issue

I can add some styles to the Menu component. But how can I add style to the Menu.Item component?
const LayoutHeader = styled(Menu)`
&& {
background: red;
}
`
Use this:
const LayoutHeaderItem = styled(Main.Item)`
&&&{
/* your code */
}
`;

How to type a styled component without losing any prop with Typescript?

I'm new to styled components and I'd like to be able to type my styled components correctly so that when I pass props "vs code" I can autodetect all those props I have, not just the one in the theme or the ones I could put with an interface.
Would there be any way without using a HOC for it as I've seen in some other answer? Is it possible to get a general prop to use in all without having to be defining in each property of style this as in the example?
app.theme.ts
export const theme = {
palette: {
primaryColor: '#FF5018',
secondaryColor: '#252729',
tertiaryColor: '#1A1C1E',
textColor: 'white',
},
};
export type Theme = typeof theme;
navigation-bar.styled.component.ts
export const NavigationBarStyled = styled.div`
grid-area: navigation-bar-item;
padding-left: 2rem;
display: flex;
align-items: center;
color: ${({ theme }) => theme.palette.primaryColor};
background-color: ${({ theme }) => theme.palette.primaryColor};
`;
Thanks in advance,
Best
It could be solved as #Huy-Nguyen said but in practice, you lose properties on Styled Components or you have to define the same many times.
So the best option is this as the Styled-Components website says (to define a theme interface):
theme.ts
export default interface ThemeInterface {
primaryColor: string;
primaryColorInverted: string;
}
styled-components.ts
import * as styledComponents from "styled-components";
import ThemeInterface from "./theme";
const {
default: styled,
css,
createGlobalStyle,
keyframes,
ThemeProvider
} = styledComponents as styledComponents.ThemedStyledComponentsModule<ThemeInterface>;
export { css, createGlobalStyle, keyframes, ThemeProvider };
export default styled;
And then, you use that:
import styled from 'app/styled-components';
// theme is now fully typed
const Title = styled.h1`
color: ${props => props.theme.primaryColor};
`;
Just pass the link: https://www.styled-components.com/docs/api#define-a-theme-interface
Thank you so much for all.
For some reason (possibly due to the way styled-components's typescript definition is written), the typing for Theme works if you remove one of level of nesting. This snippet typechecks with no error for me (v4) i.e. typescript knows that primaryColor is a string:
const theme = {
primaryColor: '#FF5018',
secondaryColor: '#252729',
tertiaryColor: '#1A1C1E',
textColor: 'white',
};
type Theme = typeof theme;
type Props = Theme & {
// ... other keys
}
const NavigationBarStyled = styled.div<Props>`
grid-area: navigation-bar-item;
padding-left: 2rem;
display: flex;
align-items: center;
color: ${props => props.primaryColor};
background-color: ${props => props.primaryColor};
`;
We approached this is a different way.
NOTE: Styled Components v5 with React Native
Define your theme type.
// MyTheme.ts
export type MyTheme = {
colors: {
primary: string;
background: string;
};
};
Use the type on your themes.
// themes.ts
export const LightTheme: MyTheme = {
colors: {
primary: 'white',
background: 'white',
},
};
export const DarkTheme: MyTheme = {
colors: {
primary: 'grey',
background: 'black',
},
};
Use declaration merging to "merge" the MyTheme type into Styled Components default theme.
// styled.d.ts
import 'styled-components';
import { MyTheme } from '../src/themes/MyTheme';
declare module 'styled-components' {
// eslint-disable-next-line #typescript-eslint/no-empty-interface
export interface DefaultTheme extends MyTheme {}
}
OK, cool. The theme prop is correctly typed.
What about the components themselves?
Wrap your specific component props in the StyledProps type.
import { StyledProps } from 'styled-components';
import styled from 'styled-components/native';
type MyViewProps = StyledProps<{
backgroundColor?: string;
isAlert?: boolean;
}>;
const MyView = styled.View(
(props: MyViewProps) => `
background-color: ${props.backgroundColor || props.theme.colors.background};
color: ${props.isAlert ? red : props.theme.colors.primary}
`,
);
In this example both props.backgroundColor and props.theme.colors.background will auto-complete. When you update MyTheme type or the specific component type it should just work. 👍
I don't know if it's the best method. But I found a solution as follows.
// theme.ts
const theme = {
colors: {
...
}
};
export type themeTypes = typeof theme;
export default theme;
// styled.d.ts ( in root folder )
import 'styled-components'
import { themeTypes } from '#/styles/theme'
declare module 'styled-components' {
// eslint-disable-next-line #typescript-eslint/no-empty-interface
export interface DefaultTheme extends themeTypes {}
}

React + Jest: How to test private styled components?

I have a Charities component that shows text "Sorry..." when the status prop is === "error":
import React from "react";
import styled from "styled-components";
const Notification = styled.p`
text-align: center;
padding: 10px;
font-family: Raleway;
display: ${props => (props.hide ? "none" : "block")};
`;
const ErrorNotification = styled(Notification)`
background: #e3c7c4;
`;
export const Charities = ({
..., status
}) => (
<Container>
<ErrorNotification hide={status !== "error"}>
Sorry, something was wrong with your payment. Please try again.
</ErrorNotification>
...
</Container>
);
export default Charities;
I'm trying to test this with jest like this:
import React from "react";
import { mount, shallow } from "enzyme";
import { Charities } from "./index";
describe("Charities", () => {
let props;
let mountedCharities;
const charities = () => {
if (!mountedCharities) {
mountedCharities = mount(<Charities {...props} />);
}
return mountedCharities;
};
beforeEach(() => {
props = {
status: undefined,
...
};
mountedCharities = undefined;
});
describe("when status is pending", () => {
beforeEach(() => {
props.status = "pending";
});
it("doesn't render error", () => {
expect(charities().text()).not.toMatch(/Sorry/); // <---------- FAILS
});
});
});
My test fails with:
Expected value not to match:
/Sorry/
Received:
"Sorry, something was wrong with your payment. Please try again.Congratulations! You have successfully made a donation."
It seems like it's loading the children of the styled components even when it doesn't meet the conditions. How would I test this?
Your code is working as expected, it's just that styled() works by putting class names on the elements to control the styling.
In the unit test ErrorNotification is still there but it has css classes on it that will give it display: none in the final rendered HTML.
To make your components easier to unit test I recommend doing the hiding within Charities like this:
import React from "react";
import styled from "styled-components";
const Notification = styled.p`
text-align: center;
padding: 10px;
font-family: Raleway;
display: block;
`;
const ErrorNotification = styled(Notification)`
background: #e3c7c4;
`;
export const Charities = ({
..., status
}) => (
<Container>
{status !== "error" ? null : (
<ErrorNotification>
Sorry, something was wrong with your payment. Please try again.
</ErrorNotification>
)}
...
</Container>
);
export default Charities;
That way if the status in the props for Charities is anything except 'error' then ErrorNotification won't be rendered at all.
You are using the attribute hide which maps to 'display: none' when true this still renders the component albeit invisibly you should do something like this instead:
{ status === "error" &&
<ErrorNotification >
Sorry, something was wrong with your payment. Please try again.
</ErrorNotification>
}

Resources