npm package: @purposeinplay/feral-icons
Description: Custom React icon library with tree-shaking support
Latest version: 0.1.13

# @purposeinplay/feral-icons

Custom React icon library with full tree-shaking support and TypeScript autocomplete.

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)

## Features

- 🎯 **Name-based API** - `<FeralIcon name="camera" />` - simple and clean
- 🌲 **Tree-shakable** - Only imports what you use
- 📦 **Tiny bundle** - ~0.3KB per icon gzipped
- 🔷 **TypeScript** - Full type safety with icon name autocomplete
- ⚡ **Fast** - Optimized SVGs with SVGO
- 🎨 **Customizable** - Control size, color, and all SVG props
- 🔧 **Easy to extend** - Add new icons by dropping SVG files

## Installation

```bash
npm install @purposeinplay/feral-icons
# or
yarn add @purposeinplay/feral-icons
# or
pnpm add @purposeinplay/feral-icons
```

## Usage

### Basic Usage

```tsx
import { FeralIcon } from "@purposeinplay/feral-icons";

function App() {
  return (
    <>
      <FeralIcon name="arrow-left" size={24} />
      <FeralIcon name="camera" fill="red" />
      <FeralIcon name="user" className="text-blue-500" fill="#3B82F6" />
    </>
  );
}
```

### With Dynamic Names

```tsx
import { FeralIcon, type IconName } from "@purposeinplay/feral-icons";

function IconDisplay({ iconName }: { iconName: IconName }) {
  return <FeralIcon name={iconName} size={24} />;
}

// Usage
<IconDisplay iconName="camera" />;
```

### With Tailwind CSS

Icons work seamlessly with Tailwind CSS color utilities:

```tsx
// Using Tailwind text color classes (recommended)
<FeralIcon name="camera" className="text-blue-600" />
<FeralIcon name="user" className="text-red-500 hover:text-red-700" />

// Using Tailwind colors with fill prop
<FeralIcon name="arrow-left" fill="rgb(59 130 246)" />

// Responsive colors
<FeralIcon name="camera" className="text-blue-500 md:text-purple-500" />
```

**Pro tip:** Since icons default to `fill="currentColor"`, you can control their color with Tailwind's `text-*` utility classes!

## Props

The `FeralIcon` component accepts these props:

- `name: IconName` - **Required**. The icon name (e.g., `"camera"`, `"user"`, `"arrow-left"`)
- `size?: number | string` - Icon size (default: 24)
- `fill?: string` - Fill color (default: 'currentColor')
- `className?: string` - Additional CSS classes
- All standard SVG attributes (`onClick`, `onMouseEnter`, etc.)

## Available Icons

- `arrow-left` - Navigation arrow pointing left
- `camera` - Camera icon
- `user` - User profile icon

All icon names are available as the `IconName` type for TypeScript autocomplete.

## Tree-Shaking

This library is fully tree-shakable. Only imported icons are included in your bundle.

**Example bundle impact:**

- Importing all icons: ~2KB gzipped
- Importing single icon: ~0.3KB gzipped

## Local Development & Testing

### View Icons in Storybook

Run Storybook to browse and interact with all icons:

```bash
npm run storybook
```

This will open Storybook at `http://localhost:6006` where you can:
- Browse all available icons
- Test different sizes and colors
- Try Tailwind CSS integration
- Copy code examples
- Test accessibility features

### Test Locally with npm link

```bash
# In the @purposeinplay/feral-icons directory
npm link

# In your app directory
npm link @purposeinplay/feral-icons
```

### Use in Your App

```tsx
import { FeralIcon } from "@purposeinplay/feral-icons";

function MyComponent() {
  return <FeralIcon name="arrow-left" size={32} fill="#3B82F6" />;
}
```

## Adding New Icons

### Quick Start

1. **Add your SVG file** to the `svgs/` directory

   ```bash
   # Example: Add a new icon called "heart"
   cp ~/Downloads/heart.svg svgs/heart.svg
   ```

2. **Regenerate the icon pack** so the new icon is registered and available

   ```bash
   npm run generate
   ```

   This creates/updates the registry and components. You can now use the icon in code and see it in Storybook.

3. **Build the library** (when preparing a release)

   ```bash
   npm run build
   ```

   This runs `generate` plus the full library build.

4. **Use your new icon**
   ```tsx
   <FeralIcon name="heart" size={24} />
   ```

That's it! The build process automatically:

- ✅ Optimizes the SVG
- ✅ Generates a React component (`Heart.tsx`)
- ✅ Adds it to the icon registry
- ✅ Updates TypeScript types for autocomplete

### SVG Requirements

Your SVG files should follow these guidelines:

**Required:**

