Skip to content

Commit cbc47f4

Browse files
garronejsylvainlg
andauthored
feat: ✨ Add FranceConnect and MonComptePerso connexion butto… (#129)
* feat: ✨ Add FranceConnect and MonComptePerso connexion buttons (#128) * feat: ✨ Add FranceConnect and MonComptePerso connexion buttons * fixup review * #129 FranceConnectButton * #129 AgentConnetButton * Bump version --------- Co-authored-by: Sylvain LE GLEAU <sylvain.le.gleau@gmail.com>
1 parent b64fd6d commit cbc47f4

11 files changed

+405
-3
lines changed

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ A few things:
1818
- 🙏🏻 Don't be afraid to push even if you aren't 100% happy with your code or [if it's still WIP](https://github.com/codegouvfr/react-dsfr/blob/1fdcf15cb085c67d37c31badf6ffa4725795ba0f/stories/Accordion.stories.tsx#L6).
1919
- 📣 Let everyone know what component you are working on by [oppening an issue](https://github.com/codegouvfr/react-dsfr/issues).
2020
- 📚 You can draw inspiration from [`dataesr/react-dsfr`](https://github.com/dataesr/react-dsfr/tree/master/src/components/interface) and the implementation of [france connect](https://github.com/france-connect/sources/tree/main/front/libs/dsfr).
21-
- 🔗 Use the component returned by `useLink()` instead of `<a />`. [Example in the `<Header />` component](https://github.com/codegouvfr/react-dsfr/blob/bbaf4a81d78de08d6fdcb059a9f4cb8a78ce4d5a/src/Header.tsx#L84-L87). We want to [play nice with all routing libraries](https://react-dsfr.etalab.studio/integration-with-routing-libraries).
21+
- 🔗 Use the component returned by `getLink()` instead of `<a />`. [Example in the `<Header />` component](https://github.com/codegouvfr/react-dsfr/blob/bbaf4a81d78de08d6fdcb059a9f4cb8a78ce4d5a/src/Header.tsx#L84-L87). We want to [play nice with all routing libraries](https://react-dsfr.etalab.studio/integration-with-routing-libraries).
2222
- 🕹️ When it's relevant, try to enable components to be used either in controlled or uncontrolled mode. [Example with <Tabs />](https://react-dsfr-components.etalab.studio/?path=/docs/components-tabs--default).
2323
- 🌎 Avoid hard coding text in JSX, use [the i18n mechanism](https://react-dsfr.etalab.studio/i18n) instead. [Here is an example](https://github.com/codegouvfr/react-dsfr/blob/bbaf4a81d78de08d6fdcb059a9f4cb8a78ce4d5a/src/DarkModeSwitch.tsx#L162-L199). (Don't worry about providing translations other than French.)
2424
- 🍳 If you have to arbitrate between ease of use and customisability I'd encourage you to favor ease of use. People that would need a greater level of customizability can always fall back to making their own wrapper from the reference documentation using [`fr.cx()`](https://react-dsfr.etalab.studio/cx).

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@codegouvfr/react-dsfr",
3-
"version": "0.56.0",
3+
"version": "0.57.0",
44
"description": "French State Design System React integration library",
55
"repository": {
66
"type": "git",

scripts/build/build.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { tsc } from "../tools/tsc";
22
import { getProjectRoot } from "../../src/bin/tools/getProjectRoot";
3-
import { join as pathJoin } from "path";
3+
import { join as pathJoin, basename as pathBasename } from "path";
44
import * as fs from "fs";
55
import { getPatchedRawCssCodeForCompatWithRemixIcon, collectIcons } from "./icons";
66
import { cssToTs } from "./cssToTs";
@@ -105,6 +105,22 @@ import { patchCssForMui } from "./patchCssForMui";
105105
"doWatch": false
106106
});
107107

108+
{
109+
const assertSrcDirPath = pathJoin(projectRootDirPath, "src", "assets");
110+
111+
fs.cpSync(
112+
assertSrcDirPath,
113+
pathJoin(
114+
projectRootDirPath,
115+
JSON.parse(
116+
fs.readFileSync(pathJoin(projectRootDirPath, "tsproject.json")).toString("utf8")
117+
)["compilerOptions"]["outDir"],
118+
pathBasename(assertSrcDirPath)
119+
),
120+
{ "recursive": true }
121+
);
122+
}
123+
108124
//NOTE: From here it's only for local linking, required for storybook and running integration apps.
109125
if (!args.npm) {
110126
fs.writeFileSync(

src/AgentConnectButton.tsx

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
"use client";
2+
import React, { forwardRef, memo, useState, type CSSProperties } from "react";
3+
import { symToStr } from "tsafe/symToStr";
4+
import { createComponentI18nApi } from "./i18n";
5+
import { fr } from "./fr";
6+
import { assert, type Equals } from "tsafe/assert";
7+
import agentconnectBtnPrincipalSvgUrl from "./assets/agentconnect-btn-principal.svg";
8+
import agentconnectBtnPrincipalHoverSvgUrl from "./assets/agentconnect-btn-principal-hover.svg";
9+
import agentconnectBtnAlternatifSvgUrl from "./assets/agentconnect-btn-alternatif.svg";
10+
import agentconnectBtnAlternatifHoverSvgUrl from "./assets/agentconnect-btn-alternatif-hover.svg";
11+
import { useIsDark } from "./useIsDark";
12+
import { useColors } from "./useColors";
13+
import { getAssetUrl } from "./tools/getAssetUrl";
14+
15+
export type AgentConnectButtonProps = {
16+
className?: string;
17+
redirectUrl: string;
18+
style?: CSSProperties;
19+
};
20+
21+
/** @see <https://react-dsfr-components.etalab.studio/?path=/docs/components-franceconnectbutton> */
22+
export const AgentConnectButton = memo(
23+
forwardRef<HTMLDivElement, AgentConnectButtonProps>((props, ref) => {
24+
const { className, redirectUrl, style, ...rest } = props;
25+
26+
assert<Equals<keyof typeof rest, never>>();
27+
28+
const { t } = useTranslation();
29+
30+
const [isMouseHover, setIsMouseHover] = useState(false);
31+
32+
const { isDark } = useIsDark();
33+
const theme = useColors();
34+
35+
return (
36+
<div className={className} style={style} ref={ref}>
37+
<a
38+
href={redirectUrl}
39+
style={{
40+
"display": "block",
41+
"backgroundImage": "unset"
42+
}}
43+
onMouseEnter={() => setIsMouseHover(true)}
44+
onMouseLeave={() => setIsMouseHover(false)}
45+
>
46+
<img
47+
src={getAssetUrl(
48+
isDark
49+
? isMouseHover
50+
? agentconnectBtnAlternatifHoverSvgUrl
51+
: agentconnectBtnAlternatifSvgUrl
52+
: isMouseHover
53+
? agentconnectBtnPrincipalHoverSvgUrl
54+
: agentconnectBtnPrincipalSvgUrl
55+
)}
56+
/>
57+
</a>
58+
<a
59+
style={{
60+
"display": "inline-block",
61+
"marginTop": fr.spacing("1v"),
62+
"color": theme.decisions.text.actionHigh.blueFrance.default
63+
}}
64+
className={fr.cx("fr-text--sm")}
65+
href="https://agentconnect.gouv.fr/"
66+
target="_blank"
67+
>
68+
{t("what is AgentConnect ?")}
69+
</a>
70+
</div>
71+
);
72+
})
73+
);
74+
75+
AgentConnectButton.displayName = symToStr({ AgentConnectButton });
76+
77+
export default AgentConnectButton;
78+
79+
const { useTranslation, addAgentConnectButtonTranslations } = createComponentI18nApi({
80+
"componentName": symToStr({ AgentConnectButton }),
81+
"frMessages": {
82+
/* spell-checker: disable */
83+
"what is AgentConnect ?": "Qu’est-ce que AgentConnect ?"
84+
/* spell-checker: enable */
85+
}
86+
});
87+
88+
addAgentConnectButtonTranslations({
89+
"lang": "en",
90+
"messages": {
91+
"what is AgentConnect ?": "What's AgentConnect ?"
92+
}
93+
});
94+
95+
export { addAgentConnectButtonTranslations };

src/FranceConnectButton.tsx

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import React, { forwardRef, memo, type CSSProperties } from "react";
2+
import { symToStr } from "tsafe/symToStr";
3+
import { createComponentI18nApi } from "./i18n";
4+
import { fr } from "./fr";
5+
import { assert, type Equals } from "tsafe/assert";
6+
import { cx } from "./tools/cx";
7+
8+
export type FranceConnectButtonProps = {
9+
className?: string;
10+
redirectUrl: string;
11+
/** Default: false */
12+
plus?: boolean;
13+
classes?: Partial<Record<"root" | "login" | "brand", string>>;
14+
style?: CSSProperties;
15+
};
16+
17+
/** @see <https://react-dsfr-components.etalab.studio/?path=/docs/components-franceconnectbutton> */
18+
export const FranceConnectButton = memo(
19+
forwardRef<HTMLDivElement, FranceConnectButtonProps>((props, ref) => {
20+
const { classes = {}, className, redirectUrl, plus = false, style, ...rest } = props;
21+
22+
assert<Equals<keyof typeof rest, never>>();
23+
24+
const { t } = useTranslation();
25+
26+
return (
27+
<div
28+
className={cx(fr.cx("fr-connect-group"), classes.root, className)}
29+
style={style}
30+
ref={ref}
31+
>
32+
<a className={fr.cx("fr-btn", "fr-connect")} href={redirectUrl}>
33+
<span className={cx(fr.cx("fr-connect__login"), classes.login)}>
34+
S’identifier avec
35+
</span>
36+
<span className={cx(fr.cx("fr-connect__brand"), classes.brand)}>
37+
FranceConnect{plus ? "+" : ""}
38+
</span>
39+
</a>
40+
<p>
41+
<a
42+
href={
43+
plus
44+
? "https://franceconnect.gouv.fr/france-connect-plus"
45+
: "https://franceconnect.gouv.fr/"
46+
}
47+
target="_blank"
48+
rel="noopener"
49+
title={`${t("what is service", { plus })} - ${t("new window")}`}
50+
>
51+
{t("what is service", { plus })}
52+
</a>
53+
</p>
54+
</div>
55+
);
56+
})
57+
);
58+
59+
FranceConnectButton.displayName = symToStr({ FranceConnectButton });
60+
61+
export default FranceConnectButton;
62+
63+
const { useTranslation, addFranceConnectButtonTranslations } = createComponentI18nApi({
64+
"componentName": symToStr({ FranceConnectButton }),
65+
"frMessages": {
66+
/* spell-checker: disable */
67+
"what is service": (params: { plus: boolean }) =>
68+
`Qu’est-ce que FranceConnect${params.plus ? "+" : ""} ?`,
69+
"new window": "nouvelle fenêtre"
70+
/* spell-checker: enable */
71+
}
72+
});
73+
74+
addFranceConnectButtonTranslations({
75+
"lang": "en",
76+
"messages": {
77+
"what is service": ({ plus }) => `What's FranceConnect${plus ? "+" : ""} ?`,
78+
"new window": "new window"
79+
}
80+
});
81+
82+
export { addFranceConnectButtonTranslations };

0 commit comments

Comments
 (0)