MAD Lab
/
Tutor > Navegação e overlays > Abas (tabs)

Abas (tabs)

Navegação e overlays

mad-tabs mad-tab mad-tab-bar mad-segmented
Básico

mad-tabs (variant default) com mad-tabs-list + mad-tab-panel.

Dados pessoais do usuário: nome, e-mail e telefone.

Formulário de troca de senha.

Preferências de e-mail e push.

resources/views/tutor/examples/navegacao-tabs/basico.blade.php
<mad-tabs default="perfil">
    <mad-tabs-list>
        <mad-tab name="perfil" icon="user">Perfil</mad-tab>
        <mad-tab name="senha" icon="lock">Senha</mad-tab>
        <mad-tab name="notificacoes" icon="bell">Notificações</mad-tab>
    </mad-tabs-list>
    <mad-tab-panel name="perfil">
        <p>Dados pessoais do usuário: nome, e-mail e telefone.</p>
    </mad-tab-panel>
    <mad-tab-panel name="senha">
        <p>Formulário de troca de senha.</p>
    </mad-tab-panel>
    <mad-tab-panel name="notificacoes">
        <p>Preferências de e-mail e push.</p>
    </mad-tab-panel>
</mad-tabs>
Underline + ícones + aba desabilitada

variant=underline, ícones Lucide no mad-tab e disabled.

Configurações gerais do módulo.

Histórico de versões: v1.2, v1.1, v1.0.

Recurso disponível apenas no plano Premium.

resources/views/tutor/examples/navegacao-tabs/underline-icones.blade.php
<mad-tabs default="geral" variant="underline">
    <mad-tabs-list>
        <mad-tab name="geral" icon="info">Geral</mad-tab>
        <mad-tab name="versoes" icon="git-branch">Versões (3)</mad-tab>
        <mad-tab name="premium" icon="lock" disabled>Premium</mad-tab>
    </mad-tabs-list>
    <mad-tab-panel name="geral">
        <p>Configurações gerais do módulo.</p>
    </mad-tab-panel>
    <mad-tab-panel name="versoes">
        <p>Histórico de versões: v1.2, v1.1, v1.0.</p>
    </mad-tab-panel>
    <mad-tab-panel name="premium">
        <p>Recurso disponível apenas no plano Premium.</p>
    </mad-tab-panel>
</mad-tabs>
Vertical

variant=vertical — abas como sidebar de configurações.

Nome, foto e biografia.

Autenticação em dois fatores e sessões ativas.

Plano atual e histórico de faturas.

resources/views/tutor/examples/navegacao-tabs/vertical.blade.php
<mad-tabs default="profile" variant="vertical">
    <mad-tabs-list>
        <mad-tab name="profile" icon="user">Perfil</mad-tab>
        <mad-tab name="security" icon="shield">Segurança</mad-tab>
        <mad-tab name="billing" icon="credit-card">Cobrança</mad-tab>
    </mad-tabs-list>
    <mad-tab-panel name="profile">
        <p>Nome, foto e biografia.</p>
    </mad-tab-panel>
    <mad-tab-panel name="security">
        <p>Autenticação em dois fatores e sessões ativas.</p>
    </mad-tab-panel>
    <mad-tab-panel name="billing">
        <p>Plano atual e histórico de faturas.</p>
    </mad-tab-panel>
</mad-tabs>
Segmented control (ação wire)

mad-segmented + mad-segmented-item com mad:click real — troca o plano ativo no servidor.

Plano selecionado: Mensal

resources/views/tutor/examples/navegacao-tabs/segmented.blade.php
<mad-segmented>
    <div id="tutor-seg-items" style="display:contents;">
        <mad-segmented-item active mad:click="onSelecionarPlano('mensal')">Mensal</mad-segmented-item>
        <mad-segmented-item mad:click="onSelecionarPlano('anual')">Anual</mad-segmented-item>
        <mad-segmented-item mad:click="onSelecionarPlano('vitalicio')">Vitalício</mad-segmented-item>
    </div>
</mad-segmented>
<div id="tutor-seg-resumo" style="margin-top:.75rem;">
    <p>Plano selecionado: <strong>Mensal</strong></p>
</div>
app/control/Tutor/TutorNavegacaoTabs.php
/**
 * Alterna o plano ativo do segmented control — prova o ciclo real de
 * mad:click num mad-segmented-item: reescreve os itens (classe active +
 * aria-selected) e o resumo via MadResponse->html(), sem full re-render.
 */
public function onSelecionarPlano(string $plano): MadResponse
{
    if (!isset(self::PLANOS[$plano])) {
        return (new MadResponse())->toast('Plano inválido.', 'danger');
    }

    $this->planoTutor = $plano;
    $label = self::PLANOS[$plano];

    $itens = '';
    foreach (self::PLANOS as $key => $itemLabel) {
        $active = $key === $plano;
        $itens .= '<button type="button" class="mad-seg-item' . ($active ? ' active' : '') . '" '
            . 'role="tab" aria-selected="' . ($active ? 'true' : 'false') . '" '
            . 'data-mad-click="onSelecionarPlano(\'' . $key . '\')">' . e($itemLabel) . '</button>';
    }

    $resumo = '<p style="margin-top:.75rem;">Plano selecionado: <strong>' . e($label) . '</strong></p>';

    return (new MadResponse())
        ->html('#tutor-seg-items', $itens)
        ->html('#tutor-seg-resumo', $resumo)
        ->toast("Plano \"{$label}\" selecionado.", 'success');
}
mad-tab-bar (documentado, sem demo ao vivo)

