|
| 1 | +import React, { memo, forwardRef, useId } from "react"; |
| 2 | +import { symToStr } from "tsafe/symToStr"; |
| 3 | +import { RegisteredLinkProps, useLink } from "./lib/routing"; |
| 4 | +import { fr } from "./lib"; |
| 5 | +import { cx } from "./lib/tools/cx"; |
| 6 | + |
| 7 | +// We make users import dsfr.css, so we don't need to import the scoped CSS |
| 8 | +// but in the future if we have a complete component coverage it |
| 9 | +// we could stop requiring users to import the hole CSS and only import on a |
| 10 | +// per component basis. |
| 11 | +import "./dsfr/component/breadcrumb/breadcrumb.css"; |
| 12 | + |
| 13 | +export type BreadcrumbProps = { |
| 14 | + className?: string; |
| 15 | + links: BreadcrumbProps.Link[]; |
| 16 | +}; |
| 17 | + |
| 18 | +export namespace BreadcrumbProps { |
| 19 | + export type Link = { |
| 20 | + text: string; |
| 21 | + linkProps: RegisteredLinkProps; |
| 22 | + isActive?: boolean; |
| 23 | + }; |
| 24 | +} |
| 25 | + |
| 26 | +//Longueur et lisibilité : Afin qu’il reste lisible, évitez que le fil d’Ariane soit trop long et passe sur plusieurs lignes. |
| 27 | +// Si les titres de page de votre site sont longs, nous conseillons de n’afficher que les 4 premiers mots du nom de la page courante et d’indiquer que l’élément est tronqué par l’affichage de “…” |
| 28 | +const trimText = (label: string) => { |
| 29 | + if (label && label.split(" ").length > 4) { |
| 30 | + return label.split(" ").slice(0, 4).join(" ") + "..."; |
| 31 | + } |
| 32 | + return label; |
| 33 | +}; |
| 34 | + |
| 35 | +/** @see <https://react-dsfr-components.etalab.studio/?path=/docs/components-breadcrumb> */ |
| 36 | +export const Breadcrumb = memo( |
| 37 | + forwardRef<HTMLDivElement, BreadcrumbProps>((props, ref) => { |
| 38 | + const { links, className, ...rest } = props; |
| 39 | + |
| 40 | + const { Link } = useLink(); |
| 41 | + const breadcrumbId = useId(); |
| 42 | + return ( |
| 43 | + <nav |
| 44 | + ref={ref} |
| 45 | + role="navigation" |
| 46 | + className={cx(fr.cx("fr-breadcrumb"), className)} |
| 47 | + aria-label="vous êtes ici :" |
| 48 | + {...rest} |
| 49 | + > |
| 50 | + <button |
| 51 | + className="fr-breadcrumb__button" |
| 52 | + aria-expanded="false" |
| 53 | + aria-controls={breadcrumbId} |
| 54 | + > |
| 55 | + Voir le fil d’Ariane |
| 56 | + </button> |
| 57 | + <div className="fr-collapse" id={breadcrumbId}> |
| 58 | + <ol className="fr-breadcrumb__list"> |
| 59 | + <> |
| 60 | + {links.map(link => ( |
| 61 | + <li key={link.linkProps.href}> |
| 62 | + <Link |
| 63 | + {...link.linkProps} |
| 64 | + className={cx( |
| 65 | + fr.cx("fr-breadcrumb__link"), |
| 66 | + link.linkProps.className |
| 67 | + )} |
| 68 | + aria-current={link.isActive ? "page" : undefined} |
| 69 | + > |
| 70 | + {trimText(link.text)} |
| 71 | + </Link> |
| 72 | + </li> |
| 73 | + ))} |
| 74 | + </> |
| 75 | + </ol> |
| 76 | + </div> |
| 77 | + </nav> |
| 78 | + ); |
| 79 | + }) |
| 80 | +); |
| 81 | + |
| 82 | +Breadcrumb.displayName = symToStr({ Breadcrumb }); |
| 83 | + |
| 84 | +export default Breadcrumb; |
0 commit comments