9. Filter Modal
9.1 Advanced Multi-Select Filtering
Filter Modal Interaction Flow
Interaction Workflow:
- Open - User clicks "Filters" button → Modal slides in from right (300ms animation)
- Select - User checks/unchecks multiple filter options across 7 dimensions
- Logic - Toggle AND (all match) vs. OR (any match) for filter logic
- Preview - Real-time match count updates as filters change (debounced 200ms)
- Action - Clear All, Save View, Apply, or Close (✕)
- Result - Filtered data updates main view, URL params persist filters
Key Features:
- Multi-select - Checkboxes for simultaneous selection (Status, Projects, Priority)
- Single-select - Radio buttons for exclusive choice (Due Date)
- Search - Fuzzy search for Assignees
- Expandable - Collapsible categories (Projects grouped by type)
- Real-time - Live match count calculation
- Persistence - Save custom views to localStorage
- Accessibility - Full keyboard navigation, ARIA labels, focus trap
Filter Logic:
- AND - All selected filters must match (intersection)
- OR - Any selected filter matches (union)
- Mixed - Within each dimension uses OR, across dimensions uses selected logic
Full Specification:
┌────────────────────────────────────────────────────────────────┐
│ Advanced Filters [✕] Close │
├────────────────────────────────────────────────────────────────┤
│ │
│ 🏷️ STATUS │
│ ☑ Pending (115) ☑ In Progress (70) ☑ Complete (415) │
│ ☐ Paused (0) │
│ │
│ ────────────────────────────────────────────────────────── │
│ │
│ 📁 PROJECTS (8 categories, 46 total) │
│ ☑ All ☐ Core (3) ☐ Cloud (4) ☐ Dev (10) ☐ Frontend (8) │
│ ☐ Security (5) ☐ Testing (6) ☐ Docs (4) ☐ Tools (6) │
│ │
│ Category Expansion: │
│ ▼ Core (3 projects) │
│ ☑ coditect-core (530 tasks) │
│ ☐ coditect-core-framework (45 tasks) │
│ ☐ coditect-core-architecture (12 tasks) │
│ │
│ ────────────────────────────────────────────────────────── │
│ │
│ 📊 PHASE │
│ ☑ Phase 0 (350) ☑ Phase 0.5 (120) ☑ Phase 1 (530) │
│ ☐ Dashboard 2.0 (80) ☐ Phase 2 (0) ☐ Phase 3 (0) │
│ │
│ ────────────────────────────────────────────────────────── │
│ │
│ 🎯 PRIORITY │
│ ☑ P0 (23) ☑ P1 (156) ☐ P2 (198) ☐ P3 (153) │
│ │
│ ────────────────────────────────────────────────────────── │
│ │
│ 👤 ASSIGNEE │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 🔍 Search assignee... │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ☐ Hal Casteel (487 tasks) │
│ ☐ Unassigned (45 tasks) │
│ │
│ ────────────────────────────────────────────────────────── │
│ │
│ 📅 DUE DATE │
│ ○ All │
│ ○ Overdue (12 tasks) │
│ ○ This Week (45 tasks) │
│ ○ This Month (89 tasks) │
│ ○ Custom Range: [Start Date ▼] → [End Date ▼] │
│ │
│ ────────────────────────────────────────────────────────── │
│ │
│ 🔗 LINKED TO │
│ ☐ Has Checkpoint (421 tasks) │
│ ☐ Has Commits (312 tasks) │
│ ☐ Has Dependencies (89 tasks) │
│ ☐ Has Blockers (12 tasks) │
│ │
│ ────────────────────────────────────────────────────────── │
│ │
│ ⚙️ FILTER LOGIC │
│ ○ AND (all conditions must match) 145 tasks match │
│ ○ OR (any condition matches) 530 tasks match │
│ │
├────────────────────────────────────────────────────────────────┤
│ Matching: 145 of 530 tasks │
│ [Clear All] [Save as View ▼] [Apply Filters] │
└────────────────────────────────────────────────────────────────┘
9.2 Implementation Prompt - Filter Modal
Prompt for AI/Developer:
Create an advanced filter modal with multi-select checkboxes and AND/OR logic.
MODAL STRUCTURE:
HTML:
<div class="modal-overlay" role="dialog" aria-labelledby="filter-modal-title" aria-modal="true">
<div class="modal-container filter-modal">
<header class="modal-header">
<h2 id="filter-modal-title">Advanced Filters</h2>
<button class="close-btn" aria-label="Close filters">✕</button>
</header>
<div class="modal-body">
<!-- Filter sections -->
</div>
<footer class="modal-footer">
<div class="match-count">Matching: <strong>145 of 530</strong> tasks</div>
<button class="btn-secondary">Clear All</button>
<button class="btn-secondary dropdown-trigger">
Save as View ▼
</button>
<button class="btn-primary">Apply Filters</button>
</footer>
</div>
</div>
CSS:
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
animation: fadeIn 0.2s ease;
}
.filter-modal {
background: var(--white);
border-radius: 8px;
max-width: 700px;
width: 90%;
max-height: 90vh;
display: flex;
flex-direction: column;
box-shadow: 0 10px 40px rgba(0,0,0,0.3);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24px;
border-bottom: 1px solid var(--gray-300);
}
.modal-body {
flex: 1;
overflow-y: auto;
padding: 24px;
}
.modal-footer {
display: flex;
align-items: center;
gap: 12px;
padding: 16px 24px;
border-top: 1px solid var(--gray-300);
background: var(--gray-50);
}
.match-count {
flex: 1;
font-size: 14px;
color: var(--gray-700);
}
FILTER SECTIONS:
Each section follows this pattern:
<div class="filter-section">
<h3 class="filter-heading">
<span class="icon">🏷️</span>
STATUS
</h3>
<div class="filter-options">
<label class="checkbox-label">
<input type="checkbox" checked />
<span>Pending</span>
<span class="count">(115)</span>
</label>
<label class="checkbox-label">
<input type="checkbox" checked />
<span>In Progress</span>
<span class="count">(70)</span>
</label>
<!-- More options -->
</div>
</div>
CSS:
.filter-section {
margin-bottom: 32px;
}
.filter-section:not(:last-child) {
padding-bottom: 24px;
border-bottom: 1px dashed var(--gray-300);
}
.filter-heading {
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--gray-700);
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 8px;
}
.filter-options {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.checkbox-label {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: var(--gray-100);
border: 1px solid var(--gray-300);
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.2s;
user-select: none;
}
.checkbox-label:hover {
background: var(--gray-200);
border-color: var(--gray-500);
}
.checkbox-label input:checked + span {
font-weight: 600;
}
.checkbox-label input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
}
.count {
color: var(--gray-500);
font-size: 12px;
}
CATEGORY EXPANSION (Projects):
Collapsible category with nested project checkboxes:
<div class="category-toggle">
<button class="expand-btn" aria-expanded="true">
<span class="chevron">▼</span>
Core (3 projects)
</button>
</div>
<div class="category-projects">
<label class="checkbox-label nested">
<input type="checkbox" checked />
<span>coditect-core</span>
<span class="count">(530 tasks)</span>
</label>
<!-- More projects -->
</div>
CSS:
.expand-btn {
width: 100%;
text-align: left;
background: transparent;
border: none;
padding: 8px 0;
font-size: 14px;
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
}
.chevron {
transition: transform 0.2s;
}
.expand-btn[aria-expanded="false"] .chevron {
transform: rotate(-90deg);
}
.category-projects {
margin-left: 24px;
margin-top: 8px;
display: flex;
flex-direction: column;
gap: 8px;
}
.checkbox-label.nested {
background: transparent;
border: 1px solid transparent;
padding-left: 0;
}
.checkbox-label.nested:hover {
background: var(--gray-100);
border-color: var(--gray-300);
}
ASSIGNEE SEARCH:
Text input with autocomplete:
<div class="search-input-container">
<input
type="text"
placeholder="Search assignee..."
class="search-input"
aria-label="Search assignees"
/>
<span class="search-icon">🔍</span>
</div>
<div class="search-results">
<!-- Filtered assignee checkboxes -->
</div>
Behavior:
- Type to filter: Filter assignee list on keyup (instant)
- Fuzzy match: Match substring anywhere in name
- Empty state: "No assignees found" if no matches
DUE DATE (Radio Buttons):
Single-select radio group:
<div class="radio-group">
<label class="radio-label">
<input type="radio" name="due-date" value="all" checked />
<span>All</span>
</label>
<label class="radio-label">
<input type="radio" name="due-date" value="overdue" />
<span>Overdue</span>
<span class="count">(12 tasks)</span>
</label>
<!-- More options -->
</div>
Custom Date Range (shows when "Custom Range" selected):
<div class="date-range-inputs">
<input type="date" aria-label="Start date" />
<span>→</span>
<input type="date" aria-label="End date" />
</div>
FILTER LOGIC TOGGLE:
Radio buttons for AND/OR logic:
<div class="logic-toggle">
<label class="radio-label">
<input type="radio" name="logic" value="and" checked />
<span>AND (all conditions must match)</span>
<span class="match-preview">145 tasks match</span>
</label>
<label class="radio-label">
<input type="radio" name="logic" value="or" />
<span>OR (any condition matches)</span>
<span class="match-preview">530 tasks match</span>
</label>
</div>
Behavior:
- Change: Recalculate matching task count
- Update: Update footer match count in real-time
REAL-TIME MATCH COUNT:
Calculate on every filter change:
- Get all selected filters
- Apply logic (AND/OR)
- Count matching tasks
- Update footer: "Matching: X of 530 tasks"
- Debounce: Wait 200ms after last change before calculating
SAVE AS VIEW:
Dropdown with preset options + custom:
<div class="save-view-dropdown">
<button class="dropdown-item">Save as "My Active Tasks"</button>
<button class="dropdown-item">Save as "P0 Blockers"</button>
<button class="dropdown-item">+ New Custom View</button>
</div>
Behavior:
- Click preset: Save current filters with that name
- Click custom: Show input dialog for custom name
- Storage: Save to localStorage as JSON
- Load: Show saved views in top bar filter dropdown
INTERACTIONS:
Open:
- Click "🎛️ Filters" button in top bar
- Keyboard: Shift+F shortcut
Close:
- Click [✕] button
- Click outside modal (on overlay)
- Press Esc key
- Click [Apply Filters] (auto-close)
Apply:
- Click [Apply Filters]: Close modal, apply filters to main view
- Updates: URL query params (e.g., ?status=pending,in_progress&priority=p0,p1)
- Persistence: Save active filters in localStorage
Clear All:
- Click [Clear All]: Uncheck all checkboxes, reset to defaults
- Confirmation: Show "Are you sure?" if >5 filters active
KEYBOARD NAVIGATION:
- Tab: Navigate through filter options
- Space: Toggle checkbox/radio
- Enter: Apply filters
- Esc: Close modal
- Arrow keys: Navigate within radio groups
ACCESSIBILITY:
- Focus trap: Keep focus inside modal when open
- Focus restoration: Return focus to trigger button on close
- ARIA: aria-modal="true", aria-labelledby for title
- Screen reader: Announce match count changes
DATA SOURCE:
- Fetch from data/tasks.json
- Calculate counts for each filter option
- Real-time filtering using client-side logic
PERFORMANCE:
- Debounce: Debounce match count calculations (200ms)
- Virtualize: If >100 projects, virtualize project list
- Cache: Cache filter results for faster recalculation