mcpField
D
data-to-ui

data-to-ui

github.com/dralgorhythm開発12859 回インストール14 週間前に更新
リポジトリ

data-to-ui について

data-to-uiは、静的データから型安全なReact UIを作る開発者向けスキルです。JSONからTypeScript、Reactへ変換するパターンに加え、意味に基づくマッピング、書式設定、集計を扱います。

主な機能

  1. 01JSON構造からTypeScript型を作成
  2. 02UIコンポーネント向け派生型
  3. 03意味に基づく色とアイコンの対応付け
  4. 04数値と日付の書式設定ユーティリティ
  5. 05型安全なグループ化と集計

活用シーン

  1. 01JSONデータからReactコンポーネントを作成
  2. 02重要度に応じた色とアイコンの統一
  3. 03表示用の数値と日付の書式設定
  4. 04レコードの分類と集計値の計算

インストール

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

$npx skills add https://github.com/dralgorhythm/claude-agentic-framework --skill data-to-ui

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

詳細

データから UI へ

概要

静的データを型安全な React コンポーネントに変換するためのパターンです。このスキルでは、意味に基づくカラーシステム、派生型、フォーマットユーティリティに重点を置いた JSON → TypeScript → React のパイプラインを扱います。

ワークフロー

1. JSON スキーマ → TypeScript 型
  • JSON スキーマ/データ構造を読み取る
  • JSON の形状に一致する基本的な TypeScript インターフェースを作成する
  • enum 用のユニオン型をエクスポートする(例:type Severity = 'safety_hazard' | 'repair_needed')
  • null または欠損するフィールドにはオプショナルプロパティ(?)を使用する
  • 複雑な型には JSDoc コメントを追加する
2. UI 用の派生型
  • extends を使って基本型を拡張した合成型を作成する
  • コンポーネントの props には Pick<T, K> と Omit<T, K> を使用する
  • & を使って、結合したデータの交差型を構築する(例:FindingWithAsset)
  • 統計や要約用の集約インターフェースを作成する
3. カラーマッピングシステム
  • 意味に基づく色には Record<EnumType, ColorValue> を定義する
  • バッジ、背景、テキスト、枠線、ドットなど、複数のカラー形式を用意する
  • Tailwind のユーティリティクラスを使用する(例:'bg-red-500 text-red-600')
  • アクセサー関数をエクスポートする(例:getSeverityColors())
  • コメントで色の選択理由を説明する
4. アイコンマッピング
  • lucide-react のアイコン名に対応する Record<EnumType, string> を作成する
  • PascalCase のアイコン名を使用する(例:'AlertTriangle'、'Thermometer')
  • アクセサー関数をエクスポートする(例:getSeverityIcon())
5. フォーマットユーティリティ
  • 通貨:小数点以下の桁数を指定せず、USD を使って Intl.NumberFormat を使用する
  • 日付:月を短縮形式にして toLocaleDateString を使用する
  • 計算:年数、割合、耐用年数のヘルパーを作成する
  • ラベル:人が読みやすいラベルのマップを作成する
6. 集約とグループ化
  • reduce または forEach を使用した groupBy パターンを実装する
  • 重要度/優先度の順序を使ったカスタム比較関数でソートする
  • 要約統計量(最小値、最大値、平均値、件数)を計算する
  • 型を厳密に指定した集約結果を返す

リファレンス実装

カラーマッピングシステム
// Single source of truth for semantic colors
export interface SeverityColors {
  badge: string;  // 'text-red-600 bg-red-100'
  bg: string;     // 'bg-red-500'
  text: string;   // 'text-red-600'
  border: string; // 'border-red-500'
  dot: string;    // 'bg-red-500'
}

const SEVERITY_COLOR_MAP: Record<Severity, SeverityColors> = {
  safety_hazard: {
    badge: 'text-red-600 bg-red-100',
    bg: 'bg-red-500',
    text: 'text-red-600',
    dot: 'bg-red-500',
    border: 'border-red-500',
  },
  // ... other severities
};

export function getSeverityColors(severity: Severity): SeverityColors {
  return SEVERITY_COLOR_MAP[severity];
}
アイコンマッピング
export function getSeverityIcon(severity: Severity): string {
  const icons: Record<Severity, string> = {
    safety_hazard: 'AlertTriangle',
    repair_needed: 'Wrench',
    maintenance_item: 'Settings',
    monitor: 'Eye',
    informational: 'Info'
  };
  return icons[severity];
}
派生型
// Base type
export interface Finding {
  id: string;
  assetId?: string | null;
  severity: Severity;
  title: string;
}

// Derived type with relationship
export interface FindingWithAsset extends Finding {
  asset?: Asset;
}

// Aggregate type
export interface PropertyWithDetails {
  property: Property;
  inspectionReport: InspectionReport;
  findings: Finding[];
  assets: Asset[];
}
フォーマットユーティリティ
export function formatCurrency(value: number): string {
  return new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency: 'USD',
    minimumFractionDigits: 0,
    maximumFractionDigits: 0
  }).format(value);
}

export function formatDate(dateString: string): string {
  return new Date(dateString).toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'short',
    day: 'numeric'
  });
}

export function yearsSince(dateString: string): number {
  const date = new Date(dateString);
  const now = new Date();
  return Math.floor((now.getTime() - date.getTime()) / (365.25 * 24 * 60 * 60 * 1000));
}
集約パターン
// Group by enum value
export function groupFindingsBySeverity(findings: Finding[]): Record<Severity, Finding[]> {
  const grouped: Record<Severity, Finding[]> = {
    safety_hazard: [],
    repair_needed: [],
    maintenance_item: [],
    monitor: [],
    informational: []
  };

  findings.forEach(f => grouped[f.severity].push(f));
  return grouped;
}

// Sort by priority
export function sortFindingsBySeverity(findings: Finding[]): Finding[] {
  const severityOrder: Record<Severity, number> = {
    safety_hazard: 0,
    repair_needed: 1,
    maintenance_item: 2,
    monitor: 3,
    informational: 4
  };
  return [...findings].sort((a, b) =>
    severityOrder[a.severity] - severityOrder[b.severity]
  );
}

ベストプラクティス

  • 信頼できる唯一の情報源:すべての色/アイコンのマッピングを一か所にまとめ、アクセサー関数を用意する
  • 複数形式の色:柔軟に使えるよう、バッジ、背景、テキスト、枠線、ドットのバリエーションを用意する
  • 型安全性:プレーンオブジェクトではなく Record<EnumType, Value> を使用する
  • Intl API:ローカライズには Intl.NumberFormat と Intl.DateTimeFormat を使用する
  • 不変性:配列のソート/フィルタリングにはスプレッド演算子を使用する
  • ドキュメント:色の選択理由とデータ構造を説明する JSDoc コメントを追加する
  • ユーティリティの同じ場所への配置:インポートしやすいよう、型とユーティリティを同じファイルにまとめる

アンチパターン

  • 使用しないでください:意味を持たない汎用的な色名を使う(例:'safety_hazard' ではなく 'red')
  • 使用しないでください:コンポーネント内に色クラスを直接記述する。必ずマッピング関数を使用する
  • 使用しないでください:any 型を使う。unknown と型ガードを優先する
  • 使用しないでください:ソート/フィルター関数で入力配列を変更する。必ずコピーを作成する
  • 使用しないでください:日付形式をハードコードする。一貫性を保つため Intl.DateTimeFormat を使用する
  • 使用しないでください:単純なユーティリティを別ファイルに分ける。型と同じ場所にまとめる
  • 忘れないでください:オプショナルフィールドの null/undefined を処理する
  • 使用しないでください:TypeScript ファイル名に snake_case や kebab-case を使う。camelCase を使用する

フィードバックループ

  1. 型チェック:tsc --noEmit を実行して型を検証する
  2. ランタイム検証:実行時の JSON スキーマ検証には Zod の使用を検討する
  3. ビジュアルテスト:カラーシステムを検証する Storybook ストーリーを作成する
  4. データの整合性:集約した統計値をソースデータの件数と比較する
  5. インポートの確認:すべてのユーティリティがエクスポートされ、インポート可能であることを確認する

関連スキル

  • interface-design - React コンポーネントでカラーシステムを使用する
  • refactoring-code - 重複する色/フォーマットのロジックを統合する

コメント