Skip to content

Commit 8f5eb63

Browse files
author
Julien Bouquillon
authored
Merge branch 'main' into card
Signed-off-by: Julien Bouquillon <julien.bouquillon@sg.social.gouv.fr>
2 parents 7945e63 + fab3b16 commit 8f5eb63

33 files changed

+803
-298
lines changed

COMPONENTS.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Components implementation status
2+
3+
- [x] Accordion
4+
- [ ] File upload
5+
- [x] Alert
6+
- [x] Badge
7+
- [x] Notice
8+
- [x] Button
9+
- [x] Buttons group
10+
- [ ] France Connect button
11+
- [ ] Radio button
12+
- [ ] Radio rich
13+
- [ ] Checkbox
14+
- [ ] Cards
15+
- [x] Quote
16+
- [ ] Media
17+
- [x] Header
18+
- [x] Breadcrumb
19+
- [ ] Consent banner
20+
- [ ] Favicon (?)
21+
- [x] Stepper
22+
- [ ] Toggle switch
23+
- [ ] Follow
24+
- [ ] Link
25+
- [x] SkipLinks
26+
- [ ] Select
27+
- [ ] Side menu
28+
- [ ] Call out
29+
- [x] Highlight
30+
- [ ] Modal
31+
- [x] Main navigation
32+
- [x] Tabs
33+
- [ ] Pagination
34+
- [x] Display
35+
- [ ] Share
36+
- [x] Footer
37+
- [ ] Translate
38+
- [ ] Summary
39+
- [ ] Table
40+
- [ ] Tag
41+
- [ ] Download
42+
- [ ] Transcription
43+
- [ ] Tile

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
3131
This module is a wrapper/compatibility layer for [@gouvfr/dsfr](https://github.com/GouvernementFR/dsfr), the vanilla JS/CSS implementation of the DSFR.
3232

33+
[Preview.webm](https://user-images.githubusercontent.com/6702424/208798079-52c39962-94a3-4ff5-adbc-800d47b50757.webm)
34+
35+
[Youtube link](https://youtu.be/5q88JgXUAY4)
36+
3337
> For TypeScript and JavaScript projects.
3438
3539
- [x] Fully TypeSafe, well documented API.
@@ -38,7 +42,7 @@ This module is a wrapper/compatibility layer for [@gouvfr/dsfr](https://github.c
3842
- [x] Exactly the same look and feel than with [@gouvfr/dsfr](https://www.npmjs.com/package/@gouvfr/dsfr).
3943
- [x] No [white flash when reloading in SSR setup](https://github.com/codegouvfr/@codegouvfr/react-dsfr/issues/2#issuecomment-1257263480).
4044
- [x] [Perfect integration with all major React framework: Next.js, Create React App, Vue](https://react-dsfr.etalab.studio/).
41-
- [ ] All [the components](https://www.systeme-de-design.gouv.fr/elements-d-interface) are implemented (14/42)
45+
- [ ] All [the components](https://www.systeme-de-design.gouv.fr/elements-d-interface) are implemented (14/42, [see details](COMPONENTS.md))
4246
- [x] Three shakable distribution, cherry pick the components you import. (It's not all in a big .js bundle)
4347
- [x] Optional integration with [MUI](https://mui.com/). If you use MUI components they will
4448
be automatically adapted to look like [DSFR components](https://www.systeme-de-design.gouv.fr/elements-d-interface). See [documentation](https://react-dsfr.etalab.studio/mui-integration).

package.json

100755100644
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@codegouvfr/react-dsfr",
3-
"version": "0.5.1",
3+
"version": "0.10.0",
44
"description": "French State Design System React integration library",
55
"repository": {
66
"type": "git",
@@ -74,7 +74,7 @@
7474
"@emotion/react": "^11.10.4",
7575
"@emotion/styled": "^11.10.4",
7676
"@gouvfr/dsfr": "1.8.4",
77-
"@mui/material": "5.10.7",
77+
"@mui/material": "^5.11.0",
7878
"@storybook/addon-actions": "^6.5.13",
7979
"@storybook/addon-essentials": "^6.5.13",
8080
"@storybook/addon-interactions": "^6.5.13",
@@ -126,12 +126,14 @@
126126
"./Tabs": "./dist/Tabs.js",
127127
"./Stepper": "./dist/Stepper.js",
128128
"./SkipLinks": "./dist/SkipLinks.js",
129+
"./Quote": "./dist/Quote.js",
129130
"./Notice": "./dist/Notice.js",
130131
"./Highlight": "./dist/Highlight.js",
131132
"./Header": "./dist/Header/index.js",
132133
"./Footer": "./dist/Footer.js",
133134
"./Display": "./dist/Display.js",
134135
"./Card": "./dist/Card.js",
136+
"./ButtonsGroup": "./dist/ButtonsGroup.js",
135137
"./Button": "./dist/Button.js",
136138
"./Breadcrumb": "./dist/Breadcrumb.js",
137139
"./Badge": "./dist/Badge.js",

src/Badge.tsx

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,27 @@ import { assert } from "tsafe/assert";
44
import type { Equals } from "tsafe";
55
import { fr } from "./lib";
66
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/badge/badge.css";
7+
import type { AlertProps } from "./Alert";
138

149
export type BadgeProps = {
1510
className?: string;
16-
severity?: BadgeProps.Severity;
17-
isSmall?: boolean;
11+
severity?: AlertProps.Severity | "new";
12+
small?: boolean;
1813
noIcon?: boolean;
1914
label: NonNullable<ReactNode>;
2015
};
2116

22-
export namespace BadgeProps {
23-
export type Severity = "info" | "success" | "error" | "warning" | "new";
24-
}
25-
2617
/** @see <https://react-dsfr-components.etalab.studio/?path=/docs/components-badge> */
2718
export const Badge = memo(
2819
forwardRef<HTMLDivElement, BadgeProps>((props, ref) => {
29-
const { className, severity, label, isSmall, noIcon, ...rest } = props;
20+
const {
21+
className,
22+
severity,
23+
label,
24+
small: isSmall = false,
25+
noIcon = false,
26+
...rest
27+
} = props;
3028

3129
assert<Equals<keyof typeof rest, never>>();
3230

@@ -35,9 +33,9 @@ export const Badge = memo(
3533
className={cx(
3634
fr.cx(
3735
"fr-badge",
38-
{ [`fr-badge--${severity}`]: severity != null },
36+
severity !== undefined && `fr-badge--${severity}`,
3937
{ "fr-badge--sm": isSmall },
40-
{ "fr-badge--no-icon": noIcon || severity == null }
38+
{ "fr-badge--no-icon": noIcon || severity === undefined }
4139
),
4240
className
4341
)}

src/Breadcrumb.tsx

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,71 @@
1-
import React, { memo, forwardRef, useId } from "react";
1+
import React, { memo, forwardRef, useId, ReactNode } from "react";
22
import { symToStr } from "tsafe/symToStr";
3-
import { RegisteredLinkProps, useLink } from "./lib/routing";
3+
import { assert } from "tsafe/assert";
4+
import type { Equals } from "tsafe";
5+
6+
import { RegisteredLinkProps, getLink } from "./lib/routing";
7+
import { createComponentI18nApi } from "./lib/i18n";
48
import { fr } from "./lib";
59
import { cx } from "./lib/tools/cx";
610

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.
1111
import "./dsfr/component/breadcrumb/breadcrumb.css";
1212

1313
export type BreadcrumbProps = {
1414
className?: string;
1515
links: BreadcrumbProps.Link[];
16+
classes?: Partial<Record<"root" | "button" | "collapse" | "list" | "link" | "text", string>>;
1617
};
1718

1819
export namespace BreadcrumbProps {
1920
export type Link = {
20-
text: string;
21+
text: ReactNode;
2122
linkProps: RegisteredLinkProps;
2223
isActive?: boolean;
2324
};
2425
}
2526

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-
3527
/** @see <https://react-dsfr-components.etalab.studio/?path=/docs/components-breadcrumb> */
3628
export const Breadcrumb = memo(
3729
forwardRef<HTMLDivElement, BreadcrumbProps>((props, ref) => {
38-
const { links, className, ...rest } = props;
30+
const { links, className, classes = {}, ...rest } = props;
31+
32+
assert<Equals<keyof typeof rest, never>>();
33+
34+
const { t } = useTranslation();
3935

40-
const { Link } = useLink();
36+
const { Link } = getLink();
4137
const breadcrumbId = useId();
38+
4239
return (
4340
<nav
4441
ref={ref}
4542
role="navigation"
46-
className={cx(fr.cx("fr-breadcrumb"), className)}
47-
aria-label="vous êtes ici :"
43+
className={cx(fr.cx("fr-breadcrumb"), classes.root, className)}
44+
aria-label={`${t("navigation label")} :`}
4845
{...rest}
4946
>
5047
<button
51-
className="fr-breadcrumb__button"
48+
className={cx(fr.cx("fr-breadcrumb__button"), classes.button)}
5249
aria-expanded="false"
5350
aria-controls={breadcrumbId}
5451
>
55-
Voir le fil d’Ariane
52+
{t("show breadcrumb")}
5653
</button>
57-
<div className="fr-collapse" id={breadcrumbId}>
58-
<ol className="fr-breadcrumb__list">
54+
<div className={cx(fr.cx("fr-collapse"), classes.collapse)} id={breadcrumbId}>
55+
<ol className={cx(fr.cx("fr-breadcrumb__list"), classes.list)}>
5956
<>
6057
{links.map(link => (
6158
<li key={link.linkProps.href}>
6259
<Link
6360
{...link.linkProps}
6461
className={cx(
6562
fr.cx("fr-breadcrumb__link"),
63+
classes.link,
6664
link.linkProps.className
6765
)}
6866
aria-current={link.isActive ? "page" : undefined}
6967
>
70-
{trimText(link.text)}
68+
{link.text}
7169
</Link>
7270
</li>
7371
))}
@@ -81,4 +79,22 @@ export const Breadcrumb = memo(
8179

8280
Breadcrumb.displayName = symToStr({ Breadcrumb });
8381

82+
const { useTranslation, addBreadcrumbTranslations } = createComponentI18nApi({
83+
"componentName": symToStr({ Breadcrumb }),
84+
"frMessages": {
85+
"show breadcrumb": "Voir le fil d’Ariane",
86+
"navigation label": "vous êtes ici"
87+
}
88+
});
89+
90+
addBreadcrumbTranslations({
91+
"lang": "en",
92+
"messages": {
93+
"show breadcrumb": "Show navigation",
94+
"navigation label": "you are here"
95+
}
96+
});
97+
98+
export { addBreadcrumbTranslations };
99+
84100
export default Breadcrumb;

src/Button.tsx

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type {
88
import { fr } from "./lib";
99
import { cx } from "./lib/tools/cx";
1010
import type { FrIconClassName, RiIconClassName } from "./lib/generatedFromCss/classNames";
11-
import { RegisteredLinkProps, useLink } from "./lib/routing";
11+
import { RegisteredLinkProps, getLink } from "./lib/routing";
1212
import { assert } from "tsafe/assert";
1313
import type { Equals } from "tsafe";
1414
import { symToStr } from "tsafe/symToStr";
@@ -26,15 +26,15 @@ export namespace ButtonProps {
2626
};
2727

2828
export type IconOnly = {
29-
label?: never;
29+
children?: never;
3030
/** Function of the button */
3131
title: string;
3232
iconId: FrIconClassName | RiIconClassName;
3333
iconPosition?: never;
3434
};
3535

3636
export type WithIcon = {
37-
label: ReactNode;
37+
children: ReactNode;
3838
/** Function of the button, to provide if the label isn't explicit */
3939
title?: string;
4040
iconId: FrIconClassName | RiIconClassName;
@@ -43,7 +43,7 @@ export namespace ButtonProps {
4343
};
4444

4545
export type WithoutIcon = {
46-
label: ReactNode;
46+
children: ReactNode;
4747
/** Function of the button, to provide if the label isn't explicit */
4848
title?: string;
4949
iconId?: never;
@@ -69,7 +69,7 @@ export const Button = memo(
6969
forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonProps>((props, ref) => {
7070
const {
7171
className: prop_className,
72-
label,
72+
children,
7373
title,
7474
iconId,
7575
iconPosition = "left",
@@ -84,29 +84,28 @@ export const Button = memo(
8484

8585
assert<Equals<keyof typeof rest, never>>();
8686

87-
const { Link } = useLink();
87+
const { Link } = getLink();
8888

8989
const className = cx(
90-
fr.cx("fr-btn"),
91-
priority !== "primary" &&
92-
fr.cx(
90+
fr.cx(
91+
"fr-btn",
92+
priority !== "primary" &&
9393
`fr-btn--${
9494
priority === "tertiary no outline" ? "tertiary-no-outline" : priority
95-
}`
96-
),
97-
size !== "medium" &&
98-
fr.cx(
95+
}`,
96+
size !== "medium" &&
9997
`fr-btn--${(() => {
10098
switch (size) {
10199
case "small":
102100
return "sm";
103101
case "large":
104102
return "lg";
105103
}
106-
})()}`
107-
),
108-
iconId !== undefined &&
109-
fr.cx(iconId, label !== undefined && `fr-btn--icon-${iconPosition}`),
104+
})()}`,
105+
...(iconId === undefined
106+
? []
107+
: [iconId, children !== undefined && (`fr-btn--icon-${iconPosition}` as const)])
108+
),
110109
linkProps !== undefined && linkProps.className,
111110
prop_className
112111
);
@@ -119,7 +118,7 @@ export const Button = memo(
119118
ref={ref as React.ForwardedRef<HTMLAnchorElement>}
120119
{...rest}
121120
>
122-
{label}
121+
{children}
123122
</Link>
124123
) : (
125124
<button
@@ -131,7 +130,7 @@ export const Button = memo(
131130
ref={ref as React.ForwardedRef<HTMLButtonElement>}
132131
{...rest}
133132
>
134-
{label}
133+
{children}
135134
</button>
136135
);
137136
})

0 commit comments

Comments
 (0)