mad-image-field
Imagem com crop, rotate, zoom, câmera (Cropper.js).
Campo de upload de imagem completo: drag-and-drop, webcam, crop, rotate, zoom. Usa Cropper.js.
Props
| Prop | Tipo | Default | Descricao |
|---|---|---|---|
| name | string | '' | Nome do campo (obrigatorio) |
| label | string | '' | Label do campo |
| value | string | '' | Imagem atual: URL, base64 (data:image/...) ou path de arquivo ja salvo (com storage, o path e resolvido automaticamente para uma URL de preview) |
| width | string | '240px' | Largura do componente |
| height | string | '240px' | Altura da area de preview |
| camera | bool | false | Habilita botao de camera |
| crop | bool | false | Habilita ferramentas de crop |
| aspect-ratio | string | '' | Proporcao: 1:1, 16:9, 4:3, free |
| max-size | string | '5MB' | Tamanho maximo (KB, MB, GB) |
| accept | string | 'image/png,image/jpeg,image/gif' | MIME types aceitos |
| output | string | 'base64' | Reservado — o JS sempre trabalha com base64 internamente (imageData); o prop e aceito mas nao muda o comportamento hoje. Use storage="disk"/storage="db" para persistir como arquivo |
| placeholder-icon | string | 'image-plus' | Icone Lucide no estado vazio |
| storage | string | '' | disk (filesystem) ou db (BLOB base64 no banco) |
| folder | string | 'uploads' | Diretorio destino (somente com storage="disk") |
| name-column | string | '' | Coluna para nome original do arquivo |
| file-name | string | 'prefix' | Modo do nome: prefix, unique, original, record |
| hint | string | '' | Texto de ajuda |
| error | string | '' | Mensagem de erro |
| required | bool | false | Campo obrigatorio |
| disabled | bool | false | Campo desabilitado |
Upload simples (base64 — comportamento original)
<mad-image-field name="logo" label="Logo" />
Sem storage, a imagem e enviada como base64 direto na coluna do model.
Storage automatico (disco)
<mad-image-field name="foto" label="Foto do produto"
storage="disk" folder="uploads/fotos" name-column="foto_nome"
crop aspect-ratio="1:1" />
// Controller — zero codigo de arquivo:
$this->form->save($record);
// 1. Salva em uploads/fotos/<hash-aleatorio>_foto.jpg (modo file-name="prefix")
// 2. $record->foto = 'uploads/fotos/<hash-aleatorio>_foto.jpg'
// 3. $record->foto_nome = 'foto.jpg'
A gravacao passa pela API de Filesystem do Laravel (MadUploadStorage): disco
default mad_uploads (root storage/app/mad), ou o disco de MAD_UPLOAD_DISK
(config('mad.uploads.disk')) quando o destino e S3 ou outro adapter. Nao ha
escrita direta em filesystem nem fallback para a raiz do projeto. A coluna do
model guarda o caminho relativo, entao trocar de disco nao exige migracao
de dados; para exibir, use a URL de download do framework em vez de montar um
caminho publico a mao.
Storage automatico (BLOB no banco)
<mad-image-field name="avatar_blob" label="Avatar"
storage="db" name-column="avatar_nome"
camera crop aspect-ratio="1:1" />
// No onEdit, carregar o BLOB:
$this->form->loadBlob($record, 'avatar_blob', 'avatar_nome');
$this->form->fill($record);
// No onSave:
$this->form->save($record); // BLOB salvo via prepared statement
Com crop
{{-- Crop livre --}}
<mad-image-field name="foto" label="Foto" crop />
{{-- Crop com proporcao fixa --}}
<mad-image-field name="avatar" label="Avatar"
crop aspect-ratio="1:1" width="200px" height="200px" />
{{-- Crop 16:9 --}}
<mad-image-field name="capa" label="Capa"
crop aspect-ratio="16:9" width="320px" height="180px" />
Com camera
{{-- Camera sem crop --}}
<mad-image-field name="foto" label="Foto" camera />
{{-- Camera + crop (foto de perfil) --}}
<mad-image-field name="avatar" label="Foto de perfil"
camera crop aspect-ratio="1:1" width="200px" height="200px" />
Tamanho e formatos customizados
<mad-image-field name="foto" label="Foto"
max-size="10MB"
accept="image/png,image/jpeg,image/webp" />
Com valor pre-carregado
<mad-image-field name="avatar" label="Avatar"
:value="$usuario->avatar_url"
camera crop aspect-ratio="1:1" />
Dentro de formulario
<mad-form submit="onSave">
<mad-image-field name="foto" label="Foto do produto"
crop aspect-ratio="4:3" />
<mad-input-field name="nome" label="Nome" />
<mad-btn type="submit" variant="primary" icon="save">Salvar</mad-btn>
</mad-form>
use Illuminate\Support\Facades\DB;
public function onSave(): MadResponse
{
$data = $this->form->getData();
// $data->foto contem o base64 da imagem
DB::connection('business')->transaction(function () use ($data) {
$produto = Produto::findOrNew($this->registroId);
$this->form->fillRecord($produto);
if (!empty($data->foto)) {
// Salvar base64 como arquivo — SEMPRE via Storage, nunca
// file_put_contents() em caminho montado a mao
$imageData = base64_decode(preg_replace('#^data:image/\w+;base64,#i', '', $data->foto));
$key = 'uploads/produto_' . $produto->id . '.jpg';
\Mad\Service\MadUploadStorage::putContent($key, $imageData);
$produto->imagem = $key; // caminho RELATIVO no banco
}
$produto->save();
$this->registroId = (int) $produto->id;
});
return (new MadResponse())
->toast('Produto salvo!', 'success')
->closeDrawer();
}
Estados visuais
| Estado | Descricao |
|---|---|
| Empty (dropzone) | Area pontilhada, icone, texto "Arraste uma imagem", botao camera |
| Preview | Imagem carregada + toolbar (crop, rotate, zoom, delete) |
| Cropping | Cropper.js ativo + toolbar (confirmar, cancelar, zoom) |
| Camera | Viewfinder + botao captura estilo iOS + trocar camera |
| Drag-over | Borda azul + fundo azul claro |
Toolbar
| Modo | Botoes disponiveis |
|---|---|
| Preview (sem crop) | Girar esquerda, girar direita, remover |
| Preview (com crop) | Recortar, girar esquerda, girar direita, remover |
| Cropping | Zoom +, zoom -, confirmar, cancelar |
Evento change (callback ao selecionar/capturar/recortar)
Ao selecionar, capturar pela camera, recortar ou girar, o arquivo e enviado ao servidor e fica no disco de scratch mad_tmp (storage/app/mad-tmp) sob o prefixo mad_uploads/. O metodo PHP recebe a chave relativa de Storage (mad_uploads/<uniqid>_<nome>), nao um caminho absoluto — mesma API do mad-file-field e mad-multi-file-field.
<mad-image-field name="avatar" label="Avatar"
camera crop aspect-ratio="1:1"
mad:change="onAvatarChange" />
use Mad\Service\MadScratchStorage;
// Mesma assinatura do file-field: recebe a CHAVE no disco de scratch
public function onAvatarChange(string $tempKey): void
{
$bytes = MadScratchStorage::disk()->get($tempKey);
$size = getimagesizefromstring($bytes);
$this->form->set('dimensoes', $size[0] . 'x' . $size[1]);
}
NUNCA fazer
{{-- ERRADO: upload com input file nativo --}}
<input type="file" name="foto" accept="image/*">
{{-- CERTO --}}
<mad-image-field name="foto" label="Foto" />
{{-- ERRADO: montar dropzone manualmente --}}
<div class="dropzone" ondragover="...">...</div>
{{-- CERTO --}}
<mad-image-field name="foto" label="Foto" />