Componente moderno e reutilizável para upload, recorte, zoom e rotação de imagens, construído com Next.js, TypeScript, React Hook Form, Zod e react-easy-crop. Fornece uma experiência de edição em modal com preview e suporte a dimensões de saída exatas.
- 🖼️ Upload de imagem com validação de tamanho
- ✂️ Editor em modal com recorte interativo
- 🔍 Zoom e ↻ rotação controláveis
- 🟢 Suporte a preview e botão para remover imagem
- 🧩 Integração simples com React Hook Form + Zod
- 📐 Saída em dimensões exatas (ex.: 800x800) mantendo aspecto
- 🟣 Suporte a formato circular (visual) ou retangular
- ⚙️ Configurável: aspecto, grid, limites de zoom e rótulos
- ♿ Acessível e responsivo
src/
├── app/
│ ├── layout.tsx
│ └── page.tsx
├── components/
│ ├── image-upload.tsx # ImageCropField (componente principal)
│ └── form-exemple.tsx # Exemplo de uso com RHF + Zod
└── public/
└── preview.png # Imagem de preview (opcional)
O componente foi projetado para ser plugável em qualquer formulário e para exportar um File válido para validação com Zod.
-
Componentização
- Componente controlado via RHF (
Controller) ouvalue/onChange - Propriedades tipadas em TypeScript
- API de configuração enxuta e extensível
- Componente controlado via RHF (
-
Performance
- Recorte feito via Canvas API
- Redimensionamento com suavização
- Geração do arquivo final sob demanda (Confirmar)
-
UX/UI
- Modal de edição com grid opcional
- Preview preenchendo a área (cover)
- Botão “×” para remover no preview
graph LR
A[Input File] -->|URL local| B[Editor (Modal)]
B -->|Crop + Zoom + Rotate| C[Canvas]
C -->|Blob → File| D[RHF Field]
D -->|value| E[Preview]
-
Dimensões de Saída
output.width/output.heightpadronizam o arquivo final- Mantêm o aspecto travado para o usuário no editor
-
Integração com Formulários
- O
Fileresultante atendez.instanceof(File) - Funciona com
Controllerou de forma controlada
- O
-
Acessibilidade e UX
- Foco em interações claras e feedback imediato
- Botões semanticamente corretos e com rótulos
import ImageCropField from "@/components/image-upload";
import { Controller, useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
const schema = z.object({
name: z.string().min(1, "O nome é obrigatório"),
image: z.instanceof(File),
});
type FormData = z.infer<typeof schema>;
export default function Example() {
const { control, handleSubmit } = useForm<FormData>({
resolver: zodResolver(schema),
});
return (
<form onSubmit={handleSubmit(console.log)}>
<Controller
name="image"
control={control}
render={({ field }) => (
<ImageCropField
field={field}
label="Enviar imagem"
viewportWidth={460}
viewportHeight={260}
cropShape="round"
output={{ width: 800, height: 800, quality: 0.92 }}
/>
)}
/>
<button type="submit">Enviar</button>
</form>
);
}aspect: proporção do recorte (seoutputnão for usado)cropShape:"round" | "rect"viewportWidth/viewportHeight: dimensões do previewminZoom/maxZoom/initialZoom: controles de zoomoutput:{ width, height, mime?, quality?, fileName? }label,instruction,accept,maxFileSizeBytes
| Foto | Nome | Cargo |
|---|---|---|
| Jonatas Silva | FullStack Developer |
Este projeto está sob a licença MIT.
