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

Configuration and helpers for the dashboard context selector.

The context selector allows users to switch between multiple contexts
(organizations, farms, teams, workspaces, etc.) in the dashboard.
Users with only one context won't see the selector.

## Single Selector Configuration (Legacy)

Configure in your `config/config.exs`:

    config :phoenix_kit, :dashboard_context_selector,
      loader: {MyApp.Farms, :list_for_user},
      display_name: fn farm -> farm.name end,
      id_field: :uuid,
      label: "Farm",
      icon: "hero-building-office",
      position: :sidebar,
      sub_position: :end,
      empty_behavior: :hide,
      session_key: "dashboard_context_uuid",
      tab_loader: {MyApp.Farms, :get_tabs_for_context}

## Multiple Selectors Configuration

Configure multiple independent or dependent selectors:

    config :phoenix_kit, :dashboard_context_selectors, [
      %{
        key: :organization,
        loader: {MyApp.Orgs, :list_for_user},
        display_name: fn org -> org.name end,
        label: "Organization",
        icon: "hero-building-office",
        position: :header,
        sub_position: :start,
        priority: 100
      },
      %{
        key: :project,
        depends_on: :organization,
        loader: {MyApp.Projects, :list_for_org},
        display_name: fn p -> p.name end,
        label: "Project",
        icon: "hero-folder",
        position: :header,
        sub_position: :end,
        priority: 200,
        on_parent_change: :reset
      }
    ]

## Configuration Options

- `:key` - Required for multi-selector. Unique atom identifier (e.g., `:organization`).
  Used in session storage and routes.

- `:loader` - Required. A `{Module, :function}` tuple that takes a user ID
  and returns a list of context items. Example: `{MyApp.Farms, :list_for_user}`
  For dependent selectors, the function receives `(user_uuid, parent_context)`.

- `:display_name` - Required. A function that takes a context item and returns
  the display string. Example: `fn farm -> farm.name end`

- `:id_field` - Optional. The field to use as the unique identifier.
  Defaults to `:id`. Can be an atom or a function.

- `:label` - Optional. The label shown in the UI (e.g., "Farm", "Organization").
  Defaults to `"Context"`.

- `:icon` - Optional. Heroicon name for the selector. Defaults to `"hero-building-office"`.

- `:position` - Optional. Which area to show the selector in.
  Options: `:header` (default), `:sidebar`.

- `:sub_position` - Optional. Where within the area to place the selector.
  For header: `:start` (left, after logo), `:end` (right, before user menu),
    or `{:priority, N}` to sort among other header items.
  For sidebar: `:start` (top), `:end` (pinned to very bottom),
    or `{:priority, N}` to sort among tabs.
  Defaults to `:start`.

- `:priority` - Optional. Integer for ordering within same position/sub_position.
  Lower values come first. Defaults to 500.

- `:depends_on` - Optional. Key of parent selector (for dependent selectors).
  When set, the loader receives `(user_uuid, parent_context)` instead of just `user_uuid`.

- `:on_parent_change` - Optional. What to do when parent selector changes.
  Options: `:reset` (default, select first), `:keep`, `{:redirect, "/path"}`.

- `:empty_behavior` - Optional. What to do when user has no contexts.
  Options: `:hide` (default), `:show_empty`, `{:redirect, "/path"}`.

- `:separator` - Optional. Separator shown between logo and selector in header
  (only applies to `position: :header, sub_position: :start`).
  Defaults to `"/"`. Set to `false` or `nil` to disable. Can be any string
  like `"›"`, `"|"`, or `"·"`.

  Note: The separator may appear slightly off-center due to internal padding
  in the selector dropdown. This is a visual quirk and can be adjusted by
  customizing the layout template if precise alignment is required.

- `:session_key` - Optional. The session key for storing the selected context UUID.
  Defaults to `"dashboard_context_uuid"` for single selector, or
  `"dashboard_context_uuids"` (map) for multiple selectors.

- `:tab_loader` - Optional. A `{Module, :function}` tuple that takes a context
  item and returns a list of tab definitions. Enables dynamic tabs that change
  based on the selected context. Example: `{MyApp.Farms, :get_tabs_for_context}`

## Usage in LiveViews

The `ContextProvider` on_mount hook automatically sets these assigns:

### Single Selector (Legacy)
- `@dashboard_contexts` - List of all contexts for the user
- `@current_context` - The currently selected context item
- `@show_context_selector` - Boolean, true only if user has 2+ contexts
- `@dashboard_tabs` - (Optional) List of Tab structs when `tab_loader` is configured

### Multiple Selectors
- `@dashboard_contexts_map` - Map of key => list of contexts
- `@current_contexts_map` - Map of key => current context item
- `@show_context_selectors_map` - Map of key => boolean
- `@context_selector_configs` - List of all ContextSelector configs

Access the current context in your LiveView:

    def mount(_params, _session, socket) do
      context = socket.assigns.current_context
      items = MyApp.Items.list_for_context(context.id)
      {:ok, assign(socket, items: items)}
    end

Or use the helper functions:

    context_uuid = PhoenixKit.Dashboard.current_context_uuid(socket)

# `on_parent_change`

```elixir
@type on_parent_change() :: :reset | :keep | {:redirect, String.t()}
```

# `sub_position`

```elixir
@type sub_position() :: :start | :end | {:priority, integer()}
```

# `t`

