// Adapted from Kibo UI (MIT), with owned React/CSS APIs. See THIRD_PARTY_NOTICES.md. "use client"; import { useRef, useState, type ComponentProps } from "react"; import ReactCrop, { type Crop, type PercentCrop } from "./_ynrlib/crop.js"; export type ImageCropProps = Omit, "onError"> & { src: string; alt: string; aspect?: number; onCrop?: (blob: Blob) => void; onError?: (error: Error) => void; disabled?: boolean; }; export function ImageCrop({ src, alt, aspect, onCrop, onError, disabled, className = "", ...props }: ImageCropProps) { const ref = useRef(null); const [crop, setCrop] = useState(); const [completed, setCompleted] = useState(); const [status, setStatus] = useState(""); const [busy, setBusy] = useState(false); const save = async () => { if ( !ref.current?.complete || !ref.current.naturalWidth || !completed?.width || !completed.height ) return; setBusy(true); try { const image = ref.current; const sx = image.naturalWidth / 100, sy = image.naturalHeight / 100; const canvas = document.createElement("canvas"); canvas.width = Math.max(1, Math.round(completed.width * sx)); canvas.height = Math.max(1, Math.round(completed.height * sy)); const ctx = canvas.getContext("2d"); if (!ctx) throw new Error("Image processing unavailable."); ctx.drawImage( image, completed.x * sx, completed.y * sy, completed.width * sx, completed.height * sy, 0, 0, canvas.width, canvas.height, ); const blob = await new Promise((resolve, reject) => canvas.toBlob( (result) => result ? resolve(result) : reject(new Error("Could not encode image.")), "image/png", ), ); onCrop?.(blob); setStatus(`Cropped image: ${canvas.width} Ă— ${canvas.height}`); } catch (error) { const failure = error instanceof Error ? error : new Error("Crop failed."); setStatus(failure.message); onError?.(failure); } finally { setBusy(false); } }; return (
setCrop(percent)} onComplete={(_, percent) => setCompleted(percent)} > {alt} { const image = event.currentTarget; const ratio = aspect && Number.isFinite(aspect) && aspect > 0 ? aspect : undefined; const height = ratio ? Math.min( 80, (80 * image.naturalWidth) / (ratio * image.naturalHeight), ) : 80; const width = ratio ? (height * ratio * image.naturalHeight) / image.naturalWidth : 80; const initial: PercentCrop = { unit: "%", x: (100 - width) / 2, y: (100 - height) / 2, width, height, }; setCrop(initial); setCompleted(initial); }} onError={() => { setStatus("Image unavailable."); setCompleted(undefined); }} /> {status}
); }