Skip to content

Instantly share code, notes, and snippets.

@rg3915
Last active November 7, 2024 01:47
Show Gist options
  • Save rg3915/f9566cbb6997098e3e06be114a04c292 to your computer and use it in GitHub Desktop.
Save rg3915/f9566cbb6997098e3e06be114a04c292 to your computer and use it in GitHub Desktop.
state machine

Uma máquina de estados é um modelo de design que organiza a lógica de um sistema em "estados" finitos, onde o sistema pode estar em apenas um estado por vez. Cada estado define o comportamento do sistema e permite transições para outros estados, geralmente com condições específicas. As máquinas de estado são usadas para representar fluxos e interações previsíveis, como etapas de um formulário, navegação em uma interface ou processos de login.

Exemplo com AlpineJS

Vamos criar um exemplo em que uma página exibe seções diferentes (<section>) com base em um estado. Vamos supor que temos três seções: Home, Sobre e Contato, e usaremos AlpineJS para controlar a exibição de cada seção com uma máquina de estado simples.

Estrutura HTML + AlpineJS

<div x-data="{ estado: 'home' }">
    <!-- Navegação -->
    <nav>
        <button @click="estado = 'home'">Home</button>
        <button @click="estado = 'sobre'">Sobre</button>
        <button @click="estado = 'contato'">Contato</button>
    </nav>

    <!-- Seções da Página -->
    <section x-show="estado === 'home'">
        <h1>Bem-vindo à Home</h1>
        <p>Esta é a seção de início da página.</p>
    </section>

    <section x-show="estado === 'sobre'">
        <h1>Sobre Nós</h1>
        <p>Informações sobre a nossa empresa.</p>
    </section>

    <section x-show="estado === 'contato'">
        <h1>Contato</h1>
        <p>Informações de contato e formulário.</p>
    </section>
</div>

Explicação

  1. Definindo o Estado: No atributo x-data, declaramos uma propriedade chamada estado e a inicializamos com o valor 'home', indicando que, ao carregar a página, o estado inicial será home.

  2. Transições de Estado: Cada botão no <nav> usa @click="estado = '...' para atualizar o estado atual quando clicado. Assim, ao clicar no botão Sobre, o estado é alterado para 'sobre', e a seção Sobre será exibida.

  3. Controle de Visibilidade com x-show: Cada <section> usa x-show="estado === '...' para determinar se deve ser exibida ou não com base no estado atual. A seção Home será visível apenas quando estado for 'home', e assim por diante para as outras seções.

Esse padrão permite que a lógica de exibição da página seja controlada com transições de estado simples, tornando o comportamento previsível e facilitando a manutenção da interface.

Exemplo com sub-estado

Vamos criar um exemplo de máquina de estado onde, ao entrar em uma <section>, você ganha novas opções de estado, simulando um sistema de navegação em "subseções".

Neste exemplo, teremos três seções principais (Home, Sobre e Contato). Quando acessarmos a seção Sobre, ela nos oferecerá duas opções adicionais: Missão e Equipe, que são subseções dentro de Sobre.

Estrutura HTML + AlpineJS

<div x-data="{ estado: 'home', subEstado: null }">
    <!-- Navegação Principal -->
    <nav>
        <button @click="estado = 'home'; subEstado = null">Home</button>
        <button @click="estado = 'sobre'; subEstado = null">Sobre</button>
        <button @click="estado = 'contato'; subEstado = null">Contato</button>
    </nav>

    <!-- Seções Principais -->
    <section x-show="estado === 'home'">
        <h1>Bem-vindo à Home</h1>
        <p>Esta é a seção de início da página.</p>
    </section>

    <section x-show="estado === 'sobre'">
        <h1>Sobre Nós</h1>
        <p>Informações sobre a nossa empresa.</p>

        <!-- Navegação de Subseções -->
        <div>
            <button @click="subEstado = 'missao'">Missão</button>
            <button @click="subEstado = 'equipe'">Equipe</button>
        </div>

        <!-- Subseção: Missão -->
        <div x-show="subEstado === 'missao'">
            <h2>Missão</h2>
            <p>Nossa missão é melhorar o mundo através de nossa empresa.</p>
        </div>

        <!-- Subseção: Equipe -->
        <div x-show="subEstado === 'equipe'">
            <h2>Equipe</h2>
            <p>Conheça nossa equipe dedicada de profissionais.</p>
        </div>
    </section>

    <section x-show="estado === 'contato'">
        <h1>Contato</h1>
        <p>Informações de contato e formulário.</p>
    </section>
</div>

Explicação

  1. Definindo os Estados: No x-data, temos duas propriedades:

    • estado controla a seção principal (home, sobre ou contato).
    • subEstado controla subseções dentro da seção sobre e é null por padrão.
  2. Transições de Estado Principal: Cada botão de navegação altera o estado para a seção desejada (home, sobre ou contato) e redefine subEstado para null, para evitar subseções abertas ao sair da seção principal.

  3. Transições de SubEstado: Dentro da seção Sobre, temos dois botões (Missão e Equipe). Ao clicar nesses botões, subEstado é atualizado com o valor 'missao' ou 'equipe', exibindo a subseção apropriada.

  4. Controle de Visibilidade: Usamos x-show para exibir as seções principais com base em estado e as subseções com base em subEstado. Se estado não for 'sobre', as subseções (Missão e Equipe) não aparecem, mantendo o controle centralizado.

Esse padrão permite uma navegação condicional e organizada, com subseções dependentes da seção principal, simulando uma máquina de estado hierárquica em AlpineJS.

Vou criar uma máquina de estados que simula um processo de pedido onde você primeiro escolhe o tipo de entrega (2 opções) e depois o método de pagamento (3 opções), com a possibilidade de voltar e alterar suas escolhas.

<!DOCTYPE html>
<html lang="pt-BR">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Processo de Pedido</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/picocss/1.5.10/pico.min.css">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/alpinejs/3.13.5/cdn.min.js"></script>
</head>
<body>
    <main class="container" x-data="{
        state: 'delivery',
        deliveryType: null,
        paymentMethod: null,
        
        resetPayment() {
            this.paymentMethod = null;
        },
        
        getStateTitle() {
            return this.state === 'delivery' ? 'Escolha o Tipo de Entrega' : 'Escolha o Método de Pagamento';
        }
    }">
        <article class="grid">
            <!-- Cabeçalho com Progresso -->
            <header>
                <h2 x-text="getStateTitle()"></h2>
                <div class="progress-nav">
                    <button 
                        @click="state = 'delivery'; resetPayment()"
                        :class="{ 'secondary': state !== 'delivery' }"
                        class="outline">
                        1. Entrega
                    </button>
                    <button 
                        :class="{ 'secondary': state !== 'payment' }"
                        class="outline"
                        :disabled="!deliveryType">
                        2. Pagamento
                    </button>
                </div>
            </header>

            <!-- Estado: Escolha de Entrega -->
            <section x-show="state === 'delivery'" class="grid">
                <button 
                    @click="deliveryType = 'express'; state = 'payment'"
                    :class="{ 'primary': deliveryType === 'express' }">
                    Entrega Expressa (2-3 dias)
                    <small>R$ 29,90</small>
                </button>
                
                <button 
                    @click="deliveryType = 'standard'; state = 'payment'"
                    :class="{ 'primary': deliveryType === 'standard' }">
                    Entrega Normal (5-7 dias)
                    <small>R$ 15,90</small>
                </button>
            </section>

            <!-- Estado: Escolha de Pagamento -->
            <section x-show="state === 'payment'" class="grid">
                <button 
                    @click="paymentMethod = 'credit'"
                    :class="{ 'primary': paymentMethod === 'credit' }">
                    Cartão de Crédito
                    <small>Até 12x sem juros</small>
                </button>
                
                <button 
                    @click="paymentMethod = 'pix'"
                    :class="{ 'primary': paymentMethod === 'pix' }">
                    PIX
                    <small>5% de desconto</small>
                </button>
                
                <button 
                    @click="paymentMethod = 'boleto'"
                    :class="{ 'primary': paymentMethod === 'boleto' }">
                    Boleto Bancário
                    <small>3% de desconto</small>
                </button>
            </section>

            <!-- Resumo das Escolhas -->
            <footer>
                <div x-show="deliveryType || paymentMethod">
                    <h3>Suas Escolhas:</h3>
                    <p x-show="deliveryType">
                        Entrega: <strong x-text="deliveryType === 'express' ? 'Expressa' : 'Normal'"></strong>
                    </p>
                    <p x-show="paymentMethod">
                        Pagamento: <strong x-text="paymentMethod === 'credit' ? 'Cartão de Crédito' : paymentMethod === 'pix' ? 'PIX' : 'Boleto'"></strong>
                    </p>
                </div>
            </footer>
        </article>
    </main>
</body>
</html>

Neste exemplo, criei uma máquina de estados que representa um processo de pedido com:

Estados:

  1. delivery - Com 2 opções:

    • Entrega Expressa
    • Entrega Normal
  2. payment - Com 3 opções:

    • Cartão de Crédito
    • PIX
    • Boleto Bancário

Características principais:

  1. Navegação entre Estados:

    • Você pode voltar do pagamento para a entrega usando o botão de progresso
    • Ao voltar, a escolha de pagamento é resetada
    • O estado de pagamento só é acessível após escolher a entrega
  2. Feedback Visual:

    • Os botões mostram claramente qual opção está selecionada
    • Há um resumo das escolhas na parte inferior
    • A navegação superior mostra o progresso atual
  3. Layout Responsivo:

    • Usando Pico CSS para um design limpo e responsivo
    • Grid system para organização dos elementos
    • Botões grandes e claros para fácil interação
  4. Gerenciamento de Estado:

    • Uso de x-data para gerenciar o estado atual
    • Variáveis separadas para deliveryType e paymentMethod
    • Função resetPayment para limpar escolhas quando necessário

Você pode expandir este exemplo:

  • Adicionando mais estados
  • Incluindo validações
  • Adicionando animações entre transições
  • Implementando um processo de confirmação final
  • Salvando as escolhas em localStorage

Gostaria que eu explicasse alguma parte específica em mais detalhes?

<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Processo de Pedido</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/picocss/1.5.10/pico.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/alpinejs/3.13.5/cdn.min.js"></script>
</head>
<body>
<main class="container" x-data="{
state: 'delivery',
deliveryType: null,
paymentMethod: null,
resetPayment() {
this.paymentMethod = null;
},
getStateTitle() {
return this.state === 'delivery' ? 'Escolha o Tipo de Entrega' : 'Escolha o Método de Pagamento';
}
}">
<article class="grid">
<!-- Cabeçalho com Progresso -->
<header>
<h2 x-text="getStateTitle()"></h2>
<div class="progress-nav">
<button
@click="state = 'delivery'; resetPayment()"
:class="{ 'secondary': state !== 'delivery' }"
class="outline">
1. Entrega
</button>
<button
:class="{ 'secondary': state !== 'payment' }"
class="outline"
:disabled="!deliveryType">
2. Pagamento
</button>
</div>
</header>
<!-- Estado: Escolha de Entrega -->
<section x-show="state === 'delivery'" class="grid">
<button
@click="deliveryType = 'express'; state = 'payment'"
:class="{ 'primary': deliveryType === 'express' }">
Entrega Expressa (2-3 dias)
<small>R$ 29,90</small>
</button>
<button
@click="deliveryType = 'standard'; state = 'payment'"
:class="{ 'primary': deliveryType === 'standard' }">
Entrega Normal (5-7 dias)
<small>R$ 15,90</small>
</button>
</section>
<!-- Estado: Escolha de Pagamento -->
<section x-show="state === 'payment'" class="grid">
<button
@click="paymentMethod = 'credit'"
:class="{ 'primary': paymentMethod === 'credit' }">
Cartão de Crédito
<small>Até 12x sem juros</small>
</button>
<button
@click="paymentMethod = 'pix'"
:class="{ 'primary': paymentMethod === 'pix' }">
PIX
<small>5% de desconto</small>
</button>
<button
@click="paymentMethod = 'boleto'"
:class="{ 'primary': paymentMethod === 'boleto' }">
Boleto Bancário
<small>3% de desconto</small>
</button>
</section>
<!-- Resumo das Escolhas -->
<footer>
<div x-show="deliveryType || paymentMethod">
<h3>Suas Escolhas:</h3>
<p x-show="deliveryType">
Entrega: <strong x-text="deliveryType === 'express' ? 'Expressa' : 'Normal'"></strong>
</p>
<p x-show="paymentMethod">
Pagamento: <strong x-text="paymentMethod === 'credit' ? 'Cartão de Crédito' : paymentMethod === 'pix' ? 'PIX' : 'Boleto'"></strong>
</p>
</div>
</footer>
</article>
</main>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment