🎉

ReactでIconとTextをカスタマイズできるButton Componentの作り方

2023/10/10に公開

こんにちは、AIQ株式会社のフロントエンドエンジニアのまさぴょんです!
今回は、ReactでIconとTextをカスタマイズできるButton Componentの作り方について解説します。

前回の記事で、ReactでJSX.ElementなどのReact ComponentをPropsとして渡す方法をご紹介しましたが、その方法を活用して、Componentを作っていきます。

https://zenn.dev/aiq_dev/articles/cfd5330d02d570

IconとTextをPropsで設定できるButton Componentを実装する

今回の要件は、次のとおり。

  1. IconとラベルTextがあるButton Componentを実装する。
  2. SVGIcon Componentは、Propsで渡して、設定できる。
  3. ボタンのラベルTextは、Propsで渡して、設定できる。

SampleCodeを実際に使用すると、次のようなButton Componentになります。

SVG Icon Component

今回は、Sampleとして、画像と動画の2つのSVG Icon Componentを作成します。

画像IconのSVG Componentは、次のとおりです。

ImageIconSvg.tsx
import { CSSProperties } from "react";

/** Propsの型定義 */
interface PropsType {
  style?: CSSProperties;
  color?: string;
}

/**
 * NOTE: ImageIconSvg
 *  => 画像 Icon の SVG
 */
const ImageIconSvg = (props: PropsType) => {
  return (
    <svg
      width="18"
      height="18"
      viewBox="0 0 18 18"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      style={props.style ? props.style : undefined}
    >
      <path
        d="M2 18C1.45 18 0.979167 17.8042 0.5875 17.4125C0.195833 17.0208 0 16.55 0 16V2C0 1.45 0.195833 0.979167 0.5875 0.5875C0.979167 0.195833 1.45 0 2 0H16C16.55 0 17.0208 0.195833 17.4125 0.5875C17.8042 0.979167 18 1.45 18 2V16C18 16.55 17.8042 17.0208 17.4125 17.4125C17.0208 17.8042 16.55 18 16 18H2ZM4 14H14C14.2 14 14.35 13.9083 14.45 13.725C14.55 13.5417 14.5333 13.3667 14.4 13.2L11.65 9.525C11.55 9.39167 11.4167 9.325 11.25 9.325C11.0833 9.325 10.95 9.39167 10.85 9.525L8.25 13L6.4 10.525C6.3 10.3917 6.16667 10.325 6 10.325C5.83333 10.325 5.7 10.3917 5.6 10.525L3.6 13.2C3.46667 13.3667 3.45 13.5417 3.55 13.725C3.65 13.9083 3.8 14 4 14Z"
        fill={props.color ? props.color : "white"}
      />
    </svg>
  );
};

export default ImageIconSvg;

動画IconのSVG Componentは、次のとおりです。

MovieIconSvg.tsx
import { CSSProperties } from "react";

/** Propsの型定義 */
interface PropsType {
  style?: CSSProperties;
  color?: string;
}

/**
 * NOTE: ReelIconSvg
 *  => 動画のIcon
 */
const MovieIconSvg = (props: PropsType) => {
  return (
    <svg
      width="20"
      height="16"
      viewBox="0 0 20 16"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      style={props.style ? props.style : undefined}
    >
      <path
        d="M2 0L4 4H7L5 0H7L9 4H12L10 0H12L14 4H17L15 0H18C18.55 0 19.0208 0.195833 19.4125 0.5875C19.8042 0.979167 20 1.45 20 2V14C20 14.55 19.8042 15.0208 19.4125 15.4125C19.0208 15.8042 18.55 16 18 16H2C1.45 16 0.979167 15.8042 0.5875 15.4125C0.195833 15.0208 0 14.55 0 14V2C0 1.45 0.195833 0.979167 0.5875 0.5875C0.979167 0.195833 1.45 0 2 0ZM2 6V14H18V6H2Z"
        fill={props.color ? props.color : "white"}
      />
    </svg>
  );
};

export default MovieIconSvg;

IconMsgButtonコンポーネント

続いて、IconとTextをPropsで設定できるButton Componentを実装します。

実装の際のポイントをまとめると、次のとおりです。

IconMsgButtonコンポーネントのポイント
  1. callbackIdと、callBackを渡すことで、親ComponentにButton Clickの結果を返却する。
  2. svgIconはJSX.Element として Component 定義したものを受け取る設定にする。
  3. labelタグと連携して Click 判定領域を拡張する
  4. IconのPositionを「左 or 右」で変えられるようにしている。
IconMsgButton.tsx
import { CSSProperties, Dispatch, SetStateAction } from "react";

/** Propsの型定義 */
interface PropsType {
  /** label タグと連携して Click 判定領域を拡張するための id */
  btnId: string;
  /** SVG_Icon は JSX.Element として Component 定義したものを想定しています */
  svgIcon: JSX.Element;
  /** ボタンの表示 Text・ラベル */
  text: string;
  /** CallBack_Func => Setterを想定しています */
  callBack: Dispatch<SetStateAction<any>>;
  /** CallBack_Func で返す id */
  callbackId: number | string;
  wrapperStyle?: CSSProperties;
  textStyle?: CSSProperties;
  btnStyle?: CSSProperties;
  /** Icon の Position は Default で左なので、右の場合は、こちらを true にする */
  isIconPositionRight?: boolean;
}

