# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

`@epilot360/icons` is a curated icon library for epilot360 based on Material Symbols. It uses **code generation** to create 230+ React components and SVG modules from a single configuration file. The package is shipped as an external system module in the 360 portal and supports tree-shaking for external consumers.

## Common Commands

```bash
# Development
yarn storybook              # Start Storybook dev server at http://localhost:6006
yarn start                  # Serve UMD bundle at http://localhost:5600/bundle.js

# Code Generation (run after modifying icons.config.yaml)
yarn codegen                # Regenerate all icon components and SVG modules

# Building
yarn build                  # Full build: ESM + CommonJS bundles
yarn build:cjs              # CommonJS build only (tsc + webpack)

# Linting
yarn lint                   # Check for linting errors
yarn lint:fix               # Auto-fix linting errors

# Testing
yarn test                   # Run Jest tests (currently passes with no tests)

# Publishing
npm run prepublishOnly      # Clean + build + flatten structure (auto-runs before publish)
npm run postpublish         # Clean up flattened files (auto-runs after publish)
```

## Architecture

### Code Generation System

The entire codebase is **generated from `icons.config.yaml`** using [generator/run.ts](generator/run.ts):

1. **Configuration** ([icons.config.yaml](icons.config.yaml)):
   - Single source of truth for all 230+ icons
   - Each icon has: `name`, `aliases`, and optional `svg_import`
   - `svg_import` points to Material Symbols package (e.g., `@material-symbols/svg-600/rounded/edit.svg`)
   - **Omit `svg_import` for custom icons** (prevents codegen override)

2. **Template System** ([generator/templates/](generator/templates/)):
   - EJS templates generate consistent code
   - React components: [react/component/index.tsx.ejs](generator/templates/react/component/index.tsx.ejs)
   - SVG modules: [svg/module/index.ts.ejs](generator/templates/svg/module/index.ts.ejs)
   - Storybook stories: [react/component/story.stories.tsx.ejs](generator/templates/react/component/story.stories.tsx.ejs)

3. **Generated Outputs**:
   - `src/svg/[IconName]/data.ts` - **single source of truth**: the inner SVG markup
     for each variant (SVGO-optimized to integer coordinates) plus any viewBox /
     root-attribute overrides. Imported by both the React component and the SVG
     string module, so path data is never bundled twice.
   - `src/react/[IconName]/index.tsx` - React component (thin `makeIcon(data, …)` call)
   - `src/svg/[IconName]/index.ts` - SVG string export function (thin `buildSvgString(data, …)` call)
   - `src/svg/[IconName]/icon.svg` + `icon-fill.svg` - Dual variant SVG files (source for `data.ts`)
   - `src/react/index.ts` - Barrel export of all components
   - `src/svg/index.ts` - Barrel export + `svgIcon()` utility

### Dual Build Pipeline

**ESM Build** ([webpack.config.js](webpack.config.js)):
- Single bundle: `index.js` + source maps
- Used in 360 portal as external system module

**CommonJS Build** ([webpack.config-cjs.js](webpack.config-cjs.js)):
- Dynamic entry generation: each source file becomes separate entry point
- Enables tree-shaking: `import EditIcon from '@epilot360/icons/react/Edit'`
- Outputs to `build/`, then flattened to root during publish
- Copy plugin handles SVG file distribution

### Icon Variants

Every icon has **two variants**:
- `outlined` (default stroke-based)
- `filled` (solid fill)

Components switch variants via props:
```tsx
<EditIcon variant="outlined" />
<EditIcon variant="filled" />  // default
```

SVG files stored as:
- `src/svg/[IconName]/icon.svg` (outlined)
- `src/svg/[IconName]/icon-fill.svg` (filled)

### Component Structure

Each generated React component is a thin `makeIcon(data, name, displayName)` call
([src/react/makeIcon.tsx](src/react/makeIcon.tsx)). `makeIcon` returns a component that:
- Reads the inner SVG markup for the active variant from the shared `data` module
- Renders a single `<svg>` via `dangerouslySetInnerHTML` (no `@svgr/webpack`, no DOMParser)
- Merges `defaultIconProps` with user props and spreads them onto the `<svg>`
- Switches between variants based on `variant` prop
- Supports `size` prop override (shorthand for width + height)
- Default props: 20x20, `fill="currentColor"`, `variant="filled"`

Type system ([src/types.ts](src/types.ts)):
```typescript
interface IconProps {
  size?: number | string;
  width?: number | string;
  height?: number | string;
  fill?: string;
  variant?: 'filled' | 'outlined';
}
```

### SVG Handling

