UI improvements: search dropdown, layout fixes, taller step fields

- Recipe search now shows dropdown suggestions while typing;
  full results load on Enter or clicking "Keresés" button
- Search field moved above tag filter; active filter tags below input
- Instruction textarea doubled in height (60px → 120px) on both
  import and edit pages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-26 08:45:11 +01:00
parent 8f4f7f245b
commit 4a1ebfaf4e
3 changed files with 101 additions and 26 deletions
+1 -1
View File
@@ -98,7 +98,7 @@
flex-shrink: 0;
margin-top: 0.4rem;
}
.instruction-row textarea { margin-bottom: 0; flex: 1; min-height: 60px; }
.instruction-row textarea { margin-bottom: 0; flex: 1; min-height: 120px; }
.instruction-row button {
background: var(--danger);
border: none;
+1 -1
View File
@@ -84,7 +84,7 @@
flex-shrink: 0;
margin-top: 0.4rem;
}
.instruction-row textarea { margin-bottom: 0; flex: 1; min-height: 60px; }
.instruction-row textarea { margin-bottom: 0; flex: 1; min-height: 120px; }
.instruction-row button {
background: var(--danger);
border: none;
+99 -24
View File
@@ -30,23 +30,44 @@
}
/* --- Search & filter bar --- */
.search-bar {
display: flex;
gap: 0.75rem;
align-items: flex-start;
flex-wrap: wrap;
.search-section {
margin-bottom: 1rem;
}
.search-bar .search-input-wrap {
flex: 1;
min-width: 200px;
.search-row {
display: flex;
gap: 0.5rem;
align-items: center;
margin-bottom: 0.75rem;
position: relative;
}
.search-bar input[type="text"] {
.search-row input[type="text"] {
margin-bottom: 0;
}
.tag-filter-wrap {
min-width: 200px;
flex: 1;
}
.search-dropdown {
position: absolute;
top: 100%;
left: 0;
right: 60px;
background: var(--surface2);
border: 1px solid var(--border);
border-radius: var(--radius);
max-height: 250px;
overflow-y: auto;
z-index: 110;
display: none;
}
.search-dropdown.open { display: block; }
.search-dropdown-item {
padding: 0.5rem 0.75rem;
cursor: pointer;
font-size: 0.9rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.search-dropdown-item:hover { background: var(--accent-glow); }
.tag-filter-wrap {
position: relative;
}
.tag-filter-wrap input[type="text"] {
@@ -56,7 +77,7 @@
display: flex;
flex-wrap: wrap;
gap: 0.3rem;
margin-bottom: 0.5rem;
margin-top: 0.5rem;
}
.filter-chip {
display: inline-flex;
@@ -275,17 +296,19 @@
{% endif %}
</div>
<!-- Search & tag filter -->
<div class="search-bar">
<div class="search-input-wrap">
<!-- Search -->
<div class="search-section">
<div class="search-row">
<input type="text" id="recipeSearch" placeholder="Recept keresése..."
oninput="onSearchInput()" autocomplete="off">
oninput="onSearchInput()" onkeydown="onSearchKeydown(event)" autocomplete="off">
<button class="btn btn-primary" onclick="executeSearch()">Keresés</button>
<div id="searchDropdown" class="search-dropdown"></div>
</div>
<div class="tag-filter-wrap">
<div class="active-filter-tags" id="activeFilterTags"></div>
<input type="text" id="tagFilterSearch" placeholder="Szűrés címke szerint..."
autocomplete="off" oninput="onFilterTagSearch()" onfocus="onFilterTagSearch()">
<div id="tagFilterDropdown" class="tag-dropdown"></div>
<div class="active-filter-tags" id="activeFilterTags"></div>
</div>
</div>
@@ -371,12 +394,60 @@ function switchBackend(b) {
function onSearchInput() {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
currentPage = 1;
selectedIds.clear();
loadRecipes();
loadSearchSuggestions();
}, 300);
}
function onSearchKeydown(e) {
if (e.key === 'Enter') {
e.preventDefault();
document.getElementById('searchDropdown').classList.remove('open');
executeSearch();
} else if (e.key === 'Escape') {
document.getElementById('searchDropdown').classList.remove('open');
}
}
function executeSearch() {
document.getElementById('searchDropdown').classList.remove('open');
currentPage = 1;
selectedIds.clear();
loadRecipes();
}
async function loadSearchSuggestions() {
const q = document.getElementById('recipeSearch').value.trim();
const dropdown = document.getElementById('searchDropdown');
if (!q) { dropdown.classList.remove('open'); return; }
const params = new URLSearchParams({ page: 1, per_page: 8, search: q });
if (activeFilterTagIds.length) params.set('tag_ids', activeFilterTagIds.join(','));
try {
const resp = await fetch('/api/recipes/' + currentBackend + '?' + params);
const data = await resp.json();
if (!data.ok || data.items.length === 0) {
dropdown.classList.remove('open');
return;
}
let html = '';
for (const r of data.items) {
html += '<div class="search-dropdown-item" onclick="selectSearchItem(\'' +
escHtml(r.name).replace(/'/g, "\\'") + '\')">' + escHtml(r.name) + '</div>';
}
dropdown.innerHTML = html;
dropdown.classList.add('open');
} catch (e) {
dropdown.classList.remove('open');
}
}
function selectSearchItem(name) {
document.getElementById('recipeSearch').value = name;
document.getElementById('searchDropdown').classList.remove('open');
executeSearch();
}
/* ===== Tag filter ===== */
async function loadBackendTags() {
try {
@@ -444,12 +515,16 @@ function renderFilterChips() {
wrap.innerHTML = html;
}
/* ===== Close dropdown on outside click ===== */
/* ===== Close dropdowns on outside click ===== */
document.addEventListener('click', e => {
const wrap = document.querySelector('.tag-filter-wrap');
if (wrap && !wrap.contains(e.target)) {
const tagWrap = document.querySelector('.tag-filter-wrap');
if (tagWrap && !tagWrap.contains(e.target)) {
document.getElementById('tagFilterDropdown').classList.remove('open');
}
const searchRow = document.querySelector('.search-row');
if (searchRow && !searchRow.contains(e.target)) {
document.getElementById('searchDropdown').classList.remove('open');
}
});
/* ===== Load recipes ===== */