npm package: @sudoh/animated-icons
Description: Playful, spring-animated SVG icons for React
Latest version: 0.1.3

# @sudoh/animated-icons

Playful, spring-animated SVG icons for React. Each icon responds to hover (or click) with a satisfying spring-physics "boop" animation powered by [react-spring](https://www.react-spring.dev/). Respects `prefers-reduced-motion` out of the box.

[See them in action →](https://nichole.is/icons)

## Install

```bash
npm install @sudoh/animated-icons react react-dom react-spring
```

`react`, `react-dom`, and `react-spring` are peer dependencies — bring your own versions (React 18+ and react-spring 9+ are supported).

## Quick start

```tsx
import { IconSearch, IconMoon, IconGitHub } from '@sudoh/animated-icons';

function App() {
  return (
    <nav>
      <IconSearch />
      <IconMoon size={24} />
      <IconGitHub size={32} />
    </nav>
  );
}
```

Hover over any icon and it springs to life. That's it.

## Props

Every icon accepts these props:

```tsx
<IconSearch
  size={24}        // icon size in px (default: 30)
  boopTiming={200} // how long the boop stays active in ms
/>
```

Icons are wrapped in a `<span>` so they also accept standard HTML span attributes like `className`, `style`, `onClick`, etc.

## Examples

### Basic usage

```tsx
import { IconArrowRight } from '@sudoh/animated-icons';

function ReadMoreLink() {
  return (
    <a href="/blog" style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
      Read more <IconArrowRight size={20} />
    </a>
  );
}
```

### Color mode toggle

`IconColorMode` is a special icon that toggles between sun and moon on click:

```tsx
import { IconColorMode } from '@sudoh/animated-icons';

function Header() {
  return (
    <button aria-label="Toggle color mode" style={{ background: 'none', border: 'none' }}>
      <IconColorMode size={28} />
    </button>
  );
}
```

### Social links

```tsx
import { IconGitHub, IconLinkedIn, IconRSS } from '@sudoh/animated-icons';

function SocialLinks() {
  return (
    <div style={{ display: 'flex', gap: 16 }}>
      <a href="https://github.com" aria-label="GitHub">
        <IconGitHub size={24} />
      </a>
      <a href="https://linkedin.com" aria-label="LinkedIn">
        <IconLinkedIn size={24} />
      </a>
      <a href="/rss.xml" aria-label="RSS Feed">
        <IconRSS size={24} />
      </a>
    </div>
  );
}
```

## Building custom icons

The library exports primitives so you can create your own animated icons with the same boop behavior:

```tsx
import { createIcon, useAnimatedIcon, BaseSvg } from '@sudoh/animated-icons';
import type { AnimatedIconProps } from '@sudoh/animated-icons';
import { animated } from 'react-spring';

function IconHeart({ size, isBooped = false }: AnimatedIconProps) {
  const [heartSpring] = useAnimatedIcon(isBooped, [
    {
      from: { transform: 'scale(1)' },
      to: { transform: 'scale(1.2)' },
      springiness: { tension: 300, friction: 10 },
    },
  ]);

  return (
    <BaseSvg size={size} style={heartSpring}>
      <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78L12 21.23l8.84-8.84a5.5 5.5 0 0 0 0-7.78z" />
    </BaseSvg>
  );
}

export default createIcon(IconHeart, 'Heart');
```

### How it works

- `useAnimatedIcon(isBooped, springs)` — takes a boolean trigger and an array of spring definitions. Returns an array of animated style objects, one per spring.
- `BaseSvg` — a pre-configured `animated.svg` with sensible defaults (24×24 viewBox, round stroke caps).
- `createIcon(Component, altText)` — wraps your icon component with `IconWrapper`, which handles hover/click detection and the boop timing.

Each spring definition looks like:

```ts
{
  from: { transform: 'rotate(0deg)' },   // resting state
  to: { transform: 'rotate(-28deg)' },   // booped state
  springiness: { tension: 300, friction: 12 }, // optional spring config
  delay: 50,                              // optional stagger delay in ms
}
```

## Accessibility

- All icons automatically respect `prefers-reduced-motion` — animations are disabled when the user has requested reduced motion.
- Icons are wrapped with alt text passed to `createIcon` for screen reader context.
- Every component is marked `'use client'` for Next.js App Router compatibility.

## License

MIT