mcpField
D
data-to-ui

data-to-ui

github.com/dralgorhythm开发12859 次安装14 周前更新
代码仓库

关于 data-to-ui

data-to-ui 是面向开发者的技能,帮助将静态数据转为类型安全的 React 界面,涵盖 JSON、TypeScript 到 React 的流程,以及语义映射、格式化和聚合。

主要功能

  1. 01将 JSON 结构转为 TypeScript 类型
  2. 02为 UI 组件创建派生类型
  3. 03语义化颜色与图标映射
  4. 04数值和日期格式化工具
  5. 05类型安全的分组与汇总

适用场景

  1. 01根据 JSON 数据构建 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 Schema → TypeScript 类型
  • 读取 JSON schema/数据结构
  • 创建与 JSON 结构匹配的基础 TypeScript 接口
  • 导出枚举的联合类型(例如,type Severity = 'safety_hazard' | 'repair_needed')
  • 对可为 null 或缺失的字段使用可选属性(?)
  • 为复杂类型添加 JSDoc 注释
2. UI 派生类型
  • 创建通过 extends 扩展基础类型的组合类型
  • 使用 Pick<T, K> 和 Omit<T, K> 定义组件属性
  • 使用 & 构建用于合并数据的交叉类型(例如,FindingWithAsset)
  • 为统计数据/摘要创建聚合接口
3. 颜色映射系统
  • 定义 Record<EnumType, ColorValue>,用于映射语义化颜色
  • 提供多种颜色格式:徽章、背景、文本、边框、圆点
  • 使用 Tailwind 实用类(例如,'bg-red-500 text-red-600')
  • 导出访问器函数(例如,getSeverityColors())
  • 使用注释说明颜色选择
4. 图标映射
  • 创建 Record<EnumType, string>,将类型映射到 lucide-react 图标名称
  • 使用 PascalCase 图标名称(例如,'AlertTriangle'、'Thermometer')
  • 导出访问器函数(例如,getSeverityIcon())
5. 格式化工具
  • 货币:使用 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. 运行时验证:考虑使用 Zod 在运行时验证 JSON schema
  3. 视觉测试:构建 Storybook stories,验证颜色系统
  4. 数据一致性:将聚合统计数据与源数据计数进行比较
  5. 导入验证:确保所有工具都已导出且可以导入

相关技能

  • interface-design - 在 React 组件中使用颜色系统
  • refactoring-code - 整合重复的颜色/格式化逻辑

评论

支持 Markdown