MailglassAdmin.Components (MailglassAdmin v1.9.0)

Copy Markdown View Source

Brand-book-aligned shared UI atoms used throughout mailglass_admin.

Components

  • icon/1 — Heroicon via the vendored heroicons.js Tailwind plugin (Phoenix 1.8 installer convention). Classes matching the pattern hero-<name> are resolved at build time into inline SVG.
  • logo/1 — sealed-flap mailglass lockup rendered inline so it inherits admin chrome color; the stable logo.svg route remains served from priv/static/mailglass-logo.svg via MailglassAdmin.Controllers.Assets.
  • flash/1 — toast-style flash message for LiveReload + success notifications. Brand-voice: no "Oops!", no "Uh oh!"; specific and composed per brand book §5.
  • badge/1 — sidebar status badge with two variants: :warning (preview_props/0 raised) and :stub (no preview_props defined).

Brand voice enforcement

Copy throughout these atoms follows the 05-UI-SPEC Copywriting Contract: clear, exact, confident, warm, technical — "a thoughtful maintainer." Banned phrases ("Oops", "Whoops", "Uh oh", "Something went wrong") never appear in this module; the voice test greps the rendered HTML to enforce the floor.

Boundary classification: submodule auto-classifies into the MailglassAdmin root boundary declared in lib/mailglass_admin.ex; classify_to: is reserved for mix tasks and protocol implementations and is not used here.

Summary

Functions

Sidebar status badge. Two variants

Renders the thin group-surface shell: border, radius, surface tone, and outer padding only.

Renders one of four distinct data-state templates.

Renders one explicit, labelled filter control.

Renders a visible filter control group.

Renders a brand-voice flash message in a daisyUI toast wrapper.

Renders a Heroicon via the vendored heroicons.js Tailwind plugin.

Renders the sealed-flap mailglass lockup inline for theme-aware color.

Masks the local/domain halves of an already-split email address. Public so the inbound components can mask address-shaped values that are pre-split.

Masks a recipient email for operator display (PII minimization).

Masks a single value: keeps the first grapheme, stars the rest. Public so other admin surfaces reuse the one masking primitive rather than reinventing it.

Renders the desktop operator navigation link primitive.

Renders the compact operator navigation pill primitive.

Normalizes inbound @outcomes singular atoms to the canonical past-tense atoms expected by status_badge/1. The mailglass_inbound @outcomes schema (locked 1.0 contract) is never modified; normalization is admin-side only.

Renders a canonical stat card with label, no-wrap value, and severity.

Unified delivery, inbound, and timeline status badge. Renders an outline Heroicon (decorative, aria-hidden) and a text label inside a daisyUI badge container.

Renders the read-only tenant context chip.

Renders the three-choice theme picker primitive.

Functions

badge(assigns)

(since 0.1.0)

Sidebar status badge. Two variants:

  • :warning — preview_props/0 raised; shows an exclamation-triangle Heroicon + the literal copy "Error" (per 05-UI-SPEC Badge section).
  • :stub — mailable has no preview_props/0 defined; shows the "—" glyph in Slate (secondary) color.

Attributes

  • variant (:atom) (required) - Must be one of :warning, or :stub.

card(assigns)

(since 1.13.0)

Renders the thin group-surface shell: border, radius, surface tone, and outer padding only.

This is the single source for the group-surface shell. It deliberately owns no layout engine — no header/footer/grid slots, no inter-card rhythm, no dl/ol spacing. shadow-raised and data-group-card are applied at call sites, not baked in here. Padding is the one closed knob (:md -> p-md, :lg -> p-lg).

Attributes

  • padding (:atom) - Defaults to :md. Must be one of :md, or :lg.
  • Global attributes are accepted.

Slots

  • inner_block (required)

data_state(assigns)

(since 1.13.0)

Renders one of four distinct data-state templates.

Each kind maps to a unique testid, icon, and icon color so that permission-denied is never mistaken for no-data, and error is never conflated with stale-data.

Kinds

  • :empty — no records; rendered with hero-inbox and text-secondary
  • :error — unavailable/error; rendered with hero-exclamation-circle and text-error
  • :permission_denied — access restricted; rendered with hero-lock-closed and text-warning
  • :stale — data may be out of date; rendered with hero-clock and text-secondary

Attributes

  • kind (:atom) (required) - Must be one of :empty, :error, :permission_denied, or :stale.
  • title (:string) (required)
  • body (:string) (required)
  • icon (:string) - Defaults to nil.
  • Global attributes are accepted.

filter_field(assigns)

(since 1.8.0)

Renders one explicit, labelled filter control.

Defaults derive from Phoenix.HTML.FormField metadata when field is provided. Explicit id, name, and value remain supported for gallery and certification surfaces that render without a form struct.

Attributes

  • field (:any) - Defaults to nil.
  • id (:string) - Defaults to nil.
  • name (:string) - Defaults to nil.
  • value (:any) - Defaults to nil.
  • type (:atom) - Defaults to :text. Must be one of :text, :number, :select, :textarea, or :checkbox.
  • label (:string) (required)
  • help (:string) - Defaults to nil.
  • error (:any) - Defaults to nil.
  • options (:list) - Defaults to [].
  • prompt (:string) - Defaults to nil.
  • disabled (:boolean) - Defaults to false.
  • readonly (:boolean) - Defaults to false.
  • display_value (:string) - Defaults to nil.
  • submit_readonly (:boolean) - Defaults to true.
  • Global attributes are accepted. Supports all globals plus: ["autocomplete", "inputmode", "max", "min", "pattern", "placeholder", "step"].

