From 52d573d03fe99b47a3b7efa7375a650bf441b5cf Mon Sep 17 00:00:00 2001 From: Noor A Date: Thu, 24 Sep 2026 16:15:40 +0200 Subject: [PATCH 1/4] feat(filters): reorder sections, remove source search, add select all per category Sources, SDGs, Languages order in both the filter panel and the selected-filter pills. Each source category gets a Select all checkbox (partial state shows a dash). Co-Authored-By: Claude Opus 5.5 (1M context) --- src/components/CheckboxCompnent.vue | 26 ++- src/components/FiltersComponent.vue | 76 +++----- .../__snapshots__/QandA.spec.ts.snap | 180 +++++++++--------- src/components/dropdowns/SourcesSelector.vue | 98 +++++++++- src/localisation/en.ts | 1 - src/localisation/fr.ts | 1 - src/stores/filters.ts | 10 + 7 files changed, 225 insertions(+), 167 deletions(-) diff --git a/src/components/CheckboxCompnent.vue b/src/components/CheckboxCompnent.vue index f7be416..909130d 100644 --- a/src/components/CheckboxCompnent.vue +++ b/src/components/CheckboxCompnent.vue @@ -10,6 +10,7 @@ const props = defineProps<{ selectAll?: () => void; isDisabled?: boolean; isCapitalized?: boolean; + isIndeterminate?: boolean; }>(); const emit = defineEmits(['update:modelValue', 'selectAll']); @@ -39,7 +40,7 @@ const model = computed({ :checked="checked" :disabled="isDisabled" /> - + diff --git a/src/components/FiltersComponent.vue b/src/components/FiltersComponent.vue index 3973939..69a2307 100644 --- a/src/components/FiltersComponent.vue +++ b/src/components/FiltersComponent.vue @@ -15,29 +15,8 @@ const props = defineProps<{ }>(); const sourcesStore = useSourcesStore(); -const availableSources = ref(sourcesStore.sourcesList); const { shouldClose } = toRefs(props); -const handleSearchFilters = (event: Event) => { - if (!(event.target instanceof HTMLInputElement)) - availableSources.value = sourcesStore.sourcesList; - const target = event.target as HTMLInputElement; - const value = target.value; - let temp = undefined; - - for (const [category, sources] of Object.entries(sourcesStore.sourcesList)) { - const filteredSources = sources.filter((source) => - source.name.toLowerCase().includes(value.toLowerCase()) - ); - if (filteredSources.length > 0) { - temp = temp || {}; - temp[category] = filteredSources; - } - } - - availableSources.value = temp || sourcesStore.sourcesList; -}; - const hideFilters = ref(false); watch(shouldClose, (close) => { if (close) { @@ -45,10 +24,6 @@ watch(shouldClose, (close) => { } }); -watch(sourcesStore, () => { - availableSources.value = sourcesStore.sourcesList; -}); - const filters = useFiltersStore(); const toggleFilters = () => (hideFilters.value = !hideFilters.value); @@ -70,22 +45,6 @@ const clearFilters = () => {
-
-

{{ $t('languages') }}{{ $t(':') }}

- - - -

{{ $t('sources') }}{{ $t(':') }}

{
+
+

{{ $t('languages') }}{{ $t(':') }}

+ + + +

{{ $t('removeAll') }}

@@ -121,29 +96,22 @@ const clearFilters = () => { {{ $t('noFiltersSelected') }}

- -
- {{ $t('languages') }} + {{ $t('sources') }}
- +
- {{ $t('sources') }} + {{ $t('sdgsAcronym') }}
- +
- {{ $t('sdgsAcronym') }} + {{ $t('languages') }}
- +
diff --git a/src/components/__tests__/__snapshots__/QandA.spec.ts.snap b/src/components/__tests__/__snapshots__/QandA.spec.ts.snap index e62cc25..8d41cbc 100644 --- a/src/components/__tests__/__snapshots__/QandA.spec.ts.snap +++ b/src/components/__tests__/__snapshots__/QandA.spec.ts.snap @@ -271,99 +271,6 @@ exports[`QandA > renders properly 1`] = ` class="pr-5 filters" data-v-e843c1dd="" > - -
- - languages - -
-
- -
- -
-
- -
- -
-
-
renders properly 1`] = `
+
+ + languages + +
+
+ +
+ +
+
+ +
+ +
+
+
diff --git a/src/components/dropdowns/SourcesSelector.vue b/src/components/dropdowns/SourcesSelector.vue index 4c85956..c012393 100644 --- a/src/components/dropdowns/SourcesSelector.vue +++ b/src/components/dropdowns/SourcesSelector.vue @@ -1,18 +1,53 @@ diff --git a/src/localisation/en.ts b/src/localisation/en.ts index 39dcf99..eecaafb 100644 --- a/src/localisation/en.ts +++ b/src/localisation/en.ts @@ -292,7 +292,6 @@ export const en = { removeAll: 'Clear all', removeBookmark: 'Remove bookmark', removeSelection: 'Clear', - searchBarPlaceholder: 'Search...', sdgsAcronym: 'SDGs', sdgs: { 1: 'No Poverty', diff --git a/src/localisation/fr.ts b/src/localisation/fr.ts index 29807da..fbcb91f 100644 --- a/src/localisation/fr.ts +++ b/src/localisation/fr.ts @@ -294,7 +294,6 @@ export const fr = { removeAll: 'Tout effacer', removeBookmark: 'Retirer des favoris', removeSelection: 'Effacer', - searchBarPlaceholder: 'Rechercher...', sdgsAcronym: 'ODD', sdgs: { 1: 'Pas de pauvreté', diff --git a/src/stores/filters.ts b/src/stores/filters.ts index 322ee61..e238b18 100644 --- a/src/stores/filters.ts +++ b/src/stores/filters.ts @@ -45,6 +45,15 @@ export const useFiltersStore = defineStore('filters', () => { } }; + // "Select all" for one category: untick all if every source is ticked, otherwise tick the missing ones + const toggleCategorySources = (names: Corpus['name'][]) => { + if (names.every((name) => sourcesFilters.value.includes(name))) { + sourcesFilters.value = sourcesFilters.value.filter((item) => !names.includes(item)); + } else { + sourcesFilters.value = [...new Set([...sourcesFilters.value, ...names])]; + } + }; + const handleResetFilters = () => { sdgFilters.value = []; sourcesFilters.value = []; @@ -58,6 +67,7 @@ export const useFiltersStore = defineStore('filters', () => { sourcesFilters, handleSdgFilterChange, handleSourcesFilterChange, + toggleCategorySources, handleLanguageFilterChange, languageList, languageFilters, From 5de8c1ef6958a55e866a1270855a5eb2c19c8e14 Mon Sep 17 00:00:00 2001 From: Noor A Date: Thu, 24 Sep 2026 16:19:29 +0200 Subject: [PATCH 2/4] fix(search): give results the full column and collapse the search box on scroll Only the results area scrolls now, below the search box. Scrolling down collapses the box into a one-line bar showing the query; clicking it (or scrolling back to the top) expands it. Collapsing never changes the scroll position, so there is no flicker. On stacked (mobile/tablet) layouts the search column fills the screen and filters sit below. Co-Authored-By: Claude Opus 5.5 (1M context) --- src/views/SearchSDG.vue | 141 ++++++++++++++++++++++++++++++++++------ 1 file changed, 121 insertions(+), 20 deletions(-) diff --git a/src/views/SearchSDG.vue b/src/views/SearchSDG.vue index 00aad62..05b4d80 100644 --- a/src/views/SearchSDG.vue +++ b/src/views/SearchSDG.vue @@ -10,14 +10,49 @@ import Pill from '@/components/PillComponent.vue'; import FiltersComponent from '@/components/FiltersComponent.vue'; import ColumnTemplate from '@/components/ColumnTemplate.vue'; +import ChevronDownIcon from '@/components/icons/ChevronDown.vue'; +import { nextTick, ref } from 'vue'; + const store = useSearchStore(); + +// The search box collapses into a one-line bar while the user scrolls down through the results, +// and comes back when they click it or scroll back to the top. +// Only the results area scrolls, so collapsing never moves the scroll position (no flicker loop). +const wrapper = ref(null); +const isCompact = ref(false); +let lastScrollTop = 0; + +const onResultsScroll = (event: Event) => { + const top = (event.target as HTMLElement).scrollTop; + if (top === 0) isCompact.value = false; + else if (top > 24 && top > lastScrollTop) isCompact.value = true; + lastScrollTop = top; +}; + +const expandSearch = async () => { + isCompact.value = false; + await nextTick(); + wrapper.value?.querySelector('textarea')?.focus(); +};