PhoenixKit.Dashboard.Tab (phoenix_kit v1.7.71)

Copy Markdown View Source

Defines the Tab struct and related types for the user dashboard navigation system.

Tabs can be configured with rich features including:

  • Labels and icons
  • Conditional visibility based on roles, feature flags, or custom logic
  • Badge indicators with live updates via PubSub
  • Attention animations (pulse, bounce, shake)
  • Grouping with headers and dividers
  • Subtabs with parent/child relationships
  • Custom path matching logic
  • Tooltips and accessibility features

Basic Usage

%Tab{
  id: :orders,
  label: "My Orders",
  icon: "hero-shopping-bag",
  path: "/dashboard/orders",
  priority: 100
}

With Badge

%Tab{
  id: :notifications,
  label: "Notifications",
  icon: "hero-bell",
  path: "/dashboard/notifications",
  badge: %Badge{type: :count, value: 5, color: :error}
}

With Live Updates

%Tab{
  id: :printers,
  label: "Printers",
  icon: "hero-cube",
  path: "/dashboard/printers",
  badge: %Badge{
    type: :count,
    subscribe: {"farm:stats", fn msg -> msg.printing_count end}
  }
}

Conditional Visibility

Use visible for non-permission conditional logic (feature flags, user data). For access control, use the permission field instead.

%Tab{
  id: :beta_feature,
  label: "Beta",
  icon: "hero-beaker",
  path: "/dashboard/beta",
  visible: fn scope -> scope.user.features["beta_enabled"] == true end
}

Subtabs

Tabs can have parent/child relationships. Subtabs appear indented under their parent:

# Parent tab
%Tab{
  id: :orders,
  label: "Orders",
  icon: "hero-shopping-bag",
  path: "/dashboard/orders",
  subtab_display: :when_active  # Show subtabs only when this tab is active
}

# Subtabs
%Tab{
  id: :pending_orders,
  label: "Pending",
  path: "/dashboard/orders/pending",
  parent: :orders
}

%Tab{
  id: :completed_orders,
  label: "Completed",
  path: "/dashboard/orders/completed",
  parent: :orders
}

Subtab display modes:

  • :when_active - Show subtabs only when parent tab is active (default)
  • :always - Always show subtabs regardless of parent state

Summary

Functions

Checks if this is an admin-level tab.

Clears the attention animation from the tab.

Creates a divider pseudo-tab for visual separation.

Checks if this tab is a divider.

Creates a group header pseudo-tab for organizing sections.

Checks if this tab is a group header.

Checks if the current path matches this tab's path according to its match strategy.

Checks if the module associated with this tab is enabled.

Checks if this tab is a regular navigable tab (not divider or header).

Creates a new Tab struct from a map or keyword list.

Creates a new Tab struct, raising on error.

Gets the parent ID of a subtab, or nil if it's a top-level tab.

Checks if permission is granted for this tab given a scope.

Resolves a relative tab path to an absolute path based on context.

Sets an attention animation on the tab.

Checks if subtabs should be shown for this tab based on its display setting and active state.

Checks if this tab is a subtab (has a parent).

Checks if this tab is a top-level tab (not a subtab).

Updates a tab's badge value.

Checks if this is a user-level tab.

Evaluates the visibility of a tab given a scope.

Types

dynamic_children_fn()

@type dynamic_children_fn() :: (map() -> [t()]) | nil

level()

@type level() :: :user | :admin | :all

match_type()

@type match_type() :: :exact | :prefix | :regex | (String.t() -> boolean())

subtab_animation()

@type subtab_animation() :: :none | :slide | :fade | :collapse

subtab_display()

@type subtab_display() :: :when_active | :always

t()

