Files
komp_ac/web/templates/ui/navbar.html
2026-08-24 09:35:19 +02:00

67 lines
9.5 KiB
HTML

{#
Shared navbar, rendered on every page by base.html. Reads the `nav` field of
the surrounding page struct (crate::ui::Nav).
Penguin UI's default navbar, from
penguinui-components/navbar/default-navbar.html: the library's markup with
this app's brand and links in place of the demo's, and the same link list
repeated in the mobile menu exactly as the library repeats it. The active
link is the library's own "current page" styling (`font-bold text-primary`
plus aria-current) rather than the underline the old hand-written `.topbar`
drew, and logging out is a form posting to /logout, styled as a link so it
sits in the list with the others.
This replaced the hand-written dark `.topbar` bar, so the site chrome is now
the library's: white, bordered underneath, with a hamburger menu below `md`.
#}
<nav x-data="{ mobileMenuIsOpen: false }" x-on:click.away="mobileMenuIsOpen = false" class="flex items-center justify-between border-b border-outline px-6 py-4 dark:border-outline-dark" aria-label="{{ nav.tr("nav-main-menu") }}">
<!-- Brand -->
<a href="/" class="text-2xl font-bold text-on-surface-strong dark:text-on-surface-dark-strong">
<span>Komp <span class="text-primary dark:text-primary-dark">Accounting</span></span>
{% if !nav.role.is_empty() %}<span class="ml-2 align-middle text-xs font-medium text-on-surface dark:text-on-surface-dark">{{ nav.role }}</span>{% endif %}
</a>
<!-- Desktop Menu -->
<ul class="hidden items-center gap-4 md:flex">
{% if nav.can_admin %}<li><a href="/admin" class="{% if nav.active == "admin" %}font-bold text-primary dark:text-primary-dark{% else %}font-medium text-on-surface dark:text-on-surface-dark dark:hover:text-primary-dark{% endif %} underline-offset-2 hover:text-primary focus:outline-hidden focus:underline" {% if nav.active == "admin" %}aria-current="page"{% endif %}>{{ nav.tr("nav-admin") }}</a></li>{% endif %}
{% if nav.can_permissions %}<li><a href="/permissions" class="{% if nav.active == "permissions" %}font-bold text-primary dark:text-primary-dark{% else %}font-medium text-on-surface dark:text-on-surface-dark dark:hover:text-primary-dark{% endif %} underline-offset-2 hover:text-primary focus:outline-hidden focus:underline" {% if nav.active == "permissions" %}aria-current="page"{% endif %}>{{ nav.tr("nav-permissions") }}</a></li>{% endif %}
<li><a href="/" class="{% if nav.active == "analytics" %}font-bold text-primary dark:text-primary-dark{% else %}font-medium text-on-surface dark:text-on-surface-dark dark:hover:text-primary-dark{% endif %} underline-offset-2 hover:text-primary focus:outline-hidden focus:underline" {% if nav.active == "analytics" %}aria-current="page"{% endif %}>{{ nav.tr("nav-analytics") }}</a></li>
{% if nav.can_exchange_rates %}<li><a href="/admin/exchange-rates" class="{% if nav.active == "ecb" %}font-bold text-primary dark:text-primary-dark{% else %}font-medium text-on-surface dark:text-on-surface-dark dark:hover:text-primary-dark{% endif %} underline-offset-2 hover:text-primary focus:outline-hidden focus:underline" {% if nav.active == "ecb" %}aria-current="page"{% endif %}>{{ nav.tr("nav-rates") }}</a></li>{% endif %}
{% if nav.can_import %}<li><a href="/admin/import" class="font-medium text-on-surface underline-offset-2 hover:text-primary focus:outline-hidden focus:underline dark:text-on-surface-dark dark:hover:text-primary-dark">{{ nav.tr("nav-import") }}</a></li>{% endif %}
{% if nav.can_export %}<li><a href="/admin/export" class="font-medium text-on-surface underline-offset-2 hover:text-primary focus:outline-hidden focus:underline dark:text-on-surface-dark dark:hover:text-primary-dark">{{ nav.tr("nav-export") }}</a></li>{% endif %}
{% if nav.authenticated %}
<li><a href="/password" class="font-medium text-on-surface underline-offset-2 hover:text-primary focus:outline-hidden focus:underline dark:text-on-surface-dark dark:hover:text-primary-dark">{{ nav.tr("nav-password") }}</a></li>
<li><form hx-post="/logout" hx-swap="none"><button type="submit" class="font-medium text-on-surface underline-offset-2 hover:text-primary focus:outline-hidden focus:underline dark:text-on-surface-dark dark:hover:text-primary-dark">{{ nav.tr("nav-log-out") }}</button></form></li>
{% else %}
<li><a href="/login" class="{% if nav.active == "login" %}font-bold text-primary dark:text-primary-dark{% else %}font-medium text-on-surface dark:text-on-surface-dark dark:hover:text-primary-dark{% endif %} underline-offset-2 hover:text-primary focus:outline-hidden focus:underline" {% if nav.active == "login" %}aria-current="page"{% endif %}>{{ nav.tr("nav-login") }}</a></li>
<li><a href="/register" class="{% if nav.active == "register" %}font-bold text-primary dark:text-primary-dark{% else %}font-medium text-on-surface dark:text-on-surface-dark dark:hover:text-primary-dark{% endif %} underline-offset-2 hover:text-primary focus:outline-hidden focus:underline" {% if nav.active == "register" %}aria-current="page"{% endif %}>{{ nav.tr("nav-register") }}</a></li>
{% endif %}
<li>{% include "ui/theme_toggle.html" %}</li>
</ul>
<!-- Mobile Menu Button -->
<button x-on:click="mobileMenuIsOpen = !mobileMenuIsOpen" x-bind:aria-expanded="mobileMenuIsOpen" x-bind:class="mobileMenuIsOpen ? 'fixed top-6 right-6 z-20' : null" type="button" class="flex text-on-surface dark:text-on-surface-dark md:hidden" aria-label="{{ nav.tr("nav-mobile-menu") }}" aria-controls="mobileMenu">
<svg x-cloak x-show="!mobileMenuIsOpen" xmlns="http://www.w3.org/2000/svg" fill="none" aria-hidden="true" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
</svg>
<svg x-cloak x-show="mobileMenuIsOpen" xmlns="http://www.w3.org/2000/svg" fill="none" aria-hidden="true" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
</svg>
</button>
<!-- Mobile Menu -->
<ul x-cloak x-show="mobileMenuIsOpen" x-transition:enter="transition motion-reduce:transition-none ease-out duration-300" x-transition:enter-start="-translate-y-full" x-transition:enter-end="translate-y-0" x-transition:leave="transition motion-reduce:transition-none ease-out duration-300" x-transition:leave-start="translate-y-0" x-transition:leave-end="-translate-y-full" id="mobileMenu" class="fixed max-h-svh overflow-y-auto inset-x-0 top-0 z-10 flex flex-col divide-y divide-outline rounded-b-radius border-b border-outline bg-surface-alt px-6 pb-6 pt-20 dark:divide-outline-dark dark:border-outline-dark dark:bg-surface-dark-alt md:hidden">
{% if nav.can_admin %}<li class="py-4"><a href="/admin" class="w-full text-lg {% if nav.active == "admin" %}font-bold text-primary dark:text-primary-dark{% else %}font-medium text-on-surface dark:text-on-surface-dark{% endif %} focus:underline" {% if nav.active == "admin" %}aria-current="page"{% endif %}>{{ nav.tr("nav-admin") }}</a></li>{% endif %}
{% if nav.can_permissions %}<li class="py-4"><a href="/permissions" class="w-full text-lg {% if nav.active == "permissions" %}font-bold text-primary dark:text-primary-dark{% else %}font-medium text-on-surface dark:text-on-surface-dark{% endif %} focus:underline" {% if nav.active == "permissions" %}aria-current="page"{% endif %}>{{ nav.tr("nav-permissions") }}</a></li>{% endif %}
<li class="py-4"><a href="/" class="w-full text-lg {% if nav.active == "analytics" %}font-bold text-primary dark:text-primary-dark{% else %}font-medium text-on-surface dark:text-on-surface-dark{% endif %} focus:underline" {% if nav.active == "analytics" %}aria-current="page"{% endif %}>{{ nav.tr("nav-analytics") }}</a></li>
{% if nav.can_exchange_rates %}<li class="py-4"><a href="/admin/exchange-rates" class="w-full text-lg {% if nav.active == "ecb" %}font-bold text-primary dark:text-primary-dark{% else %}font-medium text-on-surface dark:text-on-surface-dark{% endif %} focus:underline" {% if nav.active == "ecb" %}aria-current="page"{% endif %}>{{ nav.tr("nav-rates") }}</a></li>{% endif %}
{% if nav.can_import %}<li class="py-4"><a href="/admin/import" class="w-full text-lg font-medium text-on-surface focus:underline dark:text-on-surface-dark">{{ nav.tr("nav-import") }}</a></li>{% endif %}
{% if nav.can_export %}<li class="py-4"><a href="/admin/export" class="w-full text-lg font-medium text-on-surface focus:underline dark:text-on-surface-dark">{{ nav.tr("nav-export") }}</a></li>{% endif %}
{% if nav.authenticated %}
<li class="py-4"><a href="/password" class="w-full text-lg font-medium text-on-surface focus:underline dark:text-on-surface-dark">{{ nav.tr("nav-password") }}</a></li>
<li class="py-4"><form hx-post="/logout" hx-swap="none"><button type="submit" class="w-full text-left text-lg font-medium text-on-surface focus:underline dark:text-on-surface-dark">{{ nav.tr("nav-log-out") }}</button></form></li>
{% else %}
<li class="py-4"><a href="/login" class="w-full text-lg {% if nav.active == "login" %}font-bold text-primary dark:text-primary-dark{% else %}font-medium text-on-surface dark:text-on-surface-dark{% endif %} focus:underline" {% if nav.active == "login" %}aria-current="page"{% endif %}>{{ nav.tr("nav-login") }}</a></li>
<li class="py-4"><a href="/register" class="w-full text-lg {% if nav.active == "register" %}font-bold text-primary dark:text-primary-dark{% else %}font-medium text-on-surface dark:text-on-surface-dark{% endif %} focus:underline" {% if nav.active == "register" %}aria-current="page"{% endif %}>{{ nav.tr("nav-register") }}</a></li>
{% endif %}
<li class="py-4">{% include "ui/theme_toggle.html" %}</li>
</ul>
</nav>