Add sidebar #260

Merged
simon merged 10 commits from sidebar into main 2026-01-12 15:17:30 +01:00
4 changed files with 117 additions and 47 deletions
Showing only changes of commit 935ef52c10 - Show all commits

View file

@ -692,10 +692,11 @@ defmodule MvWeb.CoreComponents do
"""
attr :name, :string, required: true
attr :class, :string, default: "size-4"
attr :rest, :global, include: ~w[aria-hidden]
def icon(%{name: "hero-" <> _} = assigns) do
~H"""
<span class={[@name, @class]} />
<span class={[@name, @class]} {@rest} />
"""
end

View file

@ -48,19 +48,28 @@ defmodule MvWeb.Layouts do
~H"""
<%= if @current_user do %>
<div id="app-layout" class="drawer lg:drawer-open" data-sidebar-expanded="true" phx-hook="SidebarState">
<div
id="app-layout"
class="drawer lg:drawer-open"
data-sidebar-expanded="true"
phx-hook="SidebarState"
>
<input id="mobile-drawer" type="checkbox" class="drawer-toggle" />
<div class="drawer-content flex flex-col relative z-0">
<!-- Mobile Header (only visible on mobile) -->
<header class="lg:hidden sticky top-0 z-10 navbar bg-base-100 shadow-sm">
<label for="mobile-drawer" class="btn btn-square btn-ghost" aria-label={gettext("Open navigation menu")}>
<label
for="mobile-drawer"
class="btn btn-square btn-ghost"
aria-label={gettext("Open navigation menu")}
>
<.icon name="hero-bars-3" class="size-6" aria-hidden="true" />
</label>
<span class="font-bold">{@club_name}</span>
</header>
<!-- Main Content (shared between mobile and desktop) -->
<!-- Main Content (shared between mobile and desktop) -->
<main class="px-4 py-8 sm:px-6 lg:px-8">
<div class="mx-auto space-y-4 max-full">
{render_slot(@inner_block)}

View file

