# ImageViewer

Image viewer with zoom, pan, rotate, flip and gallery navigation.

## Features

- Mouse wheel zoom
- Drag to pan
- Zoom presets (25%, 50%, 100%, 200%, 400%)
- Rotate 90°
- Flip horizontal/vertical
- Fullscreen dialog mode
- **Gallery mode** — multiple images with prev/next buttons
- **Keyboard shortcuts** — `+/-/0/r` for zoom/rotate, `←/→` for gallery navigation
- Checkerboard background for transparency
- Image dimensions display

## Installation

```tsx
import { ImageViewer } from '@djangocfg/ui-tools/image-viewer';
```

## Basic Usage

```tsx
// Single image — URL
<ImageViewer
  images={[{
    file: { name: 'photo.jpg', path: '/images/photo.jpg' },
    src: 'https://example.com/photo.jpg',
  }]}
/>

// Single image — raw bytes / base64
<ImageViewer
  images={[{
    file: { name: 'photo.png', path: 'unique-key', mimeType: 'image/png' },
    content: arrayBuffer,  // ArrayBuffer or base64 string
  }]}
/>

// Gallery
<ImageViewer
  images={[
    { file: { name: 'Photo 1', path: 'p1' }, src: 'https://example.com/1.jpg' },
    { file: { name: 'Photo 2', path: 'p2' }, src: 'https://example.com/2.jpg' },
    { file: { name: 'Photo 3', path: 'p3' }, src: 'https://example.com/3.jpg' },
  ]}
  initialIndex={0}
/>
```

## Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `images` | `ImageItem[]` | required | Array of images to display |
| `initialIndex` | `number` | `0` | Index of the image to show first |
| `inDialog` | `boolean` | `false` | Hide expand button (for nested usage) |

## Types

```typescript
interface ImageItem {
  file: ImageFile;
  src?: string;                    // URL (CDN / server)
  content?: string | ArrayBuffer;  // Raw bytes or base64 (memory / blob)
}

interface ImageFile {
  name: string;       // Display name
  path: string;       // File path (for state tracking / cache key)
  mimeType?: string;  // MIME type (e.g., 'image/png')
}
```

Both `src` and `content` are optional — supply whichever you have. When both are provided, `src` takes precedence (passed directly to `useImageLoading` as the direct URL).

### content support

| Value | Behaviour |
|-------|-----------|
| `data:image/...` string | used directly as `<img src>` |
| binary string | encoded → Blob URL via `TextEncoder` |
| `ArrayBuffer` | converted to Blob URL with caching |

## Gallery Navigation

When `images` has more than one item, prev/next buttons appear and keyboard navigation is enabled:

```tsx
<ImageViewer
  images={photos}
  initialIndex={2}  // open at 3rd photo
/>
```

| Key | Action |
|-----|--------|
| `←` | Previous image |
| `→` | Next image |

## Keyboard Shortcuts

| Key | Action |
|-----|--------|
| `+` / `=` | Zoom in |
| `-` | Zoom out |
| `0` | Reset to fit |
| `R` | Rotate 90° |
| `←` | Previous image (gallery) |
| `→` | Next image (gallery) |

## Toolbar Controls

The floating toolbar provides:

- **Zoom out** button
- **Zoom level** dropdown with presets
- **Zoom in** button
- **Fit to view** button
- **Flip horizontal** toggle
- **Flip vertical** toggle
- **Rotate 90°** button
- **Expand** fullscreen button

## Fullscreen Mode

Click the expand button to open in a fullscreen dialog. Gallery navigation works inside the dialog too.

```tsx
// Fullscreen available by default
<ImageViewer images={images} />

// Disable expand button when embedding in your own dialog
<Dialog>
  <ImageViewer images={images} inDialog />
</Dialog>
```

## Used as the chat attachment lightbox

The [Chat tool](../../chat/README.md) uses `LazyImageViewer` as the **default**
lightbox for image attachments. When a host wires no `onAttachmentOpen` /
`onClick`, clicking an image attachment opens this viewer in a dialog
(`inDialog`), seeded with all of that message's image attachments so prev/next
gallery navigation works across them, opened at the clicked `initialIndex`. See
`chat/messages/Attachments.tsx` (`ImageLightboxProvider`).

## Styling

The component fills its container. Wrap in a sized element:

```tsx
<div className="w-full h-[500px]">
  <ImageViewer images={images} />
</div>
```

## Architecture

```
ImageViewer/
├── index.ts                    # Public API exports
├── types.ts                    # Type definitions
├── ImageViewer.story.tsx       # Playground stories
├── README.md
├── components/
│   ├── index.ts
│   ├── ImageViewer.tsx         # Main viewer component
│   ├── ImageToolbar.tsx        # Zoom/rotate/flip controls
│   └── ImageInfo.tsx           # Dimensions display
├── hooks/
│   ├── index.ts
│   ├── useImageLoading.ts      # Blob URL & LQIP management
│   └── useImageTransform.ts    # Rotation/flip state
└── utils/
    ├── index.ts
    ├── constants.ts            # Size limits, zoom presets
    └── lqip.ts                 # Low-Quality Image Placeholder
```

## Dependencies

- `react-zoom-pan-pinch` — zoom and pan
- `lucide-react` — icons
- `@djangocfg/ui-core` — UI components (Button, Dialog, etc.)
- `@djangocfg/ui-core/hooks` — `useHotkey` for keyboard navigation
