- name
- ui4
- description
- Manually invoked skill for reskinning Payload UI components. Requires Figma URL. Usage: /ui4
# Payload UI Reskin (ui4)
**Figma URL is REQUIRED.** If not provided, ask before proceeding.
---
## Process
### Step 0: Icon Scan
**Goal:** Identify icon dependencies before starting work.
1. **Scan component files** for icon imports:
```
grep -E "from.*icons|import.*Icon" packages/ui/src/elements/ComponentName/
```
2. **List existing icons** in `packages/ui/src/icons/`:
- Each icon has its own folder with `index.tsx` + `index.css`
3. **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?
4. **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: `icon-{size}-{name}.tsx` (e.g., `icon-16-close.tsx`, `icon-24-chevron-down.tsx`)
To find the correct icon:
1. Note the icon name from Figma design (e.g., "close", "chevron-down")
2. Check both 16px and 24px variants if they exist
3. Read the corresponding files and extract the SVG paths for each size
**Icon implementation rules:**
1. **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
}
```
2. **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
}
```
3. **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>
```
4. **Payload conventions:**
- Use `fill="currentColor"` instead of `fill="var(--color-icon)"`
- Use `fillRule` and `clipRule` (React camelCase) instead of kebab-case
- Default size should match most common usage (typically 24)
5. **Reference implementation:** See `packages/ui/src/icons/Chevron/index.tsx` for the pattern.
**If icons are missing from Figma source:** Ask user how to proceed before continuing.
---
### Step 1: SCSS → CSS Migration
**Goal:** Syntax conversion only. Component must look IDENTICAL after.
1. Read component files: `packages/ui/src/elements/ComponentName/` or `packages/ui/src/fields/ComponentName/`
2. Create `index.css` with converted styles:
- `$var` → `var(--token)`
- Keep CSS nesting with `&` (preferred)
- Remove `@use`/`@import` (tokens are global)
- Inline any mixins
3. Update import: `import './index.scss'` → `import './index.css'`
4. Delete `index.scss`
5. Wrap in `@layer payload-default {}`
6. **Convert legacy `var(--base)` to `--spacer` tokens** (see below)
7. **Check for SCSS-only variables** (see below)
---
#### SCSS Variable Dependencies
**CRITICAL:** The `packages/ui/src/scss/` folder has been removed. All global tokens now live in `packages/ui/src/css/`. Any CSS variable you use must exist there.
**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:**
1. Check if there's an equivalent in the CSS folder
2. If not, add it to the appropriate CSS file:
- `spacing.css` — spacers, gutters, layout spacing, breakpoints
- `colors.css` — color tokens
- `typography.css` — font tokens
- `radius.css` — border-radius tokens
- `utilities.css` — accessibility, misc utilities
**Common SCSS-only variables to watch for:**
| SCSS Variable | CSS Equivalent / Action |
| ----------------------- | ------------------------------------------------ |
| `--spacing-view-bottom` | Defined in `spacing.css` |
| `--breakpoint-m-width` | Defined in `spacing.css` (1024px) |
| `--breakpoint-s-width` | Defined in `spacing.css` (768px) |
| `--gutter-h` | Defined in `spacing.css` |
| `$breakpoint-m-width` | Use `var(--breakpoint-m-width)` in media queries |
| `@include mid-break` | Use `@media (max-width: 1024px)` |
| `@include small-break` | Use `@media (max-width: 768px)` |
---
#### Legacy Token Migration: `var(--base)` → `--spacer`
**What is `--base`?** A legacy spacing token equal to `20px` (1.25rem). It must be replaced with `--spacer-*` tokens.
**Spacer token values:**
| Token | Value | Pixels |
| -------------- | ----- | ------ |
| `--spacer-0` | 0 | 0px |
| `--spacer-1` | 4px | 4px |
| `--spacer-2` | 8px | 8px |
| `--spacer-2-5` | 12px | 12px |
| `--spacer-3` | 16px | 16px |
| `--spacer-4` | 24px | 24px |
| `--spacer-5` | 32px | 32px |
| `--spacer-6` | 40px | 40px |
**Conversion strategy:**
1. **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 */
```
2. **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);
```
3. **ALWAYS round to nearest spacer token.** Never use `calc()` to preserve non-standard pixel values. Round all calculated values to the nearest token:
| Pixel Range | Token | Notes |
| ----------- | --------------------- | ----------------------------- |
| 0-2px | `--spacer-0` | Use 0 |
| 3-6px | `--spacer-1` (4px) | 5-6px rounds to 4px |
| 7-10px | `--spacer-2` (8px) | 10px rounds DOWN to 8px |
| 11-14px | `--spacer-2-5` (12px) | 13.33px rounds to 12px |
| 15-20px | `--spacer-3` (16px) | 15px, 20px both round to 16px |
| 21-28px | `--spacer-4` (24px) | |
| 29-36px | `--spacer-5` (32px) | 30px rounds to 32px |
| 37-48px | `--spacer-6` (40px) | |
4. **Common `var(--base)` conversions** (base = 20px):
| Original | Pixels | Rounded Token |
| -------------------- | ------ | --------------------------------------------------------------- |
| `var(--base) * 0.25` | 5px | `--spacer-1` (4px) |
| `var(--base) * 0.3` | 6px | `--spacer-1` (4px) |
| `var(--base) * 0.4` | 8px | `--spacer-2` |
| `var(--base) * 0.5` | 10px | `--spacer-2` (8px) |
| `var(--base) * 0.6` | 12px | `--spacer-2-5` |
| `var(--base) / 1.5` | 13.3px | `--spacer-2-5` (12px) |
| `var(--base) * 0.75` | 15px | `--spacer-3` (16px) |
| `var(--base) * 0.8` | 16px | `--spacer-3` |
| `var(--base)` | 20px | `--spacer-3` (16px) or `--spacer-4` (24px) |
| `var(--base) * 1.2` | 24px | `--spacer-4` |
| `var(--base) * 1.5` | 30px | `--spacer-5` (32px) |
| `var(--base) * 2` | 40px | `--spacer-6` |
| `var(--base) * 3` | 60px | `calc(var(--spacer-4) * 2.5)` — only use calc for values > 40px |
**Rule:** For values ≤ 40px, ALWAYS use a single token. For values > 40px, use `calc()` with a spacer token.
5. **Check Figma design:** The best approach is to check the Figma design for the intended spacing value and use the matching `--spacer-*` token directly.
---
**CRITICAL: SCSS nesting patterns that DON'T work in CSS:**
**1. BEM element concatenation (`&__element`):**
```scss
// 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 (`&--modifier`):**
```scss
// SCSS - WORKS (produces .block--active)
.block {
&--active {
background: blue;
}
}
```
```css
/* CSS - DOES NOT WORK! Use flat selector: */
.block { ... }
.block--active { background: blue; }
```
Auf GitHub ansehen