import type { Meta, StoryObj } from '@storybook/nextjs-vite';
import { useState } from 'react';
import { ImageUploader } from '../components/ui/image-uploader';
const SAMPLE_IMAGE =
'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1200&q=80';
const meta = {
title: 'UI/ImageUploader',
component: ImageUploader,
parameters: {
layout: 'padded',
docs: {
description: {
component:
'Image dropzone with preview, replace, and remove actions. Decoupled from upload logic — the consumer handles uploading via `onChange(file)`.',
},
},
},
tags: ['autodocs'],
decorators: [
(Story) => (
),
],
} satisfies Meta;
export default meta;
type Story = StoryObj;
export const Default: Story = {
args: {
onChange: () => {},
},
};
export const AllStates: Story = {
args: {
onChange: () => {},
},
parameters: {
docs: {
description: {
story: 'All four states from Figma (default, hover, action, uploaded). Hover/action states are static visual representations driven by CSS classes — interact with the Default story to see real hover/drag behavior.',
},
},
},
decorators: [
(Story) => (
),
],
render: () => (
uploaded
{}} onRemove={() => {}} />
),
};
export const WithFieldLabel: Story = {
args: {
fieldLabel: 'Cover image',
onChange: () => {},
},
};
export const WithPreview: Story = {
args: {
value: SAMPLE_IMAGE,
onChange: () => {},
onRemove: () => {},
},
};
export const PreviewWithoutRemove: Story = {
args: {
value: SAMPLE_IMAGE,
onChange: () => {},
},
};
export const SquareAspectRatio: Story = {
args: {
value: SAMPLE_IMAGE,
aspectRatio: '1 / 1',
onChange: () => {},
onRemove: () => {},
},
decorators: [
(Story) => (
),
],
};
export const ObjectFitContain: Story = {
args: {
value: SAMPLE_IMAGE,
objectFit: 'contain',
onChange: () => {},
onRemove: () => {},
},
};
export const Loading: Story = {
args: {
value: SAMPLE_IMAGE,
loading: true,
onChange: () => {},
onRemove: () => {},
},
};
export const LoadingEmpty: Story = {
args: {
loading: true,
onChange: () => {},
},
};
export const Disabled: Story = {
args: {
disabled: true,
onChange: () => {},
},
};
export const DisabledWithPreview: Story = {
args: {
value: SAMPLE_IMAGE,
disabled: true,
onChange: () => {},
onRemove: () => {},
},
};
export const WithError: Story = {
args: {
fieldLabel: 'Cover image',
error: 'Image is required',
onChange: () => {},
},
};
export const CustomCopy: Story = {
args: {
label: 'Drop your logo here',
description: 'PNG or SVG, up to 2MB',
accept: 'image/png,image/svg+xml',
maxSize: 2 * 1024 * 1024,
onChange: () => {},
},
};
export const Interactive: Story = {
args: {
onChange: () => {},
},
render: function InteractiveImageUploader() {
const [preview, setPreview] = useState();
const [fileName, setFileName] = useState();
const handleChange = (file: File) => {
setFileName(file.name);
const reader = new FileReader();
reader.onload = () => setPreview(reader.result as string);
reader.readAsDataURL(file);
};
const handleRemove = () => {
setPreview(undefined);
setFileName(undefined);
};
return (
{fileName ? `Selected: ${fileName}` : 'No image selected'}
);
},
};