# `MishkaGervaz.UIAdapters.Tailwind`
[🔗](https://github.com/mishka-group/mishka_gervaz/blob/v0.0.2/lib/mishka_gervaz/ui_adapters/tailwind.ex#L1)

Default Tailwind CSS UI adapter.

Provides plain Tailwind-styled components for tables and forms.
This is the default adapter used when no other is specified.

## Loading variants

A row action button dims and stops taking clicks while its click is pending, through the
`phx-click-loading:` variant. The host's stylesheet declares it, as a new Phoenix app's does:

    @custom-variant phx-click-loading (.phx-click-loading&, .phx-click-loading &);

# `alert`

Render a static alert/notice.

## Assigns
  * `:type` - `:info` | `:warning` | `:error` | `:success` | `:neutral`
  * `:title` - optional headline string
  * `:content` - body string or pre-rendered inner block
  * `:icon` - optional heroicon name
  * `:dismissible` - boolean
  * `:dismiss_event` - phx-click event name for the dismiss button
  * `:dismiss_value` - phx-value-name payload for dismiss
  * `:phx_target` - LiveComponent target
  * `:class` - extra wrapper classes
  * `:inner_block` - optional slot for HEEx content (overrides `:content`)

# `archive_toggle`

Render archive status toggle.

## Assigns
  * `:archive_status` - Current status (:active or :archived)
  * `:myself` - LiveComponent target
  * `:active_label` - Label for active option
  * `:archived_label` - Label for archived option
  * `:status_label` - Label prefix

# `bulk_action_bar`

Render bulk actions bar container.

## Assigns
  * `:select_all` - Whether all are selected
  * `:selected_count` - Number of selected items
  * `:excluded_count` - Number of excluded items
  * `:inner_block` - Slot for action buttons

# `bulk_action_button`

Render individual bulk action button.

## Assigns
  * `:action` - The action map
  * `:myself` - LiveComponent target
  * `:count` - The number of rows the action runs on, or `nil` when the table cannot tell.
    `{count}` in the action's `confirm` becomes this number.

# `button`

## Attributes

* `label` (`:string`) - Defaults to `nil`.
* `class` (`:string`) - Defaults to `nil`.
* `type` (`:string`) - Defaults to `"button"`.
* `icon` (`:string`) - Defaults to `nil`.
* `variant` (`:atom`) - Defaults to `:default`.
* Global attributes are accepted. Supports all globals plus: `["phx-click", "phx-target", "phx-value-id", "phx-value-event", "phx-value-values", "data-confirm", "disabled"]`.

# `cell_array`

Render array/list container.

## Assigns
  * `:class` - Container CSS class (default: "flex flex-wrap gap-1")
  * `:badges` - List of pre-rendered badge elements (for badge mode)
  * `:remaining` - Pre-rendered remaining count element
  * `:inner_block` - Slot for array items (alternative to badges)

# `cell_avatars`

Render an overlapping avatar stack with a `+N` inline expand/collapse toggle.

The people counterpart to `cell_tags/1`, and it behaves identically: `:shown` faces overlap, then a
`+N` disc stands for the `:rest`. Clicking `+N` reveals the rest in place (the disc swaps to a `−`
collapse handle); clicking `−`, or anywhere outside, collapses. The toggle is a pure
`Phoenix.LiveView.JS` class swap, so it survives the table's `overflow-x-auto` wrapper uncut.

Each entry is a map, so a caller can hand over whatever it has: `:image` draws a photo, and its
absence falls back to `:initial` on a tinted disc — `:tint` is the caller's, since the tint usually
belongs to the page's own palette. `:label` becomes the face's `title`.

## Assigns
  * `:id` - Stable, unique element id (namespaces the toggle targets)
  * `:shown` - Entries always visible, `%{image:, initial:, label:, tint:}`
  * `:rest` - Entries revealed on expand, same shape
  * `:more` - Count for the `+N` disc (`0` hides the toggle)
  * `:size` - Disc size class (default `"size-[26px]"`)
  * `:empty` - Text shown when there are no entries (default `"—"`)

# `cell_bars`

Bar-gauge cell: a numeric value drawn as `max` bars (the first `filled` in `color`, the rest muted)
followed by the number. Used by the `:bars` column type.

## Assigns
  * `:value`  - the number shown after the bars
  * `:filled` - how many bars are lit
  * `:max`    - total number of bars
  * `:color`  - hex string (e.g. `"#1f9d6b"`) for the lit bars and the number

The colour rides an inline `style`, not a Tailwind class: it comes from the column's `:scale`
option, chosen per row from the cell's value, so there is no literal for Tailwind to find.

# `cell_code`

Render code/monospace cell value (for UUID, etc.). It always reads left to right.

## Assigns
  * `:value` - The value to display
  * `:title` - Optional tooltip (for truncated values)
  * `:class` - CSS class (default: "text-xs font-mono")

# `cell_date`

Render date cell value.

## Assigns
  * `:formatted` - The formatted date string
  * `:class` - CSS class

# `cell_datetime`

Render datetime cell value.

## Assigns
  * `:formatted` - The formatted datetime string
  * `:iso` - ISO 8601 string for datetime attribute
  * `:variant` - :default or :relative
  * `:class` - CSS class

# `cell_empty`

Render empty cell value (nil/missing data).

## Assigns
  * `:text` - Text to show (default: "-")
  * `:class` - CSS class (default: "text-[#a8a5a0]")

# `cell_number`

Render number cell value.

## Assigns
  * `:value` - The formatted number string
  * `:prefix` - Prefix (e.g., "$")
  * `:suffix` - Suffix (e.g., "%")
  * `:class` - CSS class (default: "tabular-nums")

# `cell_stacked`

A two-line cell: a primary line over a quieter secondary one.

The shape almost every admin row leads with — a name over its slug, a site over its date, a person
over their role. A resource hands over the two strings and this decides how they look.

`:title` is the row's headline — the thing you scan a column for — and `:meta` is a supporting pair
that should not compete with it. A leading `:icon` indents the secondary line under the text rather
than under the icon, so the two lines read as one block.

## Assigns
  * `:primary` - the top line
  * `:secondary` - the line beneath it (omitted when nil or "")
  * `:icon` - icon name shown before the primary line
  * `:copy` - `%{id: , value: }` to trail the secondary line with a copy button
  * `:variant` - `:title` (default) or `:meta`
  * `:empty` - text when there is no primary line at all (default `"—"`)

# `cell_stats`

A wrapping row of icon+count markers, optionally followed by chips.

What a row's numbers collapse into once they stop deserving a column each — comments, versions,
contributors, words. The row wraps rather than overflowing, so a narrow column costs a second line
instead of a scrollbar.

An item may carry its own `:class` to colour a count that means something (drafts pending review),
and a chip is for the short labels that ride alongside the numbers, such as a language or a version.

## Assigns
  * `:items` - `[%{icon:, value:, label:, class:}]`; `:label` becomes the marker's `title`
  * `:chips` - `[%{text:, class:}]` rendered after the counts
  * `:empty` - text when there is nothing at all (default `"—"`)

# `cell_tags`

Render a list of tags/chips with a `+N` inline expand/collapse toggle.

Shows `:shown` chips, then a `+N` chip for the `:rest`. Clicking `+N` reveals the rest inline (the
`+N` swaps to a `- less` collapse handle); clicking it again — or anywhere outside — collapses.
The toggle is a `Phoenix.LiveView.JS` class swap, so it is never clipped by the table's
`overflow-x-auto` wrapper.

## Assigns
  * `:id` - Stable, unique element id (namespaces the toggle targets)
  * `:shown` - Chips always visible
  * `:rest` - Chips revealed on expand
  * `:more` - Count for the `+N` chip (`0` hides the toggle)
  * `:badge_class` - Chip CSS class
  * `:empty` - Text shown when there are no chips (default `"—"`)

A chip is a string, or a map for a chip that looks unlike its neighbours:
`%{label: "FA", class: "…", title: "…"}` — `:class` replaces `:badge_class` for that chip alone,
`:title` becomes its tooltip, and both may be left out.

# `cell_text`

Render text cell value.

## Assigns
  * `:text` - The text to display
  * `:title` - Optional tooltip (for truncated text)
  * `:class` - CSS class
  * `:dir` - `"ltr"`, `"rtl"` or `"auto"`; left out, a text of printable ASCII with no space
    (an email, a URL, an ID, a slug) reads left to right and anything else follows the page
  * `:suffix` - Optional suffix text (with different styling)
  * `:suffix_class` - CSS class for suffix (default: "text-[#8a877f]")

# `copy_button`

Copy-to-clipboard button for a short value (e.g. an id). Requires the consuming app to register a
`CopyToClipboard` JS hook; reads the text from `data-copy`.

## Assigns
  * `:value` - text placed on the clipboard
  * `:id` - DOM id (must be unique per row)
  * `:label` - tooltip / a11y title (default "Copy")

# `date_input`

A date input.

Given `:picker`, as a form's `:date` field is, it is a calendar the form draws instead of the
browser's: a button showing the date, and under it the month and year, a step a month or a year
either way, and the days. `:picker` is `%{month: month}`, the first day of the month shown, or
`%{month: nil}` while it is closed; `:field_name` and `:target` name the field and the form its
`picker_*` events go to. The value is in a hidden input of `:name`.

## Assigns
  * `:name`, `:value`, `:id`, `:min`, `:max` - the input's attributes
  * `:search` - `true` draws the filter bar's input instead of the form's
  * `:icon` - an icon inside the input's leading edge
  * `:aria_label` - the input's accessible name, for an input without a `<label>` of its own
  * `:disabled`, `:readonly` - switch the input off
  * `:class` - replaces the input's classes
  * `:picker`, `:field_name`, `:target` - the form's calendar, above

# `date_range_container`

Render a date range: the two date inputs side by side with an arrow between them, each at least
140px wide. In a space narrower than both, the second input moves to the next line.

## Assigns
  * `:class` - Container CSS class (default: "flex flex-wrap items-center gap-2")
  * `:separator_class` - The arrow's CSS class (default: "text-[11px] font-semibold text-[#bcb9b2]")
  * `:from_input` - Pre-rendered from date input
  * `:to_input` - Pre-rendered to date input

# `datetime_input`

A date and time input.

Given `:picker`, as a form's `:datetime` field is, it is the calendar `date_input/1` draws, with
the hour and the minute under the days — sent as `picker_time` — and a Done that closes it.

# `disabled_class`

```elixir
@spec disabled_class() :: String.t()
```

The classes every field of this adapter wears when it is disabled or readonly.

Call it from an adapter that overrides one input, to switch it off the way its neighbours are.

# `dropdown`

Render a dropdown menu: a 30px trigger that shows and hides the menu below it, and a click
anywhere else that hides it.

The trigger carries `aria-expanded`, `"true"` while the menu is shown. A table's sticky Actions
cell is drawn above the rows after it while a control inside it has `aria-expanded="true"`, so a
custom adapter's `dropdown/1` sets it too for its menu to open over those rows.

## Assigns
  * `:icon` - The trigger's icon (default: "hero-ellipsis-vertical")
  * `:inner_block` - The menu's items

# `field_wrapper`

Render a field wrapper with label, input, error display and help text.

## Assigns
  * `:label` - label text above the input
  * `:required` - whether to mark the label required
  * `:field_name` - the input's id, used as the label's `for`; leave it out for a control that is
    not one labelable element, and name the control with `:label_id` instead
  * `:label_id` - the label's own id, for a control that names itself with `aria-labelledby`
  * `:errors` - error messages, rendered below the input
  * `:description` - help text, rendered below the errors
  * `:class` - extra wrapper classes

# `filter_reset_button`

Render filter reset/clear button.

## Assigns
  * `:label` - Button label text
  * `:class` - CSS class

# `form_footer`

Render the form footer (content below the submit row).

## Assigns
  * `:content` - footer text
  * `:class` - extra wrapper classes
  * `:inner_block` - optional slot

# `form_header`

Render the form header (title + description).

## Assigns
  * `:title` - heading text
  * `:description` - subtitle text
  * `:icon` - optional heroicon name
  * `:class` - extra wrapper classes

# `icon`

## Attributes

* `variant` (`:atom`) - Defaults to `:default`.

# `load_more_select`

Single-select dropdown with paginated load-more (no search input).

Shows a clickable trigger that opens a dropdown with options and a
"Load more" button for pagination.

The trigger carries `aria-expanded`, `"true"` while the list is open.

# `loading`

Render a loading state.

This is an optional function that templates can use for customizable loading UI.
Supports `:spinner` (default), `:skeleton`, and `:dots` types.

## Assigns

  * `:type` - Loading type: `:initial`, `:reset`, `:more` (default: `:initial`)
  * `:style` - Loading style: `:spinner`, `:skeleton`, `:dots` (default: `:spinner`)
  * `:text` - Optional loading text
  * `:class` - Additional CSS classes

## Examples

    <.loading type={:initial} />
    <.loading type={:reset} style={:skeleton} />

# `loading_state`

Render loading state.

## Assigns
  * `:type` - Loading type (:initial, :reset, :more)
  * `:text` - Loading text
  * `:style` - Style (:spinner, :skeleton, :dots)

# `multi_select`

Multi-select dropdown with search support for relation filters.

Shows a searchable dropdown where users can select multiple items.
Selected items appear with checkmarks in the dropdown. With `:show_selected`, they also appear as
chips under the input while it is closed, each with a button that removes it; a chip reads its
label from `:selected_options`, or the value when its label is not known.

The input carries `aria-expanded`, `"true"` while the list is open.

# `multiline_class`

```elixir
@spec multiline_class(String.t()) :: String.t()
```

The same field as `input_class/1`, for a control that grows down the page instead of holding one
line. Everything but the height is shared; `rows` decides the height, so `extra` carries it. It
is a block, so the ring a field with an error is wrapped in fits it.

# `nav_link`

## Attributes

* `variant` (`:atom`) - Defaults to `:default`.
* `external` (`:boolean`) - Defaults to `false`.

# `pagination_container`

Render pagination container with page info.

## Assigns
  * `:page` - Current page
  * `:total_pages` - Total pages
  * `:total_count` - Total record count
  * `:show_total` - Whether to show total info
  * `:page_info_format` - Format string for page info
  * `:inner_block` - Slot for pagination buttons

# `pagination_nav_button`

Render pagination nav button (prev/next/first/last).

## Assigns
  * `:label` - Button label
  * `:disabled` - Whether button is disabled
  * `:event` - Event name to trigger
  * `:page` - Page value (for go_to_page)
  * `:myself` - LiveComponent target

# `pagination_page_button`

Render pagination page number button.

## Assigns
  * `:page_num` - Page number to display
  * `:current_page` - Currently active page
  * `:disabled` - Whether button is disabled
  * `:myself` - LiveComponent target

# `primary_button_class`

```elixir
@spec primary_button_class(boolean()) :: list()
```

The form's primary button — the same gradient the submit row wears.

Call it from a template drawing its own navigation, so its primary button matches the submit row.

# `search_select`

Single-select dropdown with search support for relation filters.

`multi_select` adapted for single selection. Whatever is currently selected is merged in from
`:selected_options` and sorted to the top of the list, so a value restored from the URL is shown
even when it is not on the loaded page of options.

Options may be grouped as `select/1`'s are, `{group_label, [options]}`: each group is drawn under
its label, and a grouped option reads `"Group · Label"` once picked.

The input carries `aria-expanded`, `"true"` while the list is open.

# `secondary_button_class`

```elixir
@spec secondary_button_class() :: String.t()
```

The bordered companion to `primary_button_class/1` — Back, Cancel, and anything else that sits
beside the primary action without competing with it.

# `switcher_icon_class`

```elixir
@spec switcher_icon_class(String.t()) :: String.t()
```

The class that draws a switcher button's glyph, written literally per icon name so the runtime CSS
compiler emits its mask. Falls back to the raw name for any icon not named here (which the compiler
only sees if it appears literally elsewhere in the source).

# `template_switcher`

The list/grid switch — one recessed tray with the active view lifted onto a white tile. The single
switcher every admin page (and every `fallback: __MODULE__` adapter) uses.

The buttons are inlined here rather than drawn as a child component, and each glyph goes through
`switcher_icon_class/1` so its name reaches the CSS compiler as a literal.

## Assigns
  * `:switchable_templates` - List of template modules
  * `:current_template` - Currently active template module
  * `:myself` - LiveComponent target

# `template_switcher_button`

A single switcher button, for the behaviour's `template_switcher_button` callback.
`template_switcher/1` inlines its own buttons rather than calling this.

# `upload_progress`

Upload progress bar: the entry's filename, its percentage, and a track filled to that percentage.

## Assigns
  * `:entry` - a `Phoenix.LiveView.UploadEntry`
  * `:class` - wrapper classes (default `"w-full"`)

The fill width rides an inline `style`, not a Tailwind class, as in `cell_bars/1`: the percentage
arrives from LiveView's upload state as the bytes land, so there is no literal for Tailwind to
find.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
