Dynamically adjust ImageList columns based on screen size?

Viewed 2304

I've tried using ImageList component instead of Grid as I just need a grid of photos with titles and it seems to be the whole point of ImageList. My issue is that unlike with Grid I cannot pass breakpoint props for different screen sizes (which I find weird as this would seem logical) so that I can get different count of columns on different screens. What would be the best approach to adjust number of columns based on screen size?

6 Answers

ImageList uses CSS grid and needs the col prop to set the grid-template-columns but without any responsive API baked in. You can swap the ImageList with a Box component with the display set to grid, and uses the sx prop to declare the column template value depend on the screen size, but first let define some breakpoints:

const theme = createTheme({
  breakpoints: {
    values: {
      mobile: 0,
      bigMobile: 350,
      tablet: 650,
      desktop: 900
    }
  }
});

Then in the component, you can start using it like this:

import ImageListItem, { imageListItemClasses } from "@mui/material/ImageListItem";
<ThemeProvider theme={theme}>
  <Box
    sx={{
      display: "grid",
      gridTemplateColumns: {
        mobile: "repeat(1, 1fr)",
        bigMobile: "repeat(2, 1fr)",
        tablet: "repeat(3, 1fr)",
        desktop: "repeat(4, 1fr)"
      }
      // standard variant from here:
      // https://github.com/mui-org/material-ui/blob/3e679ac9e368aeb170d564d206d59913ceca7062/packages/mui-material/src/ImageListItem/ImageListItem.js#L42-L43
      [`& .${imageListItemClasses.root}`]: {
        display: "flex",
        flexDirection: "column"
      }
    }}
  >
    {itemData.map((item) => <ImageListItem {...}/>)}
  </Box>
</ThemeProvider>

Live Demo

Codesandbox Demo

References

This solution I came up with works, but seems like a lot of lines for something that Grid handles out of the box. Doesn't ImageList have some built in responsive design implementation?

export function Example(props) {
    // not sure if there is a way to get something like this dictionary from React?
    const breakpoints = {
        xs: 0,
        sm: 600,
        md: 960,
        lg: 1280,
        xl: 1920
    }

    const getColumns = (width) => {
        if (width < breakpoints.sm) {
            return 2
        } else if (width < breakpoints.md) {
            return 3
        } else if (width < breakpoints.lg) {
            return 6
        } else if (width < breakpoints.xl) {
            return 7
        } else {
            return 8
        }
    }

    const [columns, setColumns] = useState(getColumns(window.innerWidth))
    const updateDimensions = () => {
        setColumns(getColumns(window.innerWidth))
    }

    useEffect(() => {
        window.addEventListener("resize", updateDimensions);
        return () => window.removeEventListener("resize", updateDimensions);
    }, []);

    return (
        <ImageList cols={columns}>
          {/* list items ... */}
        </ImageList>
    )
}

I spotted a similar problem. The ImageList renders a <ul> tag in DOM. Hence I created my own ImageList styled <ul> component which works fine with ImageListItem. Here as per the gridTemplateColumns attribute for screens with display size sm will show 2 images, display size md will show 4 images and display size lg will show 5 images.

import * as React from 'react';
import ImageListItem from '@mui/material/ImageListItem';
import { styled } from '@mui/material/styles';

const ImageGalleryList = styled('ul')(({ theme }) => ({
    display: 'grid',
    padding: 0,
    margin: theme.spacing(0, 4),
    gap: 8,
    [theme.breakpoints.up('sm')]: {
        gridTemplateColumns: 'repeat(2, 1fr)'
    },
    [theme.breakpoints.up('md')]: {
        gridTemplateColumns: 'repeat(4, 1fr)'
    },
    [theme.breakpoints.up('lg')]: {
        gridTemplateColumns: 'repeat(5, 1fr)'
    },
}));

export default function ImageGallery({imageData}) {
    return (
        <ImageGalleryList>
            {itemData.map((item) => (
                <ImageListItem key={item.img}>
                    // Replace this with your ImageListItem
                </ImageListItem>
            ))}
        </ImageGalleryList>
    );
}

Instead of using ImageList I used "Image masonry" seems to work. https://mui.com/material-ui/react-masonry/#main-content

<Masonry columns={{ xs: 1, sm: 2, md: 3, lg: 4, xl: 5 }} spacing={{ xs: 1, sm: 2 }}>

Image masonry This example demonstrates the use of Masonry for images. Masonry orders its children by row. If you'd like to order images by column, check out ImageList.

I resolved this by overriding column-count property on ImageList (root component)

So, you can add breakpoints using sx props

<ImageList
  sx={{
    columnCount: {
      xs: '1 !important',
      sm: '2 !important',
      md: '3 !important',
      lg: '4 !important',
      xl: '5 !important',
    },
  }}
>
  {/* list items ... */}
</ImageList>

I used the useMediaQuery hook to get the cols props for the ImageList component.

import { ImageList, ImageListItem, useMediaQuery } from '@mui/material';

function Gallery() {
const matches = useMediaQuery('(min-width:600px)');
return (
    <ImageList cols={matches ? 3 : 2} variant="masonry">
      <ImageListItem>
       ...
      </ImageListItem>
    </ImageList>
  );
}
Related