🤖

M4Gミニ四駆ギャラリー:AI活用でトースト通知を改善した話

に公開

はじめに

M4Gミニ四駆ギャラリーのユーザー体験改善の一環として、シンプルなフラッシュメッセージをリッチなトースト通知に改善しました。今回はClaude Codeを活用して効率的に実装できたので、その過程を紹介します。

改善前の課題

Before: シンプルなフラッシュメッセージ

改善前のフラッシュメッセージ

改善前は以下のような課題がありました:

  • 手動で閉じるまで画面に残り続ける
  • 視覚的なインパクトが弱い
  • アニメーションがなく、ユーザビリティが低い
  • すべてのflashタイプを処理していて制御が難しい
<% flash.each do |key, message| %>
  <% next if message.blank? %>
  <% css_class = case key.to_sym
    when :notice
      'bg-green-100 border border-green-400 text-green-700'
    when :alert
      'bg-red-100 border border-red-400 text-red-700'
    else
      'bg-gray-100 border border-gray-400 text-gray-700'
    end
  %>
  <div class="<%= css_class %> px-4 py-3 m-4 rounded relative" role="alert">
    <span class="block sm:inline"><%= message %></span>
  </div>
<% end %>

AI活用による改善プロセス

Issue作成とプランニング

まずGitHub Issue #86で要件を整理:

機能要件

  • 5秒後の自動非表示
  • noticealertのみ対象
  • 手動クローズボタン
  • フェードアニメーション

技術要件

  • Tailwind CSS + Stimulus
  • Flowbiteデザインシステム採用
  • アクセシビリティ対応

Claude Codeでの実装

Claude Codeを使って以下のファイルを実装:

  1. Stimulusコントローラー (app/javascript/controllers/toast_controller.js)
  2. ERBテンプレート (app/views/application/_flash_messages.html.erb)

実装詳細

Stimulusコントローラー

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static values = { 
    duration: { type: Number, default: 5000 },
    type: String 
  }

  connect() {
    this.show()
    this.setupAutoHide()
  }

  show() {
    // フェードインアニメーション
    this.element.style.opacity = "0"
    this.element.style.transform = "translateY(-100%)"
    
    requestAnimationFrame(() => {
      this.element.style.transition = "all 0.3s ease-in-out"
      this.element.style.opacity = "1"
      this.element.style.transform = "translateY(0)"
    })
  }

  hide() {
    // フェードアウトアニメーション
    this.element.style.transition = "all 0.3s ease-in-out"
    this.element.style.opacity = "0"
    this.element.style.transform = "translateY(-100%)"
    
    setTimeout(() => {
      if (this.element.parentNode) {
        this.element.remove()
      }
    }, 300)
  }

  close() {
    this.clearAutoHideTimer()
    this.hide()
  }

  setupAutoHide() {
    this.autoHideTimer = setTimeout(() => {
      this.hide()
    }, this.durationValue)
  }

  // キーボードアクセシビリティ: Escapeキーで閉じる
  keydown(event) {
    if (event.key === "Escape") {
      this.close()
    }
  }
}

ERBテンプレート

<!-- Toast通知コンテナ(右上固定位置) -->
<div class="fixed top-4 right-4 z-50 space-y-2">
  <% [:notice, :alert].each do |type| %>
    <% message = flash[type] %>
    <% next if message.blank? %>
    
    <div data-controller="toast" 
         data-toast-type-value="<%= type %>"
         data-toast-duration-value="5000"
         class="flex items-center w-full max-w-xs p-4 text-gray-500 bg-white rounded-lg shadow-lg border"
         role="alert"
         aria-live="polite"
         tabindex="0"
         data-action="keydown->toast#keydown">
      
      <!-- アイコン部分 -->
      <div class="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 rounded-lg <%= type == :notice ? 'text-green-500 bg-green-100' : 'text-red-500 bg-red-100' %>">
        <% if type == :notice %>
          <%= inline_svg_tag "icons/circle-check-filled.svg", class: "w-5 h-5", "aria-hidden": true %>
        <% else %>
          <%= inline_svg_tag "icons/alert-circle-filled.svg", class: "w-5 h-5", "aria-hidden": true %>
        <% end %>
      </div>
      
      <!-- メッセージテキスト -->
      <div class="ms-3 text-sm font-normal text-gray-900"><%= message %></div>
      
      <!-- 閉じるボタン -->
      <button type="button" 
              class="ms-auto -mx-1.5 -my-1.5 bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-2 focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex items-center justify-center h-8 w-8 transition-colors duration-200"
              data-action="click->toast#close"
              aria-label="通知を閉じる">
        <span class="sr-only">閉じる</span>
        <%= inline_svg_tag "icons/x.svg", class: "w-3 h-3", "aria-hidden": true %>
      </button>
    </div>
  <% end %>
</div>

改善結果

After: リッチなトースト通知

改善後のトースト通知

実現した機能

5秒自動非表示 - ユーザーの操作を妨げない
手動クローズ - 必要に応じて即座に閉じられる
滑らかなアニメーション - フェードイン・フェードアウト
キーボードアクセシビリティ - Escapeキーで閉じる
ARIA属性対応 - スクリーンリーダー対応
レスポンシブ - モバイル・デスクトップ両対応
右上固定位置 - 他の要素を邪魔しない

技術的な改善点

  • 対象の限定: flash.eachから[:notice, :alert].eachに変更
  • アニメーション: CSS transitionsによる滑らかな動作
  • 状態管理: Stimulusによる適切なライフサイクル管理
  • アクセシビリティ: ARIA属性とキーボード操作対応

AIによる開発効率化

Claude Codeの活用メリット

  1. 要件整理の支援: Issue作成時の要件定義
  2. 設計提案: Flowbiteデザインシステムの提案
  3. コード生成: StimulusコントローラーとERBテンプレートの実装
  4. テストプラン: アクセシビリティを含む包括的なテスト項目

開発時間の短縮

  • 従来の手動実装: 半日〜1日
  • AI活用: 1-2時間で完了
  • コードレビューやテストに時間を割けるようになった

まとめ

シンプルなフラッシュメッセージをリッチなトースト通知に改善することで、ユーザー体験が大幅に向上しました。Claude Codeを活用することで、実装時間を大幅に短縮しつつ、アクセシビリティや保守性も考慮した質の高いコードを素早く実装できました。

小さな改善の積み重ねが、全体的なサービス品質向上に繋がることを実感した取り組みでした。

デザインはなんか微妙な気もしますが、機能実現してある程度整ったらデザインも直して行こうと思います!
直近はmongodbからpostgresqlに移行したいので、管理画面などを進めています!

M4G( https://www.mini4wg.com/ )ではvibe coadingでサイトを作り上げていきます。
よければフォローしてください!

参考リンク

Discussion