Skip to content

Commit c7df287

Browse files
authored
Merge pull request #24 from codegouvfr/breadcrumb
feat: add Breadcrumb component
2 parents 52570aa + ecdcca4 commit c7df287

File tree

3 files changed

+134
-0
lines changed

3 files changed

+134
-0
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@
131131
"./Footer": "./dist/Footer.js",
132132
"./Display": "./dist/Display.js",
133133
"./Button": "./dist/Button.js",
134+
"./Breadcrumb": "./dist/Breadcrumb.js",
134135
"./Badge": "./dist/Badge.js",
135136
"./Alert": "./dist/Alert.js",
136137
"./Accordion": "./dist/Accordion.js"

src/Breadcrumb.tsx

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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;

stories/Breadcrumb.stories.tsx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { Breadcrumb } from "../dist/Breadcrumb";
2+
import { sectionName } from "./sectionName";
3+
import { getStoryFactory } from "./getStory";
4+
5+
const { meta, getStory } = getStoryFactory({
6+
sectionName,
7+
"wrappedComponent": { Breadcrumb },
8+
"description": `
9+
- [See DSFR documentation](https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/fil-d-ariane)
10+
- [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/Breadcrumb.tsx)`,
11+
"disabledProps": ["lang"]
12+
});
13+
14+
export default meta;
15+
16+
export const Default = getStory({
17+
links: [
18+
{ text: "Accueil", linkProps: { href: "/" } },
19+
{ text: "Page 1", linkProps: { href: "/page1" } }
20+
]
21+
});
22+
23+
export const ActiveBreadcrumb = getStory({
24+
links: [
25+
{ text: "Accueil", linkProps: { href: "/" } },
26+
{ text: "Page 1", linkProps: { href: "/page1" }, isActive: true }
27+
]
28+
});
29+
30+
export const LongBreadcrumb = getStory({
31+
links: [
32+
{ text: "Accueil", linkProps: { href: "/" } },
33+
{ text: "Page de démo 1", linkProps: { href: "/page1" } },
34+
{ text: "Page de démo 2", linkProps: { href: "/page2" } },
35+
{ text: "Page de démo 3", linkProps: { href: "/page3" } },
36+
{ text: "Page de démo 4", linkProps: { href: "/page4" } },
37+
{ text: "Page de démo 5", linkProps: { href: "/page5" } },
38+
{ text: "Page de démo 6", linkProps: { href: "/page6" } },
39+
{ text: "Page de démo 7", linkProps: { href: "/page7" }, isActive: true }
40+
]
41+
});
42+
43+
export const LongLabelBreadcrumb = getStory({
44+
links: [
45+
{ text: "Accueil", linkProps: { href: "/" } },
46+
{ text: "Page 1", linkProps: { href: "/page1" } },
47+
{ text: "Un article très très long", linkProps: { href: "/article1" }, isActive: true }
48+
]
49+
});

0 commit comments

Comments
 (0)