feat(ui): implement comprehensive toast notification system

Added a complete toast notification system with:
- Toast store using Zustand for state management
- Toast component with 4 types: success, error, warning, info
- Animated slide-in/slide-out transitions
- Auto-dismiss after configurable duration
- Close button on each toast
- Utility functions for easy access (toast.success(), toast.error(), etc.)

Integrated toast notifications into file operations:
- Success notifications for: open image, open project, export image, save project
- Error notifications for: failed operations
- Warning notifications for: unsupported file types

UI Features:
- Stacks toasts in top-right corner
- Color-coded by type with icons (CheckCircle, AlertCircle, AlertTriangle, Info)
- Accessible with ARIA attributes
- Smooth animations using custom CSS keyframes

This provides immediate user feedback for all major operations throughout
the application.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-21 15:42:50 +01:00
parent 2f51f11263
commit 3ad7dbf314
8 changed files with 209 additions and 23 deletions

View File

@@ -11,6 +11,7 @@ import {
isImageFile,
isProjectFile,
} from '@/lib/file-utils';
import { toast } from '@/lib/toast-utils';
import type { Layer } from '@/types';
export function useFileOperations() {
@@ -63,9 +64,11 @@ export function useFileOperations() {
ctx.drawImage(img, 0, 0);
}
}
toast.success(`Opened ${file.name}`);
} catch (error) {
console.error('Failed to open image:', error);
alert('Failed to open image file');
toast.error('Failed to open image file');
}
},
[clearLayers, clearHistory, setDimensions, createLayer]
@@ -106,9 +109,11 @@ export function useFileOperations() {
}
}
}
toast.success(`Opened project ${file.name}`);
} catch (error) {
console.error('Failed to open project:', error);
alert('Failed to open project file');
toast.error('Failed to open project file');
}
},
[clearLayers, clearHistory, setDimensions, createLayer]
@@ -119,29 +124,38 @@ export function useFileOperations() {
*/
const exportImage = useCallback(
async (format: 'png' | 'jpeg' | 'webp', quality: number, filename: string) => {
// Create temporary canvas with all layers
const tempCanvas = document.createElement('canvas');
tempCanvas.width = width;
tempCanvas.height = height;
const ctx = tempCanvas.getContext('2d');
try {
// Create temporary canvas with all layers
const tempCanvas = document.createElement('canvas');
tempCanvas.width = width;
tempCanvas.height = height;
const ctx = tempCanvas.getContext('2d');
if (!ctx) return;
if (!ctx) {
toast.error('Failed to create export canvas');
return;
}
// Draw all visible layers
layers
.filter((layer) => layer.visible && layer.canvas)
.sort((a, b) => a.order - b.order)
.forEach((layer) => {
if (!layer.canvas) return;
ctx.globalAlpha = layer.opacity;
ctx.globalCompositeOperation = layer.blendMode as GlobalCompositeOperation;
ctx.drawImage(layer.canvas, layer.x, layer.y);
});
// Draw all visible layers
layers
.filter((layer) => layer.visible && layer.canvas)
.sort((a, b) => a.order - b.order)
.forEach((layer) => {
if (!layer.canvas) return;
ctx.globalAlpha = layer.opacity;
ctx.globalCompositeOperation = layer.blendMode as GlobalCompositeOperation;
ctx.drawImage(layer.canvas, layer.x, layer.y);
});
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = 'source-over';
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = 'source-over';
await exportCanvasAsImage(tempCanvas, format, quality, filename);
await exportCanvasAsImage(tempCanvas, format, quality, filename);
toast.success(`Exported ${filename}.${format}`);
} catch (error) {
console.error('Failed to export image:', error);
toast.error('Failed to export image');
}
},
[layers, width, height]
);
@@ -151,7 +165,13 @@ export function useFileOperations() {
*/
const saveProject = useCallback(
async (filename: string) => {
await exportProject(layers, width, height, filename);
try {
await exportProject(layers, width, height, filename);
toast.success(`Saved project ${filename}.json`);
} catch (error) {
console.error('Failed to save project:', error);
toast.error('Failed to save project');
}
},
[layers, width, height]
);
@@ -166,7 +186,7 @@ export function useFileOperations() {
} else if (isImageFile(file)) {
await openImage(file);
} else {
alert('Unsupported file type');
toast.warning('Unsupported file type');
}
},
[openProject, openImage]