ImageAttachment Component
Overview
The ImageAttachment component provides a user interface for managing image attachments on transactions. It displays images in a thumbnail grid with add/delete functionality and enforces image count limits.
Features
- Image Thumbnail Grid: Displays uploaded images in a 3-column grid layout
- Add Images: File input for selecting images from device (supports JPEG, PNG, HEIC)
- Delete Images: Remove button overlay on each thumbnail
- Image Count Display: Shows current count vs maximum (e.g., "2 / 9")
- Limit Warning: Displays warning when approaching the 9-image limit (at 7+ images)
- Image Preview: Click on thumbnails to preview full-size images
- Disabled State: Supports read-only mode
Requirements Validated
This component validates the following requirements from the accounting-feature-upgrade spec:
- 4.1: Transaction form displays image attachment entry button
- 4.2: Opens image selector, supports album selection or camera
- 4.5: Shows image thumbnail preview after upload
- 4.7: Delete button removes image attachment
- 4.9: Limits single transaction to max 9 images
- 4.12: Shows prompt when exceeding limit
Usage
import { ImageAttachment } from './components/transaction/ImageAttachment';
import type { TransactionImage } from './types';
function TransactionForm() {
const [images, setImages] = useState<TransactionImage[]>([]);
const handleAddImage = async (file: File) => {
try {
const uploadedImage = await uploadImage(transactionId, file, 'medium');
setImages([...images, uploadedImage]);
} catch (error) {
console.error('Failed to upload image:', error);
}
};
const handleRemoveImage = async (imageId: number) => {
try {
await deleteImage(transactionId, imageId);
setImages(images.filter(img => img.id !== imageId));
} catch (error) {
console.error('Failed to delete image:', error);
}
};
const handlePreviewImage = (index: number) => {
// Open image preview modal/fullscreen
setPreviewIndex(index);
setShowPreview(true);
};
return (
<ImageAttachment
images={images}
onAdd={handleAddImage}
onRemove={handleRemoveImage}
onPreview={handlePreviewImage}
compressionLevel="medium"
/>
);
}
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
images |
TransactionImage[] |
Yes | - | Array of uploaded images |
onAdd |
(file: File) => void |
Yes | - | Callback when user selects a file to upload |
onRemove |
(imageId: number) => void |
Yes | - | Callback when user clicks delete button |
onPreview |
(index: number) => void |
Yes | - | Callback when user clicks on an image thumbnail |
compressionLevel |
'low' | 'medium' | 'high' |
No | 'medium' |
Image compression level (for display purposes) |
disabled |
boolean |
No | false |
Disables add/delete/preview interactions |
className |
string |
No | '' |
Additional CSS class names |
Image Constraints
The component enforces the following constraints (defined in imageService.ts):
- Maximum Images: 9 images per transaction
- Maximum File Size: 10MB per image
- Allowed Formats: JPEG, PNG, HEIC
Behavior
Adding Images
- User clicks the "添加图片" (Add Image) button
- File input opens with
accept="image/jpeg,image/png,image/heic"andmultipleattributes - User selects one or more images
- Component validates:
- Current count + new count ≤ 9
- If validation fails, shows alert: "最多添加9张图片"
- For each valid file, calls
onAdd(file)callback - Parent component handles upload and updates
imagesprop
Deleting Images
- User hovers over an image thumbnail
- Delete button (×) appears in top-right corner
- User clicks delete button
- Component calls
onRemove(imageId)callback - Parent component handles deletion and updates
imagesprop
Previewing Images
- User clicks on an image thumbnail
- Component calls
onPreview(index)callback with the image index - Parent component handles showing full-screen preview
Warning Display
- When
images.length >= 7, displays warning: "接近图片数量限制" (Approaching image count limit) - Warning appears in orange color with alert icon
- Helps users avoid hitting the hard limit
Disabled State
When disabled={true}:
- Add button is hidden
- Delete buttons are hidden
- Preview clicks are ignored
- Component is in read-only mode
Testing
The component has comprehensive test coverage:
Unit Tests (21 tests)
Located in ImageAttachment.test.tsx:
- Image attachment entry and selection (3 tests)
- Image thumbnail preview (3 tests)
- Delete button functionality (4 tests)
- Image count limit (3 tests)
- Limit exceeded prompt (2 tests)
- Disabled state (2 tests)
- Edge cases (3 tests)
- Custom className (1 test)
Property-Based Tests (6 tests)
Located in ImageAttachment.property.test.tsx:
- Property 8: Image deletion consistency (validates Requirement 4.7)
- Image count display consistency (validates Requirement 4.9)
- Add button visibility based on count (validates Requirements 4.9, 4.12)
- Warning display when approaching limit (validates Requirements 4.9, 4.12)
- Delete button count matches image count (validates Requirement 4.7)
- Preview callback receives correct index (validates Requirement 4.6)
All tests run 100 iterations to verify properties hold across diverse inputs.
Styling
The component uses CSS modules with the following key classes:
.image-attachment: Main container.image-attachment__grid: 3-column grid layout.image-attachment__item: Individual image thumbnail container.image-attachment__thumbnail: Image element.image-attachment__delete: Delete button overlay.image-attachment__add: Add button.image-attachment__info: Info section with count and warning.image-attachment__count: Image count display.image-attachment__warning: Warning message
Accessibility
- Add button has
aria-label="添加图片" - Delete buttons have
aria-label="删除图片" - Images have
altattributes with file names - Keyboard navigation supported (buttons are focusable)
Related Components
ImagePreview: Full-screen image preview component (task 11.2)TransactionForm: Parent form that uses this component
Related Services
imageService.ts: Handles image upload, compression, and validationIMAGE_CONSTRAINTS: Defines max images, max size, allowed typesCOMPRESSION_SETTINGS: Defines compression levels
Future Enhancements
- Drag-and-drop file upload
- Image reordering via drag-and-drop
- Batch delete functionality
- Image cropping/editing
- Progress indicators during upload
- Thumbnail lazy loading for performance