Shmew/Feliz.MaterialUI

Can't resolve some theme properties

Closed this issue · 18 comments

Shmew commented

I'm not sure if there is a way to get this, but it doesn't appear to be possible to use the theme.palette type within Styles.makeStyles similar to how it's shown in the examples:

const useStyles = makeStyles(theme => ({
  root: {
    color: theme.status.danger,
    '&$checked': {
      color: theme.status.danger,
    },
  },
  checked: {},
}));

theme.palette.primary for example is a U2<PaletteIntention,Color> and trying to use it like this:

let useStyles : unit -> _ = Styles.makeStyles <| fun (theme: Theme) ->
        let drawerWidth = 200

        {| appBar =
            Styles.create
                [ style.zIndex (theme.zIndex.drawer + 1)
                  style.cursor "default"
                  style.userSelect.none
                  style.display.grid
                  style.color (
                      match theme.palette.primary with
                      | U2.Case1 pI -> pI.dark 
                      | U2.Case2 c -> c.``500``) ] |}

Results in the following compiler error:
FABLE: Cannot type test: interface "Feliz.MaterialUI.Color"

BTW: Thanks for the tip about the markdown language thing, very handy!

Taking a step back from the actual type test error, I'm not sure the palette is supposed to be (or indeed can be) used that way.

For reference, here is the relevant MUI documentation. It says that

The intention value can either be a color object, or an object with one or more of the keys specified by the following TypeScript interface:

interface PaletteIntention {
  light?: string;
  main: string;
  dark?: string;
  contrastText?: string;
};

It can be either a color or a intention object when setting it, but I'm not sure whether it can be a color object when getting it from a MUI-supplied theme argument. C.f. the default theme object where it's an intention object.

I encourage you to ask a MUI-related question on Stack Overflow, or if you think the MUI docs should be improved, to post an issue on their repo. If you find out that Feliz.MaterialUI should actually support something it doesn't today, let me know.

Shmew commented

Yeah, I expected the value to just be a color string, and that's what it looks like it should be returning. That's why I posted the issue, I think the type should be different in Feliz.MaterialUI when it's returned vs when setting it. I only had the | U2.Case2 c -> c.``500`` because that was the only way to resolve the compiler errors.

Yeah, I expected the value to just be a color string

What exactly is it you expected to be a color string? For clarity, I was talking about theme.palette.primary, which is either a color object (not string) or a PaletteIntention.

I think the type should be different in Feliz.MaterialUI when it's returned vs when setting it

Related: #2 and in particular #2 (comment) by @Zaid-Ajaj.

Also, Feliz.MaterialUI must be based on documented MUI behaviour. It'd be fantastic if you could point to the MUI documentation (or a MUI issue) that shows how Feliz.MaterialUI is wrong. :) Otherwise I'd just be implementing based on guesswork from observed behavior, which might change.

Shmew commented

I had expected to be able to just do this:

let useStyles : unit -> _ = Styles.makeStyles <| fun (theme: Theme) ->
        let drawerWidth = 200

        {| appBar =
            Styles.create
                [ style.zIndex (theme.zIndex.drawer + 1)
                  style.cursor "default"
                  style.userSelect.none
                  style.display.grid
                  style.color theme.palette.primary.dark ] |}

I'm sorry if this all is just my naive understanding of MaterialUI, I'll make more of an effort to ensure I'm doing things correctly before bugging you with more issues ;).

The comment by @Zaid-Ajaj is exactly what I was getting at. As a user I would expect that if a function is returning an object to me everything I could try to use would be correct. Which could just be me being spoiled by .NET.

I had expected to be able to just do this:

Ah, I see.

I'm sorry if this all is just my naive understanding of MaterialUI, I'll make more of an effort to ensure I'm doing things correctly before bugging you with more issues ;).

