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

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

# `dynamic_children_fn`

```elixir
@type dynamic_children_fn() :: (map() -&gt; [t()]) | nil
```

# `level`

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

# `match_type`

```elixir
@type match_type() :: :exact | :prefix | :regex | (String.t() -&gt; boolean())
```

# `subtab_animation`

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

# `subtab_display`

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

# `t`

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

```elixir
@type visibility() :: boolean() | (map() -&gt; boolean())
```

# `admin?`

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

Checks if this is an admin-level tab.

# `clear_attention`

```elixir
@spec clear_attention(t()) :: t()
```

Clears the attention animation from the tab.

# `divider`

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

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

Checks if this tab is a divider.

# `group_header`

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

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

Checks if this tab is a group header.

# `matches_path?`

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

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

# `navigable?`

```elixir
@spec navigable?(t()) :: boolean()
```

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

# `new`

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

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

Creates a new Tab struct, raising on error.

# `parent_id`

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

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

# `permission_granted?`

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

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

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

Sets an attention animation on the tab.

# `show_subtabs?`

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

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

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

# `top_level?`

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

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

# `update_badge`

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

Updates a tab's badge value.

# `user?`

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

Checks if this is a user-level tab.

# `visible?`

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

---

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