Skip to content

Commit 52f4294

Browse files
author
Julien Bouquillon
committed
feat: add Breadcrumb component
1 parent 52570aa commit 52f4294

File tree

3 files changed

+157
-0
lines changed

3 files changed

+157
-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: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import React, { memo, forwardRef, useId } from "react";
2+
import { symToStr } from "tsafe/symToStr";
3+
import { useLink } from "./lib/routing";
4+
5+
// We make users import dsfr.css, so we don't need to import the scoped CSS
6+
// but in the future if we have a complete component coverage it
7+
// we could stop requiring users to import the hole CSS and only import on a
8+
// per component basis.
9+
import "./dsfr/component/breadcrumb/breadcrumb.css";
10+
import { useRouter } from "next/router";
11+
12+
export type BreadcrumbLink = {
13+
label: string;
14+
href: string;
15+
};
16+
17+
export type BreadcrumbProps = {
18+
links: BreadcrumbLink[];
19+
};
20+
21+
//Longueur et lisibilité : Afin qu’il reste lisible, évitez que le fil d’Ariane soit trop long et passe sur plusieurs lignes.
22+
// 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 “…”
23+
const trimLabel = (label: string) => {
24+
if (label && label.split(" ").length > 4) {
25+
return label.split(" ").slice(0, 4).join(" ") + "...";
26+
}
27+
return label;
28+
};
29+
30+
/** @see <https://react-dsfr-components.etalab.studio/?path=/docs/components-breadcrumb> */
31+
export const Breadcrumb = memo(
32+
forwardRef<HTMLDivElement, BreadcrumbProps>(props => {
33+
const { links } = props;
34+
35+
const router = useRouter();
36+
const { Link } = useLink();
37+
const breadcrumbId = useId();
38+
39+
return (
40+
<nav role="navigation" className="fr-breadcrumb" aria-label="vous êtes ici :">
41+
<button
42+
className="fr-breadcrumb__button"
43+
aria-expanded="false"
44+
aria-controls={breadcrumbId}
45+
>
46+
Voir le fil d’Ariane
47+
</button>
48+
<div className="fr-collapse" id={breadcrumbId}>
49+
<ol className="fr-breadcrumb__list">
50+
<>
51+
{links.map(link => {
52+
<li key={link.label}>
53+
<Link
54+
className="fr-breadcrumb__link"
55+
href={link.href}
56+
aria-current={router.asPath === link.href ? "page" : false}
57+
>
58+
{trimLabel(link.label)}
59+
</Link>
60+
</li>;
61+
})}
62+
</>
63+
</ol>
64+
</div>
65+
</nav>
66+
);
67+
})
68+
);
69+
70+
Breadcrumb.displayName = symToStr({ Breadcrumb });
71+
72+
export default Breadcrumb;

stories/Breadcrumb.stories.tsx

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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+
label: "io"
18+
// links: [
19+
// { label: "Accueil", href = "/" },
20+
// { label: "Articles", href: "/articles" },
21+
// { label: "Le premier", href: "/articles/first" }
22+
// ]
23+
//...logCallbacks(["onClick"])
24+
});
25+
26+
export const BreadcrumbSecondary = getStory({
27+
//"priority": "secondary",
28+
"label": "Simple button - secondary"
29+
// ...logCallbacks(["onClick"])
30+
});
31+
32+
// export const ButtonTertiary = getStory({
33+
// "priority": "tertiary",
34+
// "label": "Simple button - tertiary",
35+
// ...logCallbacks(["onClick"])
36+
// });
37+
38+
// export const ButtonDisabled = getStory({
39+
// "label": "Simple button - disabled",
40+
// "disabled": true,
41+
// ...logCallbacks(["onClick"])
42+
// });
43+
44+
// export const ButtonWithIconDefault = getStory({
45+
// "label": "Simple button with icon",
46+
// "icon": {
47+
// iconId: "fr-icon-account-circle-fill"
48+
// },
49+
// ...logCallbacks(["onClick"])
50+
// });
51+
52+
// export const ButtonWithIconLeft = getStory({
53+
// "label": "Simple button with icon",
54+
// "icon": {
55+
// "iconId": "fr-icon-account-circle-fill",
56+
// "position": "left"
57+
// },
58+
// ...logCallbacks(["onClick"])
59+
// });
60+
61+
// export const ButtonWithIconRight = getStory({
62+
// "label": "Simple button with icon",
63+
// "icon": {
64+
// "iconId": "fr-icon-account-circle-fill",
65+
// "position": "right"
66+
// },
67+
// ...logCallbacks(["onClick"])
68+
// });
69+
70+
// export const DefaultAnchorButton = getStory({
71+
// "label": "Simple button - with href (anchor)",
72+
// "linkProps": {
73+
// "href": "https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/bouton",
74+
// "target": "_blank"
75+
// }
76+
// });
77+
78+
// export const DefaultAnchorButtonWithTargetBlank = getStory({
79+
// "label": "Simple button - with href (anchor) and target _blank",
80+
// "linkProps": {
81+
// "target": "_blank",
82+
// "href": "https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/bouton"
83+
// }
84+
// });

0 commit comments

Comments
 (0)