chore: release v0.37.1

This commit is contained in:
Ulas Kalayci
2026-04-30 07:44:30 +02:00
parent 3a99734b4c
commit 3cd6eb40d4
5 changed files with 89 additions and 35 deletions
+7
View File
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [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 ## [0.37.0] - 2026-04-30
### Added ### Added
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "oikos", "name": "oikos",
"version": "0.36.1", "version": "0.37.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "oikos", "name": "oikos",
"version": "0.36.1", "version": "0.37.1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"bcrypt": "^6.0.0", "bcrypt": "^6.0.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "oikos", "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.", "description": "Self-hosted family planner - calendar, tasks, shopping, meal planning, budget and more. Private, open-source, no subscription.",
"main": "server/index.js", "main": "server/index.js",
"type": "module", "type": "module",
+44 -31
View File
@@ -132,7 +132,7 @@ let _pendingLoginRedirect = false;
const ROUTE_ORDER = ['/', '/calendar', '/tasks', '/meals', '/recipes', '/shopping', const ROUTE_ORDER = ['/', '/calendar', '/tasks', '/meals', '/recipes', '/shopping',
'/birthdays', '/notes', '/contacts', '/budget', '/documents', '/settings']; '/birthdays', '/notes', '/contacts', '/budget', '/documents', '/settings'];
const PRIMARY_NAV = 2; 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';
@@ -504,23 +504,6 @@ function renderAppShell(container) {
kitchenBtn.addEventListener('click', () => navigate(getLastKitchenRoute())); kitchenBtn.addEventListener('click', () => navigate(getLastKitchenRoute()));
bottomItems.appendChild(kitchenBtn); 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'); 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';
@@ -553,6 +536,24 @@ function renderAppShell(container) {
dragHandle.className = 'more-sheet__handle'; dragHandle.className = 'more-sheet__handle';
dragHandle.setAttribute('aria-hidden', 'true'); dragHandle.setAttribute('aria-hidden', 'true');
moreSheet.insertAdjacentElement('afterbegin', dragHandle); 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))); navItems().filter((i) => !i.kitchenGroup).slice(PRIMARY_NAV).forEach((item) => moreSheet.appendChild(moreItemEl(item)));
const searchOverlay = document.createElement('div'); const searchOverlay = document.createElement('div');
@@ -606,15 +607,15 @@ function renderAppShell(container) {
}); });
}); });
initMoreSheet(container); const openSearch = initSearch(container);
initMoreSheet(container, openSearch);
initNavHideOnScroll(container); initNavHideOnScroll(container);
initSearch(container);
initOfflineBanner(); initOfflineBanner();
initKeyboardShortcuts(); initKeyboardShortcuts();
} }
const SHORTCUTS = [ 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: 'n', description: () => t('shortcuts.new'), action: () => document.querySelector('.page-fab')?.click() },
{ key: '?', description: () => t('shortcuts.help'), action: () => showShortcutsModal() }, { key: '?', description: () => t('shortcuts.help'), action: () => showShortcutsModal() },
{ key: 'g d', description: () => t('shortcuts.goDash'), action: () => navigate('/') }, { 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. * Öffnet/schließt das More-Sheet und die Backdrop.
*/ */
function initMoreSheet(container) { function initMoreSheet(container, openSearch) {
const moreBtn = container.querySelector('#more-btn'); const moreBtn = container.querySelector('#more-btn');
const backdrop = container.querySelector('#more-backdrop'); const backdrop = container.querySelector('#more-backdrop');
const sheet = container.querySelector('#more-sheet'); const sheet = container.querySelector('#more-sheet');
@@ -812,6 +813,15 @@ function initMoreSheet(container) {
el.addEventListener('click', () => closeSheet()); 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; window._closeMoreSheet = closeSheet;
} }
@@ -819,12 +829,11 @@ function initMoreSheet(container) {
* Initialisiert die Suchfunktion (Overlay + API-Calls). * Initialisiert die Suchfunktion (Overlay + API-Calls).
*/ */
function initSearch(container) { function initSearch(container) {
const searchBtn = container.querySelector('#search-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');
const results = container.querySelector('#search-results'); 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. // Leichtgewichtiger Focus Trap für das Search Overlay.
// Eigenständig (kein modal.js), da modul-globale Variablen in modal.js // Eigenständig (kein modal.js), da modul-globale Variablen in modal.js
@@ -832,7 +841,6 @@ 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');
@@ -866,8 +874,7 @@ function initSearch(container) {
results.replaceChildren(); results.replaceChildren();
} }
searchBtn.addEventListener('click', openSearch); if (searchClose) searchClose.addEventListener('click', closeSearch);
searchClose.addEventListener('click', closeSearch);
document.addEventListener('keydown', (e) => { document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && overlay.classList.contains('search-overlay--visible')) { if (e.key === 'Escape' && overlay.classList.contains('search-overlay--visible')) {
@@ -892,6 +899,8 @@ function initSearch(container) {
} }
}, 300); }, 300);
}); });
return openSearch;
} }
/** /**
@@ -1253,17 +1262,21 @@ window.addEventListener('locale-changed', () => {
} }
if (bottomItems) { if (bottomItems) {
const kitchenBtnEl = bottomItems.querySelector('#kitchen-btn'); const kitchenBtnEl = bottomItems.querySelector('#kitchen-btn');
const searchBtnEl = bottomItems.querySelector('#search-btn');
const moreBtn = bottomItems.querySelector('#more-btn'); const moreBtn = bottomItems.querySelector('#more-btn');
if (kitchenBtnEl) kitchenBtnEl.querySelector('.nav-item__label').textContent = t('nav.kitchen'); 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); const newItems = navItems().slice(0, PRIMARY_NAV).map(navItemEl);
bottomItems.replaceChildren(...newItems, kitchenBtnEl, searchBtnEl, moreBtn); bottomItems.replaceChildren(...newItems, kitchenBtnEl, moreBtn);
} }
if (moreSheet) { if (moreSheet) {
const handle = moreSheet.querySelector('.more-sheet__handle'); 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); 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) => { document.querySelectorAll('[data-route]').forEach((el) => {
+35 -1
View File
@@ -218,7 +218,7 @@
padding: var(--space-4) var(--space-4) calc(var(--space-4) + var(--safe-area-inset-bottom)); padding: var(--space-4) var(--space-4) calc(var(--space-4) + var(--safe-area-inset-bottom));
z-index: calc(var(--z-nav) + 2); z-index: calc(var(--z-nav) + 2);
display: grid; display: grid;
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(3, 1fr);
gap: var(--space-3); gap: var(--space-3);
transform: translateY(100%); transform: translateY(100%);
transition: transform 0.25s var(--ease-out); transition: transform 0.25s var(--ease-out);
@@ -238,6 +238,40 @@
margin: 0 auto var(--space-2); 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 ── */
.more-item { .more-item {
display: flex; display: flex;