PhoenixKit.Dashboard (phoenix_kit v1.7.43)

Copy Markdown View Source

User Dashboard Tab Management System.

PhoenixKit's dashboard provides a flexible, extensible navigation system that parent applications can customize with their own tabs, badges, and features.

Features

  • Dynamic Tabs: Register tabs from config or at runtime
  • Live Badges: Real-time badge updates via PubSub
  • Grouping: Organize tabs into logical sections with headers
  • Conditional Visibility: Show/hide tabs based on roles or custom logic
  • Attention Indicators: Pulse, bounce, shake animations to draw attention
  • Presence Tracking: Show how many users are viewing each tab
  • Path Matching: Flexible active state detection (exact, prefix, custom)

Quick Start

1. Configure Tabs in config.exs

config :phoenix_kit, :user_dashboard_tabs, [
  %{
    id: :orders,
    label: "My Orders",
    icon: "hero-shopping-bag",
    path: "/dashboard/orders",
    priority: 100
  },
  %{
    id: :notifications,
    label: "Notifications",
    icon: "hero-bell",
    path: "/dashboard/notifications",
    priority: 200,
    badge: %{type: :count, value: 0, color: :error}
  }
]

2. Register Tabs at Runtime (Optional)

# In your application startup or a LiveView mount
PhoenixKit.Dashboard.register_tabs(:my_app, [
  %{
    id: :printers,
    label: "Printers",
    icon: "hero-cube",
    path: "/dashboard/printers",
    priority: 150,
    badge: %{
      type: :count,
      subscribe: {"farm:stats", fn msg -> msg.printing_count end}
    }
  }
])

3. Update Badges Live

# From anywhere in your app
PhoenixKit.Dashboard.update_badge(:notifications, 5)
PhoenixKit.Dashboard.update_badge(:printers, count: 3, color: :warning)

4. Trigger Attention

# Make a tab pulse to draw attention
PhoenixKit.Dashboard.set_attention(:alerts, :pulse)

Tab Groups

Organize tabs into sections:

config :phoenix_kit, :user_dashboard_tab_groups, [
  %{id: :main, label: nil, priority: 100},
  %{id: :farm, label: "Farm Management", priority: 200, icon: "hero-cube"},
  %{id: :account, label: "Account", priority: 900}
]

Then assign tabs to groups:

%{id: :printers, label: "Printers", path: "/dashboard/printers", group: :farm}

Conditional Visibility

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

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

Live Badges with PubSub

Badges can subscribe to PubSub topics for real-time updates:

%{
  id: :notifications,
  label: "Notifications",
  path: "/dashboard/notifications",
  badge: %{
    type: :count,
    color: :error,
    subscribe: {"user:#{user_id}:notifications", :unread_count}
  }
}

When a message is broadcast to the topic, the badge automatically updates.

Presence Tracking

Track which users are viewing which tabs:

# In your LiveView
def mount(_params, _session, socket) do
  if connected?(socket) do
    PhoenixKit.Dashboard.Presence.track_tab(socket, :orders)
  end
  {:ok, socket}
end

The sidebar will show "2 viewing" indicators.

Summary

Functions

Clears attention animation from a tab.

Clears a tab's badge.

Checks if the context selector feature is enabled.

Creates a count badge.

Gets the current context from socket assigns.

Gets the current context ID from socket assigns.

Decrements a tab's count badge by a given amount.

Creates a divider for visual separation in the sidebar.

Creates a dot badge.

Gets all admin-level tabs, filtered by permission and module-enabled status.

Gets viewer counts for all tabs.

Gets all registered tab groups.

Gets all subtabs for a given parent tab ID.

Gets a specific tab by ID.

Gets all registered tabs, sorted by priority.

Gets all tabs with their active state for the given path.

Gets only top-level tabs (tabs without a parent).

Gets all user-level tabs, filtered by visibility and scope.

Gets the number of users viewing a specific tab.

Creates a group header for organizing tabs.

Checks if the user has multiple contexts available.

Checks if a tab has any subtabs.

Increments a tab's count badge by a given amount.

Creates a live badge that subscribes to PubSub updates.

Loads the default admin tabs into the registry.

Checks if a tab matches the given path.

Creates a new Badge struct.

Creates a new Tab struct.

Creates a new Tab struct, raising on error.

Gets the PubSub topic for tab updates.

Registers admin tabs for an application namespace.

Registers tab groups for organizing the sidebar.

Registers dashboard tabs for an application namespace.

