code-connect icon indicating copy to clipboard operation
code-connect copied to clipboard

Reference instance swap components by name

Open andreiduca opened this issue 1 year ago • 3 comments

Currently, "instance swap" properties are referenced as "links" to other components. It would be great to be able to reference them by name, and get a string value when mapping back to the React component. Ideally, we should also be able to manipulate the strings when mapping (for example, to convert from snake_case to camelCase).


For icons for example, we don't have a React component for each individual icon. Instead, we use a single <Icon> component, and we use a string prop to reference icons by id.

<Icon id="icChef" />

When connecting this to the Figma component, we get a "link" to the instance-swap icon, but we don't have a connected React component for every icon in Figma.

figma.connect(
  Icon,
  "https://www.figma.com/file/...[Icon component with an instance swap]",
  {
    props: {
      iconId: figma.instance("Icon ID"),
    },
    example: (props) => <Icon id={props.iconId} />
  },
);

So ideally, we should be able to do something like this, where the instance has a name attribute we can access.

figma.connect(
  Icon,
  "https://www.figma.com/file/...[Icon component]",
  {
    props: {
      iconId: figma.instance("Icon ID"),
    },
    example: (props) => <Icon id={props.iconId.name} />
  },
);

Additionally, with a custom mapper function, we could also manipulate the instance at will:

    props: {
      // just get the `name` as a string
      iconId: figma.instance("Icon ID", (instance) => instance.name),
    },
    example: (props) => <Icon id={props.iconId} />

or even change it how we see fit:

  iconId: figma.instance("Icon ID", (instance) => toCamelCase(instance.name)),

andreiduca avatar Apr 17 '24 19:04 andreiduca

Hey @andreiduca, so right now the way we suggest you solve this is that you create a connection for every icon. You can have multiple connections for the same React component, so you could have a file icons.figma.tsx with something like:

// Chef icon
figma.connect(Icon, "https://figma.com/file/123?node_id=1:1", {
  example: () => <Icon id="icChef" />
}

// Plate icon
figma.connect(Icon, "https://figma.com/file/123?node_id=1:2", {
  example: () => <Icon id="icPlate" />
}

// Fork icon
figma.connect(Icon, "https://figma.com/file/123?node_id=1:3", {
  example: () => <Icon id="icFork" />
}

// etc...

We talk a bit about this in the README, although I think we could probably make this clearer and show some examples.

There's a template script which you can use a basis for automatically generating such a file from Figma. If you have any questions or comments about implementing this, please let us know and we'd be happy to help out.

That said, we have also talked about concepts like you describe, where e.g. strings can be manipulated. Right now, we don't have concrete plans on whether we'll support this or when, but your input is really helpful and I'll share it with the team for discussion. We might follow up with further questions if we do look at implementing something like this.

tomduncalf-figma avatar Apr 18 '24 11:04 tomduncalf-figma

Connecting all individual icon components in Figma is exactly what I am trying to avoid. It's a scalability issue to have to "connect" every time a new icon gets added to the library.

In Figma, we have 250+ icons as individual components. So we created a separate component named Icon as a wrapper around them, which uses an instance swap named Icon ID to swap the actual icon component being rendered inside.

Similarly, in React, we only have a single <Icon> component, and we reference individual icons by a string id prop.

We can easily connect these two, only once, since the APIs are identical, and then it shouldn't matter how many new icons get added to the library – the "connection" remains the same: an Icon component on both sides with an icon-id property, which is an instance swap in Figma, but a string in React.

So mapping the swap instance to a string, like I tried to explain in my original comment, and referencing the swapped instance by its name should solve this problem.

andreiduca avatar Apr 18 '24 17:04 andreiduca

I got similar case with passing component as an icon. Example:

import { IconPlay } from 'icon-library'
import { BrandIcon } from 'ui-library'

function BrandIcon({ icon: Icon }) {
  return <Icon className="brand-icon" />
}

function View() {
  return (
    <div>
      <BrandIcon icon={IconPlay} /> Title
    </div>
  )
}

In this example I use icon for dependency injection. Of course I could pass icon={<IconPlay />}, but in this case I need to use cloneElement (what is not common case https://react.dev/reference/react/cloneElement) or prop render function. But it's more complex in this case. Passing component itself instead of instance is a common case for UI libraries:

  • component props in MUI https://mui.com/material-ui/api/button/#button-prop-component
  • elementType in react-aria https://react-spectrum.adobe.com/react-aria/useButton.html#api
  • asChild in @radix-ui https://www.radix-ui.com/primitives/docs/components/tabs#root
  • as prop in Chakra UI https://v2.chakra-ui.com/docs/components/box/usage#usage

izorg avatar May 31 '24 07:05 izorg