@type t() :: %PhoenixKit.Dashboard.Tab{
  attention: atom() | nil,
  badge: PhoenixKit.Dashboard.Badge.t() | nil,
  dynamic_children: dynamic_children_fn(),
  external: boolean(),
  group: atom() | nil,
  highlight_with_subtabs: boolean(),
  icon: String.t() | nil,
  id: atom(),
  inserted_at: DateTime.t() | nil,
  label: String.t(),
  level: level(),
  live_view: {module(), atom()} | nil,
  match: match_type(),
  metadata: map(),
  new_tab: boolean(),
  parent: atom() | nil,
  path: String.t(),
  permission: String.t() | nil,
  priority: integer(),
  redirect_to_first_subtab: boolean(),
  subtab_animation: subtab_animation() | nil,
  subtab_display: subtab_display(),
  subtab_icon_size: String.t() | nil,
  subtab_indent: String.t() | nil,
  subtab_text_size: String.t() | nil,
  tooltip: String.t() | nil,
  visible: visibility()
}

visibility()

@type visibility() :: boolean() | (map() -> boolean())

Functions

admin?(arg1)

@spec admin?(t()) :: boolean()

Checks if this is an admin-level tab.

clear_attention(tab)

@spec clear_attention(t()) :: t()

Clears the attention animation from the tab.

divider(opts \\ [])

@spec divider(keyword()) :: t()

Creates a divider pseudo-tab for visual separation.

Options

  • :id - Unique identifier (default: auto-generated)
  • :priority - Sort order (required to position the divider)
  • :group - Group this divider belongs to (optional)
  • :label - Optional label text for the divider (shows as a header)

Examples

Tab.divider(priority: 150)
Tab.divider(priority: 200, label: "Account")

divider?(arg1)

@spec divider?(t()) :: boolean()

Checks if this tab is a divider.

group_header(opts)

@spec group_header(keyword()) :: t()

Creates a group header pseudo-tab for organizing sections.

Options

  • :id - Unique identifier (required)
  • :label - Header text (required)
  • :priority - Sort order (required)
  • :icon - Optional icon for the header
  • :collapsible - Whether the group can be collapsed (default: false)
  • :collapsed - Initial collapsed state (default: false)

Examples

Tab.group_header(id: :farm_section, label: "Farm Management", priority: 100)
Tab.group_header(id: :account_section, label: "Account", priority: 200, collapsible: true)

group_header?(arg1)

@spec group_header?(t()) :: boolean()

Checks if this tab is a group header.

matches_path?(arg1, current_path)

@spec matches_path?(t(), String.t()) :: boolean()

Checks if the current path matches this tab's path according to its match strategy.

Examples

iex> tab = %Tab{path: "/dashboard", match: :exact}
iex> Tab.matches_path?(tab, "/dashboard")
true
iex> Tab.matches_path?(tab, "/dashboard/orders")
false

iex> tab = %Tab{path: "/dashboard", match: :prefix}
iex> Tab.matches_path?(tab, "/dashboard/orders")
true

module_enabled?(arg1)

@spec module_enabled?(t()) :: boolean()

Checks if the module associated with this tab is enabled.

Returns true if:

  • The tab has no permission requirement (permission is nil)
  • The feature module for the permission key is enabled

new(attrs)

@spec new(map() | keyword()) :: {:ok, t()} | {:error, String.t()}

Creates a new Tab struct from a map or keyword list.

