0

I've got a React/Material UI app. The Portal displays a Panel and a Widget.

Main App: Display Portal and Set Background to Blue

export default function App() {
  return (
    <div
      style={{
        backgroundColor: "blue"
      }}
    >
      <Portal></Portal>
    </div>
  );
}

Here is my codesandbox.

The key files to look at are Portal.js, and Layout.js.

Layout defines parameters to be applied at each breakpoint. It is intended to aid in the design of the Portal responsiveness to window size.

Portal places the Widget at the upper left, and dynamically "flexes" the Panel to fall beneath the widget at "xs", and "sm", and to the right of the widget at "md", "lg", and "xl".

The Widget retains its aspect ratio. In the following example, the Widget has a width of 400 and a height of 800. However this can change based on user action.

Portal has two primary methods, the React functional component, and the Material UI makeStyles function.

Portal: React Functional Component

export default function Portal() {
  const selWidHeight = 400;
  const selWidWidth = 800;

  const layout = Layout(selWidHeight / selWidWidth);
  const sizes = {
    containerWidth: layout.selWidgetWidth,
    containerHeight: layout.selWidgetHeight,
    bookmarkPanelContainerHeight: "600px",
    widgetWidth: "90%",
    widgetHeight: "90%",
    viewWidth: layout.viewWidth
  };
  const classes = useStyles(sizes);

  return (
    <>
      <>
        {layout.viewWidth > 0 ? (
          <Grid
            container
            direction="row"
            alignItems="flex-start"
            alignContent="flex-start"
            className={classes.root}
          >
            <Grid
              item
              xs={layout.xs.widgetGridCells}
              sm={layout.sm.widgetGridCells}
              md={layout.md.widgetGridCells}
              lg={layout.lg.widgetGridCells}
              xl={layout.xl.widgetGridCells}
            >
              <Container
                className={classes.widgetContainer}
                disableGutters={true}
                maxWidth={false}
              >
                <Widget
                  className={classes.widget}
                  width={sizes.widgetWidth}
                  height={sizes.widgetHeight}
                />
              </Container>
            </Grid>

            <Grid
              item
              xs={layout.xs.bmkGridCells}
              sm={layout.sm.bmkGridCells}
              md={layout.md.bmkGridCells}
              lg={layout.lg.bmkGridCells}
              xl={layout.xl.bmkGridCells}
            >
              <Container
                className={classes.bookmarkPanel}
                disableGutters={true}
              >
                <Panel />
              </Container>
            </Grid>
          </Grid>
        ) : (
          <div>Window Too Small</div>
        )}
      </>
    </>
  );
}

Portal: makeStyles Function

const useStyles = makeStyles((theme) => {
  return {
    root: {
      width: (props) => props.viewWidth,
      height: "100%",
      margin: "0px"
    },
    rootContainer: {
      display: "flex",
      justifyContent: "flex-start",
      flexDirection: "row"
    },
    widgetContainer: {
      width: (props) => props.containerWidth,
      height: (props) => props.containerHeight,
      minWidth: (props) => props.containerWidth,
      backgroundColor: "orange",
      display: "flex",
      flexDirection: "row",
      flexWrap: "wrap",
      justifyContent: "center",
      alignItems: "center",
      position: "relative"
    },
    bookmarkPanel: {
      overflowY: "scroll",
      border: "1px solid green",
      float: "left",
      height: (props) => props.bookmarkPanelContainerHeight,
      position: "relative",
      background: "repeating-linear-gradient(17deg,  #ebf8e1 15%, #f69d3c 20%)"
    },
    widget: {
      width: (props) => {
        return props.containerWidth;
      },
      height: (props) => {
        return props.widgetHeight;
      },
      background:
        "repeating-linear-gradient(45deg, #3f87a6, #ebf8e1 15%, #f69d3c 20%)",
      position: "absolute"
    }
  };
});

Panel: Styled Div

const useStyles = makeStyles((theme) => ({
  root: {
    backgroundColor: "red"
  }
}));

const Panel = () => {
  const classes = useStyles();
  return <div className={classes.root}>Panel </div>;
};

Widget: Styled Div of a Given Width and Height - Aspect Ratio is Important

const useStyles = makeStyles((theme) => ({
  root: {
    width: (props) => props.width,
    height: (props) => props.height,
    background:
      "repeating-linear-gradient(45deg, #3f87a6, #ebf8e1 15%, #f69d3c 20%)"
  }
}));

const Widget = ({ width, height }) => {
  const props = { width: width, height: height };
  const classes = useStyles(props);
  return <div className={classes.root}>Widget </div>;
};

Layout Controls Key Parameters:

