Skip to content

feat(slider): add accessible Swiper carousel foundation - #197

Open
cedric07 wants to merge 1 commit into
mainfrom
feat/a11y-slider
Open

cedric07 wants to merge 1 commit into
mainfrom
feat/a11y-slider

Conversation

@cedric07

@cedric07 cedric07 commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Reprise / adaptation de la PR du BFF : BeAPI/beapi-frontend-framework#510

Contexte et objectif

Cette évolution ajoute au thème Be API Frontend Framework une base réutilisable pour les carrousels basés sur Swiper (v14). L’objectif est de centraliser la configuration (modules, navigation, messages d’accessibilité), d’aligner le markup et les styles sur les conventions du thème, et de préparer l’internationalisation des chaînes exposées aux technologies d’assistance.

Il ne s’agit pas d’un bloc Gutenberg ni d’un composant visuel figé : c’est une brique technique (classe JS, partial PHP, feuille SCSS) que les projets enfants ou les futurs blocs/composants pourront brancher selon leurs besoins (slidesPerView, pagination, autoplay, etc.).

Dépendances

Package Rôle
swiper (^14.3.0) Bibliothèque de carrousel (import ESM + modules)
@wordpress/i18n Traduction des messages A11y côté JavaScript

Les styles de base Swiper sont importés dans le SCSS du thème (swiper/css, swiper/css/a11y).

Fichiers livrés

src/js/classes/Slider.js

Wrapper fin autour de Swiper, héritant de AbstractDomElement comme les autres classes DOM du thème.

Comportement par défaut

  • Modules toujours actifs : A11y et Navigation.
  • Les modules passés dans options.modules sont ajoutés sans écraser les défauts (évite la perte d’A11y/Navigation quand l’utilisateur ne redéclare pas tout).
  • Messages d’accessibilité Swiper traduits via __() et le text domain beapi-frontend-framework (prevSlideMessage, nextSlideMessage, première/dernière diapositive, pagination, libellé de slide).
  • Callback onChange (option du wrapper) branché sur l’événement slideChange de Swiper.

Options spécifiques au wrapper

Option Défaut Description
hideNavigationFromA11y false Si true : boutons préc./suiv. retirés de l’arbre d’accessibilité (tabindex="-1", aria-hidden="true") et suppression de .swiper-notification, utile quand chaque slide est déjà navigable au clavier (cartes focusables). Réappliqué après transitionEnd car Swiper réinitialise le tabindex.
fixLastSlideActiveOnEnd false Corrige .swiper-slide-active en fin de parcours pour slidesPerView: 'auto' ou des fractions « non demi » (ex. 2,75), où la dernière slide peut rester partiellement visible sans la classe active.
options voir Slider.defaults Objet natif SwiperOptions fusionné avec les défauts (dont a11y).

Accessibilité intégrée

  • Si les slides sont des <li>, slideRole est vidé pour ne pas imposer role="group" sur des éléments de liste (sémantique liste préservée pour les lecteurs d’écran). Surcharge possible via options.a11y.
  • Si la navigation reste exposée aux AT, la région live .swiper-notification est conservée pour les annonces première / précédente / suivante / dernière diapositive au clavier.
  • wrapperLiveRegion : laissé au comportement Swiper par défaut (polite sans autoplay, off avec autoplay), surcharge possible via options.a11y.

Initialisation

  • Slider.init('.selector', { … }) (pattern AbstractDomElement) ou new Slider(element, { … }).
  • getInstance() renvoie l’instance Swiper native pour les API avancées.

components/parts/common/swiper-controls.php

Partial PHP pour les contrôles de navigation :

  • Structure BEM : .swiper__controls > .swiper__buttons > boutons .swiper-button-prev / .swiper-button-next avec classe thème .swiper__button.
  • Libellés aria-label traduits via esc_attr_e() (Previous slide / Next slide, même msgid que le JS) ; Swiper peut les ajuster au runtime selon sa config A11y.

À inclure dans le conteneur .swiper :

get_template_part( 'components/parts/common/swiper', 'controls' );

Markup minimal attendu côté slide :

<div class="swiper">
  <div class="swiper-wrapper">
    <div class="swiper-slide">…</div>
  </div>
  <!-- partial swiper-controls -->
</div>

La navigation Swiper doit pointer vers les sélecteurs des boutons (ex. navigation: { prevEl: '.swiper-button-prev', nextEl: '.swiper-button-next' }), en général dans le scope du carrousel concerné.

src/scss/10-vendor/_swiper.scss

Couche de style projet par-dessus Swiper :

  • Couleur thème via --swiper-theme-color (variable SCSS primaire).
  • Zone contrôles centrée, masquée si Swiper verrouille la navigation (:has(.swiper-button-lock)).
  • Boutons 44×44 px (cible tactile confortable), fond circulaire, état désactivé (opacité + cursor: not-allowed).
  • Flèche précédente retournée en scaleX(-1).
  • Modificateur .swiper-overflow-visible pour déborder le viewport si besoin.
  • Reset liste sur .swiper-wrapper (padding, margin, list-style) pour usage avec <ul> / <li>.

Import ajouté dans style.scss et editor.scss pour cohérence front et éditeur.

