ObsiViewer/docs/GRAPH/GRAPH_ACCORDION_IMPLEMENTATION.md

7.5 KiB

Graph Settings Accordion - Implementation CDK

📋 Vue d'ensemble

Remplacement de l'accordéon PrimeNG par un accordéon headless basé sur @angular/cdk/accordion avec styling Tailwind pour le panneau Graph settings d'ObsiViewer.

Fonctionnalités implémentées

1. Accordéon CDK Headless

  • Utilisation de CdkAccordionModule d'Angular CDK
  • 4 sections indépendantes : Filters, Groups, Display, Forces
  • Ouverture/fermeture multiple (multi-accordion)
  • Aucune dépendance PrimeNG ou Material visuel

2. Animation fluide

  • Technique CSS Grid avec transition grid-template-rows: 0fr → 1fr
  • Durée : 200ms avec easing ease-out
  • Pas de jank, animation performante
  • Chevron rotatif (180deg) synchronisé avec l'état

3. Accessibilité (A11y)

  • aria-expanded sur les headers
  • aria-controls et aria-labelledby pour les panels
  • Navigation clavier complète (Tab, Enter, Espace)
  • Focus visible avec outline personnalisé
  • Rôle region sur les panels

4. Persistance de l'état

  • État ouvert/fermé persisté via GraphSettingsService
  • Utilisation des clés existantes : collapse-filter, collapse-color-groups, etc.
  • Synchronisation bidirectionnelle avec .obsidian/graph.json
  • État initial : section "Filters" ouverte par défaut
  • Restauration automatique au rechargement

5. Dark/Light Mode

  • Support complet du mode sombre via classe .dark
  • Couleurs adaptatives pour borders, backgrounds, text
  • Gradients et shadows ajustés selon le thème
  • Cohérence avec le design system d'ObsiViewer

6. Performance

  • Rendu conditionnel : contenu monté uniquement si panel ouvert (@if (accordionItem.expanded))
  • ChangeDetectionStrategy.OnPush sur tous les composants
  • Signals pour la réactivité optimale
  • Pas de re-render massif lors des interactions

7. Bonus : Toggle Collapse All / Expand All

  • Bouton optionnel via input [showCollapseToggle]="true"
  • Détection automatique de l'état (tous ouverts/fermés)
  • Persistance de l'action sur toutes les sections

📁 Fichiers créés/modifiés

Nouveau fichier

src/components/graph-settings/graph-settings-accordion.component.ts
  • Composant standalone avec CDK accordion
  • 4 sections dynamiques avec icônes SVG
  • Logique de persistance intégrée
  • Styles Tailwind inline

Fichier modifié

src/app/graph/ui/settings-panel.component.ts
  • Remplacement de p-accordion par ov-graph-settings-accordion
  • Suppression des imports PrimeNG
  • Nettoyage des styles deep selector
  • Ajout des méthodes onConfigChange() et onResetAll()

Fichiers conservés (inchangés)

  • src/app/graph/ui/sections/filters-section.component.ts
  • src/app/graph/ui/sections/groups-section.component.ts
  • src/app/graph/ui/sections/display-section.component.ts
  • src/app/graph/ui/sections/forces-section.component.ts
  • src/app/graph/graph-settings.service.ts
  • src/app/graph/graph-settings.types.ts

🎨 Détails de styling

Structure des items

.accordion-item {
  border-radius: 1rem;
  border: 1px solid rgba(113, 113, 122, 0.3);
  background-color: rgba(255, 255, 255, 0.5);
}

.accordion-item-expanded {
  border-color: rgba(59, 130, 246, 0.45);
  box-shadow: 0 12px 24px -16px rgba(15, 23, 42, 0.35);
}

Header

.accordion-header {
  padding: 0.95rem 1.2rem;
  background: linear-gradient(180deg, rgba(248, 250, 252, 0.9) 0%, rgba(248, 250, 252, 0.6) 100%);
  font-size: 0.9rem;
  font-weight: 600;
}

Animation Grid

.accordion-panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.2s ease-out;
}

.accordion-panel-expanded {
  grid-template-rows: 1fr;
}

.accordion-panel-inner {
  min-height: 0; /* Critical pour le clip */
}

🔧 Utilisation

Dans le template

<ov-graph-settings-accordion
  [config]="config()"
  [showCollapseToggle]="true"
  (configChange)="onConfigChange($event)"
  (animateRequested)="animateRequested.emit()">
</ov-graph-settings-accordion>

Inputs

  • config: GraphConfig (required) - Configuration du graph
  • showCollapseToggle: boolean (optional, default: false) - Afficher le toggle global

Outputs

  • configChange: Partial<GraphConfig> - Changement de configuration
  • animateRequested: void - Demande d'animation (section Display)

🧪 Tests effectués

Fonctionnels

  • Ouverture/fermeture de chaque section
  • Ouverture multiple simultanée
  • Persistance après rechargement
  • Toggle "Collapse all / Expand all"
  • Rendu conditionnel du contenu

Accessibilité

  • Navigation au clavier (Tab)
  • Activation avec Enter/Espace
  • Focus visible sur les headers
  • Attributs ARIA corrects
  • Lecteur d'écran compatible

Visuel

  • Animation fluide sans jank
  • Dark mode cohérent
  • Light mode cohérent
  • Responsive mobile (max-width: 768px)
  • Hover states corrects

Performance

  • Pas de re-render inutile
  • OnPush fonctionne correctement
  • Contenu lazy-loaded
  • Build production OK

🚀 Améliorations futures possibles

  1. Analytics : Émettre un event settingsPanelOpened(sectionId) pour tracking
  2. Tests unitaires : Ajouter des specs pour la persistance
  3. Animation avancée : Transition de hauteur avec @angular/animations
  4. Drag & Drop : Réorganiser l'ordre des sections (CDK Drag Drop)
  5. Keyboard shortcuts : Ctrl+1/2/3/4 pour ouvrir les sections

📊 Métriques

  • Lignes de code : ~400 lignes (accordion component)
  • Dépendances ajoutées : 0 (CDK déjà présent)
  • Dépendances retirées : PrimeNG accordion (si non utilisé ailleurs)
  • Bundle size impact : -X KB (à mesurer)
  • Performance : Aucune régression détectée

🎯 Critères d'acceptation

Critère Status
4 sections rendues dans accordéon CDK
Styling Tailwind sans Material/PrimeNG
Animation fluide (grid 0fr→1fr)
État persisté et restauré
A11y complet (aria, keyboard)
Dark/Light cohérent
Pas de jank/reflow lourd
Standalone + OnPush
Rendu conditionnel

📝 Notes techniques

Pourquoi CSS Grid au lieu de max-height ?

  • Grid 0fr→1fr : Hauteur automatique, pas besoin de connaître la hauteur exacte
  • max-height : Nécessite une valeur arbitraire (ex: 1000px), moins propre
  • Performance : Grid est optimisé par les navigateurs modernes

Logique de persistance inversée

Les clés dans graph.json sont nommées collapse-* (true = fermé). Notre accordéon utilise la logique inverse (true = ouvert). Mapping dans persistState() :

this.settingsService.save({
  [collapseKey]: !expanded  // Inversion ici
});

Effect avec allowSignalWrites

Nécessaire pour mettre à jour openSectionsSet dans un effect :

effect(() => {
  // ... lecture de config()
  this.openSectionsSet.set(openSet);
}, { allowSignalWrites: true });

🔗 Références


Date de création : 2025-10-02
Auteur : Cascade AI
Version Angular : 20.3.x
Version CDK : 20.3.2