```elixir
@type t() :: %PhoenixKit.Dashboard.ContextSelector{
  depends_on: atom() | nil,
  display_name: (any() -&gt; String.t()) | nil,
  empty_behavior: :hide | :show_empty | {:redirect, String.t()},
  enabled: boolean(),
  icon: String.t() | nil,
  id_field: atom() | (any() -&gt; any()),
  key: atom() | nil,
  label: String.t(),
  loader: {module(), atom()} | nil,
  on_parent_change: on_parent_change(),
  position: :header | :sidebar,
  priority: integer(),
  separator: String.t() | false | nil,
  session_key: String.t(),
  sub_position: sub_position() | nil,
  tab_loader: {module(), atom()} | nil
}
```

# `enabled?`

```elixir
@spec enabled?() :: boolean()
```

Checks if the context selector feature is enabled.

Returns `true` if the feature is configured with a valid loader.

## Examples

    iex> PhoenixKit.Dashboard.ContextSelector.enabled?()
    true

# `find_by_id`

```elixir
@spec find_by_id(list(), any()) :: any() | nil
```

Finds a context by ID from a list of contexts.

Handles both string and integer ID comparison.

## Examples

    iex> PhoenixKit.Dashboard.ContextSelector.find_by_id(contexts, "123")
    %Farm{id: 123, ...}

# `get_all_configs`

```elixir
@spec get_all_configs() :: [t()]
```

Gets all context selector configurations.

Returns a list of validated `%ContextSelector{}` structs.
Handles both the legacy single selector config (`:dashboard_context_selector`)
and the new multi-selector config (`:dashboard_context_selectors`).

For legacy single selectors, assigns the default key `:default`.

## Examples

    iex> PhoenixKit.Dashboard.ContextSelector.get_all_configs()
    [%ContextSelector{key: :organization, ...}, %ContextSelector{key: :project, ...}]

# `get_config`

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

Gets the context selector configuration.

Returns a validated `%ContextSelector{}` struct if configured,
or a disabled struct if not configured.

## Examples

    iex> PhoenixKit.Dashboard.ContextSelector.get_config()
    %ContextSelector{enabled: true, loader: {MyApp.Farms, :list_for_user}, ...}

    iex> PhoenixKit.Dashboard.ContextSelector.get_config()
    %ContextSelector{enabled: false}

# `get_dependent_keys`

```elixir
@spec get_dependent_keys([t()], atom()) :: [atom()]
```

Gets the keys of all selectors that depend on the given key.

Useful for determining which selectors need to be reset when a parent changes.

## Examples

    iex> configs = [%{key: :org}, %{key: :project, depends_on: :org}]
    iex> PhoenixKit.Dashboard.ContextSelector.get_dependent_keys(configs, :org)
    [:project]

# `get_display_name`

```elixir
@spec get_display_name(any()) :: String.t()
```

Gets the display name for a context item using the configured function.

## Examples

    iex> PhoenixKit.Dashboard.ContextSelector.get_display_name(farm)
    "My Farm"

# `get_display_name_for_config`

```elixir
@spec get_display_name_for_config(t(), any()) :: String.t()
```

Gets the display name for a context item using a specific config.

## Examples

    iex> PhoenixKit.Dashboard.ContextSelector.get_display_name_for_config(config, item)
    "My Organization"

# `get_id`

```elixir
@spec get_id(any()) :: any()
```

Gets the ID for a context item using the configured id_field.

## Examples

    iex> PhoenixKit.Dashboard.ContextSelector.get_id(%{id: 123})
    123

# `get_id_for_config`

```elixir
@spec get_id_for_config(t(), any()) :: any()
```

Gets the ID for a context item using a specific config.

# `load_contexts`

```elixir
@spec load_contexts(any()) :: list()
```

Loads contexts for a user using the configured loader.

Returns an empty list if the feature is not enabled or the loader fails.

## Examples

    iex> PhoenixKit.Dashboard.ContextSelector.load_contexts(user_uuid)
    [%Farm{id: 1, name: "My Farm"}, %Farm{id: 2, name: "Other Farm"}]

# `load_contexts_for_config`

```elixir
@spec load_contexts_for_config(t(), any(), any()) :: list()
```

Loads contexts for a specific selector config.

For independent selectors, calls `loader(user_uuid)`.
For dependent selectors, calls `loader(user_uuid, parent_context)`.

## Parameters

- `config` - The selector configuration
- `user_uuid` - The user UUID to load contexts for
- `parent_context` - The parent context (required for dependent selectors)

# `load_tabs`

```elixir
@spec load_tabs(any()) :: list()
```

Loads tabs for the given context using the configured tab_loader.

Returns an empty list if no tab_loader is configured or if the loader fails.

## Examples

    iex> PhoenixKit.Dashboard.ContextSelector.load_tabs(context)
    [%{id: :overview, label: "Overview", ...}, ...]

# `multi_selector_enabled?`

```elixir
@spec multi_selector_enabled?() :: boolean()
```

Checks if multiple selectors are configured.

Returns `true` if the plural `:dashboard_context_selectors` config is set.

# `multi_session_key`

```elixir
@spec multi_session_key() :: String.t()
```

Gets the session key for storing multiple context IDs.

Returns the configured multi-selector session key.

# `order_by_dependencies`

```elixir
@spec order_by_dependencies([t()]) :: [t()]
```

Orders selectors by their dependencies using topological sort.

Independent selectors come first, then dependent selectors in order
of their dependency chain. Selectors with the same dependency level
are sorted by priority (lower first).

## Examples

    iex> configs = [%{key: :project, depends_on: :org}, %{key: :org}]
    iex> PhoenixKit.Dashboard.ContextSelector.order_by_dependencies(configs)
    [%{key: :org}, %{key: :project, depends_on: :org}]

# `session_key`

```elixir
@spec session_key() :: String.t()
```

Gets the session key for storing the context ID.

---

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