<!-- source-hash: 31fb4dc0d85de6bd77dd4c3210fe1592 -->
Reusable React component for interactive image cropping, supporting drag/resize, zoom, rotation, circular crop mode, and PNG export via canvas.

## Key Components

### `ImageCropperResult`
Return type from a confirmed crop operation.

| Field | Type | Description |
|-------|------|-------------|
| `dataUrl` | `string` | Cropped PNG as a data URI |
| `blob` | `Blob` | Corresponding PNG Blob |

### `ImageCropperProps`

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `src` | `string` | — | Source image URL or data URI |
| `onComplete` | `(result) => void` | — | Fired when user confirms crop |
| `onCancel` | `() => void` | — | Fired when user cancels |
| `aspectRatio` | `number` | free-form | Width/height ratio |
| `circular` | `boolean` | `false` | Circular overlay for avatars |
| `maxSizePx` | `number` | `512` | Max exported image dimension |
| `className` | `string` | — | Wrapper class override |

### `ImageCropper`
Main component. Internally manages crop position, zoom (1–3×), and rotation (0–360°). On confirm, renders the crop to an off-screen `<canvas>`, scales it to `maxSizePx`, optionally clips to a circle, then resolves a PNG `Blob` + data URL.

**Keyboard shortcuts:** `Enter` confirms, `Escape` cancels.

## Usage Example

```typescript
import { ImageCropper, ImageCropperResult } from "@/components/image-cropper"

function AvatarEditor({ imageUrl }: { imageUrl: string }) {
  const handleCropComplete = (result: ImageCropperResult) => {
    // Upload the blob or set preview via dataUrl
    console.log("PNG data URL:", result.dataUrl)
    uploadAvatar(result.blob)
  }

  return (
    <ImageCropper
      src={imageUrl}
      aspectRatio={1}
      circular
      maxSizePx={256}
      onComplete={handleCropComplete}
      onCancel={() => console.log("Cancelled")}
      className="max-w-lg"
    />
  )
}
```