npm package: @madebywild/wild-rough-notation
Description: Create and animate hand-drawn annotations in React
Author: we are wild
Latest version: 1.1.0

# wild-rough-notation

Create and animate hand-drawn annotations in React. This package combines two great libraries: [rough-notation](https://github.com/rough-stuff/rough-notation) and [react-rough-notation](https://github.com/linkstrifer/react-rough-notation). It was extended to support custom annotations and we added events.

## Installation

```bash
npm install @madebywild/wild-rough-notation
```

## Usage

### Basic Example

```tsx
import { useState } from 'react'
import { RoughNotation } from '@madebywild/wild-rough-notation'

function App() {
  const [show, setShow] = useState(true)

  return (
    <p>
      This is a <RoughNotation type="highlight" color="yellow" show={show}>highlighted</RoughNotation> word.
    </p>
  )
}
```

### Annotation Types

- `underline` - Underline the element
- `underline-jiggly` - Wavy underline with non-uniform frequency
- `underline-zigzag` - Zigzag underline pattern
- `underline-tick` - Underline with a tick mark at the end
- `box` - Draw a box around the element
- `circle` - Draw a circle around the element
- `highlight` - Highlight the element (like a marker)
- `strike-through` - Strike through the element
- `crossed-off` - Cross off the element with an X
- `bracket` - Draw brackets around the element
- `custom-path` - Render a custom SVG path
- `custom` - Render custom React content (see Custom Annotations below)
- `custom:*` - Named custom types (e.g. `custom:label`, `custom:badge`) for multiple distinct custom annotations

### Group Animations

Use `RoughNotationGroup` to animate multiple annotations in sequence:

```tsx
import { useState } from 'react'
import { RoughNotation, RoughNotationGroup } from '@madebywild/wild-rough-notation'

function App() {
  const [show, setShow] = useState(false)

  return (
    <RoughNotationGroup show={show}>
      <p>
        <RoughNotation type="underline" color="red" order={1}>
          First
        </RoughNotation>{' '}
        then{' '}
        <RoughNotation type="circle" color="blue" order={2}>
          second
        </RoughNotation>{' '}
        and finally{' '}
        <RoughNotation type="box" color="green" order={3}>
          third
        </RoughNotation>
      </p>
    </RoughNotationGroup>
  )
}
```

## Props

### RoughNotation

| Prop                  | Type                                                             | Default          | Description                                             |
| --------------------- | ---------------------------------------------------------------- | ---------------- | ------------------------------------------------------- |
| `type`                | `string`                                                         | `'underline'`    | Type of annotation                                      |
| `show`                | `boolean`                                                        | `false`          | Whether to show the annotation                          |
| `animate`             | `boolean`                                                        | `true`           | Whether to animate the annotation                       |
| `animationDelay`      | `number`                                                         | `0`              | Delay before animation starts (ms)                      |
| `animationDuration`   | `number`                                                         | `800`            | Duration of the animation (ms)                          |
| `color`               | `string`                                                         | `'currentColor'` | Color of the annotation                                 |
| `strokeWidth`         | `number`                                                         | `1`              | Width of the annotation stroke                          |
| `padding`             | `number \| [number, number] \| [number, number, number, number]` | `5`              | Padding around the element                              |
| `iterations`          | `number`                                                         | `2`              | Number of iterations for the rough effect               |
| `multiline`           | `boolean`                                                        | `false`          | Handle multiline text                                   |
| `brackets`            | `'left' \| 'right' \| 'top' \| 'bottom' \| Array`                | `'right'`        | Bracket position(s) for bracket type                    |
| `customElement`       | `string`                                                         | `'span'`         | HTML element to wrap children                           |
| `order`               | `number`                                                         | -                | Order in group animation                                |
| `getAnnotationObject` | `(annotation) => void`                                           | -                | Callback to get the annotation instance                 |
| `customPath`          | `string`                                                         | -                | SVG path string for `custom-path` type                  |
| `customPathViewBox`   | `{ width: number; height: number }`                              | -                | ViewBox dimensions for scaling custom paths             |
| `customRender`        | `ReactNode`                                                      | -                | Custom content to render for `custom` / `custom:*` types |
| `onAnimationStart`    | `() => void`                                                     | -                | Callback when animation starts                          |
| `onAnimationComplete` | `() => void`                                                     | -                | Callback when animation completes                       |
| `onRefresh`           | `() => void`                                                     | -                | Callback when annotation is refreshed (e.g., on resize) |

### RoughNotationGroup

| Prop       | Type        | Default | Description                                        |
| ---------- | ----------- | ------- | -------------------------------------------------- |
| `show`     | `boolean`   | `false` | Whether to show all annotations in sequence        |
| `children` | `ReactNode` | -       | Child elements containing RoughNotation components |

### Custom Annotations

The `custom` type allows you to render any React content as an annotation. This is useful for custom graphics, icons, or complex decorations that can't be achieved with the built-in types.

```tsx
import { RoughNotation } from '@madebywild/wild-rough-notation'

function App() {
  return (
    <RoughNotation
      type="custom"
      show={true}
      customRender={
        <div className="absolute inset-0 flex items-center justify-center">
          <svg viewBox="0 0 100 100" className="w-full h-full">
            <circle cx="50" cy="50" r="40" fill="none" stroke="red" strokeWidth="2" />
          </svg>
        </div>
      }
    >
      Custom annotation
    </RoughNotation>
  )
}
```

The `customRender` content is rendered inside an absolutely positioned container that matches the dimensions of the annotated element. The annotation automatically handles browser resize events.

#### Named Custom Types

When you need multiple distinct custom annotations, use the `custom:` prefix with any name. This avoids hardcoding a fixed set of custom types and lets you distinguish between them in your application logic.

```tsx
import { RoughNotation, isCustomType } from '@madebywild/wild-rough-notation'

function App() {
  return (
    <>
      <RoughNotation
        type="custom:label"
        show={true}
        customRender={<LabelAnnotation text="New!" />}
      >
        Feature
      </RoughNotation>

      <RoughNotation
        type="custom:badge"
        show={true}
        customRender={<BadgeAnnotation icon="star" />}
      >
        Premium
      </RoughNotation>
    </>
  )
}
```

Any type matching `custom:*` behaves identically to `custom` -- it renders nothing in the core SVG layer and delegates entirely to `customRender`. The type name is available for you to branch on in your own code.

The exported `isCustomType()` helper returns `true` for both `'custom'` and any `'custom:*'` type:

```tsx
import { isCustomType } from '@madebywild/wild-rough-notation'

isCustomType('custom')       // true
isCustomType('custom:label') // true
isCustomType('underline')    // false
```

### Custom Path Annotations

The `custom-path` type lets you use an SVG path string rendered with the rough/hand-drawn style:

```tsx
<RoughNotation
  type="custom-path"
  show={true}
  customPath="M0.166992 21.9907L183.167 1.49066L163.667 13.4907"
  customPathViewBox={{ width: 184, height: 24 }}
  color="red"
>
  Custom path underline
</RoughNotation>
```

## Advanced Usage

For custom implementations, you can import the core utilities:

```tsx
import {
  annotate,
  annotationGroup,
  renderAnnotation,
  isCustomType,
  type RoughAnnotationConfig,
  type RoughAnnotation,
} from '@madebywild/wild-rough-notation'

// Create an annotation manually
const element = document.getElementById('my-element')
const annotation = annotate(element, {
  type: 'underline',
  color: 'red',
  animationDuration: 1000,
})

annotation.show()
```

## Publishing

This package uses [Changesets](https://github.com/changesets/changesets) for version management and publishing. The package is configured as a **private package** and will be published to npm with restricted access.

### Release Workflow

To release a new version, follow these steps:

```bash
# 1. Create a changeset describing your changes
npm run changeset

npm run release

this will ask for your npm OTP code and then publish the package to npm.
```

### Important Notes

- **Private Package**: This package is configured with `"publishConfig": { "access": "restricted" }`, which means it will be published as a private package on npm. Only users/organizations with access can install it.
- **2FA Required**: If your npm account has two-factor authentication enabled, you'll need to provide a one-time password (OTP) when publishing. Get the code from your authenticator app and pass it with the `--otp` flag.
- **Version Bumping**: The `npm run version` command automatically bumps the version based on your changeset (patch, minor, or major).

## License

MIT