黄昏の迷宮

知的好奇心を刺激する冒険

【React】コンポーネントにアイコンを渡す

React + Chakra UIでちょっとしたサイトを作っています。

Chakra UIに慣れようということでまず404ページを作成しました。

ホームに戻るアイコン部分は当然コンポーネント化したのですが、このままではホームに戻る専用なのでもう少し汎用的に使えるようにしたいと思いました。

import { Center, Icon, Text, VStack } from "@chakra-ui/react";
import { RiHomeLine } from "react-icons/ri";
import { Link } from "wouter";

function LinkIconHome() {
 return (
  <Link href="/" style={{ textDecoration: "none" }}>
   <Center>
    <VStack>
     <Icon>
      <RiHomeLine size="32" />
     </Icon>
     <Text fontSize="sm">Home</Text>
    </VStack>
   </Center>
  </Link>
 );
}

export default LinkIconHome;

ここでちょっと悩んだのがアイコンってどうやって渡せばいいんだろうということでした。

PropsでIconTypeを渡せばいけるのではないかと思ったりもしましたが、IconコンポーネントはReactNodeを受け取るので、IconTypeを渡すとエラーになってしまいます。

そこで、Iconコンポーネントの子要素としてアイコンを渡すことにしました。

これで汎用的に使えるようになりました。

import { Center, Icon, Text, VStack } from "@chakra-ui/react";
import type { ReactNode } from "react";
import { Link } from "wouter";

interface Props {
 path: string;
 label: string;
 size?: string;
 children: ReactNode;
}

function LinkIcon(props: Props) {
 return (
  <Link href={props.path} style={{ textDecoration: "none" }}>
   <Center>
    <VStack>
     <Icon>{props.children}</Icon>
     <Text fontSize={props.size ?? "sm"}>{props.label}</Text>
    </VStack>
   </Center>
  </Link>
 );
}

export default LinkIcon;

呼び出す側(抜粋)はこんな感じです。

import { RiHomeLine } from "react-icons/ri";
import LinkIcon from "@/components/LinkIcon";

<LinkIcon path="/" label="Home">
  <RiHomeLine size="32" />
</LinkIcon>

解決すれば簡単なことでしたが、Chidrenを使うことはなかなか思いつきませんでした。 まあ、こうした悩んだ時間は案外楽しいものですよね。

JSONから共通コンポーネントを作る

先日作ったxlsx2jsonを使って、まずは試しに都道府県の一覧をExcelのワークシートからJSONに変換してみました。

[
  {
    "code": "01",
    "name": "北海道"
  },
  {
    "code": "02",
    "name": "青森県"
  },
]

これをどうやってSvelteKitで使うかというと、まずはFetchしてからコンポーネントに渡す必要があります。以下のように書きます。

データを配置する

まず、src/data/shared/pref.jsonに上記のJSONを配置します。

データを読み込む

型定義して、データを読み込むためのコードを書きます。以下のようにsrc/lib/shared/pref.tsを作成します。

import rawJson from "@data/shared/pref.json";

export type Pref = {
 code: string;
 name: string;
};

export const prefs = rawJson as Pref[];

コンポーネントで使う

都道府県選択のドロップダウンを表示するコンポーネントを作成します。 以下のようにsrc/components/shared/PrefSelect.svelteを作成します。

ちなみにclassに定義されているのは、BeerCSSのスタイルを適用するためのものです。

<script lang="ts">
  import { prefs } from "@lib/shared/pref";
</script>

<div class="field label suffix border">
  <select id="select-warrior">
    {#each prefs as pref}
      <option value={pref.code}>{pref.name}</option>
    {/each}
  </select>
  <label for="select-warrior">Pref</label>
  <i>arrow_drop_down</i>
</div>

ページで使う

最後に、ページでこのコンポーネントを使います。以下のようにsrc/routes/+page.svelteを作成します。

<script lang="ts">
  import PrefSelect from "@components/shared/PrefSelect.svelte";
</script>

<h1>Welcome to SvelteKit</h1>
<p>
  Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the
  documentation
</p>

<PrefSelect />

ブラウザで見るとこんな感じに表示されます。

画面表示イメージ

共通コンポーネントはこんな感じでJSONとセットで使うと便利ですね。

pecoで便利に

昨今の「AI」というキーワードに惑わされて試してみようかなとVoidというエディターを使ってみることにしました。

普段使っているテキストエディターはVS Codeです。

VS Codeを使っているプロジェクトはghqで1つのディレクトリにまとめてpecoで移動するようにしているのですが、その中にVoidで編集しているプロジェクトを混ぜてしまうと使うエディターを間違ってしまうリスクが当然高くなります。

ということで指定したディレクトリー配下のサブディレクトリーのリストを表示するコマンドをGoで作ってみました。

package main

import (
 "fmt"
 "os"
 "path/filepath"
)

func main() {
 if len(os.Args) != 2 {
  return
 }
 baseDir := os.Args[1]
 entries, err := os.ReadDir(baseDir)
 if err != nil {
  return
 }
 for _, entry := range entries {
  if !entry.IsDir() {
   continue
  }
  dirPath := filepath.Join(baseDir, entry.Name())
  gitDir := filepath.Join(dirPath, ".git")
  info, err := os.Stat(gitDir)
  if err != nil || !info.IsDir() {
   continue
  }
  fmt.Println(dirPath)
 }
}

🔗GitHub: hyperdb/listGitDir

やっていることは単純で指定したディレクトリーの配下のサブディレクトリーのうち.gitディレクトリーが存在するものだけをリストアップするものです。

ビルドしてpecoで表示するまでを試してみましたが、とくに問題もなく使うことができました。こういう小さなツールを自作すると作業が快適になりますね。

ただ、pecoみたいな便利なツールに慣れてしまうといちいちcdコマンドで移動するとか面倒になってしまうので一概に良いこととは言えないかもしれませんが・・・。