Formularios

Estado: Beta

Select nativo

El <select> de siempre (lo que el admin manda y lo que en el teléfono abre el selector del sistema) con la superficie de los campos, un chevron propio que gira al abrir y el placeholder tenue mientras no hay valor. Agrupa opciones con un Hash y toma valor y error del form builder. Para una lista animada con descripciones está el Select con resorte.

Quien suple aparece en el horario público.

app/views/… (la demo de arriba)

<%# El equipo va agrupado (Hash → <optgroup>); el nivel recibe un Hash de
    etiqueta → valor. El último es un f.select suelto con
    Ui::SelectComponent.classes (chevron en CSS). %>
<div class="@container w-full max-w-3xl">
  <div class="grid gap-x-8 gap-y-5 @xl:grid-cols-2">
    <%= render Ui::SelectComponent.new(name: "clase[template_id]", id: "select-demo-template", label: "Plantilla de clase",
                                       prompt: "Elige una plantilla", required: true,
                                       options: [ [ "Vinyasa Flow · 60 min", 1 ], [ "Reformer Básico · 50 min", 2 ], [ "Barre Sculpt · 45 min", 3 ], [ "Yin & Meditación · 75 min", 4 ] ]) %>
    <%= render Ui::SelectComponent.new(name: "clase[host_id]", id: "select-demo-teacher", label: "Quién la da", selected: 12,
                                       hint: "Quien suple aparece en el horario público.",
                                       options: {
                                         "Titulares" => [ [ "Ana Robles", 11 ], [ "Mariana Ortiz", 12 ], [ "Sofía Treviño", 13 ] ],
                                         "Suplentes" => [ [ "Lucía Gamboa", 21 ], [ "Renata Lira", 22, { disabled: true } ] ]
                                       }) %>
    <div class="hidden grid-cols-[1fr_auto] items-end gap-3 @xl:col-span-2 @xl:grid">
      <%= render Ui::SelectComponent.new(name: "clase[level]", id: "select-demo-level", label: "Nivel", selected: "multinivel",
                                         options: { "Todos los niveles" => "all_levels", "Multinivel" => "multinivel", "Int-Avanzado" => "int_avanzado" }) %>
      <label class="grid gap-1.5 text-sm font-medium text-foreground" for="select-demo-sort">
        Ordenar
        <select id="select-demo-sort" name="sort" class="<%= Ui::SelectComponent.classes(size: :md, extra: "w-40") %>">
          <option>Más recientes</option>
          <option>Por fecha de clase</option>
          <option>Por maestra</option>
        </select>
      </label>
    </div>
  </div>
</div>

Qué hace

  • Ui::SelectComponent.classes(invalid:, size:) viste un f.select o f.collection_select suelto; ahí el chevron va dibujado con gradientes que siguen los tokens.
  • El placeholder se ve tenue con :has(option[value=""]:checked): sin JS.
  • El chevron se aclara al pasar y gira mientras la lista del sistema está abierta (donde el navegador soporta :open).
  • Las opciones y el selector del sistema siguen el modo del tema (color-scheme).

Props

Nombre Tipo Default Descripción
name String nil Nombre del campo. Con form: + attribute: sale del builder.
options Array | Hash [] Como options_for_select: [ "a" ], [ [ "Etiqueta", valor, { disabled: true } ] ]; un Hash de listas son grupos (<optgroup>).
label String nil Etiqueta visible (sin ella queda oculta y nombrada).
selected String nil Valor elegido al cargar (con form: sale del modelo).
include_blank Boolean | String nil Opción vacía siempre presente (tenue mientras está elegida).
prompt String nil Como include_blank pero solo mientras no hay valor (como en Rails).
form: + attribute: FormBuilder, Symbol nil Usa f.select: nombre, id, valor y error del modelo.
hint / error String nil Ayuda y error (el del modelo se toma solo).
required / optional / disabled Boolean false Asterisco, Opcional o deshabilitado.
size :sm | :md | :lg :md Alto del control.
**attrs Hash {} id, data, aria, multiple… van al <select>; class: se suma a la superficie.

Accesibilidad