# `PhoenixKit.Dashboard`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.87/lib/phoenix_kit/dashboard/dashboard.ex#L1)

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_uuid}: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.

# `clear_attention`

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

Clears attention animation from a tab.

# `clear_badge`

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

Clears a tab's badge.

# `context_selector_enabled?`

```elixir
@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`

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

Creates a count badge.

# `current_context`

```elixir
@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_uuid`

```elixir
@spec current_context_uuid(Phoenix.LiveView.Socket.t() | map()) :: any() | nil
```

Gets the current context ID from socket assigns.

Convenience function that extracts just the ID.

## Examples

    context_uuid = PhoenixKit.Dashboard.current_context_uuid(socket)
    # => "550e8400-e29b-41d4-a716-446655440000"

# `decrement_badge`

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

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

Will not go below 0.

# `divider`

```elixir
@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`

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

Creates a dot badge.

# `get_admin_tabs`

```elixir
@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`

```elixir
@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`

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

Gets all registered tab groups.

# `get_subtabs`

```elixir
@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`

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

Gets a specific tab by ID.

## Examples

    tab = PhoenixKit.Dashboard.get_tab(:orders)

# `get_tabs`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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?`

```elixir
@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?`

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

Checks if a tab has any subtabs.

## Examples

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

# `increment_badge`

```elixir
@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`

```elixir
@spec live_badge(String.t(), atom() | (map() -&gt; any()), keyword()) ::
  PhoenixKit.Dashboard.Badge.t()
```

Creates a live badge that subscribes to PubSub updates.

# `load_admin_defaults`

```elixir
@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?`

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

Checks if a tab matches the given path.

# `new_badge`

```elixir
@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`

```elixir
@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!`

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

Creates a new Tab struct, raising on error.

# `pubsub_topic`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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?`

```elixir
@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`

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

Creates a status badge.

# `subscribe`

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

Subscribes the current process to tab updates.

Convenience wrapper around Phoenix.PubSub.subscribe/2.

# `subtab?`

```elixir
@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`

```elixir
@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`

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

Unregisters a specific tab by ID.

## Examples

    PhoenixKit.Dashboard.unregister_tab(:orders)

# `unregister_tabs`

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

Unregisters all tabs for a namespace.

## Examples

    PhoenixKit.Dashboard.unregister_tabs(:my_app)

# `update_badge`

```elixir
@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`

```elixir
@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?`

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

Checks if a tab is visible for the given scope.

---

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