Reorganizacja górnych paneli i ujednolicenie stylu filtrów

- Kalendarz: data między strzałkami zamiast napisu "Dziś", nawigacja po prawej, mniejszy komponent dopasowany do wysokości dnia
- MealPlanner/Pantry/RecipeList: spójne nagłówki z tytułem po lewej i kontrolkami po prawej
- RecipeList: nowy top bar z przyciskami filtrów i wyszukiwania wzorowany na spiżarni
- Filter popup: ujednolicony styl z popoverem spiżarni (ciemniejsze tło, jaśniejsze obramowanie, spójne chipy)
- Usunięcie przyciemnienia otoczenia przy otwieraniu filtrów
- Badge z liczbą aktywnych filtrów na przycisku, zachowujący stan po zamknięciu popupu
- Usunięcie ikon kalendarza z pigułek w spiżarni

Made-with: Cursor
This commit is contained in:
2026-04-16 00:17:41 +02:00
parent d3a68a80eb
commit 4d7a1a12ae
7 changed files with 553 additions and 170 deletions

View File

@@ -1,13 +1,12 @@
import { RECIPES } from '../data/catalog.js?v=8';
import { getRecipeGridSectionHTML, renderRecipeGrid } from '../ui/recipeGrid.js';
import {
getRecipeSearchFieldHTML,
syncRecipeSearchShellShadow,
} from '../ui/recipeSearchField.js';
const DEFAULT_MIN_MINUTES = 5;
const DEFAULT_MAX_MINUTES = 120;
/** Jak w spiżarni — cień „pigówek” i powłoki wyszukiwania */
const SEARCH_SHELL_SHADOW = '0 5px 10px rgba(0,0,0,0.16), 0 14px 22px rgba(0,0,0,0.24), 0 22px 34px rgba(0,0,0,0.18), inset 0 1px 0 rgba(255,255,255,0.04)';
let filterState = {
query: '',
slots: [],
@@ -16,6 +15,9 @@ let filterState = {
maxMinutes: DEFAULT_MAX_MINUTES,
};
let isSearchExpanded = false;
let recipeListDocListenersBound = false;
function matchesFilters(recipe) {
const { query, slots, tags, minMinutes, maxMinutes } = filterState;
@@ -46,7 +48,51 @@ function getFilteredRecipes() {
function syncRecipeScrollShadow() {
const searchShell = document.getElementById('recipe-search-shell');
syncRecipeSearchShellShadow(searchShell);
if (searchShell) {
searchShell.style.boxShadow = SEARCH_SHELL_SHADOW;
}
}
function syncRecipeTopbarUI() {
const defaultRow = document.getElementById('recipe-default-row');
const searchShell = document.getElementById('recipe-search-shell');
const showSearch = isSearchExpanded;
if (defaultRow) {
defaultRow.style.opacity = showSearch ? '0' : '1';
defaultRow.style.transform = showSearch ? 'translateY(-2px) scale(0.98)' : 'translateY(0) scale(1)';
defaultRow.style.pointerEvents = showSearch ? 'none' : 'auto';
}
if (searchShell) {
searchShell.style.opacity = showSearch ? '1' : '0';
searchShell.style.transform = showSearch ? 'translateY(0) scale(1)' : 'translateY(-2px) scale(0.98)';
searchShell.style.pointerEvents = showSearch ? 'auto' : 'none';
searchShell.style.boxShadow = SEARCH_SHELL_SHADOW;
}
}
function closeSearch() {
const input = document.getElementById('recipe-search-input');
const hadQuery = Boolean(input?.value);
if (input) {
input.value = '';
input.blur();
}
filterState.query = '';
isSearchExpanded = false;
syncRecipeTopbarUI();
if (hadQuery) renderGrid();
}
function openSearch() {
isSearchExpanded = true;
window.closeFilters?.();
syncRecipeTopbarUI();
window.requestAnimationFrame(() => {
document.getElementById('recipe-search-input')?.focus();
});
}
function renderGrid() {
@@ -61,6 +107,7 @@ function renderGrid() {
showSlotLabels: false,
cardClassName: 'recipe-list-card',
});
syncRecipeTopbarUI();
requestAnimationFrame(syncRecipeScrollShadow);
}
@@ -68,15 +115,33 @@ export function getRecipeListHTML() {
return `
<div id="main-view" class="flex flex-col h-full absolute inset-0 bg-[#2d2e2b] z-10" style="background:#2d2e2b !important;">
<div id="recipe-top-bar" class="pointer-events-none absolute inset-x-0 top-0 z-[12] px-4 pt-4" style="background:transparent !important; border:none !important;">
<div class="pointer-events-auto">
${getRecipeSearchFieldHTML({
shellId: 'recipe-search-shell',
inputId: 'recipe-search-input',
placeholder: 'Szukaj przepisów...',
filterButtonId: 'recipe-filter-btn',
filterButtonAction: 'openFilters()',
filterButtonLabel: 'Otwórz filtry',
})}
<div class="pointer-events-auto relative z-[1] mx-auto" style="width:min(calc(100% - 0.5rem), 22.4rem);">
<div id="recipe-topbar" class="relative min-h-12">
<div id="recipe-default-row" class="flex min-h-12 items-center gap-2 transition-all duration-200" style="opacity:1; transform:translateY(0) scale(1);">
<h1 class="min-w-0 flex-1 truncate" style="margin:0;padding:0;color:#f2efe8;font-family:var(--app-font);font-size:18px;font-weight:700;line-height:1.2;letter-spacing:-0.02em;">Katalog przepisów</h1>
<div id="recipe-filter-wrap" class="relative shrink-0">
<button type="button" id="recipe-filter-btn" class="relative w-11 h-11 rounded-full shrink-0 flex items-center justify-center transition-all duration-200" style="background:#393937; border:1px solid #41423f; box-shadow:${SEARCH_SHELL_SHADOW}; color:#ddd6ca;" aria-label="Otwórz filtry">
<i class="fas fa-sliders-h text-[12px]" aria-hidden="true"></i>
<span id="recipe-filter-count" class="hidden absolute -top-1 -right-1 min-w-[1.1rem] h-[1.1rem] px-1 rounded-full text-[9px] font-bold leading-none items-center justify-center" style="background:#23221e; border:1px solid #787876; color:#f2efe8;"></span>
</button>
</div>
<button type="button" id="recipe-search-toggle" class="w-11 h-11 rounded-full shrink-0 flex items-center justify-center transition-all duration-200" style="background:#393937 !important; border:1px solid #41423f !important; box-shadow:${SEARCH_SHELL_SHADOW} !important; color:#ddd6ca;" aria-label="Szukaj">
<i class="fas fa-search text-[13px]" aria-hidden="true"></i>
</button>
</div>
<div id="recipe-search-shell" class="absolute inset-0 flex items-center gap-2 rounded-full px-3 transition-all duration-200 pointer-events-none" style="background:#23221e !important; border:1px solid #787876 !important; box-shadow:${SEARCH_SHELL_SHADOW} !important; opacity:0; transform:translateY(-2px) scale(0.98);">
<i class="fas fa-search text-[13px] shrink-0" style="color:#9b978f;"></i>
<input type="search" id="recipe-search-input" autocomplete="off" placeholder="Szukaj przepisów…"
class="flex-1 min-w-0 h-full bg-transparent outline-none text-[15px] leading-none py-0" style="background:transparent !important; border:none !important; box-shadow:none !important; color:#ddd6ca; margin:0;">
<button type="button" id="recipe-search-close" class="w-8 h-8 rounded-full shrink-0 flex items-center justify-center transition-colors" style="background:#2f2f2d; border:none; color:#9b978f;">
<i class="fas fa-xmark text-[13px]"></i>
</button>
</div>
</div>
</div>
</div>
@@ -84,7 +149,7 @@ export function getRecipeListHTML() {
scrollId: 'recipe-scroll',
gridId: 'recipe-grid',
emptyStateId: 'recipe-empty-state',
scrollClassName: 'relative flex-1 overflow-y-auto px-4 pt-24 pb-24 bg-[#2d2e2b]',
scrollClassName: 'relative flex-1 overflow-y-auto px-4 pt-[5.35rem] pb-24 bg-[#2d2e2b]',
gridClassName: 'grid grid-cols-3 gap-2 bg-[#2d2e2b]',
emptyTitle: 'Brak wyników',
emptyMessage: 'Zmień kryteria wyszukiwania lub filtry',
@@ -117,6 +182,21 @@ export function setupRecipeList() {
filterState.query = e.target.value.trim();
renderGrid();
});
document.getElementById('recipe-search-input')?.addEventListener('keydown', (e) => {
if (e.key === 'Escape') closeSearch();
});
document.getElementById('recipe-search-toggle')?.addEventListener('click', () => openSearch());
document.getElementById('recipe-search-close')?.addEventListener('click', () => closeSearch());
document.getElementById('recipe-filter-btn')?.addEventListener('click', (e) => {
e.stopPropagation();
if (isSearchExpanded) {
isSearchExpanded = false;
syncRecipeTopbarUI();
}
window.openFilters?.('recipes');
});
document.getElementById('recipe-grid')?.addEventListener('click', (e) => {
const card = e.target.closest('.recipe-browser-card');
@@ -127,4 +207,14 @@ export function setupRecipeList() {
document.getElementById('recipe-scroll')?.addEventListener('scroll', syncRecipeScrollShadow);
syncRecipeScrollShadow();
if (!recipeListDocListenersBound) {
recipeListDocListenersBound = true;
document.addEventListener('keydown', (e) => {
if (e.key !== 'Escape' || !isSearchExpanded) return;
if (!document.getElementById('main-view')?.classList.contains('hidden')) {
closeSearch();
}
});
}
}