mcpField
P
prowler-ui

prowler-ui

github.com/prowler-cloud开发15.1k75 次安装今天更新
代码仓库

关于 prowler-ui

prowler-ui 是面向 Prowler UI 开发的编码技能,提供组件、样式、目录结构与代码质量方面的约定,帮助开发者遵循现有设计系统和项目模式。

主要功能

  1. 01shadcn/ui 与 Tailwind 使用规范
  2. 02组件放置与目录决策规则
  3. 03表单验证与状态管理模式
  4. 04共享样式和显示工具复用
  5. 05提交前质量检查清单

适用场景

  1. 01为 Prowler UI 添加页面与组件
  2. 02按既有模式实现表单和状态
  3. 03审查界面变更是否遵循设计系统

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/prowler-cloud/prowler --skill prowler-ui

安装后在 Claude Code 中输入/prowler-ui确认已加载。

附带文件references/ui-docs.md

详细介绍

相关通用技能

  • typescript - Const 类型、扁平接口
  • react-19 - 不使用 useMemo/useCallback、编译器
  • nextjs-16 - App Router、Server Actions
  • tailwind-4 - cn() 工具、样式规则
  • zod-4 - Schema 验证
  • zustand-5 - 状态管理
  • ai-sdk-5 - 聊天/AI 功能
  • playwright - E2E 测试(另见 prowler-test-ui)

技术栈(版本)

Next.js 16.2.3 | React 19.2.5 | Tailwind 4.1.18 | shadcn/ui
Zod 4.1.11 | React Hook Form 7.62.0 | Zustand 5.0.8
NextAuth 5.0.0-beta.30 | Recharts 2.15.4

关键:组件库规则

  • 始终:使用 shadcn/ui + Tailwind(components/shadcn/)
  • 绝不:向 components/ui/ 添加组件(仅为 prowler-cloud 覆盖层提供临时重新导出垫片)

设计系统规范(必须遵守)

适用于所有 UI 工作。设计系统是唯一可信来源——准确复用,并审慎扩展。

  • 优先复用,绝不重新发明。 开始构建前,先在 components/shadcn/ 和代码库中的现有用法里查找等效实现。已有组件时,切勿创建自定义组件、模态框包装器或基础组件。
  • 严格使用已定义的变体/样式,不多不少。 在调用处,通过组件的 variant/size/tone 属性控制外观。绝不要为共享控件(Button、SelectTrigger、SelectItem、Modal、徽章等)添加临时视觉 className(颜色、不透明度、悬停/聚焦/禁用状态、仅为视觉效果设置的间距),也绝不要跳过正确的语义变体。
  • 模态框:仅使用 @/components/shadcn/modal。Select:使用 components/shadcn/select。
  • 颜色:复用 ui/styles/globals.css 中现有的语义令牌。不要使用原始 Tailwind 颜色工具类(例如 bg-blue-950/40),也不要使用十六进制颜色。如果没有合适的令牌,立即停止并询问设计负责人——不要自行发明或创建近似令牌。
  • 确实需要新变体/令牌? 这属于设计系统变更:先将其添加到共享组件 API(并获得设计审批),再使用。绝不能在调用处自行决定。

审查 UI PR 时,标记以下问题:重复实现 shadcn 功能的自定义模态框/基础组件、在调用处为共享控件设置视觉 className、原始颜色工具类,以及未通过共享组件 API 引入的新变体/令牌。

决策树

组件放置位置
New UI primitive?   → components/shadcn/ (shadcn/ui + Tailwind)
Used by 1 domain?   → components/{domain}/
Used by 2+ domains? → components/shared/
Needs state/hooks?  → "use client"
Server component?    → No directive needed
代码位置
Server action      → actions/{feature}/{feature}.ts
Data transform     → actions/{feature}/{feature}.adapter.ts
Types (shared 2+)  → types/{domain}.ts
Types (local 1)    → {feature}/types.ts
Utils (shared 2+)  → lib/
Utils (local 1)    → {feature}/utils/
Hooks (shared 2+)  → hooks/
Hooks (local 1)    → {feature}/hooks.ts
UI primitive       → components/shadcn/
Domain component   → components/{domain}/

