feat: restructure bottom-nav — search as primary slot, sidebar tooltips for collapsed mode
This commit is contained in:
+41
-4
@@ -131,7 +131,7 @@ let _pendingLoginRedirect = false;
|
|||||||
const ROUTE_ORDER = ['/', '/tasks', '/calendar', '/birthdays', '/meals', '/recipes', '/shopping',
|
const ROUTE_ORDER = ['/', '/tasks', '/calendar', '/birthdays', '/meals', '/recipes', '/shopping',
|
||||||
'/notes', '/contacts', '/budget', '/documents', '/settings'];
|
'/notes', '/contacts', '/budget', '/documents', '/settings'];
|
||||||
|
|
||||||
const PRIMARY_NAV = 4;
|
const PRIMARY_NAV = 3;
|
||||||
|
|
||||||
const DEFAULT_APP_NAME = 'Oikos';
|
const DEFAULT_APP_NAME = 'Oikos';
|
||||||
const APP_NAME_STORAGE_KEY = 'oikos-app-name';
|
const APP_NAME_STORAGE_KEY = 'oikos-app-name';
|
||||||
@@ -478,6 +478,20 @@ function renderAppShell(container) {
|
|||||||
const bottomItems = document.createElement('div');
|
const bottomItems = document.createElement('div');
|
||||||
bottomItems.className = 'nav-bottom__items';
|
bottomItems.className = 'nav-bottom__items';
|
||||||
navItems().slice(0, PRIMARY_NAV).forEach((item) => bottomItems.appendChild(navItemEl(item)));
|
navItems().slice(0, PRIMARY_NAV).forEach((item) => bottomItems.appendChild(navItemEl(item)));
|
||||||
|
const searchNavBtn = document.createElement('button');
|
||||||
|
searchNavBtn.className = 'nav-item nav-item--search';
|
||||||
|
searchNavBtn.id = 'search-nav-btn';
|
||||||
|
searchNavBtn.setAttribute('aria-label', t('search.title'));
|
||||||
|
const searchNavIcon = document.createElement('i');
|
||||||
|
searchNavIcon.dataset.lucide = 'search';
|
||||||
|
searchNavIcon.className = 'nav-item__icon';
|
||||||
|
searchNavIcon.setAttribute('aria-hidden', 'true');
|
||||||
|
const searchNavLabel = document.createElement('span');
|
||||||
|
searchNavLabel.className = 'nav-item__label';
|
||||||
|
searchNavLabel.textContent = t('search.title');
|
||||||
|
searchNavBtn.appendChild(searchNavIcon);
|
||||||
|
searchNavBtn.appendChild(searchNavLabel);
|
||||||
|
bottomItems.appendChild(searchNavBtn);
|
||||||
const moreBtn = document.createElement('button');
|
const moreBtn = document.createElement('button');
|
||||||
moreBtn.className = 'nav-item nav-item--more';
|
moreBtn.className = 'nav-item nav-item--more';
|
||||||
moreBtn.id = 'more-btn';
|
moreBtn.id = 'more-btn';
|
||||||
@@ -750,6 +764,7 @@ function initMoreSheet(container) {
|
|||||||
*/
|
*/
|
||||||
function initSearch(container) {
|
function initSearch(container) {
|
||||||
const searchBtn = container.querySelector('#search-btn');
|
const searchBtn = container.querySelector('#search-btn');
|
||||||
|
const searchNavBtn = container.querySelector('#search-nav-btn');
|
||||||
const searchClose = container.querySelector('#search-close');
|
const searchClose = container.querySelector('#search-close');
|
||||||
const overlay = container.querySelector('#search-overlay');
|
const overlay = container.querySelector('#search-overlay');
|
||||||
const input = container.querySelector('#search-input');
|
const input = container.querySelector('#search-input');
|
||||||
@@ -762,6 +777,7 @@ function initSearch(container) {
|
|||||||
let _searchTrapHandler = null;
|
let _searchTrapHandler = null;
|
||||||
|
|
||||||
function openSearch() {
|
function openSearch() {
|
||||||
|
window._openSearch = openSearch;
|
||||||
if (window._closeMoreSheet) window._closeMoreSheet();
|
if (window._closeMoreSheet) window._closeMoreSheet();
|
||||||
overlay.setAttribute('aria-hidden', 'false');
|
overlay.setAttribute('aria-hidden', 'false');
|
||||||
overlay.classList.add('search-overlay--visible');
|
overlay.classList.add('search-overlay--visible');
|
||||||
@@ -796,6 +812,7 @@ function initSearch(container) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
searchBtn.addEventListener('click', openSearch);
|
searchBtn.addEventListener('click', openSearch);
|
||||||
|
if (searchNavBtn) searchNavBtn.addEventListener('click', openSearch);
|
||||||
searchClose.addEventListener('click', closeSearch);
|
searchClose.addEventListener('click', closeSearch);
|
||||||
|
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener('keydown', (e) => {
|
||||||
@@ -872,11 +889,11 @@ function navItems() {
|
|||||||
return [
|
return [
|
||||||
{ path: '/', label: t('nav.dashboard'), icon: 'layout-dashboard' },
|
{ path: '/', label: t('nav.dashboard'), icon: 'layout-dashboard' },
|
||||||
{ path: '/tasks', label: t('nav.tasks'), icon: 'check-square' },
|
{ path: '/tasks', label: t('nav.tasks'), icon: 'check-square' },
|
||||||
{ path: '/birthdays', label: t('nav.birthdays'), icon: 'cake' },
|
|
||||||
{ path: '/calendar', label: t('nav.calendar'), icon: 'calendar' },
|
{ path: '/calendar', label: t('nav.calendar'), icon: 'calendar' },
|
||||||
|
{ path: '/shopping', label: t('nav.shopping'), icon: 'shopping-cart' },
|
||||||
{ path: '/meals', label: t('nav.meals'), icon: 'utensils' },
|
{ path: '/meals', label: t('nav.meals'), icon: 'utensils' },
|
||||||
{ path: '/recipes', label: t('nav.recipes'), icon: 'book-text' },
|
{ path: '/recipes', label: t('nav.recipes'), icon: 'book-text' },
|
||||||
{ path: '/shopping', label: t('nav.shopping'), icon: 'shopping-cart' },
|
{ path: '/birthdays', label: t('nav.birthdays'), icon: 'cake' },
|
||||||
{ path: '/notes', label: t('nav.notes'), icon: 'sticky-note' },
|
{ path: '/notes', label: t('nav.notes'), icon: 'sticky-note' },
|
||||||
{ path: '/contacts', label: t('nav.contacts'), icon: 'book-user' },
|
{ path: '/contacts', label: t('nav.contacts'), icon: 'book-user' },
|
||||||
{ path: '/budget', label: t('nav.budget'), icon: 'wallet' },
|
{ path: '/budget', label: t('nav.budget'), icon: 'wallet' },
|
||||||
@@ -1120,7 +1137,27 @@ window.addEventListener('locale-changed', () => {
|
|||||||
if (bottomItems) {
|
if (bottomItems) {
|
||||||
const moreBtn = bottomItems.querySelector('#more-btn');
|
const moreBtn = bottomItems.querySelector('#more-btn');
|
||||||
const newItems = navItems().slice(0, PRIMARY_NAV).map(navItemEl);
|
const newItems = navItems().slice(0, PRIMARY_NAV).map(navItemEl);
|
||||||
bottomItems.replaceChildren(...newItems, moreBtn);
|
// Such-Button neu erstellen (wird durch replaceChildren entfernt)
|
||||||
|
const newSearchBtn = document.createElement('button');
|
||||||
|
newSearchBtn.className = 'nav-item nav-item--search';
|
||||||
|
newSearchBtn.id = 'search-nav-btn';
|
||||||
|
newSearchBtn.setAttribute('aria-label', t('search.title'));
|
||||||
|
const newSearchIcon = document.createElement('i');
|
||||||
|
newSearchIcon.dataset.lucide = 'search';
|
||||||
|
newSearchIcon.className = 'nav-item__icon';
|
||||||
|
newSearchIcon.setAttribute('aria-hidden', 'true');
|
||||||
|
const newSearchLbl = document.createElement('span');
|
||||||
|
newSearchLbl.className = 'nav-item__label';
|
||||||
|
newSearchLbl.textContent = t('search.title');
|
||||||
|
newSearchBtn.appendChild(newSearchIcon);
|
||||||
|
newSearchBtn.appendChild(newSearchLbl);
|
||||||
|
bottomItems.replaceChildren(...newItems, newSearchBtn, moreBtn);
|
||||||
|
// Event-Listener auf neuen Such-Button
|
||||||
|
if (newSearchBtn) {
|
||||||
|
newSearchBtn.addEventListener('click', () => {
|
||||||
|
if (window._openSearch) window._openSearch();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (moreSheet) {
|
if (moreSheet) {
|
||||||
const searchBtn = moreSheet.querySelector('#search-btn');
|
const searchBtn = moreSheet.querySelector('#search-btn');
|
||||||
|
|||||||
@@ -663,6 +663,38 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Tooltip für collapsed Sidebar (nur Icons sichtbar bei 1024–1439px) */
|
||||||
|
@media (min-width: 1024px) and (max-width: 1439px) {
|
||||||
|
.nav-sidebar .nav-item {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-sidebar .nav-item::after {
|
||||||
|
content: attr(title);
|
||||||
|
position: absolute;
|
||||||
|
left: calc(var(--sidebar-width) + var(--space-2));
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background: var(--color-surface);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
padding: var(--space-1) var(--space-3);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
border: 1px solid var(--color-border-subtle);
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.15s ease;
|
||||||
|
z-index: calc(var(--z-nav) + 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-sidebar .nav-item:hover::after {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ================================================================
|
/* ================================================================
|
||||||
* Sidebar Expanded (≥ 1280px) - Labels sichtbar
|
* Sidebar Expanded (≥ 1280px) - Labels sichtbar
|
||||||
* ================================================================ */
|
* ================================================================ */
|
||||||
|
|||||||
Reference in New Issue
Block a user