Sets an attention animation on a tab.

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

Creates a status badge.

Subscribes the current process to tab updates.

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

Tracks a user's presence on a dashboard tab.

Unregisters a specific tab by ID.

Unregisters all tabs for a namespace.

Updates a tab's badge.

Updates an existing tab's attributes by ID.

Checks if a tab is visible for the given scope.

Functions

clear_attention(tab_id)

@spec clear_attention(atom()) :: :ok

Clears attention animation from a tab.

clear_badge(tab_id)

@spec clear_badge(atom()) :: :ok

Clears a tab's badge.

context_selector_enabled?()

@spec context_selector_enabled?() :: boolean()

Checks if the context selector feature is enabled.

Examples

if PhoenixKit.Dashboard.context_selector_enabled?() do
  # Context switching is available
end

count_badge(value, opts \\ [])

@spec count_badge(
  integer(),
  keyword()
) :: PhoenixKit.Dashboard.Badge.t()

Creates a count badge.

current_context(arg1)

@spec current_context(Phoenix.LiveView.Socket.t() | map()) :: any() | nil

Gets the current context from socket assigns.

Returns nil if context selector is not configured or no context is selected.

Examples

context = PhoenixKit.Dashboard.current_context(socket)
# => %MyApp.Farm{id: 1, name: "My Farm"}

context = PhoenixKit.Dashboard.current_context(socket.assigns)
# => %MyApp.Farm{id: 1, name: "My Farm"}

current_context_id(socket_or_assigns)

@spec current_context_id(Phoenix.LiveView.Socket.t() | map()) :: any() | nil

Gets the current context ID from socket assigns.

Convenience function that extracts just the ID.

Examples

context_id = PhoenixKit.Dashboard.current_context_id(socket)
# => 1

decrement_badge(tab_id, amount \\ 1)

@spec decrement_badge(atom(), integer()) :: :ok

Decrements a tab's count badge by a given amount.

Will not go below 0.

divider(opts \\ [])

@spec divider(keyword()) :: PhoenixKit.Dashboard.Tab.t()

Creates a divider for visual separation in the sidebar.

Examples

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

dot_badge(opts \\ [])

@spec dot_badge(keyword()) :: PhoenixKit.Dashboard.Badge.t()

Creates a dot badge.

get_admin_tabs(opts \\ [])

@spec get_admin_tabs(keyword()) :: [PhoenixKit.Dashboard.Tab.t()]

Gets all admin-level tabs, filtered by permission and module-enabled status.

Options

  • :scope - The current authentication scope for permission filtering

Examples

tabs = PhoenixKit.Dashboard.get_admin_tabs(scope: scope)

get_all_viewer_counts()

@spec get_all_viewer_counts() :: map()

Gets viewer counts for all tabs.

Examples

counts = PhoenixKit.Dashboard.get_all_viewer_counts()
# => %{orders: 3, settings: 1, printers: 2}

get_groups()

@spec get_groups() :: [PhoenixKit.Dashboard.Group.t()]

Gets all registered tab groups.

get_subtabs(parent_id, opts \\ [])

@spec get_subtabs(
  atom(),
  keyword()
) :: [PhoenixKit.Dashboard.Tab.t()]

Gets all subtabs for a given parent tab ID.

Examples

PhoenixKit.Dashboard.get_subtabs(:orders)
# => [%Tab{id: :pending_orders, parent: :orders, ...}, ...]

get_tab(tab_id)

@spec get_tab(atom()) :: PhoenixKit.Dashboard.Tab.t() | nil

Gets a specific tab by ID.

Examples

tab = PhoenixKit.Dashboard.get_tab(:orders)

get_tabs(opts \\ [])

@spec get_tabs(keyword()) :: [PhoenixKit.Dashboard.Tab.t()]

Gets all registered tabs, sorted by priority.

Options

  • :scope - Filter by visibility using the current scope
  • :include_hidden - Include tabs that would be hidden (default: false)

Examples

tabs = PhoenixKit.Dashboard.get_tabs()
tabs = PhoenixKit.Dashboard.get_tabs(scope: socket.assigns.phoenix_kit_current_scope)

get_tabs_with_active(current_path, opts \\ [])

@spec get_tabs_with_active(
  String.t(),
  keyword()
) :: [map()]

Gets all tabs with their active state for the given path.

Returns tabs with an additional :active key.

Examples

tabs = PhoenixKit.Dashboard.get_tabs_with_active("/dashboard/orders")

get_top_level_tabs(opts \\ [])

@spec get_top_level_tabs(keyword()) :: [PhoenixKit.Dashboard.Tab.t()]

Gets only top-level tabs (tabs without a parent).

Examples

PhoenixKit.Dashboard.get_top_level_tabs()
# => [%Tab{id: :orders, parent: nil, ...}, ...]

get_user_tabs(opts \\ [])

@spec get_user_tabs(keyword()) :: [PhoenixKit.Dashboard.Tab.t()]

Gets all user-level tabs, filtered by visibility and scope.

Options

  • :scope - The current authentication scope for visibility filtering

Examples

tabs = PhoenixKit.Dashboard.get_user_tabs(scope: scope)

get_viewer_count(tab_id)

@spec get_viewer_count(atom()) :: integer()

Gets the number of users viewing a specific tab.

Examples

count = PhoenixKit.Dashboard.get_viewer_count(:orders)

group_header(opts)

@spec group_header(keyword()) :: PhoenixKit.Dashboard.Tab.t()

Creates a group header for organizing tabs.

Examples

PhoenixKit.Dashboard.group_header(id: :farm, label: "Farm Management", priority: 200)

has_multiple_contexts?(arg1)

@spec has_multiple_contexts?(Phoenix.LiveView.Socket.t() | map()) :: boolean()

Checks if the user has multiple contexts available.

Returns true only if context selector is enabled and user has 2+ contexts.

Examples

if PhoenixKit.Dashboard.has_multiple_contexts?(socket) do
  # Show context-specific UI
end

has_subtabs?(tab_id)

@spec has_subtabs?(atom()) :: boolean()

Checks if a tab has any subtabs.

Examples

PhoenixKit.Dashboard.has_subtabs?(:orders)
# => true

increment_badge(tab_id, amount \\ 1)

@spec increment_badge(atom(), integer()) :: :ok

Increments a tab's count badge by a given amount.

Examples

PhoenixKit.Dashboard.increment_badge(:notifications)
PhoenixKit.Dashboard.increment_badge(:notifications, 5)

live_badge(topic, extractor, opts \\ [])

@spec live_badge(String.t(), atom() | (map() -> any()), keyword()) ::
  PhoenixKit.Dashboard.Badge.t()

Creates a live badge that subscribes to PubSub updates.

load_admin_defaults()

@spec load_admin_defaults() :: :ok

Loads the default admin tabs into the registry.

Called automatically on Registry startup, but can be called manually to reload defaults after changes.

matches_path?(tab, path)

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

Checks if a tab matches the given path.

new_badge(attrs)

@spec new_badge(map() | keyword()) ::
  {:ok, PhoenixKit.Dashboard.Badge.t()} | {:error, String.t()}

Creates a new Badge struct.

See PhoenixKit.Dashboard.Badge.new/1 for options.

new_tab(attrs)

@spec new_tab(map() | keyword()) ::
  {:ok, PhoenixKit.Dashboard.Tab.t()} | {:error, String.t()}

Creates a new Tab struct.

See PhoenixKit.Dashboard.Tab.new/1 for options.

new_tab!(attrs)

@spec new_tab!(map() | keyword()) :: PhoenixKit.Dashboard.Tab.t()

Creates a new Tab struct, raising on error.

pubsub_topic()

@spec pubsub_topic() :: String.t()

Gets the PubSub topic for tab updates.

Subscribe to this topic to receive real-time tab updates in LiveViews.

Example

def mount(_params, _session, socket) do
  if connected?(socket) do
    Phoenix.PubSub.subscribe(PubSubHelper.pubsub(), PhoenixKit.Dashboard.pubsub_topic())
  end
  {:ok, socket}
end

def handle_info({:tab_updated, tab}, socket) do
  # Handle tab update - refresh sidebar
  {:noreply, assign(socket, tabs: PhoenixKit.Dashboard.get_tabs())}
end

def handle_info(:tabs_refreshed, socket) do
  # Full tab list refresh
  {:noreply, assign(socket, tabs: PhoenixKit.Dashboard.get_tabs())}
end

register_admin_tabs(namespace, tabs)

@spec register_admin_tabs(atom(), [map() | PhoenixKit.Dashboard.Tab.t()]) ::
  :ok | {:error, term()}

Registers admin tabs for an application namespace.

Automatically sets level: :admin on all tabs.

Examples

PhoenixKit.Dashboard.register_admin_tabs(:my_app, [
  %{id: :admin_analytics, label: "Analytics", path: "/admin/analytics",
    icon: "hero-chart-bar", permission: "dashboard"}
])

register_groups(groups)

@spec register_groups([PhoenixKit.Dashboard.Group.t() | map()]) :: :ok

Registers tab groups for organizing the sidebar.

Examples

PhoenixKit.Dashboard.register_groups([
  %{id: :main, label: nil, priority: 100},
  %{id: :farm, label: "Farm Management", priority: 200, icon: "hero-cube"},
  %{id: :account, label: "Account", priority: 900}
])

register_tabs(namespace, tabs)

@spec register_tabs(atom(), [map() | PhoenixKit.Dashboard.Tab.t()]) ::
  :ok | {:error, term()}

Registers dashboard tabs for an application namespace.

Examples

# Register multiple tabs
PhoenixKit.Dashboard.register_tabs(:my_app, [
  %{id: :orders, label: "Orders", path: "/dashboard/orders", icon: "hero-shopping-bag"},
  %{id: :history, label: "History", path: "/dashboard/history", icon: "hero-clock"}
])

# Register a single tab
PhoenixKit.Dashboard.register_tabs(:my_app, [
  Tab.new!(id: :custom, label: "Custom", path: "/dashboard/custom")
])

set_attention(tab_id, animation)

@spec set_attention(atom(), atom()) :: :ok

Sets an attention animation on a tab.

Animation Types

  • :pulse - Gentle pulsing glow
  • :bounce - Bouncing motion
  • :shake - Shaking motion (for errors/alerts)
  • :glow - Glowing effect

Examples

PhoenixKit.Dashboard.set_attention(:alerts, :pulse)
PhoenixKit.Dashboard.set_attention(:errors, :shake)

show_subtabs?(tab, active)

@spec show_subtabs?(PhoenixKit.Dashboard.Tab.t(), boolean()) :: boolean()

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

Examples

PhoenixKit.Dashboard.show_subtabs?(tab, true)  # parent is active
# => true (for :when_active or :always)

PhoenixKit.Dashboard.show_subtabs?(tab, false)  # parent not active
# => true (only for :always)

status_badge(value, opts \\ [])

@spec status_badge(
  atom() | String.t(),
  keyword()
) :: PhoenixKit.Dashboard.Badge.t()

Creates a status badge.

subscribe()

@spec subscribe() :: :ok | {:error, term()}

Subscribes the current process to tab updates.

Convenience wrapper around Phoenix.PubSub.subscribe/2.

subtab?(tab)

@spec subtab?(PhoenixKit.Dashboard.Tab.t()) :: boolean()

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

Examples

PhoenixKit.Dashboard.subtab?(:pending_orders)
# => true

track_presence(socket, tab_id, opts \\ [])

@spec track_presence(Phoenix.LiveView.Socket.t(), atom(), keyword()) ::
  {:ok, String.t()} | {:error, term()}

Tracks a user's presence on a dashboard tab.

Examples

PhoenixKit.Dashboard.track_presence(socket, :orders)

unregister_tab(tab_id)

@spec unregister_tab(atom()) :: :ok

Unregisters a specific tab by ID.

Examples

PhoenixKit.Dashboard.unregister_tab(:orders)

unregister_tabs(namespace)

@spec unregister_tabs(atom()) :: :ok

Unregisters all tabs for a namespace.

Examples

PhoenixKit.Dashboard.unregister_tabs(:my_app)

update_badge(tab_id, value)

@spec update_badge(
  atom(),
  integer() | map() | keyword() | PhoenixKit.Dashboard.Badge.t() | nil
) :: :ok

Updates a tab's badge.

Examples

# Set a count badge
PhoenixKit.Dashboard.update_badge(:notifications, 5)

# Set badge with options
PhoenixKit.Dashboard.update_badge(:alerts, count: 3, color: :error, pulse: true)

# Set a dot badge
PhoenixKit.Dashboard.update_badge(:status, type: :dot, color: :success)

# Clear a badge
PhoenixKit.Dashboard.update_badge(:notifications, nil)

update_tab(tab_id, attrs)

@spec update_tab(atom(), map()) :: :ok | {:error, :not_found}

Updates an existing tab's attributes by ID.

Examples

PhoenixKit.Dashboard.update_tab(:admin_dashboard, %{label: "Home", icon: "hero-home"})

visible?(tab, scope)

@spec visible?(PhoenixKit.Dashboard.Tab.t(), map()) :: boolean()

Checks if a tab is visible for the given scope.