Skip to content

Commit b002bcd

Browse files
feat: add new filters and facets to global search (#913)
Related to datagouv/data.gouv.fr#1432 Follow up of #907 Replace #847 --------- Co-authored-by: maudetes <maudet.estelle@gmail.com>
1 parent e9e3ce5 commit b002bcd

16 files changed

+460
-96
lines changed
Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
11
<template>
2-
<span class="inline-flex gap-0.5">
2+
<span class="inline-flex gap-0.5 items-end h-2">
33
<span
44
v-for="i in 3"
55
:key="i"
6-
class="w-1 h-1 bg-current rounded-full animate-bounce"
7-
:style="{ animationDelay: `${(i - 1) * 150}ms` }"
6+
class="w-1 h-1 bg-current rounded-full animate-[bounce-dot_0.6s_ease-in-out_infinite]"
7+
:style="`animation-delay: ${(i - 1) * 100}ms !important`"
88
/>
99
</span>
1010
</template>
11+
12+
<style>
13+
@keyframes bounce-dot {
14+
0%, 100% {
15+
transform: translateY(0);
16+
}
17+
50% {
18+
transform: translateY(-3px);
19+
}
20+
}
21+
</style>

datagouv-components/src/components/Form/AccessTypeSelect.vue

Lines changed: 0 additions & 23 deletions
This file was deleted.

datagouv-components/src/components/RadioGroup.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<template>
2-
<fieldset class="flex flex-col gap-2">
2+
<fieldset class="flex flex-col gap-2 min-w-0">
33
<legend
44
v-if="legend"
5-
class="font-bold text-sm leading-6 text-gray-900 mb-2 uppercase"
5+
class="fr-label mb-2"
66
>
77
{{ legend }}
88
</legend>

datagouv-components/src/components/RadioInput.vue

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<template>
22
<label
33
class="flex items-center gap-2 p-1 rounded cursor-pointer transition has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-blue-500"
4-
:class="isSelected ? 'bg-gray-200' : 'hover:bg-gray-100'"
4+
:class="selectedClass"
55
>
66
<input
77
type="radio"
@@ -16,12 +16,13 @@
1616
v-if="icon"
1717
class="w-4 h-4"
1818
/>
19-
<span class="text-sm flex-1">
19+
<span class="text-sm flex-1 min-w-0">
2020
<slot />
2121
</span>
2222
<span
2323
v-if="loading || count !== undefined"
24-
class="bg-gray-200 text-gray-600 text-xs font-bold px-1 py-0.5 rounded"
24+
class="text-xs font-bold px-1 py-0.5 rounded"
25+
:class="isSelected && highlighted ? 'bg-white/20 text-white' : 'bg-gray-200 text-gray-600'"
2526
>
2627
<BouncingDots v-if="loading" />
2728
<template v-else>{{ formattedCount }}</template>
@@ -40,6 +41,7 @@ type Props = {
4041
count?: number
4142
loading?: boolean
4243
icon?: Component
44+
highlighted?: boolean
4345
}
4446
4547
const props = defineProps<Props>()
@@ -49,6 +51,12 @@ const { locale } = useTranslation()
4951
5052
const isSelected = computed(() => group?.modelValue.value === props.value)
5153
54+
const selectedClass = computed(() => {
55+
if (!isSelected.value) return 'hover:bg-gray-100'
56+
if (props.highlighted) return 'bg-blue-800 text-white'
57+
return 'bg-gray-200'
58+
})
59+
5260
const formattedCount = computed(() => {
5361
if (props.count === undefined) return ''
5462
return new Intl.NumberFormat(locale, { notation: 'compact' }).format(props.count)

datagouv-components/src/components/Search/BasicAndAdvancedFilters.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<template>
2-
<div class="flex flex-col">
2+
<!-- [&_.fr-input-group]:!mb-0 disables DSFR margin-bottom since we use gap for spacing -->
3+
<div class="flex flex-col gap-4 [&_.fr-input-group]:!mb-0">
34
<slot
45
:is-enabled="isBasicFilter"
56
:get-order="getBasicOrder"
@@ -19,7 +20,7 @@
1920
/>
2021
{{ t('Filtres avancés') }}
2122
</DisclosureButton>
22-
<DisclosurePanel class="flex flex-col mt-4">
23+
<DisclosurePanel class="flex flex-col gap-4 mt-4 [&_.fr-input-group]:!mb-0">
2324
<slot
2425
:is-enabled="isAdvancedFilter"
2526
:get-order="getAdvancedOrder"
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<template>
2+
<FilterButtonGroup
3+
:model-value="modelValue"
4+
:options="options"
5+
:label="t(`Modalités d'accès`)"
6+
:all-label="t('Toutes')"
7+
:facets="facets"
8+
:loading="loading"
9+
name="access_type"
10+
highlight-active
11+
@update:model-value="emit('update:modelValue', $event)"
12+
/>
13+
</template>
14+
15+
<script setup lang="ts">
16+
import type { FacetItem } from '../../../types/search'
17+
import { useTranslation } from '../../../composables/useTranslation'
18+
import FilterButtonGroup from './FilterButtonGroup.vue'
19+
20+
defineProps<{
21+
modelValue: string | undefined
22+
facets?: FacetItem[]
23+
loading?: boolean
24+
}>()
25+
26+
const emit = defineEmits<{
27+
'update:modelValue': [value: string | undefined]
28+
}>()
29+
30+
const { t } = useTranslation()
31+
32+
const options = [
33+
{ value: 'open', label: t('Téléchargement libre') },
34+
{ value: 'open_with_account', label: t('Ouvert sous condition') },
35+
{ value: 'restricted', label: t('Accessible sous habilitation') },
36+
]
37+
</script>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<template>
2+
<FilterButtonGroup
3+
:model-value="modelValue"
4+
:options="options"
5+
:label="t('Label de donnée')"
6+
:all-label="t('Tous')"
7+
:facets="facets"
8+
:loading="loading"
9+
name="badge"
10+
highlight-active
11+
@update:model-value="emit('update:modelValue', $event)"
12+
/>
13+
</template>
14+
15+
<script setup lang="ts">
16+
import { computed } from 'vue'
17+
import type { FacetItem } from '../../../types/search'
18+
import { useFetch } from '../../../functions/api'
19+
import { useTranslation } from '../../../composables/useTranslation'
20+
import FilterButtonGroup from './FilterButtonGroup.vue'
21+
22+
defineProps<{
23+
modelValue: string | undefined
24+
facets?: FacetItem[]
25+
loading?: boolean
26+
}>()
27+
28+
const emit = defineEmits<{
29+
'update:modelValue': [value: string | undefined]
30+
}>()
31+
32+
const { t } = useTranslation()
33+
34+
const { data: badgesRecord } = await useFetch<Record<string, string>>('/api/1/datasets/badges/', { lazy: true })
35+
36+
const options = computed(() => {
37+
if (!badgesRecord.value) return []
38+
return Object.entries(badgesRecord.value).map(([value, label]) => ({ value, label }))
39+
})
40+
</script>
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<template>
2+
<RadioGroup
3+
:model-value="modelValue ?? ''"
4+
:name="name"
5+
:legend="label"
6+
@update:model-value="emit('update:modelValue', $event === '' ? undefined : $event)"
7+
>
8+
<RadioInput
9+
value=""
10+
:count="totalCount"
11+
:loading="loading"
12+
>
13+
{{ allLabel }}
14+
</RadioInput>
15+
<RadioInput
16+
v-for="option in options"
17+
:key="option.value"
18+
:value="option.value"
19+
:count="getCount(option.value)"
20+
:loading="loading"
21+
:highlighted="props.highlightActive && !!props.modelValue"
22+
>
23+
<span class="flex items-center gap-1 min-w-0 overflow-hidden">
24+
<span
25+
class="shrink min-w-0 truncate"
26+
:title="option.label"
27+
>{{ option.label }}</span>
28+
<span
29+
v-if="option.description"
30+
class="flex-1 basis-0 min-w-0 text-gray-400 text-xs truncate"
31+
>
32+
{{ option.description }}
33+
</span>
34+
</span>
35+
</RadioInput>
36+
</RadioGroup>
37+
</template>
38+
39+
<script setup lang="ts">
40+
import { computed } from 'vue'
41+
import type { FacetItem } from '../../../types/search'
42+
import RadioGroup from '../../RadioGroup.vue'
43+
import RadioInput from '../../RadioInput.vue'
44+
45+
export interface FilterOption {
46+
value: string
47+
label: string
48+
description?: string
49+
}
50+
51+
const props = withDefaults(defineProps<{
52+
modelValue: string | undefined
53+
options: FilterOption[]
54+
label: string
55+
name: string
56+
allLabel?: string
57+
facets?: FacetItem[]
58+
loading?: boolean
59+
highlightActive?: boolean
60+
}>(), {
61+
allLabel: 'Tous',
62+
loading: false,
63+
highlightActive: false,
64+
})
65+
66+
const emit = defineEmits<{
67+
'update:modelValue': [value: string | undefined]
68+
}>()
69+
70+
function getCount(value: string): number | undefined {
71+
if (!props.facets) return undefined
72+
const facet = props.facets.find(f => f.name === value)
73+
return facet?.count ?? 0
74+
}
75+
76+
const totalCount = computed(() => getCount('all'))
77+
</script>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<template>
2+
<FilterButtonGroup
3+
:model-value="modelValue"
4+
:options="options"
5+
:label="t('Format de données')"
6+
:all-label="t('Tous')"
7+
:facets="facets"
8+
:loading="loading"
9+
name="format_family"
10+
highlight-active
11+
@update:model-value="emit('update:modelValue', $event)"
12+
/>
13+
</template>
14+
15+
<script setup lang="ts">
16+
import type { FacetItem } from '../../../types/search'
17+
import { useTranslation } from '../../../composables/useTranslation'
18+
import FilterButtonGroup from './FilterButtonGroup.vue'
19+
20+
defineProps<{
21+
modelValue: string | undefined
22+
facets?: FacetItem[]
23+
loading?: boolean
24+
}>()
25+
26+
const emit = defineEmits<{
27+
'update:modelValue': [value: string | undefined]
28+
}>()
29+
30+
const { t } = useTranslation()
31+
32+
const options = [
33+
{ value: 'tabular', label: t('Tabulaires'), description: 'csv, xls, xlsx, ods, parquet...' },
34+
{ value: 'machine_readable', label: t('Structurées'), description: 'json, rdf, xml, sql...' },
35+
{ value: 'geographical', label: t('Géographiques'), description: 'geojson, shp, kml...' },
36+
{ value: 'documents', label: t('Documents'), description: 'pdf, doc, docx, md, txt, html...' },
37+
{ value: 'other', label: t('Autre') },
38+
]
39+
</script>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<template>
2+
<FilterButtonGroup
3+
:model-value="modelValue"
4+
:options="options"
5+
:label="t('Date de mise à jour')"
6+
:all-label="t('Toutes')"
7+
:facets="facets"
8+
:loading="loading"
9+
name="last_update_range"
10+
highlight-active
11+
@update:model-value="emit('update:modelValue', $event)"
12+
/>
13+
</template>
14+
15+
<script setup lang="ts">
16+
import type { FacetItem } from '../../../types/search'
17+
import { useTranslation } from '../../../composables/useTranslation'
18+
import FilterButtonGroup from './FilterButtonGroup.vue'
19+
20+
defineProps<{
21+
modelValue: string | undefined
22+
facets?: FacetItem[]
23+
loading?: boolean
24+
}>()
25+
26+
const emit = defineEmits<{
27+
'update:modelValue': [value: string | undefined]
28+
}>()
29+
30+
const { t } = useTranslation()
31+
32+
const options = [
33+
{ value: 'last_30_days', label: t('Les 30 derniers jours') },
34+
{ value: 'last_12_months', label: t('Les 12 derniers mois') },
35+
{ value: 'last_3_years', label: t('Les 3 dernières années') },
36+
]
37+
</script>

0 commit comments

Comments
 (0)