ui4
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePayload UI Reskin (ui4)
Payload UI 换肤 (ui4)
Figma URL is REQUIRED. If not provided, ask before proceeding.
必须提供 Figma URL。 如果未提供,请在继续之前询问。
Process
流程
Step 0: Icon Scan
步骤 0:图标扫描
Goal: Identify icon dependencies before starting work.
-
Scan component files for icon imports:
grep -E "from.*icons|import.*Icon" packages/ui/src/elements/ComponentName/ -
List existing icons in:
packages/ui/src/icons/- Each icon has its own folder with +
index.tsxindex.css
- Each icon has its own folder with
-
Compare Figma design to available icons:
- Does the design use icons not currently in the component?
- Does the design use icons that don't exist yet?
-
Document findings:
- Existing & used: No action needed
- Existing but not imported: Will need to add import
- Missing from codebase: Flag for user — need to source/create icon
Figma Icons Source:
When updating or creating icons, reference the Figma icon library at:
~/figma/figma/fpl/icons/src/icons/Icon naming convention: (e.g., , )
icon-{size}-{name}.tsxicon-16-close.tsxicon-24-chevron-down.tsxTo find the correct icon:
- Note the icon name from Figma design (e.g., "close", "chevron-down")
- Check both 16px and 24px variants if they exist
- Read the corresponding files and extract the SVG paths for each size
Icon implementation rules:
-
Props: Icon components MUST accept these props (keep existing props when updating):typescript
type IconProps = { readonly className?: string readonly size?: 16 | 24 // Add more sizes as needed // ... keep any existing component-specific props } -
Multi-size support: Store path data keyed by size:typescript
const paths = { 16: 'M4.854 4.146...', // from icon-16-{name}.tsx 24: 'M6.854 6.146...', // from icon-24-{name}.tsx } -
SVG rendering: Use the size prop to select path and viewBox:tsx
<svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} fill="none"> <path d={paths[size]} fill="currentColor" /> </svg> -
Payload conventions:
- Use instead of
fill="currentColor"fill="var(--color-icon)" - Use and
fillRule(React camelCase) instead of kebab-caseclipRule - Default size should match most common usage (typically 24)
- Use
-
Reference implementation: Seefor the pattern.
packages/ui/src/icons/Chevron/index.tsx
If icons are missing from Figma source: Ask user how to proceed before continuing.
目标: 在开始工作前识别图标依赖项。
-
扫描组件文件中的图标导入项:
grep -E "from.*icons|import.*Icon" packages/ui/src/elements/ComponentName/ -
列出中的现有图标:
packages/ui/src/icons/- 每个图标都有独立文件夹,其中包含 +
index.tsxindex.css
- 每个图标都有独立文件夹,其中包含
-
将 Figma 设计与可用图标进行比较:
- 设计是否使用了组件中目前没有的图标?
- 设计是否使用了尚不存在的图标?
-
记录发现:
- 已存在且正在使用: 无需操作
- 已存在但未导入: 需要添加 import
- 代码库中缺失: 标记给用户 — 需要获取或创建图标
Figma 图标源:
更新或创建图标时,参考以下 Figma 图标库:
~/figma/figma/fpl/icons/src/icons/图标命名约定:(例如 、)
icon-{size}-{name}.tsxicon-16-close.tsxicon-24-chevron-down.tsx查找正确图标的方法:
- 记下 Figma 设计中的图标名称(例如 、
close)chevron-down - 如果 16px 和 24px 变体都存在,则检查两者
- 读取对应文件,并提取每种尺寸的 SVG path
图标实现规则:
-
Props: 图标组件必须接受以下 props(更新时保留现有 props):typescript
type IconProps = { readonly className?: string readonly size?: 16 | 24 // Add more sizes as needed // ... keep any existing component-specific props } -
多尺寸支持: 按 size 存储 path 数据:typescript
const paths = { 16: 'M4.854 4.146...', // from icon-16-{name}.tsx 24: 'M6.854 6.146...', // from icon-24-{name}.tsx } -
SVG 渲染: 使用 size prop 选择 path 和 viewBox:tsx
<svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} fill="none"> <path d={paths[size]} fill="currentColor" /> </svg> -
Payload 约定:
- 使用 ,而不是
fill="currentColor"fill="var(--color-icon)" - 使用 和
fillRule(React camelCase),而不是 kebab-caseclipRule - 默认 size 应匹配最常见的使用方式(通常为 24)
- 使用
-
参考实现: 参阅了解具体模式。
packages/ui/src/icons/Chevron/index.tsx
如果 Figma 源中缺少图标: 在继续之前询问用户应如何处理。
Step 1: SCSS → CSS Migration
步骤 1:SCSS → CSS 迁移
Goal: Syntax conversion only. Component must look IDENTICAL after.
- Read component files: or
packages/ui/src/elements/ComponentName/packages/ui/src/fields/ComponentName/ - Create with converted styles:
index.css- →
$varvar(--token) - Keep CSS nesting with (preferred)
& - Remove /
@use(tokens are global)@import - Inline any mixins
- Update import: →
import './index.scss'import './index.css' - Delete
index.scss - Wrap in
@layer payload-default {} - Convert legacy to
var(--base)tokens (see below)--spacer - Check for SCSS-only variables (see below)
目标: 仅转换语法。迁移后组件外观必须完全一致。
- 读取组件文件:或
packages/ui/src/elements/ComponentName/packages/ui/src/fields/ComponentName/ - 使用转换后的样式创建 :
index.css- →
$varvar(--token) - 保留使用 的 CSS 嵌套(推荐)
& - 移除 /
@use(令牌是全局的)@import - 内联所有 mixins
- 更新导入:→
import './index.scss'import './index.css' - 删除
index.scss - 包裹在 中
@layer payload-default {} - 将旧版 转换为
var(--base)令牌(见下文)--spacer - 检查仅存在于 SCSS 的变量(见下文)
SCSS Variable Dependencies
SCSS 变量依赖项
CRITICAL: The folder has been removed. All global tokens now live in . Any CSS variable you use must exist there.
packages/ui/src/scss/packages/ui/src/css/Before using a variable, verify it exists in the CSS folder:
bash
grep -r "variable-name" packages/ui/src/css/If a variable is only in SCSS:
- Check if there's an equivalent in the CSS folder
- If not, add it to the appropriate CSS file:
- — spacers, gutters, layout spacing, breakpoints
spacing.css - — color tokens
colors.css - — font tokens
typography.css - — border-radius tokens
radius.css - — accessibility, misc utilities
utilities.css
Common SCSS-only variables to watch for:
| SCSS Variable | CSS Equivalent / Action |
|---|---|
| Defined in |
| Defined in |
| Defined in |
| Defined in |
| Use |
| Use |
| Use |
关键: 文件夹已删除。所有全局令牌现在位于 中。使用的每个 CSS 变量都必须存在于该目录中。
packages/ui/src/scss/packages/ui/src/css/使用变量前,验证它是否存在于 CSS 文件夹中:
bash
grep -r "variable-name" packages/ui/src/css/如果变量仅存在于 SCSS 中:
- 检查 CSS 文件夹中是否有等效变量
- 如果没有,将其添加到适当的 CSS 文件中:
- — 间距、边距、布局间距和断点
spacing.css - — 颜色令牌
colors.css - — 字体令牌
typography.css - — 圆角令牌
radius.css - — 无障碍和其他实用工具
utilities.css
需要关注的常见 SCSS 专属变量:
| SCSS 变量 | CSS 等效项/操作 |
|---|---|
| --spacing-view-bottom | 定义于 |
| --breakpoint-m-width | 定义于 |
| --breakpoint-s-width | 定义于 |
| --gutter-h | 定义于 |
| $breakpoint-m-width | 在媒体查询中使用 |
| @include mid-break | 使用 |
| @include small-break | 使用 |
Legacy Token Migration: var(--base)
→ --spacer
var(--base)--spacer旧版令牌迁移:var(--base)
→ --spacer
var(--base)--spacerWhat is ? A legacy spacing token equal to (1.25rem). It must be replaced with tokens.
--base20px--spacer-*Spacer token values:
| Token | Value | Pixels |
|---|---|---|
| 0 | 0px |
| 4px | 4px |
| 8px | 8px |
| 12px | 12px |
| 16px | 16px |
| 24px | 24px |
| 32px | 32px |
| 40px | 40px |
Conversion strategy:
-
Direct match: If the result equals a spacer token, use it directly:css
/* Before: var(--base) = 20px → closest is --spacer-3 (16px) or --spacer-4 (24px) */ padding: var(--base); /* After: Choose semantically correct size */ padding: var(--spacer-4); /* if 24px is acceptable */ -
Calculated values: When exact pixel value is important, use:
calc()css/* Before: calc(var(--base) * 0.5) = 10px */ gap: calc(var(--base) * 0.5); /* After: calc(var(--spacer-1) * 2.5) = 10px */ gap: calc(var(--spacer-1) * 2.5); -
ALWAYS round to nearest spacer token. Never useto preserve non-standard pixel values. Round all calculated values to the nearest token:
calc()Pixel Range Token Notes 0-2px --spacer-0Use 0 3-6px (4px)--spacer-15-6px rounds to 4px 7-10px (8px)--spacer-210px rounds DOWN to 8px 11-14px (12px)--spacer-2-513.33px rounds to 12px 15-20px (16px)--spacer-315px, 20px both round to 16px 21-28px (24px)--spacer-429-36px (32px)--spacer-530px rounds to 32px 37-48px (40px)--spacer-6 -
Commonconversions (base = 20px):
var(--base)Original Pixels Rounded Token var(--base) * 0.255px (4px)--spacer-1var(--base) * 0.36px (4px)--spacer-1var(--base) * 0.48px --spacer-2var(--base) * 0.510px (8px)--spacer-2var(--base) * 0.612px --spacer-2-5var(--base) / 1.513.3px (12px)--spacer-2-5var(--base) * 0.7515px (16px)--spacer-3var(--base) * 0.816px --spacer-3var(--base)20px (16px) or--spacer-3(24px)--spacer-4var(--base) * 1.224px --spacer-4var(--base) * 1.530px (32px)--spacer-5var(--base) * 240px --spacer-6var(--base) * 360px — only use calc for values > 40pxcalc(var(--spacer-4) * 2.5)Rule: For values ≤ 40px, ALWAYS use a single token. For values > 40px, usewith a spacer token.calc() -
Check Figma design: The best approach is to check the Figma design for the intended spacing value and use the matchingtoken directly.
--spacer-*
CRITICAL: SCSS nesting patterns that DON'T work in CSS:
1. BEM element concatenation ():
&__elementscss
// SCSS - WORKS (produces .block__element)
.block {
&__element {
color: red;
}
&__other {
color: blue;
}
}css
/* CSS - DOES NOT WORK! &__element is invalid */
/* You must use flat selectors: */
.block { ... }
.block__element { color: red; }
.block__other { color: blue; }2. BEM modifier concatenation ():
&--modifierscss
// SCSS - WORKS (produces .block--active)
.block {
&--active {
background: blue;
}
}css
/* CSS - DOES NOT WORK! Use flat selector: */
.block { ... }
.block--active { background: blue; }3. Parent reference from child:
scss
// SCSS - WORKS
.child {
opacity: 0.5;
.parent--active & {
opacity: 1;
}
}css
/* CSS - DOES NOT WORK! Restructure: */
.child {
opacity: 0.5;
}
.parent--active .child {
opacity: 1;
}What DOES work in CSS nesting:
- ,
&:hover,&:focus(pseudo-classes)&:active - ,
&::before(pseudo-elements)&::after - (descendant nesting with space)
.parent { .child { } }
Migration rule: Convert all and to flat BEM selectors.
&__&--Post-migration validation: After creating the CSS file, run the ui4-review skill to catch any remaining violations (SCSS nesting patterns, hardcoded values, legacy variables). Fix any issues before proceeding.
什么是 ? 一个等于 ()的旧版间距令牌。必须将其替换为 令牌。
--base20px1.25rem--spacer-*间距令牌值:
| 令牌 | 数值 | 像素 |
|---|---|---|
| 0 | 0px |
| 4px | 4px |
| 8px | 8px |
| 12px | 12px |
| 16px | 16px |
| 24px | 24px |
| 32px | 32px |
| 40px | 40px |
转换策略:
-
直接匹配: 如果结果与某个间距令牌相等,直接使用:css
/* Before: var(--base) = 20px → closest is --spacer-3 (16px) or --spacer-4 (24px) */ padding: var(--base); /* After: Choose semantically correct size */ padding: var(--spacer-4); /* if 24px is acceptable */ -
计算值: 当精确像素值很重要时,使用:
calc()css/* Before: calc(var(--base) * 0.5) = 10px */ gap: calc(var(--base) * 0.5); /* After: calc(var(--spacer-1) * 2.5) = 10px */ gap: calc(var(--spacer-1) * 2.5); -
始终舍入到最接近的间距令牌。 绝不要使用保留非标准像素值。将所有计算值舍入到最接近的令牌:
calc()像素范围 令牌 说明 0-2px --spacer-0使用 0 3-6px (4px)--spacer-15-6px 舍入为 4px 7-10px (8px)--spacer-210px 向下舍入为 8px 11-14px (12px)--spacer-2-513.33px 舍入为 12px 15-20px (16px)--spacer-315px、20px 均舍入为 16px 21-28px (24px)--spacer-429-36px (32px)--spacer-530px 舍入为 32px 37-48px (40px)--spacer-6 -
常见的转换(base = 20px):
var(--base)原始值 像素 舍入后的令牌 var(--base) * 0.255px (4px)--spacer-1var(--base) * 0.36px (4px)--spacer-1var(--base) * 0.48px --spacer-2var(--base) * 0.510px (8px)--spacer-2var(--base) * 0.612px --spacer-2-5var(--base) / 1.513.3px (12px)--spacer-2-5var(--base) * 0.7515px (16px)--spacer-3var(--base) * 0.816px --spacer-3var(--base)20px (16px)或--spacer-3(24px)--spacer-4var(--base) * 1.224px --spacer-4var(--base) * 1.530px (32px)--spacer-5var(--base) * 240px --spacer-6var(--base) * 360px — 仅当值大于 40px 时使用 calccalc(var(--spacer-4) * 2.5)规则: 对于小于或等于 40px 的值,始终使用单个令牌。对于大于 40px 的值,使用配合间距令牌。calc() -
检查 Figma 设计: 最佳做法是检查 Figma 设计中的预期间距值,并直接使用匹配的令牌。
--spacer-*
关键:不适用于 CSS 的 SCSS 嵌套模式:
1. BEM 元素拼接():
&__elementscss
// SCSS - WORKS (produces .block__element)
.block {
&__element {
color: red;
}
&__other {
color: blue;
}
}css
/* CSS - DOES NOT WORK! &__element is invalid */
/* You must use flat selectors: */
.block { ... }
.block__element { color: red; }
.block__other { color: blue; }2. BEM 修饰符拼接():
&--modifierscss
// SCSS - WORKS (produces .block--active)
.block {
&--active {
background: blue;
}
}css
/* CSS - DOES NOT WORK! Use flat selector: */
.block { ... }
.block--active { background: blue; }3. 从子元素引用父元素:
scss
// SCSS - WORKS
.child {
opacity: 0.5;
.parent--active & {
opacity: 1;
}
}css
/* CSS - DOES NOT WORK! Restructure: */
.child {
opacity: 0.5;
}
.parent--active .child {
opacity: 1;
}CSS 嵌套中可用的模式:
- 、
&:hover、&:focus(伪类)&:active - 、
&::before(伪元素)&::after - (带空格的后代嵌套)
.parent { .child { } }
迁移规则: 将所有 和 转换为扁平的 BEM 选择器。
&__&--迁移后验证: 创建 CSS 文件后,运行 ui4-review skill 以发现任何剩余违规(SCSS 嵌套模式、硬编码值、旧版变量)。继续之前修复所有问题。
Step 2: Analyze Figma Component Variants
步骤 2:分析 Figma 组件变体
Goal: Understand ALL visual states before implementing CSS.
-
Get metadata first to discover variants:
mcp_figma2_get_metadata(fileKey, nodeId) -
Parse variant properties from symbol names. Common patterns:
State=Default, Validation=None, Selected=false, Read Only=false- Properties often include: State, Validation, Selected, Disabled, Read Only, Size
-
Build a variant matrix:
State Validation Selected Read Only CSS Mapping Default None false false base styles Hover None false false :hoverFocus None false false :focus-visibleDefault Invalid false false or.error&--errorDefault None true false .is-selectedDefault None false true ,.read-only[disabled] -
Fetch design context for key variants (in parallel):
- Default unselected
- Selected
- Hover
- Focus
- Invalid/Error
- Disabled/Read-only
mcp_figma2_get_design_context(fileKey, variantNodeId) -
Compare visual differences between variants:
- Border color changes?
- Background color changes?
- Inner element visibility/opacity?
- Focus ring/outline?
- Text color changes?
If access fails: STOP. Ask user to share file.
目标: 实现 CSS 前了解所有视觉状态。
-
先获取元数据以发现变体:
mcp_figma2_get_metadata(fileKey, nodeId) -
从符号名称中解析变体属性。 常见模式:
State=Default, Validation=None, Selected=false, Read Only=false- 属性通常包括:State、Validation、Selected、Disabled、Read Only、Size
-
构建变体矩阵:
State Validation Selected Read Only CSS 映射 Default None false false 基础样式 Hover None false false :hoverFocus None false false :focus-visibleDefault Invalid false false 或.error&--errorDefault None true false .is-selectedDefault None false true 、.read-only[disabled] -
并行获取关键变体的设计上下文:
- 默认未选中
- 已选中
- 悬停
- 聚焦
- 无效/错误
- 禁用/只读
mcp_figma2_get_design_context(fileKey, variantNodeId) -
比较变体之间的视觉差异:
- 边框颜色是否变化?
- 背景颜色是否变化?
- 内部元素的可见性/透明度是否变化?
- 聚焦环/轮廓是否变化?
- 文字颜色是否变化?
如果访问失败: 停止操作。请用户提供文件。
Step 3: Restyle to Match Figma
步骤 3:重新设计样式以匹配 Figma
-
Read token files (do this BEFORE writing CSS):
- — spacer tokens
packages/ui/src/css/spacing.css - — color tokens
packages/ui/src/css/colors.css - — text tokens
packages/ui/src/css/typography.css - — border-radius tokens
packages/ui/src/css/radius.css - — accessibility tokens
packages/ui/src/css/utilities.css
-
Update styles using tokens from files:
- Colors: ,
--bg-*,--text-*,--icon-*--border-* - Spacing: (ALWAYS check file for matching value)
--spacer-* - Typography: ,
--text-body-*--text-heading-* - Radius:
--radius-none/small/medium/large/full - Focus states: (NEVER use
--accessibility-focus-colordirectly)--color-border-selected
- Colors:
-
Focus state rules:
- Always use for focus outlines/borders
--accessibility-focus-color - Use (not
:focus-visible) for keyboard-only focus:focus - Standard focus outline:
outline: 1px solid var(--accessibility-focus-color) - For parent containers: use to detect child focus
:has(:focus-visible)
- Always use
-
Use canonical shorthands — see the shorthand table in.
.agents/skills/ui4-review/SKILL.md -
Color rules — NEVER GUESS:
- Always extract exact token from Figma design context — the response includes CSS with token names
get_design_context - Don't assume hierarchy — e.g., don't assume "less prominent = tertiary". Check the design.
- When creating new elements (icons, buttons, etc.), fetch the specific Figma node to get correct colors
- If Figma shows a raw hex value, map it to the closest token and note this for user review
- Always extract exact token from Figma design context — the
-
Spacing rules:
- First choice: use token
--spacer-* - If no match: use rem and tell user
- NEVER use px (except 1px borders)
- First choice: use
-
读取令牌文件(必须在编写 CSS 前完成):
- — 间距令牌
packages/ui/src/css/spacing.css - — 颜色令牌
packages/ui/src/css/colors.css - — 文字令牌
packages/ui/src/css/typography.css - — 圆角令牌
packages/ui/src/css/radius.css - — 无障碍令牌
packages/ui/src/css/utilities.css
-
使用文件中的令牌更新样式:
- 颜色:、
--bg-*、--text-*、--icon-*--border-* - 间距:(始终检查文件中是否存在匹配值)
--spacer-* - 字体:、
--text-body-*--text-heading-* - 圆角:
--radius-none/small/medium/large/full - 聚焦状态:(绝不直接使用
--accessibility-focus-color)--color-border-selected
- 颜色:
-
聚焦状态规则:
- 始终将 用于聚焦轮廓/边框
--accessibility-focus-color - 使用 (而非
:focus-visible)实现仅键盘聚焦:focus - 标准聚焦轮廓:
outline: 1px solid var(--accessibility-focus-color) - 对于父容器,使用 检测子元素焦点
:has(:focus-visible)
- 始终将
-
使用标准简写 — 参阅中的简写表。
.agents/skills/ui4-review/SKILL.md -
颜色规则 — 绝不猜测:
- 始终从 Figma 设计上下文中提取准确令牌 — 响应包含带有令牌名称的 CSS
get_design_context - 不要假设层级 — 例如,不要假设“弱化 = tertiary”。请检查设计。
- 创建新元素时(图标、按钮等),获取对应的 Figma 节点以获取正确颜色
- 如果 Figma 显示原始十六进制值,将其映射到最接近的令牌,并告知用户以便审核
- 始终从 Figma 设计上下文中提取准确令牌 —
-
间距规则:
- 首选 令牌
--spacer-* - 如果没有匹配项,使用 rem 并告知用户
- 绝不使用 px(1px 边框除外)
- 首选
Step 4: Ensure Test Collection Has All Variants
步骤 4:确保测试 Collection 包含所有变体
Goal: Before visual verification, ensure the test collection has field variants for all states.
-
Read the test collection config:
test/v4/collections/{ComponentName}/index.ts -
Check for required variants based on Figma variant matrix from Step 2:
Figma Variant Required Field Config Default { name: 'default', type: 'component' }Required { name: 'required', type: 'component', required: true }Disabled { name: 'disabled', type: 'component', admin: { disabled: true }, defaultValue: '...' }Read Only { name: 'readOnly', type: 'component', admin: { readOnly: true }, defaultValue: '...' }With Description { name: 'withDescription', type: 'component', admin: { description: 'Help text' } } -
Add missing variants if any are missing. Includefor disabled/readOnly so there's visible content to test.
defaultValue -
Restart dev server if collection was modified:
pnpm run dev v4
目标: 在进行视觉验证之前,确保测试 collection 包含所有字段状态变体。
-
读取测试 collection 配置:
test/v4/collections/{ComponentName}/index.ts -
根据步骤 2 中的 Figma 变体矩阵检查必需变体:
Figma 变体 必需字段配置 Default { name: 'default', type: 'component' }Required { name: 'required', type: 'component', required: true }Disabled { name: 'disabled', type: 'component', admin: { disabled: true }, defaultValue: '...' }Read Only { name: 'readOnly', type: 'component', admin: { readOnly: true }, defaultValue: '...' }With Description { name: 'withDescription', type: 'component', admin: { description: 'Help text' } } -
如果缺少任何变体,则添加。 为 disabled/readOnly 包含,以提供可见的测试内容。
defaultValue -
如果修改了 collection,则重启开发服务器:
pnpm run dev v4
Step 5: Verify with Playwright (LOOP)
步骤 5:使用 Playwright 验证(循环)
Dev Server: Use when working on field components. The suite has dedicated collections for each field type with various states (default, required, disabled).
pnpm run dev v4test/v4URL Pattern:
- Fields:
http://localhost:3000/admin/collections/{field-type}-fields/create - Elements: Use the appropriate page that displays the element
Handling Modal Dialogs (beforeunload):
When the browser has unsaved changes, a "beforeunload" dialog may block ALL Playwright operations. You'll see this error pattern:
undefined开发服务器: 处理字段组件时,使用 。 测试套件为每种字段类型提供专用 collection,并包含多种状态(default、required、disabled)。
pnpm run dev v4test/v4URL 模式:
- 字段:
http://localhost:3000/admin/collections/{field-type}-fields/create - 元素:使用显示该元素的相应页面
处理模态对话框(beforeunload):
当浏览器存在未保存的更改时, 对话框可能会阻止所有 Playwright 操作。你会看到以下错误模式:
beforeunloadundefinedError
Error
Error: Tool "browser_snapshot" does not handle the modal state.
Error: Tool "browser_snapshot" does not handle the modal state.
Modal state
Modal state
- ["beforeunload" dialog with message ""]: can be handled by browser_handle_dialog
**BEFORE retrying any operation**, you MUST dismiss the dialog:
1. Call `browser_handle_dialog({ accept: true })` to dismiss
2. Then retry your intended operation (navigate, snapshot, screenshot, etc.)
If the dialog persists after handling, call `browser_close()` to close the tab, then `browser_navigate` to reopen the page fresh.
**Verification Steps:**
1. Navigate: `browser_navigate` to component page
2. Screenshot: `browser_take_screenshot({ fullPage: true })`
3. Compare to Figma design
4. Check:
- [ ] Padding correct?
- [ ] Margin correct?
- [ ] Gap correct?
- [ ] Flex alignment correct?
- [ ] Colors match?
5. **Verify ALL variant states:**
- [ ] Default state matches Figma default variant?
- [ ] Hover state matches Figma hover variant? (use `browser_hover`)
- [ ] Focus state matches Figma focus variant? (tab to element)
- [ ] Error state matches Figma invalid variant? (trigger validation)
- [ ] Disabled/read-only matches Figma disabled variant?
- [ ] Selected state matches Figma selected variant? (if applicable)
6. **If wrong:** fix CSS → goto step 1
7. **If correct:** continue- ["beforeunload" dialog with message ""]: can be handled by browser_handle_dialog
**在重试任何操作之前**,必须关闭该对话框:
1. 调用 `browser_handle_dialog({ accept: true })` 关闭对话框
2. 然后重试预期操作(导航、快照、截图等)
如果处理后对话框仍然存在,调用 `browser_close()` 关闭标签页,然后调用 `browser_navigate` 重新打开全新页面。
**验证步骤:**
1. 导航:使用 `browser_navigate` 打开组件页面
2. 截图:`browser_take_screenshot({ fullPage: true })`
3. 与 Figma 设计进行比较
4. 检查:
- [ ] Padding 是否正确?
- [ ] Margin 是否正确?
- [ ] Gap 是否正确?
- [ ] Flex 对齐是否正确?
- [ ] 颜色是否匹配?
5. **验证所有变体状态:**
- [ ] Default 状态是否匹配 Figma default 变体?
- [ ] Hover 状态是否匹配 Figma hover 变体?(使用 `browser_hover`)
- [ ] Focus 状态是否匹配 Figma focus 变体?(Tab 切换到元素)
- [ ] Error 状态是否匹配 Figma invalid 变体?(触发验证)
- [ ] Disabled/read-only 是否匹配 Figma disabled 变体?
- [ ] Selected 状态是否匹配 Figma selected 变体?(如适用)
6. **如果有误:** 修复 CSS → 转到步骤 1
7. **如果正确:** 继续Step 6: User Confirmation
步骤 6:用户确认
Share screenshot and dev server URL. User validates or requests changes.
分享截图和开发服务器 URL。由用户进行验证或提出更改请求。
CSS Structure
CSS 结构
Always use and CSS nesting:
@layercss
@layer payload-default {
.component {
display: flex;
gap: var(--spacer-2);
padding: var(--spacer-2) var(--spacer-3);
background: var(--bg-default-secondary);
border: 1px solid var(--border-default-default);
border-radius: var(--radius-medium);
&__header {
display: flex;
gap: var(--spacer-1);
}
&--error {
border-color: var(--border-danger-strong);
}
&:hover {
background: var(--bg-default-secondary-hover);
}
}
}始终使用 和 CSS 嵌套:
@layercss
@layer payload-default {
.component {
display: flex;
gap: var(--spacer-2);
padding: var(--spacer-2) var(--spacer-3);
background: var(--bg-default-secondary);
border: 1px solid var(--border-default-default);
border-radius: var(--radius-medium);
&__header {
display: flex;
gap: var(--spacer-1);
}
&--error {
border-color: var(--border-danger-strong);
}
&:hover {
background: var(--bg-default-secondary-hover);
}
}
}Red Flags - STOP
危险信号 — 停止
| Thought | Reality |
|---|---|
| "I'll use flat selectors" | Use CSS nesting with |
| "I'll use 8px here" | Read spacing.css, use token |
| "No matching spacer" | Did you actually check spacing.css? |
| "I'll guess the colors" | Read colors.css, use exact token |
| "Close enough" | Screenshot and compare to Figma |
| "Skip verification" | Always run Playwright loop |
| "Just need the default state" | Get metadata first, analyze ALL variants |
| "I'll figure out states later" | Build variant matrix BEFORE writing CSS |
| 想法 | 实际情况 |
|---|---|
| “我会使用扁平选择器” | 使用带 |
| “我会在这里使用 8px” | 读取 spacing.css,使用令牌 |
| “没有匹配的 spacer” | 你真的检查 spacing.css 了吗? |
| “我会猜颜色” | 读取 colors.css,使用准确令牌 |
| “足够接近了” | 截图并与 Figma 比较 |
| “跳过验证” | 始终运行 Playwright 循环 |
| “只需要 default 状态” | 先获取元数据,分析所有变体 |
| “状态稍后再处理” | 编写 CSS 前先构建变体矩阵 |
Step 7: Write Variant E2E Tests
步骤 7:编写变体 E2E 测试
Goal: Create e2e tests that verify all visual variants from the Figma design.
-
Create test file in
test/v4/collections/{ComponentName}/e2e.spec.ts -
Test structure:typescript
import type { Page } from '@playwright/test' import { expect, test } from '@playwright/test' import path from 'path' import { fileURLToPath } from 'url' import { ensureCompilationIsDone, initPageConsoleErrorCatch, } from '../../../__helpers/e2e/helpers.js' import { AdminUrlUtil } from '../../../__helpers/shared/adminUrlUtil.js' import { initPayloadE2ENoConfig } from '../../../__helpers/shared/initPayloadE2ENoConfig.js' import { TEST_TIMEOUT_LONG } from '../../../playwright.config.js' import { componentFieldsSlug } from '../../slugs.js' const filename = fileURLToPath(import.meta.url) const currentFolder = path.dirname(filename) const dirname = path.resolve(currentFolder, '../../') const { beforeAll, describe } = test let page: Page let serverURL: string let url: AdminUrlUtil describe('ComponentName Field Variants', () => { beforeAll(async ({ browser }, testInfo) => { testInfo.setTimeout(TEST_TIMEOUT_LONG) ;({ serverURL } = await initPayloadE2ENoConfig({ dirname })) url = new AdminUrlUtil(serverURL, componentFieldsSlug) const context = await browser.newContext() page = await context.newPage() initPageConsoleErrorCatch(page) await ensureCompilationIsDone({ page, serverURL }) }) // Test each variant from the Figma design }) -
Write tests for each Figma variant:Map the variant matrix from Step 2 to test cases:typescript
test('default state renders correctly', async () => { await page.goto(url.create) const field = page.locator('#field-componentName') await expect(field).toBeVisible() // Verify visual properties match Figma default variant }) test('hover state shows correct styling', async () => { await page.goto(url.create) const field = page.locator('#field-componentName') await field.hover() // Verify hover styles match Figma hover variant }) test('focus state shows correct styling', async () => { await page.goto(url.create) const field = page.locator('#field-componentName') await field.focus() // Verify focus ring/outline matches Figma focus variant }) test('error state renders correctly', async () => { await page.goto(url.create) // Trigger validation by submitting without required field await page.locator('button#action-save').click() const field = page.locator('#field-requiredComponent') // Verify error styling matches Figma invalid variant }) test('disabled state renders correctly', async () => { await page.goto(url.create) const field = page.locator('#field-disabledComponent') await expect(field).toBeDisabled() // Verify disabled styling matches Figma disabled variant }) test('read-only state renders correctly', async () => { await page.goto(url.create) const field = page.locator('#field-readOnlyComponent') await expect(field).toHaveAttribute('readonly') // Verify read-only styling matches Figma read-only variant }) -
Collection variants already configured: (See Step 4)The test collection should already have all required variants from Step 4.
-
Run tests to verify:bash
pnpm run test:e2e --grep "ComponentName Field Variants"
目标: 创建 e2e 测试,验证 Figma 设计中的所有视觉变体。
-
创建测试文件
test/v4/collections/{ComponentName}/e2e.spec.ts -
测试结构:typescript
import type { Page } from '@playwright/test' import { expect, test } from '@playwright/test' import path from 'path' import { fileURLToPath } from 'url' import { ensureCompilationIsDone, initPageConsoleErrorCatch, } from '../../../__helpers/e2e/helpers.js' import { AdminUrlUtil } from '../../../__helpers/shared/adminUrlUtil.js' import { initPayloadE2ENoConfig } from '../../../__helpers/shared/initPayloadE2ENoConfig.js' import { TEST_TIMEOUT_LONG } from '../../../playwright.config.js' import { componentFieldsSlug } from '../../slugs.js' const filename = fileURLToPath(import.meta.url) const currentFolder = path.dirname(filename) const dirname = path.resolve(currentFolder, '../../') const { beforeAll, describe } = test let page: Page let serverURL: string let url: AdminUrlUtil describe('ComponentName Field Variants', () => { beforeAll(async ({ browser }, testInfo) => { testInfo.setTimeout(TEST_TIMEOUT_LONG) ;({ serverURL } = await initPayloadE2ENoConfig({ dirname })) url = new AdminUrlUtil(serverURL, componentFieldsSlug) const context = await browser.newContext() page = await context.newPage() initPageConsoleErrorCatch(page) await ensureCompilationIsDone({ page, serverURL }) }) // Test each variant from the Figma design }) -
为每个 Figma 变体编写测试:将步骤 2 中的变体矩阵映射到测试用例:typescript
test('default state renders correctly', async () => { await page.goto(url.create) const field = page.locator('#field-componentName') await expect(field).toBeVisible() // Verify visual properties match Figma default variant }) test('hover state shows correct styling', async () => { await page.goto(url.create) const field = page.locator('#field-componentName') await field.hover() // Verify hover styles match Figma hover variant }) test('focus state shows correct styling', async () => { await page.goto(url.create) const field = page.locator('#field-componentName') await field.focus() // Verify focus ring/outline matches Figma focus variant }) test('error state renders correctly', async () => { await page.goto(url.create) // Trigger validation by submitting without required field await page.locator('button#action-save').click() const field = page.locator('#field-requiredComponent') // Verify error styling matches Figma invalid variant }) test('disabled state renders correctly', async () => { await page.goto(url.create) const field = page.locator('#field-disabledComponent') await expect(field).toBeDisabled() // Verify disabled styling matches Figma disabled variant }) test('read-only state renders correctly', async () => { await page.goto(url.create) const field = page.locator('#field-readOnlyComponent') await expect(field).toHaveAttribute('readonly') // Verify read-only styling matches Figma read-only variant }) -
Collection 变体已配置:(见步骤 4)测试 collection 应已包含步骤 4 中的所有必需变体。
-
运行测试进行验证:bash
pnpm run test:e2e --grep "ComponentName Field Variants"
Step 8: Run ui4-review
步骤 8:运行 ui4-review
After user confirms the component looks correct, invoke the skill.
ui4-reviewThis will:
- Scan all changed CSS files
- Auto-fix any remaining hardcoded values
- Report what was fixed/flagged
用户确认组件外观正确后,调用 skill。
ui4-review它将:
- 扫描所有已更改的 CSS 文件
- 自动修复任何剩余的硬编码值
- 报告已修复或标记的问题
Reference
参考
- Example migrated component:
packages/ui/src/elements/Button/index.css - Token files:
packages/ui/src/css/*.css - Legacy token migration: See Step 1 for →
var(--base)conversion table--spacer - v4 test suite: — dedicated collections per field type
test/v4/- Each collection should have: default, required, disabled, readOnly field variants
- Disabled/readOnly fields need for visible content
defaultValue - Run with:
pnpm run dev v4 - URL:
http://localhost:3000/admin/collections/{slug}/create - Available: ,
text-fields,textarea-fields,email-fields,number-fields,password-fields,checkbox-fields,select-fields,relationship-fields,upload-fields,slug-fields,code-fields,json-fields,collapsible-fields,group-fields,tabs-fields,point-fields,radio-fields,row-fields,array-fields,blocks-fieldsdate-fields
- E2E test examples: See for patterns
test/fields/collections/*/e2e.spec.ts- Test helper imports from
test/__helpers/e2e/helpers.js - Use for URL construction
AdminUrlUtil - Use for test setup
initPayloadE2ENoConfig
- Test helper imports from
- 迁移后的示例组件:
packages/ui/src/elements/Button/index.css - 令牌文件:
packages/ui/src/css/*.css - 旧版令牌迁移: 参阅步骤 1 中的 →
var(--base)转换表--spacer - v4 测试套件: — 每种字段类型都有专用 collection
test/v4/- 每个 collection 应包含:default、required、disabled、readOnly 字段变体
- Disabled/readOnly 字段需要 才能显示内容
defaultValue - 运行方式:
pnpm run dev v4 - URL:
http://localhost:3000/admin/collections/{slug}/create - 可用项:、
text-fields、textarea-fields、email-fields、number-fields、password-fields、checkbox-fields、select-fields、relationship-fields、upload-fields、slug-fields、code-fields、json-fields、collapsible-fields、group-fields、tabs-fields、point-fields、radio-fields、row-fields、array-fields、blocks-fieldsdate-fields
- E2E 测试示例: 参阅 中的模式
test/fields/collections/*/e2e.spec.ts- 从 导入测试辅助函数
test/__helpers/e2e/helpers.js - 使用 构建 URL
AdminUrlUtil - 使用 设置测试
initPayloadE2ENoConfig
- 从