カートに入れました

レスポンシブデザインで画面を広げた時に右側にサブトピックをつける

こんな感じで画面サイズを変えたら記事の配置を変えたい

修正前は最新記事が2等分になって表示するようになっている。タブレットサイズ以上はここの右側に新しいおすすめ記事項目を作りたい。

このサイトのテンプレートは gatsby-starter-netlify-cms でcssのフレームワークにBulmaが使われている。

Bulmaの勉強も兼ねてデザインを整えていく

インストールするパッケージ

react-responsiveというパッケージを入れる

yarn add react-responsive

const isMobile = useMediaQuery({query: "(min-device-width: 350px)",})

こんな感じで場合分けできるようになる

修正前コード

src/templates/index-page.js
import React, { useState, useEffect } from "react";
import PropTypes from "prop-types";
import { Link, graphql } from "gatsby";
import { getImage } from "gatsby-plugin-image";

import Layout from "../components/Layout";
import FullWidthImage from "../components/FullWidthImage";
import Seo from "../components/Seo";
import BlogRoll from "../components/BlogRoll";
import useSiteMetadata from "../components/SiteMetadata";
import Products from "../components/products/Products"

// eslint-disable-next-line
export const IndexPageTemplate = ({
  image,
  title,
  heading,
  subheading,
  mainpitch,
  description,
  intro,
  blogs,
  location,
}) => {
  const heroImage = getImage(image) || image;
  const { siteUrl } = useSiteMetadata();

  const [innerWidth, setInnerWidth] = useState('')
  useEffect(() => {
    setInnerWidth(window.innerWidth)
  }, [])

  return (
    <div>
      <Seo
        url={`${siteUrl}${location.pathname}`}
        title={title}
      />
      <FullWidthImage img={heroImage} title={title} subheading={subheading} />
      <section className={`section section--gradient`}>
        <div className="container">
          <div className={innerWidth > 820 ? `section` : ``}>
            <div className="columns">
              <div className="column is-10 is-offset-1">
                <div className="content">
                    <div className="column is-12">
                      <h3 className="has-text-weight-semibold is-size-2" style={innerWidth > 820 ? {} : {paddingTop: 32}}>
                        {heading}
                      </h3>
                      <p>{description}</p>
                    </div>
                    <div className="column is-12 has-text-centered">
                      <Products />
                      <Link className="btn" to="/products">
                        See all products
                      </Link>
                    </div>
                  <hr style={{margin: 64}}/>
                  <div className="column is-12">
                    <h3 className="has-text-weight-semibold is-size-2">
                      最新記事
                    </h3>
                    <p style={{marginBottom: "1.4rem",}}>....................................</p>
                    <BlogRoll blogs={blogs}/>
                    <div className="column is-12 has-text-centered">
                      <Link className="btn" to="/blog">
                        Read more
                      </Link>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </section>
    </div>
  );
};

IndexPageTemplate.propTypes = {
  image: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
  title: PropTypes.string,
  heading: PropTypes.string,
  subheading: PropTypes.string,
  mainpitch: PropTypes.object,
  description: PropTypes.string,
  intro: PropTypes.shape({
    blurbs: PropTypes.array,
  }),
  blogs: PropTypes.array,
};

const IndexPage = ({ data, location }) => {
  const { frontmatter } = data.markdownRemark;
  const blogs = data.blogs.edges;

  return (
    <Layout>
      <IndexPageTemplate
        image={frontmatter.image}
        title={frontmatter.title}
        heading={frontmatter.heading}
        subheading={frontmatter.subheading}
        mainpitch={frontmatter.mainpitch}
        description={frontmatter.description}
        intro={frontmatter.intro}
        blogs={blogs}
        location={location}
      />
    </Layout>
  );
};

IndexPage.propTypes = {
  data: PropTypes.shape({
    markdownRemark: PropTypes.shape({
      frontmatter: PropTypes.object,
    }),
    blogs: PropTypes.shape({
      edges: PropTypes.array,
    }),
  }),
};

export default IndexPage;

export const pageQuery = graphql`
  query IndexPageTemplate {
    markdownRemark(frontmatter: { templateKey: { eq: "index-page" } }) {
      frontmatter {
        title
        image {
          childImageSharp {
            gatsbyImageData(quality: 100, layout: FULL_WIDTH)
          }
        }
        heading
        subheading
        mainpitch {
          title
          description
        }
        description
        intro {
          blurbs {
            image {
              childImageSharp {
                gatsbyImageData(width: 240, quality: 64, layout: CONSTRAINED)
              }
            }
            text
          }
        }
      }
    }
    blogs: allMarkdownRemark(
      sort: { order: DESC, fields: [frontmatter___date] }
      filter: { frontmatter: { templateKey: { eq: "blog-post" } } }
      limit: 4
    ) {
      edges {
        node {
          excerpt(pruneLength: 200)
          id
          fields {
            slug
          }
          frontmatter {
            title
            templateKey
            date(formatString: "MMMM DD, YYYY")
            featuredpost
            featuredimage {
              childImageSharp {
                gatsbyImageData(
                  width: 120
                  quality: 100
                  layout: CONSTRAINED
                )

              }
            }
          }
        }
      }
    }
  }
`

