# VelvetKit

## Select (`UI::Select`)

A styled dropdown with keyboard navigation and type-ahead, posting like a native select.

Add it: `bin/rails generate velvetkit:add select`

Also adds: `label`, `form_field`, `icon`

Stimulus: `ui--select`

Installs: `@floating-ui/dom`

### Arguments

```ruby
UI::Select.new(name: nil, options: [], selected: nil, prompt: nil, label: nil, hint: nil, size: :default, searchable: false, multiple: false, required: false, error: nil, disabled: false, form: nil, field: nil, class: nil, **html_attrs)
```

### Notes

UI::Select — a dropdown for choosing one value, or several, styled to match the kit.

The visible part is a WAI-ARIA "select-only combobox": a button with
role=combobox and a floating role=listbox. Focus stays on the button while
the arrow keys move through the options, as the pattern specifies.

The value lives in a visually hidden native <select>. That keeps everything a
form already relies on: the field submits under its name, `required` blocks
submission, form reset works, the form builder binds it, and anything
listening for `change` on it still hears one. The Stimulus controller only
mirrors the choice into it.

`searchable: true` adds a search box that filters the list; `multiple: true`
lets people pick several, posting `name[]` like a native multiple select.

UI::NativeSelect is the browser's own control, for when a phone's system
picker is the better fit.

Usage:
  <%= render(UI::Select.new(form: f, field: :plan, label: "Plan",
                            options: [["Free", "free"], ["Pro", "pro"]],
                            prompt: "Choose a plan")) %>

### Example: Default

```erb
<div class="max-w-xs">
  <%= render(UI::Select.new(name: "plan", label: "Plan", prompt: "Choose a plan",
                            options: [ [ "Free", "free" ], [ "Team", "team" ], [ "Business", "business" ] ])) %>
</div>
```

### Example: States

```erb
<div class="flex max-w-xs flex-col gap-5">
  <%= render(UI::Select.new(name: "a", label: "Selected", options: %w[Viewer Editor Admin], selected: "Editor")) %>
  <%= render(UI::Select.new(name: "b", label: "Required", options: %w[Viewer Editor Admin], prompt: "Choose a role", required: true)) %>
  <%= render(UI::Select.new(name: "c", label: "With an error", options: %w[Viewer Editor Admin], prompt: "Choose a role", error: true)) %>
  <%= render(UI::Select.new(name: "d", label: "Disabled", options: %w[Viewer Editor Admin], selected: "Viewer", disabled: true)) %>
</div>
```

### Example: Sizes

```erb
<div class="flex max-w-xs flex-col gap-5">
  <% %i[sm default lg].each do |size| %>
    <%= render(UI::Select.new(name: "size_#{size}", label: size.to_s, size: size, options: %w[Daily Weekly Monthly], selected: "Weekly")) %>
  <% end %>
</div>
```

### Example: Searchable

```erb
<%# Searchable: open it and type to filter. Arrow keys and Enter work from the search box. %>
<div class="max-w-xs">
  <%= render(UI::Select.new(name: "time_zone", label: "Time zone", prompt: "Choose a time zone", searchable: true,
                            options: %w[Auckland Bangkok Berlin Cairo Chicago Dubai Honolulu Istanbul Jakarta Kolkata Lagos Lima London Madrid Mexico\ City Moscow Nairobi New\ York Paris Santiago Seoul Singapore Sydney Tokyo Toronto])) %>
</div>
```

### Example: Multiple

```erb
<%# Stays open while you tick; past two choices the trigger counts them. %>
<div class="max-w-xs">
  <%= render(UI::Select.new(name: "reviewers", label: "Reviewers", prompt: "Add reviewers", multiple: true, searchable: true,
                            options: [ [ "Asha Kulkarni", 1 ], [ "Ravi Menon", 2 ], [ "Sara Joseph", 3 ], [ "Tomás Ortega", 4 ], [ "Mei Lin", 5 ] ],
                            selected: [ 1, 2 ])) %>
</div>
```
