stylemapper icon indicating copy to clipboard operation
stylemapper copied to clipboard

Helper to apply classNames to a dynamic element?

Open mgcrea opened this issue 2 years ago • 2 comments

Would be nice to have a className-like helper when you want to dynamically apply classes according to a variant config.

In my case, I'm playing with a basic <Button />component that would have an icon prop that could receive an ReactElement:

      <Button size="xs" icon={<XMarkIcon />}>
        Default
      </Button>

In this case I'd like to apply some size-based variants to the icon as well, something like:

const iconClassNames = variantClassNames({
  variants: {
    size: {
      xs: "w-4 h-4 -ml-0.5 mr-1",
      sm: "w-4 h-4 -ml-0.5 mr-2",
      md: "w-5 h-5 -ml-1 mr-2",
      lg: "w-6 h-6 -ml-1 mr-3",
      xl: "w-6 h-6 -ml-1 mr-3",
    }
  },
  defaultVariants: {
    size: "md",
  },
});

That could be used this way:

<button>
  {icon ? cloneElement(icon, {className: iconClassNames(props)}) : null}
  {children}
</button>

Do you think this could be useful or would you do it differently (eg. nested styles, etc.)?

mgcrea avatar Jan 26 '23 17:01 mgcrea

The className is passed to the components, so you can just apply it wherever you need it:

function MyCustomComponent(props) {
   const {className} = props;

   return (
    <button>
      {icon ? cloneElement(icon, {className}) : null}
      {children}
    </button>
   );
}

const MyStyledCustomComponent = styled(MyCustomComponent, {
  variants: {
    size: {
      xs: "w-4 h-4 -ml-0.5 mr-1",
      sm: "w-4 h-4 -ml-0.5 mr-2",
      md: "w-5 h-5 -ml-1 mr-2",
      lg: "w-6 h-6 -ml-1 mr-3",
      xl: "w-6 h-6 -ml-1 mr-3",
    }
  },
  defaultVariants: {
    size: "md",
  },
});

Does that work?

ivome avatar Jan 30 '23 02:01 ivome

Or another option could be to create a wrapper component that passes the className. Something like this should work (haven't test it):

function IconWrapper(props: {
  children: (className: string) => ReactElement,
  className: string
}) {
  const {children, className} = props;
  <>
   {children(className)}
  </>
}

const StyledIcon = styled(IconWrapper, {
  variants: {
    size: {
      xs: "w-4 h-4 -ml-0.5 mr-1",
      sm: "w-4 h-4 -ml-0.5 mr-2",
      md: "w-5 h-5 -ml-1 mr-2",
      lg: "w-6 h-6 -ml-1 mr-3",
      xl: "w-6 h-6 -ml-1 mr-3",
    }
  },
  defaultVariants: {
    size: "md",
  },
}

function Button(props) {
   const {icon, ...rest} = props;
   return (
    <button>
      {icon ? (
        <StyledIcon {...rest}>
           {(className) => cloneElement(icon, {className})}
        </StyledIcon>
        : null}
      {children}
    </button>
  )
}

ivome avatar Jan 30 '23 04:01 ivome