Widget — Global filter + date-range bar
A sticky global filter bar for analytics dashboards, built for the fictional Northwind Pulse commerce workspace. It pairs a Today/7d/30d/Custom date-range control with multi-select dropdowns for channel, region and status, plus a debounced search box. Every choice renders as a removable colour-coded chip with a live active-filters count and clear-all. Applying filters re-renders four KPI cards with deltas and inline-SVG sparklines, an animated orders-by-day bar chart and a results table — all vanilla JS, no libraries.
MCP
Code
:root {
--brand: #5b5bf0;
--brand-d: #4646d6;
--brand-700: #3a3ab8;
--brand-50: #eef0ff;
--accent: #00b4a6;
--accent-soft: #d8f5f2;
--ink: #101322;
--ink-2: #3a4060;
--muted: #6c7393;
--bg: #f6f7fb;
--white: #ffffff;
--surface: #ffffff;
--line: rgba(16, 19, 34, 0.1);
--line-2: rgba(16, 19, 34, 0.16);
--ok: #2f9e6f;
--warn: #d98a2b;
--danger: #d4503e;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 20px;
--sh-1: 0 1px 2px rgba(16, 19, 34, 0.08);
--sh-2: 0 8px 24px rgba(16, 19, 34, 0.08);
--sidebar-w: 232px;
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
font-family: "Inter", system-ui, -apple-system, sans-serif;
font-size: 14px;
line-height: 1.5;
color: var(--ink);
background: var(--bg);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h1, h2, p { margin: 0; }
a { color: inherit; text-decoration: none; }
button { font-family: inherit; cursor: pointer; }
:focus-visible {
outline: 2px solid var(--brand);
outline-offset: 2px;
border-radius: 6px;
}
.vh {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden; clip: rect(0 0 0 0);
white-space: nowrap; border: 0;
}
/* ===== Shell ===== */
.shell {
display: grid;
grid-template-columns: var(--sidebar-w) 1fr;
min-height: 100vh;
}
/* ===== Sidebar ===== */
.sidebar {
background: var(--white);
border-right: 1px solid var(--line);
padding: 20px 16px;
display: flex;
flex-direction: column;
gap: 8px;
position: sticky;
top: 0;
height: 100vh;
}
.brand {
display: flex;
align-items: center;
gap: 10px;
padding: 6px 8px 16px;
font-weight: 800;
font-size: 16px;
letter-spacing: -0.01em;
}
.brand__mark {
display: grid;
place-items: center;
width: 32px; height: 32px;
border-radius: 9px;
background: linear-gradient(140deg, var(--brand), var(--accent));
color: #fff;
font-size: 15px;
}
.navlist { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.navlink {
display: flex;
align-items: center;
gap: 11px;
padding: 9px 11px;
border-radius: var(--r-sm);
color: var(--ink-2);
font-weight: 500;
transition: background 0.15s, color 0.15s;
}
.navlink__ico { width: 18px; text-align: center; opacity: 0.75; }
.navlink:hover { background: var(--brand-50); color: var(--ink); }
.navlink.is-active { background: var(--brand-50); color: var(--brand-700); font-weight: 600; }
.side-foot {
margin-top: auto;
display: flex;
align-items: center;
gap: 10px;
padding: 12px 8px 0;
border-top: 1px solid var(--line);
}
.avatar {
width: 34px; height: 34px;
border-radius: 50%;
display: grid; place-items: center;
background: var(--brand-700);
color: #fff;
font-weight: 700;
font-size: 12px;
flex: none;
}
.side-foot__id { display: flex; flex-direction: column; line-height: 1.25; }
.side-foot__id strong { font-size: 13px; }
.side-foot__id span { color: var(--muted); font-size: 12px; }
/* ===== Main ===== */
.main-col { min-width: 0; }
.page { padding: 24px 28px 48px; max-width: 1180px; }
.page-head {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 16px;
margin-bottom: 18px;
}
.eyebrow {
text-transform: uppercase;
letter-spacing: 0.08em;
font-size: 11px;
font-weight: 600;
color: var(--muted);
margin-bottom: 4px;
}
.page-head h1 { font-size: 26px; font-weight: 800; letter-spacing: -0.02em; }
.ghost-btn {
border: 1px solid var(--line-2);
background: var(--white);
color: var(--ink-2);
font-weight: 600;
font-size: 13px;
padding: 9px 14px;
border-radius: var(--r-sm);
box-shadow: var(--sh-1);
transition: background 0.15s, border-color 0.15s;
}
.ghost-btn:hover { background: var(--brand-50); border-color: var(--brand); color: var(--brand-700); }
/* ===== Filter bar ===== */
.filterbar {
position: sticky;
top: 0;
z-index: 30;
background: rgba(255, 255, 255, 0.86);
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 14px 16px;
box-shadow: var(--sh-1);
margin-bottom: 16px;
display: flex;
flex-direction: column;
gap: 12px;
}
.filterbar.is-stuck { box-shadow: var(--sh-2); border-color: var(--line-2); }
.filterbar__row { display: flex; flex-wrap: wrap; gap: 18px; align-items: flex-end; }
.filterbar__row--filters { align-items: center; justify-content: space-between; }
.ctrl { display: flex; flex-direction: column; gap: 6px; }
.ctrl--search { flex: 1; min-width: 200px; }
.ctrl--range { flex: none; }
.ctrl--facets { flex: 1; }
.ctrl__label {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--muted);
}
/* Presets */
.range { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.presets {
display: inline-flex;
background: var(--bg);
border: 1px solid var(--line);
border-radius: var(--r-sm);
padding: 3px;
gap: 2px;
}
.preset {
border: 0;
background: transparent;
color: var(--ink-2);
font-weight: 600;
font-size: 13px;
padding: 6px 12px;
border-radius: 6px;
transition: background 0.15s, color 0.15s, box-shadow 0.15s;
}
.preset:hover { color: var(--ink); }
.preset.is-active {
background: var(--white);
color: var(--brand-700);
box-shadow: var(--sh-1);
}
.custom-dates { display: inline-flex; align-items: center; gap: 8px; }
.custom-dates__sep { color: var(--muted); }
.datefield input {
font-family: inherit;
font-size: 13px;
color: var(--ink);
border: 1px solid var(--line-2);
border-radius: var(--r-sm);
padding: 6px 9px;
background: var(--white);
}
.datefield input:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
/* Search */
.searchbox {
display: flex;
align-items: center;
gap: 8px;
border: 1px solid var(--line-2);
border-radius: var(--r-sm);
background: var(--white);
padding: 0 11px;
transition: border-color 0.15s, box-shadow 0.15s;
}
.searchbox:focus-within { border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-50); }
.searchbox__ico { color: var(--muted); font-size: 16px; }
.searchbox input {
border: 0;
outline: 0;
background: transparent;
font-family: inherit;
font-size: 14px;
color: var(--ink);
padding: 9px 0;
flex: 1;
width: 100%;
}
.searchbox input::placeholder { color: var(--muted); }
/* Facets */
.facets { display: flex; flex-wrap: wrap; gap: 8px; }
.facet { position: relative; }
.facet__btn {
display: inline-flex;
align-items: center;
gap: 7px;
border: 1px solid var(--line-2);
background: var(--white);
color: var(--ink-2);
font-weight: 600;
font-size: 13px;
padding: 8px 12px;
border-radius: var(--r-sm);
transition: border-color 0.15s, background 0.15s, color 0.15s;
}
.facet__btn:hover { border-color: var(--brand); color: var(--brand-700); }
.facet.is-active .facet__btn,
.facet__btn[aria-expanded="true"] {
border-color: var(--brand);
color: var(--brand-700);
background: var(--brand-50);
}
.facet__count {
display: inline-grid;
place-items: center;
min-width: 18px;
height: 18px;
padding: 0 5px;
border-radius: 9px;
background: var(--brand);
color: #fff;
font-size: 11px;
font-weight: 700;
}
.caret { font-size: 10px; opacity: 0.7; transition: transform 0.15s; }
.facet__btn[aria-expanded="true"] .caret { transform: rotate(180deg); }
.facet__menu {
position: absolute;
top: calc(100% + 6px);
left: 0;
z-index: 40;
min-width: 188px;
background: var(--white);
border: 1px solid var(--line-2);
border-radius: var(--r-md);
box-shadow: var(--sh-2);
padding: 6px;
display: flex;
flex-direction: column;
gap: 1px;
}
.opt {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 10px;
border-radius: var(--r-sm);
font-size: 13px;
font-weight: 500;
color: var(--ink-2);
cursor: pointer;
}
.opt:hover { background: var(--brand-50); color: var(--ink); }
.opt input { accent-color: var(--brand); width: 15px; height: 15px; }
.clear-all {
border: 0;
background: transparent;
color: var(--danger);
font-weight: 600;
font-size: 13px;
padding: 8px 6px;
border-radius: var(--r-sm);
flex: none;
}
.clear-all:hover { background: rgba(212, 80, 62, 0.08); }
/* Chips */
.chips-row {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px;
padding-top: 12px;
border-top: 1px dashed var(--line);
}
.chips-row__label {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--muted);
display: inline-flex;
align-items: center;
gap: 7px;
}
.chips-count {
display: inline-grid;
place-items: center;
min-width: 20px;
height: 20px;
padding: 0 6px;
border-radius: 10px;
background: var(--ink);
color: #fff;
font-size: 11px;
}
.chips { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 7px; }
.chip {
display: inline-flex;
align-items: center;
gap: 7px;
background: var(--brand-50);
border: 1px solid rgba(91, 91, 240, 0.25);
color: var(--brand-700);
font-weight: 600;
font-size: 12px;
padding: 5px 6px 5px 11px;
border-radius: 999px;
animation: chipIn 0.18s ease;
}
.chip[data-group="region"] { background: var(--accent-soft); border-color: rgba(0, 180, 166, 0.3); color: #047d73; }
.chip[data-group="status"] { background: #fff3e3; border-color: rgba(217, 138, 43, 0.32); color: #a9651d; }
.chip[data-group="search"] { background: #f0f1f6; border-color: var(--line-2); color: var(--ink-2); }
.chip__group { opacity: 0.7; font-weight: 500; }
.chip__x {
display: grid;
place-items: center;
width: 18px; height: 18px;
border: 0;
border-radius: 50%;
background: rgba(16, 19, 34, 0.08);
color: inherit;
font-size: 12px;
line-height: 1;
}
.chip__x:hover { background: rgba(16, 19, 34, 0.18); }
@keyframes chipIn { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } }
/* Summary */
.summary {
font-size: 13px;
color: var(--ink-2);
margin-bottom: 16px;
}
.summary strong { color: var(--ink); font-weight: 700; }
/* ===== KPIs ===== */
.kpis {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 14px;
margin-bottom: 16px;
}
.kpi {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 15px 16px;
box-shadow: var(--sh-1);
display: flex;
flex-direction: column;
gap: 8px;
}
.kpi__label { font-size: 12px; font-weight: 600; color: var(--muted); }
.kpi__value { font-size: 25px; font-weight: 800; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.kpi__foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.delta {
font-size: 12px;
font-weight: 700;
padding: 2px 7px;
border-radius: 999px;
}
.delta.up { color: var(--ok); background: rgba(47, 158, 111, 0.12); }
.delta.down { color: var(--danger); background: rgba(212, 80, 62, 0.12); }
.spark { width: 84px; height: 28px; flex: none; }
/* ===== Widgets ===== */
.widgets {
display: grid;
grid-template-columns: 1.3fr 1fr;
gap: 16px;
}
.card {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow: var(--sh-1);
padding: 18px;
display: flex;
flex-direction: column;
}
.card__head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-bottom: 16px;
}
.card__title { font-size: 15px; font-weight: 700; letter-spacing: -0.01em; }
.menu-btn {
border: 0;
background: transparent;
color: var(--muted);
font-size: 18px;
line-height: 1;
padding: 2px 8px;
border-radius: var(--r-sm);
}
.menu-btn:hover { background: var(--bg); color: var(--ink); }
.rowcount { font-size: 12px; font-weight: 600; color: var(--muted); }
/* Chart */
.chart {
display: grid;
grid-auto-flow: column;
grid-auto-columns: 1fr;
align-items: end;
gap: 7px;
height: 188px;
padding-bottom: 2px;
border-bottom: 1px solid var(--line);
}
.bar {
position: relative;
border-radius: 6px 6px 0 0;
background: linear-gradient(180deg, var(--brand), var(--brand-d));
min-height: 4px;
transition: height 0.5s cubic-bezier(0.22, 1, 0.36, 1), background 0.2s;
}
.bar:hover { background: linear-gradient(180deg, var(--accent), #009e92); }
.bar::after {
content: attr(data-tip);
position: absolute;
bottom: calc(100% + 6px);
left: 50%;
transform: translateX(-50%);
background: var(--ink);
color: #fff;
font-size: 11px;
font-weight: 600;
padding: 4px 8px;
border-radius: 6px;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.15s;
}
.bar:hover::after { opacity: 1; }
.chart-x {
display: grid;
grid-auto-flow: column;
grid-auto-columns: 1fr;
gap: 7px;
margin-top: 8px;
}
.chart-x span {
font-size: 10px;
font-weight: 600;
color: var(--muted);
text-align: center;
}
/* Table */
.table-wrap { overflow-x: auto; }
.dtable { width: 100%; border-collapse: collapse; font-size: 13px; }
.dtable th {
text-align: left;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--muted);
padding: 0 10px 10px;
border-bottom: 1px solid var(--line);
}
.dtable th.num, .dtable td.num { text-align: right; font-variant-numeric: tabular-nums; }
.dtable td {
padding: 11px 10px;
border-bottom: 1px solid var(--line);
color: var(--ink-2);
}
.dtable tbody tr:last-child td { border-bottom: 0; }
.dtable tbody tr:hover td { background: var(--brand-50); }
.dtable td:first-child { font-weight: 700; color: var(--ink); }
.pill {
display: inline-flex;
align-items: center;
gap: 5px;
font-size: 11px;
font-weight: 700;
padding: 3px 9px;
border-radius: 999px;
}
.pill::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.pill--paid { color: var(--ok); background: rgba(47, 158, 111, 0.12); }
.pill--processing { color: var(--brand-700); background: var(--brand-50); }
.pill--shipped { color: #047d73; background: var(--accent-soft); }
.pill--refunded { color: var(--warn); background: #fff3e3; }
.pill--cancelled { color: var(--danger); background: rgba(212, 80, 62, 0.12); }
.empty {
text-align: center;
color: var(--muted);
font-weight: 600;
padding: 32px 0;
}
/* Toast */
.toast {
position: fixed;
left: 50%;
bottom: 28px;
transform: translate(-50%, 16px);
background: var(--ink);
color: #fff;
font-size: 13px;
font-weight: 600;
padding: 11px 18px;
border-radius: 999px;
box-shadow: var(--sh-2);
opacity: 0;
pointer-events: none;
transition: opacity 0.2s, transform 0.2s;
z-index: 80;
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }
/* ===== Responsive ===== */
@media (max-width: 980px) {
.kpis { grid-template-columns: repeat(2, 1fr); }
.widgets { grid-template-columns: 1fr; }
}
@media (max-width: 720px) {
.shell { grid-template-columns: 1fr; }
.sidebar {
position: sticky;
top: 0;
height: auto;
flex-direction: row;
align-items: center;
gap: 12px;
padding: 12px 16px;
overflow-x: auto;
z-index: 50;
}
.brand { padding: 0; border: 0; }
.navlist { flex-direction: row; }
.navlink span:not(.navlink__ico) { display: none; }
.navlink { padding: 9px; }
.side-foot { display: none; }
.page { padding: 18px 16px 40px; }
.filterbar__row { gap: 12px; }
.ctrl--range, .ctrl--search, .ctrl--facets { flex: 1 1 100%; }
.filterbar__row--filters { align-items: flex-start; }
}
@media (max-width: 420px) {
.kpis { grid-template-columns: 1fr; }
.page-head h1 { font-size: 22px; }
.presets { width: 100%; justify-content: space-between; }
.preset { flex: 1; text-align: center; padding: 7px 6px; }
.facets { width: 100%; }
.facet, .facet__btn { width: 100%; }
.facet__btn { justify-content: space-between; }
.custom-dates { flex-wrap: wrap; }
}
@media (prefers-reduced-motion: reduce) {
* { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; }
}(function () {
"use strict";
/* ---------- Fictional dataset (Northwind Pulse) ---------- */
var CHANNELS = ["Web", "Mobile", "Marketplace", "Retail", "Partner"];
var REGIONS = ["North America", "EMEA", "APAC", "LATAM"];
var STATUSES = ["Paid", "Processing", "Shipped", "Refunded", "Cancelled"];
var NAMES = [
"Aria Bennett", "Leo Marsh", "Priya Nair", "Tomas Vidal", "Nora Holt",
"Kenji Watanabe", "Sofia Reyes", "Liam O'Shea", "Mei Lin", "Omar Haddad",
"Greta Lund", "Diego Castro", "Hana Park", "Ivan Petrov", "Zoe Carter",
];
// Seeded pseudo-random so each render is deterministic per range.
function rng(seed) {
var s = seed % 2147483647;
if (s <= 0) s += 2147483646;
return function () {
s = (s * 16807) % 2147483647;
return (s - 1) / 2147483646;
};
}
var RANGE_DAYS = { today: 1, "7d": 7, "30d": 30, custom: 14 };
var ORDERS = buildOrders();
function buildOrders() {
var rand = rng(42);
var out = [];
for (var i = 0; i < 240; i++) {
var daysAgo = Math.floor(rand() * 30);
out.push({
id: "NW-" + (10480 - i),
channel: CHANNELS[Math.floor(rand() * CHANNELS.length)],
region: REGIONS[Math.floor(rand() * REGIONS.length)],
status: STATUSES[Math.floor(rand() * STATUSES.length)],
customer: NAMES[Math.floor(rand() * NAMES.length)],
total: Math.round((28 + rand() * 940) * 100) / 100,
daysAgo: daysAgo,
});
}
return out;
}
/* ---------- State ---------- */
var state = {
range: "7d",
customStart: null,
customEnd: null,
search: "",
channel: [],
region: [],
status: [],
};
/* ---------- Elements ---------- */
var $ = function (s, r) { return (r || document).querySelector(s); };
var $$ = function (s, r) { return Array.prototype.slice.call((r || document).querySelectorAll(s)); };
var filterbar = $("#filterbar");
var customDates = $("#customDates");
var dateStart = $("#dateStart");
var dateEnd = $("#dateEnd");
var searchInput = $("#searchInput");
var clearAllBtn = $("#clearAll");
var chipsRow = $("#chipsRow");
var chips = $("#chips");
var chipsCount = $("#chipsCount");
var summary = $("#summary");
var tbody = $("#tbody");
var emptyMsg = $("#empty");
var rowCount = $("#rowCount");
var chart = $("#chart");
var chartX = $("#chartX");
var toastEl = $("#toast");
/* ---------- Toast ---------- */
var toastTimer;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () { toastEl.classList.remove("show"); }, 2200);
}
/* ---------- Date presets ---------- */
$$(".preset").forEach(function (btn) {
btn.addEventListener("click", function () {
$$(".preset").forEach(function (b) {
b.classList.remove("is-active");
b.setAttribute("aria-selected", "false");
});
btn.classList.add("is-active");
btn.setAttribute("aria-selected", "true");
state.range = btn.dataset.range;
var isCustom = state.range === "custom";
customDates.hidden = !isCustom;
if (isCustom && !dateStart.value) {
var today = new Date();
var start = new Date();
start.setDate(today.getDate() - 13);
dateStart.value = iso(start);
dateEnd.value = iso(today);
state.customStart = dateStart.value;
state.customEnd = dateEnd.value;
}
apply();
});
});
function iso(d) { return d.toISOString().slice(0, 10); }
dateStart.addEventListener("change", function () { state.customStart = dateStart.value; apply(); });
dateEnd.addEventListener("change", function () { state.customEnd = dateEnd.value; apply(); });
/* ---------- Search (debounced) ---------- */
var searchTimer;
searchInput.addEventListener("input", function () {
clearTimeout(searchTimer);
searchTimer = setTimeout(function () {
state.search = searchInput.value.trim();
apply();
}, 180);
});
/* ---------- Facet dropdowns ---------- */
$$(".facet").forEach(function (facet) {
var btn = $(".facet__btn", facet);
var menu = $(".facet__menu", facet);
var key = facet.dataset.facet;
btn.addEventListener("click", function (e) {
e.stopPropagation();
var open = btn.getAttribute("aria-expanded") === "true";
closeAllMenus();
if (!open) {
btn.setAttribute("aria-expanded", "true");
menu.hidden = false;
}
});
$$("input[type=checkbox]", menu).forEach(function (cb) {
cb.addEventListener("change", function () {
var v = cb.value;
var arr = state[key];
var idx = arr.indexOf(v);
if (cb.checked && idx === -1) arr.push(v);
else if (!cb.checked && idx !== -1) arr.splice(idx, 1);
apply();
});
});
});
function closeAllMenus() {
$$(".facet__btn").forEach(function (b) { b.setAttribute("aria-expanded", "false"); });
$$(".facet__menu").forEach(function (m) { (m.hidden = true); });
}
document.addEventListener("click", closeAllMenus);
document.addEventListener("keydown", function (e) {
if (e.key === "Escape") closeAllMenus();
});
/* ---------- Clear all ---------- */
clearAllBtn.addEventListener("click", function () {
state.search = "";
state.channel = [];
state.region = [];
state.status = [];
searchInput.value = "";
$$(".facet__menu input[type=checkbox]").forEach(function (cb) { (cb.checked = false); });
apply();
toast("Filters cleared");
});
/* ---------- Remove single chip ---------- */
function removeFilter(group, value) {
if (group === "search") {
state.search = "";
searchInput.value = "";
} else {
var arr = state[group];
var idx = arr.indexOf(value);
if (idx !== -1) arr.splice(idx, 1);
var cb = $('.facet[data-facet="' + group + '"] input[value="' + cssEscape(value) + '"]');
if (cb) cb.checked = false;
}
apply();
}
function cssEscape(v) { return v.replace(/(["\\])/g, "\\$1"); }
/* ---------- Export ---------- */
$("#exportBtn").addEventListener("click", function () {
toast("Exporting " + currentRows().length + " filtered orders…");
});
$("#chart").parentNode.querySelector(".menu-btn").addEventListener("click", function () {
toast("Widget options coming soon");
});
/* ---------- Sticky shadow ---------- */
var sentinel = document.createElement("div");
sentinel.style.cssText = "position:absolute;top:0;height:1px;width:1px;";
filterbar.parentNode.insertBefore(sentinel, filterbar);
if ("IntersectionObserver" in window) {
new IntersectionObserver(function (entries) {
filterbar.classList.toggle("is-stuck", !entries[0].isIntersecting);
}, { threshold: [1] }).observe(sentinel);
}
/* ---------- Filtering ---------- */
function currentRows() {
var maxDays = RANGE_DAYS[state.range] || 7;
var q = state.search.toLowerCase();
return ORDERS.filter(function (o) {
if (o.daysAgo >= maxDays) return false;
if (state.channel.length && state.channel.indexOf(o.channel) === -1) return false;
if (state.region.length && state.region.indexOf(o.region) === -1) return false;
if (state.status.length && state.status.indexOf(o.status) === -1) return false;
if (q) {
var hay = (o.id + " " + o.customer + " " + o.channel + " " + o.region).toLowerCase();
if (hay.indexOf(q) === -1) return false;
}
return true;
});
}
function activeCount() {
return state.channel.length + state.region.length + state.status.length + (state.search ? 1 : 0);
}
/* ---------- Render chips ---------- */
var GROUP_LABEL = { channel: "Channel", region: "Region", status: "Status", search: "Search" };
function renderChips() {
var items = [];
["channel", "region", "status"].forEach(function (g) {
state[g].forEach(function (v) { items.push({ group: g, value: v }); });
});
if (state.search) items.push({ group: "search", value: state.search });
chips.innerHTML = "";
items.forEach(function (it) {
var li = document.createElement("li");
li.className = "chip";
li.dataset.group = it.group;
li.innerHTML =
'<span class="chip__group">' + GROUP_LABEL[it.group] + ":</span> " +
'<span class="chip__val"></span>' +
'<button class="chip__x" type="button" aria-label="Remove filter">×</button>';
li.querySelector(".chip__val").textContent = it.value;
li.querySelector(".chip__x").setAttribute(
"aria-label",
"Remove " + GROUP_LABEL[it.group] + " filter " + it.value
);
li.querySelector(".chip__x").addEventListener("click", function () {
removeFilter(it.group, it.value);
});
chips.appendChild(li);
});
var n = activeCount();
chipsCount.textContent = String(n);
chipsRow.hidden = n === 0;
clearAllBtn.hidden = n === 0;
// update facet button counts
["channel", "region", "status"].forEach(function (g) {
var facet = $('.facet[data-facet="' + g + '"]');
var c = $(".facet__count", facet);
var len = state[g].length;
c.textContent = String(len);
c.hidden = len === 0;
facet.classList.toggle("is-active", len > 0);
});
}
/* ---------- KPIs ---------- */
var prevKpi = null;
function renderKpis(rows) {
var orders = rows.length;
var revenue = rows.reduce(function (s, o) { return s + o.total; }, 0);
var aov = orders ? revenue / orders : 0;
var refunds = rows.filter(function (o) { return o.status === "Refunded"; }).length;
var refundRate = orders ? (refunds / orders) * 100 : 0;
var kpi = { orders: orders, revenue: revenue, aov: aov, refund: refundRate };
setKpi("orders", orders, prevKpi && prevKpi.orders, fmtInt, false);
setKpi("revenue", revenue, prevKpi && prevKpi.revenue, fmtMoney, false);
setKpi("aov", aov, prevKpi && prevKpi.aov, fmtMoney, false);
setKpi("refund", refundRate, prevKpi && prevKpi.refund, fmtPct, true);
prevKpi = kpi;
}
function setKpi(key, value, prev, fmt, inverse) {
var card = $('.kpi[data-kpi="' + key + '"]');
var valEl = $("[data-val]", card);
countUp(valEl, value, fmt);
var deltaEl = $("[data-delta]", card);
var pct;
if (prev === null || prev === undefined || prev === 0) {
pct = value === 0 ? 0 : 100;
} else {
pct = ((value - prev) / prev) * 100;
}
var up = pct >= 0;
var good = inverse ? !up : up; // refund up = bad
deltaEl.classList.toggle("up", good);
deltaEl.classList.toggle("down", !good);
deltaEl.textContent = (up ? "▲ " : "▼ ") + Math.abs(pct).toFixed(1) + "%";
drawSpark($("[data-spark]", card), key, good);
}
function fmtInt(v) { return Math.round(v).toLocaleString("en-US"); }
function fmtMoney(v) {
return "$" + Math.round(v).toLocaleString("en-US");
}
function fmtPct(v) { return v.toFixed(1) + "%"; }
function countUp(el, target, fmt) {
var from = parseFloat(el.dataset.raw || "0");
var start = performance.now();
var dur = 480;
function step(now) {
var t = Math.min(1, (now - start) / dur);
var e = 1 - Math.pow(1 - t, 3);
var cur = from + (target - from) * e;
el.textContent = fmt(cur);
if (t < 1) requestAnimationFrame(step);
else el.dataset.raw = String(target);
}
requestAnimationFrame(step);
}
/* ---------- Sparklines ---------- */
function drawSpark(svg, key, good) {
var rand = rng(key.charCodeAt(0) * 31 + Math.floor(prevKpi ? prevKpi.orders : 7));
var pts = [];
var n = 12;
for (var i = 0; i < n; i++) pts.push(6 + rand() * 20);
var w = 120, h = 32;
var d = pts.map(function (p, i) {
var x = (i / (n - 1)) * w;
var y = h - p;
return (i === 0 ? "M" : "L") + x.toFixed(1) + " " + y.toFixed(1);
}).join(" ");
var color = good ? "var(--ok)" : "var(--danger)";
var area = d + " L" + w + " " + h + " L0 " + h + " Z";
svg.innerHTML =
'<path d="' + area + '" fill="' + color + '" opacity="0.12" />' +
'<path d="' + d + '" fill="none" stroke="' + color + '" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" />';
}
/* ---------- Chart (orders by day) ---------- */
function renderChart(rows) {
var days = Math.min(RANGE_DAYS[state.range] || 7, 14);
var buckets = new Array(days).fill(0);
rows.forEach(function (o) {
if (o.daysAgo < days) buckets[days - 1 - o.daysAgo]++;
});
var max = Math.max(1, Math.max.apply(null, buckets));
chart.innerHTML = "";
chartX.innerHTML = "";
var labels = labelsFor(days);
buckets.forEach(function (count, i) {
var bar = document.createElement("div");
bar.className = "bar";
bar.style.height = "0%";
bar.setAttribute("data-tip", count + " orders · " + labels[i]);
chart.appendChild(bar);
// animate after layout
requestAnimationFrame(function () {
bar.style.height = Math.max(3, (count / max) * 100) + "%";
});
var lx = document.createElement("span");
lx.textContent = labels[i];
chartX.appendChild(lx);
});
}
function labelsFor(days) {
var out = [];
var today = new Date();
var step = days <= 7 ? 1 : Math.ceil(days / 7);
for (var i = days - 1; i >= 0; i--) {
var d = new Date(today);
d.setDate(today.getDate() - i);
var idx = days - 1 - i;
out.push(idx % step === 0 || days <= 8 ? (d.getMonth() + 1) + "/" + d.getDate() : "");
}
return out;
}
/* ---------- Table ---------- */
function statusClass(s) { return "pill--" + s.toLowerCase(); }
function renderTable(rows) {
var slice = rows.slice(0, 8);
tbody.innerHTML = "";
slice.forEach(function (o) {
var tr = document.createElement("tr");
tr.innerHTML =
"<td></td>" +
"<td></td>" +
"<td></td>" +
'<td><span class="pill ' + statusClass(o.status) + '"></span></td>' +
'<td class="num"></td>';
tr.children[0].textContent = o.id;
tr.children[1].textContent = o.channel;
tr.children[2].textContent = o.region;
tr.children[3].firstChild.textContent = o.status;
tr.children[4].textContent = "$" + o.total.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
tbody.appendChild(tr);
});
emptyMsg.hidden = rows.length !== 0;
rowCount.textContent = rows.length.toLocaleString("en-US") + (rows.length === 1 ? " row" : " rows");
}
/* ---------- Summary ---------- */
function renderSummary(rows) {
var n = activeCount();
var rangeText = {
today: "today",
"7d": "the last 7 days",
"30d": "the last 30 days",
custom: state.customStart && state.customEnd
? state.customStart + " → " + state.customEnd
: "a custom range",
}[state.range];
var filterText = n === 0
? "no filters applied"
: n + (n === 1 ? " filter" : " filters") + " applied";
summary.innerHTML =
"Showing <strong>" + rows.length.toLocaleString("en-US") +
"</strong> orders for <strong>" + rangeText + "</strong> · " + filterText + ".";
}
/* ---------- Apply (master render) ---------- */
function apply() {
var rows = currentRows();
renderChips();
renderKpis(rows);
renderChart(rows);
renderTable(rows);
renderSummary(rows);
}
/* ---------- Live tick: simulate a new incoming order every 6s ---------- */
var liveRand = rng(7);
setInterval(function () {
// shift one recent order in by decrementing daysAgo for a random fresh one
var idx = Math.floor(liveRand() * ORDERS.length);
var o = ORDERS[idx];
var fresh = {
id: "NW-" + (10481 + Math.floor(liveRand() * 900)),
channel: CHANNELS[Math.floor(liveRand() * CHANNELS.length)],
region: REGIONS[Math.floor(liveRand() * REGIONS.length)],
status: STATUSES[Math.floor(liveRand() * STATUSES.length)],
customer: NAMES[Math.floor(liveRand() * NAMES.length)],
total: Math.round((28 + liveRand() * 940) * 100) / 100,
daysAgo: 0,
};
ORDERS.unshift(fresh);
ORDERS.pop();
apply();
}, 6000);
/* ---------- Init ---------- */
apply();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Northwind Pulse — Global filter + date-range bar</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="shell">
<!-- Sidebar nav -->
<nav class="sidebar" aria-label="Primary">
<div class="brand">
<span class="brand__mark" aria-hidden="true">◈</span>
<span class="brand__name">Northwind Pulse</span>
</div>
<ul class="navlist">
<li><a href="#" class="navlink"><span class="navlink__ico" aria-hidden="true">▤</span> Overview</a></li>
<li><a href="#" class="navlink is-active" aria-current="page"><span class="navlink__ico" aria-hidden="true">⛶</span> Orders</a></li>
<li><a href="#" class="navlink"><span class="navlink__ico" aria-hidden="true">◷</span> Channels</a></li>
<li><a href="#" class="navlink"><span class="navlink__ico" aria-hidden="true">◍</span> Regions</a></li>
<li><a href="#" class="navlink"><span class="navlink__ico" aria-hidden="true">⚙</span> Settings</a></li>
</ul>
<div class="side-foot">
<div class="avatar" aria-hidden="true">MA</div>
<div class="side-foot__id">
<strong>Maya Alvarez</strong>
<span>Ops analyst</span>
</div>
</div>
</nav>
<div class="main-col">
<main class="page" role="main">
<header class="page-head">
<div>
<p class="eyebrow">Northwind Pulse · Commerce analytics</p>
<h1>Orders explorer</h1>
</div>
<button class="ghost-btn" id="exportBtn" type="button">⤓ Export</button>
</header>
<!-- ===== Sticky global filter bar ===== -->
<section class="filterbar" id="filterbar" aria-label="Global filters and date range">
<div class="filterbar__row">
<!-- Date range -->
<div class="ctrl ctrl--range">
<span class="ctrl__label" id="rangeLabel">Date range</span>
<div class="range" role="group" aria-labelledby="rangeLabel">
<div class="presets" role="tablist" aria-label="Date presets">
<button class="preset" role="tab" aria-selected="false" data-range="today" type="button">Today</button>
<button class="preset is-active" role="tab" aria-selected="true" data-range="7d" type="button">7d</button>
<button class="preset" role="tab" aria-selected="false" data-range="30d" type="button">30d</button>
<button class="preset" role="tab" aria-selected="false" data-range="custom" type="button">Custom</button>
</div>
<div class="custom-dates" id="customDates" hidden>
<label class="datefield">
<span class="vh">Start date</span>
<input type="date" id="dateStart" />
</label>
<span class="custom-dates__sep" aria-hidden="true">→</span>
<label class="datefield">
<span class="vh">End date</span>
<input type="date" id="dateEnd" />
</label>
</div>
</div>
</div>
<!-- Search -->
<div class="ctrl ctrl--search">
<span class="ctrl__label" id="searchLabel">Search</span>
<div class="searchbox">
<span class="searchbox__ico" aria-hidden="true">⌕</span>
<input type="search" id="searchInput" placeholder="Order ID, SKU, customer…" aria-labelledby="searchLabel" />
</div>
</div>
</div>
<div class="filterbar__row filterbar__row--filters">
<!-- Multi-select dropdowns -->
<div class="ctrl ctrl--facets">
<span class="ctrl__label">Filters</span>
<div class="facets">
<div class="facet" data-facet="channel">
<button class="facet__btn" type="button" aria-haspopup="true" aria-expanded="false">
Channel <span class="facet__count" hidden>0</span> <span class="caret" aria-hidden="true">▾</span>
</button>
<div class="facet__menu" role="menu" hidden>
<label class="opt"><input type="checkbox" value="Web" /> Web store</label>
<label class="opt"><input type="checkbox" value="Mobile" /> Mobile app</label>
<label class="opt"><input type="checkbox" value="Marketplace" /> Marketplace</label>
<label class="opt"><input type="checkbox" value="Retail" /> Retail POS</label>
<label class="opt"><input type="checkbox" value="Partner" /> Partner API</label>
</div>
</div>
<div class="facet" data-facet="region">
<button class="facet__btn" type="button" aria-haspopup="true" aria-expanded="false">
Region <span class="facet__count" hidden>0</span> <span class="caret" aria-hidden="true">▾</span>
</button>
<div class="facet__menu" role="menu" hidden>
<label class="opt"><input type="checkbox" value="North America" /> North America</label>
<label class="opt"><input type="checkbox" value="EMEA" /> EMEA</label>
<label class="opt"><input type="checkbox" value="APAC" /> APAC</label>
<label class="opt"><input type="checkbox" value="LATAM" /> LATAM</label>
</div>
</div>
<div class="facet" data-facet="status">
<button class="facet__btn" type="button" aria-haspopup="true" aria-expanded="false">
Status <span class="facet__count" hidden>0</span> <span class="caret" aria-hidden="true">▾</span>
</button>
<div class="facet__menu" role="menu" hidden>
<label class="opt"><input type="checkbox" value="Paid" /> Paid</label>
<label class="opt"><input type="checkbox" value="Processing" /> Processing</label>
<label class="opt"><input type="checkbox" value="Shipped" /> Shipped</label>
<label class="opt"><input type="checkbox" value="Refunded" /> Refunded</label>
<label class="opt"><input type="checkbox" value="Cancelled" /> Cancelled</label>
</div>
</div>
</div>
</div>
<button class="clear-all" id="clearAll" type="button" hidden>Clear all</button>
</div>
<!-- Active filter chips -->
<div class="chips-row" id="chipsRow" aria-live="polite" aria-label="Active filters" hidden>
<span class="chips-row__label">
Active filters <span class="chips-count" id="chipsCount">0</span>
</span>
<ul class="chips" id="chips"></ul>
</div>
</section>
<!-- ===== Results summary ===== -->
<p class="summary" id="summary" aria-live="polite"></p>
<!-- ===== KPI cards ===== -->
<section class="kpis" aria-label="Key metrics">
<article class="kpi" data-kpi="orders">
<header class="kpi__head"><span class="kpi__label">Orders</span></header>
<div class="kpi__value" data-val>0</div>
<div class="kpi__foot">
<span class="delta up" data-delta>▲ 0%</span>
<svg class="spark" viewBox="0 0 120 32" preserveAspectRatio="none" aria-hidden="true" data-spark></svg>
</div>
</article>
<article class="kpi" data-kpi="revenue">
<header class="kpi__head"><span class="kpi__label">Revenue</span></header>
<div class="kpi__value" data-val>0</div>
<div class="kpi__foot">
<span class="delta up" data-delta>▲ 0%</span>
<svg class="spark" viewBox="0 0 120 32" preserveAspectRatio="none" aria-hidden="true" data-spark></svg>
</div>
</article>
<article class="kpi" data-kpi="aov">
<header class="kpi__head"><span class="kpi__label">Avg order value</span></header>
<div class="kpi__value" data-val>0</div>
<div class="kpi__foot">
<span class="delta down" data-delta>▼ 0%</span>
<svg class="spark" viewBox="0 0 120 32" preserveAspectRatio="none" aria-hidden="true" data-spark></svg>
</div>
</article>
<article class="kpi" data-kpi="refund">
<header class="kpi__head"><span class="kpi__label">Refund rate</span></header>
<div class="kpi__value" data-val>0</div>
<div class="kpi__foot">
<span class="delta down" data-delta>▼ 0%</span>
<svg class="spark" viewBox="0 0 120 32" preserveAspectRatio="none" aria-hidden="true" data-spark></svg>
</div>
</article>
</section>
<!-- ===== Widgets ===== -->
<section class="widgets">
<article class="card card--chart">
<header class="card__head">
<h2 class="card__title">Orders by day</h2>
<button class="menu-btn" type="button" aria-label="Widget menu">⋯</button>
</header>
<div class="chart" id="chart" role="img" aria-label="Bar chart of orders per day for the selected range"></div>
<div class="chart-x" id="chartX" aria-hidden="true"></div>
</article>
<article class="card card--table">
<header class="card__head">
<h2 class="card__title">Recent orders</h2>
<span class="rowcount" id="rowCount">0 rows</span>
</header>
<div class="table-wrap">
<table class="dtable">
<thead>
<tr>
<th scope="col">Order</th>
<th scope="col">Channel</th>
<th scope="col">Region</th>
<th scope="col">Status</th>
<th scope="col" class="num">Total</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
<p class="empty" id="empty" hidden>No orders match the current filters.</p>
</div>
</article>
</section>
</main>
</div>
</div>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Global filter + date-range bar
A self-contained dashboard filter bar for the fictional Northwind Pulse commerce analytics
workspace. The bar pins to the top of the scroll area (gaining a deeper shadow once it sticks) and
groups three controls: a segmented date-range picker with Today / 7d / 30d / Custom presets that
reveals two <input type="date"> fields, a debounced search box that matches order IDs, customers
and channels, and a trio of multi-select dropdowns for Channel, Region and Status.
Each selection becomes a removable, colour-coded chip below the bar; an “active filters” count and
a Clear all button appear as soon as anything is set, and each dropdown button shows its own
selected-count badge. Removing a chip, toggling a checkbox, or clearing all re-runs a single apply()
render: a results summary updates in an aria-live region, four KPI cards (orders, revenue, average
order value, refund rate) count up to new values with up/down deltas and hand-drawn SVG sparklines,
an orders-by-day bar chart animates to its new heights, and the recent-orders table re-filters with a
graceful empty state.
Everything is vanilla JS with no dependencies. Data is seeded pseudo-randomly so renders are stable,
a live tick injects a fresh fictional order every few seconds, dropdowns close on outside-click and
Escape, and the layout collapses the sidebar to an icon rail and the grid to a single column below
720px while staying usable down to 360px. Controls are keyboard-operable with focus-visible rings
and labelled for screen readers.