mui / material-ui

Material UI: Comprehensive React component library that implements Google's Material Design. Free forever.
https://mui.com/material-ui/
MIT License
93.9k stars 32.26k forks source link

Can we add new property for color in palette in MuiTheme #14185

Closed kushal-tech closed 5 years ago

kushal-tech commented 5 years ago

Like MuiTheme already provided a way to override colors of 3 property

This is the below way to change the colors of existing properties.

palette: {
  primary: {
    main: palette.primary[500],
  },
  secondary: {
    main: palette.secondary.A400,
  },
  error: {
    main: palette.error[500],
  },
},

What If we need to define new property for color like

palette: {
  primary: {
    main: palette.primary[500],
  },
  secondary: {
    main: palette.secondary.A400,
  },
  error: {
    main: palette.error[500],
  },

>   success : {
>     main : "#bac778"
>   }

},

Is that possible ?

oliviertassinari commented 5 years ago

@kushaljain03 Yes, you can:

const theme = createMuiTheme({
  palette: {
    success: {
      main : "#bac778",
    },
  },
})
kushal-tech commented 5 years ago

Yes I can but I cannot use it like <Button color="success">Login</Button>

It gives warning about to use only primary default secondary

oliviertassinari commented 5 years ago

No, you can't use the success color property directly. You need to create a wrapping component to use the theme values. Going forward, I wish we can make it work. We have recently added the support for style functions. If the performance are good enough. We can imagine changing the implementation.

eps1lon commented 5 years ago

Adding additional properties is not explicitly documented. At least I couldn't find it. I think we can add another section that also includes how one would make this work with TypeScript.

Actually: https://material-ui.com/customization/themes/#custom-variables for JS and https://material-ui.com/guides/typescript/#customization-of-theme with additional info for TS.

davidcalhoun commented 5 years ago

Color styling has been one of the major difficulties when using this library. For instance, a simple use case where you have three buttons side-by-side, all different colors. Trying to style this with themes is really limiting and unintuitive. From what I can tell this is only possible to implement with the first two buttons using one theme, then the third button using a different (nested) theme:

But this is a weird way to break things up logically, because in this app all of these colors should be under one theme.

Another option is to avoid themes for this entirely, but then this seems to give up the nice benefits of having the contrastText helpers and such.

oliviertassinari commented 5 years ago

@davidcalhoun Yes, you are right, this issue is about extending the Button implementation so people can just do:

const theme = createMuiTheme({
  palette: {
    success: {
      main : "#bac778",
    },
  },
})

then have a working:

<Button color="success" />

implementation.

The best solution right now is to add the color in the theme, then to create a wrapping component:

import React from "react";
import { withStyles, ThemeProvider } from "@material-ui/styles";
import MuiButton from "@material-ui/core/Button";
import { createMuiTheme } from "@material-ui/core/styles";

const Button = withStyles(theme => ({
  root: props =>
    props.color === "success" && props.variant === "contained"
      ? {
          color: theme.palette.success.contrastText,
          backgroundColor: theme.palette.success.main,
          "&:hover": {
            backgroundColor: theme.palette.success.dark,
            // Reset on touch devices, it doesn't add specificity
            "@media (hover: none)": {
              backgroundColor: theme.palette.success.main
            }
          }
        }
      : {}
}))(MuiButton);

const theme = createMuiTheme();

theme.palette.success = theme.palette.augmentColor({
  main: "#689f38"
});

function Theming() {
  return (
    <ThemeProvider theme={theme}>
      <Button variant="contained" color="primary">
        primary
      </Button>
      <Button variant="contained" color="success">
        success
      </Button>
    </ThemeProvider>
  );
}

export default Theming;

https://codesandbox.io/s/7zmw6ox8m1

capture d ecran 2019-03-02 a 19 13 54


By the way, we are planing on extending the color to primary | secondary | success | warning | error/danger? | info. Would that help in your case?

oliviertassinari commented 5 years ago

If you don't care about the warnings, you can do 🤷‍♂️:

import React from "react";
import { ThemeProvider } from "@material-ui/styles";
import Button from "@material-ui/core/Button";
import { createMuiTheme } from "@material-ui/core/styles";

const theme = createMuiTheme();
theme.palette.success = theme.palette.augmentColor({
  main: "#689f38"
});

const isSuccess = style => props =>
  props.color === "success" && props.variant === "contained" ? style : {};

theme.overrides = {
  MuiButton: {
    root: {
      color: isSuccess(theme.palette.success.contrastText),
      backgroundColor: isSuccess(theme.palette.success.main),
      "&:hover": {
        backgroundColor: isSuccess(theme.palette.success.dark)
      }
    }
  }
};

function Theming() {
  return (
    <ThemeProvider theme={theme}>
      <Button variant="contained" color="primary">
        primary
      </Button>
      <Button variant="contained" color="success">
        success
      </Button>
    </ThemeProvider>
  );
}

export default Theming;

https://codesandbox.io/s/r15rlnlznq

davidcalhoun commented 5 years ago

Thanks for the great examples! That helps. For sure, having other color names outside of just "primary" and "secondary" helps a lot, but I'm not sure if it solves my use case.

In my case I'm trying to use color themes for a dropdown button I'm creating with three states:

  1. Draft (a greyish background)
  2. In Progress (bluish background)
  3. Completed (greenish color)

It would be great to be able to define these in a theme somehow, but these states don't necessarily map onto semantic names (primary and secondary work fine for two of the three colors though!). I like defining them in the theme and then being able to rely on the theme to automatically figure out derived colors (text state based on contrast, hover state colors, etc).

I'm guessing the philosophy is different (or maybe it's a performance concern?), but I would kind of prefer being able to define custom color names inside the theme that wouldn't necessarily be semantic or whatnot, just practical. For the cases above, I'd like to define some colors inside the theme called grey, blue, and green.

I especially like the second example, but I want to avoid anything that generates warnings! For this particular use case, I think I'll just avoid using themes for now and look for other styling options.

eps1lon commented 5 years ago

@davidcalhoun This is something I'm experimenting locally. The material design guidelines already define a color and the "contrast" color (or "on" color in material speak).

The idea is that you can add any color in the theme and either let us figure out what a good "on"-color would be or let you define that too. The component should then take care of the rest i.e. given a color name figure out what color to choose for background, text or iconography.

States like hover, disabled, focused etc are currently handled via opacity in the material guidelines. Would be interesting to explore possible APIs to make this customizable too e.g. let the user define a theme wide function with the following signature: (Color, State) => Color.

kaitlynbrown commented 5 years ago

I have wanted something like this for as long as I've been using Material UI. I've always been extremely frustrated by the fact that the palette is limited to 3 colors, and we have to resort to these awful, dirty, awkward hacks to make it work with more. When can we expect this to be fixed?

mbrookes commented 5 years ago

When can we expect this to be fixed?

@kaitlynbrown And you started so well...

kaitlynbrown commented 5 years ago

@mbrookes And you started so vaguely...

eps1lon commented 5 years ago

Closing this in favor of #13875. You can already add custom colors to the palette. Just not use them in color props of our components.

acapro commented 4 years ago

For a typescript implementation you can do something like this. Using props in the styles are avoided to improve JSS performance. (This avoids generating unique classes for every button and works great in large lists, etc...)

import React from "react";
import { createStyles, Theme, makeStyles } from "@material-ui/core/styles";

import Button, { ButtonProps } from "@material-ui/core/Button";
import capitalize from "lodash/capitalize";

export type ColorTypes =
  | "primary"
  | "secondary"
  | "error"
  | "success"
  | "warning"
  | "default"
  | "inherit"
  | "info";

export type ColoredButtonProps = { color: ColorTypes } & Omit<
  ButtonProps,
  "color"
>;

const useStyles = makeStyles<Theme>(theme =>
  createStyles({
    outlinedSuccess: {
      borderColor: theme.palette.success.main,
      color: theme.palette.success.main
    },
    outlinedError: {
      borderColor: theme.palette.error.main,
      color: theme.palette.error.main
    },
    outlinedWarning: {
      borderColor: theme.palette.warning.main,
      color: theme.palette.warning.main
    },
    outlinedInfo: {
      borderColor: theme.palette.info.main,
      color: theme.palette.info.main
    },
    containedSuccess: {
      backgroundColor: theme.palette.success.main,
      color: theme.palette.success.contrastText,
      "&:hover": {
        backgroundColor: theme.palette.success.dark
      }
    },
    containedError: {
      backgroundColor: theme.palette.error.main,
      color: theme.palette.error.contrastText,
      "&:hover": {
        backgroundColor: theme.palette.error.dark
      }
    },
    containedWarning: {
      backgroundColor: theme.palette.warning.main,
      color: theme.palette.warning.contrastText,
      "&:hover": {
        backgroundColor: theme.palette.warning.dark
      }
    },
    containedInfo: {
      backgroundColor: theme.palette.info.main,
      color: theme.palette.info.contrastText,
      "&:hover": {
        backgroundColor: theme.palette.info.dark
      }
    }
  })
);

const ColoredButton: React.FC<ColoredButtonProps> = ({
  children,
  color,
  ...props
}) => {
  const classes = useStyles();
  const className = classes?.[`${props.variant}${capitalize(color)}`];
  const colorProp =
    ["default", "inherit", "primary", "secondary"].indexOf(color) > -1
      ? (color as "default" | "inherit" | "primary" | "secondary")
      : undefined;

  return (
    <Button {...props} color={colorProp} className={className}>
      {children}
    </Button>
  );
};

ColoredButton.displayName = "ColoredButton";

export default ColoredButton;
nazanin-bayati commented 2 years ago

For a typescript implementation and mui 5, you can act like this. Example of new color in the palette file:

import { colors } from "@mui/material",

const common = {
  infromation: {
    warnning: '#CFDD4A',
  }
}

type.ts will be like this:

import Palette from "@mui/material/styles/createPalette";

declare module '@mui/material/styles' {
  interface Palette {
    infromation?: {
      warnning?: string
    }
  }
}