<!-- source-hash: 3916654dd739ee2e8657ff84693270c8 -->
Storybook stories for the `ImageUploader` UI component, covering all visual states, prop variants, and an interactive upload simulation.

## Key Components

| Export | Description |
|--------|-------------|
| `Default` | Bare uploader with no initial value |
| `AllStates` | Side-by-side grid of all four Figma states: default, hover, action, uploaded |
| `WithFieldLabel` | Uploader with an accessible label above the dropzone |
| `WithPreview` | Pre-populated image preview with remove action |
| `PreviewWithoutRemove` | Preview state with `onRemove` omitted |
| `SquareAspectRatio` | Custom `1 / 1` aspect ratio at 320px width |
| `ObjectFitContain` | Image preview using `contain` fit instead of the default `cover` |
| `Loading` | Spinner overlay on an existing preview |
| `LoadingEmpty` | Spinner overlay on the empty dropzone |
| `Disabled` / `DisabledWithPreview` | Non-interactive states |
| `WithError` | Field-level validation error message |
| `CustomCopy` | Custom label, description, accepted MIME types, and max file size |
| `Interactive` | Fully wired story using `useState` + `FileReader` to simulate real upload flow |

## Usage Example

```typescript
// Interactive story pattern — wire up local state to preview uploads
import { useState } from 'react';
import { ImageUploader } from '../components/ui/image-uploader';

function Demo() {
  const [preview, setPreview] = useState<string | undefined>();

  const handleChange = (file: File) => {
    const reader = new FileReader();
    reader.onload = () => setPreview(reader.result as string);
    reader.readAsDataURL(file);
  };

  return (
    <ImageUploader
      value={preview}
      onChange={handleChange}
      onRemove={() => setPreview(undefined)}
      fieldLabel="Cover image"
    />
  );
}
```

> `ImageUploader` is intentionally decoupled from upload logic — it calls `onChange(file)` and the consumer owns the upload, preview URL management, and remove behaviour.