Lib-owned canonical admin component set for Sigra's admin LiveViews.
Provides 16 flat, stateless Phoenix.Component function components that consolidate
the duplicated admin chrome across LiveViews. Security-critical design and a11y fixes
propagate to host apps via mix deps.update (D-05).
Each component emits only sg-* CSS classes defined by the design contract.
See the Admin Design Contract for the authoritative per-component markup, class, and ARIA specification.
Components
stat_link/1— Numeric KPI with a nav destination (always<a>)stat/1— Read-only numeric KPI, no navtask_card/1— Verb-first action prompt with CTAsummary_chip/1— Non-interactive aggregate posture badgeapplied_chip/1— Active filter indicator with remove affordanceempty_state/1— Dashed centred zero-rows placeholderpage_back/1— Single-step back to listscope_ribbon/1— Persistent in-body scope indicatornotice/1— Block-level toned contextual alertnotice_link/1— Inline notice action linkfield_help/1— Label-adjacent explanatory tooltip triggerskeleton/1— Loading-shape placeholderaudit_row/1— Audit event card (sg-list-row) with unified tone and date formattingdetail_input/1— Text input field for the auth branding Details panel (D-05)color_field/1— Colour picker control for auth branding Light/Dark palettes (D-05)preview_pair/1— Login + email preview rail for auth branding panels (D-05)
Summary
Functions
Renders an active-filter indicator with a remove affordance.
Renders the audit zero-state, parametrized for per-page copy.
Renders the honest-cursor pagination <nav> for both audit pages.
Renders an audit event card (sg-list-row) with a pill cluster, actor summary,
optional detail lines, and a formatted timestamp.
Renders the desktop audit table row (<tr>) for the shared 4-column audit table.
Renders a colour picker control for the auth branding Light/Dark palette panels.
Renders a single text input field for the auth branding Details panel.
Renders a centred zero-rows placeholder with a variable body block.
Renders a compact label-adjacent help trigger with a non-interactive tooltip.
Renders a block-level toned contextual alert.
Renders an inline navigation action for use inside notice/1 copy.
Renders a single-step back-to-list ghost button with a fixed ← glyph prefix.
Renders the side-by-side login + email preview rail for a single branding panel (Light, Dark, or Details).
Renders a persistent in-body scope indicator as a decorative inline span.
Renders a loading-shape placeholder.
Renders a read-only numeric KPI with no navigation affordance.
Renders a numeric KPI stat with a navigation destination (always an <a> element).
Renders a non-interactive aggregate posture badge using definition-list semantics.
Renders a verb-first action prompt card with a call-to-action link.
Functions
Renders an active-filter indicator with a remove affordance.
The chip label is displayed inline; the remove link carries an aria-label for
screen readers and a visually hidden fallback. The × glyph is aria-hidden.
Examples
<.applied_chip label="Active" remove_href="/admin/users?status=" />Attributes
label(:string) (required) - the filter label shown inside the chip.remove_href(:string) (required) - the URL to navigate to in order to remove this filter.class(:any) - additional CSS classes merged onto the root element. Defaults tonil.- Global attributes are accepted. arbitrary HTML attributes added to the root element.
Renders the audit zero-state, parametrized for per-page copy.
Wraps the generic empty_state/1 with audit-specific attributes. The title attr
supplies the heading; the inner block renders the per-page body copy (e.g., the
filter-aware copy with "Clear all filters" link on the index page, vs the simple
"no scoped events" copy on the per-user page).
Examples
<.audit_empty_state title="No audit events match this view">
<p class="sg-muted sg-text-sm">No audit events match the active filters.</p>
<div class="sg-cluster sg-cluster--center">
<a href={index_path(@admin_scope)} class="sg-btn sg-btn--secondary sg-btn--sm">Clear all filters</a>
</div>
</.audit_empty_state>
<.audit_empty_state title="No audit events for this user">
<p class="sg-muted sg-text-sm">No scoped events are currently tied to this user.</p>
</.audit_empty_state>Attributes
title(:string) (required) - the heading for the audit empty state.
Slots
inner_block- per-page body copy rendered below the title.
Renders an audit event card (sg-list-row) with a pill cluster, actor summary,
optional detail lines, and a formatted timestamp.
This is the single shared component for all three audit surfaces:
AuditIndexLivemobile card list (show_detail: true,show_codes: true)AuditUserLivemobile card list (show_detail: true,show_codes: true)UserShowLive"Recent Audit" compact block (defaults:show_detail: false,show_codes: false)
Tone is derived from row.outcome and row.action_badge via the single internal
audit_tone/1 helper, which is the authoritative source replacing the divergent
row_tone/1 helpers previously present in AuditIndexLive and AuditUserLive.
The format_date/1 helper handles %DateTime{} and %NaiveDateTime{} values,
renders nil as "—", and raises ArgumentError on any other type (D-09 fix).
Examples
<.audit_row row={row} />
<.audit_row row={row} show_detail show_codes />Attributes
row(:map) (required) - the presenter row map for the audit event.show_detail(:boolean) - renders the Actor and Effective user detail lines (on in explorers, off in compact block). Defaults tofalse.show_codes(:boolean) - renders the event id and action code lines (on in explorers, off in compact block). Defaults tofalse.class(:any) - additional CSS classes merged onto the root element. Defaults tonil.- Global attributes are accepted. arbitrary HTML attributes added to the root element.
Renders the desktop audit table row (<tr>) for the shared 4-column audit table.
This is the single shared component for byte-coherent desktop-table markup across both audit pages:
AuditIndexLivedesktop table bodyAuditUserLivedesktop table body
Column order is FROZEN (td:nth-child positional selectors depend on this):
- Occurred — timestamp only
- Event —
action_labelpill + optionalaction_badgepill +<details>disclosure holding both raw codes (event_idand action code) ascode.sg-codetext nodes - Actor —
actor_summaryspan (first) + conditional detail lines - Outcome — risk pill or muted span
Both raw code.sg-code nodes live inside the Event <td> disclosure so the
firstTexts(desktop, 'code.sg-code', 2) content-equivalence assertion still returns
exactly 2 (D-06 contract preserved).
Tone is derived via the shared private audit_tone/1 (single source of truth, D-08).
Timestamp uses format_timestamp/1 (with-seconds) for forensic precision, consistent
across both pages.
Examples
<.audit_table_row row={row} />
<.audit_table_row :for={row <- @rows} row={row} />Attributes
row(:map) (required) - the presenter row map for the audit event.
Renders a colour picker control for the auth branding Light/Dark palette panels.
The control pairs a native <input type="color"> (hidden circle) with a
sibling span that mirrors the current hex value as readable text. Client-side
hooks update the span in real-time as the picker changes.
Uses the data-sg-auth-branding-color and data-sg-auth-branding-color-value
attribute hooks so AuthBrandingPreview can push live CSS variable updates.
Examples
<.color_field :for={{name, label} <- @light_color_fields}
name={name}
label={label}
value={field_value(@draft_params, name)}
/>Attributes
name(:string) (required) - the form field name; also used as the hook data attribute value.label(:string) (required) - the accessible label for the colour picker.value(:string) (required) - the current hex colour value (e.g., "#c2410c").
Renders a single text input field for the auth branding Details panel.
The id and help_id are derived deterministically from name using the
branding-<name> convention (underscores replaced with hyphens) so the label
for and <.field_help> id always stay in sync.
Examples
<.detail_input name="product_name" label="Product name" value={@value} required />
<.detail_input
name="logo_url"
label="Logo URL"
value={@value}
help="Shown on generated auth screens and email headers when set."
/>Attributes
name(:string) (required) - the form field name (used in both the HTML name and id).label(:string) (required) - the visible label for the field.value(:string) (required) - the current field value.required(:boolean) - marks the input as required. Defaults tofalse.help(:string) - optional help tooltip copy. Defaults tonil.
Renders a centred zero-rows placeholder with a variable body block.
The title attr provides the primary heading. The inner_block slot accepts the
variable body (e.g., filter-specific messaging or a CTA).
Examples
<.empty_state title="No users match this view">
<p class="sg-muted sg-text-sm">Try adjusting your filters.</p>
</.empty_state>Attributes
title(:string) (required) - the primary heading shown in the empty state.class(:any) - additional CSS classes merged onto the root element. Defaults tonil.- Global attributes are accepted. arbitrary HTML attributes added to the root element.
Slots
inner_block- the variable body rendered below the title (e.g., CTA or filter copy).
Renders a compact label-adjacent help trigger with a non-interactive tooltip.
Use this for admin form fields whose effect is not obvious from the label alone. Keep the tooltip copy brief and plain-language; the panel should not contain links or other interactive content.
Examples
<span class="sg-field-label-row">
<label class="sg-field-label" for="branding-logo-url">Logo URL</label>
<.field_help id="branding-logo-url-help" label="Logo URL">
Shown on generated auth screens and email headers when set.
</.field_help>
</span>Attributes
id(:string) (required) - stable id for the tooltip panel.label(:string) (required) - field label used in the trigger's accessible name.open(:boolean) - renders the tooltip open for deterministic design-gallery evidence. Defaults tofalse.class(:any) - additional CSS classes merged onto the root element. Defaults tonil.- Global attributes are accepted. arbitrary HTML attributes added to the root element.
Slots
inner_block(required) - plain explanatory tooltip copy.
Renders a block-level toned contextual alert.
Ships the final sg-notice form per the design contract (D-07). The data-tone attribute
drives visual treatment via CSS. No live-region role is applied by default (D-08):
Load-present content makes any live-region role inert (WAI-ARIA APG), and adding one
risks duplicate announcements on LiveView re-render. A live-region role is opt-in
per call site via :rest for genuinely post-load dynamic notices.
See the Admin Design Contract for the full rationale (D-07/D-08/D-09).
Examples
<.notice tone={:warn}>
Password reset email could not be delivered.
</.notice>
<.notice tone={:ok}>
Email address verified successfully.
</.notice>Attributes
tone(:atom) - the visual tone applied via data-tone; renders as a string in the HTML attribute. Defaults tonil. Must be one of:ok,:warn,:risk,:info, ornil.class(:any) - additional CSS classes merged onto the root element. Defaults tonil.- Global attributes are accepted. arbitrary HTML attributes (e.g., a live-region role for opt-in post-load notices).
Slots
inner_block(required) - the notice message content.
Renders an inline navigation action for use inside notice/1 copy.
This remains a native link and keeps notice content sentence-shaped. Use it
when the notice names a concrete next step; use task_card/1 or sg-btn
variants for standalone primary actions.
Examples
<.notice tone={:risk}>
3 accounts need review —
<.notice_link href="/admin/users?needs_review=true">Review accounts</.notice_link>
</.notice>Attributes
href(:string) (required) - the URL the notice action navigates to.class(:any) - additional CSS classes merged onto the root element. Defaults tonil.- Global attributes are accepted. arbitrary HTML attributes added to the root link.
Slots
inner_block(required) - the notice action label.
Renders a single-step back-to-list ghost button with a fixed ← glyph prefix.
The label (e.g., "Back to users") is supplied by the caller. The arrow glyph is
always rendered aria-hidden so screen readers read only the label text.
Examples
<.page_back return_to={~p"/admin/users"} label="Back to users" />Attributes
return_to(:string) (required) - the URL the button navigates back to.label(:string) (required) - the visible button label (e.g., "Back to users").class(:any) - additional CSS classes merged onto the root element. Defaults tonil.- Global attributes are accepted. arbitrary HTML attributes added to the root element.
Renders the side-by-side login + email preview rail for a single branding panel (Light, Dark, or Details).
The two preview surfaces are wrapped in sg-branding-preview-rail and accept
data-sg-auth-branding-preview attribute hooks that the AuthBrandingPreview
client hook reads to push live CSS variable updates as the palette changes.
The active flag controls which data-testid values are emitted so Playwright
assertions can target the live panel's previews regardless of tab position.
Examples
<.preview_pair
profile={@preview_profile}
theme="light"
active={@active_panel == :light}
login_testid="admin-auth-branding-light-login-preview"
email_testid="admin-auth-branding-light-email-preview"
email_surface_testid="admin-auth-branding-light-email-preview-surface"
/>Attributes
profile(:any) (required) - the %Sigra.Branding.Profile{} used to derive CSS variables and preview content.theme(:string) (required) - the data-theme attribute value ("light" or "dark").active(:boolean) (required) - true when this panel is the currently visible tab.login_testid(:string) (required) - data-testid for the login preview card (used when not active).email_testid(:string) (required) - data-testid for the email preview card (used when not active).email_surface_testid(:string) (required) - data-testid for the email preview surface (used when not active).
Renders a persistent in-body scope indicator as a decorative inline span.
The copy is caller-supplied (e.g., the result of scope_copy/1). The component
emits the stable hook class sg-scope-ribbon (consumed by Playwright, per D-07)
alongside the visual utility classes sg-muted sg-text-sm.
Examples
<.scope_ribbon copy={scope_copy(@admin_scope)} />Attributes
copy(:string) (required) - the scope indicator copy (e.g., "Platform admin").class(:any) - additional CSS classes merged onto the root element. Defaults tonil.- Global attributes are accepted. arbitrary HTML attributes added to the root element.
Renders a loading-shape placeholder.
Shimmer motion is owned entirely by the CSS layer (app.css) and is stripped by
the universal prefers-reduced-motion rule. No inline motion or transition style
is added by this component.
Examples
<.skeleton class="h-8 w-32" />Attributes
class(:any) - additional CSS classes merged onto the root element (e.g., height/width). Defaults tonil.- Global attributes are accepted. arbitrary HTML attributes added to the root element.
Renders a read-only numeric KPI with no navigation affordance.
Unlike stat_link/1, this component renders no <a> element and introduces no
dedicated component class. It reuses the existing sg-metric* utility classes.
Examples
<.stat label="Active sessions" value={42} />Attributes
label(:string) (required) - the human-readable KPI label.value(:integer) (required) - the numeric KPI value.class(:any) - additional CSS classes merged onto the root element. Defaults tonil.- Global attributes are accepted. arbitrary HTML attributes added to the root element.
Renders a numeric KPI stat with a navigation destination (always an <a> element).
Examples
<.stat_link label="Total users" value={1_234} href="/admin/users" />Attributes
href(:string) (required) - the URL the stat link navigates to.label(:string) (required) - the human-readable KPI label.value(:integer) (required) - the numeric KPI value.class(:any) - additional CSS classes merged onto the root element. Defaults tonil.- Global attributes are accepted. arbitrary HTML attributes added to the root element.
Renders a non-interactive aggregate posture badge using definition-list semantics.
The basic form preserves the original compact <dt>/<dd> output. Optional
icon, value_suffix, subvalue, tone, and help assigns opt into the richer
posture-summary treatment used by list page headers.
Examples
<.summary_chip label="MFA enabled" value={7} />
<.summary_chip
id="users-metric-mfa"
icon="mfa"
label="MFA enrolled"
value={42}
value_unit="%"
value_suffix="MFA coverage"
subvalue="7 users with MFA"
help="These users have multifactor authentication enabled. Higher coverage lowers account takeover risk."
/>Attributes
id(:string) - optional root id; also seeds the help id. Defaults tonil.label(:string) (required) - the term / KPI label.value(:integer) (required) - the numeric KPI value.icon(:string) - optional built-in metric icon name. Defaults tonil.value_unit(:string) - optional unit rendered tight against the number. Defaults tonil.value_suffix(:string) - optional second-line label for the number. Defaults tonil.subvalue(:string) - optional secondary metric context. Defaults tonil.help(:string) - optional short explanatory help copy. Defaults tonil.open(:boolean) - renders help open for deterministic design-gallery evidence. Defaults tofalse.tone(:string) - optional metric tone (risk, warn, ok, info). Defaults tonil.class(:any) - additional CSS classes merged onto the root element. Defaults tonil.- Global attributes are accepted. arbitrary HTML attributes added to the root element.
Renders a verb-first action prompt card with a call-to-action link.
Examples
<.task_card
title="Invite your team"
body="Add teammates so they can access the admin panel."
href="/admin/users/invite"
action="Send invitations"
/>Attributes
title(:string) (required) - the card heading.body(:string) (required) - the card description copy.href(:string) (required) - the URL the CTA button navigates to.action(:string) (required) - the CTA button label.class(:any) - additional CSS classes merged onto the root element. Defaults tonil.- Global attributes are accepted. arbitrary HTML attributes added to the root element.