Crie um componente de guia JavaScript com uma UI de passo adaptável

No passado, mostrei como criar diferentes interfaces com guias. Hoje, construiremos outro componente de guia JavaScript responsivo, onde as guias clicáveis ​​aparecerão como um componente de passo.

Se você não estiver familiarizado com componentes de passo, seu objetivo principal é melhorar a experiência do usuário organizando grandes blocos de conteúdo lógico em etapas sequenciais menores. Um caso de uso generalizado desse componente é a criação de um checkout em várias etapas em sites de comércio eletrônico.

Nosso componente de guia

Aqui está o que vamos criar—redimensione seu navegador para ver como o layout da guia muda:

variantes de layoutvariantes de layoutvariantes de layout
Variantes de layout

Não focaremos tanto na acessibilidade neste tutorial, então explorar como tornar esse componente mais acessível seria um próximo passo válido.

1. Comece com a marcação da página

Dentro de um container, colocaremos duas listas que incluem as abas e seus conteúdos associados (painéis).

Por padrão, a primeira aba estará ativa.

Aqui está a marcação necessária:

1
 class="grid">
2
   class="tab-list">
3
     class="active">
4
       href="">
5
         class="dot">
6
        ...
7
      
8
    
9
    
10
  
11
   class="tab-panels">
12
     class="active">...
13
    
14
  
15

2. Adicione o CSS

Vamos nos concentrar nos estilos principais – você pode ver todos eles clicando no botão CSS guia do projeto de demonstração.

Em telas grandes (>700px), o componente da aba será assim:

O layout da área de trabalho do nosso componente de guiaO layout da área de trabalho do nosso componente de guiaO layout da área de trabalho do nosso componente de guia

Nos menores, ficará assim:

O layout móvel do nosso componente de guiaO layout móvel do nosso componente de guiaO layout móvel do nosso componente de guia

Observe como o stepper alterna entre a orientação horizontal e vertical dependendo do tamanho da tela.

Além disso, considere que todos os painéis de guias serão empilhados e movidos 100% para a esquerda; a qualquer momento, apenas aquele com o active a turma aparecerá e ficará em sua posição inicial.

Aqui está uma parte dos estilos necessários:

1
/*CUSTOM VARIABLES HERE*/
2

3
.grid {
4
  display: grid;
5
  grid-template-columns: auto auto;
6
  gap: 70px;
7
  max-width: 1000px;
8
  padding: 0 20px;
9
  margin: 0 auto;
10
}
11

12
.tab-list li {
13
  display: flex;
14
}
15

16
.tab-list li:not(:last-child) {
17
  margin-bottom: 40px;
18
}
19

20
.tab-list a {
21
  display: inline-flex;
22
  align-items: center;
23
  gap: 24px;
24
  text-decoration: none;
25
}
26

27
.tab-list a .dot {
28
  position: relative;
29
  display: inline-block;
30
  width: 32px;
31
  height: 32px;
32
  border-radius: 50%;
33
  border: 1px solid var(--stepper-outline-color);
34
}
35

36
.tab-list li a .dot::before,
37
.tab-list li:not(:last-child) a .dot::after {
38
  content: "";
39
  position: absolute;
40
  left: 50%;
41
}
42

43
.tab-list li a .dot::before {
44
  top: 50%;
45
  transform: translate(-50%, -50%) scale(0);
46
  width: 18px;
47
  height: 18px;
48
  border-radius: 50%;
49
  background: var(--stepper-active-color);
50
  transition: transform 0.3s;
51
}
52

53
.tab-list li:not(:last-child) a .dot::after {
54
  top: calc(100% + 1px);
55
  transform: translateX(-50%);
56
  height: 40px;
57
  border-left: 2px dashed var(--stepper-connector-color);
58
}
59

60
.tab-list li.active a {
61
  font-weight: bold;
62
}
63

64
.tab-list li.active a .dot::before {
65
  transform: translate(-50%, -50%) scale(1);
66
}
67

68
.tab-panels {
69
  display: grid;
70
  overflow: hidden;
71
}
72

73
.tab-panels > li {
74
  grid-area: 1/1;
75
  opacity: 0;
76
  transform: translateX(-100%);
77
  transition: opacity 0.35s ease-in-out, transform 0.7s ease-in-out;
78
}
79

80
.tab-panels > li.active {
81
  opacity: 1;
82
  transform: none;
83
}
84

