Skip to content

Commit 5bac4f0

Browse files
committed
Add full search filter controls
Add Pagefind-backed filter controls on top of the runtime core. Read available filter values from Pagefind and render draft-driven dropdowns for Proposal Category, Status, and Author. Establish the draft, staged, and committed state model for full search. Dropdown edits are staged while a dropdown is open. Query and filter edits update draft UI state. Search or Enter commits the draft query and filters together, updates the URL, and runs Pagefind. Dropdown Apply, outside click, chip removal, clear-field actions, and author mode changes update draft state only. Empty searches clear to the clean search route without calling Pagefind. Sort continues to commit on change against committed state. Use the rendered proposal filter contract introduced by the template hook foundation: proposal_category, status, and author. Encode Proposal Category with URL parameter proposalCategory and Pagefind field proposal_category. Keep Pagefind import, filter loading, and search execution behind loader.mjs. Render disabled loading filter shells in the template so the filter layout is present before JavaScript hydrates it. Add dropdown overlay, chevron, chip, result-hover, and loading-state styles with validation coverage for the URL/state contract and Pagefind filter queries.
1 parent 7cc239d commit 5bac4f0

8 files changed

Lines changed: 1781 additions & 126 deletions

File tree

sass/assets/css/search.scss

Lines changed: 285 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@
4141
padding: 0.4rem 0.65rem;
4242
}
4343