已弃用: components/ui/ 是一个临时重新导出垫片,用于为 prowler-cloud 覆盖层将旧版导入路径映射到 components/shadcn/。 HeroUI 已完全移除。绝不要在此处添加或导入组件——请改用 @/components/shadcn(基础组件)或 @/components/{domain}。 待 cloud 仓库迁移到 @/components/shadcn 后,删除此垫片。

样式决策
Tailwind class exists? → className
Dynamic value?         → style prop
Conditional styles?    → cn()
Static only?           → className (no cn())
Recharts/library?      → CHART_COLORS constant + var()
作用范围规则(绝对)
  • 在 2 个或更多位置使用 → lib/ 或 types/ 或 hooks/(组件放在 components/{domain}/)
  • 在 1 个位置使用 → 保留在功能目录中
  • 所有文件夹结构决策均由此规则决定

项目结构

ui/
├── app/
│   ├── (auth)/              # Auth pages (login, signup)
│   └── (prowler)/           # Main app
│       ├── compliance/
│       ├── findings/
│       ├── providers/
│       ├── scans/
│       ├── services/
│       └── integrations/
├── components/
│   ├── shadcn/              # shadcn/ui primitives (USE THIS)
│   ├── shared/             # Cross-domain composed components (2+ domains)
│   ├── ui/                  # DEPRECATED shim → re-exports shadcn (do not use)
│   ├── {domain}/            # Domain-specific (compliance, findings, providers, etc.)
│   ├── filters/             # Filter components
│   ├── graphs/              # Chart components
│   └── icons/               # Icon components
├── actions/                 # Server actions
├── types/                   # Shared types
├── hooks/                   # Shared hooks
├── lib/                     # Utilities
├── store/                   # Zustand state
├── tests/                   # Playwright E2E
└── styles/                  # Global CSS

Recharts(特殊情况)

对于不接受 className 的 Recharts 属性:

const CHART_COLORS = {
  primary: "var(--color-primary)",
  secondary: "var(--color-secondary)",
  text: "var(--color-text)",
  gridLine: "var(--color-border)",
};

// Only use var() for library props, NEVER in className
<XAxis tick={{ fill: CHART_COLORS.text }} />
<CartesianGrid stroke={CHART_COLORS.gridLine} />

表单 + 验证模式

"use client";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";

const schema = z.object({
  email: z.email(),  // Zod 4 syntax
  name: z.string().min(1),
});

type FormData = z.infer<typeof schema>;

export function MyForm() {
  const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
    resolver: zodResolver(schema),
  });

  const onSubmit = async (data: FormData) => {
    await serverAction(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("email")} />
      {errors.email && <span>{errors.email.message}</span>}
      <button type="submit">Submit</button>
    </form>
  );
}

命令

# Development
cd ui && pnpm install
cd ui && pnpm run dev

# Code Quality
cd ui && pnpm run typecheck
cd ui && pnpm run lint:fix
cd ui && pnpm run format:write
cd ui && pnpm run healthcheck    # typecheck + lint

# Testing
cd ui && pnpm run test:e2e
cd ui && pnpm run test:e2e:ui
cd ui && pnpm run test:e2e:debug

# Build
cd ui && pnpm run build
cd ui && pnpm start

批量与即时组件 API(必须遵守)

当组件同时支持批量(延迟、基于提交)和即时(立即回调)行为时,使用判别联合类型对两者的关联关系建模——绝不能将其定义为互不相关的可选属性。关联属性必须要么全部提供,要么全部不提供。

// ❌ NEVER: Independent optionals — allows invalid half-states
interface FilterProps {
  onBatchApply?: (values: string[]) => void;
  onInstantChange?: (value: string) => void;
  isBatchMode?: boolean;
}

