MAD Lab
/
Tutor > Estrutura de formulário > Wizard

Wizard

Estrutura de formulário

mad-wizard mad-wizard-step mad-steps
Wizard básico com validação por etapa

mad-wizard + mad-wizard-step; Avançar valida só os campos da etapa atual e Concluir devolve toast com o payload.

Dados
2 Endereço
3 Confirmação

Revise os dados
Ao concluir, os dados das 3 etapas são lidos e devolvidos num toast — nenhum registro é salvo em banco.

resources/views/tutor/examples/estrutura-wizard/basico.blade.php
@php
    // $that (como $__component) e uma variavel LEXICAL setada por
    // MadComponent::render() no template de TOPO da pagina. Este partial e
    // incluido de dentro do componente Blade nativo tutor-demo (escopo
    // isolado), entao $that nao chega ate aqui sozinho — precisa ser
    // reobtido via o stack estatico de MadRenderContext (push/pop em
    // MadComponent::render(), atravessa qualquer aninhamento de componentes).
    $that = \Mad\Component\MadRenderContext::getComponent();
@endphp
<mad-form submit="onWizardFinish">
    <mad-wizard :current="$that->wizStep" variant="numbers">
        <mad-wizard-step key="dados" title="Dados" icon="user" description="Nome e e-mail">
            <mad-form-section title="Dados pessoais" icon="user">
                <mad-form-grid :cols="2">
                    <mad-input-field name="nome_wiz" label="Nome completo" required icon="user" />
                    <mad-input-field name="email_wiz" label="E-mail" type="email" required icon="mail" />
                </mad-form-grid>
            </mad-form-section>
        </mad-wizard-step>

        <mad-wizard-step key="endereco" title="Endereço" icon="map-pin" description="Cidade">
            <mad-form-section title="Endereço" icon="map-pin">
                <mad-form-grid :cols="2">
                    <mad-input-field name="cidade_wiz" label="Cidade" required />
                    <mad-input-field name="uf_wiz" label="UF" maxlength="2" force-case="upper" />
                </mad-form-grid>
            </mad-form-section>
        </mad-wizard-step>

        <mad-wizard-step key="confirmacao" title="Confirmação" icon="check" description="Revisar e concluir">
            <mad-form-section title="Confirmação" icon="check-circle">
                <mad-callout type="info" title="Revise os dados" icon="info">
                    Ao concluir, os dados das 3 etapas são lidos e devolvidos num toast — nenhum registro é salvo em banco.
                </mad-callout>
            </mad-form-section>
        </mad-wizard-step>
    </mad-wizard>

    <mad-separator />

    <mad-form-actions align="right">
        @if($that->wizStep > 1)
            <mad-btn variant="ghost" icon="arrow-left" mad:click="onWizardPrev">Voltar</mad-btn>
        @endif
        @if($that->wizStep < 3)
            <mad-btn variant="primary" icon-end="arrow-right" mad:click="onWizardNext">Avançar</mad-btn>
        @else
            <mad-btn variant="primary" type="submit" icon="check">Concluir</mad-btn>
        @endif
    </mad-form-actions>
</mad-form>
app/control/Tutor/TutorEstruturaWizard.php
/**
 * Conclusão do exemplo "básico" — valida a última etapa e devolve um
 * toast com o payload das 3 etapas (getDataRaw: sem schema/token, seguro
 * mesmo havendo só este <mad-form> na página). Não persiste em banco.
 */
public function onWizardFinish(): MadResponse
{
    try {
        $this->form->validate($this->rulesForWizStep(3));
    } catch (MadValidationException $e) {
        return $e->asInline();
    }

    $data   = $this->form->getDataRaw();
    $resumo = trim(($data->nome_wiz ?? '') . ' | ' . ($data->email_wiz ?? '') . ' | ' . ($data->cidade_wiz ?? ''), ' |');

    return (new MadResponse())
        ->toast("Wizard concluído: {$resumo}", 'success', 'onWizardFinish');
}
Navegação clicável no indicador

mad-wizard clickable + mad:click no indicador — só permite ir a uma etapa já alcançada.

Categoria
Escolha a categoria
2
Detalhes
Preencha os detalhes
3
Revisão
Revise e conclua
Etapa 1
Clique num círculo do indicador ACIMA já visitado pra voltar sem usar os botões — indo além da etapa alcançada mostra um aviso.
Etapa 2
Segunda etapa alcançada — o indicador já deixa clicar de volta na etapa 1.
Etapa 3
Última etapa — agora dá pra clicar em qualquer círculo do indicador.

resources/views/tutor/examples/estrutura-wizard/clicavel.blade.php
@php
    // $that (como $__component) e uma variavel LEXICAL setada por
    // MadComponent::render() no template de TOPO da pagina. Este partial e
    // incluido de dentro do componente Blade nativo tutor-demo (escopo
    // isolado), entao $that nao chega ate aqui sozinho — precisa ser
    // reobtido via o stack estatico de MadRenderContext (push/pop em
    // MadComponent::render(), atravessa qualquer aninhamento de componentes).
    $that = \Mad\Component\MadRenderContext::getComponent();
