Open6

モダンjavaScript Atomic Design

どるあがどるあが

Atomic Design

画面を5段階に分け、組み合わせることでUIを実現

  1. ATOMS(原子)
    それ以上分解できない
  • ボタン
  • テキストボックス
  • アイコン など
  1. MOLECULES(分子)
    ATOMの組み合わせ
  • アイコン+メニュー名
  • プロフィール写真+テキストボックス
  • アイコンセット など
  1. ORGANISMS(有機体)
    ある程度の意味を持つ要素群
  • サイドメニュー
  • ツイート入力エリア
  1. TEMPLATES
    ページのレイアウトのみを表現。データを持たない
  • サイドメニュー
  • ツイートエリア
    などのレイアウト情報
  1. PAGES
    最終的に表示される1画面
どるあがどるあが

ATOM単位のボタンを作る

まずはBaseとなるスタイルを作ろう

BaseButton.jsx
import styled from "styled-components";

export const BaseButton = styled.button`
  color: #fff;
  padding: 6px 24px;
  border: none;
  border-radius: 1000px;
  outline: none;
  &:hover {
    cursor: pointer;
    opacity: 0.8;
  }
`;

styled-componentsを使用してSButtonを定義する

Button.jsx before
const SButton = styled.button`
  background-color: #11999e;
  color: #fff;
  padding: 6px 24px;
  border: none;
  border-radius: 1000px;
  outline: none;
  &:hover {
    cursor: pointer;
    opacity: 0.8;
  }
`;

これをBaseButtonを使用して書き換える
styled.buttonとしていたところを、styled()とカッコで書き換える

Button.jsx after
import { BaseButton } from "./BaseButton";
const SButton = styled(BaseButton)`
  background-color: #40514e;
`;

スタイルの書き換えや追加は``の中に追記する。

childrenをpropsから取得することで、普通のボタンのように使えるようになる

PrimaryButton.jsx
import styled from "styled-components";
import { BaseButton } from "./BaseButton";

export const PrimaryButton = (props) => {
  const { children } = props;
  return <SButton>{children}</SButton>;
};

const SButton = styled(BaseButton)`
  background-color: #40514e;
`;

App.js
return (
    <div className="App">
      <PrimaryButton>ぼたん</PrimaryButton>
      <PrimaryButton>検索</PrimaryButton>
      <SecondaryButton>yaha-</SecondaryButton>
    </div>
  );

どるあがどるあが

MOLECULE単位の検索ボックスとボタンのセットを作る

以下のように部品単位でフォルダを作っていく
非常にわかりやすい

新たにATOM単位でInputを作り、MOLECULE単位でInputとButtonを合わせて検索ボックスを作る
非常に作りやすい

Input.jsx
import styled from "styled-components";

export const Input = (props) => {
  // デフォルトを指定できる
  const { placeholder = "" } = props;
  return <SInput type="text" placeholder={placeholder} />;
};

const SInput = styled.input`
  padding: 8px 16px;
  border: solid #ddd 1px;
  border-radius: 1000px;
  outline: none;
`;

ビジュアルで考えたものをそのままプログラムに落とし込みやすい

SearchInput.jsx
import styled from "styled-components";

import { PrimaryButton } from "../atoms/button/PrimaryButton";
import { Input } from "../atoms/input/Input";

export const SearchInput = () => {
  return (
    <SContainer>
      <Input placeholder="検索条件を入力" />
      <SButtonWrapper>
        <PrimaryButton>検索</PrimaryButton>
      </SButtonWrapper>
    </SContainer>
  );
};

const SButtonWrapper = styled.div`
  padding-left: 8px;
`

const SContainer = styled.div`
  display: flex;
  align-items: center;
`
どるあがどるあが

ORGANISMS

以下のようなコンポーネントを作る

オブジェクト指向みたいな作り方だけど、より一層ビジュアルとプログラムが近い

UserCard.jsx
import styled from "styled-components";
import { Card } from "../../atoms/card/Card";
import { UserIconWithName } from "../../molecules/user/UserIconWithName";

export const UserCard = (props) => {
  const { user } = props;
  return (
    <Card>
      <UserIconWithName image={user.image} name={user.name} />
      <SDl>
        <dt>メール</dt>
        <dd>{user.email}</dd>
        <dt>TEL</dt>
        <dd>{user.phone}</dd>
        <dt>会社名</dt>
        <dd>{user.company.name}</dd>
        <dt>WEB</dt>
        <dd>{user.website}</dd>
      </SDl>
    </Card>
  );
};

const SDl = styled.dl`
  text-align: left;
  margin-bottom: 0px;
  dt {
    float: left;
  }
  dd {
    padding-left: 32px;
    padding-bottom: 8px;
    /* 画面が小さくなったら文字を折り返して全部を表示する */
    overflow-wrap: break-word;
  }
`;

Card.jsx
import styled from "styled-components";

// いろいろなところに使うため、大きさの概念を入れない
// レイアウトの責務は呼ぶ側にある
export const Card = (props) => {
  const { children } = props;
  return <SCard>{children}</SCard>;
};

const SCard = styled.div`
  background-color: #fff;
  /* 色 x軸の距離 y軸の距離 ぼかし どれだけ広げるか*/
  box-shadow: #ddd 0px 0px 4px 2px;
  border-radius: 8px;
  padding: 16px;
`;
UserIconWithName.jsx
import styled from "styled-components";

