ben-rogerson / twin.macro

🦹‍♂️ Twin blends the magic of Tailwind with the flexibility of css-in-js (emotion, styled-components, solid-styled-components, stitches and goober) at build time.
MIT License
7.89k stars 184 forks source link

Props don't get passed down when composing components #853

Closed ymslavov closed 5 months ago

ymslavov commented 5 months ago

I believe there's a bug in how nested/wrapped components pass their props. This is the setup I have.

import tw, { styled } from "twin.macro";

export const MilestoneV1 = styled.div<MilestoneProps>(({ $isActive }) => [
  $isActive && tw`bg-accent-blue-500`,
]);

export const MilestoneV2 = styled(MilestoneV1)`
  color: ${theme`colors.base.white`};
`;

Usage:

<MilestoneV2 $isActive={true} />

Expected behaviour:

Actual behavior:

Libraries:

"twin.macro": "^3.4.1",
"styled-components": "^6.1.8"
ben-rogerson commented 5 months ago

Hey there

I've tested and all seems to work as expected - the prop os delivered to the original component.

Code

type MilestoneProps = {
  $isActive?: boolean
}

export const MilestoneV1 = styled.div<MilestoneProps>(({ $isActive }) => [
  $isActive && tw`bg-black`,
])

export const MilestoneV2 = styled(MilestoneV1)`
  color: white;
`

const App = () => (
  <div>
    <MilestoneV1 $isActive={true}>...</MilestoneV1>
    <MilestoneV1 $isActive={false}>...</MilestoneV1>
    <MilestoneV2 $isActive={true}>...</MilestoneV2>
    <MilestoneV2 $isActive={false}>...</MilestoneV2>
  </div>
)

Result

image

ymslavov commented 5 months ago

Ok, so in my willingness to make the example as simple as possible, I kinda simplified it wrong :) Apologies for that.

Here's the actual example which is not working on my side:

import tw, { styled, css, theme } from "twin.macro";

export const MilestoneV1 = styled.div<MilestoneProps>(({ $positionPercentage }) => [
  css`
    left: ${$positionPercentage}%;
  `,
]);

export const MilestoneV2 = styled(MilestoneV1)`
  width: 44px;
  height: 44px;
  background-color: ${theme`colors.base.white`} !important;
`;
<MilestoneV2 $positionPercentage={50} />             // This results in left: %, i.e. the number is not rendered in the CSS
ben-rogerson commented 5 months ago

This is working for me - on "styled-components": "^6.1.8" I can see the styles popup in the styles panel:

image

I used the following test code:

type MilestoneProps = {
  $positionPercentage: number
}

export const MilestoneV1 = styled.div<MilestoneProps>(
  ({ $positionPercentage }) => [
    css`
      left: ${$positionPercentage}%;
    `,
  ],
)

const MilestoneV2 = styled(MilestoneV1)`
  width: 44px;
  height: 44px;
  background-color: ${theme`colors.white`} !important;
`

const App = () => (
  <div>
    <MilestoneV2 $positionPercentage={50} />
  </div>
)
ymslavov commented 5 months ago

I ran the exact same example on my end, and it doesn't render the number in the CSS.

I then managed to narrow it down to what causes it - if I remove the $ from the prop name (i.e. positionPercentage instead of $positionPercentage) it works as expected. Which leads me to believe it may be a misconfiguration on my end.

Do you have any ideas that can point me in the right direction?

ben-rogerson commented 5 months ago

Perhaps you could look at this issue about transient props, looks like a good place to start.

... a "transient prop" is only available on the top-level StyledComponent and no other child component. It may sometimes become available when a styled component wraps a styled component as then those two get folded together ... but otherwise the transient prop is intentionally isolated only to the StyledComponent and not passed on.