@ -10,12 +10,17 @@ defmodule MvWeb.Layouts.Sidebar do
def sidebar(assigns) do
~H"""
<label for="mobile-drawer" aria-label={gettext("Close sidebar")} class="drawer-overlay lg:hidden focus:outline-none focus:ring-2 focus:ring-primary" tabindex="-1">
<label
for="mobile-drawer"
aria-label={gettext("Close sidebar")}
class="drawer-overlay lg:hidden focus:outline-none focus:ring-2 focus:ring-primary"
tabindex="-1"
>
</label>
<aside id="main-sidebar" class="sidebar" aria-label={gettext("Main navigation")}>
<%= sidebar_header(assigns) %>
{sidebar_header(assigns)}
<%= if @current_user do %>
<%= sidebar_menu(assigns) %>
{sidebar_menu(assigns)}
<% end %>
<%= if @current_user do %>
<.sidebar_footer current_user={@current_user} />
@ -30,12 +35,12 @@ defmodule MvWeb.Layouts.Sidebar do
<!-- Logo -->
<img src={~p"/images/mila.svg"} alt="Mila Logo" class="size-8 shrink-0" />
<!-- Club Name -->
<!-- Club Name -->
<span class="menu-label text-lg font-bold truncate">
{@club_name}
</span>
<!-- Toggle Button (Desktop only) -->
<!-- Toggle Button (Desktop only) -->
<%= unless @mobile do %>
<button
type="button"
@ -77,12 +82,12 @@ defmodule MvWeb.Layouts.Sidebar do
label={gettext("Users")}
/>
<.menu_item
href={~p"/custom_fields"}
href={~p"/custom_field_values"}
icon="hero-rectangle-group"
label={gettext("Custom Fields")}
/>
<!-- Nested Menu: Contributions -->
<!-- Nested Menu: Contributions -->
<.menu_group
icon="hero-currency-dollar"
label={gettext("Contributions")}
@ -129,7 +134,11 @@ defmodule MvWeb.Layouts.Sidebar do
<li role="none" class="menu-group">
<!-- Expanded Mode: Details/Summary -->
<details class="expanded-menu-group">
<summary class="flex items-center gap-3 cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2" role="menuitem" aria-haspopup="true">
<summary
class="flex items-center gap-3 cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
role="menuitem"
aria-haspopup="true"
>
<.icon name={@icon} class="size-5 shrink-0" aria-hidden="true" />
<span class="menu-label">{@label}</span>
</summary>
@ -138,7 +147,7 @@ defmodule MvWeb.Layouts.Sidebar do
</ul>
</details>
<!-- Collapsed Mode: Dropdown -->
<!-- Collapsed Mode: Dropdown -->
<div class="collapsed-menu-group dropdown dropdown-right">
<button
type="button"
@ -169,7 +178,11 @@ defmodule MvWeb.Layouts.Sidebar do
defp menu_subitem(assigns) do
~H"""
<li role="none">
<.link navigate={@href} role="menuitem" class="focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2">
<.link
navigate={@href}
role="menuitem"
class="focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
>
{@label}
</.link>
</li>
@ -195,10 +208,10 @@ defmodule MvWeb.Layouts.Sidebar do
</select>
</form>
<!-- Theme Toggle (immer sichtbar) -->
<!-- Theme Toggle (immer sichtbar) -->
<.theme_toggle />
<!-- User Menu (nur wenn current_user existiert) -->
<!-- User Menu (nur wenn current_user existiert) -->
<%= if @current_user do %>
<.user_menu current_user={@current_user} />
<% end %>
@ -208,7 +221,10 @@ defmodule MvWeb.Layouts.Sidebar do
defp theme_toggle(assigns) do
~H"""
<label class="flex items-center gap-2 cursor-pointer justify-center focus-within:outline-none focus-within:ring-2 focus-within:ring-primary focus-within:ring-offset-2" aria-label={gettext("Toggle dark mode")}>
<label
class="flex items-center gap-2 cursor-pointer justify-center focus-within:outline-none focus-within:ring-2 focus-within:ring-primary focus-within:ring-offset-2"
aria-label={gettext("Toggle dark mode")}
>
<.icon name="hero-sun" class="size-5" aria-hidden="true" />
<input
type="checkbox"
@ -227,23 +243,26 @@ defmodule MvWeb.Layouts.Sidebar do
# Defensive check: ensure current_user and email exist
# current_user might be a struct, so we need to handle both maps and structs
# email might be an Ash.CiString, so we use to_string/1 (not String.to_string/1)
email = case assigns.current_user do
nil -> ""
%{email: email_val} when not is_nil(email_val) -> to_string(email_val)
_ -> ""
end
email =
case assigns.current_user do
nil -> ""
%{email: email_val} when not is_nil(email_val) -> to_string(email_val)
_ -> ""
end
first_letter = if email != "" do
String.first(email) |> String.upcase()
else
"?"
end
first_letter =
if email != "" do
String.first(email) |> String.upcase()
else
"?"
end
user_id = case assigns.current_user do
nil -> nil
%{id: id_val} when not is_nil(id_val) -> id_val
_ -> nil
end
user_id =
case assigns.current_user do
nil -> nil
%{id: id_val} when not is_nil(id_val) -> id_val
_ -> nil
end
# Store computed values in assigns to avoid HEEx warnings
assigns = assign(assigns, :email, email)
@ -276,7 +295,11 @@ defmodule MvWeb.Layouts.Sidebar do
>
<li role="none">
<%= if @user_id do %>
<.link navigate={~p"/users/#{@user_id}"} role="menuitem" class="focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2">
<.link
navigate={~p"/users/#{@user_id}"}
role="menuitem"
class="focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
>
{gettext("Profile")}
</.link>
<% else %>
@ -284,7 +307,11 @@ defmodule MvWeb.Layouts.Sidebar do
<% end %>
</li>
<li role="none">
<.link href={~p"/sign-out"} role="menuitem" class="focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2">
<.link
href={~p"/sign-out"}
role="menuitem"
class="focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
>
{gettext("Logout")}
</.link>
</li>

View file

@ -287,7 +287,7 @@ msgstr ""
msgid "Enabled"
msgstr ""
#: lib/mv_web/components/layouts/navbar.ex
#: lib/mv_web/components/layouts/sidebar.ex
#, elixir-autogen, elixir-format
msgid "Logout"
msgstr ""
@ -304,6 +304,7 @@ msgid "Member"
msgstr ""
#: lib/mv_web/components/layouts/navbar.ex
#: lib/mv_web/components/layouts/sidebar.ex
#: lib/mv_web/live/contribution_type_live/index.ex
#: lib/mv_web/live/member_live/index.ex
#: lib/mv_web/live/member_live/index.html.heex
@ -342,11 +343,6 @@ msgstr ""
msgid "Password Authentication"
msgstr ""
#: lib/mv_web/components/layouts/navbar.ex
#, elixir-autogen, elixir-format
msgid "Profil"
msgstr ""
#: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format
msgid "Required"
@ -363,6 +359,7 @@ msgid "Select member"
msgstr ""
#: lib/mv_web/components/layouts/navbar.ex
#: lib/mv_web/components/layouts/sidebar.ex
#: lib/mv_web/live/global_settings_live.ex
#, elixir-autogen, elixir-format
msgid "Settings"
@ -530,11 +527,13 @@ msgid "Back to users list"
msgstr ""
#: lib/mv_web/components/layouts/navbar.ex
#: lib/mv_web/components/layouts/sidebar.ex
#, elixir-autogen, elixir-format
msgid "Select language"
msgstr ""
#: lib/mv_web/components/layouts/navbar.ex
#: lib/mv_web/components/layouts/sidebar.ex
#, elixir-autogen, elixir-format
msgid "Toggle dark mode"
msgstr ""
@ -546,6 +545,7 @@ msgid "Search..."
msgstr ""
#: lib/mv_web/components/layouts/navbar.ex
#: lib/mv_web/components/layouts/sidebar.ex
#, elixir-autogen, elixir-format
msgid "Users"
msgstr ""
@ -615,6 +615,7 @@ msgstr ""
msgid "Please select a custom field first"
msgstr ""
#: lib/mv_web/components/layouts/sidebar.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#: lib/mv_web/live/member_live/form.ex
#: lib/mv_web/live/member_live/show.ex
@ -908,6 +909,7 @@ msgstr ""
msgid "Contribution Start"
msgstr ""
#: lib/mv_web/components/layouts/sidebar.ex
#: lib/mv_web/live/contribution_type_live/index.ex
#, elixir-autogen, elixir-format
msgid "Contribution Types"
@ -924,6 +926,7 @@ msgid "Contribution types define different membership fee structures. Each type
msgstr ""
#: lib/mv_web/components/layouts/navbar.ex
#: lib/mv_web/components/layouts/sidebar.ex
#, elixir-autogen, elixir-format
msgid "Contributions"
msgstr ""
@ -1826,3 +1829,33 @@ msgstr ""
#, elixir-autogen, elixir-format
msgid "Not set"
msgstr ""
#: lib/mv_web/components/layouts/sidebar.ex
#, elixir-autogen, elixir-format
msgid "Close sidebar"
msgstr ""
#: lib/mv_web/components/layouts/sidebar.ex
#, elixir-autogen, elixir-format
msgid "Main navigation"
msgstr ""
#: lib/mv_web/components/layouts.ex
#, elixir-autogen, elixir-format
msgid "Open navigation menu"
msgstr ""
#: lib/mv_web/components/layouts/sidebar.ex
#, elixir-autogen, elixir-format
msgid "Profile"
msgstr ""
#: lib/mv_web/components/layouts/sidebar.ex
#, elixir-autogen, elixir-format
msgid "Toggle sidebar"
msgstr ""
#: lib/mv_web/components/layouts/sidebar.ex
#, elixir-autogen, elixir-format
msgid "User menu"
msgstr ""