feat: US-004 - Frontend: Search and filter collapse to icons
This commit is contained in:
@@ -31,15 +31,86 @@
|
||||
}
|
||||
|
||||
/* Filter bar */
|
||||
/* Filter/Search Bar - Collapsible */
|
||||
.filter-bar {
|
||||
display: flex;
|
||||
gap: var(--space-3);
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: var(--space-4);
|
||||
padding: var(--space-3);
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.filter-toolbar {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-2);
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.filter-icon-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 44px;
|
||||
min-height: 36px;
|
||||
padding: var(--space-2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-base);
|
||||
}
|
||||
|
||||
.filter-icon-btn:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.filter-icon-btn.active {
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.search-container, .filter-container {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 300ms ease, padding 300ms ease;
|
||||
}
|
||||
|
||||
.search-container.expanded {
|
||||
max-height: 100px;
|
||||
padding: 0 var(--space-3) var(--space-3) var(--space-3);
|
||||
}
|
||||
|
||||
.filter-container.expanded {
|
||||
max-height: 500px;
|
||||
padding: 0 var(--space-3) var(--space-3) var(--space-3);
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
font-size: var(--text-sm);
|
||||
transition: all var(--transition-base);
|
||||
}
|
||||
|
||||
.search-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 2px var(--accent-alpha);
|
||||
}
|
||||
|
||||
.filter-options {
|
||||
display: flex;
|
||||
gap: var(--space-3);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.filter-group {
|
||||
@@ -47,6 +118,7 @@
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
min-width: 150px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
@@ -79,7 +151,7 @@
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.filter-bar {
|
||||
.filter-options {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@@ -917,37 +989,57 @@
|
||||
</div>
|
||||
|
||||
<div class="filter-bar">
|
||||
<div class="filter-group">
|
||||
<label class="filter-label">Category</label>
|
||||
<select id="categoryFilter" class="filter-select" onchange="applyFiltersAndSort()">
|
||||
<option value="all">All</option>
|
||||
<option value="work">Work</option>
|
||||
<option value="health">Health</option>
|
||||
<option value="growth">Growth</option>
|
||||
<option value="personal">Personal</option>
|
||||
</select>
|
||||
<!-- Collapsed toolbar with icons -->
|
||||
<div class="filter-toolbar">
|
||||
<button class="filter-icon-btn" id="searchToggle" title="Search habits">
|
||||
<i data-lucide="search"></i>
|
||||
</button>
|
||||
<button class="filter-icon-btn" id="filterToggle" title="Filter and sort">
|
||||
<i data-lucide="sliders"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label class="filter-label">Status</label>
|
||||
<select id="statusFilter" class="filter-select" onchange="applyFiltersAndSort()">
|
||||
<option value="all">All</option>
|
||||
<option value="active_today">Active Today</option>
|
||||
<option value="done_today">Done Today</option>
|
||||
<option value="overdue">Overdue</option>
|
||||
</select>
|
||||
<!-- Search container (collapsed by default) -->
|
||||
<div class="search-container" id="searchContainer">
|
||||
<input type="text" id="searchInput" class="search-input" placeholder="Search habits by name...">
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label class="filter-label">Sort By</label>
|
||||
<select id="sortSelect" class="filter-select" onchange="applyFiltersAndSort()">
|
||||
<option value="priority_asc">Priority (Low to High)</option>
|
||||
<option value="priority_desc">Priority (High to Low)</option>
|
||||
<option value="name_asc">Name A-Z</option>
|
||||
<option value="name_desc">Name Z-A</option>
|
||||
<option value="streak_desc">Streak (Highest)</option>
|
||||
<option value="streak_asc">Streak (Lowest)</option>
|
||||
</select>
|
||||
<!-- Filter options container (collapsed by default) -->
|
||||
<div class="filter-container" id="filterContainer">
|
||||
<div class="filter-options">
|
||||
<div class="filter-group">
|
||||
<label class="filter-label">Category</label>
|
||||
<select id="categoryFilter" class="filter-select" onchange="applyFiltersAndSort()">
|
||||
<option value="all">All</option>
|
||||
<option value="work">Work</option>
|
||||
<option value="health">Health</option>
|
||||
<option value="growth">Growth</option>
|
||||
<option value="personal">Personal</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label class="filter-label">Status</label>
|
||||
<select id="statusFilter" class="filter-select" onchange="applyFiltersAndSort()">
|
||||
<option value="all">All</option>
|
||||
<option value="active_today">Active Today</option>
|
||||
<option value="done_today">Done Today</option>
|
||||
<option value="overdue">Overdue</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label class="filter-label">Sort By</label>
|
||||
<select id="sortSelect" class="filter-select" onchange="applyFiltersAndSort()">
|
||||
<option value="priority_asc">Priority (Low to High)</option>
|
||||
<option value="priority_desc">Priority (High to Low)</option>
|
||||
<option value="name_asc">Name A-Z</option>
|
||||
<option value="name_desc">Name Z-A</option>
|
||||
<option value="streak_desc">Streak (Highest)</option>
|
||||
<option value="streak_asc">Streak (Lowest)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1173,6 +1265,81 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Search and filter collapse/expand
|
||||
function toggleSearch() {
|
||||
const searchContainer = document.getElementById('searchContainer');
|
||||
const searchToggle = document.getElementById('searchToggle');
|
||||
const filterContainer = document.getElementById('filterContainer');
|
||||
const filterToggle = document.getElementById('filterToggle');
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
|
||||
const isExpanded = searchContainer.classList.contains('expanded');
|
||||
|
||||
if (isExpanded) {
|
||||
// Collapse search
|
||||
searchContainer.classList.remove('expanded');
|
||||
searchToggle.classList.remove('active');
|
||||
} else {
|
||||
// Collapse filter first if open
|
||||
filterContainer.classList.remove('expanded');
|
||||
filterToggle.classList.remove('active');
|
||||
|
||||
// Expand search
|
||||
searchContainer.classList.add('expanded');
|
||||
searchToggle.classList.add('active');
|
||||
|
||||
// Focus input after animation
|
||||
setTimeout(() => searchInput.focus(), 300);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleFilters() {
|
||||
const filterContainer = document.getElementById('filterContainer');
|
||||
const filterToggle = document.getElementById('filterToggle');
|
||||
const searchContainer = document.getElementById('searchContainer');
|
||||
const searchToggle = document.getElementById('searchToggle');
|
||||
|
||||
const isExpanded = filterContainer.classList.contains('expanded');
|
||||
|
||||
if (isExpanded) {
|
||||
// Collapse filters
|
||||
filterContainer.classList.remove('expanded');
|
||||
filterToggle.classList.remove('active');
|
||||
} else {
|
||||
// Collapse search first if open
|
||||
searchContainer.classList.remove('expanded');
|
||||
searchToggle.classList.remove('active');
|
||||
|
||||
// Expand filters
|
||||
filterContainer.classList.add('expanded');
|
||||
filterToggle.classList.add('active');
|
||||
}
|
||||
}
|
||||
|
||||
function collapseAll() {
|
||||
const searchContainer = document.getElementById('searchContainer');
|
||||
const searchToggle = document.getElementById('searchToggle');
|
||||
const filterContainer = document.getElementById('filterContainer');
|
||||
const filterToggle = document.getElementById('filterToggle');
|
||||
|
||||
searchContainer.classList.remove('expanded');
|
||||
searchToggle.classList.remove('active');
|
||||
filterContainer.classList.remove('expanded');
|
||||
filterToggle.classList.remove('active');
|
||||
}
|
||||
|
||||
// Search habits by name
|
||||
function searchHabits(habits, query) {
|
||||
if (!query || query.trim() === '') {
|
||||
return habits;
|
||||
}
|
||||
|
||||
const lowerQuery = query.toLowerCase();
|
||||
return habits.filter(habit =>
|
||||
habit.name.toLowerCase().includes(lowerQuery)
|
||||
);
|
||||
}
|
||||
|
||||
// Apply filters and sort
|
||||
function applyFiltersAndSort() {
|
||||
const categoryFilter = document.getElementById('categoryFilter').value;
|
||||
@@ -1279,8 +1446,10 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply filters and sort
|
||||
let filteredHabits = filterHabits(habits);
|
||||
// Apply search, filters, and sort
|
||||
const searchQuery = document.getElementById('searchInput').value;
|
||||
let searchedHabits = searchHabits(habits, searchQuery);
|
||||
let filteredHabits = filterHabits(searchedHabits);
|
||||
let sortedHabits = sortHabits(filteredHabits);
|
||||
|
||||
if (sortedHabits.length === 0) {
|
||||
@@ -2251,6 +2420,38 @@
|
||||
// Initialize page
|
||||
lucide.createIcons();
|
||||
restoreFilters();
|
||||
|
||||
// Add event listeners for search/filter collapse
|
||||
document.getElementById('searchToggle').addEventListener('click', toggleSearch);
|
||||
document.getElementById('filterToggle').addEventListener('click', toggleFilters);
|
||||
|
||||
// Search input listener
|
||||
document.getElementById('searchInput').addEventListener('input', () => {
|
||||
renderHabits();
|
||||
});
|
||||
|
||||
// ESC key to collapse
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
collapseAll();
|
||||
}
|
||||
});
|
||||
|
||||
// Click outside to collapse
|
||||
document.addEventListener('click', (e) => {
|
||||
const filterBar = document.querySelector('.filter-bar');
|
||||
const searchContainer = document.getElementById('searchContainer');
|
||||
const filterContainer = document.getElementById('filterContainer');
|
||||
|
||||
// If click is outside filter bar and something is expanded
|
||||
if (!filterBar.contains(e.target)) {
|
||||
if (searchContainer.classList.contains('expanded') ||
|
||||
filterContainer.classList.contains('expanded')) {
|
||||
collapseAll();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
loadHabits();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user