- ✅ 19x19 viewBox (e.g., `viewBox="0 0 19 19"`)
- ✅ Path-based design with filled paths (not strokes)
- ✅ Kebab-case naming (e.g., `arrow-left.svg`, `user-circle.svg`)

**Recommended:**

- ✅ Use `fill="currentColor"` or no fill attribute (will inherit from parent)
- ✅ Remove any `stroke` attributes (optimizer will strip them anyway)
- ✅ Remove unnecessary attributes like `id`, `class`, `data-*`

**Example SVG:**

```svg
<svg xmlns="http://www.w3.org/2000/svg" width="19" height="19" viewBox="0 0 19 19">
  <path d="M9.5 2L2 6v8c0 4.44 3.07 8.59 7.5 9.6 4.43-1.01 7.5-5.16 7.5-9.6V6l-7.5-4z"/>
</svg>
```

### Icon Naming Convention

SVG filenames are converted to icon names:

- `arrow-left.svg` → `name="arrow-left"`
- `user-circle.svg` → `name="user-circle"`
- `chevron-down.svg` → `name="chevron-down"`

Component names are PascalCase internally:

- `arrow-left.svg` → `ArrowLeft` component
- `user-circle.svg` → `UserCircle` component

## Development Scripts

### Generating Icons

```bash
# Generate components from SVGs (without building)
npm run generate
```

## Versioning and Releases

