import React, { useState, useCallback, lazy, Suspense } from 'react'; // PERF: Lazy load react-easy-crop (composant volumineux ~100KB) // Mock Cropper since react-easy-crop is missing const Cropper = lazy(() => Promise.resolve({ default: (_props: any) => (
Cropper Mock
), }), ); import { Button } from '@/components/ui/button'; import { X, ZoomIn, RotateCw, Check } from 'lucide-react'; import { LoadingSpinner } from './loading-spinner'; /** * ImageCropperProps - Propriétés du composant ImageCropper * * @interface ImageCropperProps */ interface ImageCropperProps { /** * URL ou source de l'image à recadrer */ imageSrc: string; /** * Ratio d'aspect du recadrage * * - `1`: Ratio carré (pour avatars) * - `3`: Ratio paysage (pour bannières) * * @example * ```tsx * * ``` */ aspectRatio: number; /** * Fonction appelée pour annuler le recadrage */ onCancel: () => void; /** * Fonction appelée lorsque le recadrage est terminé * * @param {any} croppedAreaPixels - Zone recadrée en pixels */ onCropComplete: (croppedAreaPixels: any) => void; /** * Si `true`, utilise un recadrage circulaire (pour avatars) * * @default false */ circularCrop?: boolean; } /** * ImageCropper - Composant de recadrage d'image avec design system Kodo * * Composant modal pour recadrer des images avec support pour : * - Zoom (1x à 3x) * - Rotation (0° à 360°) * - Recadrage circulaire ou rectangulaire * - Ratio d'aspect personnalisable * * @example * ```tsx * // Recadrage d'avatar (carré, circulaire) * setShowCropper(false)} * onCropComplete={(area) => handleCrop(area)} * /> * ``` * * @example * ```tsx * // Recadrage de bannière (paysage, rectangulaire) * setShowCropper(false)} * onCropComplete={(area) => handleCrop(area)} * /> * ``` * * @component * @param {ImageCropperProps} props - Propriétés du composant * @returns {JSX.Element} Modal de recadrage avec contrôles */ export const ImageCropper: React.FC = ({ imageSrc, aspectRatio, onCancel, onCropComplete, circularCrop = false, }) => { const [crop, setCrop] = useState({ x: 0, y: 0 }); const [zoom, setZoom] = useState(1); const [rotation, setRotation] = useState(0); const [croppedAreaPixels, setCroppedAreaPixels] = useState(null); const onCropChange = (crop: { x: number; y: number }) => { setCrop(crop); }; const onCropCompleteHandler = useCallback( (_croppedArea: any, croppedAreaPixels: any) => { setCroppedAreaPixels(croppedAreaPixels); }, [], ); const handleSave = () => { onCropComplete(croppedAreaPixels); }; return (
{/* Header */}

Edit Image

{/* Cropper Area */}
} >
{/* Controls */}
Zoom setZoom(Number(e.target.value))} className="flex-1 h-1 bg-muted rounded-lg appearance-none cursor-pointer [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:bg-primary [&::-webkit-slider-thumb]:rounded-full" />
Rotate setRotation(Number(e.target.value))} className="flex-1 h-1 bg-muted rounded-lg appearance-none cursor-pointer [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:bg-primary [&::-webkit-slider-thumb]:rounded-full" />
); };