/**
 * NOTE: IconMsgButton
 * => SVG_Icon or img & Text(MSG) を表示する Btn
 */
const IconMsgButton = (props: PropsType) => {
  const {
    btnId,
    svgIcon,
    text,
    callBack,
    callbackId,
    wrapperStyle,
    btnStyle,
    textStyle,
    isIconPositionRight,
  } = props;

  /** Default の Btn Style */
  const defaultWrapperStyle = {
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    gap: "12px",
    width: "170px",
    height: "35px",
    backgroundColor: "#59B9C6",
    borderRadius: "8px",
    color: "#fff",
    fontWeight: "600",
  } as CSSProperties;

  /** Default の Btn Style */
  const defaultBtnStyle = {
    backgroundColor: "transparent",
    border: "none",
    cursor: "pointer",
    outline: "none",
    padding: "0",
    appearance: "none",
  } as CSSProperties;

  /** Default の Text Style */
  const defaultTextStyle = {
    color: "white",
    fontWeight: "bold",
  } as CSSProperties;

  return (
    <label
      htmlFor={btnId}
      style={wrapperStyle ? wrapperStyle : defaultWrapperStyle}
      onClick={() => callBack(callbackId)}
    >
      {/* 左側_Iconの場合 */}
      {!isIconPositionRight && svgIcon}
      <button id={btnId} style={btnStyle ? btnStyle : defaultBtnStyle}>
        <span style={textStyle ? textStyle : defaultTextStyle}>{text}</span>
      </button>
      {/* 右側_Iconの場合 */}
      {isIconPositionRight && svgIcon}
    </label>
  );
};

export default IconMsgButton;

IconMsgButton を実際に使ってみる

作ってみた、IconMsgButton Componentを実際に使ってみます。

実装の際のポイントをまとめると、次のとおりです。

IconMsgButtonを親Componentから呼び出す際のポイント
  1. callbackIdと、callBackを渡すことで、親ComponentでButton Clickの結果を制御する。
  2. svgIconは、JSX.Elementの形にしたデータ(Component)を渡す。
  3. Selectしていない場合のStyleなども追加して、Propsで渡します。
Test.tsx
import { useState, CSSProperties } from "react";
import IconMsgButton from "../ui-elements/button/IconMsgButton";
import ImageIconSvg from "../ui-elements/icons/ImageIconSvg";
import MovieIconSvg from "../ui-elements/icons/MovieIconSvg";

const Test = () => {
  /** Btnで、Selectした 投稿コンテンツの表示タイプ管理 State => 初期値は、フィード(PR除外) */
  const [selectContentDisplayType, setSelectContentDisplayType] =
    useState<number>(1);

  /** Selectしていない場合の iconBtn の Style */
  const noSelectIconBtnStyle = {
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    gap: "12px",
    width: "170px",
    height: "35px",
    backgroundColor: "#AFAEB3",
    borderRadius: "8px",
    color: "#fff",
    fontWeight: "600",
    cursor: "pointer",
  } as CSSProperties;

  return (
    <div>
      {/* Btn Block */}
      <div
        style={{
          display: "flex",
          gap: "8px",
        }}
      >
        <IconMsgButton
          btnId={"image-type-btn"}
          svgIcon={<ImageIconSvg />}
          text={"画像"}
          callBack={setSelectContentDisplayType}
          callbackId={1}
          wrapperStyle={
            selectContentDisplayType === 1 ? undefined : noSelectIconBtnStyle
          }
        />
        <IconMsgButton
          btnId={"movie-type-btn"}
          svgIcon={<MovieIconSvg />}
          text={"動画"}
          callBack={setSelectContentDisplayType}
          callbackId={2}
          wrapperStyle={
            selectContentDisplayType === 2 ? undefined : noSelectIconBtnStyle
          }
        />
      </div>
      <div>
        <div>Content Block</div>
      </div>
    </div>
  );
};

export default Test;

まとめ

今回は、ReactでIconとTextをカスタマイズできるButton Componentの作り方について、解説しました。

引き続き、Component職人として、いろいろなComponent開発にChallengeしていきたいと思います。

個人で、Blogもやっています、よかったら見てみてください。

https://masanyon.com/

注意事項

この記事は、AIQ 株式会社の社員による個人の見解であり、所属する組織の公式見解ではありません。

求む、冒険者!

AIQ株式会社では、一緒に働いてくれるエンジニアを絶賛、募集しております🐱🐹✨

詳しくは、Wantedly (https://www.wantedly.com/companies/aiqlab)を見てみてください。

Webエンジニア向け説明

https://www.wantedly.com/projects/1089410

データサイエンティスト向け説明

https://www.wantedly.com/projects/1089406

採用技術 (一部抜粋)

  • FrontEnd: TypeScript, JavaScript, React.js, Vue.js, Next.js, Nuxt.js など
  • BackEnd: Node.js, Express,Python など
  • その他技術: Docker, AWS, Git, GitHub など

人事に直通(?)・ご紹介Plan(リファラル採用)

私経由で、ご紹介もできますので、興味のある方や気軽にどんな会社なのか知りたい方は、X(旧:Twitter)にて、DMを送ってくれても大丈夫です。
https://twitter.com/masanyon1212

AIQ Tech Blog (有志)

Discussion