修正後

useState、useEffectをやめた。

おすすめ記事をgraphqlでrecommendという名前で取得するようにした。

列で分けたいセクションは

<div className="column is-12-mobile is-12-tablet is-9-desktop is-9-widescreen">

のようにclassをつける

BlogRollコンポーネントの引数にroleを持たせて場合分けする

src/templates/index-page.js
import React from "react";
import PropTypes from "prop-types";
import { Link, graphql } from "gatsby";
import { getImage } from "gatsby-plugin-image";

import Layout from "../components/Layout";
import FullWidthImage from "../components/FullWidthImage";
import Seo from "../components/Seo";
import BlogRoll from "../components/BlogRoll";
import useSiteMetadata from "../components/SiteMetadata";
import Products from "../components/products/Products"

// eslint-disable-next-line
export const IndexPageTemplate = ({
  image,
  title,
  heading,
  subheading,
  mainpitch,
  description,
  intro,
  blogs,
  location,
  recommend,
}) => {
  const heroImage = getImage(image) || image;
  const { siteUrl } = useSiteMetadata();

  return (
    <div>
      <Seo
        url={`${siteUrl}${location.pathname}`}
        title={title}
      />
      <FullWidthImage img={heroImage} title={title} subheading={subheading} />
      <section className={`section section--gradient`}>
        <div className="container">
          <div className="columns is-multiline">
            <div className="column is-12-mobile is-12-tablet is-9-desktop is-9-widescreen">
              <div className="content">
                <div className="column is-12">
                  <h3 className="has-text-weight-semibold is-size-2">
                    {heading}
                  </h3>
                  <p>{description}</p>
                </div>
                <div className="column is-12 has-text-centered">
                  <Products />
                  <Link className="btn" to="/products">
                    See all products
                  </Link>
                </div>
                <hr style={{margin: 64}}/>
                <div className="column is-12">
                  <h3 className="has-text-weight-semibold is-size-2">
                    最新記事
                  </h3>
                  <p style={{marginBottom: "1.4rem",}}>....................................</p>
                  <BlogRoll blogs={blogs} role={"main"}/>
                  <div className="column is-12 has-text-centered">
                    <Link className="btn" to="/blog">
                      Read more
                    </Link>
                  </div>
                </div>
              </div>
            </div>
            <div className="column is-12-mobile is-12-tablet is-3-desktop is-3-widescreen">
              <div className="content">
                <div className="column is-12">
                  <h3 className="has-text-weight-semibold is-size-10">
                    おすすめ記事
                  </h3>
                  <p style={{marginBottom: "1.4rem",}}>....................................</p>
                  <BlogRoll blogs={recommend} role={"sub"}/>
                </div>
              </div>
            </div>
          </div>
        </div>
      </section>
    </div>
  );
};

IndexPageTemplate.propTypes = {
  image: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
  title: PropTypes.string,
  heading: PropTypes.string,
  subheading: PropTypes.string,
  mainpitch: PropTypes.object,
  description: PropTypes.string,
  intro: PropTypes.shape({
    blurbs: PropTypes.array,
  }),
  blogs: PropTypes.array,
  recommend: PropTypes.array,
};

const IndexPage = ({ data, location }) => {
  const { frontmatter } = data.markdownRemark;
  const blogs = data.blogs.edges;
  const recommend = data.recommend.edges;

  return (
    <Layout>
      <IndexPageTemplate
        image={frontmatter.image}
        title={frontmatter.title}
        heading={frontmatter.heading}
        subheading={frontmatter.subheading}
        mainpitch={frontmatter.mainpitch}
        description={frontmatter.description}
        intro={frontmatter.intro}
        blogs={blogs}
        location={location}
        recommend={recommend}
      />
    </Layout>
  );
};

IndexPage.propTypes = {
  data: PropTypes.shape({
    markdownRemark: PropTypes.shape({
      frontmatter: PropTypes.object,
    }),
    blogs: PropTypes.shape({
      edges: PropTypes.array,
    }),
    recommend: PropTypes.shape({
      edges: PropTypes.array,
    }),
  }),
};

export default IndexPage;

