# `PhoenixKit.ProfileSettingsTab`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v2.64.1/lib/phoenix_kit/profile_settings_tab.ex#L1)

A tab a host application or a module adds to the signed-in user's settings
strip, `/profile/settings/<id>` (`PhoenixKitWeb.Components.ProfileSettingsTabs`).

The tab is a link to a page of its own, like core's Integrations tab: every
tab of the strip navigates to it, and it navigates back. The page renders
the strip itself so it looks like the other tabs:

    <PhoenixKitWeb.Components.LayoutWrapper.app_layout
      socket={@socket}
      flash={@flash}
      phoenix_kit_current_scope={@phoenix_kit_current_scope}
      page_title={gettext("My tab")}
      current_path={PhoenixKitWeb.Components.ProfileSettingsTabs.path("my-tab")}
    >
      <div class="max-w-7xl mx-auto px-4 py-6 sm:px-6 lg:px-8">
        <PhoenixKitWeb.Components.ProfileSettingsTabs.profile_tabs
          active="my-tab"
          scope={@phoenix_kit_current_scope}
        />
        ...
      </div>
    </PhoenixKitWeb.Components.LayoutWrapper.app_layout>

## Declaring tabs

A host lists them in config:

    config :phoenix_kit, :profile_settings_tabs, [
      %{
        id: "my-tab",
        label: {MyAppWeb.ProfileTabs, :my_tab_label, []},
        icon: "hero-puzzle-piece",
        after: "security",
        visible: {MyApp.MyFeature, :available?},
        live_view: MyAppWeb.MyTabLive
      }
    ]

Use the tuple forms in config: a release cannot carry a function in its
config. A module returns the same maps from `c:PhoenixKit.Module.profile_settings_tabs/0`
(functions are fine there);
its tabs are offered only while the module is enabled. Host tabs come
first; when two declare one id, the first wins and the other is dropped
with a warning.

## Fields

  * `:id` (required) — the URL segment, `/profile/settings/<id>`: lowercase
    letters, digits, `-` and `_`, starting with a letter or digit. Not one of
    core's tab ids or `confirm-email` (`reserved_ids/0`).
  * `:label` (required) — a string, a `{module, function, args}` or a
    zero-arity function. The last two are called on every render, after
    the kit has set the request's Gettext locale, so they can return the
    host's own `gettext("My tab")` — a plain string is shown as is.
  * `:icon` — a heroicon name (`"hero-puzzle-piece"`), default
    `"hero-squares-2x2"`.
  * `:after` — the id of the tab this one follows (a core tab or another
    registered one). Absent, or naming a tab that does not exist, puts it
    at the end. Tabs after the same one keep their declared order.
  * `:permission` — a permission key the user must hold
    (`PhoenixKit.Users.Auth.Scope.can?/2`, including its base permission
    and live feature-enablement checks). It must be a key the permission
    system knows — a module's, a core section's, or a host key registered
    with `PhoenixKit.Users.Permissions.register_custom_key/2`; an unknown
    key reads as a disabled feature and hides the tab.
  * `:visible` — `{module, function}` or a one-argument function, called
    with the scope (`nil` for nobody signed in); the tab is offered when it
    returns `true`. One that raises hides the tab (with one warning). With
    `:permission` too, both must pass.
  * `:live_view` — the page: a LiveView module or `{module, action}`
    (action `:index` by default). Core then declares
    `live "/profile/settings/<id>"` in its authenticated `live_session`, in
    both the locale-prefixed and the plain URL shapes, before its own
    `/profile/settings/:tab`. Without it the host declares the route ITSELF,
    before `phoenix_kit_routes()` (and in both shapes); a tab whose page is
    missing lands on `ProfileSettings`, which redirects it to the first tab.

The tab decides what the strip offers, not who may open the page: the
page checks access in its own `mount/3`, as any page does.

# `label`

```elixir
@type label() :: String.t() | {module(), atom(), list()} | (-&gt; String.t())
```

# `t`

```elixir
@type t() :: %PhoenixKit.ProfileSettingsTab{
  after: String.t() | nil,
  icon: String.t(),
  id: String.t(),
  label: label(),
  live_view: {module(), atom()} | nil,
  module: module() | nil,
  permission: String.t() | nil,
  visible: visible()
}
```

# `visible`

```elixir
@type visible() :: {module(), atom()} | (term() -&gt; boolean()) | nil
```

# `all`

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

The registered tabs: the host's (`config :phoenix_kit, :profile_settings_tabs`)
followed by those of the enabled modules. Invalid and duplicate entries are
dropped, each with one warning.

# `config_entries`

```elixir
@spec config_entries() :: term()
```

The host's raw config entries, as the router compares them for recompiles.

# `from_module`

```elixir
@spec from_module(module()) :: [t()]
```

The tabs `mod.profile_settings_tabs/0` declares, normalised. A callback that
raises contributes nothing (one warning).

# `label`

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

The tab's label for this render.

# `new`

```elixir
@spec new(term()) :: {:ok, t()} | {:error, String.t()}
```

Normalise one entry (a map or a keyword list). Returns `{:error, reason}`
(a string) for anything that is not a valid tab.

# `reserved_ids`

```elixir
@spec reserved_ids() :: [String.t()]
```

The ids a registered tab cannot take.

# `routed`

```elixir
@spec routed([module()]) :: [t()]
```

The tabs whose page core routes, for the router macros: the host's and
those of every discovered module, enabled or not (routes are compiled;
the strip asks `enabled?/0` at render time). `modules` are the modules to
ask, already compiled.

# `visible?`

```elixir
@spec visible?(t(), term()) :: boolean()
```

Whether `scope` is offered the tab: its `:permission` held and its
`:visible` true. Fails closed — a check that raises hides the tab.

---

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