figma / code-connect

A tool for connecting your design system components in code with your design system in Figma
MIT License
490 stars 45 forks source link

Mapping an enum in Figma to a boolean prop in React code #14

Closed chsmc-stripe closed 3 weeks ago

chsmc-stripe commented 1 month ago

Our (Stripe's) Button component in Figma has a state enum like this:

state: figma.enum("State", {
  Default: "default",
  Hovered: "hovered",
  Pressed: "pressed",
  Focused: "focused",
  Disabled: "disabled",
})

In code the hovered, pressed, and focused states are based on CSS pseudo states and cannot be set via a prop. However, the disabled state can be manually set:

<Button disabled />

I'm not entirely sure how to model this in my Code Connect file to get the right syntax. It seems like I'd have to do this:

example: ({state}) => (
  <Button disabled={state === 'disabled'} />
),

which of course does not result in the correct or even working code in Figma:

image

I tried to do this sort of thing:

example: ({state}) => state === 'disabled' ? <Button disabled /> : <Button />

But for some reason the code in Figma always shows up as <Button disabled /> no matter which enum is selected.

Would appreciate some guidance on how to handle this—thanks in advance!

mryechkin commented 1 month ago

I'd love to know this as well, basically have the same use case 👀

andreiduca commented 1 month ago

I think this was called out in the demo. You only need to map the states you're interested in to the props you need in code, like this:

  props: {
    variant: figma.enum("Variant", {
      Primary: "primary",
      Secondary: "secondary",
      Plain: "plain",
    }),
    disabled: figma.enum("State", {
      Disabled: true, // map "Disabled" state to boolean `true`
      // ignore other "State" values, so `disabled` will be undefined
    }),
    busy: figma.enum("State", {
      Busy: true, // map "Busy" state to boolean `true`
      // ignore other "State" values, so `busy` will be undefined
    }),
  },
  example: (props) => (
    <Button
      variant={props.variant}
      disabled={props.disabled}
      busy={props.busy}
    >
      {props.label}
    </Button>
  ),

Essentially, the props you define in the figma.connect() block are whatever you want them to be, they are only a bridge between the Figma props and the React props.

chsmc-stripe commented 1 month ago

@andreiduca that did the trick, thank you so much! I didn't get to catch the demo so have been trying to piece things together from the docs.

Also I just noticed this from the docs which is helpful in explaining why my conditional rendering approach wasn't working:

Code Connect files are not executed. While they're written using real components from your codebase, the Figma CLI essentially treats code snippets as strings. This means you can use, for example, hooks without needing to mock data. However, this also means that logical operators such as ternaries or conditionals will be output verbatim in your example code rather than executed to show the result. You also won't be able to dynamically construct figma.connect calls in a for-loop, as an example. If something you're trying to do is not possible because of this restriction in the API, we'd love to hear your feedback.

andreiduca commented 1 month ago

Good call. I saw this behavior but wasn't sure how exactly it was working 😅

So in general, if you need to map stuff, this should be done via the prop object, before the mapped props are passed to the example() block.

You can also do some neat stuff, like this:

props: {
  iconOnly: figma.boolean("Icon Only"),
  label: figma.boolean("Icon Only", {
    false: figma.string("↳ Label"),
    true: undefined,
  }),
  icon: figma.boolean("Has icon", {
    true: figma.children("Icon"),
    false: undefined,
  }),
}

where you map an Icon Only Figma prop to an iconOnly boolean, but use the same to map the actual label conditionally.

Similarly, you can conditionally map a child instance property (Icon) from a boolean Has icon property.

Then you just use them in the example block:

example: (props) => (
  <Button iconOnly={props.iconOnly}>
    {props.icon}
    {props.label}
  </Button>
)
ptomas-figma commented 3 weeks ago

Hey @chsmc-stripe! Thanks for the question and thanks everybody for the discussion. Seems like the original question is solved, right? Would be ok for me to resolve this issue?

chsmc-stripe commented 3 weeks ago

@ptomas-figma yes, go for it!