Skip to content

Commit ed5c248

Browse files
authored
Merge pull request #122 from javaevolved/copilot/add-filtering-by-jdk-version
Add JDK and Category filters as compact dropdowns to homepage
2 parents 4192edd + 2ced987 commit ed5c248

File tree

5 files changed

+221
-95
lines changed

5 files changed

+221
-95
lines changed

site/app.js

Lines changed: 103 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -199,62 +199,126 @@
199199
};
200200

201201
/* ==========================================================
202-
2. Category Filter Pills (homepage)
202+
2. Category + JDK Dropdown Filters (homepage)
203203
========================================================== */
204204
const initFilters = () => {
205-
const pills = document.querySelectorAll('.filter-pill');
206205
const cards = document.querySelectorAll('.tip-card');
207-
if (!pills.length || !cards.length) return;
206+
if (!cards.length) return;
208207

209-
pills.forEach(pill => {
210-
pill.addEventListener('click', () => {
211-
const category = pill.dataset.filter || 'all';
212-
const wasActive = pill.classList.contains('active');
208+
let activeCategory = null;
209+
let activeJdk = null;
213210

214-
// Update active pill (toggle off if re-clicked)
215-
pills.forEach(p => p.classList.remove('active'));
216-
if (!wasActive) pill.classList.add('active');
211+
// LTS cycle ranges: each entry covers all versions introduced since the previous LTS
212+
const LTS_RANGES = {
213+
'11': [9, 11],
214+
'17': [12, 17],
215+
'21': [18, 21],
216+
'25': [22, 25]
217+
};
217218

218-
const showAll = (!wasActive && category === 'all');
219-
const showCategory = (!wasActive && category !== 'all') ? category : null;
219+
const applyFilters = () => {
220+
cards.forEach(card => {
221+
const matchesCategory = !activeCategory || card.dataset.category === activeCategory;
222+
let matchesJdk = true;
223+
if (activeJdk) {
224+
const version = parseInt(card.dataset.jdk, 10);
225+
const range = LTS_RANGES[activeJdk];
226+
matchesJdk = range && version >= range[0] && version <= range[1];
227+
}
228+
card.classList.toggle('filter-hidden', !(matchesCategory && matchesJdk));
229+
});
220230

221-
// Filter cards
222-
cards.forEach(card => {
223-
if (showAll || card.dataset.category === showCategory) {
224-
card.classList.remove('filter-hidden');
225-
} else {
226-
card.classList.add('filter-hidden');
227-
}
231+
if (window.updateViewToggleState) {
232+
window.updateViewToggleState();
233+
}
234+
};
235+
236+
// Generic helper to wire up a dropdown
237+
const initDropdown = (dropdownEl, onSelect) => {
238+
if (!dropdownEl) return;
239+
const toggleBtn = dropdownEl.querySelector('.jdk-dropdown-toggle');
240+
const labelEl = dropdownEl.querySelector('.jdk-label');
241+
const list = dropdownEl.querySelector('ul');
242+
243+
const openDropdown = () => {
244+
list.style.display = 'block';
245+
toggleBtn.setAttribute('aria-expanded', 'true');
246+
};
247+
248+
const closeDropdown = () => {
249+
list.style.display = 'none';
250+
toggleBtn.setAttribute('aria-expanded', 'false');
251+
};
252+
253+
const selectItem = (li) => {
254+
list.querySelectorAll('li').forEach(l => l.classList.remove('active'));
255+
li.classList.add('active');
256+
if (labelEl) labelEl.textContent = li.textContent.trim();
257+
};
258+
259+
toggleBtn.addEventListener('click', (e) => {
260+
e.stopPropagation();
261+
list.style.display === 'block' ? closeDropdown() : openDropdown();
262+
});
263+
264+
document.addEventListener('click', closeDropdown);
265+
document.addEventListener('keydown', (e) => {
266+
if (e.key === 'Escape') closeDropdown();
267+
});
268+
269+
list.querySelectorAll('li').forEach(li => {
270+
li.addEventListener('click', (e) => {
271+
e.stopPropagation();
272+
closeDropdown();
273+
selectItem(li);
274+
onSelect(li, toggleBtn);
228275
});
276+
});
229277

230-
// Update URL hash to reflect active filter
231-
const activeFilter = pill.classList.contains('active') ? category : null;
232-
if (activeFilter && activeFilter !== 'all') {
233-
history.replaceState(null, '', '#' + activeFilter);
234-
} else {
235-
history.replaceState(null, '', window.location.pathname + window.location.search);
278+
return { closeDropdown, setActive: (value) => {
279+
const target = list.querySelector(`li[data-filter="${value}"]`);
280+
if (target) {
281+
selectItem(target);
282+
toggleBtn.classList.toggle('has-filter', value !== 'all');
236283
}
284+
}};
285+
};
237286

238-
// Update view toggle button state
239-
if (window.updateViewToggleState) {
240-
window.updateViewToggleState();
241-
}
242-
});
287+
// Category dropdown
288+
const categoryDropdown = document.getElementById('categoryDropdown');
289+
const catDropdownCtrl = initDropdown(categoryDropdown, (li, toggleBtn) => {
290+
const category = li.dataset.filter;
291+
activeCategory = category !== 'all' ? category : null;
292+
toggleBtn.classList.toggle('has-filter', !!activeCategory);
293+
if (activeCategory) {
294+
history.replaceState(null, '', '#' + activeCategory);
295+
} else {
296+
history.replaceState(null, '', window.location.pathname + window.location.search);
297+
}
298+
applyFilters();
299+
});
300+
301+
// JDK dropdown
302+
const jdkDropdown = document.getElementById('jdkDropdown');
303+
initDropdown(jdkDropdown, (li, toggleBtn) => {
304+
const version = li.dataset.jdkFilter;
305+
activeJdk = version !== 'all' ? version : null;
306+
toggleBtn.classList.toggle('has-filter', !!activeJdk);
307+
applyFilters();
243308
});
244309

245310
// Apply filter from a given category string (or "all" / empty for no filter)
246311
const applyHashFilter = (category) => {
247-
const target = category
248-
? document.querySelector(`.filter-pill[data-filter="${category}"]`)
249-
: null;
250-
if (target) {
251-
target.click();
252-
// Scroll the filter section into view
312+
if (category && catDropdownCtrl) {
313+
catDropdownCtrl.setActive(category);
314+
activeCategory = category;
315+
applyFilters();
253316
const section = document.getElementById('all-comparisons');
254317
if (section) section.scrollIntoView({ behavior: 'smooth' });
255-
} else {
256-
const allButton = document.querySelector('.filter-pill[data-filter="all"]');
257-
if (allButton) allButton.click();
318+
} else if (catDropdownCtrl) {
319+
catDropdownCtrl.setActive('all');
320+
activeCategory = null;
321+
applyFilters();
258322
}
259323
};
260324

site/styles.css

Lines changed: 84 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -406,46 +406,6 @@ nav {
406406
margin-bottom: 16px;
407407
}
408408

409-
/* ---------- Filter Pills ---------- */
410-
.filter-pills {
411-
display: flex;
412-
flex-wrap: wrap;
413-
gap: 8px;
414-
justify-content: center;
415-
align-items: center;
416-
margin-bottom: 40px;
417-
}
418-
419-
.filter-label {
420-
font-size: 0.82rem;
421-
font-weight: 500;
422-
color: var(--text-muted);
423-
padding: 7px 8px 7px 0;
424-
}
425-
426-
.filter-pill {
427-
padding: 7px 16px;
428-
border-radius: 999px;
429-
font-size: 0.82rem;
430-
font-weight: 500;
431-
border: 1px solid var(--border);
432-
background: var(--surface);
433-
color: var(--text-muted);
434-
cursor: pointer;
435-
transition: all 0.2s;
436-
}
437-
438-
.filter-pill:hover {
439-
border-color: var(--border-light);
440-
color: var(--text);
441-
}
442-
443-
.filter-pill.active {
444-
background: var(--accent);
445-
border-color: var(--accent);
446-
color: #fff;
447-
}
448-
449409
/* ---------- Tips / Card Grid ---------- */
450410
.tips-grid {
451411
display: grid;
@@ -667,9 +627,93 @@ nav {
667627
.view-toggle-wrap {
668628
display: flex;
669629
justify-content: center;
630+
align-items: center;
631+
gap: 10px;
670632
margin: 16px 0;
671633
}
672634

635+
/* ---------- JDK Dropdown ---------- */
636+
.jdk-dropdown {
637+
position: relative;
638+
display: inline-flex;
639+
}
640+
641+
.jdk-dropdown-toggle {
642+
display: inline-flex;
643+
align-items: center;
644+
gap: 6px;
645+
padding: 10px 16px;
646+
font-size: 0.88rem;
647+
font-weight: 500;
648+
color: var(--text);
649+
background: var(--surface);
650+
border: 1px solid var(--border);
651+
border-radius: var(--radius-sm);
652+
cursor: pointer;
653+
transition: all 0.25s;
654+
white-space: nowrap;
655+
}
656+
657+
.jdk-dropdown-toggle:hover {
658+
background: var(--surface-2);
659+
border-color: var(--border-light);
660+
transform: translateY(-2px);
661+
}
662+
663+
.jdk-dropdown-toggle.has-filter {
664+
border-color: var(--accent);
665+
color: var(--accent);
666+
}
667+
668+
.jdk-label {
669+
font-weight: 600;
670+
}
671+
672+
.dropdown-caret {
673+
font-size: 0.75rem;
674+
opacity: 0.6;
675+
}
676+
677+
.jdk-dropdown ul {
678+
display: none;
679+
position: absolute;
680+
bottom: calc(100% + 6px);
681+
left: 50%;
682+
transform: translateX(-50%);
683+
margin: 0;
684+
padding: 4px 0;
685+
list-style: none;
686+
background: var(--surface);
687+
border: 1px solid var(--border);
688+
border-radius: var(--radius-sm);
689+
box-shadow: 0 4px 16px rgba(0,0,0,0.15);
690+
z-index: 100;
691+
min-width: 130px;
692+
}
693+
694+
.jdk-dropdown li {
695+
padding: 8px 16px;
696+
cursor: pointer;
697+
font-size: 0.88rem;
698+
color: var(--text);
699+
white-space: nowrap;
700+
transition: background 0.15s;
701+
}
702+
703+
.jdk-dropdown li:hover {
704+
background: var(--accent);
705+
color: #fff;
706+
}
707+
708+
.jdk-dropdown li.active {
709+
font-weight: 600;
710+
color: var(--accent);
711+
}
712+
713+
.jdk-dropdown li.active:hover {
714+
color: #fff;
715+
}
716+
673717
.view-toggle-btn {
674718
display: inline-flex;
675719
align-items: center;

templates/index-card.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<a href="{{cardHref}}" class="tip-card filter-hidden" data-category="{{category}}">
1+
<a href="{{cardHref}}" class="tip-card filter-hidden" data-category="{{category}}" data-jdk="{{jdkVersion}}">
22
<div class="tip-card-body">
33
<div class="tip-card-header">
44
<div class="tip-badges"><span class="badge {{category}}">{{catDisplay}}</span></div>

templates/index.html

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -233,22 +233,38 @@ <h1>{{site.tagline_line1}}<br><span class="gradient">{{site.tagline_line2}}</spa
233233
<h2 class="section-title">{{site.allComparisons}}</h2>
234234
<span class="section-badge">{{site.snippetsBadge}}</span>
235235
</div>
236-
<div class="filter-pills" id="categoryFilter">
237-
<span class="filter-label">{{filters.show}}</span>
238-
<button class="filter-pill" data-filter="all">{{filters.all}}</button>
239-
<button class="filter-pill" data-filter="language">Language</button>
240-
<button class="filter-pill" data-filter="collections">Collections</button>
241-
<button class="filter-pill" data-filter="strings">Strings</button>
242-
<button class="filter-pill" data-filter="streams">Streams</button>
243-
<button class="filter-pill" data-filter="concurrency">Concurrency</button>
244-
<button class="filter-pill" data-filter="io">I/O</button>
245-
<button class="filter-pill" data-filter="errors">Errors</button>
246-
<button class="filter-pill" data-filter="datetime">Date/Time</button>
247-
<button class="filter-pill" data-filter="security">Security</button>
248-
<button class="filter-pill" data-filter="tooling">Tooling</button>
249-
<button class="filter-pill" data-filter="enterprise">Enterprise</button>
250-
</div>
251236
<div class="view-toggle-wrap">
237+
<div class="jdk-dropdown" id="categoryDropdown">
238+
<button class="jdk-dropdown-toggle" aria-haspopup="listbox" aria-expanded="false">
239+
{{filters.category}} <span class="jdk-label">{{filters.all}}</span> <span class="dropdown-caret"></span>
240+
</button>
241+
<ul role="listbox">
242+
<li data-filter="all" class="active">{{filters.all}}</li>
243+
<li data-filter="language">Language</li>
244+
<li data-filter="collections">Collections</li>
245+
<li data-filter="strings">Strings</li>
246+
<li data-filter="streams">Streams</li>
247+
<li data-filter="concurrency">Concurrency</li>
248+
<li data-filter="io">I/O</li>
249+
<li data-filter="errors">Errors</li>
250+
<li data-filter="datetime">Date/Time</li>
251+
<li data-filter="security">Security</li>
252+
<li data-filter="tooling">Tooling</li>
253+
<li data-filter="enterprise">Enterprise</li>
254+
</ul>
255+
</div>
256+
<div class="jdk-dropdown" id="jdkDropdown">
257+
<button class="jdk-dropdown-toggle" aria-haspopup="listbox" aria-expanded="false">
258+
{{filters.jdk}} <span class="jdk-label">{{filters.all}}</span> <span class="dropdown-caret"></span>
259+
</button>
260+
<ul role="listbox">
261+
<li data-jdk-filter="all" class="active">{{filters.all}}</li>
262+
<li data-jdk-filter="11">Java 11</li>
263+
<li data-jdk-filter="17">Java 17</li>
264+
<li data-jdk-filter="21">Java 21</li>
265+
<li data-jdk-filter="25">Java 25</li>
266+
</ul>
267+
</div>
252268
<button class="view-toggle-btn" id="viewToggle" aria-label="Toggle view mode">
253269
<span class="view-toggle-icon"></span>
254270
<span class="view-toggle-text">{{view.expandAll}}</span>

translations/strings/en.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ sections:
3333
filters:
3434
show: 'Show:'
3535
all: All
36+
jdk: 'JDK:'
37+
category: 'Category:'
3638
difficulty:
3739
beginner: Beginner
3840
intermediate: Intermediate

0 commit comments

Comments
 (0)