diff --git a/CHANGELOG.md b/CHANGELOG.md index bee3aa1..1c9ce42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.37.1] - 2026-04-30 + +### Changed +- Bottom navigation: Tasks replaces Search as a primary tab bar item +- More menu: layout changed from two columns to a three-column grid (two rows of three) +- Search: embedded as a narrow bar at the top of the More sheet instead of a standalone bottom-nav button + ## [0.37.0] - 2026-04-30 ### Added diff --git a/package-lock.json b/package-lock.json index e22e06c..6d398f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "oikos", - "version": "0.36.1", + "version": "0.37.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "oikos", - "version": "0.36.1", + "version": "0.37.1", "license": "MIT", "dependencies": { "bcrypt": "^6.0.0", diff --git a/package.json b/package.json index 142e330..d23a3aa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oikos", - "version": "0.37.0", + "version": "0.37.1", "description": "Self-hosted family planner - calendar, tasks, shopping, meal planning, budget and more. Private, open-source, no subscription.", "main": "server/index.js", "type": "module", diff --git a/public/router.js b/public/router.js index abd3748..b484903 100644 --- a/public/router.js +++ b/public/router.js @@ -132,7 +132,7 @@ let _pendingLoginRedirect = false; const ROUTE_ORDER = ['/', '/calendar', '/tasks', '/meals', '/recipes', '/shopping', '/birthdays', '/notes', '/contacts', '/budget', '/documents', '/settings']; -const PRIMARY_NAV = 2; +const PRIMARY_NAV = 3; const DEFAULT_APP_NAME = 'Oikos'; const APP_NAME_STORAGE_KEY = 'oikos-app-name'; @@ -504,23 +504,6 @@ function renderAppShell(container) { kitchenBtn.addEventListener('click', () => navigate(getLastKitchenRoute())); bottomItems.appendChild(kitchenBtn); - const searchNavBtn = document.createElement('button'); - searchNavBtn.className = 'nav-item nav-item--search'; - searchNavBtn.id = 'search-btn'; - searchNavBtn.type = 'button'; - searchNavBtn.setAttribute('aria-label', t('nav.search')); - searchNavBtn.setAttribute('title', t('nav.search')); - 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('nav.search'); - searchNavBtn.appendChild(searchNavIcon); - searchNavBtn.appendChild(searchNavLabel); - bottomItems.appendChild(searchNavBtn); - const moreBtn = document.createElement('button'); moreBtn.className = 'nav-item nav-item--more'; moreBtn.id = 'more-btn'; @@ -553,6 +536,24 @@ function renderAppShell(container) { dragHandle.className = 'more-sheet__handle'; dragHandle.setAttribute('aria-hidden', 'true'); moreSheet.insertAdjacentElement('afterbegin', dragHandle); + + const moreSearchBar = document.createElement('div'); + moreSearchBar.className = 'more-sheet__search'; + moreSearchBar.id = 'more-sheet-search'; + moreSearchBar.setAttribute('role', 'button'); + moreSearchBar.setAttribute('tabindex', '0'); + moreSearchBar.setAttribute('aria-label', t('search.placeholder')); + const moreSearchIcon = document.createElement('i'); + moreSearchIcon.dataset.lucide = 'search'; + moreSearchIcon.className = 'more-sheet__search-icon'; + moreSearchIcon.setAttribute('aria-hidden', 'true'); + const moreSearchPlaceholder = document.createElement('span'); + moreSearchPlaceholder.className = 'more-sheet__search-placeholder'; + moreSearchPlaceholder.textContent = t('search.placeholder'); + moreSearchBar.appendChild(moreSearchIcon); + moreSearchBar.appendChild(moreSearchPlaceholder); + moreSheet.appendChild(moreSearchBar); + navItems().filter((i) => !i.kitchenGroup).slice(PRIMARY_NAV).forEach((item) => moreSheet.appendChild(moreItemEl(item))); const searchOverlay = document.createElement('div'); @@ -606,15 +607,15 @@ function renderAppShell(container) { }); }); - initMoreSheet(container); + const openSearch = initSearch(container); + initMoreSheet(container, openSearch); initNavHideOnScroll(container); - initSearch(container); initOfflineBanner(); initKeyboardShortcuts(); } const SHORTCUTS = [ - { key: '/', description: () => t('shortcuts.search'), action: () => document.getElementById('search-btn')?.click() }, + { key: '/', description: () => t('shortcuts.search'), action: () => document.getElementById('more-sheet-search')?.click() }, { key: 'n', description: () => t('shortcuts.new'), action: () => document.querySelector('.page-fab')?.click() }, { key: '?', description: () => t('shortcuts.help'), action: () => showShortcutsModal() }, { key: 'g d', description: () => t('shortcuts.goDash'), action: () => navigate('/') }, @@ -774,7 +775,7 @@ function initNavHideOnScroll(container) { /** * Öffnet/schließt das More-Sheet und die Backdrop. */ -function initMoreSheet(container) { +function initMoreSheet(container, openSearch) { const moreBtn = container.querySelector('#more-btn'); const backdrop = container.querySelector('#more-backdrop'); const sheet = container.querySelector('#more-sheet'); @@ -812,6 +813,15 @@ function initMoreSheet(container) { el.addEventListener('click', () => closeSheet()); }); + const moreSearchBar = sheet.querySelector('#more-sheet-search'); + if (moreSearchBar && openSearch) { + const triggerSearch = () => { closeSheet(); openSearch(); }; + moreSearchBar.addEventListener('click', triggerSearch); + moreSearchBar.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); triggerSearch(); } + }); + } + window._closeMoreSheet = closeSheet; } @@ -819,12 +829,11 @@ function initMoreSheet(container) { * Initialisiert die Suchfunktion (Overlay + API-Calls). */ function initSearch(container) { - const searchBtn = container.querySelector('#search-btn'); const searchClose = container.querySelector('#search-close'); const overlay = container.querySelector('#search-overlay'); const input = container.querySelector('#search-input'); const results = container.querySelector('#search-results'); - if (!searchBtn || !overlay || !input || !results) return; + if (!overlay || !input || !results) return null; // Leichtgewichtiger Focus Trap für das Search Overlay. // Eigenständig (kein modal.js), da modul-globale Variablen in modal.js @@ -832,7 +841,6 @@ function initSearch(container) { let _searchTrapHandler = null; function openSearch() { - window._openSearch = openSearch; if (window._closeMoreSheet) window._closeMoreSheet(); overlay.setAttribute('aria-hidden', 'false'); overlay.classList.add('search-overlay--visible'); @@ -866,8 +874,7 @@ function initSearch(container) { results.replaceChildren(); } - searchBtn.addEventListener('click', openSearch); - searchClose.addEventListener('click', closeSearch); + if (searchClose) searchClose.addEventListener('click', closeSearch); document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && overlay.classList.contains('search-overlay--visible')) { @@ -892,6 +899,8 @@ function initSearch(container) { } }, 300); }); + + return openSearch; } /** @@ -1253,17 +1262,21 @@ window.addEventListener('locale-changed', () => { } if (bottomItems) { const kitchenBtnEl = bottomItems.querySelector('#kitchen-btn'); - const searchBtnEl = bottomItems.querySelector('#search-btn'); const moreBtn = bottomItems.querySelector('#more-btn'); if (kitchenBtnEl) kitchenBtnEl.querySelector('.nav-item__label').textContent = t('nav.kitchen'); - if (searchBtnEl) searchBtnEl.querySelector('.nav-item__label').textContent = t('nav.search'); const newItems = navItems().slice(0, PRIMARY_NAV).map(navItemEl); - bottomItems.replaceChildren(...newItems, kitchenBtnEl, searchBtnEl, moreBtn); + bottomItems.replaceChildren(...newItems, kitchenBtnEl, moreBtn); } if (moreSheet) { const handle = moreSheet.querySelector('.more-sheet__handle'); + const searchBar = moreSheet.querySelector('#more-sheet-search'); + if (searchBar) { + const placeholder = searchBar.querySelector('.more-sheet__search-placeholder'); + if (placeholder) placeholder.textContent = t('search.placeholder'); + searchBar.setAttribute('aria-label', t('search.placeholder')); + } const newMoreItems = navItems().filter((i) => !i.kitchenGroup).slice(PRIMARY_NAV).map(moreItemEl); - moreSheet.replaceChildren(handle, ...newMoreItems); + moreSheet.replaceChildren(handle, ...(searchBar ? [searchBar] : []), ...newMoreItems); } document.querySelectorAll('[data-route]').forEach((el) => { diff --git a/public/styles/layout.css b/public/styles/layout.css index 0cf0b26..a7138ae 100755 --- a/public/styles/layout.css +++ b/public/styles/layout.css @@ -218,7 +218,7 @@ padding: var(--space-4) var(--space-4) calc(var(--space-4) + var(--safe-area-inset-bottom)); z-index: calc(var(--z-nav) + 2); display: grid; - grid-template-columns: repeat(2, 1fr); + grid-template-columns: repeat(3, 1fr); gap: var(--space-3); transform: translateY(100%); transition: transform 0.25s var(--ease-out); @@ -238,6 +238,40 @@ margin: 0 auto var(--space-2); } +/* ── More-Sheet Suchleiste ── */ +.more-sheet__search { + grid-column: 1 / -1; + display: flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-2) var(--space-3); + border-radius: var(--radius-lg); + background-color: var(--color-surface-elevated); + border: 1.5px solid var(--color-border); + color: var(--color-text-tertiary); + cursor: text; + margin-bottom: var(--space-1); + -webkit-tap-highlight-color: transparent; + transition: border-color var(--transition-fast); +} + +.more-sheet__search:active, +.more-sheet__search:focus { + outline: none; + border-color: var(--color-accent); +} + +.more-sheet__search-icon { + width: var(--space-4); + height: var(--space-4); + flex-shrink: 0; +} + +.more-sheet__search-placeholder { + font-size: var(--text-sm); + color: var(--color-text-tertiary); +} + /* ── More-Item ── */ .more-item { display: flex;