カートに入れました

gatsby-starter-blogでブログ記事一覧をレスポンシブ対応する

PCの場合はこんな感じ

スマホは

mui関連の使用パッケージ

今回はmaterial-uiのv5を使用

以前と比べて表記などが多少変わっている

インストールしているパッケージは以下になる

  • "@emotion/react": "^11.8.1",
  • "@emotion/styled": "^11.8.1",
  • "@mui/icons-material": "^5.4.4",
  • "@mui/material": "^5.4.4",
  • "@mui/styles": "^5.4.4",

実装

記事一覧のソースコードは

import * as React from "react"
import { Link } from "gatsby"
import { GatsbyImage, getImage } from "gatsby-plugin-image"
import { styled } from "@mui/material/styles";
import { useTheme, useMediaQuery } from '@mui/material';

const BlogContainer = styled("article")(({ theme }) => ({
  ...theme.mixins.blogContainer,
}));

const BlogImage = styled("div")(({ theme }) => ({
  ...theme.mixins.blogImage,
}));

const BlogTitle = styled("div")(({ theme }) => ({
  ...theme.mixins.blogTitle,
}));

const Blogs = ({ posts }) => {
  const theme = useTheme();
  const isMobile = useMediaQuery(theme.breakpoints.down("xs"));
  // theme.breakpoints.up(xs)
  // theme.breakpoints.only(xs)
  // theme.breakpoints.between(xs, sm)

  return (
    <ol style={{ listStyle: `none` }}>
      {posts.map(post => {
        const title = post.frontmatter.title || post.fields.slug

        return (
          <BlogContainer key={post.fields.slug}>
            {
              post.frontmatter.hero ?
                <BlogImage>
                  <GatsbyImage style={{ objectFit: "fill" }} image={getImage(post.frontmatter.hero?.childImageSharp.gatsbyImageData)} alt={post.frontmatter.title} />
                </BlogImage>
              : ""
            }
            <BlogTitle>
              <Link to={`${post.fields.slug}`} style={{ textDecoration: `none`, }}>
                  {title}
              </Link>
            </BlogTitle>
            {
              !post.frontmatter.hero || isMobile ? <small>{post.frontmatter.date}</small> : ''
            }
            <section>
                <p
                  style={{ overflow: "hidden" }}
                  dangerouslySetInnerHTML={{
                      __html: post.frontmatter.description || post.excerpt,
                  }}
                  itemProp="description"
                />
            </section>
          </BlogContainer>
        )
      })}
    </ol>
  )
}

export default Blogs

src/component/mui-theme-provider.jsというファイルを作る

import { ThemeProvider, createTheme } from "@mui/material/styles"
import { CssBaseline } from "@mui/material"
import React from "react"

const MuiThemeProvider = ({ children }) => {
  const theme = createTheme({
    palette: {
      mode: "light",
      // primary: {
      //     main: '#fff',
      // },
    },
    mixins: {
      blogContainer: {
        width: "100%",
        minHeight: "250px",
        marginBottom: "24px",
        "@media (max-width:375px)": {
          minHeight: "150px",
        },
        "@media (min-width: 376px) and (max-width:768px)": {
        },
        "@media (min-width: 769px)" : {
        },
      },
      blogImage: {
        float: "left",
        maxWidth: "250px",
        width: "30%",
        margin: "24px",
        marginLeft: 0,
        borderRadius: 8,
        "@media (max-width:375px)": {
          float: "none",
          width: "200px",
          minHeight: "100px",
          maxHeight: "150px",
          marginLeft: "auto",
          marginRight: "auto",
          overflow: "hidden",
        },
        "@media (min-width: 376px) and (max-width:768px)": {
          fontSize: "1.2em",
        },
        "@media (min-width: 769px)" : {
          fontSize: "1.2em",
        },
      },
      blogTitle: {
        fontWeight: "bold",
        "@media (max-width:375px)": {
          fontSize: "1.0em",
          marginBottom: "8px",
        },
        "@media (min-width: 376px) and (max-width:768px)": {
          fontSize: "1.2em",
          marginBottom: "16px",
        },
        "@media (min-width: 769px)" : {
          fontSize: "1.5em",
          marginBottom: "24px",
        },
      },
    },
    // レスポンシブのブレイクポイント
    'breakpoints': {
      'keys': [
        'xs',
        'sm',
        'md',
        'lg',
        'xl',
      ],
      'values': {
        'xs': 376, // スマホ用
        'sm': 768, // タブレット用
        'md': 992, // PC用
        'lg': 1400,
        'xl': 1800,
      },
    },
  })

  return (
    <ThemeProvider theme={theme}>
      <CssBaseline />
      {children}
    </ThemeProvider>
  )
}

export default MuiThemeProvider

createThemeのmixinsの中にサイズに合わせたcssの記述をしている。これを

import { styled } from "@mui/material/styles";

const BlogContainer = styled("article")(({ theme }) => ({
  ...theme.mixins.blogContainer,
}));

のように用いることができる

GatsbyのプロジェクトでThemeProviderを使うには

import React from "react"
import MuiThemeProvider from "./src/components/mui-theme-provider"

export const wrapRootElement = ({ element }) => {
  return <MuiThemeProvider>{element}</MuiThemeProvider>
}

をgatsby-browser.jsに追加する必要がある。

このままgit pushしてhi1t0.comで確かめたところ、スタイルが反映されていなかった

調べてみたところgatsby-theme-material-uiというプラグインがあったのでインストール

gatsby-config.jsに追記

module.exports = { plugins: [gatsby-theme-material-ui], };

どうやらmixinsが効いてない

useMediaQueryの方は使えている

cssを書く場所を変更する

import * as React from "react"
import { Link } from "gatsby"
import { GatsbyImage, getImage } from "gatsby-plugin-image"
import { styled } from "@mui/material/styles";
import { useTheme, useMediaQuery } from '@mui/material';

const BlogContainer = styled("article")({
  // ...theme.mixins.blogContainer,
  width: "100%",
  minHeight: "250px",
  marginBottom: "24px",
  "@media (max-width:375px)": {
    minHeight: "150px",
  },
  "@media (min-width: 376px) and (max-width:768px)": {
  },
  "@media (min-width: 769px)" : {
  },
});

const BlogImage = styled("div")({
  float: "left",
  maxWidth: "250px",
  width: "30%",
  margin: "24px",
  marginLeft: 0,
  borderRadius: 8,
  "@media (max-width:375px)": {
    float: "none",
    minWidth: "320px",
    minHeight: "100px",
    maxHeight: "160px",
    marginLeft: "auto",
    marginRight: "auto",
    overflow: "hidden",
  },
  "@media (min-width: 376px) and (max-width:768px)": {
  },
  "@media (min-width: 769px)" : {
  },
});

const BlogTitle = styled("div")({
  fontWeight: "bold",
  "@media (max-width:375px)": {
    fontSize: "1.0em",
    marginBottom: "8px",
  },
  "@media (min-width: 376px) and (max-width:768px)": {
    fontSize: "1.2em",
    marginBottom: "16px",
  },
  "@media (min-width: 769px)" : {
    fontSize: "1.5em",
    marginBottom: "24px",
  },
});

const Blogs = ({ posts }) => {
  const theme = useTheme();
  const isMobile = useMediaQuery(theme.breakpoints.down("xs"));

  return (
    <ol style={{ listStyle: `none` }}>
      {posts.map(post => {
        const title = post.frontmatter.title || post.fields.slug

        return (
          <BlogContainer key={post.fields.slug}>
            {
              post.frontmatter.hero ?
                <BlogImage>
                  <GatsbyImage style={{borderRadius: 8}} image={getImage(post.frontmatter.hero?.childImageSharp.gatsbyImageData)} alt={post.frontmatter.title} />
                </BlogImage>
              : ""
            }
            <BlogTitle>
              <Link to={`${post.fields.slug}`} style={{ textDecoration: `none`, }}>
                  {title}
              </Link>
            </BlogTitle>
            {
              !post.frontmatter.hero || isMobile ? <small>{post.frontmatter.date}</small> : ''
            }
            <section>
                <p
                  style={{ overflow: "hidden" }}
                  dangerouslySetInnerHTML={{
                      __html: post.frontmatter.description || post.excerpt,
                  }}
                  itemProp="description"
                />
            </section>
          </BlogContainer>
        )
      })}
    </ol>
  )
}

export default Blogs

無事スタイルが反映された

参考: