Search code examples
reactjsresponsive-designgrommet

How can i include Responsive Context to make my layout responsive on mobile?


I made an appliaction in React and used the library Grommet to design my components. I defined a theme file to set breakpoints for different mobile devices:

const customBreakpoints = deepMerge(grommet, {
  global: {
    breakpoints: {
      small: {
        value: 768,
        borderSize: {
          xsmall: "1px",
          small: "2px",
          medium: "4px",
          large: "6px",
          xlarge: "12px"
        },
        edgeSize: {
          none: "0px",
          hair: "1px",
          xxsmall: "2px",
          xsmall: "3px",
          small: "6px",
          medium: "12px",
          large: "24px",
          xlarge: "48px"
        },
        size: {
          xxsmall: "24px",
          xsmall: "48px",
          small: "96px",
          medium: "192px",
          large: "384px",
          xlarge: "768px",
          full: "100%"
        }
      },
      medium: { value: 1536 },
      large: {}
    }
  }
});

To layout the buttons I used grommets Box component:

const App = () => (
  <Grommet theme={customBreakpoints}>
    <ResponsiveContext.Consumer>
      {size => (
        <div>
          <Box
            direction="column"
            align="center"
            gap="medium"
            pad="small"
            overflow={{
              horizontal: "auto"
            }}
          >
            <Button
              primary
              hoverIndicator="true"
              style={{
                width: "100%"
              }}
              label="Next"
            />
            <Box width="medium" direction="row-responsive">
              <Button
                primary
                icon={<DocumentPdf color="white" />}
                style={{
                  boxSizing: "border-box",
                  background: "red",
                  height: "38px",
                  lineHeight: "24px",
                  fontSize: "18px",
                  fontWeight: 600,
                  paddingLeft: "20px",
                  paddingRight: "20px"
                }}
                label="Export PDF"
              />

              <Button
                primary
                icon={<DocumentPdf color="white" />}
                style={{
                  boxSizing: "border-box",
                  background: "red",
                  height: "38px",
                  lineHeight: "24px",
                  fontSize: "18px",
                  fontWeight: 600,
                  paddingLeft: "20px",
                  paddingRight: "20px"
                }}
                label="Export all"
              />
            </Box>
          </Box>
        </div>
      )}
    </ResponsiveContext.Consumer>
  </Grommet>
);

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

When I run the application and inspect my current window on different devices I get the following output:

enter image description here

and

enter image description here

Even with ResponsiveContext.Consumer and setting the width=size} it didn´t work.

Any suggestions?

I made a Sandbox example.


Solution

  • Buttons are not the driver of the layout, and their layout is controlled by the wrapper layout component, in your case, it is the Box component below the Grommet component on the codesanbox.

    The Box component is automatically responsive to the custom breakpoints you have defined and that seems to work as expected, that being said, grommet does provide well-calculated out of the box breakpoint definitions that your app will use as default anyway, so unless you have special requirements for custom breakpoints, using the default usually does the trick and removes complexity.

    Your code was importing ResponsiveContext, but ResponsiveContext wasn't wrapping the layout component, and it didn't use the size prop, so once those are added I think it works as expected. I only replaced the div with ResponsiveContext (div isn't needed when using a Box), and added size and width prop to Box as follows:

        <ResponsiveContext.Consumer>
          {size => (
            <Box
              direction="column"
              align="center"
              gap="medium"
              pad="small"
              overflow={{
                horizontal: "auto"
              }}
              width={size==="small" ? "200px" : "400px"}
            >
       ...
    
    

    Using the above code portion in your sandbox will give you a better idea of how to control the buttons in a responsive manner. You can play with the values on the width prop as you see fit, currently, it sets the width of the Button to 200px on 'small' screen size, and uses 400px otherwise (I've picked random values), and you can play with the logic and values as you see fit. As you suggested, you can also use width={size} and that will default the size of the width to the breakpoint value ('small', 'medium', 'large'...)

    Here is the full code example:

    import React from "react";
    import ReactDOM from "react-dom";
    import { Box, Button, Grommet, ResponsiveContext } from "grommet";
    import { DocumentPdf } from "grommet-icons";
    
    import { deepMerge } from "grommet/utils";
    import { grommet } from "grommet/themes";
    
    import "./styles.css";
    
    const customBreakpoints = deepMerge(grommet, {
      global: {
        breakpoints: {
          small: {
            value: 368,
            borderSize: {
              xsmall: "1px",
              small: "2px",
              medium: "4px",
              large: "6px",
              xlarge: "12px"
            },
            edgeSize: {
              none: "0px",
              hair: "1px",
              xxsmall: "2px",
              xsmall: "3px",
              small: "6px",
              medium: "12px",
              large: "24px",
              xlarge: "48px"
            },
            size: {
              xxsmall: "24px",
              xsmall: "48px",
              small: "96px",
              medium: "192px",
              large: "384px",
              xlarge: "768px",
              full: "100%"
            }
          },
          medium: {
            value: 768,
            borderSize: {
              xsmall: "2px",
              small: "4px",
              medium: "8px",
              large: "12px",
              xlarge: "16px"
            },
            edgeSize: {
              none: "0px",
              hair: "1px",
              xxsmall: "2px",
              xsmall: "3px",
              small: "6px",
              medium: "12px",
              large: "24px",
              xlarge: "48px"
            },
            size: {
              xxsmall: "48px",
              xsmall: "96px",
              small: "192px",
              medium: "384px",
              large: "768px",
              xlarge: "1200px",
              full: "100%"
            }
          },
          large: {}
        }
      }
    });
    
    const App = () => (
      <Grommet theme={customBreakpoints}>
        <ResponsiveContext.Consumer>
          {size => (
            <Box
              direction="column"
              align="center"
              gap="medium"
              pad="small"
              overflow={{
                horizontal: "auto"
              }}
              width={size==="small" ? "200px" : "400px"}
            >
              <Button
                primary
                hoverIndicator="true"
                style={{
                  width: "100%"
                }}
                label="Next"
              />
    
              <Button
                primary
                icon={<DocumentPdf color="white" />}
                style={{
                  width: "100%"
                }}
                label="Export PDF"
              />
    
              <Button
                primary
                icon={<DocumentPdf color="white" />}
                style={{
                  width: "100%"
                }}
                label="Export all"
              />
            </Box>
          )}
        </ResponsiveContext.Consumer>
      </Grommet>
    );
    
    const rootElement = document.getElementById("root");
    ReactDOM.render(<App />, rootElement);
    

    Hope this helps.