const Layout = (aspect_ratio) => {
  const theme = useTheme();
  const xsMatches = useMediaQuery(theme.breakpoints.up("xs"));
  const smMatches = useMediaQuery(theme.breakpoints.up("sm"));
  const mdMatches = useMediaQuery(theme.breakpoints.up("md"));
  const lgMatches = useMediaQuery(theme.breakpoints.up("lg"));
  const xlMatches = useMediaQuery(theme.breakpoints.up("xl"));

  var sizingDetails = {
    xl: {
      widgetGridCells: 10,
      bmkGridCells: 2
    },
    lg: {
      widgetGridCells: 8,
      bmkGridCells: 4
    },
    md: {
      widgetGridCells: 9,
      bmkGridCells: 3
    },
    sm: {
      widgetGridCells: 12,
      bmkGridCells: 12
    },
    xs: {
      widgetGridCells: 12,
      bmkGridCells: 12
    }
  };

  if (xlMatches) {
    sizingDetails.selWidgetWidth = (theme.breakpoints.values.xl * 10.0) / 12.0;
    sizingDetails.viewWidth = theme.breakpoints.values.xl;
    console.log("xl: ", sizingDetails);
  } else if (lgMatches) {
    sizingDetails.selWidgetWidth = (theme.breakpoints.values.lg * 8.0) / 12.0;
    sizingDetails.viewWidth = theme.breakpoints.values.lg;
    console.log("lg: ", sizingDetails);
  } else if (mdMatches) {
    sizingDetails.selWidgetWidth = (theme.breakpoints.values.md * 9) / 12.0;
    sizingDetails.viewWidth = theme.breakpoints.values.md;
    console.log("md: ", sizingDetails);
  } else if (smMatches) {
    sizingDetails.selWidgetWidth = theme.breakpoints.values.sm;
    sizingDetails.viewWidth = theme.breakpoints.values.sm;
    console.log("sm: ", sizingDetails);
  } else if (xsMatches) {
    sizingDetails.selWidgetWidth = theme.breakpoints.values.xs;
    sizingDetails.viewWidth = theme.breakpoints.values.xs;
    console.log("xs: ", sizingDetails);
  } else {
    sizingDetails.selWidgetWidth = 0;
    sizingDetails.viewWidth = 0;
    console.log("none: ", sizingDetails);
  }
  sizingDetails.selWidgetHeight = sizingDetails.selWidgetWidth * aspect_ratio;

  return sizingDetails;
};

export default Layout;

I am building this to be responsive to the size of the containing window (shown blue below). My current issue is with "md", "lg", and "xl" breakpoints.

I am currently ok with "sm", and "xs" (in that case the Panel is displayed below the Widget, and its width is not an issue for me.). enter image description here

At medium, large and extra large I want to display the Panel to the right as you see here (for medium - md): enter image description here

In this view the Widget is displayed properly. The Panel is in the correct location, with the correct height. However I want its width to "right-fill" the blue containing window at this breakpoint, and it does not.

In short, the Panel's width should expand dynamically (as the browser window's width is increased in size, for a given breakpoint) to fill the blue containing div.

The styling on the Panel div shows that it is a fixed 240 pixels wide at this breakpoint. This 240 must be changed dynamically to ensure that the div increases to fill the container.

enter image description here

The styling on the Grid container: enter image description here In response to prior feedback, I've reduced this experiment to the smallest possible code footprint, and have captured that in codesandbox

Here is my codesandbox.

David Sargrad
  • 121
  • 1
  • 9
  • I may be close to an answer given this answer to a related question. I found that by adjusting flex: 0 0 230px on the rightmost column to flex: 1 0 230px it seemed to give me what I want: https://stackoverflow.com/a/23794791/15307667 In addition to this, it is necessary for me to increase the min-width on the containing div (the top-level Grid container). I'll post my answer as a codesandbox .. assuming that I can nail it. My problem is still a bit more complex since I want the Panel to move from right of widget to beneath widget as a function of breakpoint. – David Sargrad Oct 11 '21 at 13:00
  • I'm struggling to come up with the right approach. Its possible that I should not even be using the material-ui grid. It seems that this requires material-ui and flex expertise. I hope one of the experts in "flex" and/or material-ui can weigh in on this. – David Sargrad Oct 11 '21 at 13:31

1 Answers1

0

I've implemented a fix that I am quite happy with. I spent a fair amount of time refactoring my application to support this.

Credit to various sources for a key element of my fix: See here, and here.

In short, my Portal is now displayed in 2 modes: (Large Width, and Small Width). The Small Width mode stacks the Panel beneath the Widget, and it uses the Grid mechanism as displayed in my question.

The Large Width mode does not use the material ui Grid (since that did not give me adequate control to ensure dynamic sizing of the Panel. In this case I simply used material - ui Container as the container and as the Panel item. Then I style as suggested in the link above. The key in this case, is to dynamically track the change of the containing windows width and height, and then to use this to set the corresponding widths of the containing div, and the items within that container.

<Container className={classes.rootContainer}>
              <Container
                ref={playerContainerRef}
                className={classes.widgetContainer}
                disableGutters={true}
                maxWidth={false}
              >
                {my_widget}
              </Container>

              <Container
                className={classes.bookmarkPanelContainer}
                disableGutters={true}
              >
                {my_panel}
              </Container>
            </Container>

Then I style with:

rootContainer: {
      display: "flex",
      maxWidth: (props) => props.viewWidth,
    },
    widgetContainer: {
      width: (props) => props.widgetContainerWidth,
      height: (props) => props.widgetContainerHeight,
      minWidth: (props) => props.widgetContainerWidth,
      backgroundColor: "red",
      display: "flex",
      flexDirection: "row",
      flexWrap: "wrap",
      justifyContent: "center",
      alignItems: "center",
      position: "relative",
    },
    bookmarkPanelContainer: {
      overflowY: "scroll",
      border: "1px solid red",
      float: "left",
      height: (props) => props.bookmarkPanelContainerHeight,
      position: "relative",
    },
David Sargrad
  • 121
  • 1
  • 9