Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 76 additions & 2 deletions .claude/rules/icons.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,82 @@ paths:

# Icons Package Rules

Source SVGs are transformed into auto-generated Vue components (separate outputs for `vue3/` and `android/`).
## Source SVG Location

Source SVGs live in `packages/dialtone-icons/src/svg/icons/<category>/`. The 18 categories are: alerts, arrows, brand, brand-full-color, communication, controls, data, devices, editing, general, os, people, places, time, weather.

Standard icons use `12x12` viewBox. Full-color gradient icons in `brand-full-color/` may use `24x24`.

## SVG Preparation (Figma Export Cleanup)

Always source from the Figma component's **12px size** (size 100).

**Note:** Icons in the `brand-full-color` category are exempt from the cleanup rules below. They may use solid brand hex colors (e.g., `#5059C9`), gradients, and other attributes that should be preserved as-is.

### Fill color normalization

The build pipeline only replaces these fill values with `currentColor`: `black`, `#000`, `#000000`, `#0D0C0F`, `#222`, `#222222`. Figma commonly exports `fill="#1C1C1C"` or other near-black hex values that will NOT be converted, causing icons to render as hardcoded colors instead of inheriting CSS color. **Normalize any non-standard dark fill to `fill="black"`.**

### Fill-based only (no strokes)

Icons must use fills, not strokes. In Figma, run **"Outline Stroke"** before exporting to convert strokes to filled paths. Confirm no undesirable mangling occurred.

### Remove no-op clipPath wrappers

Figma adds `<g clip-path="url(#...)">` with a `<clipPath><rect width="12" height="12">` when "Clip content" is enabled on the frame. This clips to the full viewBox โ€” a no-op. Strip the `<g>` wrapper, the `<defs>`, and the `<clipPath>`. This also avoids unnecessary `uniqueID` handling in the generated Vue component.

### Consider combining path elements

If multiple `<path>` elements share all attributes (fill, fill-rule, clip-rule, opacity, transform, etc.), consider combining them into a single `<path>` by concatenating their `d` values (space-separated). Do not combine paths that differ in any attribute beyond `d`.

### Required SVG attributes

The root `<svg>` must have: `width`, `height`, `viewBox`, `fill="none"`, `xmlns="http://www.w3.org/2000/svg"`.

## Build Pipeline

The gulp build (`gulpfile.cjs`) transforms source SVGs:

1. Strips `fill="none"` from `<svg>` tag
2. Replaces recognized fill colors with `fill="currentColor"`
3. Strips `width` and `height` from `<svg>` tag
4. Injects accessibility attributes: `aria-hidden`, `focusable`, `role`, `data-name`, `class`
5. Optimizes via svgmin (multipass)
6. Flattens category directories in dist output

Then `transformSVGtoVue.cjs` wraps processed SVGs into Vue components, adding `uniqueID` prefixing for gradient/clipPath IDs (SSR safety).

Build command: `pnpm nx run dialtone-icons:build`

## Generated Files (Never Edit Manually)

- `dist/svg/icons/*.svg` โ€” processed SVGs
- `src/icons/*.vue` โ€” Vue component sources
- `vue3/dist/`, `vue2/dist/` โ€” compiled Vue 2/3 components
- `dist/icons.js` โ€” icon name list
- `index.js` โ€” barrel exports

Icon sizing uses the numeric scale: `100`, `200`, `300`, `400`, `500`, `600`, `700`, `800`.

Do not manually edit generated Vue component files โ€” modify source SVGs and rebuild.
## Keywords

Each icon needs keywords in `src/keywords-icons.json` for discoverability. Structure:

```json
{
"categories": {
"<category>": {
"<icon-name>": ["keyword1", "keyword2"]
}
}
}
```

## Verification

After adding or updating icons:

1. Run `pnpm nx run dialtone-icons:build`
2. Check dist SVGs: mono icons have `fill="currentColor"`, gradient icons preserve gradients
3. Spot-check a generated `.vue` file in `src/icons/`
4. Validate rendering at `http://localhost:4000/design/icons/`
84 changes: 84 additions & 0 deletions .claude/skills/icon.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
description: "Icon add and update pipeline. Use '/icon add <name>' to add a new icon, or '/icon update <name>' to update an existing icon's SVG."
---

# Icon Skill

Icon rules (SVG conventions, fill normalization, build pipeline details) are defined in `.claude/rules/icons.md` and auto-loaded when editing files in `packages/dialtone-icons/**`.

## Adding a New Icon (`/icon add <name>`)

### 1. Gather Information

- Ask the user for the **category** (one of: alerts, arrows, brand, brand-full-color, communication, controls, data, devices, editing, general, os, people, places, time, weather)
- Ask for the **SVG content** (should be exported from the 12px / size 100 Figma component)
- **Suggest keywords** inferred from the icon name and propose them to the user for confirmation. Split the kebab-case name into individual words, then add synonyms and related terms. For example: `alert-circle` โ†’ suggest "alert, circle, warning, caution, error"; `arrow-left` โ†’ suggest "arrow, left, direction, back, previous". Let the user confirm, modify, or add to the suggestions.

### 2. Validate and Clean the SVG

Apply the SVG preparation rules from `.claude/rules/icons.md`. For standard icons (not `brand-full-color`), check:

- viewBox is correct (`0 0 12 12` standard, `0 0 24 24` full-color)
- Required root attributes present (`width`, `height`, `viewBox`, `fill="none"`, `xmlns`)
- No stroke attributes on visible paths
- Fill colors normalized to `black` (not `#1C1C1C` or other hex)
- No no-op clipPath wrappers (Figma artifact)
- Paths combined where they share all attributes

For `brand-full-color` icons, only check viewBox and required root attributes โ€” preserve all fills, gradients, and structure as-is.

### 3. Write the SVG File

Write to: `packages/dialtone-icons/src/svg/icons/<category>/<name>.svg`

File name must be **kebab-case**.

### 4. Add Keywords

Add an entry to `packages/dialtone-icons/src/keywords-icons.json` under the correct category, keeping them alphabetically sorted:

```json
"<category>": {
"<name>": ["keyword1", "keyword2"]
}
```

### 5. Build

```bash
pnpm nx run dialtone-icons:build
```

### 6. Verify

- Read the dist SVG at `dist/svg/icons/<name>.svg`
- Mono icons: must have `fill="currentColor"` (not a hardcoded hex)
- Gradient icons: gradient definitions must be preserved
- Spot-check the generated Vue component at `src/icons/<name>.vue`
- Remind the user to validate rendering at `http://localhost:4000/design/icons/`

---

## Updating an Existing Icon (`/icon update <name>`)

### 1. Locate the Existing Icon

Search for the icon in `packages/dialtone-icons/src/svg/icons/**/<name>.svg` to find its category directory. Read the current SVG to understand what's changing.

### 2. Validate and Clean

Apply the same checks as "Add" step 2.

### 3. Replace the SVG

Overwrite the source SVG file with the cleaned version.

### 4. Build

```bash
pnpm nx run dialtone-icons:build
```

### 5. Verify

Same as "Add" step 6. Additionally, compare the old and new dist SVGs to confirm the intended changes took effect.
24 changes: 19 additions & 5 deletions apps/dialtone-documentation/docs/design/icons/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,26 @@ Go to the [Icon Builder page](https://www.figma.com/file/zz40wi0uW9MvaJ5RuhcRZR/

### Exporting

<dt-notice
kind="info"
class="d-wmx100p d-my24"
hide-close
title="Claude Code"
>
The <code>/icon</code> skill automates SVG validation, normalization, and build verification. Run <code>/icon add &lt;name&gt;</code> or <code>/icon update &lt;name&gt;</code>.
</dt-notice>

1. [Create a new branch](https://github.com/dialpad/dialtone/tree/staging/packages/dialtone-css/.github/CONTRIBUTING.md#making-a-pull-request) in [dialtone](https://github.com/dialpad/dialtone/tree/staging) repo starting with "dlt-xxxx-" in the name.
2. Place the exported SVG file(s) in the appropriate folder category inside `./src/svg/`, files names should be in kebab-case.
3. Run `nx run dialtone-icons:build`
4. Add keywords related to the icon(s) in the `packages/dialtone-icons/src/keywords-icons.json` file.
5. [Commit](https://github.com/dialpad/dialtone/tree/staging/.github/COMMIT_CONVENTION.md) and push your branch to [dialtone](https://github.com/dialpad/dialtone/tree/staging).
6. Open a pull request, once approved it can be merged into main and will go out in the next [dialtone](https://github.com/dialpad/dialtone/tree/staging) release.
2. Export the SVG from the **12px (size 100)** Figma component. For standard icons (all categories except `brand-full-color`), prepare the SVG before placing it:
- Run **Edit > Outline Stroke** in Figma to ensure the icon is fill-based (no strokes).
- Normalize fill colors to `fill="black"`. Figma often exports `fill="#1C1C1C"` or other near-black hex values that the build pipeline won't convert to `currentColor`.
- Remove no-op `<clipPath>` wrappers โ€” Figma adds these when "Clip content" is enabled, but they are unnecessary when the clip rect matches the viewBox.
- Consider combining multiple `<path>` elements into a single `<path>` where they share all attributes (fill, fill-rule, opacity, etc.). Do not combine paths that differ in any attribute beyond `d`.
- Icons in `brand-full-color` may use solid brand hex colors, gradients, and other attributes โ€” preserve these as-is.
3. Place the exported SVG file(s) in the appropriate folder category inside `./src/svg/`, file names should be in kebab-case.
4. Run `nx run dialtone-icons:build`
5. Add keywords related to the icon(s) in the `packages/dialtone-icons/src/keywords-icons.json` file.
6. [Commit](https://github.com/dialpad/dialtone/tree/staging/.github/COMMIT_CONVENTION.md) and push your branch to [dialtone](https://github.com/dialpad/dialtone/tree/staging), and open a pull request.

<script setup>
import { ref } from 'vue';
Expand Down
28 changes: 28 additions & 0 deletions packages/dialtone-icons/.github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,34 @@ or standalone as SVG files.

[Learn more on How to craft an icon.](https://dialtone.dialpad.com/design/icons/#crafting-an-icon)

> **Claude Code users:** The `/icon` skill automates SVG validation, normalization, and build verification. Run `/icon add <name>` or `/icon update <name>` to use it.

## SVG Preparation

Figma exports require cleanup before committing. These steps apply to **standard icons** (all categories except `brand-full-color`). Icons in `brand-full-color` may use solid brand hex colors, gradients, and other attributes that should be preserved as-is.

### Source from 12px (size 100)

Always export from the Figma component's **12px size** (size 100). Full-color icons in the `brand-full-color` category may use 24x24.

### Outline strokes

Icons must be fill-based, not stroke-based. In Figma, run **Edit > Outline Stroke** before exporting. Confirm the icon renders correctly after outlining.

### Normalize fill colors

The build pipeline replaces these fill values with `currentColor`: `black`, `#000`, `#000000`, `#0D0C0F`, `#222`, `#222222`.

Figma commonly exports `fill="#1C1C1C"` or other near-black hex values. **These are NOT in the replacement list** and will pass through as hardcoded colors, preventing CSS color inheritance. Change any non-standard dark fill to `fill="black"` before committing.

### Remove no-op clipPath wrappers

Figma adds `<g clip-path="url(#...)">` with a `<clipPath><rect width="12" height="12">` when "Clip content" is enabled on the frame. This clips to the full viewBox โ€” a no-op that adds unnecessary markup. Remove the `<g>` wrapper, the `<defs>`, and the `<clipPath>` block.

### Consider combining path elements

If multiple `<path>` elements share all attributes (fill, fill-rule, clip-rule, opacity, transform, etc.), consider combining them into a single `<path>` by concatenating their `d` values. Do not combine paths that differ in any attribute beyond `d`.

## Icon build process

Because our SVG's come from Figma, it's possible to have duplicated identifiers if we exported the icons as is.
Expand Down
Loading