How to define props on framer-motion styled-component? (PSA) - reactjs

PSA:
(not a question but I didn't see any answers to this on stack so here's the answer.)
Define props on a styled-components that wraps a motion, it's a little confusing how to define them when wrapping motion with styled().
how to define props according to styled components docs
import styled from 'styled-components';
import Header from './Header';
interface TitleProps {
readonly isActive: boolean;
}
const Title = styled.h1<TitleProps>`
color: ${(props) => (props.isActive ? props.theme.colors.main : props.theme.colors.secondary)};
`;
How to define the props in your code without motion:
import styled from "styled-components";
interface Props {
height?: number;
}
const Container = styled.div<Props>`
height: ${({ height }) => height};
`;
export default Container;
How to define the props in your code with motion:
import { HTMLMotionProps, motion } from "framer-motion";
import styled from "styled-components";
/**
* notice the props extend HTMLMotionProps<"div"> this is so all the default
* props are passed such as `onClick`
*/
interface Props extends HTMLMotionProps<"div"> {
height?: number;
}
//notice motion is called as a function instead of `motion.div`
const Container = styled(motion<Props>("div"))`
height: ${({ height }) => height};
`;
export default Container;

The problem is that you are defining your "motion div" wrong. It should be defined this way instead:
interface Props {
height?: number;
}
// motion is an object that gives you access to the html tags (like the div)
const Container = styled(motion.div)<Props>`
height: ${({ height }) => height};
`;
As you can see above, you just need to pass in motion.div as any other component, into the styled function.

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?

React Material UI v5 styled with defaultProps

When using multiple styled components, the top one overrides other default props.
import { styled } from '#mui/material/styles'
import { Badge } from '#mui/material'
const Badge1 = styled(Badge)``
// this works if Badge1 is used directly: <Badge1 />
Badge1.defaultProps = {
max: Infinity
}
const Badge2 = styled(Badge1)`` // styled Badge1
// this overrides defaultProps from Badge1. Prop max: Infinity does no apply here
Badge2.defaultProps = {
variant: 'standard'
}
Badge2 has only variant: 'standard' default prop. It skips max: Infinity
How can I keep all the defaultProps from each level
When you style a component via multiple styled calls using Emotion, Emotion collapses the styling layers into a single wrapper component rather than adding an additional wrapper around the first wrapper. Emotion retains the defaultProps from the previous wrapper, but you are then overwriting that when you set Badge2.defaultProps.
You can retain any previous defaultProps with the following syntax:
Badge2.defaultProps = {
...Badge2.defaultProps,
variant: 'standard'
}
Below is an example demonstrating what happens with default props with each styled wrapping. The fix is demonstrated with StyledAgainWithDefaultRetainExisting.
import styled from "#emotion/styled";
function MyComponent({ className, ...defaults }) {
return <div className={className}>Defaults: {JSON.stringify(defaults)}</div>;
}
MyComponent.defaultProps = {
orig: true
};
const StyledMyComponent = styled(MyComponent)`
background-color: blue;
color: white;
`;
StyledMyComponent.defaultProps = {
styled: true
};
const StyledAgainNoDefaultsAdded = styled(StyledMyComponent)`
background-color: purple;
`;
const StyledAgainWithDefault = styled(StyledMyComponent)`
background-color: green;
`;
StyledAgainWithDefault.defaultProps = {
styledAgain: true
};
const StyledAgainWithDefaultRetainExisting = styled(StyledMyComponent)`
background-color: brown;
`;
StyledAgainWithDefaultRetainExisting.defaultProps = {
...StyledAgainWithDefaultRetainExisting.defaultProps,
styledAgainRetain: true
};
export default function App() {
return (
<div>
<MyComponent />
<StyledMyComponent />
<StyledAgainNoDefaultsAdded />
<StyledAgainWithDefault />
<StyledAgainWithDefaultRetainExisting />
</div>
);
}

TypeScript Reassign InputHTMLAttributes

i think i know the answer to this already but i'm effectively trying to overwrite a React InputHTMLAttribute when using an interface with styled-components
import { InputHTMLAttributes } from 'react'
import styled from 'styled-components'
export interface IInputStyles
extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size'> {
/** The size of the input */
size?: 'large' | 'small'
}
export const StyledInput = styled.input<IInputStyles>({}, ({ size }) => ({
height: size === 'large' ? 48 : 32,
}))
i always get var size: never
Is there a way to reassign size or does that defeat the point of type safety?
"typescript": "^3.9.7",
i believe you cannot overwrite HTML attributes like you want, but what you can do is:
export interface IInputStyles extends InputHTMLAttributes<HTMLInputElement> {
large?: boolean;
}
export const StyledInput = styled.input.attrs<IInputStyles>((props) => ({
style: {
height: (props.large ? "48" : "32") + "px",
},
}))<IInputStyles>`
...other css styles...
`;
So, if you create a StyledInput with large attribute, the height should be 48px;
<StyledInput large />;
else the height should be 32px;
<StyledInput />;

How to set type of React Functional Component property to Styled Component `styled.div`?

import React, { FunctionComponent } from "react"
import styled from "styled-components"
interface ChildProps {
color: string
}
const Child = styled.div`
background-color: ${(props: ChildProps) => props.color};
`
interface ParentProps {
node: FunctionComponent<ChildProps> // How to set node type to Styled Component `styled.div`?
}
const Parent = function(props: ParentProps) {
const Node = props.node
return <Node color="white" />
}
npm install -D #types/styled-components
import styled, { StyledComponent } from "styled-components";
interface ParentProps {
node: StyledComponent<"div", any, ChildProps, never>
}
You can btw look up the type of something e.g. by hovering over it in VSCode.
Tip: Use a generic to declare the styled-component's props, instead of declaring it in the function. And in this case you can destructure the color.
const Child = styled.div<ChildProps>`
background-color: ${({ color }) => color};
`

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 {}
}

Resources