mad-tab-bar não é um componente de conteúdo como os outros desta página — é o tab-strip do SHELL do app (abas internas tipo navegador, opção "usar abas" nas preferências do app), renderizado UMA única vez pelo layout ao redor de #mad_main (ShellViewModel::tabBar()components.tab-bar).

Embutir outro <mad-tab-bar> aqui duplicaria esse id e o singleton global window.MadTabs (além do hook de window.__mad_load_page), quebrando a navegação do app inteiro — por isso esta página só documenta a tag, sem executá-la. Fonte: packages/mad-framework/src/mad/views/components/tab-bar.blade.php.

Código-fonte completo da página
app/control/Tutor/TutorNavegacaoTabs.php
<?php

namespace App\Control\Tutor;

use App\Support\Tutor\TutorPage;
use Mad\Http\MadResponse;

/**
 * TutorNavegacaoTabs — página "Abas (tabs)" do MAD Lab.
 *
 * Cobre mad-tabs/mad-tab (+ mad-tabs-list/mad-tab-panel, filhos obrigatórios)
 * nas 3 variantes (default/underline/vertical) e mad-segmented/
 * mad-segmented-item com uma ação wire real (troca de plano no servidor).
 *
 * mad-tab-bar (tag do catálogo) NÃO tem demo ao vivo nesta página: não é um
 * componente de CONTEÚDO como os outros — é o tab-strip do SHELL do app
 * (browser-like tabs internas, liga/desliga em Preferências > "usar abas"),
 * renderizado UMA única vez pelo layout ao redor de #mad_main
 * (ShellViewModel::tabBar() -> components.tab-bar). Embutir outro
 * <mad-tab-bar> aqui duplicaria esse id + o singleton global window.MadTabs
 * e o hook de window.__mad_load_page, quebrando a navegação do app inteiro —
 * por isso a página só documenta essa tag (callout no fim), sem executá-la.
 */
class TutorNavegacaoTabs extends TutorPage
{
    protected string $catalogKey = 'navegacao_tabs';

    /** Estado do exemplo "Segmented control" — plano ativo. */
    public string $planoTutor = 'mensal';

    private const PLANOS = [
        'mensal'    => 'Mensal',
        'anual'     => 'Anual',
        'vitalicio' => 'Vitalício',
    ];

    /**
     * Alterna o plano ativo do segmented control — prova o ciclo real de
     * mad:click num mad-segmented-item: reescreve os itens (classe active +
     * aria-selected) e o resumo via MadResponse->html(), sem full re-render.
     */
    public function onSelecionarPlano(string $plano): MadResponse
    {
        if (!isset(self::PLANOS[$plano])) {
            return (new MadResponse())->toast('Plano inválido.', 'danger');
        }

        $this->planoTutor = $plano;
        $label = self::PLANOS[$plano];

        $itens = '';
        foreach (self::PLANOS as $key => $itemLabel) {
            $active = $key === $plano;
            $itens .= '<button type="button" class="mad-seg-item' . ($active ? ' active' : '') . '" '
                . 'role="tab" aria-selected="' . ($active ? 'true' : 'false') . '" '
                . 'data-mad-click="onSelecionarPlano(\'' . $key . '\')">' . e($itemLabel) . '</button>';
        }

        $resumo = '<p style="margin-top:.75rem;">Plano selecionado: <strong>' . e($label) . '</strong></p>';

        return (new MadResponse())
            ->html('#tutor-seg-items', $itens)
            ->html('#tutor-seg-resumo', $resumo)
            ->toast("Plano \"{$label}\" selecionado.", 'success');
    }
}
resources/views/tutor/navegacao/tabs.blade.php
<x-tutor-page page="navegacao_tabs">
    <x-tutor-demo demo="tutor.examples.navegacao-tabs.basico"
        title="Básico" desc="mad-tabs (variant default) com mad-tabs-list + mad-tab-panel." />

    <x-tutor-demo demo="tutor.examples.navegacao-tabs.underline-icones"
        title="Underline + ícones + aba desabilitada" desc="variant=underline, ícones Lucide no mad-tab e disabled." />

    <x-tutor-demo demo="tutor.examples.navegacao-tabs.vertical"
        title="Vertical" desc="variant=vertical — abas como sidebar de configurações." />

    <x-tutor-demo demo="tutor.examples.navegacao-tabs.segmented"
        title="Segmented control (ação wire)" desc="mad-segmented + mad-segmented-item com mad:click real — troca o plano ativo no servidor."
        php="TutorNavegacaoTabs::onSelecionarPlano" />

    <mad-callout type="info" title="mad-tab-bar (documentado, sem demo ao vivo)" icon="info">
        <p>
            mad-tab-bar não é um componente de conteúdo como os outros desta página —
            é o tab-strip do SHELL do app (abas internas tipo navegador, opção
            "usar abas" nas preferências do app), renderizado UMA única vez pelo
            layout ao redor de <code>#mad_main</code>
            (<code>ShellViewModel::tabBar()</code> →
            <code>components.tab-bar</code>).
        </p>
        <p>
            Embutir outro <code>&lt;mad-tab-bar&gt;</code> aqui duplicaria esse id e o
            singleton global <code>window.MadTabs</code> (além do hook de
            <code>window.__mad_load_page</code>), quebrando a navegação do app
            inteiro — por isso esta página só documenta a tag, sem executá-la. Fonte:
            <code>packages/mad-framework/src/mad/views/components/tab-bar.blade.php</code>.
        </p>
    </mad-callout>
</x-tutor-page>