How to get property based CSS styles to work with local rule references in Material-UI and JSS?

677 Views Asked by At

I cannot find a way to get both a local rule reference (named '& $value' in the code below) to fully work in conjunction with using functions for CSS definitions. Any CSS properties that are defined using a function accepting a StyleProperties object and returning a CSS value are simply ignored when defined for rules that reference other local rules; please see code and comments:

import * as React from "react";

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

type StyleProperties = {
  scale: number;
};

const useStyles = makeStyles((theme: Theme) =>
  createStyles({
    root: (props: StyleProperties) => {
      const size = `${2.0 * props.scale}rem`;

      return {
        position: "relative",
        backgroundColor: theme.palette.background.paper,
        border: `1px solid ${theme.palette.divider}`,
        width: size,
        height: size,
        "& > div": {
          position: "absolute"
        }
      };
    },

    value: {
      color: theme.palette.text.primary,
      width: "100%",
      height: "100%",
      textAlign: "center",
      lineHeight: (props: StyleProperties) => `${2.0 * props.scale}rem`
    },

    static: {
      "& $value": {
        // Following are OK
        color: theme.palette.action.disabled,
        backgroundColor: theme.palette.action.disabledBackground,

        // Ignored
        border: (props: StyleProperties) =>
          `${0.0625 * props.scale}rem solid red`

        // Comment out above line and uncomment the following to see expected output
        // if the value 2.0 was passed in through props.
        //border: `${ 0.0625 * 2.0 }rem solid red`,
      }
    }
  })
);

type ValueProperties = {
  value: string;
  scale: number;
};

function Value(props: ValueProperties) {
  const classes = useStyles(props);
  return <div className={clsx(classes.value)}>{props.value}</div>;
}

type CellProperties = {
  static: boolean;
  scale: number;
};

function Cell(props: CellProperties) {
  const classes = useStyles(props);
  return (
    <div className={clsx(classes.root, { [classes.static]: props.static })}>
      <Value value="value" scale={props.scale} />
    </div>
  );
}

export default function App() {
  return (
    <div className="App">
      <CssBaseline />
      <Cell static={false} scale={2.0} />
      <Cell static={true} scale={2.0} />
    </div>
  );
}

I have also tried using a function accepting my StyleProperties object at both static and '& $value' rule levels rather than at the CSS Property levels, but both approaches fully ignored all CSS definitions found within '& $value'.

There are no error messages or warnings output when using the code above. After searching around through the documentation of both Material-UI and JSS I did not find any mention of known limitations relating to this scenario.

Ultimately, defining the CSS based on values provided by StyleProperties and the Material-UI Theme is a strict requirement for the interface of my application. So if you would like to suggest an alternate approach to declaring hierarchy based style rules, please keep that in mind.

EDIT: As requested I am providing a code sandbox to demonstrate the issue: https://codesandbox.io/s/relaxed-bird-sklgr -- see border definition on line 43 and the fact it is not being applied.

1

There are 1 best solutions below

2
On BEST ANSWER

The issue is that you are calling useStyles from both Cell and Value. This causes two different classes to be generated for the value rule and two different classes to be generated for the static rule. The value class generated for Cell is being referenced by "& $value" in the static class applied to Cell, but it is the value class generated for Value that is applied to the Value element, so it isn't matched by the descendant selector in the static class.

You can fix this by only calling useStyles in one place and passing the value class name down to the Value element as in the example below:

import * as React from "react";

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

type StyleProperties = {
  scale: number;
};

const useStyles = makeStyles((theme: Theme) =>
  createStyles({
    root: (props: StyleProperties) => {
      const size = `${2.0 * props.scale}rem`;

      return {
        position: "relative",
        backgroundColor: theme.palette.background.paper,
        border: `1px solid ${theme.palette.divider}`,
        width: size,
        height: size,
        "& > div": {
          position: "absolute"
        }
      };
    },

    value: {
      color: theme.palette.text.primary,
      width: "100%",
      height: "100%",
      textAlign: "center",
      lineHeight: (props: StyleProperties) => `${2.0 * props.scale}rem`
    },

    static: {
      "& $value": {
        color: theme.palette.action.disabled,
        backgroundColor: theme.palette.action.disabledBackground,

        border: (props: StyleProperties) =>
          `${0.0625 * props.scale}rem solid red`
      }
    }
  })
);

type ValueProperties = {
  value: string;
  scale: number;
  className: string;
};

function Value(props: ValueProperties) {
  return <div className={props.className}>{props.value}</div>;
}

type CellProperties = {
  static: boolean;
  scale: number;
};

function Cell(props: CellProperties) {
  const classes = useStyles(props);
  return (
    <div className={clsx(classes.root, { [classes.static]: props.static })}>
      <Value value="value" className={classes.value} scale={2.0} />
    </div>
  );
}

export default function App() {
  return (
    <div className="App">
      <CssBaseline />
      <Cell static={false} scale={2.0} />
      <Cell static={true} scale={2.0} />
    </div>
  );
}

Edit makeStyles with nested