(85) 99645-7140 nelclassico@gmail.com Praça Coronel Melquiades, 124
Tecnologia

Selecionando um intervalo de datas em CSS

Um seletor de intervalo de datas permite que os usuários escolham um período entre uma data de início e uma data de término, o que é útil para reservar viagens, classificar informações por blocos de datas, escolher intervalos de tempo e planejar programações.

A calendar month layout with the dates 8 and 18 selected with black backgrounds.
Exemplo retirado do Airbnb

Vou mostrar um exemplo onde, mesmo que JavaScript esteja envolvido, a maior parte do trabalho é feita pela sintaxe “n de seletor(es)” do seletor CSS :nth-child, facilitando a construção da seleção de intervalo.

 

CodePen Incorporar Fallback

A sintaxe “n do seletor”

Esta sintaxe do :nth-child seletor filtra os elementos por um determinado seletor primeiro entre todos os elementos filhos, antes de selecioná-los por uma ordem de contagem .

<p>The reclamation of land...</p>
<p>The first reclamations can be traced...</p>
<p class="accent">By 1996, a total of...</p>
<p>Much reclamation has taken...</p>
<p class="accent">Hong Kong legislators...</p>
.accent {
  color: red;
}
.accent:nth-child(2) {
  font-weight: bold; /* does not work */
}
:nth-child(2 of .accent){
  text-decoration: underline;
}
CodePen Incorporar Fallback

Existem dois parágrafos <p>The reclamation of land...</p>
<p>The first reclamations can be traced...</p>
<p class="accent">By 1996, a total of...</p>
<p>Much reclamation has taken...</p>
<p class="accent">Hong Kong legislators...</p>
-ed com .accent {
color: red;
}
.accent:nth-child(2) {
font-weight: bold; /* does not work */
}
:nth-child(2 of .accent){
text-decoration: underline;
}
texto. À medida que tentamos direcionar o segundo parágrafo acentuado, .accent falha ao selecioná-lo porque está tentando encontrar um <p>The reclamation of land...</p>
<p>The first reclamations can be traced...</p>
<p class="accent">By 1996, a total of...</p>
<p>Much reclamation has taken...</p>
<p class="accent">Hong Kong legislators...</p>
elemento que é o segundo filho de seu pai .

Considerando que, .accent:nth-child(2) consegue selecionar e estilizar o segundo parágrafo acentuado porque está procurando apenas o segundo elemento entre o .accent elementos em vez do segundo de todos os filhos.

O layout

Passando para nosso exemplo principal, vamos montar um layout de mês. São necessárias apenas algumas linhas de CSS.

<ul id="calendar">
  <li class="day">Mon</li>
  <li class="day">Tue</li>
  <!-- up to Sat -->
  <li class="date">01<input type="checkbox" value="01"></li>
  <li class="date">02<input type="checkbox" value="02"></li>
  <!-- up to 31  -->
</ul>
#calendar {
  display: grid;
  grid-template-columns: repeat(7, 1fr); /* 7 for no. of days in a week */
}
CodePen Incorporar Fallback

Escolha apenas duas datas

Agora é quando recorremos ao JavaScript, já que não podemos marcar/desmarcar um controle em CSS. Mas mesmo aqui a sintaxe “n do seletor” pode ser muito útil.

Quando escolhemos duas datas para criar um intervalo, clicar em uma terceira data atualizará o intervalo e removerá uma das datas anteriores.

Você pode configurar a lógica de reajuste de faixa da maneira que desejar. Estou usando esta abordagem: se a terceira data for anterior ou posterior à último retorno data, ela se tornará a nova data de retorno , e a antiga será desmarcada. Se a terceira data for anterior à última data posterior, ela se tornará a nova data posterior e a antiga será desmarcada.

const CAL = document.getElementById('calendar');
const DT = Array.from(CAL.getElementsByClassName('date')); 

