adminx-core 0.3.0

Framework-neutral core for the adminx admin-panel framework: the Resource trait, storage abstraction, registry, and neutral request/response types shared by every web-framework and database adapter.
Documentation
<header class="sticky top-0 z-50 w-full">
  <div class="absolute inset-0 h-16 bg-gradient-to-r from-indigo-600 via-violet-600 to-fuchsia-600 dark:from-indigo-700 dark:via-violet-700 dark:to-fuchsia-700"></div>

  <div class="relative">
    <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
      <div class="h-16 flex items-center rounded-b-2xl border border-white/10 bg-white/70 dark:bg-slate-900/60 backdrop-blur-xl shadow-lg">
        <div class="flex items-center gap-6 w-full">
          <!-- Brand + mobile toggle -->
          <div class="flex items-center gap-2 pl-3">
            {% if is_authenticated %}
            <button class="md:hidden inline-flex items-center justify-center rounded-lg p-2 hover:bg-black/5 dark:hover:bg-white/10 transition" aria-label="Menu" data-mobile-toggle>
              <svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/></svg>
            </button>
            {% endif %}
            <a href="{{ mount | safe }}" class="flex items-center gap-2">
              <span class="text-lg font-bold tracking-tight bg-gradient-to-r from-indigo-600 to-fuchsia-600 bg-clip-text text-transparent">adminx</span>
            </a>
          </div>

          <!-- Desktop nav -->
          {% if is_authenticated %}
          <nav id="desktop-nav" class="hidden md:flex items-center gap-1">
            {% for menu in menus %}
              {% set id = loop.index0 %}
              {% if menu.children and menu.children | length > 0 %}
                <div class="relative">
                  <button type="button" class="inline-flex items-center gap-1 rounded-lg px-3 py-2 text-sm font-medium text-slate-800 dark:text-slate-100 hover:bg-black/5 dark:hover:bg-white/10 transition" aria-haspopup="menu" aria-expanded="false" data-menu-trigger data-id="{{ id }}">
                    <span>{{ menu.title }}</span>
                    <svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
                  </button>
                  <div class="pointer-events-none absolute left-0 mt-2 w-64 origin-top-left rounded-xl border border-slate-200/60 dark:border-slate-700/60 bg-white/95 dark:bg-slate-900/95 backdrop-blur-xl shadow-xl opacity-0 translate-y-1 scale-95 transition-all duration-200" role="menu" data-menu-panel data-id="{{ id }}">
                    <ul class="py-2">
                      {% for child in menu.children %}
                        <li>
                          <a href="{{ mount | safe }}/{{ child.path }}/list" class="flex items-center justify-between px-3 py-2 text-sm text-slate-800 dark:text-slate-200 hover:bg-slate-50 dark:hover:bg-slate-800/60 rounded-lg mx-2 transition" data-menu-item>
                            <span>{{ child.title }}</span>
                            <svg class="h-3.5 w-3.5 opacity-60" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
                          </a>
                        </li>
                      {% endfor %}
                    </ul>
                  </div>
                </div>
              {% else %}
                <a href="{{ mount | safe }}/{{ menu.path }}/list" class="inline-flex items-center rounded-lg px-3 py-2 text-sm font-medium text-slate-800 dark:text-slate-100 hover:bg-black/5 dark:hover:bg-white/10 transition">{{ menu.title }}</a>
              {% endif %}
            {% endfor %}
          </nav>
          {% endif %}

          <!-- Right actions -->
          <div class="ml-auto flex items-center gap-2 pr-3">
            <button type="button" aria-label="Toggle theme" class="inline-flex items-center gap-2 rounded-lg border border-slate-200/60 dark:border-slate-700/60 px-3 py-2 text-sm hover:bg-black/5 dark:hover:bg-white/10 transition" data-theme-toggle>
              <svg data-icon-sun class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="4" stroke-width="2"></circle><path stroke-linecap="round" stroke-width="2" d="M12 2v2m0 16v2M20 12h2M2 12H4M17.657 6.343l1.414 1.414M4.929 19.071l1.414-1.414M6.343 6.343 4.93 7.757m14.142 10.607-1.414-1.414"/></svg>
              <svg data-icon-moon class="hidden h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12.79A9 9 0 1111.21 3a7 7 0 109.79 9.79z"/></svg>
            </button>
            {% if is_authenticated %}
              <a href="{{ mount | safe }}/logout" class="inline-flex items-center rounded-lg bg-gradient-to-r from-indigo-600 to-fuchsia-600 px-3 py-2 text-sm font-semibold text-white shadow hover:opacity-95 transition">Logout</a>
            {% else %}
              <a href="{{ mount | safe }}/login" class="inline-flex items-center rounded-lg bg-gradient-to-r from-indigo-600 to-fuchsia-600 px-3 py-2 text-sm font-semibold text-white shadow hover:opacity-95 transition">Login</a>
            {% endif %}
          </div>
        </div>
      </div>
    </div>
  </div>

  <!-- Mobile drawer -->
  {% if is_authenticated %}
  <div id="mobile-menu" class="fixed inset-x-0 top-16 mx-2 rounded-2xl border border-slate-200/60 dark:border-slate-700/60 bg-white/95 dark:bg-slate-900/95 backdrop-blur-xl shadow-2xl opacity-0 pointer-events-none transition-all duration-300 z-50">
    <nav class="p-4 text-slate-900 dark:text-slate-100">
      {% for menu in menus %}
        {% if menu.children and menu.children | length > 0 %}
          <button class="flex w-full items-center justify-between rounded-lg px-3 py-2 text-left text-sm hover:bg-slate-50 dark:hover:bg-slate-800/60 transition" data-mobile-accordion>
            {{ menu.title }}
            <svg class="h-4 w-4 transition-transform duration-200" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
          </button>
          <div class="max-h-0 overflow-hidden opacity-0 transition-all duration-300">
            {% for child in menu.children %}
              <a href="{{ mount | safe }}/{{ child.path }}/list" class="block rounded-lg px-6 py-2 text-sm hover:bg-slate-50 dark:hover:bg-slate-800/60 transition" data-mobile-link>{{ child.title }}</a>
            {% endfor %}
          </div>
        {% else %}
          <a href="{{ mount | safe }}/{{ menu.path }}/list" class="block rounded-lg px-3 py-2 text-sm hover:bg-slate-50 dark:hover:bg-slate-800/60 transition" data-mobile-link>{{ menu.title }}</a>
        {% endif %}
      {% endfor %}
    </nav>
  </div>
  {% endif %}
</header>