export const pageQuery = graphql`
  query IndexPageTemplate {
    markdownRemark(frontmatter: { templateKey: { eq: "index-page" } }) {
      frontmatter {
        title
        image {
          childImageSharp {
            gatsbyImageData(quality: 100, layout: FULL_WIDTH)
          }
        }
        heading
        subheading
        mainpitch {
          title
          description
        }
        description
        intro {
          blurbs {
            image {
              childImageSharp {
                gatsbyImageData(width: 240, quality: 64, layout: CONSTRAINED)
              }
            }
            text
          }
        }
      }
    }
    blogs: allMarkdownRemark(
      sort: { order: DESC, fields: [frontmatter___date] }
      filter: { frontmatter: { templateKey: { eq: "blog-post" } } }
      limit: 4
    ) {
      edges {
        node {
          excerpt(pruneLength: 200)
          id
          fields {
            slug
          }
          frontmatter {
            title
            templateKey
            date(formatString: "MMMM DD, YYYY")
            featuredpost
            featuredimage {
              childImageSharp {
                gatsbyImageData(
                  width: 120
                  quality: 100
                  layout: CONSTRAINED
                )
              }
            }
          }
        }
      }
    }
    recommend: allMarkdownRemark(
      sort: { order: DESC, fields: [frontmatter___date] }
      filter: { frontmatter: { templateKey: { eq: "blog-post" }, featuredpost: { eq: true } } }
      limit: 3
    ) {
      edges {
        node {
          excerpt(pruneLength: 200)
          id
          fields {
            slug
          }
          frontmatter {
            title
            templateKey
            date(formatString: "MMMM DD, YYYY")
            featuredpost
            featuredimage {
              childImageSharp {
                gatsbyImageData(
                  width: 120
                  quality: 100
                  layout: CONSTRAINED
                )
              }
            }
          }
        }
      }
    }
  }
`

react-responsiveを新たにインポート

引数のroleとisLaptopで場合分けさせた

src/components/BlogRoll.js
import React from 'react'
import { Link } from 'gatsby'
import PreviewCompatibleImage from './PreviewCompatibleImage'

import { useMediaQuery } from 'react-responsive'

const btnStyles = {
  border: "none",
  backgroundColor: "rgba(0,0,0,0)",
  color: "#aaa",
  fontSize: "0.9em",
}

export const BlogRoll = ({ blogs, role }) => {
  // const isMobile = useMediaQuery({query: "(min-width: 350px)",})
  // const isTablet = useMediaQuery({query: "(min-width: 769px)",})
  const isLaptop = useMediaQuery({query: "(min-width: 1024px)",})
  // const isDesktop = useMediaQuery({query: "(min-width: 1216px)",})
  // const isBigScreen = useMediaQuery({query: "(min-width: 1408px )",})

  // ラップトップ未満の場合はメイン記事と同じように表示させる
  if (role === "sub") role = !isLaptop ? "main" : "sub"
  const classRow = role === "sub" ? "is-parent column is-12" : "is-parent column is-6"
  const classTitle = role === "sub" ? "title has-text-primary is-size-12" : "title has-text-primary is-size-4"

  return (
    <div className="columns is-multiline">
      {blogs &&
        blogs.map(({ node: post }) => (
          <div className={classRow} key={post.id}>
            <Link to={post.fields.slug}>
              {isLaptop && role === "sub" ?
                <article className={`blog-list-item tile is-child box notification`}>
                  <div>{post.frontmatter.title}<div style={btnStyles}>続きを読む →</div></div>
                </article>
                :
                <article
                  className={`blog-list-item tile is-child box notification ${
                    post.frontmatter.featuredpost ? 'is-featured' : ''
                  }`}
                >
                  <header>
                    {post.frontmatter.featuredimage ? (
                      <div className="featured-thumbnail">
                        <PreviewCompatibleImage
                          imageInfo={{
                            image: post.frontmatter.featuredimage,
                            alt: `featured image thumbnail for post ${post.frontmatter.title}`,
                            width:
                              post.frontmatter.featuredimage.childImageSharp
                                .gatsbyImageData.width,
                            height:
                              post.frontmatter.featuredimage.childImageSharp
                                .gatsbyImageData.height,
                          }}
                        />
                      </div>
                    ) : null}
                    <div className="post-meta">
                      <div className={classTitle}>
                        {post.frontmatter.title}
                      </div>
                      {/* <span> &bull; </span> */}
                      <span className="subtitle is-size-5 is-block">
                        {post.frontmatter.date}
                      </span>
                    </div>
                  </header>
                  <div>
                    {post.excerpt}
                    <br />
                    <br />
                    <div style={btnStyles}>
                      続きを読む →
                    </div>
                  </div>
                </article>
              }
            </Link>
          </div>
        ))}
    </div>
  )
}

export default BlogRoll

表示結果

  • classNameを修正したあと確かめるにはプロセスを切って再度

yarn startでプログラムを立ち上げて見ないと反映されなかった