# `MishkaCmsCoreWeb.UIAdapters.Admin`
[🔗](https://github.com/mishka-group/mishka_cms_core/blob/v0.0.1/lib/mishka_cms_core_web/ui_adapters/admin.ex#L5)

UI adapter for the admin card pages, extending the Tailwind adapter.

Only the pieces the design redraws differ — the row-action `button`, the `nav_link`, and its
`glyph` — `textarea/1`, which is a code editor or a rich-text editor when the field asks for one,
and `text_input/1`, which is a picture from the media library when the field asks for one. Everything else falls back, including the view switcher (`template_switcher/1`), which now
lives in the Tailwind adapter as the single switcher every admin page shares.

Declared via `presentation do ui_adapter … end` by the resources across the extensions whose pages
draw these cards, and paired with `MishkaCmsCoreWeb.Templates.CardChrome`.

# `alert`

# `archive_toggle`

# `array_fields`

# `badge`

# `bulk_action_bar`

# `bulk_action_button`

# `button`

A row-action button, drawn with this design's glyphs at this design's size.

The label always reaches the DOM — as `title`, and as a `.lbl` span an action's `ui do class end`
hides with `[&_.lbl]:hidden` — but so does the Tailwind adapter's, so that is not why this exists.
It exists for `glyph/1`: the fallback renders any name that is not a heroicon as an empty span, and
`MishkaBlog.Comment` asks for `icon "reply"`. It also pins hero icons to the 15px the cards' action
squares are drawn around, rather than the fallback's 16px.

Retire this the day gervaz can render a non-heroicon name and the 1px is settled — and retire it
together with `nav_link/1`, never one alone, or a card's link actions and its button actions would
wear different icon sizes side by side in the same row.

## 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`

# `cell_avatars`

# `cell_bars`

# `cell_code`

# `cell_date`

# `cell_datetime`

# `cell_empty`

# `cell_number`

# `cell_stacked`

# `cell_stats`

# `cell_tags`

# `cell_text`

# `checkbox`

# `combobox`

# `copy_button`

# `date_input`

# `date_range_container`

# `datetime_input`

# `dropdown`

# `empty_state`

# `error_state`

# `field_error`

# `field_group`

# `field_wrapper`

# `filter_reset_button`

# `form_container`

# `form_footer`

# `form_header`

# `glyph`

A row-action's glyph, from its `ui do icon … end` name.

A `hero-*` name renders the heroicon; any other name is a design glyph the icon set has no match for,
drawn inline here — the row-action contract passes a string, so an SVG cannot travel through the
resource DSL, and this is where it lands instead. Each inline glyph is sized to 13px to match what
the card's action class shrinks the heroicons to. Add a clause per glyph the design needs.

## Attributes

* `name` (`:string`) (required)

# `icon`

# `json_editor`

# `load_more_select`

# `loading_state`

# `multi_select`

# `nav_link`

A `:link` row action, drawn the same way `button/1` is — the card's row mixes the two.

An external link leaves the admin, so it opens in its own tab and disowns this one, exactly as the
Tailwind adapter does it. `MishkaGervaz.Table.Types.Column.Link` is what sets `external`.

## Attributes

* `label` (`:string`) - Defaults to `nil`.
* `class` (`:string`) - Defaults to `nil`.
* `icon` (`:string`) - Defaults to `nil`.
* `navigate` (`:string`) - Defaults to `nil`.
* `variant` (`:atom`) - Defaults to `:default`.
* `external` (`:boolean`) - Defaults to `false`.

# `nested_fields`

# `number_input`

# `pagination_container`

# `pagination_nav_button`

# `pagination_page_button`

# `password_input`

# `range_input`

# `search_select`

# `select`

# `spinner`

# `step_indicator`

# `step_navigation`

# `string_list_input`

# `table`

# `table_header`

# `td`

# `template_switcher`

# `template_switcher_button`

# `text_input`

A text field, and a picture from the site's media library when the field asks for one.

`ui do extra %{media: :images} end` on a `:text` field holding an address draws
`MishkaCmsCoreWeb.Live.Admin.Components.MediaField`: the address, a preview of it, and the library
under it to upload a picture to or choose one from — `folder: "name"` files the uploads in that
folder. The library is the site's the form's Site field names, as the content editor's is.

`ui do extra %{share_card: &MyExtension.ShareCards.target/1} end` on a virtual field draws the
record's share card and its Regenerate button
(`MishkaCmsCoreWeb.Live.Admin.Components.ShareCardField`): the function is handed the record the
form edits and returns the `t:MishkaCmsCore.Runtime.ShareCard.target/0` its card is drawn for, or
`nil`. A create form has no record, and draws the note that there is no share image yet.

Every other text field is the fallback's.

# `textarea`

A textarea, and a real code editor or a rich-text editor when the field asks for one.

`ui do extra %{code: "js"} end` on a `:textarea` field turns it into CodeMirror with that
language's grammar, line numbers and bracket matching — the same editor the page builder's drawer
uses, loaded from the same on-demand bundle. Anything else is the Tailwind adapter's plain box,
handed exactly the assigns the field passed — its class, rows and `phx-debounce` defaults are the
Tailwind adapter's own — so this costs nothing to every other form in the admin.

The code editor is `rows` tall (four when unset) and sends its changes at the field's debounce
when that is a number of milliseconds, else every 300ms.

The textarea is still the form field. CodeMirror mounts beside it and writes into it on every
change, so validation, `phx-change` and save never learn that an editor exists — and a page where
the bundle fails to load has a working textarea rather than an empty div.

`phx-update="ignore"` is what keeps LiveView from reconciling away the DOM CodeMirror builds, and
the id is the FIELD's, not the content's: a form echoes a sanitized value back on every keystroke,
and keying on the text would remount the editor — and drop the cursor to the end — mid-word.

The page must set `:code_editor` for the bundle's address to be published; `MishkaCmsCore.Layouts.Nav`
does it for every admin page.

The box's height is `rows × 1.75rem` with a floor of six, and it is a CLASS rather than a `style`
attribute so the policy in `MishkaCmsCore.Runtime.Plugs.SecurityHeaders` can close
`style-src-attr`, which no nonce can ever reopen for an attribute. `code_height_class/1` writes
each height out whole, one clause per row count, because Tailwind reads source text — an
interpolated class name gets no rule at all, the box collapses to nothing and the editor inside it
disappears. Its clauses run to twenty, comfortably past the largest `rows` any resource declares;
a field asking for more takes twenty's height until a clause is added for it.

## The content editor

`ui do extra %{content_editor: true} end` on a `:textarea` field holding HTML turns it into a
rich-text editor, with components placed in the content as blocks. It is Mishka Chelekom's
headless `editor` engine, drawn with that component's markup contract — the `root`, a `toolbar`
of `data-editor-command` buttons, the hidden `value` textarea carrying the field's `name`, and
the `surface` the `ContentEditor` hook mounts the engine on — written here, so the admin needs no
site to have installed the kit. The engine writes the HTML into the hidden textarea and
dispatches `input` there: the textarea is the field, as it is for the code editor.

A placed component is stored as its marker, in a paragraph of its own — see
`MishkaCmsCore.Runtime.ContentComponents`. The palette, a component's settings and the answers
the hook asks for are `MishkaCmsCoreWeb.Live.Admin.Components.ContentComponentPicker`, mounted
inside the editor and named on the surface as `data-components`; a master's record places the
components of the site its Site field names. Every id is the field's control id, which Gervaz
writes from the form's, so two forms on one page draw two editors.

The document scrolls inside the editor past a height of its own, so the toolbar stays in view on a
long post — a sticky toolbar would not stick inside the form's modal, whose box clips what
overflows it. The editable element itself is the textbox: the engine names it with the field's
label, from the surface's `data-label-id`. An empty field shows its `placeholder`, or a line
saying what to do.

The page must set `:content_editor` for the bundle's address to be published;
`MishkaCmsCore.Layouts.Nav` does it for every admin page.

## Attributes

* `name` (`:string`) (required)
* `id` (`:string`)
* `value` (`:any`)
* `placeholder` (`:string`)
* `rows` (`:integer`)
* `class` (`:string`)
* `disabled` (`:boolean`)
* `readonly` (`:boolean`)
* `extra` (`:map`)

# `th`

# `toggle_input`

# `tr`

# `upload_dropzone`

# `upload_existing_file`

# `upload_file_input`

# `upload_preview`

# `upload_progress`

---

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