Skip to content

Commit 6e3f480

Browse files
committed
Improve documentation discovery with visual categorisation and filtering
Added icons, badges, and client-side filtering to documentation index pages to help visitors quickly find relevant guides as the guide count grows. - Filter buttons (All, Proxy, Filters, Kubernetes, Developer) with counts - Category icons in card headers using Bootstrap Icons (cpu, puzzle, boxes, code-slash, shield-lock) - Visible tag badges at bottom of cards - Subtle category-specific border colours on cards - Vanilla JavaScript for client-side filtering with progressive enhancement - Cards reflow properly when filtered (no gaps in grid layout) Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent f87a609 commit 6e3f480

3 files changed

Lines changed: 173 additions & 4 deletions

File tree

_layouts/released-documentation.html

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,26 @@ <h1 id="page-title" class="fs-3">{{ version }} Documentation</h1>
3535
</div>
3636
</div>
3737
<div class="col-lg-6" role="main">
38-
<div class="row row-cols-1 row-cols-md-2 g-4">
3938
{%- assign docs_for_release = site.data.documentation[underscored_version].docs | sort: "rank" -%}
39+
<!-- Documentation filters -->
40+
<div class="doc-filters mb-4" role="toolbar" aria-label="Documentation filters">
41+
<button class="btn btn-sm btn-outline-secondary active" data-filter="all">
42+
All <span class="badge">{{ docs_for_release | size }}</span>
43+
</button>
44+
<button class="btn btn-sm btn-outline-secondary" data-filter="proxy">
45+
<i class="bi bi-cpu"></i> Proxy
46+
</button>
47+
<button class="btn btn-sm btn-outline-secondary" data-filter="filter">
48+
<i class="bi bi-puzzle"></i> Filters
49+
</button>
50+
<button class="btn btn-sm btn-outline-secondary" data-filter="kubernetes">
51+
<i class="bi bi-boxes"></i> Kubernetes
52+
</button>
53+
<button class="btn btn-sm btn-outline-secondary" data-filter="developer">
54+
<i class="bi bi-code-slash"></i> Developer
55+
</button>
56+
</div>
57+
<div class="row row-cols-1 row-cols-md-2 g-4">
4058
{%- for doc in docs_for_release -%}
4159
{%- assign first1 = doc.path | slice: 0, 1 -%}
4260
{%- assign first7 = doc.path | slice: 0, 7 -%}
@@ -49,12 +67,21 @@ <h1 id="page-title" class="fs-3">{{ version }} Documentation</h1>
4967
{%- assign linkTemplate = "/documentation/" | append: version | append: "/" | append: doc.path | absolute_url -%}
5068
{%- endif -%}
5169
<div class="col">
52-
<div class="card shadow mb-2 h-100 mx-2 {%- for tag in doc.tags %} doctag-{{tag}}{%- endfor -%}">
53-
<div class="card-header">
54-
<h2 class="card-title fs-4"><a href='{{ linkTemplate | replace: "$(VERSION)", version}}'>{{ doc.title }}</a></h2>
70+
<div class="card shadow mb-2 h-100 mx-2 doc-card{%- for tag in doc.tags %} doctag-{{tag}}{%- endfor -%}" data-categories="{{ doc.tags | join: ' ' }}">
71+
<div class="card-header d-flex align-items-center justify-content-between">
72+
<h2 class="card-title fs-4 mb-0 flex-grow-1">
73+
<a href='{{ linkTemplate | replace: "$(VERSION)", version}}'>{{ doc.title }}</a>
74+
</h2>
75+
{%- assign primary_tag = doc.tags[0] -%}
76+
<i class="bi bi-{% if primary_tag == 'proxy' %}cpu{% elsif primary_tag == 'filter' %}puzzle{% elsif primary_tag == 'kubernetes' %}boxes{% elsif primary_tag == 'developer' %}code-slash{% elsif primary_tag == 'record-encryption' or primary_tag == 'encryption-at-rest' %}shield-lock{% else %}file-text{% endif %} text-secondary ms-2" aria-hidden="true"></i>
5577
</div>
5678
<div class="card-body mx-3 my-2">
5779
{{ doc.description }}
80+
<div class="mt-2">
81+
{%- for tag in doc.tags -%}
82+
<span class="badge bg-light text-dark border me-1">{{ tag }}</span>
83+
{%- endfor -%}
84+
</div>
5885
</div>
5986
</div>
6087
</div>
@@ -63,3 +90,4 @@ <h2 class="card-title fs-4"><a href='{{ linkTemplate | replace: "$(VERSION)", ve
6390
</div>
6491
</div>
6592