@endphp
<mad-wizard :current="$that->wizGotoStep" variant="dots" clickable mad:click="onWizardGotoClick">
    <mad-wizard-step key="1" title="Categoria" icon="tag" description="Escolha a categoria">
        <mad-callout type="info" title="Etapa 1" icon="tag">
            Clique num círculo do indicador ACIMA já visitado pra voltar sem usar os botões — indo além da etapa alcançada mostra um aviso.
        </mad-callout>
    </mad-wizard-step>

    <mad-wizard-step key="2" title="Detalhes" icon="file-text" description="Preencha os detalhes">
        <mad-callout type="info" title="Etapa 2" icon="file-text">
            Segunda etapa alcançada — o indicador já deixa clicar de volta na etapa 1.
        </mad-callout>
    </mad-wizard-step>

    <mad-wizard-step key="3" title="Revisão" icon="check" description="Revise e conclua">
        <mad-callout type="success" title="Etapa 3" icon="check">
            Última etapa — agora dá pra clicar em qualquer círculo do indicador.
        </mad-callout>
    </mad-wizard-step>
</mad-wizard>

<mad-separator />

<mad-form-actions align="right">
    @if($that->wizGotoStep > 1)
        <mad-btn variant="ghost" icon="arrow-left" mad:click="onWizardGotoPrev">Voltar</mad-btn>
    @endif
    @if($that->wizGotoStep < 3)
        <mad-btn variant="primary" icon-end="arrow-right" mad:click="onWizardGotoNext">Avançar</mad-btn>
    @endif
</mad-form-actions>
app/control/Tutor/TutorEstruturaWizard.php
/**
 * Clique no indicador do exemplo "clicável" (mad-wizard clickable) —
 * recebe a KEY da etapa clicada ("1".."3", posicional) e só permite ir
 * pra uma etapa já alcançada (evita pular pra frente sem passar pelas
 * anteriores).
 */
public function onWizardGotoClick(string $key): MadResponse
{
    $step = (int) $key;
    if ($step < 1 || $step > 3) {
        return new MadResponse();
    }
    if ($step > $this->wizGotoMaxReached) {
        return (new MadResponse())->toast('Complete a etapa atual antes de avançar.', 'warning');
    }

    $this->wizGotoStep = $step;

    return new MadResponse();
}
mad-steps isolado

indicador de progresso sozinho (sem mad-wizard em volta), nas 4 variantes: numbers, dots, arrows e progress.

variant="numbers"

Dados
Pagamento
3 Concluído

variant="dots" (com description)

Dados
Nome e e-mail
Pagamento
Cartão ou Pix
3
Concluído
Pedido confirmado

variant="arrows"

1 Dados
2 Pagamento
3 Concluído

variant="progress"

✓ Dados ✓ Pagamento Concluído
resources/views/tutor/examples/estrutura-wizard/steps-standalone.blade.php
<div style="display:flex; flex-direction:column; gap:28px;">
    <div>
        <p style="margin:0 0 8px; font-weight:600;">variant="numbers"</p>
        <mad-steps variant="numbers" :current="2">
            <mad-step icon="user" label="Dados" />
            <mad-step icon="credit-card" label="Pagamento" />
            <mad-step icon="check" label="Concluído" />
        </mad-steps>
    </div>

    <div>
        <p style="margin:0 0 8px; font-weight:600;">variant="dots" (com description)</p>
        <mad-steps variant="dots" :current="2">
            <mad-step icon="user" label="Dados" description="Nome e e-mail" />
            <mad-step icon="credit-card" label="Pagamento" description="Cartão ou Pix" />
            <mad-step icon="check" label="Concluído" description="Pedido confirmado" />
        </mad-steps>
    </div>

    <div>
        <p style="margin:0 0 8px; font-weight:600;">variant="arrows"</p>
        <mad-steps variant="arrows" :current="1">
            <mad-step label="Dados" />
            <mad-step label="Pagamento" />
            <mad-step label="Concluído" />
        </mad-steps>
    </div>

    <div>
        <p style="margin:0 0 8px; font-weight:600;">variant="progress"</p>
        <mad-steps variant="progress" :current="3">
            <mad-step label="Dados" />
            <mad-step label="Pagamento" />
            <mad-step label="Concluído" />
        </mad-steps>
    </div>
</div>
Código-fonte completo da página
app/control/Tutor/TutorEstruturaWizard.php
<?php

namespace App\Control\Tutor;

use App\Support\Tutor\TutorPage;
use Mad\Form\MadForm;
use Mad\Form\MadValidationException;
use Mad\Http\MadResponse;

/**
 * TutorEstruturaWizard — cobre <mad-wizard>, <mad-wizard-step> e <mad-steps>.
 *
 * Demo 1 ("básico") valida por etapa e finaliza com toast do payload lido via
 * getDataRaw() — nada é persistido. Demo 2 ("clicável") navega pelo clique no
 * indicador (clickable), travado à maior etapa já alcançada. Demo 3 é
 * <mad-steps> isolado (sem <mad-wizard> em volta), só pra ilustrar as 4
 * variantes visuais do indicador.
 */
class TutorEstruturaWizard extends TutorPage
{
    protected string $catalogKey = 'estrutura_wizard';

    public MadForm $form;

    /** Demo 1 — wizard básico com validação por etapa. */
    public int $wizStep = 1;

    /** Demo 2 — navegação clicável no indicador. */
    public int $wizGotoStep = 1;
    public int $wizGotoMaxReached = 1;

    public function mount(array $params = []): void
    {
        $this->form = new MadForm('tutor_estrutura_wizard');
    }

    /**
     * Avançar do exemplo "básico" — só valida os campos da ETAPA ATUAL antes
     * de liberar a próxima (padrão de wizard: nunca validar o form inteiro
     * de uma vez).
     */
    public function onWizardNext(): MadResponse
    {
        try {
            $this->form->validate($this->rulesForWizStep($this->wizStep));
        } catch (MadValidationException $e) {
            return $e->asInline();
        }

        if ($this->wizStep < 3) {
            $this->wizStep++;
        }

        return (new MadResponse())->toast("Etapa {$this->wizStep}/3", 'info');
    }

    public function onWizardPrev(): void
    {
        if ($this->wizStep > 1) {
            $this->wizStep--;
        }
    }

    /**
     * Conclusão do exemplo "básico" — valida a última etapa e devolve um
     * toast com o payload das 3 etapas (getDataRaw: sem schema/token, seguro
     * mesmo havendo só este <mad-form> na página). Não persiste em banco.
     */
    public function onWizardFinish(): MadResponse
    {
        try {
            $this->form->validate($this->rulesForWizStep(3));
        } catch (MadValidationException $e) {
            return $e->asInline();
        }

        $data   = $this->form->getDataRaw();
        $resumo = trim(($data->nome_wiz ?? '') . ' | ' . ($data->email_wiz ?? '') . ' | ' . ($data->cidade_wiz ?? ''), ' |');

        return (new MadResponse())
            ->toast("Wizard concluído: {$resumo}", 'success', 'onWizardFinish');
    }

    private function rulesForWizStep(int $step): array
    {
        return match ($step) {
            1       => ['nome_wiz|Nome' => 'required', 'email_wiz|E-mail' => 'required|email'],
            2       => ['cidade_wiz|Cidade' => 'required'],
            default => [],
        };
    }

    /**
     * Clique no indicador do exemplo "clicável" (mad-wizard clickable) —
     * recebe a KEY da etapa clicada ("1".."3", posicional) e só permite ir
     * pra uma etapa já alcançada (evita pular pra frente sem passar pelas
     * anteriores).
     */
    public function onWizardGotoClick(string $key): MadResponse
    {
        $step = (int) $key;
        if ($step < 1 || $step > 3) {
            return new MadResponse();
        }
        if ($step > $this->wizGotoMaxReached) {
            return (new MadResponse())->toast('Complete a etapa atual antes de avançar.', 'warning');
        }

        $this->wizGotoStep = $step;

        return new MadResponse();
    }

    public function onWizardGotoNext(): void
    {
        if ($this->wizGotoStep < 3) {
            $this->wizGotoStep++;
            $this->wizGotoMaxReached = max($this->wizGotoMaxReached, $this->wizGotoStep);
        }
    }

    public function onWizardGotoPrev(): void
    {
        if ($this->wizGotoStep > 1) {
            $this->wizGotoStep--;
        }
    }
}
resources/views/tutor/estrutura/wizard.blade.php
<x-tutor-page page="estrutura_wizard">
    <x-tutor-demo demo="tutor.examples.estrutura-wizard.basico"
        title="Wizard básico com validação por etapa" desc="mad-wizard + mad-wizard-step; Avançar valida só os campos da etapa atual e Concluir devolve toast com o payload."
        php="TutorEstruturaWizard::onWizardFinish" />

    <x-tutor-demo demo="tutor.examples.estrutura-wizard.clicavel"
        title="Navegação clicável no indicador" desc="mad-wizard clickable + mad:click no indicador — só permite ir a uma etapa já alcançada."
        php="TutorEstruturaWizard::onWizardGotoClick" />

    <x-tutor-demo demo="tutor.examples.estrutura-wizard.steps-standalone"
        title="mad-steps isolado" desc="indicador de progresso sozinho (sem mad-wizard em volta), nas 4 variantes: numbers, dots, arrows e progress." />
</x-tutor-page>