MishkaGervaz.UIAdapters.Tailwind (MishkaGervaz v0.0.2)

Copy Markdown View Source

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 &);

Summary

Functions

Render a static alert/notice.

Render archive status toggle.

Render bulk actions bar container.

Render individual bulk action button.

Render array/list container.

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

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.

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

Render date cell value.

Render datetime cell value.

Render empty cell value (nil/missing data).

Render number cell value.

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

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

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

Render text cell value.

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.

A date input.

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.

A date and time input.

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

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

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

Render filter reset/clear button.

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

Render the form header (title + description).

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

Render a loading state.

Render loading state.

Multi-select dropdown with search support for relation filters.

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.

Render pagination container with page info.

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

Render pagination page number button.

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

Single-select dropdown with search support for relation filters.

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

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).

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.

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 bar: the entry's filename, its percentage, and a track filled to that percentage.

Functions

alert(assigns)

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(assigns)

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(assigns)

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(assigns)

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(assigns)

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(assigns)

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(assigns)

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(assigns)

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(assigns)

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(assigns)

Render date cell value.

Assigns

  • :formatted - The formatted date string
  • :class - CSS class

cell_datetime(assigns)

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(assigns)

Render empty cell value (nil/missing data).

Assigns

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

cell_number(assigns)

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(assigns)

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(assigns)

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(assigns)

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(assigns)

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(assigns)

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(assigns)

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(assigns)

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(assigns)

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()

@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.

field_wrapper(assigns)

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(assigns)

Render filter reset/clear button.

Assigns

  • :label - Button label text
  • :class - CSS class

form_footer(assigns)

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

Assigns

  • :content - footer text
  • :class - extra wrapper classes
  • :inner_block - optional slot

form_header(assigns)

Render the form header (title + description).

Assigns

  • :title - heading text
  • :description - subtitle text
  • :icon - optional heroicon name
  • :class - extra wrapper classes

icon(assigns)

Attributes

  • variant (:atom) - Defaults to :default.

load_more_select(assigns)

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(assigns)

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(assigns)

Render loading state.

Assigns

  • :type - Loading type (:initial, :reset, :more)
  • :text - Loading text
  • :style - Style (:spinner, :skeleton, :dots)

multi_select(assigns)

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(extra)

@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.

pagination_container(assigns)

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(assigns)

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(assigns)

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(enabled?)

@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(assigns)

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()

@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(name)

@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(assigns)

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(assigns)

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(assigns)

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

Assigns

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.