No problem, not all parts of MUI are equally well documented (I'm confused by styling/theming stuff myself, particularly since there is a difference between creating/setting vs. getting).

As a user I would expect that if a function is returning an object to me everything I could try to use would be correct. Which could just be me being spoiled by .NET.

No, the goal is definitely to have "compiles = works" as far as possible here.

If you have any tips regarding a custom DSL for creating styles/themes, let me know. :) I haven't gotten to it yet but it's on my list (which is why #2 is still open).

To keep things focused, let's discuss theme/style creation DSL in #2. I'll leave this issue open.

Shmew commented

I'm pretty busy right now, but I definitely want to help. If we don't have a solution/decision on it I'll see if I can help resolve it when I have more time.

@Shmew I had the same issue, I’ve resolved it by using the color property of the component, instead of setting it inside a style.
As you can see in the appBar docs https://material-ui.com/api/app-bar/
It also has a color prob. For dark mode you have to set it during theme creation https://material-ui.com/customization/palette/#type-light-dark-theme

Shmew commented

Yeah I have different themes created for light/dark mode. The problem is that you can only set the component color from these options:

  • default
  • inherit
  • primary
  • secondary

I don't really get why MUI lets you build a theme with alternate values for primary, secondary and so on; then restrict you to only those options in the component api.

I don't really get why MUI lets you build a theme with alternate values for primary, secondary and so on; then restrict you to only those options in the component api.

I don't get it. If you create a theme with custom primary and secondary, then using the color.primary and color.secondary component props should use your custom colors.

I may be wrong, but I think that often, it's sufficient to customize primary/secondary without needing to set specific colors for specific components. (Certainly not in all cases, but for most plain, non-branded Material Design use-cases, it should suffice.)

By the way, in case it's helpful, I just added dark mode support to this project. Specifically, see Renderer/App.fs.

Shmew commented

Yeah I have dark mode working fine. I'm talking about if I want a component to be colored using the primary dark color like this:

Mui.textField [
    textField.color.primary.dark // or
    textField.color.primaryDark
    ...
]

Basically I'm asking why we can even assign dark and light accents to our primary if we can't use them:

let getTheme themeType = Styles.createMuiTheme(jsOptions<Theme> <| fun t ->
            t.palette <-
                jsOptions<Palette> (fun p ->
                    p.``type`` <- themeType.PaletteType
                    p.primary <-
                        !^(jsOptions<PaletteIntention> (fun pi ->
                        pi.main <- themeType.PMain
                        pi.dark <- themeType.PDark))

Oh, right. You are talking about the dark shade of the primary/secondary colors, not dark theme.

To be honest I'm not sure how to do this in MUI at all. If you figure out the way to do this in vanilla MUI, then let me know, and we can see if this is an issue with Feliz.MaterialUI. (If you already have figured out and are trying to tell me, then sorry, I apparently really need things spelled out today.)

Regarding textField.color.primary.dark and textField.color.primaryDark, those won't work, because they don't exist in MUI in the first place. Feliz.MaterialUI exposes MUI functionality, but I don't want to add "fake props" like that; I'd rather implement a good F# way to do it that mirrors the MUI way.

Shmew commented

Regarding textField.color.primary.dark and textField.color.primaryDark, those won't work, because they don't exist in MUI in the first place. Feliz.MaterialUI exposes MUI functionality, but I don't want to add "fake props" like that; I'd rather implement a good F# way to do it that mirrors the MUI way.

Yeah I absolutely agree. I was just asking/complaining "why can't we do this?". As far as I know there isn't really a way to do this except by getting the theme and setting the prop color via CSS.

Shmew commented

@cmeeren found an example of what I was trying to deal with above here

const styles = createStyles(theme => ({
  dayWrapper: {
    position: 'relative',
  },
  day: {
    width: 36,
    height: 36,
    fontSize: theme.typography.caption.fontSize,
    margin: '0 2px',
    color: 'inherit',
  },
  customDayHighlight: {
    position: 'absolute',
    top: 0,
    bottom: 0,
    left: '2px',
    right: '2px',
    border: `1px solid ${theme.palette.secondary.main}`,
    borderRadius: '50%',
  },
  nonCurrentMonthDay: {
    color: theme.palette.text.disabled,
  },
  highlightNonCurrentMonthDay: {
    color: '#676767',
  },
  highlight: {
    background: theme.palette.primary.main,
    color: theme.palette.common.white,
  },
  firstHighlight: {
    extend: 'highlight',
    borderTopLeftRadius: '50%',
    borderBottomLeftRadius: '50%',
  },
  endHighlight: {
    extend: 'highlight',
    borderTopRightRadius: '50%',
    borderBottomRightRadius: '50%',
  },
}));

I see, thanks! Are you able to find any part of the MUI docs which says that theme.palette.primary will always be a PaletteIntention object, never a color object?

By the way, in the meantime, you can work around the issue by unboxing:

style.color (unbox<PaletteIntention> theme.palette.primary).dark
Shmew commented

Nothing in the docs that says for certain, but digging around it looks like all leaf nodes of the theme should be strings.

Thanks!

it looks like all leaf nodes of the theme should be strings.

IIRC the leaf nodes of the Color object are strings, too. The issue at hand is whether theme.palette.primary, .secondary, etc. are Color objects or PaletteIntention ojects (containing light, main, dark, and contrastText).

I just re-read the MUI docs on customizing the palette, and I now interpret them as saying that they will always be PaletteIntention objects, and that if setting them to a Color object, the values will simply be set according to this mapping:

palette: {
  primary: {
    light: palette.primary[300],
    main: palette.primary[500],
    dark: palette.primary[700],
    contrastText: getContrastText(palette.primary[500]),
  },
  secondary: {
    light: palette.secondary.A200,
    main: palette.secondary.A400,
    dark: palette.secondary.A700,
    contrastText: getContrastText(palette.secondary.A400),
  },
  error: {
    light: palette.error[300],
    main: palette.error[500],
    dark: palette.error[700],
    contrastText: getContrastText(palette.error[500]),
  },
},

My conclusion is that in order to solve this particular issue, we need a proper theme creation/customization DSL. This DSL needs to account both for creating a (partial) theme from scratch, and for customizing an existing theme. Let's take that in #2.

With the new theme DSL, my understanding is that this is no longer an issue and works as expected. Let me know if that's incorrect.