😇

ReactNative: Tamagui UIライブラリ

に公開

はじめに

バックエンド・フロントエンド開発を普段やっていますがスマホアプリもたまに作ってます。
4年前くらいにReact Native Paperを使っていて何か良いものないかなぁと探していたらTamaguiを見つけて今回使ってみました。

基本的に一般的なUIライブラリと同じですが異なるところをメインにしてまとめます。

セットアップ

https://tamagui.dev/ui/intro

こちらに書いているものに従えばすぐ使えるようになります。
他のUIライブラリと同じようにProviderを使ってラップしてあげると利用できる状態になります。

Theming

いくつかThemingについてはやり方はあるため紹介

①生成してcreateTamagui(拡張する)

公式より以下のようにThemeを設定・エクスポートすることができます
https://tamagui.dev/theme

そのさきにthemeの設定コードもあるためそちらを参照するだけで利用可能になります!
結構すぐなので便利

②theme-builderを利用してコード管理する

https://tamagui.dev/docs/guides/theme-builder

コードでグローバルトークン・セマンティックトークン(ここはtamaguiに基本的に従う必要が一部ある)を管理してCLIよりthemeファイルを生成して設定に反映させるやり方です。

テーマ修正から反映の流れはこのようになります

  1. themes-in.ts(テーマファイル)を修正する
  2. コマンドよりフォーマットしてファイル出力する()
    • npx @tamagui/cli generate-themes ./themes/themes-in.ts ./themes/themes-out.ts
  3. リロードして反映する
    • tamagui.config.tsより反映される

今回自分はtheme-builderを利用しており、実装は以下の形式です。
addChildThemesといった形で今回primaryを指定しているのですが、themeとしては以下のようなテーマが生成されます。このおかげで実装側でもdark_primaryを指定したコンポーネントを作成できますし、階層構造でdark or lightテーマの中にprimaryテーマを利用するとdark_primaryとlighth_primaryが親のテーマ設定によって切り替わったりします。

  • dark
  • light
  • primary
  • dark_primary
  • light_primary
themes-in
import { createThemeBuilder } from "@tamagui/theme-builder";

const BLACK = "#222222";
const WHITE = "#EDEDED";

const palettes = {
  light: [
    "#EDEDED", // main
    "#d4d4d4",
    "#bbbbbb",
    "#a2a2a2",
    "#888888",
    "#6e6e6e",
    "#555555",
    "#3b3b3b",
    "#222222",
    "#d4d4d4BB", // opacity
    "#d4d4d477",
    "#d4d4d444",
    "#d4d4d422",
    "#6276E4", // brand
    "#6276E4BB", // opacity
    "#6276E477",
    "#6276E444",
    "#6276E422",
  ],
  dark: [
    "#EDEDED",
    "#d4d4d4",
    "#bbbbbb",
    "#a2a2a2",
    "#888888",
    "#6e6e6e",
    "#555555",
    "#3b3b3b",
    "#222222", // main
    "#3b3b3bBB", // opacity
    "#3b3b3b77",
    "#3b3b3b44",
    "#3b3b3b22",
    "#6276E4", // brand
    "#6276E4BB", // opacity
    "#6276E477",
    "#6276E444",
    "#6276E422",
  ],
  primary: [
    "#B4C1D4",
    "#A2B4D3",
    "#8FA7D2",
    "#7C99D1",
    "#6276E4",
    "#536BD1",
    "#4A61C7",
    "#3F56B6",
    "#364FAF",
    "#2E47A5",
    "#273E9B",
    "#1E358F",
    "#6276E4",
    "#6276E4BB", // opacity
    "#6276E477",
    "#6276E444",
    "#6276E422",
    WHITE,
    BLACK,
  ],
};

const templates = {
  light_template: {
    background: 0,
    backgroundHover: 9,
    backgroundPress: 10,
    backgroundFocus: 9,
    backgroundStrong: 1,
    backgroundTransparent: 12,
    color: 8,
    colorHover: 7,
    colorPress: 6,
    colorFocus: 7,
    colorTransparent: 9,
    borderColor: 1,
    borderColorHover: 2,
    borderColorFocus: 2,
    borderColorPress: 2,
    placeholderColor: 2,
    outlineColor: 3,
    brandBackground: 14,
    brandColor: 13,
  },
  dark_template: {
    background: 8,
    backgroundHover: 9,
    backgroundPress: 10,
    backgroundFocus: 9,
    backgroundStrong: 7,
    backgroundTransparent: 12,
    color: 0,
    colorHover: 1,
    colorPress: 2,
    colorFocus: 1,
    colorTransparent: 9,
    borderColor: 4,
    borderColorHover: 5,
    borderColorFocus: 5,
    borderColorPress: 6,
    placeholderColor: 3,
    outlineColor: 4,
    brandBackground: 14,
    brandColor: 13,
  },
  custom: {
    background: 12,
    backgroundHover: 13,
    backgroundPress: 4,
    backgroundFocus: 13,
    backgroundStrong: 6,
    backgroundTransparent: 15,
    color: 17,
    colorHover: 0,
    colorPress: 0,
    colorFocus: 0,
    colorTransparent: 0,
    borderColor: 0,
    borderColorHover: 0,
    borderColorFocus: 0,
    borderColorPress: 0,
    placeholderColor: 1,
    outlineColor: 0,
    brandBackground: 5,
    brandColor: 12,
  },
};

export const themes = createThemeBuilder()
  .addPalettes(palettes)
  .addTemplates(templates)
  .addThemes({
    dark: {
      palette: "dark",
      template: "dark_template",
    },
    light: {
      palette: "light",
      template: "light_template",
    },
  })
  .addChildThemes({
    primary: {
      palette: "primary",
      template: "custom",
    },
  })
  .build();

tamagui.config.ts
import { defaultConfig } from "@tamagui/config/v4";
import { createTamagui } from "@tamagui/core";
import { themes } from "@/themes/themes-out";

export const config = createTamagui({
  ...defaultConfig,
  themes,
});

エラーについて

以下のように基本的にバージョンは揃いますが、ここが不揃いだとエラーが吐かれます。
実装開始して途中でlucide-iconsをインストールすると1.126.2だったものと1.126.4が混在してエラーになりました。
チェック用のコマンドもあるのでもし発生しても大丈夫かと思いますが、ちょっとびっくりするので共有です。

package.json
"@tamagui/config": "^1.126.4",
"@tamagui/lucide-icons": "^1.126.4",
"tamagui": "^1.126.4",

さいごに

Tamaguiはコンポーネントもまあある程度揃っておりThemeの設定が結構自分好みでしたのでもう少し使ってみようと思います。
ちなみに今は以下の技術スタックで開発しております。アプリ開発はスマホで動いてわかりやすいので楽しいですね。ある程度画面もできてきているのでその辺りも今後記事にまとめたいと思います。
はやくSDK53 expo-maps使いたい..

  • ReactNative
  • Expo
  • Tamagui
  • ReactHookForm
  • Zod
  • Clerk
  • AppWrite
  • ...その他

Discussion