# `MishkaGervaz.Table.Templates.MediaGallery`
[🔗](https://github.com/mishka-group/mishka_gervaz/blob/v0.0.2/lib/mishka_gervaz/table/templates/media_gallery.ex#L1)

Media gallery template for image/file-heavy data.

Optimized for displaying images, videos, and files with:
- Thumbnail grid with hover preview
- Lightbox for full-size viewing
- File type icons for non-image files
- Quick actions overlay

## Features
- `:filter` - Filter by file type, date, etc.
- `:select` - Multi-select for bulk operations
- `:bulk_actions` - Bulk download, delete, move
- `:paginate` - Infinite scroll preferred
- `:expand` - Lightbox expansion

## Column-Based Layout

MediaGallery uses the columns DSL to determine what to display:
- First visible column becomes the thumbnail image
- Remaining visible columns are rendered below the thumbnail in order

Use `visible fn state -> state.template.name() == :media_gallery end` to show columns
only in MediaGallery, or `visible fn state -> state.template.name() == :table end`
for Table-only columns.

## The card's actions

A card draws its row actions in three parts: a star toggle over the thumbnail, one wide labelled
button, and bordered squares beside it, red if they destroy something. `split_actions/3` sorts
them, and the `:overlay_action` / `:primary_action` options below name which action goes where.

## The thumbnail that fails to load

A card paints its type glyph behind the thumbnail, so a broken `<img>` only has to be hidden for
the glyph to show through; a thumbnail that does load sits on white, so a transparent PNG does not
pick up the tile's tint.

The consuming app must register a `MediaThumb` JS hook in its admin bundle. It reads
`data-loaded-background` from the hook element for the colour a loaded image sits on, and binds
`load`/`error` on the `<img>` inside it.

## Options
- `:columns` - Number of grid columns (3, 4, 6, or 8)
- `:overlay_action` - the action drawn as the star over the thumbnail (default `:toggle_featured`)
- `:primary_action` - the action drawn wide and labelled (default `:edit`)

## Performance
Uses `@static.*` for columns, ui_adapter, etc. (no re-render on user interaction)
Uses `@state.*` for page, filter_values, etc. (re-renders when changed)

See `MishkaGervaz.Table.Behaviours.Template`,
`MishkaGervaz.Table.Templates.Shared`,
`MishkaGervaz.Table.Templates.Table` (sibling), and
`MishkaGervaz.UIAdapters.MediaGallery` (paired UI adapter).

# `featured?`

```elixir
@spec featured?(map()) :: boolean()
```

Whether this file is featured — the star, and the card's gold ring.

# `gallery_ui_adapter`

```elixir
@spec gallery_ui_adapter(module()) :: module()
```

The adapter a media card is drawn with, unless the resource named one of its own.

# `icon`

The switcher draws this glyph for the gallery view.

A grid of squares, matching the other card views in the admin.

# `media_category`

The name of the category a file belongs to, or `nil`.

Only one of the two category relationships is ever loaded — a tenant's own, or the master
alias that bypasses tenancy — so both are tried and whichever holds a record wins.

# `media_date`

The date a file was added, formatted for a card, or `nil` when it is not loaded.

    iex> media_date(%{inserted_at: ~U[2026-08-13 10:00:00Z]})
    "Aug 13, 2026"

# `media_ext`

A file's extension as declared by its `format` attribute, or `nil`.

# `media_name`

A file's display name, with its extension when the record carries one.

    iex> media_name(%{name: "cover", format: "png"})
    "cover.png"

# `media_size`

A file's size, humanised, or an em dash when the record has none.

    iex> media_size(%{size: 2048})
    "2.0 KB"

# `media_type`

```elixir
@spec media_type(map()) :: atom() | nil
```

A record's media type — `:images`, `:videos`, `:documents`, or nil.

# `render_bulk_actions`

# `render_error`

# `render_pagination`

# `render_template_switcher`

# `selected?`

```elixir
@spec selected?(map(), map()) :: boolean()
```

Whether this record is selected, under either selection mode.

`select_all?` inverts the meaning of the set: everything is chosen except what is in
`excluded_ids`.

# `split_actions`

```elixir
@spec split_actions([map()], map(), boolean()) :: %{
  overlay: [map()],
  primary: [map()],
  secondary: [map()]
}
```

The card's three action groups: the star over the thumbnail, the one wide button, and the squares.

Which action gets which part is a `template_options` decision, defaulting to the names the media
resource uses:

    presentation do
      template_options [overlay_action: :pin, primary_action: :open]
    end

A resource whose `row_actions_layout` places an action — inline, or behind a dropdown — gets all
of them back under `:secondary`.

When `featured?` is true the overlay's star is filled; otherwise it stays an outline.

# `thumbnail_url`

```elixir
@spec thumbnail_url(map(), map(), map()) :: String.t() | nil
```

The URL of a record's thumbnail, or nil when it has none to show.

The thumbnail is the first column visible to this template. Visibility is always asked as
`:media_gallery`, whichever template is drawing, so a template that borrows the card — by
delegating `render_item/1` or by calling this — still sees the columns a resource gated with
`visible fn state -> state.template.name() == :media_gallery end`.

# `tint`

```elixir
@spec tint(map()) :: String.t()
```

The background wash a file's type gets. See `media_type/1`.

# `type_glyph`

The glyph a file wears when there is no thumbnail to show instead.

See `MishkaCmsCoreWeb.Templates.AssetsList` in the CMS, which draws the same file smaller.

## Attributes

* `type` (`:atom`) - Defaults to `nil`.
* `class` (`:string`) - Defaults to `"size-[26px]"`.

---

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