labs / static /js /faq.js
3v324v23's picture
deploy: unified router + dreamy website (2026-06-16T09:46:52Z)
c1a683f
Raw
History Blame Contribute Delete
3.95 kB
/* =========================================================================
faq.js — search, category filter, accordion for the dreamy help center
• Search: case-insensitive, matches question OR answer text, real-time.
• Categories: pill filters via data-category; "All" resets.
• Accordion: single-open (opening one closes others), ARIA-aware,
keyboard accessible (buttons are real <button>s).
• Honours prefers-reduced-motion (CSS handles the transition skip).
========================================================================= */
(function () {
'use strict';
var list = document.getElementById('faq-list'); // accordion list
var search = document.getElementById('faq-search'); // search input
var cats = Array.from(document.querySelectorAll('.faq-cat')); // pill buttons
var empty = document.getElementById('faq-empty'); // no-results state
var count = document.getElementById('faq-count'); // optional count text
if (!list) return; // not on FAQ page
var items = Array.from(list.querySelectorAll('.faq-item')); // all Q/A cards
var currentCat = 'all'; // active filter
/* ---- accordion: toggle one, close the rest --------------------------- */
function toggle(item) {
var isOpen = item.getAttribute('data-open') === 'true';
// single-open behaviour: close everything first
items.forEach(function (it) {
it.setAttribute('data-open', 'false');
var btn = it.querySelector('.faq-q');
if (btn) btn.setAttribute('aria-expanded', 'false');
});
// if it was closed, open it now
if (!isOpen) {
item.setAttribute('data-open', 'true');
var b = item.querySelector('.faq-q');
if (b) b.setAttribute('aria-expanded', 'true');
}
}
// wire each question button
items.forEach(function (item) {
var btn = item.querySelector('.faq-q');
if (btn) btn.addEventListener('click', function () { toggle(item); });
});
/* ---- apply current search + category, update visibility -------------- */
function applyFilter() {
var q = (search.value || '').trim().toLowerCase(); // search term
var shown = 0;
items.forEach(function (item) {
var cat = item.getAttribute('data-category'); // item's category
var text = (item.textContent || '').toLowerCase(); // q + a combined
var matchCat = currentCat === 'all' || cat === currentCat;
var matchSearch = !q || text.indexOf(q) !== -1; // case-insensitive
var visible = matchCat && matchSearch;
item.setAttribute('data-hidden', visible ? 'false' : 'true');
if (visible) shown++;
});
// show / hide the empty state
if (empty) empty.style.display = shown === 0 ? 'block' : 'none';
if (count) count.textContent = shown; // update live count
}
/* ---- search input (real-time, debounced lightly) --------------------- */
var t;
search.addEventListener('input', function () {
clearTimeout(t);
t = setTimeout(applyFilter, 80); // tiny debounce
});
/* ---- category pills -------------------------------------------------- */
cats.forEach(function (pill) {
pill.addEventListener('click', function () {
currentCat = pill.getAttribute('data-cat'); // set active cat
cats.forEach(function (p) {
p.setAttribute('aria-pressed', p === pill ? 'true' : 'false');
});
applyFilter();
});
});
/* ---- keyboard: "/" focuses search, Escape clears --------------------- */
document.addEventListener('keydown', function (e) {
if (e.key === '/' && document.activeElement !== search) {
e.preventDefault();
search.focus();
} else if (e.key === 'Escape' && document.activeElement === search) {
search.value = '';
applyFilter();
search.blur();
}
});
// initial paint
applyFilter();
})();