feat: adds sidebar as overlay and moves navbar content there
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
parent
bb6ea0085b
commit
b0097ab99d
4 changed files with 316 additions and 100 deletions
167
assets/js/app.js
167
assets/js/app.js
|
|
@ -102,3 +102,170 @@ liveSocket.connect()
|
||||||
// >> liveSocket.disableLatencySim()
|
// >> liveSocket.disableLatencySim()
|
||||||
window.liveSocket = liveSocket
|
window.liveSocket = liveSocket
|
||||||
|
|
||||||
|
// Sidebar accessibility improvements
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const drawerToggle = document.getElementById("main-drawer")
|
||||||
|
const sidebarToggle = document.getElementById("sidebar-toggle")
|
||||||
|
const sidebar = document.getElementById("main-sidebar")
|
||||||
|
|
||||||
|
if (!drawerToggle || !sidebarToggle || !sidebar) return
|
||||||
|
|
||||||
|
// Manage tabindex for sidebar elements based on open/closed state
|
||||||
|
const updateSidebarTabIndex = (isOpen) => {
|
||||||
|
// Find all potentially focusable elements (including those with tabindex="-1")
|
||||||
|
const allFocusableElements = sidebar.querySelectorAll(
|
||||||
|
'a[href], button, select, input:not([type="hidden"]), [tabindex]'
|
||||||
|
)
|
||||||
|
|
||||||
|
allFocusableElements.forEach(el => {
|
||||||
|
// Skip the overlay button
|
||||||
|
if (el.closest('.drawer-overlay')) return
|
||||||
|
|
||||||
|
if (isOpen) {
|
||||||
|
// Remove tabindex="-1" to make focusable when open
|
||||||
|
if (el.hasAttribute('tabindex') && el.getAttribute('tabindex') === '-1') {
|
||||||
|
el.removeAttribute('tabindex')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Set tabindex="-1" to remove from tab order when closed
|
||||||
|
if (!el.hasAttribute('tabindex')) {
|
||||||
|
el.setAttribute('tabindex', '-1')
|
||||||
|
} else if (el.getAttribute('tabindex') !== '-1') {
|
||||||
|
// Store original tabindex in data attribute before setting to -1
|
||||||
|
if (!el.hasAttribute('data-original-tabindex')) {
|
||||||
|
el.setAttribute('data-original-tabindex', el.getAttribute('tabindex'))
|
||||||
|
}
|
||||||
|
el.setAttribute('tabindex', '-1')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find first focusable element in sidebar
|
||||||
|
// Priority: first navigation link (menuitem) > other links > other focusable elements
|
||||||
|
const getFirstFocusableElement = () => {
|
||||||
|
// First, try to find the first navigation link (menuitem)
|
||||||
|
const firstNavLink = sidebar.querySelector('a[href][role="menuitem"]:not([tabindex="-1"])')
|
||||||
|
if (firstNavLink && !firstNavLink.closest('.drawer-overlay')) {
|
||||||
|
return firstNavLink
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: any navigation link
|
||||||
|
const firstLink = sidebar.querySelector('a[href]:not([tabindex="-1"])')
|
||||||
|
if (firstLink && !firstLink.closest('.drawer-overlay')) {
|
||||||
|
return firstLink
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last resort: any other focusable element
|
||||||
|
const focusableSelectors = [
|
||||||
|
'button:not([tabindex="-1"]):not([disabled])',
|
||||||
|
'select:not([tabindex="-1"]):not([disabled])',
|
||||||
|
'input:not([tabindex="-1"]):not([disabled]):not([type="hidden"])',
|
||||||
|
'[tabindex]:not([tabindex="-1"])'
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const selector of focusableSelectors) {
|
||||||
|
const element = sidebar.querySelector(selector)
|
||||||
|
if (element && !element.closest('.drawer-overlay')) {
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update aria-expanded when drawer state changes
|
||||||
|
const updateAriaExpanded = () => {
|
||||||
|
const isOpen = drawerToggle.checked
|
||||||
|
sidebarToggle.setAttribute("aria-expanded", isOpen.toString())
|
||||||
|
|
||||||
|
// Update dropdown aria-expanded if present
|
||||||
|
const userMenuButton = sidebar.querySelector('button[aria-haspopup="true"]')
|
||||||
|
if (userMenuButton) {
|
||||||
|
const dropdown = userMenuButton.closest('.dropdown')
|
||||||
|
const isDropdownOpen = dropdown?.classList.contains('dropdown-open')
|
||||||
|
if (userMenuButton) {
|
||||||
|
userMenuButton.setAttribute("aria-expanded", (isDropdownOpen || false).toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for changes to the drawer checkbox
|
||||||
|
drawerToggle.addEventListener("change", () => {
|
||||||
|
const isOpen = drawerToggle.checked
|
||||||
|
updateAriaExpanded()
|
||||||
|
updateSidebarTabIndex(isOpen)
|
||||||
|
if (!isOpen) {
|
||||||
|
// When closing, return focus to toggle button
|
||||||
|
sidebarToggle.focus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Update on initial load
|
||||||
|
updateAriaExpanded()
|
||||||
|
updateSidebarTabIndex(drawerToggle.checked)
|
||||||
|
|
||||||
|
// Close sidebar with ESC key
|
||||||
|
document.addEventListener("keydown", (e) => {
|
||||||
|
if (e.key === "Escape" && drawerToggle.checked) {
|
||||||
|
drawerToggle.checked = false
|
||||||
|
updateAriaExpanded()
|
||||||
|
updateSidebarTabIndex(false)
|
||||||
|
// Return focus to toggle button
|
||||||
|
sidebarToggle.focus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Improve keyboard navigation for sidebar toggle
|
||||||
|
sidebarToggle.addEventListener("keydown", (e) => {
|
||||||
|
if (e.key === "Enter" || e.key === " ") {
|
||||||
|
e.preventDefault()
|
||||||
|
const wasOpen = drawerToggle.checked
|
||||||
|
drawerToggle.checked = !drawerToggle.checked
|
||||||
|
updateAriaExpanded()
|
||||||
|
|
||||||
|
// If opening, move focus to first element in sidebar
|
||||||
|
if (!wasOpen && drawerToggle.checked) {
|
||||||
|
updateSidebarTabIndex(true)
|
||||||
|
// Use setTimeout to ensure DOM is updated
|
||||||
|
setTimeout(() => {
|
||||||
|
const firstElement = getFirstFocusableElement()
|
||||||
|
if (firstElement) {
|
||||||
|
firstElement.focus()
|
||||||
|
}
|
||||||
|
}, 50)
|
||||||
|
} else if (wasOpen && !drawerToggle.checked) {
|
||||||
|
updateSidebarTabIndex(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Also handle click events to update tabindex and focus
|
||||||
|
sidebarToggle.addEventListener("click", () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
const isOpen = drawerToggle.checked
|
||||||
|
updateSidebarTabIndex(isOpen)
|
||||||
|
if (isOpen) {
|
||||||
|
const firstElement = getFirstFocusableElement()
|
||||||
|
if (firstElement) {
|
||||||
|
firstElement.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 50)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Handle dropdown keyboard navigation
|
||||||
|
const userMenuButton = sidebar?.querySelector('button[aria-haspopup="true"]')
|
||||||
|
if (userMenuButton) {
|
||||||
|
userMenuButton.addEventListener("click", () => {
|
||||||
|
setTimeout(updateAriaExpanded, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
userMenuButton.addEventListener("keydown", (e) => {
|
||||||
|
if (e.key === "Enter" || e.key === " ") {
|
||||||
|
e.preventDefault()
|
||||||
|
userMenuButton.click()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ defmodule MvWeb.Layouts do
|
||||||
assigns = assign(assigns, :club_name, club_name)
|
assigns = assign(assigns, :club_name, club_name)
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<div class="drawer lg:drawer-open">
|
<div class="drawer">
|
||||||
<input id="main-drawer" type="checkbox" class="drawer-toggle" />
|
<input id="main-drawer" type="checkbox" class="drawer-toggle" />
|
||||||
<div class="drawer-content">
|
<div class="drawer-content">
|
||||||
<%= if @current_user do %>
|
<%= if @current_user do %>
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,14 @@ defmodule MvWeb.Layouts.Navbar do
|
||||||
~H"""
|
~H"""
|
||||||
<header class="shadow-sm navbar bg-base-100">
|
<header class="shadow-sm navbar bg-base-100">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<label
|
<button
|
||||||
for="main-drawer"
|
type="button"
|
||||||
aria-label="open sidebar"
|
onclick="document.getElementById('main-drawer').checked = !document.getElementById('main-drawer').checked"
|
||||||
class="mr-2 btn btn-square btn-ghost lg:hidden"
|
aria-label={gettext("Toggle navigation menu")}
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-controls="main-sidebar"
|
||||||
|
id="sidebar-toggle"
|
||||||
|
class="mr-2 btn btn-square btn-ghost focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|
@ -28,87 +32,15 @@ defmodule MvWeb.Layouts.Navbar do
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
class="my-1.5 inline-block size-4"
|
class="my-1.5 inline-block size-4"
|
||||||
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z">
|
<path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z">
|
||||||
</path>
|
</path>
|
||||||
<path d="M9 4v16"></path>
|
<path d="M9 4v16"></path>
|
||||||
<path d="M14 10l2 2l-2 2"></path>
|
<path d="M14 10l2 2l-2 2"></path>
|
||||||
</svg>
|
</svg>
|
||||||
</label>
|
<span class="sr-only">{gettext("Toggle navigation menu")}</span>
|
||||||
</div>
|
</button>
|
||||||
<div class="flex gap-2">
|
|
||||||
<form method="post" action="/set_locale" class="mr-4">
|
|
||||||
<input type="hidden" name="_csrf_token" value={Plug.CSRFProtection.get_csrf_token()} />
|
|
||||||
<label class="sr-only" for="locale-select">{gettext("Select language")}</label>
|
|
||||||
<select
|
|
||||||
id="locale-select"
|
|
||||||
name="locale"
|
|
||||||
onchange="this.form.submit()"
|
|
||||||
class="select select-sm"
|
|
||||||
aria-label={gettext("Select language")}
|
|
||||||
>
|
|
||||||
<option value="de" selected={Gettext.get_locale() == "de"}>Deutsch</option>
|
|
||||||
<option value="en" selected={Gettext.get_locale() == "en"}>English</option>
|
|
||||||
</select>
|
|
||||||
</form>
|
|
||||||
<!-- Daisy UI Theme Toggle for dark and light mode-->
|
|
||||||
<label class="flex gap-2 cursor-pointer" aria-label={gettext("Toggle dark mode")}>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="20"
|
|
||||||
height="20"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<circle cx="12" cy="12" r="5" />
|
|
||||||
<path d="M12 1v2M12 21v2M4.2 4.2l1.4 1.4M18.4 18.4l1.4 1.4M1 12h2M21 12h2M4.2 19.8l1.4-1.4M18.4 5.6l1.4-1.4" />
|
|
||||||
</svg>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
value="dark"
|
|
||||||
class="toggle theme-controller"
|
|
||||||
aria-label={gettext("Toggle dark mode")}
|
|
||||||
/>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="20"
|
|
||||||
height="20"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
|
|
||||||
</svg>
|
|
||||||
</label>
|
|
||||||
<div class="dropdown dropdown-end">
|
|
||||||
<div tabindex="0" role="button" class="btn btn-ghost btn-circle avatar avatar-placeholder">
|
|
||||||
<div class="w-12 rounded-full bg-neutral text-neutral-content">
|
|
||||||
<span>AA</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ul
|
|
||||||
tabindex="0"
|
|
||||||
class="p-2 mt-3 shadow menu menu-sm dropdown-content bg-base-100 rounded-box z-1 w-52"
|
|
||||||
>
|
|
||||||
<li>
|
|
||||||
<.link navigate={~p"/users/#{@current_user.id}"}>
|
|
||||||
{gettext("Profil")}
|
|
||||||
</.link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<.link href={~p"/sign-out"}>{gettext("Logout")}</.link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -10,70 +10,187 @@ defmodule MvWeb.Layouts.Sidebar do
|
||||||
def sidebar(assigns) do
|
def sidebar(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="drawer-side is-drawer-close:overflow-visible">
|
<div class="drawer-side is-drawer-close:overflow-visible">
|
||||||
<label for="main-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
|
<button
|
||||||
<div class="flex flex-col items-start min-h-full bg-base-200 is-drawer-close:w-14 is-drawer-open:w-64">
|
type="button"
|
||||||
<ul class="w-64 menu">
|
onclick="document.getElementById('main-drawer').checked = false"
|
||||||
|
aria-label={gettext("Close sidebar")}
|
||||||
|
class="drawer-overlay focus:outline-none focus:ring-2 focus:ring-primary"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
<nav
|
||||||
|
id="main-sidebar"
|
||||||
|
aria-label={gettext("Main navigation")}
|
||||||
|
class="flex flex-col items-start min-h-full bg-base-200 is-drawer-close:w-14 is-drawer-open:w-64"
|
||||||
|
>
|
||||||
|
<ul class="w-64 menu" role="menubar">
|
||||||
<li>
|
<li>
|
||||||
<h1 class="mb-2 text-lg font-bold menu-title is-drawer-close:hidden">{@club_name}</h1>
|
<h1 class="mb-2 text-lg font-bold menu-title is-drawer-close:hidden">{@club_name}</h1>
|
||||||
</li>
|
</li>
|
||||||
<%= if @current_user do %>
|
<%= if @current_user do %>
|
||||||
<li>
|
<li role="none">
|
||||||
<.link
|
<.link
|
||||||
navigate="/members"
|
navigate="/members"
|
||||||
class={[
|
class={[
|
||||||
"is-drawer-close:tooltip is-drawer-close:tooltip-right"
|
"is-drawer-close:tooltip is-drawer-close:tooltip-right focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
|
||||||
]}
|
]}
|
||||||
data-tip={gettext("Members")}
|
data-tip={gettext("Members")}
|
||||||
|
role="menuitem"
|
||||||
>
|
>
|
||||||
<.icon name="hero-users" class="size-5" />
|
<.icon name="hero-users" class="size-5" aria-hidden="true" />
|
||||||
<span class="is-drawer-close:hidden">{gettext("Members")}</span>
|
<span class="is-drawer-close:hidden">{gettext("Members")}</span>
|
||||||
</.link>
|
</.link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li role="none">
|
||||||
<.link
|
<.link
|
||||||
navigate="/users"
|
navigate="/users"
|
||||||
class={[
|
class={[
|
||||||
"is-drawer-close:tooltip is-drawer-close:tooltip-right"
|
"is-drawer-close:tooltip is-drawer-close:tooltip-right focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
|
||||||
]}
|
]}
|
||||||
data-tip={gettext("Users")}
|
data-tip={gettext("Users")}
|
||||||
|
role="menuitem"
|
||||||
>
|
>
|
||||||
<.icon name="hero-user-circle" class="size-5" />
|
<.icon name="hero-user-circle" class="size-5" aria-hidden="true" />
|
||||||
<span class="is-drawer-close:hidden">{gettext("Users")}</span>
|
<span class="is-drawer-close:hidden">{gettext("Users")}</span>
|
||||||
</.link>
|
</.link>
|
||||||
</li>
|
</li>
|
||||||
<li class="is-drawer-close:hidden">
|
<li class="is-drawer-close:hidden" role="none">
|
||||||
<h2 class="flex items-center gap-2 menu-title">
|
<h2 class="flex items-center gap-2 menu-title">
|
||||||
<.icon name="hero-currency-dollar" class="size-5" />
|
<.icon name="hero-currency-dollar" class="size-5" aria-hidden="true" />
|
||||||
{gettext("Contributions")}
|
{gettext("Contributions")}
|
||||||
</h2>
|
</h2>
|
||||||
<ul>
|
<ul role="menu">
|
||||||
<li class="is-drawer-close:hidden">
|
<li class="is-drawer-close:hidden" role="none">
|
||||||
<.link navigate="/contribution_types">
|
<.link
|
||||||
|
navigate="/contribution_types"
|
||||||
|
role="menuitem"
|
||||||
|
class="focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
|
||||||
|
>
|
||||||
{gettext("Plans")}
|
{gettext("Plans")}
|
||||||
</.link>
|
</.link>
|
||||||
</li>
|
</li>
|
||||||
<li class="is-drawer-close:hidden">
|
<li class="is-drawer-close:hidden" role="none">
|
||||||
<.link navigate="/contribution_settings">
|
<.link
|
||||||
|
navigate="/contribution_settings"
|
||||||
|
role="menuitem"
|
||||||
|
class="focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
|
||||||
|
>
|
||||||
{gettext("Settings")}
|
{gettext("Settings")}
|
||||||
</.link>
|
</.link>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li role="none">
|
||||||
<.link
|
<.link
|
||||||
navigate="/settings"
|
navigate="/settings"
|
||||||
class={[
|
class={[
|
||||||
"is-drawer-close:tooltip is-drawer-close:tooltip-right"
|
"is-drawer-close:tooltip is-drawer-close:tooltip-right focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
|
||||||
]}
|
]}
|
||||||
data-tip={gettext("Settings")}
|
data-tip={gettext("Settings")}
|
||||||
|
role="menuitem"
|
||||||
>
|
>
|
||||||
<.icon name="hero-cog-6-tooth" class="size-5" />
|
<.icon name="hero-cog-6-tooth" class="size-5" aria-hidden="true" />
|
||||||
<span class="is-drawer-close:hidden">{gettext("Settings")}</span>
|
<span class="is-drawer-close:hidden">{gettext("Settings")}</span>
|
||||||
</.link>
|
</.link>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
<%= if @current_user do %>
|
||||||
|
<div class="flex flex-col gap-4 p-4 mt-auto w-full is-drawer-close:items-center">
|
||||||
|
<form method="post" action="/set_locale" class="w-full">
|
||||||
|
<input type="hidden" name="_csrf_token" value={get_csrf_token()} />
|
||||||
|
<label class="sr-only" for="locale-select-sidebar">{gettext("Select language")}</label>
|
||||||
|
<select
|
||||||
|
id="locale-select-sidebar"
|
||||||
|
name="locale"
|
||||||
|
onchange="this.form.submit()"
|
||||||
|
class="select select-sm w-full is-drawer-close:w-auto focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
|
||||||
|
aria-label={gettext("Select language")}
|
||||||
|
>
|
||||||
|
<option value="de" selected={Gettext.get_locale() == "de"}>Deutsch</option>
|
||||||
|
<option value="en" selected={Gettext.get_locale() == "en"}>English</option>
|
||||||
|
</select>
|
||||||
|
</form>
|
||||||
|
<!-- Daisy UI Theme Toggle for dark and light mode-->
|
||||||
|
<label class="flex gap-2 cursor-pointer is-drawer-close: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")}>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<circle cx="12" cy="12" r="5" />
|
||||||
|
<path d="M12 1v2M12 21v2M4.2 4.2l1.4 1.4M18.4 18.4l1.4 1.4M1 12h2M21 12h2M4.2 19.8l1.4-1.4M18.4 5.6l1.4-1.4" />
|
||||||
|
</svg>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
value="dark"
|
||||||
|
class="toggle theme-controller focus:outline-none"
|
||||||
|
aria-label={gettext("Toggle dark mode")}
|
||||||
|
/>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
|
||||||
|
</svg>
|
||||||
|
</label>
|
||||||
|
<div class="dropdown dropdown-top is-drawer-close:dropdown-end">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
tabindex="0"
|
||||||
|
role="button"
|
||||||
|
aria-label={gettext("User menu")}
|
||||||
|
aria-haspopup="true"
|
||||||
|
aria-expanded="false"
|
||||||
|
class="btn btn-ghost btn-circle avatar avatar-placeholder focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
|
||||||
|
>
|
||||||
|
<div class="w-12 rounded-full bg-neutral text-neutral-content">
|
||||||
|
<span aria-hidden="true">AA</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<ul
|
||||||
|
role="menu"
|
||||||
|
tabindex="0"
|
||||||
|
class="p-2 mt-3 shadow menu menu-sm dropdown-content bg-base-100 rounded-box z-1 w-52 focus:outline-none"
|
||||||
|
>
|
||||||
|
<li role="none">
|
||||||
|
<.link
|
||||||
|
navigate={~p"/users/#{@current_user.id}"}
|
||||||
|
role="menuitem"
|
||||||
|
class="focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
|
||||||
|
>
|
||||||
|
{gettext("Profil")}
|
||||||
|
</.link>
|
||||||
|
</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"
|
||||||
|
>
|
||||||
|
{gettext("Logout")}
|
||||||
|
</.link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue