13

I'm wrapping Material UI's Chip component so that I can pass in values other than "primary" and "secondary" for the colors prop. I also want to maintain the hover effect if the chip is clickable so that the chip transitions to a different color when the cursor is over it. The colors are passed in as props, so it's easy enough to set the backgroundColor and color:

<Chip
  style={{
    backgroundColor: props.backgroundColor,
    color: props.color
  }}
/> 

However, since I'd also like to pass in the hover color as a prop, I'd need to do something like this:

<Chip
  style={{
    backgroundColor: props.backgroundColor,
    color: props.color,
    '&:hover': {
      backgroundColor: props.hoverBackgroundColor,
      color: props.hoverColor
    }
  }}
/> 

However, the &:hover (as far as I know) can't be used inside of the style prop. Typically, the &:hover would be used inside of a styles object that is passed into withStyles, but I'm not able to access props from in there. Any suggestions?

Olivier Tassinari
  • 8,238
  • 4
  • 23
  • 23
matwlev
  • 315
  • 1
  • 2
  • 6

2 Answers2

39

You can achieve this by creating your own custom chip component. In order to be able to use props to control the styling, you can use the makeStyles. The makeStyles function returns a hook that can accept an object parameter for providing variables to your styles.

Here's a possible CustomChip implementaton:

import React from "react";
import Chip from "@material-ui/core/Chip";
import { makeStyles } from "@material-ui/core/styles";
import { emphasize } from "@material-ui/core/styles/colorManipulator";

const useChipStyles = makeStyles({
  chip: {
    color: ({ color }) => color,
    backgroundColor: ({ backgroundColor }) => backgroundColor,
    "&:hover, &:focus": {
      backgroundColor: ({ hoverBackgroundColor, backgroundColor }) =>
        hoverBackgroundColor
          ? hoverBackgroundColor
          : emphasize(backgroundColor, 0.08)
    },
    "&:active": {
      backgroundColor: ({ hoverBackgroundColor, backgroundColor }) =>
        emphasize(
          hoverBackgroundColor ? hoverBackgroundColor : backgroundColor,
          0.12
        )
    }
  }
});
const CustomChip = ({
  color,
  backgroundColor,
  hoverBackgroundColor,
  ...rest
}) => {
  const classes = useChipStyles({
    color,
    backgroundColor,
    hoverBackgroundColor
  });
  return <Chip className={classes.chip} {...rest} />;
};
export default CustomChip;

The styling approach (including the use of the emphasize function to generate the hover and active colors) is based on the approach used internally for Chip.

This can then be used like this:

      <CustomChip
        label="Custom Chip 1"
        color="green"
        backgroundColor="#ccf"
        onClick={() => {
          console.log("clicked 1");
        }}
      />
      <CustomChip
        label="Custom Chip 2"
        color="#f0f"
        backgroundColor="#fcc"
        hoverBackgroundColor="#afa"
        onClick={() => {
          console.log("clicked 2");
        }}
      />

Here's a CodeSandbox demonstrating this:

Edit Chip color (forked)


Here's a Material-UI v5 version of the example:

import Chip from "@material-ui/core/Chip";
import { styled } from "@material-ui/core/styles";
import { emphasize } from "@material-ui/core/styles";
import { shouldForwardProp } from "@material-ui/system";
function customShouldForwardProp(prop) {
  return (
    prop !== "color" &&
    prop !== "backgroundColor" &&
    prop !== "hoverBackgroundColor" &&
    shouldForwardProp(prop)
  );
}
const CustomChip = styled(Chip, { shouldForwardProp: customShouldForwardProp })(
  ({ color, backgroundColor, hoverBackgroundColor }) => ({
    color: color,
    backgroundColor: backgroundColor,
    "&:hover, &:focus": {
      backgroundColor: hoverBackgroundColor
        ? hoverBackgroundColor
        : emphasize(backgroundColor, 0.08)
    },
    "&:active": {
      backgroundColor: emphasize(
        hoverBackgroundColor ? hoverBackgroundColor : backgroundColor,
        0.12
      )
    }
  })
);

export default CustomChip;

Edit Chip color

Ryan Cogswell
  • 75,046
  • 9
  • 218
  • 198
  • This seems to work very well. Unfortunately, I'm on a version of React that doesn't support hooks. Hopefully we'll get there soon. In the mean time, I came up with a solution that uses an HOC to inject the styles using props into the component. Here's the [CodeSandbox](https://codesandbox.io/s/r41no67q0m?fontsize=14) – matwlev Mar 06 '19 at 19:19
  • v4 of Material-UI will require hooks support, so in order to keep getting further Material-UI enhancements, you'll definitely want to get React upgraded. As long as you're already on React 16 and not some earlier version, it should be a pretty painless upgrade. – Ryan Cogswell Mar 06 '19 at 21:54
  • @RyanCogswell why do you call `shouldForwardProp` from the system package again? The example from the [docs](https://mui.com/system/styled/#custom-components) doesn't do it and it makes no difference to me when playing around in the codesandbox with or without the second `shouldForwardProp`. – NearHuscarl Nov 08 '21 at 07:17
  • @NearHuscarl I think I included it because in looking through the source it looked like when you specify a custom `shouldForwardProp` that it isn't additive to the default -- it replaces it. On the assumption that the default implementation has an impact in at least some scenarios, I included it, but I have not found a scenario where it seems to make a difference. The example in the docs does include part of the default implementation (checking for `sx`), but it isn't clear to me in what scenario that has an effect. – Ryan Cogswell Nov 08 '21 at 16:45
  • `it isn't additive to the default` That's why it's confusing to me, it looks like your code is the way to go based on the source since a custom `shouldForwardProp` [overrides the original one from the system completely](https://github.com/mui-org/material-ui/blob/e0cdcd130db60d252c4382570844ea7278649a08/packages/mui-system/src/createStyled.js#L108-L110), it doesn't use `createChainedFunction` to merge multiple functions. There are probably some extra magic that I miss somewhere when reading the source code. – NearHuscarl Nov 08 '21 at 17:09
1

In Material UI v5, you can use sx prop to style pseudo-classes like :hover:

<Chip
  label="Chip"
  onClick={() => {}}
  sx={{
    ':hover': {
      bgcolor: 'red',
    },
  }}
/>

Another alternative is styled() if you want to create a reusable styled component:

const options = {
  shouldForwardProp: (prop) => prop !== 'hoverBgColor',
};
const StyledChip = styled(
  Chip,
  options,
)(({ hoverBgColor }) => ({
  ':hover': {
    backgroundColor: hoverBgColor,
  },
}));
<StyledChip label="Chip" onClick={() => {}} hoverBgColor="red" />

Codesandbox Demo

Olivier Tassinari
  • 8,238
  • 4
  • 23
  • 23
NearHuscarl
  • 66,950
  • 18
  • 261
  • 230