Options

  • :id - Unique identifier for the tab (required, atom)
  • :label - Display text for the tab (required, string)
  • :icon - Heroicon name, e.g., "hero-home" (optional)
  • :path - URL path for the tab (required)
  • :priority - Sort order, lower numbers appear first (default: 500)
  • :group - Group identifier for organizing tabs (optional, atom)
  • :parent - Parent tab ID for subtabs (optional, atom)
  • :subtab_display - When to show subtabs: :when_active or :always (default: :when_active)
  • :subtab_indent - Tailwind padding class for subtab indentation (e.g., "pl-6", "pl-12")
  • :subtab_icon_size - Icon size class for subtabs (e.g., "w-3 h-3", "w-5 h-5")
  • :subtab_text_size - Text size class for subtabs (e.g., "text-xs", "text-base")
  • :subtab_animation - Animation when subtabs appear: :none, :slide, :fade, :collapse
  • :redirect_to_first_subtab - Navigate to first subtab when clicking parent (default: false)
  • :highlight_with_subtabs - Highlight parent when subtab is active (default: false)
  • :match - Path matching strategy: :exact, :prefix, :regex, or function (default: :prefix)
  • :visible - Boolean or function(scope) -> boolean for non-permission conditional visibility, e.g. feature flags (default: true). For access control, use :permission instead.
  • :badge - Badge struct or map for displaying indicators (optional)
  • :tooltip - Hover text for the tab (optional)
  • :external - Whether this links to an external site (default: false)
  • :new_tab - Whether to open in a new tab (default: false)
  • :attention - Attention animation: :pulse, :bounce, :shake, :glow (optional)
  • :metadata - Custom metadata map for advanced use cases (default: %{})

Examples

iex> Tab.new(id: :home, label: "Home", path: "/dashboard", icon: "hero-home")
{:ok, %Tab{id: :home, label: "Home", path: "/dashboard", icon: "hero-home"}}

iex> Tab.new(%{id: :orders, label: "Orders", path: "/orders", priority: 100})
{:ok, %Tab{id: :orders, label: "Orders", path: "/orders", priority: 100}}

new!(attrs)

@spec new!(map() | keyword()) :: t()

Creates a new Tab struct, raising on error.

parent_id(tab)

@spec parent_id(t()) :: atom() | nil

Gets the parent ID of a subtab, or nil if it's a top-level tab.

permission_granted?(arg1, scope)

@spec permission_granted?(t(), map()) :: boolean()

Checks if permission is granted for this tab given a scope.

Returns true if:

  • The tab has no permission requirement (permission is nil)
  • The scope has module access for the tab's permission key

resolve_path(tab, context)

@spec resolve_path(t(), :admin | :settings | :user_dashboard) :: t()

Resolves a relative tab path to an absolute path based on context.

Modules define short relative paths (e.g., "hello-world") and the core prepends the appropriate prefix based on which callback returned the tab:

  • :admin — prepends /admin
  • :settings — prepends /admin/settings
  • :user_dashboard — prepends /dashboard

Absolute paths (starting with /) pass through unchanged. Empty strings resolve to the context root (e.g., "" + :admin"/admin").

set_attention(tab, attention)

@spec set_attention(t(), atom() | nil) :: t()

Sets an attention animation on the tab.

show_subtabs?(arg1, active)

@spec show_subtabs?(t(), boolean()) :: boolean()

Checks if subtabs should be shown for this tab based on its display setting and active state.

Examples

iex> tab = %Tab{subtab_display: :always}
iex> Tab.show_subtabs?(tab, false)
true

iex> tab = %Tab{subtab_display: :when_active}
iex> Tab.show_subtabs?(tab, false)
false

iex> tab = %Tab{subtab_display: :when_active}
iex> Tab.show_subtabs?(tab, true)
true

subtab?(arg1)

@spec subtab?(t()) :: boolean()

Checks if this tab is a subtab (has a parent).

top_level?(tab)

@spec top_level?(t()) :: boolean()

Checks if this tab is a top-level tab (not a subtab).

update_badge(tab, badge)

@spec update_badge(t(), PhoenixKit.Dashboard.Badge.t() | map() | nil) :: t()

Updates a tab's badge value.

user?(arg1)

@spec user?(t()) :: boolean()

Checks if this is a user-level tab.

visible?(arg1, scope)

@spec visible?(t(), map()) :: boolean()

Evaluates the visibility of a tab given a scope.

Examples

iex> tab = %Tab{visible: true}
iex> Tab.visible?(tab, %{})
true

iex> tab = %Tab{visible: fn scope -> scope.user.beta_enabled end}
iex> Tab.visible?(tab, %{user: %{beta_enabled: true}})
true