Packages
phoenix_kit
1.2.9
1.7.208
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
Files
Jump to
Current section
Files
lib/phoenix_kit_web/components/admin_nav.ex
defmodule PhoenixKitWeb.AdminNav do
@moduledoc """
Admin navigation components for the PhoenixKit admin panel.
Provides consistent navigation elements for both desktop sidebar and mobile drawer.
"""
use Phoenix.Component
alias Phoenix.LiveView.JS
alias PhoenixKit.Utils.Routes
@doc """
Renders an admin navigation item with proper active state styling.
## Examples
<.admin_nav_item
href={Routes.path("/admin/dashboard")}
icon="dashboard"
label="Dashboard"
current_path={Routes.path("/admin/dashboard")}
/>
<.admin_nav_item
href={Routes.path("/admin/users")}
icon="users"
label="Users"
current_path={Routes.path("/admin/dashboard")}
mobile={true}
/>
"""
attr :href, :string, required: true
attr :icon, :string, required: true
attr :label, :string, required: true
attr :description, :string, default: nil
attr :current_path, :string, required: true
attr :mobile, :boolean, default: false
attr :nested, :boolean, default: false
attr :disable_active, :boolean, default: false
def admin_nav_item(assigns) do
active =
if assigns.disable_active,
do: false,
else: nav_item_active?(assigns.current_path, assigns.href)
assigns = assign(assigns, :active, active)
~H"""
<.link
href={@href}
class={[
"flex items-center py-2 rounded-lg text-sm font-medium transition-colors",
"hover:bg-base-200 group",
if(@active,
do: "bg-primary text-primary-content",
else: "text-base-content hover:text-primary"
),
if(@mobile, do: "w-full", else: ""),
if(@nested, do: "pl-8 pr-3", else: "px-3")
]}
>
<%= if @nested do %>
<!-- Nested item indicator -->
<div class="w-4 h-4 mr-2 flex items-center justify-center">
<div class="w-1.5 h-1.5 bg-current opacity-50 rounded-full"></div>
</div>
<span class="text-sm">{@label}</span>
<% else %>
<.admin_nav_icon icon={@icon} active={@active} />
<span class="ml-3 font-medium">{@label}</span>
<% end %>
</.link>
"""
end
@doc """
Renders an icon for admin navigation items.
"""
attr :icon, :string, required: true
attr :active, :boolean, default: false
def admin_nav_icon(assigns) do
~H"""
<div class="flex-shrink-0">
<%= case @icon do %>
<% "dashboard" -> %>
<PhoenixKitWeb.Components.Core.Icons.icon_dashboard />
<% "users" -> %>
<PhoenixKitWeb.Components.Core.Icons.icon_users />
<% "roles" -> %>
<PhoenixKitWeb.Components.Core.Icons.icon_roles />
<% "modules" -> %>
<PhoenixKitWeb.Components.Core.Icons.icon_modules />
<% "settings" -> %>
<PhoenixKitWeb.Components.Core.Icons.icon_settings />
<% "sessions" -> %>
<PhoenixKitWeb.Components.Core.Icons.icon_sessions />
<% "live_sessions" -> %>
<PhoenixKitWeb.Components.Core.Icons.icon_live_sessions />
<% "referral_codes" -> %>
<PhoenixKitWeb.Components.Core.Icons.icon_referral_codes />
<% "email" -> %>
<PhoenixKitWeb.Components.Core.Icons.icon_email />
<% _ -> %>
<PhoenixKitWeb.Components.Core.Icons.icon_default />
<% end %>
</div>
"""
end
@doc """
Renders theme controller for admin panel.
Based on EZNews theme system with DaisyUI integration.
"""
attr :mobile, :boolean, default: false
def admin_theme_controller(assigns) do
~H"""
<div class={[
"card relative flex flex-row items-center border-2 border-base-300 bg-base-300 rounded-full",
if(@mobile, do: "scale-90", else: "")
]}>
<!-- Animated slider -->
<div class="absolute w-1/3 h-full rounded-full border-1 border-base-200 bg-base-100 brightness-200 left-0 [[data-theme=light]_&]:left-1/3 [[data-theme=dark]_&]:left-2/3 transition-[left]" />
<!-- System theme button -->
<button
class="flex p-2 cursor-pointer w-1/3 justify-center items-center tooltip z-10 relative"
phx-click={JS.dispatch("phx:set-admin-theme", detail: %{theme: "system"})}
data-tip="System theme"
data-theme-target="system"
>
<PhoenixKitWeb.Components.Core.Icons.icon_system />
</button>
<!-- Light theme button -->
<button
class="flex p-2 cursor-pointer w-1/3 justify-center items-center tooltip z-10 relative"
phx-click={JS.dispatch("phx:set-admin-theme", detail: %{theme: "light"})}
data-tip="Light theme"
data-theme-target="light"
>
<PhoenixKitWeb.Components.Core.Icons.icon_light />
</button>
<!-- Dark theme button -->
<button
class="flex p-2 cursor-pointer w-1/3 justify-center items-center tooltip z-10 relative"
phx-click={JS.dispatch("phx:set-admin-theme", detail: %{theme: "dark"})}
data-tip="Dark theme"
data-theme-target="dark"
>
<PhoenixKitWeb.Components.Core.Icons.icon_dark />
</button>
</div>
"""
end
@doc """
Renders user information section for admin panel sidebar.
Shows current user email and role information.
"""
attr :scope, :any, default: nil
def admin_user_info(assigns) do
~H"""
<%= if @scope && PhoenixKit.Users.Auth.Scope.authenticated?(@scope) do %>
<div class="bg-base-200 rounded-lg p-3 text-sm">
<div class="flex items-center gap-2 mb-2">
<div class="w-8 h-8 bg-primary rounded-full flex items-center justify-center text-primary-content text-xs font-bold">
{String.first(PhoenixKit.Users.Auth.Scope.user_email(@scope) || "?") |> String.upcase()}
</div>
<div class="flex-1 min-w-0">
<div class="truncate text-xs font-medium text-base-content">
{PhoenixKit.Users.Auth.Scope.user_email(@scope)}
</div>
<%= if PhoenixKit.Users.Auth.Scope.owner?(@scope) do %>
<div class="badge badge-error badge-xs">Owner</div>
<% else %>
<%= if PhoenixKit.Users.Auth.Scope.admin?(@scope) do %>
<div class="badge badge-warning badge-xs">Admin</div>
<% else %>
<div class="badge badge-ghost badge-xs">User</div>
<% end %>
<% end %>
</div>
</div>
<div class="flex gap-1">
<.link
href={Routes.path("/users/settings")}
class="btn btn-ghost btn-xs flex-1"
>
<PhoenixKitWeb.Components.Core.Icons.icon_settings class="w-3 h-3" />
</.link>
<.link
href={Routes.path("/users/log-out")}
method="delete"
class="btn btn-ghost btn-xs flex-1 text-error hover:bg-error hover:text-error-content"
>
<PhoenixKitWeb.Components.Core.Icons.icon_logout />
</.link>
</div>
</div>
<% else %>
<div class="bg-base-200 rounded-lg p-3 text-center text-sm">
<div class="text-base-content/70 mb-2">Not authenticated</div>
<.link href={Routes.path("/users/log-in")} class="btn btn-primary btn-sm w-full">
Login
</.link>
</div>
<% end %>
"""
end
# Helper function to determine if navigation item is active
defp nav_item_active?(current_path, href) do
current_parts = parse_admin_path(current_path)
href_parts = parse_admin_path(href)
exact_match?(current_parts, href_parts) or
tab_match?(current_parts, href_parts) or
parent_match?(current_parts, href_parts)
end
# Check if paths match exactly
defp exact_match?(current_parts, href_parts) do
href_parts.base_path == current_parts.base_path &&
is_nil(href_parts.tab) &&
is_nil(current_parts.tab)
end
# Check if tab-specific paths match
defp tab_match?(current_parts, href_parts) do
href_parts.base_path == current_parts.base_path &&
href_parts.tab == current_parts.tab
end
# Check if parent page matches when on a tab
defp parent_match?(current_parts, href_parts) do
href_parts.base_path == current_parts.base_path &&
is_nil(href_parts.tab) &&
not is_nil(current_parts.tab)
end
# Helper function to parse admin path into components
defp parse_admin_path(path) when is_binary(path) do
# Remove query parameters and split path
[path_part | _] = String.split(path, "?")
# Get dynamic prefix and normalize paths
prefix = PhoenixKit.Config.get_url_prefix()
admin_prefix = if prefix == "/", do: "/admin", else: "#{prefix}/admin"
base_path =
path_part
|> String.replace_prefix(admin_prefix, "")
|> String.replace_prefix(prefix, "")
|> case do
# Default to dashboard for root
"" -> "dashboard"
"/" -> "dashboard"
path -> String.trim_leading(path, "/")
end
# Extract tab parameter if present
tab =
if String.contains?(path, "?tab=") do
path
|> String.split("?tab=")
|> List.last()
|> String.split("&")
|> List.first()
else
nil
end
%{base_path: base_path, tab: tab}
end
defp parse_admin_path(_), do: %{base_path: "dashboard", tab: nil}
end