collapse —— Panneau repliable
Le panneau repliable est la solution standard pour « trop de contenu, replié par défaut, déplié à la demande » : fréquent dans les FAQ, les regroupements d’options de réglage et la segmentation des longs formulaires. Son modèle d’état est extrêmement simple — un tableau « quels panneaux sont ouverts » — et les seules difficultés sont le mode accordéon (un seul ouvert à la fois) et la normalisation de la valeur contrôlée.
Quand l’utiliser
Section intitulée « Quand l’utiliser »- Quand chaque plateforme implémente
LhCollapse: questions/réponses de FAQ, regroupements de réglages, repli de section dans les formulaires complexes, groupes du menu latéral. - Les blocs de contenu pouvant être repliés / dépliés indépendamment, avec un besoin d’économie d’espace vertical.
- À ne pas utiliser pour : seulement deux ou trois éléments fixes (l’affichage direct est plus simple) ; les étapes strictement linéaires (utilisez
steps).
Importation
Section intitulée « Importation »import { normalizeActiveKeys, toggleCollapseKey, getCollapseItemClassNames,} from '@lh-ui/share/core/collapse'import type { LhCollapseKey } from '@lh-ui/share/core/collapse'Intentions de conception
Section intitulée « Intentions de conception »La normalisation en trois états de normalizeActiveKeys : le v-model contrôlé peut transmettre une valeur unique ('1'), un tableau (['1','2']), ou undefined (tout replié). Une fois normalisé en tableau, le test includes en aval ne traite qu’une seule forme — un autre exemple du principe « entrée tolérante, interne strict ».
L’accordéon « autorise zéro ouvert » : toggleCollapseKey(keys, key, true) renvoie [] au lieu de conserver un autre élément quand keys.includes(key) — en mode accordéon, re-cliquer sur un panneau ouvert replie tout, ce qui est le comportement intuitif (et non un basculement vers un autre panneau). Lorsque l’accordéon transmet une valeur unique, le v-model de la couche UI reçoit un tableau et doit gérer le type lui-même (ou exposer une API à valeur unique supplémentaire pour le mode accordéon).
Mise à jour immuable : les deux fonctions renvoient un nouveau tableau, à fournir tel quel à l’état réactif, sans modifier l’entrée.
Table des fonctions
Section intitulée « Table des fonctions »| Fonction | Signature | Retour | Rôle |
|---|---|---|---|
normalizeActiveKeys |
(value: LhCollapseKey | LhCollapseKey[] | undefined) => LhCollapseKey[] |
Tableau de clés | Normalisation en trois états |
toggleCollapseKey |
(keys, key, accordion: boolean) => LhCollapseKey[] |
Nouveau tableau de clés | Accordéon / normal |
getCollapseItemClassNames |
(state: { active, disabled }) => string[] |
Classes | Déplié / désactivé |
Définitions de types
Section intitulée « Définitions de types »export type LhCollapseKey = string | numberAccessibilité
Section intitulée « Accessibilité »collapse est une implémentation typique du pattern accordion / disclosure des APG, dont les conventions sémantiques sont assemblées par la couche UI (core ne fournit pas de fonction de paquet d’attributs, car le pattern est simple et que le DOM varie beaucoup d’une plateforme à l’autre) :
<!-- Chaque élément est un disclosure --><div class="lh-collapse__item"> <h3> <button type="button" aria-expanded="true" aria-controls="panel-1" id="trigger-1"> Titre </button> </h3> <div id="panel-1" role="region" aria-labelledby="trigger-1">Contenu</div></div>aria-expandedest posé sur le bouton déclencheur, de la même source queactive— c’est l’attribut central d’un panneau repliable accessible.- Le conteneur du panneau
role="region"+aria-labelledbyréférence en retour le déclencheur, formant une association bidirectionnelle. - Élément désactivé : le bouton déclencheur reçoit
disabled(le véritable attribut disabled, pas seulement une classe) — reconnu à la fois par le clavier et par le lecteur d’écran. - Clavier : Tab se déplace entre les déclencheurs, Entrée/Espace bascule le déploiement.
- En mode accordéon, n’utilisez pas
aria-hidden="true"pour masquer les panneaux repliés — le contenu reste dans le DOM (il est visible pendant l’animation) ; une animation de hauteur CSS suffit. Si l’on retire vraiment le DOM, il faut bien gérer l’état dearia-expanded.
Exemples d’utilisation
Section intitulée « Exemples d’utilisation »import { normalizeActiveKeys, toggleCollapseKey } from '@lh-ui/share/core/collapse'
normalizeActiveKeys('1') // ['1']normalizeActiveKeys(['1', '2']) // ['1', '2']normalizeActiveKeys(undefined) // []
toggleCollapseKey([], '1', false) // ['1']toggleCollapseKey(['1'], '1', false) // [] (re-cliquer replie)toggleCollapseKey(['1'], '2', false) // ['1', '2'] (le mode normal admet plusieurs panneaux ouverts)toggleCollapseKey(['1'], '1', true) // [] (accordéon : re-cliquer replie tout)toggleCollapseKey(['1'], '2', true) // ['2'] (accordéon : seul le nouvellement ouvert est conservé)<!-- Vue3 --><div class="lh-collapse"> <div v-for="item in items" :key="item.key" :class="getCollapseItemClassNames({ active: activeKeys.includes(item.key), disabled: item.disabled })" > <h4 class="lh-collapse__header"> <button type="button" class="lh-collapse__trigger" :disabled="item.disabled" :aria-expanded="activeKeys.includes(item.key)" :aria-controls="`lh-panel-${item.key}`" :id="`lh-trigger-${item.key}`" @click="onToggle(item)" > <span class="lh-collapse__arrow">{{ activeKeys.includes(item.key) ? '▾' : '▸' }}</span> {{ item.title }} </button> </h4> <div v-show="activeKeys.includes(item.key)" class="lh-collapse__panel" :id="`lh-panel-${item.key}`" role="region" :aria-labelledby="`lh-trigger-${item.key}`" > <slot :name="item.key" :item="item" /> </div> </div></div>// Contrôlé + accordéonconst activeKeys = computed(() => normalizeActiveKeys(props.modelValue))function onToggle(item) { if (item.disabled) return emit('update:modelValue', toggleCollapseKey(activeKeys.value, item.key, props.accordion))}// React : en mode accordéon, le v-model est à valeur unique ou sous forme de tableau,// c'est l'API de la couche UI qui en décide ; core manipule toujours des tableaux,// la couche UI effectue la conversion à l'émission si besoin (en mode accordéon à// valeur unique uniquement, on prend [0])// uniapp : <view> + v-show ; l'animation de déploiement utilise la transition CSS// max-height (<view> la gère)Styles et jetons
Section intitulée « Styles et jetons »| Classe | Sémantique |
|---|---|
.lh-collapse |
Classe de base |
.lh-collapse__item |
Un élément de panneau |
.lh-collapse__item--active |
État déployé |
.lh-collapse__item--disabled |
Désactivé |
.lh-collapse__header / __trigger |
En-tête / bouton déclencheur |
.lh-collapse__arrow |
Flèche de déploiement (animation de rotation) |
.lh-collapse__panel |
Zone de contenu du panneau |
La couleur de fond de l’en-tête est --lh-fill-color, la flèche --lh-text-color-secondary, l’élément désactivé --lh-text-disabled, la bordure du panneau --lh-border-color.
Remarques
Section intitulée « Remarques »v-showvsv-if: l’animation de déploiement exige la présence permanente du DOM (v-show+ une transition de hauteur/max-height). Avecv-if, l’élément est détruit avant que l’animation de repli puisse être jouée, ce qui donne une expérience abrupte.- Le type de la valeur contrôlée et l’accordéon : logiquement, un seul panneau devrait être ouvert en mode accordéon, mais
toggleCollapseKeyrenvoie un tableau (pouvant valoir[]ou[key]). Si la couche UI expose une API à valeur unique pour l’accordéon, elle prendra[0] ?? undefinedà l’émission ; une API à tableau est également possible, mais l’appelant doit alors se contraindre lui-même. - Le moment de l’appel à
normalizeActiveKeys: normaliser à chaque rendu coûte extrêmement peu (un testArray.isArray) ; si l’inquiétude subsiste, cachez aveccomputed. - Panneaux repliables imbriqués : les
activeKeysinternes sont indépendants de l’externe ; lors de l’imbrication du DOM, lerole="region"du panneau interne se retrouve imbriqué dans un autre region — c’est permis, mais les id dearia-labelledbydoivent être globalement uniques (préfixez avec la key). - La hauteur pendant l’animation : la solution
max-heightexige une limite supérieure suffisamment grande (par exemple1000px), sinon la vitesse de l’animation est faussée pour les contenus très hauts ; une approche plus précise consiste à mesurerscrollHeighten JS puis à le poser — core n’intervient pas, la couche UI l’implémente elle-même. - L’unicité des id :
aria-controls/aria-labelledbyreposent sur des id ; sur une page où coexistent plusieurs instances de collapse, ajoutez un préfixe à la key au niveau du composant (la couche UI utiliseuseIdou l’uid de l’instance).
@lh-ui/share/core/collapse → toggleCollapseKey( )(填好参数后点运行)Copyright © 2024-2026 FengMo WenXue