CAL.addEventListener('change', e => {
  if (!CAL.querySelector(':checked')) return;
  
  /* When there are two checked boxes, calendar gets 'isRangeSelected' class  */
  CAL.className = CAL.querySelector(':nth-child(2 of :has(:checked))') ? 'isRangeSelected':'';

  /* When there are three checked boxes */
  if (CAL.querySelector(':nth-child(3 of :has(:checked))')) {

    switch (DT.indexOf(e.target.parentElement)) {

      /* If the newly checked date is first among the checked ones, 
          the second checked is unchecked. Onward date moved earlier. */
      case DT.indexOf(CAL.querySelector(':nth-child(1 of :has(:checked))')):
      CAL.querySelector(':nth-child(2 of :has(:checked)) input').checked = 0; 
      break;

      /* If the newly checked date is second among the checked ones, 
          the third checked is unchecked. Return date moved earlier. */
      case DT.indexOf(CAL.querySelector(':nth-child(2 of :has(:checked))')):
      CAL.querySelector(':nth-child(3 of :has(:checked)) input').checked = 0; 
      break;

      /* If the newly checked date is third among the checked ones, 
          the second checked is unchecked. Return date moved later. */
      case DT.indexOf(CAL.querySelector(':nth-child(3 of :has(:checked))')):
      CAL.querySelector(':nth-child(2 of :has(:checked)) input').checked = 0; 
      break;

    }
  }
});

Primeiro, obtemos o índice da data atual verificada (:nth-child(2 of .accent)), depois vemos se é igual ao primeiro verificado entre todos os verificados (**.accent**), segundo (<ul id="calendar">
<li class="day">Mon</li>
<li class="day">Tue</li>
<!-- up to Sat -->
<li class="date">01<input type="checkbox" value="01"></li>
<li class="date">02<input type="checkbox" value="02"></li>
<!-- up to 31 -->
</ul>
) ou terceiro (#calendar {
display: grid;
grid-template-columns: repeat(7, 1fr); /* 7 for no. of days in a week */
}
). Diante disso, desmarcamos a caixa relevante para revisar o intervalo de datas.

Você notará que usando a sintaxe “n do seletor”, visando a caixa const CAL = document.getElementById('calendar');
const DT = Array.from(CAL.getElementsByClassName('date'));

CAL.addEventListener('change', e => {
if (!CAL.querySelector(':checked')) return;

/* When there are two checked boxes, calendar gets 'isRangeSelected' class */
CAL.className = CAL.querySelector(':nth-child(2 of :has(:checked))') ? 'isRangeSelected':'';

/* When there are three checked boxes */
if (CAL.querySelector(':nth-child(3 of :has(:checked))')) {

switch (DT.indexOf(e.target.parentElement)) {

/* If the newly checked date is first among the checked ones,
the second checked is unchecked. Onward date moved earlier. */
case DT.indexOf(CAL.querySelector(':nth-child(1 of :has(:checked))')):
CAL.querySelector(':nth-child(2 of :has(:checked)) input').checked = 0;
break;

/* If the newly checked date is second among the checked ones,
the third checked is unchecked. Return date moved earlier. */
case DT.indexOf(CAL.querySelector(':nth-child(2 of :has(:checked))')):
CAL.querySelector(':nth-child(3 of :has(:checked)) input').checked = 0;
break;

/* If the newly checked date is third among the checked ones,
the second checked is unchecked. Return date moved later. */
case DT.indexOf(CAL.querySelector(':nth-child(3 of :has(:checked))')):
CAL.querySelector(':nth-child(2 of :has(:checked)) input').checked = 0;
break;

}
}
});

que queremos por sua posição entre todas as marcadas é muito mais simples – em vez de indexar por meio de uma lista de marcadas datas em JavaScript para isso, podemos selecioná-lo diretamente.

Estilizar a linha é ainda mais fácil do que isso.

Estilizando o intervalo

/* When two dates are selected */
.isRangeSelected { 
  /* Dates following the first but not the second of selected */
  :nth-child(1 of :has(:checked)) ~ :not(:nth-child(2 of :has(:checked)) ~ .date) {
    /* Range color */
    background-color: rgb(228 239 253); 
  }
}

Quando há duas datas escolhidas, as datas entre a primeira (DT.indexOf(e.target.parentElement)) e a segunda (:nth-child(1 of :has(:checked))) são coloridas em azul claro, criando um alcance visual para esse bloco de datas no mês.

A cor é declarada dentro de um seletor composto que seleciona datas (:nth-child(2 of :has(:checked))) após a primeira de todas as datas verificadas (**.accent**), mas não a segunda de todas as datas verificadas (:checked).

Aqui está o exemplo completo mais uma vez:

CodePen Incorporar Fallback

Selecionando um intervalo de datas em CSS publicado originalmente em CSS-Tricks , que faz parte da família DigitalOcean

Deixe um comentário

Seu email não será publicado. Campos obrigatórios marcados com *

Este site utiliza o Akismet para reduzir spam. Saiba como seus dados em comentários são processados.