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:
@@ -3,26 +3,23 @@ import { MEAL_SLOTS } from '../planner/mealSlots.js';
|
||||
import { applyFilters, getFilterState } from './RecipeList.js';
|
||||
|
||||
const FILTER_PANEL_TRANSITION = 'opacity 180ms ease, transform 180ms ease';
|
||||
const FILTER_SURFACE = '#2d2e2b';
|
||||
const FILTER_SURFACE_SOFT = '#2f2f2d';
|
||||
const FILTER_BORDER = '#444442';
|
||||
const FILTER_BORDER_ACTIVE = '#787876';
|
||||
const FILTER_CHIP_ACTIVE = '#23221e';
|
||||
const FILTER_TEXT_PRIMARY = '#ddd6ca';
|
||||
const FILTER_SURFACE = '#23221e';
|
||||
const FILTER_SURFACE_SOFT = '#2d2e2b';
|
||||
const FILTER_BORDER = '#787876';
|
||||
const FILTER_CHIP_ACTIVE_BG = '#393937';
|
||||
const FILTER_TEXT_SECONDARY = '#d7d2c8';
|
||||
const FILTER_TEXT_MUTED = '#9b978f';
|
||||
const FILTER_TEXT_DIM = '#7d7a74';
|
||||
const FILTER_TEXT_MUTED = '#b5afa5';
|
||||
const FILTER_TEXT_ACTIVE = '#f2efe8';
|
||||
const FILTER_TRACK = '#393937';
|
||||
const FILTER_TRACK_FILL = '#56534f';
|
||||
const FILTER_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)';
|
||||
const PREP_TIME_MIN = 5;
|
||||
const PREP_TIME_MAX = 120;
|
||||
const PREP_TIME_STEP = 5;
|
||||
const PREP_TIME_MIN_GAP = PREP_TIME_STEP;
|
||||
const FILTER_RECIPE_BLUR = 'blur(3px) saturate(0.94)';
|
||||
const FILTER_CONTEXTS = {
|
||||
recipes: {
|
||||
anchorShellId: 'recipe-search-shell',
|
||||
anchorShellId: 'recipe-topbar',
|
||||
buttonId: 'recipe-filter-btn',
|
||||
getState: () => getFilterState(),
|
||||
applyState: (nextState) => applyFilters(nextState),
|
||||
@@ -82,35 +79,31 @@ export function getFilterHTML() {
|
||||
outline: none;
|
||||
}
|
||||
</style>
|
||||
<div id="filter-view" class="absolute inset-0 z-[70] hidden opacity-0 transition-opacity duration-150" style="pointer-events:none; background:rgba(0,0,0,0.5) !important; background-image:none !important;" aria-hidden="true">
|
||||
<div id="filter-panel" class="absolute flex flex-col overflow-hidden rounded-[1.5rem] border" style="background:${FILTER_SURFACE} !important; background-image:none !important; border-color:${FILTER_BORDER} !important; opacity:0; transform:translateY(-0.5rem) scale(0.98); transform-origin:top center; transition:${FILTER_PANEL_TRANSITION}; box-shadow:0 18px 40px rgba(0,0,0,0.34), 0 4px 12px rgba(0,0,0,0.18); width:min(calc(100% - 1.5rem), 22rem);">
|
||||
<div class="pointer-events-none absolute inset-x-0 top-0 h-px" style="background:rgba(242,239,232,0.12);" aria-hidden="true"></div>
|
||||
<div class="shrink-0 px-3.5 pt-3 pb-2 flex justify-end" style="background:${FILTER_SURFACE} !important; background-image:none !important;">
|
||||
<div class="min-w-0 flex items-center justify-end gap-2">
|
||||
<button id="filter-clear-btn" type="button" class="shrink-0 h-8 px-3 rounded-full border text-[11px] font-semibold transition-colors" style="background:${FILTER_SURFACE_SOFT} !important; border-color:${FILTER_BORDER} !important; color:${FILTER_TEXT_SECONDARY} !important;">Wyczyść</button>
|
||||
</div>
|
||||
<div id="filter-view" class="absolute inset-0 z-[70] hidden opacity-0 transition-opacity duration-150" style="pointer-events:none; background:transparent !important; background-image:none !important;" aria-hidden="true">
|
||||
<div id="filter-panel" class="absolute flex flex-col overflow-hidden rounded-[1.35rem]" style="background:${FILTER_SURFACE} !important; background-image:none !important; border:1px solid ${FILTER_BORDER} !important; opacity:0; transform:translateY(-0.5rem) scale(0.98); transform-origin:top center; transition:${FILTER_PANEL_TRANSITION}; box-shadow:${FILTER_SHADOW}; width:min(calc(100% - 1.5rem), 22rem);">
|
||||
<div class="shrink-0 px-3 pt-3 pb-2 flex items-center justify-between gap-3">
|
||||
<p class="text-[11px] font-semibold leading-none" style="color:${FILTER_TEXT_ACTIVE};">Filtry</p>
|
||||
<button id="filter-clear-btn" type="button" class="h-8 px-2 rounded-full text-[11px] font-semibold transition-colors" style="background:transparent; border:none; color:${FILTER_TEXT_MUTED};">Wyczyść</button>
|
||||
</div>
|
||||
|
||||
<div id="filter-panel-body" class="min-h-0 flex-1 overflow-y-auto no-scrollbar px-4 pb-4 space-y-2.5" style="background:${FILTER_SURFACE} !important; background-image:none !important;">
|
||||
<section id="filter-slot-section" class="p-3.5" style="background:${FILTER_SURFACE} !important; background-image:none !important;">
|
||||
<p class="text-[10px] font-bold uppercase tracking-wider mb-3" style="color:${FILTER_TEXT_MUTED};">Pora posiłku</p>
|
||||
<div id="filter-slot-chips" class="flex flex-wrap gap-2"></div>
|
||||
<div id="filter-panel-body" class="min-h-0 flex-1 overflow-y-auto no-scrollbar px-3 pb-3 space-y-4">
|
||||
<section id="filter-slot-section">
|
||||
<p class="text-[10px] font-bold uppercase tracking-wider mb-3 px-0.5" style="color:${FILTER_TEXT_MUTED};">Pora posiłku</p>
|
||||
<div id="filter-slot-chips" class="flex flex-wrap gap-2 px-1.5"></div>
|
||||
</section>
|
||||
|
||||
<section class="p-3.5" style="background:${FILTER_SURFACE} !important; background-image:none !important;">
|
||||
<p class="text-[10px] font-bold uppercase tracking-wider mb-3" style="color:${FILTER_TEXT_MUTED};">Dieta i tagi</p>
|
||||
<div id="filter-tag-chips" class="flex flex-wrap gap-2"></div>
|
||||
<section>
|
||||
<p class="text-[10px] font-bold uppercase tracking-wider mb-3 px-0.5" style="color:${FILTER_TEXT_MUTED};">Dieta i tagi</p>
|
||||
<div id="filter-tag-chips" class="flex flex-wrap gap-2 px-1.5"></div>
|
||||
</section>
|
||||
|
||||
<section class="p-3.5" style="background:${FILTER_SURFACE} !important; background-image:none !important;">
|
||||
<div class="flex items-center justify-between gap-3 mb-3">
|
||||
<div class="min-w-0">
|
||||
<p class="text-[10px] font-bold uppercase tracking-wider" style="color:${FILTER_TEXT_MUTED};">Czas przygotowania</p>
|
||||
</div>
|
||||
<section>
|
||||
<div class="flex items-center justify-between gap-3 mb-3 px-0.5">
|
||||
<p class="text-[10px] font-bold uppercase tracking-wider" style="color:${FILTER_TEXT_MUTED};">Czas przygotowania</p>
|
||||
<span id="time-display-range" class="shrink-0 text-[11px] font-semibold tabular-nums text-right" style="color:${FILTER_TEXT_ACTIVE};">5 min - 120 min</span>
|
||||
</div>
|
||||
<div class="px-1">
|
||||
<div class="relative h-9">
|
||||
<div class="mx-1.5">
|
||||
<div class="relative h-9 mx-2">
|
||||
<div class="prep-time-range-track absolute inset-x-0 top-1/2 -translate-y-1/2" style="background:${FILTER_TRACK};" aria-hidden="true"></div>
|
||||
<div id="prep-time-range-fill" class="prep-time-range-fill absolute top-1/2 -translate-y-1/2" style="background:${FILTER_TRACK_FILL}; left:0%; width:100%;" aria-hidden="true"></div>
|
||||
<button
|
||||
@@ -183,10 +176,13 @@ function formatTimeRangeSummary(minMinutes, maxMinutes) {
|
||||
}
|
||||
|
||||
function getChipStyle(active) {
|
||||
const background = active ? FILTER_CHIP_ACTIVE : FILTER_SURFACE_SOFT;
|
||||
const border = active ? FILTER_BORDER_ACTIVE : FILTER_BORDER;
|
||||
const background = active ? FILTER_CHIP_ACTIVE_BG : FILTER_SURFACE_SOFT;
|
||||
const color = active ? FILTER_TEXT_ACTIVE : FILTER_TEXT_SECONDARY;
|
||||
return `background:${background} !important; background-image:none !important; box-shadow:none !important; border-color:${border} !important; color:${color} !important;`;
|
||||
const borderRule = active ? `border:1px solid ${FILTER_BORDER};` : 'border:none;';
|
||||
const shadow = active
|
||||
? 'box-shadow:inset 0 1px 0 rgba(255,255,255,0.04), 0 0 0 1px rgba(0,0,0,0.08);'
|
||||
: '';
|
||||
return `background:${background}; ${borderRule} color:${color}; ${shadow}`;
|
||||
}
|
||||
|
||||
function clampTimeValue(value) {
|
||||
@@ -283,6 +279,8 @@ function showFilterPanel() {
|
||||
positionFilterPanel();
|
||||
setRecipeAreaBlur(true);
|
||||
|
||||
syncPanelCount();
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
view.classList.add('opacity-100');
|
||||
panel.style.opacity = '1';
|
||||
@@ -301,6 +299,7 @@ function hideFilterPanel() {
|
||||
panel.style.opacity = '0';
|
||||
panel.style.transform = 'translateY(-0.5rem) scale(0.98)';
|
||||
setRecipeAreaBlur(false);
|
||||
syncPanelCount();
|
||||
|
||||
closeTimer = setTimeout(() => {
|
||||
view.classList.add('hidden');
|
||||
@@ -317,7 +316,7 @@ function renderSlotChips() {
|
||||
|
||||
wrap.innerHTML = MEAL_SLOTS.map((slot) => {
|
||||
const active = localSlots.includes(slot.id);
|
||||
return `<button type="button" data-filter-slot="${escapeHtml(slot.id)}" class="px-3 py-1.5 rounded-full border text-[12px] font-semibold transition-colors" style="${getChipStyle(active)}">${escapeHtml(slot.label)}</button>`;
|
||||
return `<button type="button" data-filter-slot="${escapeHtml(slot.id)}" class="px-3 py-2 rounded-full text-[11px] font-semibold transition-colors" style="${getChipStyle(active)}">${escapeHtml(slot.label)}</button>`;
|
||||
}).join('');
|
||||
|
||||
wrap.querySelectorAll('[data-filter-slot]').forEach((btn) => {
|
||||
@@ -339,7 +338,7 @@ function renderTagChips() {
|
||||
const allTags = collectAllTags();
|
||||
wrap.innerHTML = allTags.map((tag) => {
|
||||
const active = localTags.includes(tag.toLowerCase());
|
||||
return `<button type="button" data-filter-tag="${escapeHtml(tag)}" class="px-3 py-1.5 rounded-full border text-[12px] font-semibold transition-colors" style="${getChipStyle(active)}">${escapeHtml(tag)}</button>`;
|
||||
return `<button type="button" data-filter-tag="${escapeHtml(tag)}" class="px-3 py-2 rounded-full text-[11px] font-semibold transition-colors" style="${getChipStyle(active)}">${escapeHtml(tag)}</button>`;
|
||||
}).join('');
|
||||
|
||||
wrap.querySelectorAll('[data-filter-tag]').forEach((btn) => {
|
||||
@@ -360,6 +359,33 @@ function syncFilterSections() {
|
||||
slotSection.classList.toggle('hidden', !getActiveFilterConfig().showSlots);
|
||||
}
|
||||
|
||||
function getActiveFilterCount() {
|
||||
const config = getActiveFilterConfig();
|
||||
let count = localTags.length;
|
||||
if (config.showSlots) count += localSlots.length;
|
||||
if (localMinMinutes > PREP_TIME_MIN || localMaxMinutes < PREP_TIME_MAX) count += 1;
|
||||
return count;
|
||||
}
|
||||
|
||||
function syncPanelCount() {
|
||||
const count = getActiveFilterCount();
|
||||
const { buttonId } = getActiveFilterConfig();
|
||||
const button = buttonId ? document.getElementById(buttonId) : null;
|
||||
|
||||
if (button) {
|
||||
const highlight = isFilterPanelOpen() || count > 0;
|
||||
button.style.setProperty('background', highlight ? '#23221e' : '#393937', 'important');
|
||||
button.style.setProperty('border-color', highlight ? '#787876' : '#41423f', 'important');
|
||||
button.style.setProperty('color', highlight ? '#f2efe8' : '#ddd6ca', 'important');
|
||||
}
|
||||
|
||||
const badge = button?.querySelector('[id$="-filter-count"]');
|
||||
if (!badge) return;
|
||||
badge.textContent = String(count);
|
||||
badge.classList.toggle('hidden', count === 0);
|
||||
badge.classList.toggle('flex', count > 0);
|
||||
}
|
||||
|
||||
function syncLiveFilters() {
|
||||
const config = getActiveFilterConfig();
|
||||
config.applyState?.({
|
||||
@@ -368,6 +394,7 @@ function syncLiveFilters() {
|
||||
minMinutes: localMinMinutes,
|
||||
maxMinutes: localMaxMinutes,
|
||||
});
|
||||
syncPanelCount();
|
||||
}
|
||||
|
||||
export function setupFilter() {
|
||||
@@ -522,6 +549,7 @@ export function setupFilter() {
|
||||
renderSlotChips();
|
||||
renderTagChips();
|
||||
syncFilterSections();
|
||||
syncPanelCount();
|
||||
|
||||
showFilterPanel();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user