Filtres à un seul endroit par état, chips à 0 grisées, repli au-delà de 14, feuille « Filtrer » sous 768 px, pastilles actives en état Carte. FicheMiniCard : racine <article> + bouton étiré pour rendre les chips cliquables. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SCt6e8wWiYqRXLJAuKqP5i
119 lines
3.6 KiB
Vue
119 lines
3.6 KiB
Vue
<!--
|
|
VueFichesFiltres — B11-M4. Les chips de filtre d'une vue fiches, par
|
|
dimension (Chip.vue, variante de la dimension). Monté à UN seul endroit selon
|
|
l'état (SPEC §3) : panneau de gauche en Mixte, tête de grille en Fiches,
|
|
feuille du bas sur téléphone. Compteur dans le résultat courant ; chip à 0
|
|
grisée et inactive ; au-delà de 14 valeurs, repli « + N » (actives toujours
|
|
visibles) ; « Tout effacer » dès qu'un filtre est actif.
|
|
-->
|
|
<template>
|
|
<div class="vff">
|
|
<ChipGroup v-if="vf.etat.sansAdresse" aria-label="Filtre de localisation">
|
|
<Chip as="span" removable active variant="echelle" label="Sans adresse" @remove="vf.setSansAdresse(false)" />
|
|
</ChipGroup>
|
|
<div v-for="d in vf.cfg.dimensions" :key="d.cle" class="vff__dim">
|
|
<p :id="`${idBase}-${d.cle}`" class="vff__titre">{{ d.libelle }}</p>
|
|
<ChipGroup :aria-label="`Filtrer par ${d.libelle.toLowerCase()}`" :aria-describedby="`${idBase}-${d.cle}`">
|
|
<Chip
|
|
v-for="v in visibles(d)"
|
|
:key="v.valeur"
|
|
:label="v.libelle"
|
|
:variant="d.variante"
|
|
:active="actif(d.cle, v.valeur)"
|
|
:count="vf.compteurs[d.cle]?.[v.valeur] ?? 0"
|
|
:disabled="!actif(d.cle, v.valeur) && !(vf.compteurs[d.cle]?.[v.valeur])"
|
|
class="vff__chip"
|
|
@toggle="vf.toggle(d.cle, v.valeur)"
|
|
/>
|
|
<button
|
|
v-if="d.valeurs.length > SEUIL"
|
|
type="button"
|
|
class="vff__plus"
|
|
:aria-expanded="!!deplies[d.cle]"
|
|
@click="deplies[d.cle] = !deplies[d.cle]"
|
|
>{{ deplies[d.cle] ? 'Réduire' : `+ ${d.valeurs.length - visibles(d).length}` }}</button>
|
|
</ChipGroup>
|
|
</div>
|
|
|
|
<button
|
|
v-if="vf.nbActifs || vf.q"
|
|
type="button"
|
|
class="vff__effacer"
|
|
@click="vf.effacerTout()"
|
|
>Tout effacer</button>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { VueFichesEtat } from '~/composables/useVueFiches'
|
|
import type { DimensionFiltre } from '~/utils/vueFiches'
|
|
|
|
const props = defineProps<{ vf: VueFichesEtat; idBase: string }>()
|
|
|
|
const SEUIL = 14
|
|
const deplies = reactive<Record<string, boolean>>({})
|
|
|
|
function actif(dim: string, valeur: string) {
|
|
return (props.vf.etat.filtres[dim] ?? []).includes(valeur)
|
|
}
|
|
|
|
/** Les 14 premières dans l'ordre de la dimension (stable : les chips ne bougent pas au clic) + les actives hors de ce lot. */
|
|
function visibles(d: DimensionFiltre) {
|
|
if (d.valeurs.length <= SEUIL || deplies[d.cle]) return d.valeurs
|
|
return d.valeurs.filter((v, i) => i < SEUIL || actif(d.cle, v.valeur))
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.vff {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-sm);
|
|
}
|
|
|
|
.vff__titre {
|
|
margin: 0 0 var(--space-2xs);
|
|
font-size: var(--fs-xs);
|
|
font-weight: var(--fw-semibold);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.04em;
|
|
color: color-mix(in srgb, var(--text) 78%, transparent);
|
|
}
|
|
|
|
.vff__chip:disabled {
|
|
opacity: 0.45;
|
|
cursor: not-allowed;
|
|
transform: none;
|
|
}
|
|
|
|
.vff__plus {
|
|
min-height: 32px;
|
|
padding: var(--space-2xs) var(--space-sm);
|
|
border: 1.5px dashed color-mix(in srgb, var(--text) 40%, transparent);
|
|
border-radius: var(--radius-full);
|
|
background: none;
|
|
color: var(--text);
|
|
font: inherit;
|
|
font-size: var(--fs-xs);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.vff__effacer {
|
|
align-self: flex-start;
|
|
padding: var(--space-2xs) 0;
|
|
border: none;
|
|
background: none;
|
|
color: var(--text);
|
|
font: inherit;
|
|
font-size: var(--fs-xs);
|
|
text-decoration: underline;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.vff__plus:focus-visible,
|
|
.vff__effacer:focus-visible {
|
|
outline: 2px solid var(--focus-ring);
|
|
outline-offset: 2px;
|
|
}
|
|
</style>
|