mcpField
U
ui-patterns

ui-patterns

github.com/lubusinデザイン・UI69108 回インストール12 週間前に更新
リポジトリ

ui-patterns について

Frappe アプリの画面を設計する開発者向けのスキルです。公式 Frappe アプリのパターンをもとに、レイアウト、状態、コンポーネント、レスポンシブ対応を扱います。

主な機能

  1. 01アプリシェルとナビゲーションのパターン
  2. 02リスト、カンバン、詳細ビュー
  3. 03フォームとアクティビティフィード
  4. 04空状態、読み込み、ステータスの規約
  5. 05レスポンシブデザインの指針

活用シーン

  1. 01新しい Frappe アプリの画面設計
  2. 02Frappe UI による CRUD 画面の構築
  3. 03CRM や Helpdesk の操作パターンの適用

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/lubusin/frappe-skills --skill ui-patterns

インストール後、Claude Code で/ui-patternsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
app-shell-patterns.md
component-patterns.md
mobile-patterns.md

詳細

Frappe の UI パターン

公式 Frappe アプリから抽出した UI/UX パターンとデザインガイドラインです。

使用する場面

  • 新しい Frappe アプリの UI を設計するとき
  • Frappe UI で CRUD インターフェースを構築するとき
  • リストビュー、詳細パネル、フォームを実装するとき
  • CRM、Helpdesk、HRMS と一貫した UX を実現するとき
  • コンポーネントのパターンやレイアウトを選ぶとき

必要な入力

  • アプリの種類(CRM に似たもの、Helpdesk に似たもの、データ管理など)
  • 主要なエンティティとその関係
  • 主なユーザーワークフロー

参考アプリ

パターンを学ぶため、次の公式アプリを確認してください。

AppRepoKey Patterns
Frappe CRMgithub.com/frappe/crmリード/商談のパイプライン、カンバン、アクティビティフィード
Frappe Helpdeskgithub.com/frappe/helpdeskチケットキュー、SLA インジケーター、エージェントビュー
Frappe HRMSgithub.com/frappe/hrms従業員のセルフサービス、承認、ダッシュボード
Frappe Insightsgithub.com/frappe/insightsクエリビルダー、可視化、ダッシュボード
Frappe Buildergithub.com/frappe/builderドラッグ&ドロップのインターフェース、プロパティパネル

手順

0) アプリシェルの構造

すべての Frappe アプリで、一貫したシェル構造を使用します。

┌─────────────────────────────────────────────────────────────┐
│ Header (App title, search, user menu)                       │
├──────────────┬──────────────────────────────────────────────┤
│              │                                              │
│   Sidebar    │              Main Content                    │
│              │                                              │
│  - Nav items │  ┌─────────────────┬──────────────────────┐  │
│  - Filters   │  │   List View     │   Detail Panel       │  │
│  - Actions   │  │                 │                      │  │
│              │  │                 │                      │  │
│              │  └─────────────────┴──────────────────────┘  │
│              │                                              │
└──────────────┴──────────────────────────────────────────────┘

実装:

<template>
  <div class="flex h-screen">
    
    <Sidebar />
    
    
    <div class="flex-1 flex">
      <ListView 
        :class="selectedDoc ? 'w-1/2' : 'w-full'"
        @select="selectDoc"
      />
      <DetailPanel 
        v-if="selectedDoc" 
        :doc="selectedDoc"
        class="w-1/2 border-l"
      />
    </div>
  </div>
</template>
1) サイドバーのパターン

標準的な構造:

<template>
  <aside class="w-56 border-r bg-gray-50 flex flex-col">
    
    <div class="p-4 border-b">
      <h1 class="font-semibold">My App</h1>
    </div>
    
    
    <nav class="flex-1 p-2">
      <SidebarLink 
        v-for="item in navItems" 
        :key="item.name"
        :label="item.label"
        :icon="item.icon"
        :to="item.route"
        :count="item.count"
      />
    </nav>
    
    
    <div v-if="filters.length" class="p-2 border-t">
      <p class="text-xs text-gray-500 px-2 mb-1">Filters</p>
      <SidebarLink 
        v-for="filter in filters"
        :key="filter.name"
        :label="filter.label"
        :count="filter.count"
        @click="applyFilter(filter)"
      />
    </div>
    
    
    <div class="p-2 border-t">
      <UserMenu />
    </div>
  </aside>
</template>

CRM のナビゲーション項目の例:

  • リード(件数バッジ付き)
  • 商談(件数バッジ付き)
  • 連絡先
  • 組織
  • アクティビティ

  • 設定
2) リストビューのパターン

フィルター付きの標準的なリスト:

<template>
  <div class="flex-1 flex flex-col">
    
    <div class="flex items-center justify-between p-4 border-b">
      <div class="flex items-center gap-2">
        <Input 
          type="search" 
          placeholder="Search..." 
          v-model="searchQuery"
          :debounce="300"
        />
        <FilterDropdown :filters="availableFilters" v-model="activeFilters" />
      </div>
      <div class="flex items-center gap-2">
        <ViewToggle v-model="viewMode" :options="['list', 'kanban', 'grid']" />
        <Button variant="solid" @click="createNew">
          <template #prefix><FeatherIcon name="plus" /></template>
          New
        </Button>
      </div>
    </div>
    
    
    <ListView v-if="viewMode === 'list'" :data="items" @row-click="select" />
    <KanbanView v-else-if="viewMode === 'kanban'" :data="items" :columns="stages" />
    <GridView v-else :data="items" @card-click="select" />
  </div>
</template>

リスト行の構造:

<template>
  <div class="flex items-center p-3 hover:bg-gray-50 cursor-pointer border-b">
    
    <Checkbox v-if="selectable" v-model="selected" class="mr-3" />
    
    
    <Avatar :label="row.name" :image="row.image" class="mr-3" />
    
    
    <div class="flex-1 min-w-0">
      <p class="font-medium truncate">{{ row.title }}</p>
      <p class="text-sm text-gray-500 truncate">{{ row.subtitle }}</p>
    </div>
    
    
    <Badge :variant="statusVariant">{{ row.status }}</Badge>
    
    
    <span class="text-sm text-gray-500 ml-4">{{ timeAgo(row.modified) }}</span>
    
    
    <Dropdown :options="rowActions" class="ml-2">
      <Button variant="ghost" icon="more-horizontal" />
    </Dropdown>
  </div>
</template>
3) カンバンビューのパターン

使用例: CRM(商談)、Helpdesk(ステータス別のチケット)

<template>
  <div class="flex overflow-x-auto p-4 gap-4">
    <div 
      v-for="column in columns" 
      :key="column.name"
      class="flex-shrink-0 w-72 bg-gray-100 rounded-lg"
    >
      
      <div class="p-3 font-medium flex items-center justify-between">
        <span>{{ column.label }}</span>
        <Badge>{{ column.items.length }}</Badge>
      </div>
      
      
      <div class="p-2 space-y-2 min-h-[200px]">
        <KanbanCard 
          v-for="item in column.items"
          :key="item.name"
          :data="item"
          @click="select(item)"
          draggable
          @dragend="handleDrop"
        />
      </div>
      
      
      <Button variant="ghost" class="w-full" @click="addTo(column)">
        + Add {{ column.singular }}
      </Button>
    </div>
  </div>
</template>

カンバンカードの構造:

<template>
  <div class="bg-white rounded-lg p-3 shadow-sm border cursor-pointer hover:shadow">
    <p class="font-medium mb-1">{{ data.title }}</p>
    <p class="text-sm text-gray-500 mb-2">{{ data.subtitle }}</p>
    <div class="flex items-center justify-between">
      <Avatar :label="data.assigned_to" size="sm" />
      <span class="text-xs text-gray-400">{{ data.due_date }}</span>
    </div>
  </div>
</template>
4) 詳細パネル/サイドパネル

分割ビューのパターン(CRM/Helpdesk スタイル):

<template>
  <aside class="w-[480px] border-l bg-white flex flex-col">
    
    <div class="flex items-center justify-between p-4 border-b">
      <h2 class="font-semibold">{{ doc.name }}</h2>
      <Button variant="ghost" icon="x" @click="$emit('close')" />
    </div>
    
    
    <Tabs v-model="activeTab">
      <Tab name="details" label="Details" />
      <Tab name="activity" label="Activity" />
      <Tab name="notes" label="Notes" />
    </Tabs>
    
    
    <div class="flex-1 overflow-auto p-4">
      <DetailsTab v-if="activeTab === 'details'" :doc="doc" />
      <ActivityFeed v-else-if="activeTab === 'activity'" :doctype="doctype" :name="doc.name" />
      <NotesTab v-else :doctype="doctype" :name="doc.name" />
    </div>
    
    
    <div class="p-4 border-t flex justify-end gap-2">
      <Button @click="edit">Edit</Button>
      <Button variant="solid" @click="primaryAction">{{ primaryActionLabel }}</Button>
    </div>
  </aside>
