Skip to content

Commit 805354d

Browse files
committed
feat: improve sidebar
1 parent 737378e commit 805354d

File tree

5 files changed

+169
-33
lines changed

5 files changed

+169
-33
lines changed

lib/atomic/organizations.ex

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ defmodule Atomic.Organizations do
1919
[%Organization{}, ...]
2020
2121
"""
22-
def list_organizations(params \\ %{})
22+
def list_organizations do
23+
Organization |> Repo.all()
24+
end
2325

2426
def list_organizations(opts) when is_list(opts) do
2527
Organization
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
defmodule AtomicWeb.Components.Accordion do
2+
@moduledoc """
3+
Provides accordion-related components and helper functions.
4+
"""
5+
use AtomicWeb, :component
6+
7+
alias Phoenix.LiveView.JS
8+
import AtomicWeb.Components.Icon
9+
10+
@doc """
11+
Accordion components allows users to show and hide sections of related panel on a page.
12+
13+
## Examples
14+
15+
```heex
16+
<.accordion>
17+
<:trigger>Accordion</:trigger>
18+
<:panel>Content</:panel>
19+
</.accordion>
20+
```
21+
"""
22+
23+
attr :class, :any, doc: "Extend existing component styles"
24+
attr :controlled, :boolean, default: false
25+
attr :id, :string, required: true
26+
attr :rest, :global
27+
28+
slot :trigger, validate_attrs: false
29+
slot :panel, validate_attrs: false
30+
31+
@spec accordion(Socket.assigns()) :: Rendered.t()
32+
def accordion(assigns) do
33+
~H"""
34+
<div class={["accordion", assigns[:class]]} id={@id} {@rest}>
35+
<%= for {{trigger, panel}, idx} <- @trigger |> Enum.zip(@panel) |> Enum.with_index() do %>
36+
<h3>
37+
<button
38+
aria-controls={panel_id(@id, idx)}
39+
aria-expanded={to_string(panel[:default_expanded] == true)}
40+
class={[
41+
"accordion-trigger relative w-full [&_.accordion-trigger-icon]:aria-expanded:rotate-180",
42+
trigger[:class]
43+
]}
44+
id={trigger_id(@id, idx)}
45+
phx-click={handle_click(assigns, idx)}
46+
type="button"
47+
{assigns_to_attributes(trigger, [:class, :icon_name])}
48+
>
49+
{render_slot(trigger)}
50+
<.icon class="accordion-trigger-icon absolute top-1/2 right-4 h-5 w-5 -translate-y-1/2 transition-all duration-300 ease-in-out" name={trigger[:icon_name] || "hero-chevron-down"} />
51+
</button>
52+
</h3>
53+
<div class="accordion-panel grid-rows-[0fr] grid transform transition-all duration-200 ease-in data-[expanded]:grid-rows-[1fr]" data-expanded={panel[:default_expanded]} id={panel_id(@id, idx)} role="region">
54+
<div class="overflow-hidden">
55+
<div class={["accordion-panel-content", panel[:class]]} {assigns_to_attributes(panel, [:class, :default_expanded ])}>
56+
{render_slot(panel)}
57+
</div>
58+
</div>
59+
</div>
60+
<% end %>
61+
</div>
62+
"""
63+
end
64+
65+
defp trigger_id(id, idx), do: "#{id}_trigger#{idx}"
66+
defp panel_id(id, idx), do: "#{id}_panel#{idx}"
67+
68+
defp handle_click(%{controlled: controlled, id: id}, idx) do
69+
op =
70+
{"aria-expanded", "true", "false"}
71+
|> JS.toggle_attribute(to: "##{trigger_id(id, idx)}")
72+
|> JS.toggle_attribute({"data-expanded", ""}, to: "##{panel_id(id, idx)}")
73+
74+
if controlled do
75+
op
76+
|> JS.set_attribute({"aria-expanded", "false"},
77+
to: "##{id} .accordion-trigger:not(##{trigger_id(id, idx)})"
78+
)
79+
|> JS.remove_attribute("data-expanded",
80+
to: "##{id} .accordion-panel:not(##{panel_id(id, idx)})"
81+
)
82+
else
83+
op
84+
end
85+
end
86+
end

lib/atomic_web/components/organizations.ex

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,69 @@ defmodule AtomicWeb.Components.Organizations do
22
@moduledoc false
33
use AtomicWeb, :live_component
44

5-
import AtomicWeb.Components.Avatar
5+
import AtomicWeb.Components.{Avatar, Accordion}
66

77
alias Atomic.Accounts
88
alias Atomic.Organizations
99

1010
@impl true
1111
def render(assigns) do
1212
~H"""
13-
<ul role="list" class="-mx-2 mt-2 max-h-72 max-h-72 space-y-1 overflow-y-auto">
14-
<%= for organization <- @organizations do %>
15-
<li>
16-
<div
17-
phx-target={@myself}
18-
phx-click="select-organization"
19-
phx-value-organization_id={organization.id}
20-
class={
13+
<div id={@id}>
14+
<.accordion id={"#{@id}-accordion"} class="flex-grow rounded-md border" controlled={true}>
15+
<:trigger>
16+
<%= if @current_organization do %>
17+
<div class="group flex cursor-pointer gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 text-zinc-700 hover:text-primary-500">
18+
<.avatar
19+
class={"#{if @current_organization && @current_organization.id == @current_organization.id do "border-primary-600" else "border-zinc-200" end} #{(@current_organization && @current_organization.id == @current_organization.id) && "text-primary-600"} border group-hover:border-primary-600 group-hover:text-primary-500"}
20+
src={Uploaders.Logo.url({@current_organization.logo, @current_organization}, :original)}
21+
name={@current_organization.name}
22+
size={:xs}
23+
type={:organization}
24+
color={:white}
25+
/>
26+
<span class="mt-1 truncate">{@current_organization.name}</span>
27+
</div>
28+
<% else %>
29+
<div class="group cursor-pointer gap-x-3 rounded-md p-2 text-left text-sm leading-6 text-zinc-600 hover:text-primary-500">
30+
<.icon name="hero-pencil-solid" class="size-5 shrink-0 text-zinc-400 group-hover:text-primary-500" />
31+
<span class="mt-1 truncate">{gettext("Pick an organization")}</span>
32+
</div>
33+
<% end %>
34+
</:trigger>
35+
<:panel>
36+
<ul role="list" class="mt-2 max-h-72 space-y-0.5 overflow-y-auto overscroll-contain p-1">
37+
<%= for organization <- @organizations do %>
38+
<li>
39+
<div
40+
phx-target={@myself}
41+
phx-click="select-organization"
42+
phx-value-organization_id={organization.id}
43+
class={
2144
"#{if @current_organization && organization.id == @current_organization.id do
2245
"bg-zinc-50 text-primary-500"
2346
else
2447
"text-zinc-700 hover:text-primary-500 hover:bg-zinc-50"
2548
end} group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold cursor-pointer"
2649
}
27-
type="button"
28-
>
29-
<.avatar
30-
class={"#{if @current_organization && organization.id == @current_organization.id do "border-primary-600" else "border-zinc-200" end} #{(@current_organization && organization.id == @current_organization.id) && "text-primary-600"} border group-hover:border-primary-600 group-hover:text-primary-500"}
31-
src={Uploaders.Logo.url({organization.logo, organization}, :original)}
32-
name={organization.name}
33-
size={:xs}
34-
type={:organization}
35-
color={:white}
36-
/>
37-
<span class="mt-1 truncate">{organization.name}</span>
38-
</div>
39-
</li>
40-
<% end %>
41-
</ul>
50+
type="button"
51+
>
52+
<.avatar
53+
class={"#{if @current_organization && organization.id == @current_organization.id do "border-primary-600" else "border-zinc-200" end} #{(@current_organization && organization.id == @current_organization.id) && "text-primary-600"} border group-hover:border-primary-600 group-hover:text-primary-500"}
54+
src={Uploaders.Logo.url({organization.logo, organization}, :original)}
55+
name={organization.name}
56+
size={:xs}
57+
type={:organization}
58+
color={:white}
59+
/>
60+
<span class="mt-1 truncate">{organization.name}</span>
61+
</div>
62+
</li>
63+
<% end %>
64+
</ul>
65+
</:panel>
66+
</.accordion>
67+
</div>
4268
"""
4369
end
4470

lib/atomic_web/components/sidebar.ex

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@ defmodule AtomicWeb.Components.Sidebar do
4444
<.sidebar_dropdown current_user={@current_user} orientation={:down} />
4545
</div>
4646
</div>
47-
<div id="sidebar-overlay" class="fixed inset-0 z-40 hidden cursor-pointer bg-black bg-opacity-50" phx-click={hide_mobile_sidebar()}></div>
47+
<div id="sidebar-overlay" class="fixed inset-0 z-40 hidden cursor-pointer bg-black bg-opacity-50 backdrop-blur-sm" phx-click={hide_mobile_sidebar()}></div>
4848
<!-- Sidebar Panel -->
4949
<div id="mobile-sidebar" class="fixed inset-0 z-50 hidden w-64" role="dialog" aria-modal="true">
5050
<div class="fixed inset-0 flex w-fit">
51-
<div class="relative flex w-64 max-w-xs flex-col border-r bg-white">
51+
<div class="relative flex w-72 max-w-xs flex-col rounded-r-md border-r bg-white">
5252
<div class="flex justify-between p-4">
5353
<.sidebar_header />
5454
@@ -103,14 +103,17 @@ defmodule AtomicWeb.Components.Sidebar do
103103
<ul role="list" class="-mx-2 space-y-1">
104104
<%= for page <- AtomicWeb.Config.pages(@current_user, @current_organization) do %>
105105
<li class="select-none">
106-
<.link navigate={page.url} class={"#{if @current_page == page.key do "bg-zinc-50 text-primary-500" else "text-zinc-700 hover:text-primary-500 hover:bg-zinc-50" end} group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6"}>
107-
<.icon name={page.icon} class={
106+
<.link navigate={page.url} class={"#{if @current_page == page.key do "font-extrabold text-primary-500" else "text-zinc-700 hover:text-primary-500 hover:bg-zinc-50 font-medium" end} group flex gap-x-3 rounded-md p-2 leading-6"}>
107+
<.icon
108+
name={if @current_page == page.key, do: page.icon_selected, else: page.icon}
109+
class={
108110
"#{if @current_page == page.key do
109111
"text-primary-500"
110112
else
111113
"text-zinc-400 group-hover:text-primary-500"
112-
end} size-6 shrink-0"
113-
} />
114+
end} size-7 shrink-0"
115+
}
116+
/>
114117
{page.title}
115118
</.link>
116119
</li>
@@ -173,7 +176,10 @@ defmodule AtomicWeb.Components.Sidebar do
173176
transition:
174177
{"transition ease-in-out duration-300 transform", "-translate-x-full", "translate-x-0"}
175178
)
176-
|> JS.show(to: "#sidebar-overlay")
179+
|> JS.show(
180+
to: "#sidebar-overlay",
181+
transition: {"ease-in duration-300", "opacity-0", "opacity-100"}
182+
)
177183
|> JS.dispatch("focus", to: "#mobile-sidebar")
178184
end
179185

@@ -200,5 +206,12 @@ defmodule AtomicWeb.Components.Sidebar do
200206
end
201207

202208
defp get_organizations(nil), do: []
203-
defp get_organizations(user), do: Organizations.list_user_organizations(user.id)
209+
210+
defp get_organizations(user) do
211+
if user.role == :master do
212+
Organizations.list_organizations()
213+
else
214+
Organizations.list_user_organizations(user.id)
215+
end
216+
end
204217
end

lib/atomic_web/config.ex

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ defmodule AtomicWeb.Config do
2727
key: :scanner,
2828
title: "Scanner",
2929
icon: "hero-qr-code",
30+
icon_selected: "hero-qr-code-solid",
3031
url: ~p"/scanner",
3132
tabs: []
3233
}
@@ -40,27 +41,31 @@ defmodule AtomicWeb.Config do
4041
key: :departments,
4142
title: "Departments",
4243
icon: "hero-cube",
44+
icon_selected: "hero-cube-solid",
4345
url: ~p"/organizations/#{current_organization}/departments",
4446
tabs: []
4547
},
4648
%{
4749
key: :announcements,
4850
title: "Announcements",
4951
icon: "hero-newspaper",
52+
icon_selected: "hero-newspaper-solid",
5053
url: ~p"/organizations/#{current_organization}/announcements",
5154
tabs: []
5255
},
5356
%{
5457
key: :partners,
5558
title: "Partners",
5659
icon: "hero-user-group",
60+
icon_selected: "hero-user-group-solid",
5761
url: ~p"/organizations/#{current_organization}/partners",
5862
tabs: []
5963
},
6064
%{
6165
key: :scanner,
6266
title: "Scanner",
6367
icon: "hero-qr-code",
68+
icon_selected: "hero-qr-code-solid",
6469
url: ~p"/scanner",
6570
tabs: []
6671
}
@@ -73,27 +78,31 @@ defmodule AtomicWeb.Config do
7378
key: :home,
7479
title: "Home",
7580
icon: "hero-home",
81+
icon_selected: "hero-home-solid",
7682
url: ~p"/",
7783
tabs: []
7884
},
7985
%{
8086
key: :calendar,
8187
title: "Calendar",
8288
icon: "hero-calendar",
89+
icon_selected: "hero-calendar-solid",
8390
url: ~p"/calendar",
8491
tabs: []
8592
},
8693
%{
8794
key: :activities,
8895
title: "Activities",
8996
icon: "hero-academic-cap",
97+
icon_selected: "hero-academic-cap-solid",
9098
url: ~p"/activities",
9199
tabs: []
92100
},
93101
%{
94102
key: :organizations,
95103
title: "Organizations",
96104
icon: "tabler-affiliate",
105+
icon_selected: "tabler-affiliate-filled",
97106
url: ~p"/organizations",
98107
tabs: []
99108
}

0 commit comments

Comments
 (0)