mad-tabs
Abas (default/underline/vertical).
Componente de abas com 3 variantes visuais. Requer o wrapper <mad-tabs-list> em volta dos botoes de aba.
Estrutura obrigatoria
<mad-tabs default="aba1" variant="underline">
<mad-tabs-list>
<mad-tab name="aba1" icon="info">Aba 1</mad-tab>
<mad-tab name="aba2" icon="settings">Aba 2</mad-tab>
<mad-tab name="aba3" icon="users">Aba 3</mad-tab>
</mad-tabs-list>
<mad-tab-panel name="aba1">
Conteudo da aba 1
</mad-tab-panel>
<mad-tab-panel name="aba2">
Conteudo da aba 2
</mad-tab-panel>
<mad-tab-panel name="aba3">
Conteudo da aba 3
</mad-tab-panel>
</mad-tabs>
CRITICO: Os <mad-tab> DEVEM estar dentro de <mad-tabs-list>. Sem esse wrapper, os botoes renderizam empilhados verticalmente (o CSS .mad-tabs-list e quem aplica display:flex horizontal + underline).
Props do <mad-tabs>
| Prop | Tipo | Default | Descricao |
|---|---|---|---|
| variant | string | 'default' | default, underline, vertical |
| default | string | '' | Nome da aba ativa inicial |
| class | string | '' | Classes CSS extras |
| style | string | '' | Estilos inline |
Props do <mad-tab>
| Prop | Tipo | Default | Descricao |
|---|---|---|---|
| name | string | '' | Identificador da aba (deve bater com <mad-tab-panel name="...">) |
| icon | string | '' | Icone Lucide |
| title | string | '' | Tooltip nativo (title) do botao |
| disabled | bool | false | |
| class | string | '' |
<mad-tab> e <mad-tab-panel> consultam o MadRenderContext pelo name no
escopo tab (MadRenderContext::isHidden($name, 'tab')) — esconder a aba pelo
servidor esconde tambem o painel correspondente.
Props do <mad-tab-panel>
| Prop | Tipo | Default | Descricao |
|---|---|---|---|
| name | string | '' | Identificador (deve bater com <mad-tab name="...">) |
| class | string | '' | |
| style | string | '' |
Variantes
default — botoes com fundo ativo
<mad-tabs default="perfil">
<mad-tabs-list>
<mad-tab name="perfil">Perfil</mad-tab>
<mad-tab name="senha">Senha</mad-tab>
</mad-tabs-list>
<mad-tab-panel name="perfil">...</mad-tab-panel>
<mad-tab-panel name="senha">...</mad-tab-panel>
</mad-tabs>
underline — linha inferior abaixo da aba ativa
<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">Versoes</mad-tab>
<mad-tab name="atividade" icon="activity">Atividade</mad-tab>
</mad-tabs-list>
<mad-tab-panel name="geral">...</mad-tab-panel>
<mad-tab-panel name="versoes">...</mad-tab-panel>
<mad-tab-panel name="atividade">...</mad-tab-panel>
</mad-tabs>
vertical — abas laterais (sidebar)
<mad-tabs default="profile" variant="vertical">
<mad-tabs-list>
<mad-tab name="profile" icon="user">Perfil</mad-tab>
<mad-tab name="security" icon="shield">Seguranca</mad-tab>
<mad-tab name="notifications" icon="bell">Notificacoes</mad-tab>
</mad-tabs-list>
<mad-tab-panel name="profile">...</mad-tab-panel>
<mad-tab-panel name="security">...</mad-tab-panel>
<mad-tab-panel name="notifications">...</mad-tab-panel>
</mad-tabs>
Com contador dinamico no label
<mad-tab name="versoes" icon="git-branch">Versoes ({{ count($versoes) }})</mad-tab>
Aba desabilitada
<mad-tab name="premium" disabled>Premium (indisponivel)</mad-tab>
Erro de campo em aba inativa — auto-ativacao
Um erro de validacao apontando para um campo dentro de um <mad-tab-panel>
inativo ficaria invisivel: o x-show="activeTab === '...'" esconde o painel via
display:none e o usuario so veria o toast, sem saber onde corrigir.
O JS do framework (Mad._revealFieldError) resolve isso sozinho: ao aplicar o
erro no slot [data-field-error], ele sobe ate o [data-mad-tab]; se o painel
estiver escondido, pega o .mad-tabs mais proximo e escreve activeTab no
estado Alpine, ativando a aba que contem o campo com erro.
Regras do comportamento:
- A primeira aba com erro vence. Um lock curto (100ms) no root das tabs impede que erros seguintes da mesma resposta fiquem trocando de aba.
- Nao faz nada quando o painel ja esta visivel, quando o campo nao esta dentro
de nenhuma aba, ou quando o
.mad-tabsnao tem Alpine inicializado. - Nao exige configuracao: basta o campo estar dentro de um
<mad-tab-panel>comname.
Preservar a aba ativa apos forceFullRender()
A aba ativa vive so no Alpine (x-data="{ activeTab: '...' }"). Quando uma action do MadComponent chama $this->forceFullRender(), o HTML inteiro volta do server e o Alpine re-inicializa com o valor do atributo default — se ele for estatico, a aba salta de volta pra esse valor e o usuario perde onde estava.
Solucao: transformar default em estado server-side e atualizar a prop em toda action que chama forceFullRender().
class MeuForm extends MadComponent
{
public string $activeTab = 'profile'; // aba inicial
public function onAlgumaAcao2FA(): MadResponse
{
// ... logica que exige re-render completo ...
$this->activeTab = 'account'; // garante que o Alpine vai re-inicializar na aba certa
$this->forceFullRender();
return (new MadResponse())->toast('OK', 'success');
}
protected function view(): string|array
{
return ['meu-form', ['activeTab' => $this->activeTab, /* ... */]];
}
}
<mad-tabs :default="$activeTab" variant="vertical">
<mad-tabs-list>
<mad-tab name="profile" icon="user">Profile</mad-tab>
<mad-tab name="account" icon="shield">Account</mad-tab>
</mad-tabs-list>
<mad-tab-panel name="profile">...</mad-tab-panel>
<mad-tab-panel name="account">...</mad-tab-panel>
</mad-tabs>
Regras praticas
- O
<mad-tab>nao dispara evento no clique — so escreveactiveTabno Alpine. Por isso o server nao sabe quando o usuario troca de aba manualmente. - Se o re-render nunca acontece a partir de outras abas, nao precisa rastrear: basta setar
$this->activeTab = '<aba da acao>'em cada action que chamaforceFullRender(). - Quando uma mesma view tem actions em varias abas que forcam re-render, voce precisa de tracking real da aba. Duas opcoes:
- Em cada
data-mad-clickdentro da aba X, chamar um$set({activeTab: 'X'})junto ou seguir a convencao de setar a prop no inicio de cada action da aba X. - Custom: emitir evento no clique do tab via
attrsdo<mad-tabs>(wrapper Alpine com$watch('activeTab', v => MadWire.call($el, 'onTabChange', [v]))) e registrar umonTabChange(string $tab): void { $this->activeTab = $tab; }.
- Em cada
NUNCA fazer
{{-- ERRADO: sem <mad-tabs-list> — botoes empilham verticalmente --}}
<mad-tabs default="a">
<mad-tab name="a">A</mad-tab>
<mad-tab name="b">B</mad-tab>
<mad-tab-panel name="a">...</mad-tab-panel>
<mad-tab-panel name="b">...</mad-tab-panel>
</mad-tabs>
{{-- CERTO: com o wrapper --}}
<mad-tabs default="a">
<mad-tabs-list>
<mad-tab name="a">A</mad-tab>
<mad-tab name="b">B</mad-tab>
</mad-tabs-list>
<mad-tab-panel name="a">...</mad-tab-panel>
<mad-tab-panel name="b">...</mad-tab-panel>
</mad-tabs>
{{-- ERRADO: name do <mad-tab> nao bate com <mad-tab-panel> --}}
<mad-tab name="geral">Geral</mad-tab>
<mad-tab-panel name="overview">...</mad-tab-panel>
{{-- CERTO: names devem ser iguais --}}
<mad-tab name="geral">Geral</mad-tab>
<mad-tab-panel name="geral">...</mad-tab-panel>
{{-- ERRADO: montar tabs manualmente com HTML --}}
<div class="tabs">
<button @click="tab=1">A</button>
<button @click="tab=2">B</button>
<div x-show="tab===1">...</div>
</div>
{{-- CERTO: usar o componente --}}
<mad-tabs>...</mad-tabs>
Dica: padrao comum em drawers/forms
<mad-form submit="onSave">
<mad-tabs default="dados" variant="underline">
<mad-tabs-list>
<mad-tab name="dados" icon="user">Dados</mad-tab>
<mad-tab name="endereco" icon="map-pin">Endereco</mad-tab>
<mad-tab name="contato" icon="phone">Contato</mad-tab>
</mad-tabs-list>
<mad-tab-panel name="dados">
<mad-form-grid :cols="2">
<mad-input-field name="nome" label="Nome" required />
<mad-input-field name="cpf" label="CPF" />
</mad-form-grid>
</mad-tab-panel>
<mad-tab-panel name="endereco">
<mad-input-field name="cep" label="CEP" />
<mad-input-field name="logradouro" label="Logradouro" />
</mad-tab-panel>
<mad-tab-panel name="contato">
<mad-input-field name="email" label="Email" />
<mad-input-field name="telefone" label="Telefone" />
</mad-tab-panel>
</mad-tabs>
<mad-form-actions>
<mad-btn type="submit" variant="primary" icon="save">Salvar</mad-btn>
</mad-form-actions>
</mad-form>