// ✅ ALWAYS: Discriminated union — one valid shape per mode
type BatchProps = {
  mode: "batch";
  onApply: (values: string[]) => void;
  onCancel: () => void;
};

type InstantProps = {
  mode: "instant";
  onChange: (value: string) => void;
  // onApply/onCancel are forbidden here via structural exclusion
  onApply?: never;
  onCancel?: never;
};

type FilterProps = BatchProps | InstantProps;

这样,无效的属性组合会在编译时报错,而不会在运行时造成意外。

优先复用共享展示工具(必须遵守)

添加本地展示映射(标签、提供方名称、状态字符串、类别格式化函数)之前,先在 ui/types/* 和 ui/lib/* 中查找现有辅助工具。

// ✅ CHECK THESE FIRST before creating a new map:
// ui/lib/utils.ts            → general formatters
// ui/types/providers.ts      → provider display names, icons
// ui/types/findings.ts       → severity/status display maps
// ui/types/compliance.ts     → category/group formatters

// ❌ NEVER add a local map that already exists:
const SEVERITY_LABELS: Record<string, string> = {
  critical: "Critical",
  high: "High",
  // ...duplicating an existing shared map
};

// ✅ Import and reuse instead:
import { severityLabel } from "@/types/findings";

如果不存在辅助工具,且会在 2 个或更多位置使用,请将其添加到 ui/lib/ 或 ui/types/ 并复用。仅在恰好用于一个位置时保留为本地实现。

派生状态规则(必需)

避免使用 useState + useEffect 模式来同步 props 或 searchParams——这会导致同步错误和不必要的重新渲染。应直接从真实数据源派生值。

// ❌ NEVER: Mirror props into state via effect
const [localFilter, setLocalFilter] = useState(filter);
useEffect(() => { setLocalFilter(filter); }, [filter]);

// ✅ ALWAYS: Derive directly
const localFilter = filter; // or compute inline

如果确实需要本地状态(例如乐观 UI,或提交前的待处理编辑),请添加简短注释:

// Local state needed: user edits are buffered until "Apply" is clicked
const [pending, setPending] = useState(initialValues);

标签映射的严格键类型(必需)

已知键集合时,避免使用 Record<string, string>。请使用显式联合类型或 const 键对象,以便在编译时捕获拼写错误。

// ❌ Loose — typos compile silently
const STATUS_LABELS: Record<string, string> = {
  actve: "Active",   // typo, no error
};

// ✅ Tight — union key
type Status = "active" | "inactive" | "pending";
const STATUS_LABELS: Record<Status, string> = {
  active: "Active",
  inactive: "Inactive",
  pending: "Pending",
  // actve: "Active"  ← compile error
};

// ✅ Also fine — const satisfies
const STATUS_LABELS = {
  active: "Active",
  inactive: "Inactive",
  pending: "Pending",
} as const satisfies Record<Status, string>;

提交前 QA 检查清单

  • pnpm run typecheck 通过
  • pnpm run lint:fix 通过
  • pnpm run format:write 通过
  • 相关 E2E 测试通过
  • 已处理所有 UI 状态(加载、错误、空状态)
  • 代码中没有密钥(使用 .env.local)
  • 错误消息已清理(不向用户显示堆栈跟踪)
  • 存在服务端验证(不要信任客户端)
  • 无障碍:键盘导航、ARIA 标签
  • 适配移动设备(如适用)

重新请求审查前的检查清单(审查讨论串规范)

在请求审查者重新审查之前:

  • 每个未解决的行内讨论串都已修复,或已明确回复并说明理由
  • 如果你同意某条评论:更改已提交,并在回复中注明提交哈希
  • 如果你不同意:回复中应清楚说明理由——不要让讨论串一直处于未解决状态
  • 仅在所有讨论串都已妥善处理后,才重新请求审查

资源

  • 文档:请参阅 references/ 获取本地开发指南的链接

评论

支持 Markdown