<%# Filter bar: a real GET form so filtering is server-side and zero-JS. Config: rows: Control[][] rows of controls, laid out left→right pills, clearHref, label, action, applyLabel Control.type ∈ search | segmented | select | chips | multiselect | daterange | spacer. search { name, placeholder?, value?, label? } segmented { name, legend?, value?, options:{value,label,count?}[] } (radios) select { name, label, value?, options:{value,label}[] } chips { name, legend?, value?:string[], options:{value,label}[] } (checkboxes) multiselect { name, legend?, note?, value?:string[], options:{value,label}[] } (checkboxes in a popover) daterange { legend?, from:{name,value?,label?}, to:{name,value?,label?} } chips and multiselect are the same checkboxes on the same parameter: on the bar, or behind a button once the list is too long to lay there. The form is a GET, which replaces the whole query string — so the visitor's chosen language rides along as a hidden input, and every href here (pills, clear) is run through localeHref. %><% const action = locals.action || ""; const label = locals.label || t("filter.label"); const rows = locals.rows || []; const pills = locals.pills || []; const clearHref = locals.clearHref || "?"; const applyLabel = locals.applyLabel || t("filter.apply"); const eq = (a, b) => String(a ?? "") === String(b); -%>
action="<%= localeHref(action) %>"<% } %> aria-label="<%= label %>"> <% if (localeParam) { -%> <% } -%> <% rows.forEach((row) => { -%>
<% row.forEach((c) => { -%> <% if (c.type === "search") { -%> <% } else if (c.type === "segmented") { -%>
<%= c.legend || c.name %>
<% c.options.forEach((o) => { %><% }) %>
<% } else if (c.type === "select") { -%> <% } else if (c.type === "chips") { -%>
<%= c.legend || c.name %>
<% (c.options).forEach((o) => { const on = (c.value || []).map(String).includes(String(o.value)); %><% }) %>
<% } else if (c.type === "multiselect") { const legend = c.legend || c.name; const on = (c.value || []).map(String); -%> <%- include("menu", { id: "f-" + c.name + "-menu", align: "left", kebab: false, up: false, width: null, // explicit: EJS would otherwise inherit the page's own trigger: { class: "btn btn-menu", count: on.length || null, label: on.length ? t("filter.selected", { count: on.length, label: legend }) : null, text: legend }, items: [ ...(c.note ? [{ head: c.note }] : []), { group: { legend, name: c.name, options: c.options.map((o) => ({ checked: on.includes(String(o.value)), label: o.label, value: o.value })) } }, ], }) %> <% } else if (c.type === "daterange") { -%>
<%= c.legend || t("filter.dateRange") %>
value="<%= c.from.value %>"<% } %>> value="<%= c.to.value %>"<% } %>>
<% } else if (c.type === "spacer") { -%>
<% } -%> <% }) -%>
<% }) -%>
<% if (pills.length) { -%>
"><%= t("filter.applied") %><% pills.forEach((p) => { %><%= p.label %>: <%= p.value %> "><% }) %><%= t("filter.clearAll") %>
<% } -%>