We use `standard-version` to automate Semantic Versioning and CHANGELOG generation based on [Conventional Commits](https://www.conventionalcommits.org/).

### Creating a Release

To create a new version:

1. **Commit your changes** using Conventional Commit messages (e.g., `feat: ...`, `fix: ...`).
2. **Run the release script**:
   ```bash
   # Standard release (automatic version bump)
   npm run release

   # Or specify the bump type explicitly
   npm run release:patch
   npm run release:minor
   npm run release:major
   ```

This command will:
- Bump the version in `package.json` and `package-lock.json`.
- Generate/update `CHANGELOG.md`.
- Create a git commit and tag (e.g., `v0.1.5`).

3. **Push to origin**:
   ```bash
   git push --follow-tags origin main
   ```

## Development Workflow

**Adding/updating icons:**

```bash
# 1. Add SVG to svgs/ directory
cp new-icon.svg svgs/

# 2. Regenerate the pack (registers the icon; use this to see it in Storybook)
npm run generate

# 3. Full rebuild (when preparing a release)
npm run build

# Icons are now available!
```

**During development:**

```bash
# Watch mode - rebuilds on changes
npm run dev

# In another terminal, run Storybook
npm run storybook
```

### Testing Changes Locally

```bash
# In the icon library directory
npm link

# In your app directory
npm link @purposeinplay/feral-icons

# Make changes, rebuild, and test
npm run build
```

## How It Works

### Architecture Overview

```
User Code                Internal System              SVG Files
─────────                ────────────────             ─────────

<FeralIcon              →  iconRegistry               svgs/
  name="camera"            {                           ├── arrow-left.svg
  size={24}                 'camera': Camera,         ├── camera.svg
/>                          'user': User,             └── user.svg
                            ...                             ↓
                          }                           [Build Process]
                             ↓                              ↓
                         Camera component            Generated Components
                         renders with props          ├── ArrowLeft.tsx
                                                     ├── Camera.tsx
                                                     └── User.tsx
```

### Build Process

When you run `npm run build`, here's what happens:

1. **Clean** - Removes old generated files
2. **Generate** - For each SVG in `svgs/`:
   - Optimizes SVG with SVGO (removes strokes, dimensions, etc.)
   - Extracts SVG path content
   - Generates React component file
   - Adds to icon registry
   - Updates TypeScript types
3. **Compile** - TypeScript generates `.d.ts` files
4. **Bundle** - Vite creates ESM output with tree-shaking

### File Structure

```
feral-icon-pack/
├── svgs/                    # Source SVG files (you add here)
│   ├── arrow-left.svg
│   ├── camera.svg
│   └── user.svg
├── src/
│   ├── icons/              # Generated (gitignored)
│   │   ├── ArrowLeft.tsx
│   │   ├── Camera.tsx
│   │   └── User.tsx
│   ├── shared/
│   │   ├── Icon.tsx        # Base SVG wrapper
│   │   └── types.ts        # Internal types
│   ├── iconRegistry.ts     # Generated (gitignored)
│   ├── FeralIcon.tsx       # Public component
│   └── index.ts            # Generated (gitignored)
├── dist/                   # Build output (gitignored)
│   ├── index.js
│   ├── index.d.ts
│   ├── FeralIcon.js
│   └── ...
└── scripts/
    ├── generate.ts         # SVG → React converter
    └── optimize.ts         # SVGO configuration
```

### Package Exports

Only these are exported publicly:

```typescript
// From @purposeinplay/feral-icons
export { FeralIcon } from "./FeralIcon";
export type { FeralIconProps, IconName } from "./FeralIcon";
```

Everything else (individual icon components, base Icon, internal types) stays internal for the registry system.

## TypeScript Support

This library is written in TypeScript and includes full type definitions.

```tsx
import { FeralIcon, type FeralIconProps, type IconName } from "@purposeinplay/feral-icons";

// Type for icon props
const CustomIconWrapper: React.FC<FeralIconProps> = (props) => {
  return <FeralIcon {...props} />;
};

// Type for icon names (with autocomplete)
const iconName: IconName = "camera"; // TypeScript will suggest: "arrow-left" | "camera" | "user"

// Dynamic icon selection with type safety
function DynamicIcon({ name }: { name: IconName }) {
  return <FeralIcon name={name} size={24} />;
}
```

**Available Types:**

- `IconName` - Union type of all valid icon names (`"arrow-left" | "camera" | "user"`)
- `FeralIconProps` - Props interface for the FeralIcon component (`name`, `size`, `fill`, `className`, etc.)

## Using with Other Icon Libraries

If you need to use both Feral icons and other icon libraries (like `lucide-react`) in the same project, see [`DYNAMIC_ICON.md`](./DYNAMIC_ICON.md) for a complete implementation guide.

The `DYNAMIC_ICON.md` file provides:
- A production-ready `DynamicIcon` component that supports both Feral and Lucide icons
- Prefix-based icon selection (`lucide:Heart` vs `feral:check`)
- Auto-detect mode for seamless integration
- Full TypeScript support
- Usage examples and migration guides

**Note**: The `DynamicIcon` component is not exported by this package. It's a template you can copy into your project for advanced use cases.

## Browser Support

- Modern browsers with ES2020 support
- React 18.0.0 or higher
- React 19.x supported

## License

MIT

## Contributing

### Adding Icons

1. **Fork and clone** this repository
2. **Add SVG files** to `svgs/` directory
3. **Follow SVG requirements** (24x24, fill-based, kebab-case)
4. **Regenerate the pack and test**
   ```bash
   npm run generate   # Registers the new icon(s)
   npm run storybook # Verify in Storybook
   npm run build     # Full build when ready
   ```
5. **Commit and PR**
   ```bash
   git add svgs/your-icon.svg
   npm run commit  # Use Commitizen for standardized commits
   ```

### Commit Messages

This project uses [Commitizen](https://github.com/commitizen/cz-cli) for standardized commit messages following the [Conventional Commits](https://www.conventionalcommits.org/) specification.

**Using Commitizen (recommended):**

```bash
# Stage your changes
git add .

# Use commitizen to create the commit
npm run commit
```

This will prompt you with:
- **Type**: feat, fix, docs, style, refactor, perf, test, build, ci, chore
- **Scope**: Optional (e.g., storybook, icons, docs)
- **Description**: Short summary of changes
- **Body**: Optional detailed description
- **Breaking changes**: Optional breaking change notes
- **Issues**: Optional issue references

**Manual commits are also allowed** but should follow this format:
```bash
git commit -m "type(scope): description"
```

**Examples:**
```bash
feat(icons): add heart icon
fix(storybook): correct copy overlay behavior
docs(readme): update installation instructions
refactor(generate): simplify SVG optimization
```

### Guidelines

- One icon per file
- Use semantic names (`chevron-down`, not `icon-1`)
- Test in Storybook before submitting
- Include icon source/license if applicable
- Use `npm run commit` for consistent commit messages

## Troubleshooting

### Icon not showing up?

1. **Check the SVG file name**

   ```bash
   ls svgs/
   # Should see your-icon.svg
   ```

2. **Rebuild the library**

   ```bash
   npm run build
   ```

3. **Check TypeScript autocomplete**
   ```tsx
   <FeralIcon name="your-icon" />
   //              ^ Should autocomplete
   ```

### TypeScript errors?

If you see `Type '"my-icon"' is not assignable to type 'IconName'`:

1. Make sure you rebuilt: `npm run build`
2. Restart your TypeScript server (VS Code: `Cmd+Shift+P` → "Restart TS Server")
3. Check that `src/iconRegistry.ts` includes your icon

### Icons look wrong?

- Check SVG uses `fill` not `stroke`
- Verify 19x19 viewBox
- Remove hardcoded colors (SVGO will strip fill attributes automatically)

### Storybook not updating?

```bash
# Rebuild the library
npm run build

# Restart Storybook
npm run storybook
```