Skip to content

Commit ef35744

Browse files
committed
2 parents fce523c + eb2dada commit ef35744

File tree

5 files changed

+216
-0
lines changed

5 files changed

+216
-0
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,13 +124,15 @@
124124
"./mui": "./dist/mui.js",
125125
"./lib": "./dist/lib/index.js",
126126
"./Tabs": "./dist/Tabs.js",
127+
"./Stepper": "./dist/Stepper.js",
127128
"./SkipLinks": "./dist/SkipLinks.js",
128129
"./Notice": "./dist/Notice.js",
129130
"./Highlight": "./dist/Highlight.js",
130131
"./Header": "./dist/Header/index.js",
131132
"./Footer": "./dist/Footer.js",
132133
"./Display": "./dist/Display.js",
133134
"./Button": "./dist/Button.js",
135+
"./Breadcrumb": "./dist/Breadcrumb.js",
134136
"./Badge": "./dist/Badge.js",
135137
"./Alert": "./dist/Alert.js",
136138
"./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;

src/Stepper.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React, { memo, forwardRef } from "react";
2+
import { symToStr } from "tsafe/symToStr";
3+
import { assert } from "tsafe/assert";
4+
import type { Equals } from "tsafe";
5+
import { fr } from "./lib";
6+
import { cx } from "./lib/tools/cx";
7+
8+
// We make users import dsfr.css, so we don't need to import the scoped CSS
9+
// but in the future if we have a complete component coverage it
10+
// we could stop requiring users to import the hole CSS and only import on a
11+
// per component basis.
12+
import "./dsfr/component/stepper/stepper.css";
13+
14+
export type StepperProps = {
15+
className?: string;
16+
currentStep: number;
17+
steps: number;
18+
title: string;
19+
nextTitle?: string;
20+
};
21+
22+
/** @see <https://react-dsfr-components.etalab.studio/?path=/docs/components-stepper> */
23+
export const Stepper = memo(
24+
forwardRef<HTMLDivElement, StepperProps>((props, ref) => {
25+
const { className, currentStep, steps, title, nextTitle, ...rest } = props;
26+
27+
assert<Equals<keyof typeof rest, never>>();
28+
29+
return (
30+
<div className={cx(fr.cx("fr-stepper"), className)} ref={ref}>
31+
<h2 className="fr-stepper__title">
32+
<span className="fr-stepper__state">
33+
Étape {currentStep} sur {steps}
34+
</span>
35+
{title}
36+
</h2>
37+
<div
38+
className="fr-stepper__steps"
39+
data-fr-current-step={currentStep}
40+
data-fr-steps={steps}
41+
></div>
42+
{nextTitle && (
43+
<p className="fr-stepper__details">
44+
<span className="fr-text--bold">Étape suivante :</span> {nextTitle}
45+
</p>
46+
)}
47+
</div>
48+
);
49+
})
50+
);
51+
52+
Stepper.displayName = symToStr({ Stepper });
53+
54+
export default Stepper;

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+
});

stories/Stepper.stories.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Stepper } from "../dist/Stepper";
2+
import { sectionName } from "./sectionName";
3+
import { getStoryFactory } from "./getStory";
4+
5+
const { meta, getStory } = getStoryFactory({
6+
sectionName,
7+
"wrappedComponent": { Stepper },
8+
"description": `
9+
- [See DSFR documentation](https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/indicateur-d-etape)
10+
- [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/Stepper.tsx)`,
11+
"disabledProps": ["lang"]
12+
});
13+
14+
export default meta;
15+
16+
export const Default = getStory({
17+
steps: 3,
18+
currentStep: 1,
19+
title: "Titre de l’étape en cours",
20+
nextTitle: "Titre de la prochaine étape"
21+
});
22+
23+
export const StepperWithoutNext = getStory({
24+
steps: 4,
25+
currentStep: 4,
26+
title: "Titre de la dernière étape en cours"
27+
});

0 commit comments

Comments
 (0)