Docs›Arquitetura›Wrappers (MODAL/DRAWER/INTERNAL/SPA)
Arquitetura

Wrappers (MODAL/DRAWER/INTERNAL/SPA)

Como o framework embrulha o componente conforme contexto.

Todo MadComponent declara um wrapper — define COMO ele é apresentado ao chamar show(), sem que a view precise saber nada sobre modal, drawer ou chrome de página. A classe base MadComponent expõe exatamente três constantes para isso: INTERNAL, MODAL e DRAWER.

Os 3 wrappers

ConstChromeUso típico
self::INTERNAL Nenhum — renderiza direto no main da página Listagens, dashboards, MadSitePage (este portal de docs usa INTERNAL).
self::MODAL Modal centralizado, com overlay e título Confirmações, formulários curtos, pickers.
self::DRAWER Painel lateral overlay, com título e lado configurável Formulários longos, edição detalhada.

DRAWER — exemplo

class ProdutoForm extends MadComponent
{
    protected static string $wrapper = self::DRAWER;
    protected static string $title   = 'Produto';
    protected static string $size    = '900px';   // preset (sm/md/lg/xl) ou CSS direto
    protected static string $side    = 'right';    // right | left

    public MadForm $form;

    public function mount(): void
    {
        $this->form = new MadForm('form');
    }

    public function onSave(): MadResponse
    {
        // ...
        return (new MadResponse())->toast('Salvo!', 'success')->closeDrawer();
    }

    protected function view(): string|array
    {
        return 'admin.produto-form';
    }
}
class ConfirmarExclusao extends MadComponent
{
    protected static string $wrapper = self::MODAL;
    protected static string $title   = 'Confirmar exclusão';
    protected static string $size    = 'sm';

    public int $registroId = 0;

    public function mount(int $id): void
    {
        $this->registroId = $id;
    }

    public function onConfirm(): MadResponse
    {
        Produto::findOrFail($this->registroId)->delete();
        return (new MadResponse())->toast('Excluído!', 'success')->closeModal();
    }

    protected function view(): string|array
    {
        return 'admin.confirmar-exclusao';
    }
}

INTERNAL — exemplo

Listagens, dashboards e páginas full-content usam INTERNAL: nenhum chrome além do que a própria view declarar.

class ProdutoListagem extends MadDataGrid
{
    // MadDataGrid ja' usa INTERNAL por padrao — sem chrome extra alem do que
    // VOCE colocar na view (<mad-page-container>, <mad-page-header>, etc).
    protected static string $wrapper = self::INTERNAL;
    protected string $model = Produto::class;
}
MadSitePage sempre usa INTERNAL

Páginas públicas reativas (MadSitePage, base de todo conteúdo deste portal) forçam INTERNAL — o casco visual vem do layout público (public.docs-fw.layout), não do MadComponentWrapper.

Tamanhos do modal/drawer

Os presets sm/md/lg/xl mapeiam para larguras diferentes em cada componente — modal e drawer têm escalas próprias:

PresetModalDrawer
sm380px320px
md520px (default do componente mad-modal)420px
lg680px560px (default de MadComponent::$size)
xl900px720px
full—100vw (só drawer)
CSS direto"900px", "60vw", "min(900px, 90vw)" — qualquer valor fora da lista de presets vira max-width literal.

Como o framework decide o chrome

Mad\Component\MadComponentWrapper::wrap() é chamado dentro de MadComponent::_wrapRenderedHtml(), depois de dehydrate() e da criptografia do estado — um simples match sobre getWrapper():

// Mad\Component\MadComponentWrapper::wrap() — logica real

public static function wrap(MadComponent $component, string $html): string
{
    return match ($component::getWrapper()) {
        MadComponent::MODAL  => self::buildModal($component, $html),
        MadComponent::DRAWER => self::buildDrawer($component, $html),
        default              => $html,   // INTERNAL (e qualquer outro valor) => passthrough
    };
}

MODAL renderiza _wrapper-modal.blade.php (envolve o conteúdo num <mad-modal :name :title :size>); DRAWER renderiza _wrapper-drawer.blade.php (<mad-drawer :name :title :size :side>). Ambas as views fazem o mesmo housekeeping no JS: removem instâncias anteriores do mesmo overlay no DOM, fazem teleport do wrapper para o <body> (escapa de containing blocks de ancestrais com transform/filter), empilham z-index por profundidade de overlay, e disparam os eventos Alpine madmodal/maddrawer que abrem a animação de entrada.

Fechar overlays via MadResponse

return (new MadResponse())
    ->toast('Salvo!', 'success')
    ->closeDrawer()        // fecha o drawer atual
    ->closeModal();         // fecha o modal atual

// Especificando um wrapper nomeado
return (new MadResponse())->closeDrawer('produto-form');
Fechamento ciente do wrapper: returnToCombo()

Quando a tela foi aberta pelo bloco "Sem resultados → Cadastrar novo" de um <mad-dbcombo-field> (e afins), não chame closeDrawer()/closeModal() na mão: $this->returnToCombo($id) devolve a option nova já selecionada no combo de origem, avisa o usuário e fecha o overlay certo para o $wrapper da classe. Retorna null quando a tela não veio de combo — inclusive em INTERNAL, onde a tela de origem foi substituída e não existe <select> para onde voltar. Padrão de uso: if (($r = $this->returnToCombo($this->recordId)) !== null) return $r;. Companheiros: comboOrigin(): ?MadComboOrigin e openedFromCombo(): bool.

Cobertura completa de todas as ops do MadResponse (toast, html, redirect, openModal...) em MadResponse — todas as ops.

Quando usar cada um

CenárioWrapper recomendado
Listagem ou dashboardINTERNAL
CRUD form (5+ campos)DRAWER size="lg"
Confirmação destrutivaMODAL size="sm"
Picker / dialog rápidoMODAL size="md"
Página pública (site)MadSitePage (INTERNAL automático)
Detalhe rápido (não-edita)MODAL size="lg"

Próximos passos