inc/Services/Assets.php

  • Dépendance wp-i18n ajoutée au script principal scripts (en plus de jQuery).
  • wp_set_script_translations sur scripts avec le domaine beapi-frontend-framework et le dossier languages/, pour que les chaînes __() du bundle JS soient servies en .json (format Jed) côté WordPress.

Fichiers de traduction

  • Entrées ajoutées dans beapi-frontend-framework.pot et fr_FR.po pour les six messages A11y du slider.
  • Fichier JSON généré beapi-frontend-framework-fr_FR-*.json (hash lié au fichier dist/app.js après build).

Workflow traduction : après modification des chaînes dans Slider.js, rebuild du JS puis régénération / mise à jour des fichiers .pot, .po et JSON (WP-CLI ou outil du projet).

Exemple d’utilisation côté JavaScript

import Slider from './classes/Slider'
import { Pagination } from 'swiper/modules'

Slider.init('.my-slider', {
  hideNavigationFromA11y: true,
  fixLastSlideActiveOnEnd: true,
  onChange() {
    // Réagir au changement de slide (this = instance Slider)
  },
  options: {
    slidesPerView: 'auto',
    modules: [Pagination],
    navigation: {
      prevEl: '.my-slider .swiper-button-prev',
      nextEl: '.my-slider .swiper-button-next',
    },
    a11y: {
      // Optionnel : message spécifique à un contexte métier
      nextSlideMessage: 'Actualité suivante',
    },
  },
})

Les modules optionnels (Pagination, Autoplay, etc.) s’importent depuis swiper/modules et se déclarent dans options.modules ; A11y et Navigation restent présents sauf configuration Swiper explicite qui les désactiverait.

Points d’attention pour les intégrateurs

  1. Build : exécuter le build front (npm run build ou équivalent) pour que dist/app.js et le hash du fichier JSON de traduction restent alignés.
  2. Sélecteurs de navigation : en cas de plusieurs carrousels sur une page, cibler les boutons dans chaque instance (sélecteur scoped) pour éviter que Swiper ne lie tous les carrousels aux mêmes contrôles.
  3. Slides en liste : pour un carrousel sémantique <ul class="swiper-wrapper"> / <li class="swiper-slide">, le wrapper gère déjà slideRole ; conserver une structure HTML valide.
  4. Choix hideNavigationFromA11y : à activer lorsque la navigation prev/next est purement visuelle et que l’usage clavier se fait dans le contenu des slides ; à laisser à false si les boutons doivent rester utilisables et annoncés par les AT.
  5. Partial PHP : les aria-label partagent les mêmes chaînes que Slider.defaults ; recompiler le .mo après mise à jour du .po si besoin.

Synthèse

Cette PR pose les fondations Swiper + a11y + i18n + styles thème sans imposer un composant métier unique. Les prochaines étapes typiques sur un projet : brancher Slider.init depuis le point d’entrée JS du composant concerné, fournir le markup slide + partial contrôles, et étendre les options Swiper (pagination, breakpoints, loop, etc.) au cas par cas.


Note

Low Risk
Additive theme infrastructure (new deps, assets, and i18n) with no changes to auth or core business logic; integrators must wire markup and scoped navigation selectors correctly.

Overview
Introduces a reusable Swiper v14 carousel foundation for child blocks and components—not a finished Gutenberg block.

A new Slider JS class wraps Swiper with default A11y and Navigation modules, translated screen-reader strings via @wordpress/i18n, and theme-specific options (hideNavigationFromA11y, fixLastSlideActiveOnEnd, list-item slide semantics). Slider is exposed on window.beapi.classes for partial assets.

PHP adds swiper-controls.php (prev/next markup with translated aria-labels). SCSS layers theme styles over Swiper base CSS in front and editor bundles. Assets.php registers wp-i18n on the main script and calls wp_set_script_translations for beapi-blocks-theme; POT/PO/JSON include the six slider a11y strings (with French translations). ESLint gains browser: true. Dependencies: swiper, @wordpress/i18n.

Reviewed by Cursor Bugbot for commit dcf20ed. Bugbot is set up for automated code reviews on this repo. Configure here.

Align BBT with BFF slider base: Slider class, controls partial, styles, and script translations for a11y strings.

Co-authored-by: Cursor <cursoragent@cursor.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit dcf20ed. Configure here.

@@ -0,0 +1 @@
{"translation-revision-date":"2026-04-10 16:29+0200","generator":"WP-CLI\/2.12.0","source":"dist\/app.js","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"fr_FR","plural-forms":"nplurals=2; plural=(n > 1);"},"Previous slide":["Diapositive pr\u00e9c\u00e9dente"],"Next slide":["Diapositive suivante"],"This is the first slide":["C'est la premi\u00e8re diapositive"],"This is the last slide":["C'est la derni\u00e8re diapositive"],"Go to slide {{index}}":["Aller \u00e0 la diapositive {{index}}"],"{{index}} of {{slidesLength}}":["{{index}} sur {{slidesLength}}"]}}} No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JS slider translations miss production file

Medium Severity

The Jed file is hashed from dist/app.js, but production webpack emits app-min.js. wp_set_script_translations looks up JSON by MD5 of the enqueued src, and core only strips a .min.js suffix, not -min.js. In production, __() in Slider.js stays English and Swiper overwrites the PHP-translated control labels with those strings.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit dcf20ed. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant