Period picker

Selector de períodos para series temporales (períodos de planilla, meses fiscales, ciclos). Resuelve el problema que un typeahead no puede: la selección es temporal/estructural, no textual — nadie digita "la quincena pasada". La lista de un <select> nativo además crece linealmente con la antigüedad de los datos (una empresa quincenal acumula 24 períodos por año).

El DS distribuye solo el patrón visual: las clases .cl-period-picker* y el contrato de markup/ARIA de abajo. El comportamiento (navegación, teclado, estado ARIA) lo aporta la app consumidora con su propio JS.

Degradación sin JS

Renderizá server-side un <select class="cl-input"> nativo agrupado por mes dentro del wrapper y dejá que el JS de la app lo reemplace al conectar: sin JS el select agrupado sigue funcionando. El select queda oculto pero en el DOM (es el control que viaja en el form y recibe change); la barra y el panel los construye el JS. Cada <option> lleva la metadata que el JS necesita como data-attributes.

<div class="cl-period-picker">
  <select class="cl-input" name="pay_period_id">
    <option value="">Seleccione un período</option>
    <optgroup label="Julio 2026">
      <option value="42" data-start-date="2026-07-01" data-end-date="2026-07-15"
              data-status="borrador" data-status-label="Borrador"
              data-payment-date="15/07/2026">01–15 · Quincenal</option>
    </optgroup>
  </select>
</div>

Anatomía (post-enhancement)

Una barra [◀] [trigger ▾] [▶] y un panel popover (role="dialog"). Los steppers mueven la selección un período hacia atrás/adelante cronológicamente (deshabilitados en los extremos). El trigger muestra la selección vigente (etiqueta + badge de estado) y abre el panel: accesos rápidos, chips de año, grilla de meses 4×3 (los meses sin períodos deshabilitados) y la lista de períodos del mes con metadata secundaria. El demo muestra el estado abierto congelado (en la app el panel va position: fixed).

<div class="cl-period-picker">
  <select class="cl-input" name="pay_period_id" hidden></select>

  <div class="cl-period-picker-bar">
    <button type="button" class="cl-period-picker-step" aria-label="Período anterior">‹</button>
    <button type="button" class="cl-period-picker-trigger"
            aria-haspopup="dialog" aria-expanded="true">
      <span class="cl-period-picker-value">01–15 · Quincenal — Julio 2026</span>
      <span class="cl-badge cl-badge--warning">Borrador</span>
      <span class="cl-period-picker-caret">▾</span>
    </button>
    <button type="button" class="cl-period-picker-step" aria-label="Período siguiente" disabled>›</button>
  </div>

  <div class="cl-period-picker-panel" role="dialog" aria-label="Seleccionar período de pago">
    <div class="cl-period-picker-quick">
      <button type="button" class="cl-btn cl-btn--secondary cl-btn--sm">Período actual</button>
      <button type="button" class="cl-btn cl-btn--secondary cl-btn--sm">Anterior</button>
    </div>
    <div class="cl-period-picker-years" role="group" aria-label="Año">
      <button type="button" class="cl-period-picker-year">2025</button>
      <button type="button" class="cl-period-picker-year is-active">2026</button>
    </div>
    <div class="cl-period-picker-months" role="group" aria-label="Mes">
      <button type="button" class="cl-period-picker-month">Ene</button>
      <!-- … -->
      <button type="button" class="cl-period-picker-month is-active">Jul</button>
      <button type="button" class="cl-period-picker-month" disabled>Ago</button>
    </div>
    <ul class="cl-period-picker-list">
      <li>
        <button type="button" class="cl-period-picker-option is-selected" aria-current="true">
          <span>01–15 · Quincenal</span>
          <span class="cl-period-picker-option-meta">Borrador · pago 15/07/2026</span>
        </button>
      </li>
    </ul>
  </div>
</div>

Estado vacío

Sin períodos disponibles, el panel muestra una única fila no interactiva con .cl-period-picker-empty.

<div class="cl-period-picker-panel" role="dialog">
  <p class="cl-period-picker-empty">No hay períodos disponibles</p>
</div>

Contrato de comportamiento (lo implementa la app)