filter_section(assigns)

(since 1.8.0)

Renders a visible filter control group.

The primitive uses native fieldset/legend semantics so pages can group related filters without duplicating page-local label/control wrappers.

Attributes

  • title (:string) (required)
  • description (:string) - Defaults to nil.
  • Global attributes are accepted.

Slots

  • inner_block (required)

flash(assigns)

(since 0.1.0)

Renders a brand-voice flash message in a daisyUI toast wrapper.

Used for LiveReload notifications ("Reloaded: {file}") and other transient signals. Includes role="status" + aria-live="polite" per the 05-UI-SPEC Accessibility Interactions contract.

Attributes

  • kind (:atom) - Defaults to :info. Must be one of :info, :success, :warning, or :error.
  • message (:string) (required)

icon(assigns)

(since 0.1.0)

Renders a Heroicon via the vendored heroicons.js Tailwind plugin.

The plugin resolves classes matching hero-<name> into inline SVG at build time. Usage: <.icon name="hero-envelope" class="w-5 h-5" />.

Attributes

  • name (:string) (required)
  • class (:any) - Defaults to nil.

logo(assigns)

(since 0.1.0)

Renders the sealed-flap mailglass lockup inline for theme-aware color.

The public logo.svg route remains served by MailglassAdmin.Controllers.Assets at <mount>/logo.svg; the admin UI renders inline so the currentColor paths inherit text-base-content in light and dark chrome.

Attributes

  • class (:any) - Defaults to nil.

mask_email(local, domain)

(since 0.2.0)
@spec mask_email(String.t(), String.t()) :: String.t()

Masks the local/domain halves of an already-split email address. Public so the inbound components can mask address-shaped values that are pre-split.

mask_recipient(recipient)

(since 0.2.0)
@spec mask_recipient(String.t() | nil) :: String.t()

Masks a recipient email for operator display (PII minimization).

The ONE audited masking definition in the admin package: both MailglassAdmin.Operator.DeliveriesList (outbound) and the inbound components call this so there is never a second, drifting copy. Keeps the first grapheme of each segment and stars the rest, preserving the email shape:

mask_recipient("alice@example.com") #=> "a****@e******.com"
mask_recipient(nil)                 #=> "Unavailable"

mask_value(value)

(since 0.2.0)
@spec mask_value(String.t()) :: String.t()

Masks a single value: keeps the first grapheme, stars the rest. Public so other admin surfaces reuse the one masking primitive rather than reinventing it.

normalize_inbound_outcome(atom)

(since 1.5.0)
@spec normalize_inbound_outcome(atom() | nil) :: atom() | nil

Normalizes inbound @outcomes singular atoms to the canonical past-tense atoms expected by status_badge/1. The mailglass_inbound @outcomes schema (locked 1.0 contract) is never modified; normalization is admin-side only.

Maps: :accept:accepted, :reject:rejected, :bounce:bounced. All other atoms (including nil) pass through unchanged.

stat_card(assigns)

(since 1.8.0)

Renders a canonical stat card with label, no-wrap value, and severity.

Severity is a closed set and always renders as icon plus visible label plus semantic color.

Attributes

  • id (:string) - Defaults to nil.
  • label (:string) (required)
  • value (:any) - Defaults to nil.
  • severity (:atom) - Defaults to :neutral. Must be one of :neutral, :info, :success, :warning, or :error.
  • severity_label (:string) - Defaults to nil.
  • state (:atom) - Defaults to :ready. Must be one of :ready, :empty, :loading, or :unavailable.
  • empty_text (:string) - Defaults to "No data yet".
  • loading_text (:string) - Defaults to "Resolving".
  • unavailable_text (:string) - Defaults to "Unavailable".
  • Global attributes are accepted.

status_badge(assigns)

(since 1.5.0)

Unified delivery, inbound, and timeline status badge. Renders an outline Heroicon (decorative, aria-hidden) and a text label inside a daisyUI badge container.

The base badge class is always emitted by this component — call sites must NOT prepend 'badge' or 'badge badge-sm'. Use size: :sm (default) for list rows; size: :md for detail headers.

Attributes

  • status (:atom) (required) - Must be one of :dispatched, :queued, :sent, :delivered, :deferred, :bounced, :failed, :rejected, :complained, :unsubscribed, :opened, :clicked, :autoresponded, :unknown, :accepted, :no_match, :ignore, :failed_ingest, :webhook_replay_requested, :webhook_replay_succeeded, :webhook_replay_failed, :reconciled, or :suppressed.
  • size (:atom) - Defaults to :sm. Must be one of :sm, or :md.

tenant_chip(assigns)

(since 1.8.0)

Renders the read-only tenant context chip.

The chip intentionally does not expose switching, loading, or navigation behavior.

Attributes

  • tenant (:string) - Defaults to nil.
  • Global attributes are accepted.

theme_picker(assigns)

(since 1.8.0)

Renders the three-choice theme picker primitive.

The primitive owns radio semantics only. Persistence, cookie naming, root theme resolution, browser storage, and first-paint behavior are downstream shell concerns.

Attributes

  • selected (:atom) - Defaults to :system. Must be one of :system, :light, or :dark.
  • name (:string) - Defaults to "theme".
  • disabled (:boolean) - Defaults to false.
  • event (:string) - Defaults to nil.
  • target (:any) - Defaults to nil.
  • Global attributes are accepted.