Skip to content

Commit 6f1cb14

Browse files
committed
Wrap nav in sticky LiveView to preserve sidebar state across navigation
- Add Nav.Wrapper sticky LiveView that hosts the nav LiveComponent and Jobs LiveView, preserving DOM state across navigate events - Broadcast URI via attach_hook on handle_params so the sticky nav can update active link highlighting via PubSub - Update CSS selectors from sibling (~) to :has() since the sidebar toggle checkbox now lives inside the sticky wrapper
1 parent 1c7f4d3 commit 6f1cb14

6 files changed

Lines changed: 102 additions & 24 deletions

File tree

assets/css/layout.css

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
transition: margin-left 0.2s, max-width 0.2s;
6363
}
6464

65-
.sidebar-toggle-input:not(:checked) ~ .main-layout .content {
65+
.main-layout:has(.sidebar-toggle-input:not(:checked)) .content {
6666
margin-left: 0;
6767
max-width: 100vw;
6868
}
@@ -73,7 +73,7 @@
7373
max-width: calc(100vw - 300px);
7474
}
7575

76-
.sidebar-toggle-input:not(:checked) ~ .main-layout .content {
76+
.main-layout:has(.sidebar-toggle-input:not(:checked)) .content {
7777
margin-left: 0;
7878
max-width: 100vw;
7979
}
@@ -85,7 +85,7 @@
8585
max-width: calc(100vw - 200px);
8686
}
8787

88-
.sidebar-toggle-input:not(:checked) ~ .main-layout .content {
88+
.main-layout:has(.sidebar-toggle-input:not(:checked)) .content {
8989
margin-left: 0;
9090
max-width: 100vw;
9191
}

assets/css/sidebar.css

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
transition: transform 0.2s;
2929
}
3030

31-
.sidebar-toggle-input:not(:checked) ~ .main-layout .sidebar-toggle-label svg {
31+
.sidebar:has(.sidebar-toggle-input:not(:checked)) .sidebar-toggle-label svg {
3232
transform: rotate(180deg);
3333
}
3434

@@ -54,13 +54,13 @@
5454
position: relative;
5555
}
5656

57-
.sidebar-toggle-input:not(:checked) ~ .main-layout .sidebar {
57+
.sidebar:has(.sidebar-toggle-input:not(:checked)) {
5858
min-width: 0;
5959
max-width: 0;
6060
border-right: none;
6161
}
6262

63-
.sidebar-toggle-input:not(:checked) ~ .main-layout .sidebar-content {
63+
.sidebar:has(.sidebar-toggle-input:not(:checked)) .sidebar-content {
6464
overflow: hidden;
6565
}
6666

dist/css/app.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/live_admin/components/layout/app.html.heex

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -48,27 +48,17 @@
4848
</header>
4949

5050
<!-- Main Layout -->
51-
<input type="checkbox" id="sidebar-toggle" class="sidebar-toggle-input" checked>
5251
<div class="main-layout">
5352
<aside class="sidebar">
5453
<div class="sidebar-content">
55-
<.live_component
56-
id="nav"
57-
module={get_in(@config, [:components, :nav])}
58-
title={Keyword.fetch!(@config, :title)}
59-
base_path={@base_path}
60-
resources={@resources}
61-
resource={assigns[:resource]}
62-
current_view={@socket.view}
63-
prefix={assigns[:prefix]}
64-
key={assigns[:key]}
65-
config={@config}
66-
session={@session}
67-
/>
68-
{live_render(@socket, LiveAdmin.Components.Nav.Jobs,
54+
{live_render(@socket, LiveAdmin.Components.Nav.Wrapper,
6955
sticky: true,
70-
id: "jobs",
71-
session: %{"session_id" => @session.id}
56+
id: "nav",
57+
session: %{
58+
"session_id" => @session.id,
59+
"base_path" => @base_path,
60+
"opts" => @config
61+
}
7262
)}
7363
</div>
7464
<label for="sidebar-toggle" class="sidebar-toggle-label">
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
defmodule LiveAdmin.Components.Nav.Wrapper do
2+
use Phoenix.LiveView
3+
4+
@impl true
5+
def mount(
6+
_params,
7+
%{"session_id" => session_id, "base_path" => base_path, "opts" => opts},
8+
socket
9+
) do
10+
if connected?(socket) do
11+
:ok = LiveAdmin.PubSub.subscribe(session_id)
12+
end
13+
14+
session = LiveAdmin.session_store().load!(session_id)
15+
resources = LiveAdmin.resources(socket.router, base_path)
16+
nav_module = get_in(opts, [:components, :nav])
17+
18+
socket =
19+
assign(socket,
20+
session: session,
21+
base_path: base_path,
22+
resources: resources,
23+
config: opts,
24+
nav_module: nav_module,
25+
title: Keyword.fetch!(opts, :title),
26+
resource: nil,
27+
current_view: nil,
28+
prefix: nil,
29+
key: nil
30+
)
31+
32+
{:ok, socket, layout: false}
33+
end
34+
35+
@impl true
36+
def handle_info({:nav, %{uri: uri}}, socket) do
37+
%URI{host: host, path: path} = URI.parse(uri)
38+
39+
route_info = Phoenix.Router.route_info(socket.router, "GET", path, host)
40+
41+
{resource, key} =
42+
case route_info do
43+
%{resource: {key, mod}} -> {mod, key}
44+
_ -> {nil, nil}
45+
end
46+
47+
{current_view, _, _, _} = route_info.phoenix_live_view
48+
49+
{:noreply, assign(socket, resource: resource, key: key, current_view: current_view)}
50+
end
51+
52+
@impl true
53+
def handle_info(_msg, socket), do: {:noreply, socket}
54+
55+
@impl true
56+
def render(assigns) do
57+
~H"""
58+
<div>
59+
<input type="checkbox" id="sidebar-toggle" class="sidebar-toggle-input" checked>
60+
<.live_component
61+
id="nav"
62+
module={@nav_module}
63+
title={@title}
64+
base_path={@base_path}
65+
resources={@resources}
66+
resource={@resource}
67+
current_view={@current_view}
68+
prefix={@prefix}
69+
key={@key}
70+
config={@config}
71+
session={@session}
72+
/>
73+
{live_render(@socket, LiveAdmin.Components.Nav.Jobs,
74+
sticky: true,
75+
id: "jobs",
76+
session: %{"session_id" => @session.id}
77+
)}
78+
</div>
79+
"""
80+
end
81+
end

lib/live_admin/router.ex

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
defmodule LiveAdmin.Router do
22
import Phoenix.Component, only: [assign: 2]
3+
import Phoenix.LiveView, only: [attach_hook: 4]
34

45
@doc """
56
Defines a group of LiveAdmin resources that share a common path prefix, and optionally, configuration.
@@ -155,6 +156,12 @@ defmodule LiveAdmin.Router do
155156
_ -> socket
156157
end
157158

159+
socket =
160+
attach_hook(socket, :nav_uri, :handle_params, fn _params, uri, socket ->
161+
LiveAdmin.PubSub.broadcast(session_id, {:nav, %{uri: uri}})
162+
{:cont, socket}
163+
end)
164+
158165
{:cont, socket}
159166
end
160167

0 commit comments

Comments
 (0)