• #Chrome拡張機能
  • #UI設計
  • #ミラーカラムズ
  • #MoneyForward
  • #デザインシステム
開発misc-devメモ

Chrome拡張のMF連携UIをタブからミラーカラムズに書き直した記録

最初に組んだタブUIがレビューで「ナビと中身を並べて見たい」と却下され、ミラーカラムズレイアウトに全面書き直しした。左にナビ、右にコンテンツの2カラム構成。MDX-Playgroundのデザインシステム(/design-principles/a/1)を開いて構造を揃え、機能統合やレスポンシブ対応まで含めて半日かかった。


タブUIが却下されるまで

MF(MoneyForward)連携の拡張には、スプレッドシートへのインポート、明細の一括取得、事業者設定の3つの機能がある。最初はブラウザ拡張でよくあるタブ切替UIで組んだ。「インポート」「明細取得」「設定」のタブが横に並ぶ構成。

ユーザーから「タブだと今どの機能にいるか見失う。左にナビを常時出して、右で操作したい」と指摘が入った。確かに、タブを切り替えると前の画面が消える。設定を参照しながらインポートを操作するには毎回行ったり来たりになる。

ミラーカラムズレイアウト――macOSのFinderでいうカラム表示のような、左ナビ+右コンテンツの2カラム構造に切り替えることにした。


デザインシステムを参照して構造を揃える

MDX-Playgroundにはデザインガイドライン(/design-principles/a/1)がある。ここに定義されたカラー、スペーシング、コンポーネント粒度を踏襲して、拡張のUIにも同じトーンを持ち込んだ。

ポイントは3つ:

  • ナビ項目のハイライト: 選択中のパネルをアクティブ表示し、現在地を視覚的に示す
  • コンテンツ高さの固定: パネルを切り替えてもコンテナのリサイズが起きない。min-height を固定して、レイアウトシフトを殺した
  • 900px以上で2列表示: ポップアップ幅が狭いときは縦積み、広ければ左右分割。@media (min-width: 900px) で切り替え
.miller-columns {
  display: flex;
  flex-direction: column;
}
@media (min-width: 900px) {
  .miller-columns {
    flex-direction: row;
  }
  .miller-nav { width: 200px; flex-shrink: 0; }
  .miller-content { flex: 1; min-height: 480px; }
}

コンテンツ高さを固定したのは、パネル切替のたびにポップアップがビクビク伸縮するのを止めるため。480pxで固定して、中身がはみ出す場合は overflow-y: auto でスクロールさせた。


機能統合: 3画面を2パネルに減らす

タブUI時代は「インポート」「明細取得」「設定」の3画面だった。書き直しにあたって機能を整理した。

エクスポート+インポートを1パネルに統合

MF_スプレッドシートインポートとMF_明細全取得は、操作の起点が同じスプレッドシートボタン(SSボタン)だった。別画面に分ける必然性がなかったので、1つの「エクスポート/インポート」パネルに統合した。SSボタンを押すとスプレッドシートへの書き出しが走り、同じパネル内でインポート状態も確認できる。

サービス選択チェックボックスのチップ表示

MFの事業者(サービス)を選ぶチェックボックスは、対象が5件以下の場合がほとんどだった。デザインシステムの「5件以下は展開表示」ルールに従い、ドロップダウンではなくチップとして全件並べた。選択状態が一目で見える。

<div class="service-chips">
  <label v-for="svc in services" :key="svc.id" class="chip">
    <input type="checkbox" v-model="selectedServices" :value="svc.id" />
    {{ svc.name }}
  </label>
</div>

設定は別パネル

事業者一覧テーブルは情報量が多い。全事業者 x 全年度のCTI(Content Type Identifier)とスプレッドシートURLを管理する画面なので、独立した「設定」パネルに残した。ここだけで結構な面積を使う。


スプレッドシートURL入力の自動保存

設定パネルの事業者テーブルでは、各事業者にスプレッドシートURLを紐づける。当初は全行を編集して「一括保存」ボタンを押す設計だった。

実際に触ると、1行変えるたびに下までスクロールして保存ボタンを押す操作がだるい。入力欄の blur イベントで即保存に切り替えた。一括保存ボタンは削除。

URLタイトルの自動取得

スプレッドシートURLを入力すると、validateSpreadsheet APIを叩いてスプレッドシートのタイトルを自動取得する。URLの正当性チェックとタイトル取得を兼ねていて、無効なURLならエラーが返る。取得したタイトルはURL入力欄の下に表示して、正しいシートを指定できているか確認できるようにした。

const onUrlBlur = async (bizId, url) => {
  if (!url) return
  const { title, error } = await validateSpreadsheet(url)
  if (error) {
    showError(bizId, error)
    return
  }
  await saveUrl(bizId, url)
  showTitle(bizId, title)
}

OAuthクライアントIDの差し替え

前日の作業(Chrome拡張にSheets API連携を追加した記録)で設定したOAuthクライアントIDが、翌日になって bad client id エラーを返し始めた。Google Cloud Consoleで確認すると、古いクライアントIDが残ったまま新しいIDも並存している状態だった。

manifest.jsonのoauth2セクションで古いIDを新しいIDに差し替え、chrome.identity.clearAllCachedAuthTokens() でトークンキャッシュを掃除して解決。IDが2つ存在していたこと自体は問題ないが、manifest.jsonが古い方を参照していたのが原因だった。


マージとプッシュ

drive_-review ブランチで作業していた全変更を master にマージしてプッシュした。コンフリクトなし。ブランチ名にハイフンが2つ入っているのは命名ミスだが、作業ブランチなのでそのまま通した。


振り返り

タブUIからミラーカラムズへの書き直しは、コードの半分以上を捨てることになった。ただ、既存のデザインシステムを参照しながら組んだおかげで、UIコンポーネントの設計判断で迷う場面は少なかった。「5件以下は展開表示」「コンテンツ高さ固定」のようなルールが先にあると、実装中に手が止まらない。

一括保存を即時保存に変えたのは、自分で5回触って「毎回スクロールするのが面倒」と気づいたから。UIの善し悪しは、実際に繰り返し操作してみないと見えない。