figma / code-connect

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

React children as array/object #160

Open ericandrewscott opened 1 week ago

ericandrewscott commented 1 week ago

What do you do when you want to pass in children as an array or as an object, or as a const. I'll give an example below.

Breadcrumbs example

For Breadcrumbs, the number of "crumbs" ends up being an array of children, so we've hooked up our main breadcrumb component and 2 subcomponents. Here's a screenshot of our Figma component:

Screenshot 2024-09-12 at 9 25 01 AM

Link Crumb Subcomponent

Below is a screenshot of the subcomponent and then the connect statement. Same code for every variant because the browser handles the hover and active states.

Screenshot 2024-09-12 at 9 28 06 AM
figma.connect(
  Link,
  "https://www.figma.com/design/<OBFUSCATED>",
  {
    props: { text: figma.textContent("✏️ Text") },
    example: ({ text }) => (
      <Link inline={true} size="small">
        <a href={'INSERT_URI'}>{text}</a>
      </Link>
    ),
  },
);

Current Crumb Subcomponent

Below is a screenshot of the subcomponent and then the connect statement.

Screenshot 2024-09-12 at 9 28 11 AM
figma.connect(
  Link, // just a dummy placeholder, no real component
  "https://www.figma.com/design/<OBFUSCATED>",
  {
    props: { text: figma.textContent("✏️ Text") },
    example: ({ text }) => (
      <>
        <span aria-hidden="true">{text}</span>
        <span className="is-visually-hidden">Current page {text}</span>
      </>
    ),
  },
);

Breadcrumbs Component with children

To tie it all together, below is the code connect statement for the Breadcrumbs component:

figma.connect(
  Breadcrumbs,
  "https://www.figma.com/design/<OBFUSCATED>",
  {
    props: {
      items: figma.children(["_ ⚙️ Link crumb", "_ ✏️ Current crumb"]),
      hasDynamicInlineCrumbs: figma.boolean("Menu Toggle"),
    },
    example: ({ hasDynamicInlineCrumbs, items }) => {
      const [showMenu, setShowMenu] = React.useState(false);

      const handleTrigger = (e) => {
        setShowMenu(!showMenu);
      };

      const crumbs = { items };

      return (
        <EGDSBreadcrumbs isOpen={showMenu} onTriggerClick={handleTrigger} hasDynamicInlineCrumbs={hasDynamicInlineCrumbs} crumbs={crumbs} />
      );
    },
  },
);

Now, this renders them all in the right order, but the crumbs const should be an array of children, but instead it is a concatenation of the children. Is there any way to manipulate that?

Expected rendering

const crumbs = [
  <Link inline={true} size="small"><a href={'INSERT_URI'}>Link crumb</a></Link>,
  <Link inline={true} size="small"><a href={'INSERT_URI'}>Link crumb</a></Link>,
  <Link inline={true} size="small"><a href={'INSERT_URI'}>Link crumb</a></Link>,
  <>
    <span aria-hidden="true">Current crumb</span>
    <span className="is-visually-hidden">Current page Current crumb</span>
  </>
];

Actual rendering

const crumbs = {
  items: 
    <Link inline={true} size="small"><a href={'INSERT_URI'}>Link crumb</a></Link>
    <Link inline={true} size="small"><a href={'INSERT_URI'}>Link crumb</a></Link>
    <Link inline={true} size="small"><a href={'INSERT_URI'}>Link crumb</a></Link>
    <><span aria-hidden="true">Current crumb</span><span className="is-visually-hidden">Current page Current crumb</span></>
};
ptomas-figma commented 1 week ago

Thanks for the message Eric. Right now, there's no way to represent the children as an array. I brought it up to the team to discuss.

Curious, how do you imagine the API to support this, any preferred format that would feel "natural" to you?

ericandrewscott commented 1 week ago

@ptomas-figma first, thanks for asking!!!

Second, I realized a mistake in wrapping { items } when outside of the return statement, it should just be items. Still, that gets me to:

const crumbs = 
    <Link inline={true} size="small"><a href={'INSERT_URI'}>Link crumb</a></Link>
    <Link inline={true} size="small"><a href={'INSERT_URI'}>Link crumb</a></Link>
    <Link inline={true} size="small"><a href={'INSERT_URI'}>Link crumb</a></Link>
    <><span aria-hidden="true">Current crumb</span><span className="is-visually-hidden">Current page Current crumb</span></>
;

As far as how, I think it would be nice if it was an optional parameter on figma.children. So, I could see my above code updating to something like:

      items: figma.children(["_ ⚙️ Link crumb", "_ ✏️ Current crumb"], toArray),

or :

      items: figma.children(["_ ⚙️ Link crumb", "_ ✏️ Current crumb"], "Array"),

or even something like:

      items: figma.children(["_ ⚙️ Link crumb", "_ ✏️ Current crumb"], []),

That way, the manipulation is handled outside of the logic-less example.

Honestly, whatever makes the most sense to y'all. There are plenty of times where we hydrate our React components via a slot object. Another example of this is AvatarGroup, where we do:

<AvatarGroup  items={[
  { text: 'AA' },
  { text: 'MD' },
  { text: 'PS'},
  { text: 'RD' }
]} />

So in that case, maybe figma.children allows for some interpolation? Something like:

  items: items: figma.children(["_ ✏️ Avatar item"], [{
    {
      // with an enum for text, image, or icon returning the prop, like
      // text: "AB"
      // or
      // icon: "user"
    }
  }]),
ptomas-figma commented 1 week ago

Thanks again Eric! Will circle this ideas back to the team for discussion.