Skip to content

feat: add <ContentMedia /> component (img, svg, iframe, video, audio) - #512

Open
habbichelotfi wants to merge 3 commits into
codegouvfr:mainfrom
habbichelotfi:feat/content-media-component
Open

feat: add <ContentMedia /> component (img, svg, iframe, video, audio)#512
habbichelotfi wants to merge 3 commits into
codegouvfr:mainfrom
habbichelotfi:feat/content-media-component

Conversation

@habbichelotfi

@habbichelotfi habbichelotfi commented Aug 28, 2026

Copy link
Copy Markdown

Closes #511

Contexte

Cette PR implémente le composant ContentMedia conformément à la structure DSFR pour l’intégration de médias accessibles et responsives.

Ce qui est inclus

  • ajout du composant ContentMedia
  • support des images
  • support de la légende (caption)
  • support du lien de légende (captionLinkProps)
  • documentation Storybook
  • typage TypeScript complet des props

Détails d’implémentation

  • composant basé sur la structure DSFR figure / figcaption
  • props typées pour guider l’usage et éviter les combinaisons invalides
  • exemples Storybook pour montrer les cas d’usage principaux
  • mise à jour des exemples de liens externes avec attributs de sécurité (target="_blank" + rel="noopener noreferrer")

Vérifications

  • TypeScript: pas d’erreurs sur les fichiers modifiés
  • Storybook: stories ContentMedia ajoutées et documentées
  • Accessibilité: structure sémantique DSFR respectée

@kevbarns

Copy link
Copy Markdown
Collaborator

@habbichelotfi Merci pour cette PR. Le découpage des props par type discriminé est le bon choix, et les alternatives <p> dans <video> / <audio> sont conformes à la doc DSFR (src/dsfr/component/content/_part/doc/code/index.md).

Vérifié en local sur ee6579b : yarn build, tsc -p src, prettier --list-different et yarn test (103/103) passent. La CI GitHub n'a en revanche jamais tourné : le run est en action_required (PR de fork, approbation d'un mainteneur requise).

Bloquant

ContentMedia n'est pas déclaré dans src/bin/only-include-used-components.ts. resolveModuleIdToDsfrComponents({ moduleId: "ContentMedia" }) renvoie undefined (vérifié à l'exécution), ce qui déclenche le fail-safe : dès qu'un projet importe le composant, le trimming CSS est désactivé pour tout le run et un console.warn est émis. Il faut ajouter "ContentMedia": ["content", "link"] à REACT_DSFR_MODULE_TO_DSFR_COMPONENTS — la doc DSFR liste Content (obligatoire) et Link (si lien dans la légende).

Le lien de légende peut être rendu sans nom accessible. captionLinkProps et captionLinkLabel sont indépendants : passer le premier sans le second produit <a class="fr-link"></a>. Détail en inline.

À discuter

SVG non enveloppé dans fr-content-media__img. La doc DSFR écrit « Un élément <div> de classe fr-content-media__img contient l'image. L'image est un élément <img> avec la classe fr-responsive-img, ou un <svg> », et example/component/content/index.html enveloppe bien le SVG — seul le snippet de la doc ne le fait pas. Ce wrapper porte width: 100% et sert d'ancrage aux variantes de taille.

Variantes de taille et ratios absents : pas de prop pour fr-content-media--sm / --lg, et les fr-ratio-* ne passent que par imgProps.className, ce qui n'est documenté nulle part.

Optionnel

Le diff modifie HighlightExample dans test/integration/vite/src/Home.tsx (apostrophes typographiques remplacées par des apostrophes droites), sans rapport avec le composant.

Rien à signaler côté sécurité.

Comment thread src/ContentMedia.tsx Outdated
Comment on lines +23 to +26
/** Text of the optional link inside the figcaption. */
captionLinkLabel?: ReactNode;
/** Props of the optional link inside the figcaption. */
captionLinkProps?: RegisteredLinkProps;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ces deux props sont indépendantes alors qu'elles n'ont de sens qu'ensemble : captionLinkProps sans captionLinkLabel rend <a class="fr-link"></a>, un lien sans nom accessible (RGAA 6.1, WCAG 2.4.4). Le composant n'étant pas encore publié, l'appariement peut se faire au niveau du type sans coût de migration.

Suggested change
/** Text of the optional link inside the figcaption. */
captionLinkLabel?: ReactNode;
/** Props of the optional link inside the figcaption. */
captionLinkProps?: RegisteredLinkProps;
/** Label and props of the optional link inside the figcaption. Both go together. */
captionLink?: {
label: ReactNode;
linkProps: RegisteredLinkProps;
};

La suggestion ne peut pas porter le site de rendu : il faut aussi remplacer les lignes 178-181 par un test sur captionLink et adapter les stories ainsi que test/integration/vite/src/Home.tsx.

@kevbarns

kevbarns commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Une info qui te concerne, @habbichelotfi. Beau composant, la couverture des cinq types de média est complète.

#515 ajoute un test d'exhaustivité sur REACT_DSFR_MODULE_TO_DSFR_COMPONENTS dans only-include-used-components. Tout module de src/ absent de cette map résout à undefined, ce qui déclenche un fail-safe « on inclut tout » : le script émet un avertissement, sort en 0, et ne rogne plus aucun CSS pour tous les projets qui l'importent. ContentMedia est le cas qui a révélé le trou, donc une fois #515 mergée cette PR aurait échoué en CI jusqu'à ce que la map reçoive une entrée.

J'ai poussé cette entrée sur ta branche dans d095b52 plutôt que de te laisser avec une CI rouge :

"ContentMedia": ["content", "link"],

link est là parce que la légende rend un fr-link via <Link className={fr.cx("fr-link")}>. Les utilitaires d'espacement des stories viennent de core, toujours inclus, donc ils n'ont besoin de rien. Vérifié en cherry-pickant les tests de #515 sur ta branche : ils échouent en nommant ContentMedia sans l'entrée, et passent avec.

Un changement connexe bon à savoir : #515 retire aussi fr-responsive-img et fr-responsive-vid des préfixes de détection de content, puisque leurs règles de base vivent dans core et que toutes les règles de content.css qui les utilisent sont portées par .fr-content-media. Cela n'affecte pas ce composant, qui rend fr-content-media lui-même.

Édité pour traduction. Deux références mises à jour au passage : #515 a été mergée depuis, le 30/08, et le commit cité était 9b751394, remplacé par d095b52 lors du rebase de la branche. L'entrée se trouve désormais dans src/bin/only-include-css-of-used-components.ts, le fichier ayant été renommé sur main.

@kevbarns

Copy link
Copy Markdown
Collaborator

Correction sur mon commentaire précédent : main a bougé depuis la base de cette PR (621ee4ad), et deux choses changent après rebase.

Le fichier a été renommé en src/bin/only-include-css-of-used-components.ts par 52a77298. C'est là qu'il faut ajouter l'entrée, la map garde son nom REACT_DSFR_MODULE_TO_DSFR_COMPONENTS.

Surtout, 6db7255d a ajouté test/runtime/scripts/onlyIncludeUsedComponents/publicModuleCoverage.test.ts, qui asserte que tout module exposé par src/ est couvert par la map. L'entrée manquante n'est donc plus une dégradation silencieuse une fois la branche à jour : yarn test échoue, en nommant ContentMedia (from src/ContentMedia): resolves to undefined. Un rebase sur main est à prévoir.

lotfi-habbiche and others added 2 commits August 31, 2026 14:32
A module absent from REACT_DSFR_MODULE_TO_DSFR_COMPONENTS resolves to
undefined, which trips the include-everything fail-safe: the script warns,
exits 0, and trims no CSS at all for any project importing it.

content for the fr-content-media markup, link for the fr-link the caption
renders.
@habbichelotfi
habbichelotfi force-pushed the feat/content-media-component branch from b7e2d6a to d095b52 Compare August 31, 2026 12:32
@habbichelotfi

Copy link
Copy Markdown
Author

@kevbarns merci pour le retour,
J’ai rebasé la branche sur main, les tests passent bien et j’ai effectué les modifications que tu avais demandées.
Test Files 27 passed (27)
Tests 108 passed (108)
Start at 16:11:38
Duration 4.18s (transform 1.45s, setup 3ms, collect 6.30s, tests 735ms)

Done in 5.74s.
Dis-moi si j’ai oublié quelque chose ou s’il reste un point à traiter.

- Add support for sm and lg media sizes
- Add ratio prop for image and SVG media
- Wrap SVGs in media container to support DSFR ratio utilities
- Simplify caption link API with a single captionLink prop
- Enforce accessible caption links by requiring label and link props together
- Update stories and integration examples
@habbichelotfi
habbichelotfi force-pushed the feat/content-media-component branch from 3f81660 to ccb3442 Compare August 31, 2026 14:14
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.

Implement ContentMedia component

3 participants