85
@media (max-width: 700px) {
86
  .grid {
87
    grid-template-columns: 1fr;
88
    gap: 30px;
89
  }
90

91
  .tab-list {
92
    display: flex;
93
    justify-content: center;
94
  }
95

96
  .tab-list li:not(:last-child) {
97
    margin: 0 40px 0 0;
98
  }
99

100
  .tab-list li a span:last-child {
101
    display: none;
102
  }
103

104
  .tab-list a {
105
    gap: 0;
106
  }
107

108
  .tab-list li:not(:last-child) a .dot::after {
109
    top: 50%;
110
    left: calc(100% + 1px);
111
    transform: translateY(-50%);
112
    width: 40px;
113
    height: auto;
114
    border-bottom: 2px dashed var(--stepper-connector-color);
115
    border-left: 0;
116
  }
117
}

3. Adicione o JavaScript

Cada vez que clicarmos em um link de guia, removeremos o active classe da guia e painel atualmente ativos. Em seguida, colocaremos essa classe na guia e no painel associado a esse link.

Aqui está o JavaScript necessário:

1
const tabList = document.querySelector(".tab-list");
2
const tabItems = tabList.querySelectorAll("li");
3
const tabLinks = tabList.querySelectorAll("a");
4
const tabPanelsList = document.querySelector(".tab-panels");
5
const tabPanels = tabPanelsList.querySelectorAll("li");
6
const ACTIVE_CLASS = "active";
7

8
for (const tabLink of tabLinks) {
9
  tabLink.addEventListener("click", function (e) {
10
    e.preventDefault();
11
    tabList.querySelector(`li.${ACTIVE_CLASS}`).classList.remove(ACTIVE_CLASS);
12
    tabPanelsList
13
      .querySelector(`li.${ACTIVE_CLASS}`)
14
      .classList.remove(ACTIVE_CLASS);
15

16
    const parent = tabLink.parentElement;
17
    let parentIndex = Array.from(tabItems).indexOf(parent);
18
    parent.classList.add(ACTIVE_CLASS);
19
    tabPanelsList
20
      .querySelector(`li:nth-child(${++parentIndex})`)
21
      .classList.add(ACTIVE_CLASS);
22
  });
23
}

Adicionar suporte para teclado

Embora nosso componente não esteja otimizado para acessibilidade, vamos adicionar suporte para navegação por teclado.

Em telas pequenas, cada vez que o esquerda (←) ou direita (→) teclas de seta são pressionados, pegaremos a guia atualmente ativa. A partir daí, verificaremos qual seta foi clicada. EUSe essa for a seta para a direita, definiremos a próxima guia ativa como aquela que segue imediatamente a guia ativa atual. Se não existir tal guia, a próxima guia se tornará a primeira. Da mesma forma, se a seta para a esquerda for clicada, definiremos a próxima guia como aquela que precede imediatamente a guia atualmente ativa. Se não existir tal guia, a próxima guia se tornará a última.

Seguiremos o mesmo processo com o para cima (↑) e para baixo (↓) teclas em telas grandes.

Aqui está o código JavaScript relevante:

1
...
2

3
tabList.addEventListener("keyup", function (e) {
4
  const activeTabListItem = tabList.querySelector(`li.${ACTIVE_CLASS}`);
5

6
  if (
7
    e.key === "ArrowUp" ||
8
    e.key === "ArrowDown" ||
9
    e.key === "ArrowLeft" ||
10
    e.key === "ArrowRight"
11
  ) {
12
    if (
13
      (mqSm.matches && (e.key === "ArrowUp" || e.key === "ArrowDown")) ||
14
      (mqLg.matches && (e.key === "ArrowLeft" || e.key === "ArrowRight"))
15
    ) {
16
      return;
17
    }
18

19
    if (e.key === "ArrowUp" || e.key === "ArrowLeft") {
20
      const prevActiveTabListItem = activeTabListItem.previousElementSibling
21
        ? activeTabListItem.previousElementSibling
22
        : lastTabListItem;
23
      prevActiveTabListItem.querySelector("a").click();
24
    } else {
25
      const nextActiveTabListItem = activeTabListItem.nextElementSibling
26
        ? activeTabListItem.nextElementSibling
27
        : firstTabListItem;
28
      nextActiveTabListItem.querySelector("a").click();
29
    }
30
  }
31
});

Conclusão

Parabéns, pessoal! Construímos este componente de guia JavaScript responsivo lindo e exclusivo sem escrever muito código. A partir daí, você pode usá-lo como está e torná-lo mais acessível verificando o código de um componente semelhante, como as guias do Bootstrap.

Alternativamente, você pode isolar o layout da lista de guias que se parece com um componente de passo e usá-lo como desejar, adicionando funcionalidade para setas de navegação, etc.

Antes de encerrar, vamos relembrar o que criamos hoje:

Como sempre, muito obrigado pela leitura!

Deixe uma resposta