</template>
5) フォームのパターン

標準的なフォームレイアウト:

<template>
  <div class="max-w-2xl mx-auto p-6">
    
    <div class="mb-6">
      <h1 class="text-xl font-semibold">{{ isNew ? 'New' : 'Edit' }} {{ doctype }}</h1>
    </div>
    
    
    <FormSection title="Basic Information">
      <div class="grid grid-cols-2 gap-4">
        <FormControl label="Name" v-model="doc.name" :required="true" />
        <FormControl label="Status" type="select" v-model="doc.status" :options="statusOptions" />
      </div>
      <FormControl label="Description" type="textarea" v-model="doc.description" class="mt-4" />
    </FormSection>
    
    <FormSection title="Details" collapsible>
      
    </FormSection>
    
    
    <div class="flex justify-end gap-2 mt-6 pt-4 border-t">
      <Button @click="cancel">Cancel</Button>
      <Button variant="solid" @click="save" :loading="saving">Save</Button>
    </div>
  </div>
</template>

FormSection コンポーネント:

<template>
  <div class="mb-6">
    <div 
      class="flex items-center justify-between mb-3 cursor-pointer"
      @click="collapsible && (collapsed = !collapsed)"
    >
      <h3 class="font-medium text-gray-700">{{ title }}</h3>
      <FeatherIcon v-if="collapsible" :name="collapsed ? 'chevron-down' : 'chevron-up'" />
    </div>
    <div v-show="!collapsed">
      <slot />
    </div>
  </div>
</template>
6) アクティビティフィードのパターン

変更を追跡するため、すべてのアプリで使用します。

<template>
  <div class="space-y-4">
    
    <div class="flex gap-3">
      <Avatar :label="$user.name" />
      <div class="flex-1">
        <Textarea v-model="newComment" placeholder="Add a comment..." rows="2" />
        <Button class="mt-2" @click="addComment" :disabled="!newComment">Comment</Button>
      </div>
    </div>
    
    
    <div v-for="item in activities" :key="item.name" class="flex gap-3">
      <Avatar :label="item.owner" size="sm" />
      <div class="flex-1">
        <div class="flex items-baseline gap-2">
          <span class="font-medium text-sm">{{ item.owner }}</span>
          <span class="text-xs text-gray-400">{{ timeAgo(item.creation) }}</span>
        </div>
        
        <CommentContent v-if="item.type === 'comment'" :content="item.content" />
        <StatusChange v-else-if="item.type === 'status'" :from="item.from" :to="item.to" />
        <FieldChange v-else-if="item.type === 'change'" :field="item.field" :value="item.value" />
      </div>
    </div>
  </div>
</template>
7) 空の状態

常に役立つ空の状態を表示してください。

<template>
  <div class="flex flex-col items-center justify-center h-64 text-center">
    <FeatherIcon name="inbox" class="w-12 h-12 text-gray-300 mb-4" />
    <h3 class="font-medium text-gray-700 mb-1">{{ title }}</h3>
    <p class="text-sm text-gray-500 mb-4">{{ description }}</p>
    <Button v-if="action" variant="solid" @click="action.handler">
      {{ action.label }}
    </Button>
  </div>
</template>


<EmptyState 
  title="No leads yet"
  description="Create your first lead to get started"
  :action="{ label: 'Create Lead', handler: createLead }"
/>
8) 読み込み状態

体感速度を高めるため、スケルトンローダーを使用します。


<template>
  <div v-if="loading" class="space-y-2 p-4">
    <div v-for="i in 5" :key="i" class="flex items-center gap-3 p-3">
      <Skeleton class="w-10 h-10 rounded-full" />
      <div class="flex-1">
        <Skeleton class="h-4 w-1/3 mb-2" />
        <Skeleton class="h-3 w-1/2" />
      </div>
    </div>
  </div>
  <ListView v-else :data="data" />
</template>
9) 色とステータスの規則
ステータスの種類色用途
成功/アクティブ緑 (bg-green-100 text-green-700)完了、アクティブ、解決済み
警告/保留中黄色 (bg-yellow-100 text-yellow-700)保留中、進行中、期限間近
エラー/ブロック赤 (bg-red-100 text-red-700)失敗、ブロック、期限超過
情報/デフォルト青 (bg-blue-100 text-blue-700)新規、オープン、情報
ニュートラルグレー (bg-gray-100 text-gray-700)下書き、キャンセル済み、クローズ済み

Badge コンポーネントの使用方法:

<Badge variant="success">Active</Badge>
<Badge variant="warning">Pending</Badge>
<Badge variant="error">Overdue</Badge>
<Badge variant="info">New</Badge>
<Badge variant="subtle">Draft</Badge>
10) レスポンシブ対応のパターン

モバイルファーストで考慮する点:

<template>
  
  <Sidebar v-if="!isMobile" />
  <Drawer v-else v-model="sidebarOpen">
    <Sidebar />
  </Drawer>
  
  
  <div :class="isMobile ? 'flex-col' : 'flex'">
    <ListView v-show="!isMobile || !selectedDoc" />
    <DetailPanel v-if="selectedDoc" :fullScreen="isMobile" />
  </div>
</template>

コンポーネントのリファレンス

次の Frappe UI コンポーネントを一貫して使用してください。

コンポーネント用途
<Button>すべてのアクション。バリアント:solid、subtle、ghost
<Input>テキスト入力、検索フィールド
<FormControl>ラベルとバリデーション付きのフォームフィールド
<Select>ドロップダウン、ステータスセレクター
<Checkbox>真偽値の入力、一括選択
<Avatar>ユーザー画像、エンティティアイコン
<Badge>ステータスインジケーター、件数
<Dropdown>アクションメニュー、コンテキストメニュー
<Dialog>モーダルでの確認、フォーム
<Tabs>コンテンツの整理
<Tooltip>ヒント、切り詰められたテキストの表示

検証

  • アプリシェルが標準的なレイアウト(サイドバー+メイン+任意の詳細パネル)に沿っている
  • リストビューに検索、フィルター、ビュー切り替え、新規作成ボタンがある
  • 詳細パネルにタブ(詳細、アクティビティ、メモ)がある
  • 空の状態に役立つアクションが用意されている
  • 読み込み状態ではスピナーではなくスケルトンを使用している
  • ステータスの色が規則に沿っている
  • フォームがセクション分けされ、一貫性がある
  • モバイルでの使いやすさが考慮されている

障害モード/デバッグ

  • 余白が不統一:TailwindCSS のスペーシングスケール(p-2、p-4、gap-2、gap-4)を使用します
  • コンポーネントが不適切:Frappe UI のドキュメントを確認し、適切なコンポーネントと props を使用します
  • レスポンシブ対応が機能しない:モバイルのブレークポイントでテストし、sm:、md:、lg: のプレフィックスを使用します
  • 状態が不足:読み込み中、空、エラーの状態が処理されていることを確認します

エスカレーション

  • コンポーネントの実装 → frontend-development
  • バックエンド API の統合 → api-development
  • エンタープライズワークフロー → enterprise-patterns

参考資料

ガードレール

  • まず公式アプリを調査する:UI を設計する前に、CRM、Helpdesk、または関連する公式アプリを確認し、パターンを参考にします
  • Frappe UI コンポーネントを使用する:Frappe UI に相当するものがある場合は、カスタムコンポーネントを作成しないでください
  • スペーシングの規約に従う:パディング/マージンは一貫性を保ちます(4px 単位)
  • すべての状態に対応する:すべてのビューに読み込み中、空、エラーの状態を用意します
  • ナビゲーションの一貫性を保つ:サイドバーの構成は公式アプリに合わせます
  • レスポンシブにテストする:モバイルで問題なく利用できることを確認します

よくある間違い

間違い問題が起きる理由修正方法
カスタムのアプリシェルデザインユーザーにとってなじみのない UX になるCRM/Helpdesk のシェル構造をコピーする
空の状態がないデータがないときにユーザーが戸惑うアクション付きの EmptyState コンポーネントを追加する
スケルトンではなくスピナーを使う読み込み時の表示に違和感がある読み込み中は Skeleton コンポーネントを使用する
ステータスの色が不統一ユーザーが混乱する色の規約の表に従う
パンくずリストなしで深くネストするユーザーが迷うパンくずナビゲーションを追加する
モーダルの多用ワークフローが中断される詳細ビューにはサイドパネルを優先する
キーボードナビゲーションがないアクセシビリティの問題が生じる主要な操作で Tab/Enter が機能することを確認する

コメント