mirror of
https://github.com/ipnet-mesh/meshcore-hub.git
synced 2026-08-07 09:23:07 +02:00
feat: restructure mobile navbar — move hamburger to right, increase dropdown sizes
Move mobile hamburger menu from navbar-start to navbar-end so it stays right-aligned regardless of logo width. Mobile nav items are now rendered via JS (renderMobileNav) using icon functions instead of duplicated inline SVGs. Increase dropdown widths (w-52→w-56), icon sizes (h-4→h-5), and add larger mobile tap targets via CSS media query.
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
# UI Feedback - Mobile Navbar Restructure & Size Fixes
|
||||
|
||||
Date: 2026-05-05
|
||||
|
||||
## Problem
|
||||
|
||||
1. **Mobile navbar layout** — The hamburger menu currently sits in `navbar-start` (left side), leaving the mobile layout as: `[hamburger + logo/name] ... [spinner | theme | user]`. The desired order is: `[logo/name] ... [spinner | theme | user | hamburger]`.
|
||||
|
||||
2. **Dropdown menu sizes too small** — After the Tailwind v4 / DaisyUI v5 upgrade, the mobile navigation dropdown and user profile dropdown render with noticeably smaller text and icons, making tap targets difficult to hit.
|
||||
|
||||
3. **Duplicated inline SVGs** — The mobile nav dropdown in `spa.html` contains inline SVGs duplicated from the `icons.js` icon functions. These should use the existing JS icon functions to eliminate duplication.
|
||||
|
||||
## Files to Change
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `src/meshcore_hub/web/templates/spa.html` | Restructure navbar: move hamburger to `navbar-end`, remove inline SVGs from mobile dropdown, render mobile nav via JS instead |
|
||||
| `src/meshcore_hub/web/static/js/spa/app.js` | Add mobile nav rendering (inject icon + link items into the mobile dropdown via JS) |
|
||||
| `src/meshcore_hub/web/static/js/spa/components.js` | Increase user profile dropdown sizes (`menu-sm` → normal or custom, icon `h-4 w-4` → `h-5 w-5`) |
|
||||
| `src/meshcore_hub/web/static/css/app.css` | Add CSS rules for increased mobile dropdown font size, icon size, and tap target padding |
|
||||
|
||||
## Step-by-step Plan
|
||||
|
||||
### Step 1: Restructure `spa.html` navbar layout
|
||||
|
||||
**Current structure:**
|
||||
```
|
||||
navbar-start: [hamburger dropdown] [logo/name link]
|
||||
navbar-center: [desktop nav menu] (hidden on mobile)
|
||||
navbar-end: [spinner] [theme toggle] [auth section]
|
||||
```
|
||||
|
||||
**Target structure:**
|
||||
```
|
||||
navbar-start: [logo/name link]
|
||||
navbar-center: [desktop nav menu] (hidden on mobile)
|
||||
navbar-end: [spinner] [theme toggle] [auth section] [hamburger dropdown]
|
||||
```
|
||||
|
||||
Changes in `spa.html`:
|
||||
- **Remove** the entire `<div class="dropdown">` block (hamburger + mobile nav `<ul>`) from `navbar-start`.
|
||||
- **Keep** only the logo/name `<a>` link in `navbar-start`.
|
||||
- **Add** a new mobile dropdown in `navbar-end`, **after** the `{% endif %}` that closes the OIDC conditional. The hamburger must be outside the conditional so it always renders (OIDC on or off). Use `dropdown dropdown-end` so the menu aligns to the right edge.
|
||||
- The new mobile dropdown container will be an **empty `<ul>`** populated by JS (see Step 2), with the hamburger trigger button as a `<div tabindex="0" role="button">`.
|
||||
- Keep `lg:hidden` on the hamburger trigger so it only appears on mobile.
|
||||
|
||||
### Step 2: Render mobile nav items via JS (eliminate inline SVGs)
|
||||
|
||||
All the icon functions already exist in `icons.js`:
|
||||
- `iconHome()`, `iconDashboard()`, `iconNodes()`, `iconAdvertisements()`, `iconMessages()`, `iconMap()`, `iconMembers()`, `iconPage()`
|
||||
|
||||
Create a new `renderMobileNav()` function (in `app.js` or `components.js`) that:
|
||||
1. Reads feature flags (`config.features`) and custom pages (`config.custom_pages`) from `window.__APP_CONFIG__`.
|
||||
2. Builds the nav `<li>` items using lit-html, using the JS icon functions with a CSS class for the nav-icon color.
|
||||
3. Renders into the mobile nav `<ul>` container element.
|
||||
4. Sets `data-nav-link` attributes on `<a>` elements for active-state highlighting. SPA click navigation is handled automatically by the Router's document-level click listener.
|
||||
|
||||
This eliminates ~20 duplicated inline SVGs from the Jinja2 template.
|
||||
|
||||
### Step 3: Increase mobile nav dropdown sizes
|
||||
|
||||
The mobile nav dropdown currently uses `menu menu-sm` with `h-4 w-4` icons and `w-52` width.
|
||||
|
||||
Changes:
|
||||
- Remove `menu-sm` from the mobile nav dropdown `<ul>` (use default `menu` for standard sizing).
|
||||
- Increase dropdown width from `w-52` to `w-56`.
|
||||
- Nav icons in mobile dropdown: render at `h-5 w-5` (up from `h-4 w-4`) via the JS icon function class parameter.
|
||||
- Add CSS rule in `app.css` for slightly larger tap targets:
|
||||
|
||||
```css
|
||||
@media (max-width: 1023px) {
|
||||
.navbar .dropdown .menu li > a {
|
||||
padding-top: 0.625rem;
|
||||
padding-bottom: 0.625rem;
|
||||
font-size: 0.9375rem; /* 15px, up from ~13px with menu-sm */
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: Increase user profile dropdown sizes
|
||||
|
||||
The user profile dropdown in `components.js` (`renderAuthSection`) uses `menu menu-sm` with `h-4 w-4` icons.
|
||||
|
||||
Changes in `renderAuthSection()`:
|
||||
- Change dropdown `<ul>` from `menu menu-sm` to `menu` (or keep `menu-sm` with CSS override).
|
||||
- Increase dropdown width from `w-52` to `w-56`.
|
||||
- Change icon class in profile/logout items from `h-4 w-4` to `h-5 w-5`.
|
||||
- Optionally increase the avatar button and initials text slightly.
|
||||
|
||||
Add CSS for the user profile dropdown tap targets (same rule as Step 3, or a targeted one):
|
||||
|
||||
```css
|
||||
@media (max-width: 1023px) {
|
||||
#auth-section .dropdown .menu li > a {
|
||||
padding-top: 0.625rem;
|
||||
padding-bottom: 0.625rem;
|
||||
font-size: 0.9375rem;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 5: Verify and test
|
||||
|
||||
- Rebuild frontend assets: `npm run build`
|
||||
- Run targeted tests: `pytest tests/test_web/`
|
||||
- Run quality checks: `pre-commit run --all-files`
|
||||
- Visual verification on mobile viewport (browser DevTools) for:
|
||||
- Navbar order: logo left, controls + hamburger right
|
||||
- Mobile nav dropdown: larger text/icons/tap targets
|
||||
- User profile dropdown: larger text/icons/tap targets
|
||||
- Desktop nav: unchanged behavior
|
||||
|
||||
## Risk / Considerations
|
||||
|
||||
- **Jinja2 → JS nav rendering**: Feature flags, custom pages, and i18n translations are all confirmed available in `window.__APP_CONFIG__` and the `t()` translation function. The JS renderer will produce identical nav items to the Jinja2 template.
|
||||
- **Desktop nav unchanged**: The `navbar-center` desktop horizontal menu stays as-is with inline SVGs in the Jinja2 template (it works fine, is not affected by the Tailwind size issue, and is not mobile).
|
||||
|
||||
## Summary of Size Changes
|
||||
|
||||
| Element | Before | After |
|
||||
|---------|--------|-------|
|
||||
| Mobile nav menu class | `menu menu-sm` | `menu` (default) |
|
||||
| Mobile nav icon size | `h-4 w-4` (16px) | `h-5 w-5` (20px) |
|
||||
| Mobile nav dropdown width | `w-52` (13rem) | `w-56` (14rem) |
|
||||
| Mobile nav item font size | ~13px (menu-sm) | ~15px (menu default) |
|
||||
| User dropdown menu class | `menu menu-sm` | `menu` (default) |
|
||||
| User dropdown icon size | `h-4 w-4` (16px) | `h-5 w-5` (20px) |
|
||||
| User dropdown width | `w-52` (13rem) | `w-56` (14rem) |
|
||||
@@ -0,0 +1,51 @@
|
||||
# Tasks - Mobile Navbar Restructure & Size Fixes
|
||||
|
||||
## Implementation Tasks
|
||||
|
||||
- [ ] **T1. Restructure `spa.html` navbar layout**
|
||||
- Remove the entire `<div class="dropdown">` block (lines 53-83) from `navbar-start`
|
||||
- Keep only the logo/name `<a>` link in `navbar-start`
|
||||
- Add new hamburger dropdown in `navbar-end`, after the OIDC `{% endif %}` (outside the conditional)
|
||||
- New hamburger shell: `<div class="dropdown dropdown-end lg:hidden">` with `<div tabindex="0" role="button" class="btn btn-ghost">` trigger (inline hamburger SVG or use `iconMenu` from icons.js via a data attribute)
|
||||
- Empty `<ul id="mobile-nav">` with classes `dropdown-content menu z-[1] p-2 shadow bg-base-100 rounded-box w-56 mt-3` (no `menu-sm`, width `w-56`)
|
||||
- Reference: `src/meshcore_hub/web/templates/spa.html` lines 52-129
|
||||
|
||||
- [ ] **T2. Create `renderMobileNav()` function in `app.js`**
|
||||
- Import icon functions from `icons.js`: `iconHome`, `iconDashboard`, `iconNodes`, `iconAdvertisements`, `iconMessages`, `iconMap`, `iconMembers`, `iconPage`
|
||||
- Read `config.features` and `config.custom_pages` from `window.__APP_CONFIG__`
|
||||
- Build `<li>` items using lit-html, matching the same feature-flag logic as the Jinja2 template
|
||||
- Use icon functions with `'h-5 w-5'` size class plus nav-icon color class (e.g., `nav-icon-dashboard`)
|
||||
- Set `data-nav-link` attributes on `<a>` elements for active-state highlighting
|
||||
- Render into the `#mobile-nav` container element
|
||||
- Include custom pages loop: iterate `config.custom_pages` (each has `slug`, `title`, `url`, `menu_order`), use `iconPage` for icon
|
||||
- Reference: `src/meshcore_hub/web/static/js/spa/app.js`, `src/meshcore_hub/web/static/js/spa/icons.js`
|
||||
|
||||
- [ ] **T3. Wire up `renderMobileNav()` in `app.js` initialization**
|
||||
- Call `renderMobileNav()` after `loadLocale()` and alongside `renderAuthSection()` (around line 177-181)
|
||||
- Pass config to the function
|
||||
- Reference: `src/meshcore_hub/web/static/js/spa/app.js` lines 176-183
|
||||
|
||||
- [ ] **T4. Update `renderAuthSection()` in `components.js`**
|
||||
- Change `<ul>` classes from `menu menu-sm` to `menu` (line 707)
|
||||
- Change dropdown width from `w-52` to `w-56` (line 707)
|
||||
- Change icon sizes from `h-4 w-4` to `h-5 w-5` in profile item (line 696) and logout item (line 717)
|
||||
- Reference: `src/meshcore_hub/web/static/js/spa/components.js` lines 696-720
|
||||
|
||||
- [ ] **T5. Add mobile dropdown CSS rules in `app.css`**
|
||||
- Add tap target sizing for mobile nav dropdown:
|
||||
```css
|
||||
@media (max-width: 1023px) {
|
||||
.navbar .dropdown .menu li > a {
|
||||
padding-top: 0.625rem;
|
||||
padding-bottom: 0.625rem;
|
||||
font-size: 0.9375rem;
|
||||
}
|
||||
}
|
||||
```
|
||||
- Reference: `src/meshcore_hub/web/static/css/app.css`
|
||||
|
||||
- [ ] **T6. Build and test**
|
||||
- `npm run build` — rebuild Tailwind + frontend assets
|
||||
- `pytest tests/test_web/` — targeted web tests
|
||||
- `pre-commit run --all-files` — quality checks
|
||||
- Visual verification: mobile viewport navbar order, dropdown sizes, desktop nav unchanged
|
||||
@@ -385,3 +385,16 @@
|
||||
margin: 0;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Mobile Dropdown Tap Targets
|
||||
Larger touch targets and text for mobile dropdown menus.
|
||||
========================================================================== */
|
||||
|
||||
@media (max-width: 1023px) {
|
||||
.navbar .dropdown .menu li > a {
|
||||
padding-top: 0.625rem;
|
||||
padding-bottom: 0.625rem;
|
||||
font-size: 0.9375rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
*/
|
||||
|
||||
import { Router } from './router.js';
|
||||
import { getConfig, hasRole, renderAuthSection } from './components.js';
|
||||
import { html, litRender, getConfig, hasRole, renderAuthSection } from './components.js';
|
||||
import { loadLocale, t } from './i18n.js';
|
||||
import { iconHome, iconDashboard, iconNodes, iconAdvertisements, iconMessages, iconMap, iconMembers, iconPage } from './icons.js';
|
||||
|
||||
// Page modules (lazy-loaded)
|
||||
const pages = {
|
||||
@@ -172,6 +173,50 @@ router.onNavigate((pathname) => {
|
||||
updatePageTitle(pathname);
|
||||
});
|
||||
|
||||
/**
|
||||
* Render the mobile navigation dropdown.
|
||||
* Populates the #mobile-nav container with nav items based on config features.
|
||||
* @param {Object} config - App configuration object
|
||||
*/
|
||||
function renderMobileNav(config) {
|
||||
const container = document.getElementById('mobile-nav');
|
||||
if (!container) return;
|
||||
|
||||
const features = config.features || {};
|
||||
const customPages = config.custom_pages || [];
|
||||
|
||||
const items = [];
|
||||
|
||||
items.push(html`<li><a href="/" data-nav-link>${iconHome('h-5 w-5')} ${t('entities.home')}</a></li>`);
|
||||
|
||||
if (features.dashboard !== false) {
|
||||
items.push(html`<li><a href="/dashboard" data-nav-link>${iconDashboard('h-5 w-5 nav-icon-dashboard')} ${t('entities.dashboard')}</a></li>`);
|
||||
}
|
||||
if (features.nodes !== false) {
|
||||
items.push(html`<li><a href="/nodes" data-nav-link>${iconNodes('h-5 w-5 nav-icon-nodes')} ${t('entities.nodes')}</a></li>`);
|
||||
}
|
||||
if (features.advertisements !== false) {
|
||||
items.push(html`<li><a href="/advertisements" data-nav-link>${iconAdvertisements('h-5 w-5 nav-icon-adverts')} ${t('entities.advertisements')}</a></li>`);
|
||||
}
|
||||
if (features.messages !== false) {
|
||||
items.push(html`<li><a href="/messages" data-nav-link>${iconMessages('h-5 w-5 nav-icon-messages')} ${t('entities.messages')}</a></li>`);
|
||||
}
|
||||
if (features.map !== false) {
|
||||
items.push(html`<li><a href="/map" data-nav-link>${iconMap('h-5 w-5 nav-icon-map')} ${t('entities.map')}</a></li>`);
|
||||
}
|
||||
if (features.members !== false) {
|
||||
items.push(html`<li><a href="/members" data-nav-link>${iconMembers('h-5 w-5 nav-icon-members')} ${t('entities.members')}</a></li>`);
|
||||
}
|
||||
|
||||
if (features.pages !== false && customPages.length > 0) {
|
||||
for (const page of customPages) {
|
||||
items.push(html`<li><a href=${page.url} data-nav-link>${iconPage('h-5 w-5')} ${page.title}</a></li>`);
|
||||
}
|
||||
}
|
||||
|
||||
litRender(html`${items}`, container);
|
||||
}
|
||||
|
||||
// Load locale then start the router
|
||||
const locale = localStorage.getItem('meshcore-locale') || config.locale || 'en';
|
||||
await loadLocale(locale);
|
||||
@@ -180,4 +225,7 @@ await loadLocale(locale);
|
||||
const authSection = document.getElementById('auth-section');
|
||||
renderAuthSection(authSection, config);
|
||||
|
||||
// Render mobile nav (after translations are loaded)
|
||||
renderMobileNav(config);
|
||||
|
||||
router.start();
|
||||
|
||||
@@ -693,7 +693,7 @@ export function renderAuthSection(container, config) {
|
||||
return html`<span class="badge badge-primary badge-xs">${name}</span>`;
|
||||
});
|
||||
|
||||
const profileItem = html`<li><a href="/profile">${iconUser('h-4 w-4')} ${t('links.profile')}</a></li>`;
|
||||
const profileItem = html`<li><a href="/profile">${iconUser('h-5 w-5')} ${t('links.profile')}</a></li>`;
|
||||
|
||||
const debugId = config.debug && user.sub
|
||||
? html`<span class="text-xs opacity-40 font-mono">${user.sub}</span>`
|
||||
@@ -704,7 +704,7 @@ export function renderAuthSection(container, config) {
|
||||
<div tabindex="0" role="button" class="btn btn-ghost btn-circle btn-sm avatar">
|
||||
${pictureHtml}
|
||||
</div>
|
||||
<ul tabindex="0" class="dropdown-content menu menu-sm z-[1] p-2 shadow bg-base-100 rounded-box w-52 mt-3">
|
||||
<ul tabindex="0" class="dropdown-content menu z-[1] p-2 shadow bg-base-100 rounded-box w-56 mt-3">
|
||||
<li class="menu-title">
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="font-medium">${displayName}</span>
|
||||
@@ -714,7 +714,7 @@ export function renderAuthSection(container, config) {
|
||||
</li>
|
||||
<hr class="my-1 opacity-20">
|
||||
${profileItem}
|
||||
<li><a href="/auth/logout">${iconLogout('h-4 w-4')} ${t('auth.logout')}</a></li>
|
||||
<li><a href="/auth/logout">${iconLogout('h-5 w-5')} ${t('auth.logout')}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
`, container);
|
||||
|
||||
@@ -55,7 +55,7 @@ export function iconRefresh(cls = 'h-5 w-5') {
|
||||
}
|
||||
|
||||
export function iconMenu(cls = 'h-5 w-5') {
|
||||
return html`<svg xmlns="http://www.w3.org/2000/svg" class=${cls} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h8m-8 6h16" /></svg>`;
|
||||
return html`<svg xmlns="http://www.w3.org/2000/svg" class=${cls} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" /></svg>`;
|
||||
}
|
||||
|
||||
export function iconGithub(cls = 'h-5 w-5') {
|
||||
|
||||
@@ -50,37 +50,6 @@
|
||||
<!-- Navbar -->
|
||||
<div class="navbar bg-base-100 shadow-lg">
|
||||
<div class="navbar-start">
|
||||
<div class="dropdown">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost lg:hidden">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h8m-8 6h16" /></svg>
|
||||
</div>
|
||||
<ul tabindex="0" class="menu menu-sm dropdown-content mt-3 z-50 p-2 shadow bg-base-100 rounded-box w-52">
|
||||
<li><a href="/" data-nav-link><svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" /></svg> {{ t('entities.home') }}</a></li>
|
||||
{% if features.dashboard %}
|
||||
<li><a href="/dashboard" data-nav-link><svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 nav-icon-dashboard" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" /></svg> {{ t('entities.dashboard') }}</a></li>
|
||||
{% endif %}
|
||||
{% if features.nodes %}
|
||||
<li><a href="/nodes" data-nav-link><svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 nav-icon-nodes" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" /></svg> {{ t('entities.nodes') }}</a></li>
|
||||
{% endif %}
|
||||
{% if features.advertisements %}
|
||||
<li><a href="/advertisements" data-nav-link><svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 nav-icon-adverts" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5.882V19.24a1.76 1.76 0 01-3.417.592l-2.147-6.15M18 13a3 3 0 100-6M5.436 13.683A4.001 4.001 0 017 6h1.832c4.1 0 7.625-1.234 9.168-3v14c-1.543-1.766-5.067-3-9.168-3H7a3.988 3.988 0 01-1.564-.317z" /></svg> {{ t('entities.advertisements') }}</a></li>
|
||||
{% endif %}
|
||||
{% if features.messages %}
|
||||
<li><a href="/messages" data-nav-link><svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 nav-icon-messages" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z" /></svg> {{ t('entities.messages') }}</a></li>
|
||||
{% endif %}
|
||||
{% if features.map %}
|
||||
<li><a href="/map" data-nav-link><svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 nav-icon-map" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7" /></svg> {{ t('entities.map') }}</a></li>
|
||||
{% endif %}
|
||||
{% if features.members %}
|
||||
<li><a href="/members" data-nav-link><svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 nav-icon-members" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" /></svg> {{ t('entities.members') }}</a></li>
|
||||
{% endif %}
|
||||
{% if features.pages %}
|
||||
{% for page in custom_pages %}
|
||||
<li><a href="{{ page.url }}" data-nav-link><svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /></svg> {{ page.title }}</a></li>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
<a href="/" class="btn btn-ghost text-xl">
|
||||
<img src="{{ logo_url }}" alt="{{ network_name }}" class="theme-logo{% if logo_invert_light %} theme-logo--invert-light{% endif %} h-6 w-6 mr-2" />
|
||||
{{ network_name }}
|
||||
@@ -126,6 +95,13 @@
|
||||
{% if oidc_enabled %}
|
||||
<div id="auth-section"></div>
|
||||
{% endif %}
|
||||
<div class="dropdown dropdown-end lg:hidden">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" /></svg>
|
||||
</div>
|
||||
<ul id="mobile-nav" tabindex="0" class="dropdown-content menu z-[1] p-2 shadow bg-base-100 rounded-box w-56 mt-3">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user