Path data lives **once** per icon in `src/svg/[IconName]/data.ts` (generated by
[generator/run.ts](generator/run.ts), which SVGO-optimizes the source SVGs to integer
coordinates). Both consumption paths derive from it:
1. **React** — `makeIcon(data, …)` renders `<svg dangerouslySetInnerHTML={inner} …>`
2. **String** — `buildSvgString(data, …)` ([src/svgString.ts](src/svgString.ts)) builds the
   markup with a template literal (used by `svgIcon()`; works in non-DOM environments)

The `.svg` files are kept as the human-editable source (and for direct `*.svg` imports by
external consumers). The legacy `@svgr/webpack` + `raw-loader` rules remain in the Webpack
configs only for those direct `*.svg` imports — the shared bundle no longer uses them.

TypeScript declarations: [src/svg-module.d.ts](src/svg-module.d.ts)

## Key Workflows

### Adding a New Material Symbols Icon

1. Add entry to [icons.config.yaml](icons.config.yaml):
   ```yaml
   NewIcon:
     name: new_icon
     aliases: ["new", "icon", "example"]
     svg_import: "@material-symbols/svg-600/rounded/new_icon.svg"
   ```

2. Run codegen:
   ```bash
   yarn codegen
   ```

3. Verify in Storybook:
   ```bash
   yarn storybook
   # Navigate to Icons/NewIcon
   ```

**Note**: The icon name in Material Symbols must match the filename (e.g., `edit.svg` for edit icon). Check the `@material-symbols/svg-600/rounded/` directory in node_modules for available icons.

### Adding a Custom Icon (Non-Material)

1. Add entry **without** `svg_import` to [icons.config.yaml](icons.config.yaml):
   ```yaml
   CustomIcon:
     name: custom_icon
     aliases: ["custom", "brand"]
     # NO svg_import field
   ```

2. Create SVG directory and files:
   ```
   src/svg/CustomIcon/
   ├── icon.svg       # outlined variant
   └── icon-fill.svg  # filled variant
   ```

3. Run codegen (skips copying, generates component):
   ```bash
   yarn codegen
   ```

4. Example: See [src/svg/Epilot/](src/svg/Epilot/) for custom epilot logo

### Build and Publish Flow

```bash
# 1. Development cycle
yarn codegen          # After config changes
yarn storybook        # Verify icons visually

# 2. Pre-publish (automatic)
npm run prepublishOnly
├── clean             # Remove build/, react/, svg/, index files
├── build             # ESM + CommonJS builds
└── cp -r build/* .   # Flatten: build/react/ → react/

# 3. Publish
npm publish           # Pushes to npm

# 4. Post-publish cleanup (automatic)
npm run postpublish   # Removes flattened files from working dir
```

**Published package structure**:
```
@epilot360/icons/
├── index.js               # Main ESM bundle
├── index.d.ts            # Type definitions
├── react/Edit.js         # Individual CommonJS components
├── svg/Edit.js           # Individual SVG modules
├── svg/Edit/icon.svg     # Raw SVG files
└── icons.config.yaml     # Config reference
```

## Technical Details

### Single-SPA Integration

Uses `@epilot360/webpack-config-epilot360` for systemjs module federation:
- Library name: `@epilot360/icons`
- Exported as external system module (not lazy-loaded)
- Used by 360 portal micro-frontends

### Tree-Shaking Support

CommonJS build creates **individual entry points** per icon:
- Full import: `import { Edit } from '@epilot360/icons'` (bundles all icons)
- Tree-shakeable: `import Edit from '@epilot360/icons/react/Edit'` (single icon)

Webpack dynamic entry generation ([webpack.config-cjs.js](webpack.config-cjs.js:11-33)):
```javascript
// Generates entries like:
// 'react/Edit': './src/react/Edit/index.tsx'
// 'svg/Edit': './src/svg/Edit/index.ts'
```

### Component Consumption Patterns

Three usage patterns supported:

1. **Named imports** (full library):
   ```tsx
   import { Edit as EditIcon, EpilotIcon } from '@epilot360/icons'
   <EditIcon variant="outlined" size={24} />
   <EpilotIcon name="edit" />
   ```

2. **Direct imports** (tree-shakeable):
   ```tsx
   import EditIcon from '@epilot360/icons/react/Edit'
   <EditIcon />
   ```

3. **Raw SVG strings**:
   ```tsx
   import { svgIcon } from '@epilot360/icons'
   <div dangerouslySetInnerHTML={{
     __html: svgIcon({ name: 'edit', variant: 'filled', width: 24 })
   }} />
   ```

4. **Direct SVG files**:
   ```tsx
   import EditSVG from '@epilot360/icons/svg/Edit/icon.svg'
   <img src={EditSVG} />
   ```

### Material Symbols Source

Icons sourced from:
- `@material-symbols/svg-600` (weight 600, rounded style)
- `@material-symbols/svg-400` (weight 400, available but not primary)

Codegen copies SVG files from node_modules to source tree during generation.

### TypeScript Configuration

- Target: ES5 (browser compatibility)
- Strict mode enabled
- Output: `build/` directory
- Declaration files generated for all components
- Excludes: `*.stories.tsx`, `*.test.tsx`

## Important Files

| File | Purpose |
|------|---------|
| [icons.config.yaml](icons.config.yaml) | Icon registry (single source of truth) |
| [generator/run.ts](generator/run.ts) | Codegen orchestrator |
| [src/index.ts](src/index.ts) | Main entry point |
| [src/types.ts](src/types.ts) | Shared IconProps interface |
| [src/react/common.ts](src/react/common.ts) | Default props (20x20, currentColor, filled) |
| [src/react/EpilotIcon.tsx](src/react/EpilotIcon.tsx) | Generic icon component (name-based lookup) |
| [src/svg/svgIcon.ts](src/svg/svgIcon.ts) | Raw SVG string generator |
| [webpack.config.js](webpack.config.js) | ESM build config |
| [webpack.config-cjs.js](webpack.config-cjs.js) | CommonJS build (tree-shakeable) |
| [.storybook/main.ts](.storybook/main.ts) | Storybook configuration |

## Storybook

- Dev server: http://localhost:6006
- Production: https://portal.dev.epilot.cloud/epilot360-icons/
- Stories auto-generated per icon
- Build output: `storybook-static/`
- Command: `yarn build-storybook`

<!-- epilot-docs-index -->
[epilot Docs Index]|root: ./.epilot-docs|IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning|DESIGN.md: Read DESIGN.md for UI design guidelines before building UI|If docs missing run: npx @epilot/how-to-everything|00-general:{01-tech-stack.md,business-context.md,ci-cd.md,code-style-typescript.md,feature-flags.md,naming-conventions.md,repositories.md,testing.md}|01-apis:{api-design.md,calling-apis.md,updating-sdk-clients.md}|01-apis/sdk-clients/|02-epilot360-microfrontends:{env-vars-runtime-config.md,error-boundary.md,local-development.md,shared-dependencies.md,tailwind-global.md}|03-permissions:{grant-actions.md,permission-checks.md}|04-ui:{glossary.md,i18n.md,icons.md,react.md}|05-entities:{01-entities-and-relations.md,02-entity-schema-and-attributes.md,03-schema-migrations-updating-core-entities.md,04-entity-elasticsearch.md,entity-facades.md}|06-backend:{dynamodb-patterns.md,error-handling.md,logging.md}|07-workflows:{workflows-vs-flows.md}|08-journeys:{filters.md}|09-partnering:{01-overview.md,02-partner-directory.md,03-entity-sharing.md,04-permissions-and-access-control.md,05-service-architecture.md,06-frontend-integration.md}|10-frameworks/epilot-api-clients-openapi-client-axios:{operation-methods.md,typegen.md,bundling.md,api.md,intro.md}|10-frameworks/i18next:{api.md,best-practices.md,context.md,essentials.md,interpolation.md,namespaces.md,plurals.md,react-trans-component.md,react-usetranslation-hook.md}|10-frameworks/openapi-backend:{intro.md,operation-handlers.md,request-lifecycle.md,request-validation.md,response-validation.md,mocking.md,security-handlers.md,typescript.md,api.md}|10-frameworks/single-spa:{api.md,layout-overview.md,microfrontends-concept.md,module-types.md,parcels-api.md,parcels-overview.md,recommended-setup.md}|10-frameworks/volt-ui:{accordion.md,advanced.md,alert-dialog.md,api-reference.md,badge.md,breadcrumb.md,button-group.md,button.md,callout.md,card.md,chainofthought.md,checkbox.md,checkpoint.md,codeblock.md,collapsible-sidebar.md,colors.md,columns-filtering.md,confirmation.md,context.md,contributing.md,conversation.md,data-table.md,date-range-picker.md,date-time-picker.md,dialog.md,drawer.md,dropdown-menu.md,epilot-volt-ui-react.md,field-combobox.md,field-input.md,field-select.md,field-textarea.md,field.md,font.md,getting-started.md,gradients.md,index.md,inlinecitation.md,label.md,message.md,modelselector.md,pagination.md,pill.md,plan.md,popover.md,promptinput.md,queue.md,radio.md,reasoning.md,segmented-control.md,selection-actions.md,separator.md,shimmer.md,sources.md,spacing.md,spinner.md,suggestion.md,switch.md,table.md,tabs.md,task.md,text.md,theming.md,toast.md,tool.md,tooltip.md}|11-rfcs:{rfcs.md}|12-integrations:{inbound-mapping-syntax.md}|13-cli:{cli.md}|13-cli/commands/|14-notifications:{adding-modifying-notifications.md}
<!-- /epilot-docs-index -->