// いろいろなところに使うため、大きさの概念を入れない
// レイアウトの責務は呼ぶ側にある
export const Card = (props) => {
  const { children } = props;
  return <SCard>{children}</SCard>;
};

const SCard = styled.div`
  background-color: #fff;
  /* 色 x軸の距離 y軸の距離 ぼかし どれだけ広げるか*/
  box-shadow: #ddd 0px 0px 4px 2px;
  border-radius: 8px;
  padding: 16px;
`;

そのまま想像したとおりに書くことができるので、この考え方をマスターするとWEBページ作るの簡単にできるようになるなと思う

どるあがどるあが

TEMPLATES

HeaderとFooterを配置するテンプレートを作る

DefaultLayoutタグで囲むことでHeaderとFooterが自動的に配置される

DefaultLayout.jsx
import { Footer } from "../atoms/layout/Footer";
import { Header } from "../atoms/layout/Header";

export const DefaultLayout = (props) => {
  const { children } = props;
  return (
    <>
      <Header />
      {children}
      <Footer />
    </>
  );
};
Header
import styled from "styled-components";
import { Link } from "react-router-dom";

export const Header = () => {
  return (
    <SHeader>
      <SLink to="/">HOME</SLink>
      <SLink to="/users">USERS</SLink>
    </SHeader>
  );
};

const SHeader = styled.header`
  background-color: #11999e;
  color: #fff;
  text-align: center;
  padding: 8px;
`;
// 既存のスタイルの拡張は()で囲む
const SLink = styled(Link)`
  margin: 0 8px;
`;
Footer.jsx
import styled from "styled-components";
import { Link } from "react-router-dom";

export const Footer = () => {
  //  &copy;でコピーライトをつける
  return <SFooter>&copy; 2022 test Ink.</SFooter>;
};

const SFooter = styled.header`
  background-color: #11999e;
  color: #fff;
  text-align: center;
  padding: 8px;
  position: fixed;
  bottom: 0;
  width: 100%;
`;

Layoutを最初から作るよりは、後で共通部分として切り抜いた方がいいかもしれない

App.js
  return (
    // router機能を使う場合は、全体をBrowerRouterで囲う必要がある
    <BrowserRouter>
      <DefaultLayout>
        {/* childrenとしてDefaultLayoutに渡される */}
        <PrimaryButton>ぼたん</PrimaryButton>
        <SecondaryButton>yaha-</SecondaryButton>
        <br />
        <SearchInput />
        <UserCard user={user} />
      </DefaultLayout>
    </BrowserRouter>
  );
どるあがどるあが

PAGES

最後にページを作っていく
途中で自動import効かなくなったな、どうしたのか

ページのRouteによる分岐を設定

Router.jsx
import { BrowserRouter, Route } from "react-router-dom";
import { Top } from "../components/pages/Top";
import { Users } from "../components/pages/Users";
import { DefaultLayout } from "../components/templates/DefaultLayout";
import { HeaderOnly } from "../components/templates/HeaderOnly";

export const Router = () => {
  return (
    // BrowserRouterの中でページ遷移を行う
    <BrowserRouter>
      {/* switchとか必要?とか思ってたら最新バージョンでは変わってるようだ */}
      <switch>
        <Route exact path="/">
          {/* DefaultLayoutで囲む */}
          <DefaultLayout>
            {/* TOPページの取得 */}
            <Top />
          </DefaultLayout>
        </Route>
        <Route path="/users">
          {/* HeaderOnlyで囲む */}
          <HeaderOnly>
            {/* Usersページの取得 */}
            <Users />
          </HeaderOnly>
        </Route>
      </switch>
    </BrowserRouter>
  );
};

Usersの中で、UserCardコンポーネントを作成し配置する

Users.jsx
import styled from "styled-components";
import { SearchInput } from "../molecules/SearchInput";
import { UserCard } from "../organisms/user/UserCard";

// 同じデータを10個作成する
const users = [...Array(10).keys()].map((val) => {
  return {
    id: val,
    name: `sakasaka${val}`,
    //https://unsplash.com/photos/WX4i1Jq_o0Y を
    // 以下のように修正photosを消してsourceを追加
    image: "https://source.unsplash.com/WX4i1Jq_o0Y",
    email: "test@gmail.com",
    phone: "090-0000-0000",
    company: {
      name: "test会社"
    },
    website: "https://google.com"
  };
});

export const Users = () => {
  //  &copy;でコピーライトをつける
  return (
    <SContainer>
      <h2>ユーザー一覧</h2>
      <SearchInput />
      <SUserArea>
        {users.map((user) => (
          <UserCard key={user.id} user={user} />
        ))}
      </SUserArea>
    </SContainer>
  );
};

const SContainer = styled.div`
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 24px;
`;

const SUserArea = styled.div`
  padding-top: 40px;
  width: 100%;
  /* レスポンシブにグリッド表記することができる */
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  grid-gap: 20px;
`;

Topページは基本の簡単なページ
これを基に他のページを作ることが出来るだろう

Top.jsx
import styled from "styled-components";

export const Top = () => {
  return (
    <SContainer>
      <h2>TOPページです</h2>
    </SContainer>
  );
};

const SContainer = styled.div`
  text-align: center;
`;