93+
<script src="{{ '/assets/scripts/doc-filter.js' | absolute_url }}"></script>

_sass/kroxylicious.scss

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,3 +354,79 @@ b.conum * {
354354
margin-top: calc(var(--#{$prefix}card-title-spacer-y) / 2);
355355
margin-bottom: calc(var(--#{$prefix}card-title-spacer-y) / 2);
356356
}
357+
358+
// Documentation page filtering
359+
.doc-filters {
360+
display: flex;
361+
gap: 0.5rem;
362+
flex-wrap: wrap;
363+
padding: 1rem;
364+
background-color: rgba($kroxy-light, 0.3);
365+
border-radius: 0.375rem;
366+
367+
.btn {
368+
transition: all 0.2s ease-in-out;
369+
370+
&.active {
371+
background-color: $kroxy-dark-green;
372+
color: $white;
373+
border-color: $kroxy-dark-green;
374+
}
375+
376+
&:hover:not(.active) {
377+
background-color: rgba($kroxy-light-green, 0.1);
378+
}
379+
380+
.bi {
381+
font-size: 0.9rem;
382+
}
383+
384+
.badge {
385+
background-color: rgba($white, 0.3);
386+
color: currentColor;
387+
font-weight: normal;
388+
margin-left: 0.25rem;
389+
}
390+
}
391+
}
392+
393+
.col.hidden {
394+
display: none;
395+
}
396+
397+
.doc-card {
398+
transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out;
399+
400+
.card-header {
401+
border-bottom: 3px solid transparent;
402+
}
403+
404+
// Visual category hints
405+
&.doctag-proxy .card-header {
406+
border-bottom-color: rgba($kroxy-light-green, 0.3);
407+
}
408+
409+
&.doctag-filter .card-header {
410+
border-bottom-color: rgba($kroxy-mid-green, 0.3);
411+
}
412+
413+
&.doctag-kubernetes .card-header {
414+
border-bottom-color: rgba($kroxy-dark-green, 0.3);
415+
}
416+
417+
&.doctag-developer .card-header {
418+
border-bottom-color: rgba($code-color, 0.3);
419+
}
420+
421+
.badge {
422+
font-size: 0.7rem;
423+
padding: 0.25rem 0.5rem;
424+
text-transform: lowercase;
425+
}
426+
427+
// Icon in card header
428+
.card-header .bi {
429+
font-size: 1.5rem;
430+
opacity: 0.4;
431+
}
432+
}

assets/scripts/doc-filter.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* Documentation page filter functionality
3+
* Provides client-side filtering of documentation cards by category
4+
*/
5+
(function() {
6+
'use strict';
7+
8+
// Wait for DOM to be ready
9+
if (document.readyState === 'loading') {
10+
document.addEventListener('DOMContentLoaded', init);
11+
} else {
12+
init();
13+
}
14+
15+
function init() {
16+
const filters = document.querySelectorAll('.doc-filters button[data-filter]');
17+
const cards = document.querySelectorAll('.doc-card');
18+
19+
if (filters.length === 0 || cards.length === 0) {
20+
return; // Not on a documentation page
21+
}
22+
23+
filters.forEach(button => {
24+
button.addEventListener('click', function() {
25+
const filter = this.getAttribute('data-filter');
26+
applyFilter(filter, filters, cards);
27+
});
28+
});
29+
}
30+
31+
function applyFilter(filter, filterButtons, cards) {
32+
// Update active button
33+
filterButtons.forEach(btn => {
34+
if (btn.getAttribute('data-filter') === filter) {
35+
btn.classList.add('active');
36+
} else {
37+
btn.classList.remove('active');
38+
}
39+
});
40+
41+
// Filter cards
42+
let visibleCount = 0;
43+
cards.forEach(card => {
44+
const categories = card.getAttribute('data-categories') || '';
45+
const shouldShow = filter === 'all' || categories.includes(filter);
46+
const colWrapper = card.closest('.col');
47+
48+
if (shouldShow) {
49+
if (colWrapper) colWrapper.classList.remove('hidden');
50+
visibleCount++;
51+
} else {
52+
if (colWrapper) colWrapper.classList.add('hidden');
53+
}
54+
});
55+
56+
// Update badge count on "All" button
57+
const allButton = document.querySelector('.doc-filters button[data-filter="all"]');
58+
if (allButton) {
59+
const badge = allButton.querySelector('.badge');
60+
if (badge && filter !== 'all') {
61+
badge.textContent = visibleCount;
62+
}
63+
}
64+
}
65+
})();

0 commit comments

Comments
 (0)