44+
.search-page-controls input,
45+
.search-page-controls select {
46+
background: var(--bs-body-bg, #fff);
47+
background-image: none;
48+
}
49+
50+
.search-page-controls select {
51+
cursor: pointer;
52+
}
53+
4454
.search-page-controls button,
4555
.search-modal-close,
4656
.search-modal-full-link {
@@ -55,6 +65,14 @@
5565
opacity: 0.65;
5666
}
5767

68+
.search-page-controls input:disabled,
69+
.search-page-controls select:disabled {
70+
background: var(--bs-secondary-bg, #f8f9fa);
71+
color: var(--bs-secondary-color, #666);
72+
cursor: not-allowed;
73+
opacity: 0.75;
74+
}
75+
5876
.search-share-button {
5977
align-items: center;
6078
background: rgba(var(--bs-body-bg-rgb, 255, 255, 255), 0.92);
@@ -135,6 +153,246 @@
135153
color: var(--bs-danger-text-emphasis, #842029);
136154
}
137155

156+
.search-page-controls [data-search-page-submit] {
157+
background-color: var(--bs-warning-bg-subtle, #fff3cd);
158+
background-image: none;
159+
border: 1px solid var(--bs-warning-border-subtle, #ffda6a);
160+
border-radius: 4px;
161+
box-shadow: 0 0 0 0.18rem rgba(255, 193, 7, 0.22);
162+
color: var(--bs-warning-text-emphasis, #664d03);
163+
}
164+
165+
.search-page-filter-region {
166+
margin: 1rem 0;
167+
}
168+
169+
.search-filter-grid {
170+
display: grid;
171+
gap: 1rem;
172+
grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
173+
}
174+
175+
.search-filter-field {
176+
position: relative;
177+
}
178+
179+
.search-filter-label {
180+
display: block;
181+
font-weight: 600;
182+
margin-bottom: 0.35rem;
183+
}
184+
185+
.search-filter-input-row {
186+
display: grid;
187+
grid-template-columns: minmax(0, 1fr) auto;
188+
}
189+
190+
.search-filter-input,
191+
.search-filter-toggle,
192+
.search-filter-clear,
193+
.search-filter-panel-clear,
194+
.search-filter-apply,
195+
.search-filter-mode-button {
196+
border: 1px solid var(--bs-border-color, #ced4da);
197+
border-radius: 4px;
198+
font: inherit;
199+
min-height: 2.25rem;
200+
padding: 0.35rem 0.55rem;
201+
}
202+
203+
.search-filter-input {
204+
background: var(--bs-body-bg, #fff);
205+
background-image: none;
206+
border-bottom-right-radius: 0;
207+
border-top-right-radius: 0;
208+
min-width: 0;
209+
}
210+
211+
.search-filter-input:disabled,
212+
.search-filter-toggle:disabled {
213+
background: var(--bs-secondary-bg, #f8f9fa);
214+
color: var(--bs-secondary-color, #666);
215+
cursor: not-allowed;
216+
opacity: 0.75;
217+
}
218+
219+
.search-filter-toggle {
220+
align-items: center;
221+
background: var(--bs-secondary-bg, #f8f9fa);
222+
border-bottom-left-radius: 0;
223+
border-left: 0;
224+
border-top-left-radius: 0;
225+
cursor: pointer;
226+
display: inline-flex;
227+
justify-content: center;
228+
width: 2.5rem;
229+
}
230+
231+
.search-filter-toggle-icon {
232+
align-items: center;
233+
display: inline-flex;
234+
height: 0.75rem;
235+
justify-content: center;
236+
width: 0.75rem;
237+
}
238+
239+
.search-filter-toggle-icon::before {
240+
border: solid currentColor;
241+
border-width: 0 2px 2px 0;
242+
content: "";
243+
display: block;
244+
height: 0.48rem;
245+
transform: rotate(45deg) translate(-0.08rem, -0.08rem);
246+
transition: transform 0.18s ease;
247+
width: 0.48rem;
248+
}
249+
250+
.search-filter-toggle[aria-expanded="true"] .search-filter-toggle-icon::before {
251+
transform: rotate(225deg) translate(-0.08rem, -0.08rem);
252+
}
253+
254+
.search-filter-summary {
255+
color: var(--bs-secondary-color, #666);
256+
font-size: 0.9rem;
257+
margin-top: 0.3rem;
258+
}
259+
260+
.search-filter-chips {
261+
display: flex;
262+
flex-wrap: wrap;
263+
gap: 0.35rem;
264+
margin-top: 0.45rem;
265+
}
266+
267+
.search-filter-chip {
268+
align-items: center;
269+
border: 1px solid var(--bs-border-color, #dee2e6);
270+
border-radius: 4px;
271+
display: inline-flex;
272+
gap: 0.35rem;
273+
padding: 0.15rem 0.2rem 0.15rem 0.45rem;
274+
}
275+
276+
.search-filter-chip-remove {
277+
background: transparent;
278+
border: 0;
279+
border-left: 1px solid var(--bs-border-color, #dee2e6);
280+
color: inherit;
281+
cursor: pointer;
282+
font: inherit;
283+
padding: 0 0.3rem;
284+
}
285+
286+
.search-filter-clear {
287+
background: transparent;
288+
color: var(--bs-secondary-color, #666);
289+
cursor: pointer;
290+
margin-top: 0.35rem;
291+
}
292+
293+
.search-filter-panel {
294+
background: var(--bs-body-bg, #fff);
295+
border: 1px solid var(--bs-border-color, #dee2e6);
296+
border-radius: 6px;
297+
box-shadow: 0 0.75rem 1.5rem rgba(0, 0, 0, 0.18);
298+
display: flex;
299+
flex-direction: column;
300+
left: 0;
301+
max-height: min(50vh, 28rem);
302+
overflow: hidden;
303+
padding: 0;
304+
position: absolute;
305+
right: 0;
306+
top: 4.6rem;
307+
z-index: 50;
308+
}
309+
310+
.search-filter-mode {
311+
background: var(--bs-body-bg, #fff);
312+
border-bottom: 1px solid var(--bs-border-color, #dee2e6);
313+
display: grid;
314+
flex: 0 0 auto;
315+
gap: 0.45rem;
316+
margin: 0;
317+
padding: 0.65rem 0.65rem 0.55rem;
318+
}
319+
320+
.search-filter-mode-heading {
321+
color: var(--bs-secondary-color, #666);
322+
font-size: 0.86rem;
323+
font-weight: 600;
324+
text-align: center;
325+
}
326+
327+
.search-filter-mode-options {
328+
display: grid;
329+
gap: 0.4rem;
330+
grid-template-columns: 1fr 1fr;
331+
}
332+
333+
.search-filter-mode-button,
334+
.search-filter-panel-clear,
335+
.search-filter-apply {
336+
background: var(--bs-secondary-bg, #f8f9fa);
337+
color: inherit;
338+
cursor: pointer;
339+
}
340+
341+
.search-filter-mode-button[aria-pressed="true"] {
342+
background: var(--bs-primary, #0d6efd);
343+
border-color: var(--bs-primary, #0d6efd);
344+
color: #fff;
345+
}
346+
347+
.search-filter-options {
348+
display: grid;
349+
flex: 1 1 auto;
350+
gap: 0.2rem;
351+
min-height: 0;
352+
overflow: auto;
353+
padding: 0.6rem;
354+
}
355+
356+
.search-filter-option {
357+
align-items: center;
358+
border-radius: 4px;
359+
cursor: pointer;
360+
display: grid;
361+
gap: 0.45rem;
362+
grid-template-columns: auto minmax(0, 1fr) auto;
363+
padding: 0.3rem;
364+
}
365+
366+
.search-filter-option:hover,
367+
.search-filter-option:focus-within {
368+
background: var(--bs-secondary-bg, #f8f9fa);
369+
}
370+
371+
.search-filter-option[aria-selected="true"] {
372+
background: rgba(var(--bs-primary-rgb, 13, 110, 253), 0.12);
373+
}
374+
375+
.search-filter-option-count {
376+
color: var(--bs-secondary-color, #666);
377+
font-size: 0.85rem;
378+
}
379+
380+
.search-filter-empty {
381+
color: var(--bs-secondary-color, #666);
382+
padding: 0.45rem;
383+
}
384+
385+
.search-filter-actions {
386+
background: var(--bs-body-bg, #fff);
387+
border-top: 1px solid var(--bs-border-color, #dee2e6);
388+
display: flex;
389+
flex: 0 0 auto;
390+
gap: 0.5rem;
391+
justify-content: flex-end;
392+
margin-top: 0;
393+
padding: 0.6rem;
394+
}
395+
138396
.search-page-status,
139397
.search-modal-status {
140398
color: var(--bs-secondary-color, #666);
@@ -147,6 +405,12 @@
147405
gap: 0.75rem;
148406
}
149407

408+
.search-page-results {
409+
max-height: 70vh;
410+
overflow: auto;
411+
padding-right: 0.25rem;
412+
}
413+
150414
.search-result-card {
151415
border: 1px solid var(--bs-border-color, #dee2e6);
152416
border-radius: 6px;
@@ -161,10 +425,22 @@
161425
.search-result-link {
162426
display: block;
163427
color: inherit;
428+
cursor: pointer;
164429
padding: 0.85rem;
165430
text-decoration: none;
166431
}
167432

433+
.search-result-link:hover,
434+
.search-result-link:focus {
435+
text-decoration: none;
436+
}
437+
438+
.search-result-link *,
439+
.search-result-link:hover *,
440+
.search-result-link:focus * {
441+
text-decoration: none;
442+
}
443+
168444
.search-result-link:hover .search-result-title,
169445
.search-result-link:focus .search-result-title {
170446
text-decoration: underline;
@@ -248,7 +524,7 @@
248524
.search-modal-header {
249525
align-items: center;
250526
display: grid;
251-
gap: 1rem;
527+
gap: 0.75rem;
252528
grid-template-columns: 2.5rem minmax(0, 1fr) 2.5rem;
253529
margin-bottom: 0.75rem;
254530
}
@@ -318,4 +594,12 @@
318594
.search-modal-results {
319595
max-height: 48vh;
320596
}
597+
598+
.search-modal-panel {
599+
max-height: 88vh;
600+
}
601+
602+
.search-modal-results {
603+
max-height: 48vh;
604+
}
321605
}

0 commit comments

Comments
 (0)