Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ A few things:
- 🙏🏻 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).
- 📣 Let everyone know what component you are working on by [oppening an issue](https://github.com/codegouvfr/react-dsfr/issues).
- 📚 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).
- 🔗 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).
- 🔗 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).
- 🕹️ 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).
- 🌎 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.)
- 🍳 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).
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@codegouvfr/react-dsfr",
"version": "0.56.0",
"version": "0.57.0",
"description": "French State Design System React integration library",
"repository": {
"type": "git",
Expand Down
18 changes: 17 additions & 1 deletion scripts/build/build.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { tsc } from "../tools/tsc";
import { getProjectRoot } from "../../src/bin/tools/getProjectRoot";
import { join as pathJoin } from "path";
import { join as pathJoin, basename as pathBasename } from "path";
import * as fs from "fs";
import { getPatchedRawCssCodeForCompatWithRemixIcon, collectIcons } from "./icons";
import { cssToTs } from "./cssToTs";
Expand Down Expand Up @@ -105,6 +105,22 @@ import { patchCssForMui } from "./patchCssForMui";
"doWatch": false
});

{
const assertSrcDirPath = pathJoin(projectRootDirPath, "src", "assets");

fs.cpSync(
assertSrcDirPath,
pathJoin(
projectRootDirPath,
JSON.parse(
fs.readFileSync(pathJoin(projectRootDirPath, "tsproject.json")).toString("utf8")
)["compilerOptions"]["outDir"],
pathBasename(assertSrcDirPath)
),
{ "recursive": true }
);
}

//NOTE: From here it's only for local linking, required for storybook and running integration apps.
if (!args.npm) {
fs.writeFileSync(
Expand Down
95 changes: 95 additions & 0 deletions src/AgentConnectButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"use client";
import React, { forwardRef, memo, useState, type CSSProperties } from "react";
import { symToStr } from "tsafe/symToStr";
import { createComponentI18nApi } from "./i18n";
import { fr } from "./fr";
import { assert, type Equals } from "tsafe/assert";
import agentconnectBtnPrincipalSvgUrl from "./assets/agentconnect-btn-principal.svg";
import agentconnectBtnPrincipalHoverSvgUrl from "./assets/agentconnect-btn-principal-hover.svg";
import agentconnectBtnAlternatifSvgUrl from "./assets/agentconnect-btn-alternatif.svg";
import agentconnectBtnAlternatifHoverSvgUrl from "./assets/agentconnect-btn-alternatif-hover.svg";
import { useIsDark } from "./useIsDark";
import { useColors } from "./useColors";
import { getAssetUrl } from "./tools/getAssetUrl";

export type AgentConnectButtonProps = {
className?: string;
redirectUrl: string;
style?: CSSProperties;
};

/** @see <https://react-dsfr-components.etalab.studio/?path=/docs/components-franceconnectbutton> */
export const AgentConnectButton = memo(
forwardRef<HTMLDivElement, AgentConnectButtonProps>((props, ref) => {
const { className, redirectUrl, style, ...rest } = props;

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

const { t } = useTranslation();

const [isMouseHover, setIsMouseHover] = useState(false);

const { isDark } = useIsDark();
const theme = useColors();

return (
<div className={className} style={style} ref={ref}>
<a
href={redirectUrl}
style={{
"display": "block",
"backgroundImage": "unset"
}}
onMouseEnter={() => setIsMouseHover(true)}
onMouseLeave={() => setIsMouseHover(false)}
>
<img
src={getAssetUrl(
isDark
? isMouseHover
? agentconnectBtnAlternatifHoverSvgUrl
: agentconnectBtnAlternatifSvgUrl
: isMouseHover
? agentconnectBtnPrincipalHoverSvgUrl
: agentconnectBtnPrincipalSvgUrl
)}
/>
</a>
<a
style={{
"display": "inline-block",
"marginTop": fr.spacing("1v"),
"color": theme.decisions.text.actionHigh.blueFrance.default
}}
className={fr.cx("fr-text--sm")}
href="https://agentconnect.gouv.fr/"
target="_blank"
>
{t("what is AgentConnect ?")}
</a>
</div>
);
})
);

AgentConnectButton.displayName = symToStr({ AgentConnectButton });

export default AgentConnectButton;

const { useTranslation, addAgentConnectButtonTranslations } = createComponentI18nApi({
"componentName": symToStr({ AgentConnectButton }),
"frMessages": {
/* spell-checker: disable */
"what is AgentConnect ?": "Qu’est-ce que AgentConnect ?"
/* spell-checker: enable */
}
});

addAgentConnectButtonTranslations({
"lang": "en",
"messages": {
"what is AgentConnect ?": "What's AgentConnect ?"
}
});

export { addAgentConnectButtonTranslations };
82 changes: 82 additions & 0 deletions src/FranceConnectButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React, { forwardRef, memo, type CSSProperties } from "react";
import { symToStr } from "tsafe/symToStr";
import { createComponentI18nApi } from "./i18n";
import { fr } from "./fr";
import { assert, type Equals } from "tsafe/assert";
import { cx } from "./tools/cx";

export type FranceConnectButtonProps = {
className?: string;
redirectUrl: string;
/** Default: false */
plus?: boolean;
classes?: Partial<Record<"root" | "login" | "brand", string>>;
style?: CSSProperties;
};

/** @see <https://react-dsfr-components.etalab.studio/?path=/docs/components-franceconnectbutton> */
export const FranceConnectButton = memo(
forwardRef<HTMLDivElement, FranceConnectButtonProps>((props, ref) => {
const { classes = {}, className, redirectUrl, plus = false, style, ...rest } = props;

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

const { t } = useTranslation();

return (
<div
className={cx(fr.cx("fr-connect-group"), classes.root, className)}
style={style}
ref={ref}
>
<a className={fr.cx("fr-btn", "fr-connect")} href={redirectUrl}>
<span className={cx(fr.cx("fr-connect__login"), classes.login)}>
S’identifier avec
</span>
<span className={cx(fr.cx("fr-connect__brand"), classes.brand)}>
FranceConnect{plus ? "+" : ""}
</span>
</a>
<p>
<a
href={
plus
? "https://franceconnect.gouv.fr/france-connect-plus"
: "https://franceconnect.gouv.fr/"
}
target="_blank"
rel="noopener"
title={`${t("what is service", { plus })} - ${t("new window")}`}
>
{t("what is service", { plus })}
</a>
</p>
</div>
);
})
);

FranceConnectButton.displayName = symToStr({ FranceConnectButton });

export default FranceConnectButton;

const { useTranslation, addFranceConnectButtonTranslations } = createComponentI18nApi({
"componentName": symToStr({ FranceConnectButton }),
"frMessages": {
/* spell-checker: disable */
"what is service": (params: { plus: boolean }) =>
`Qu’est-ce que FranceConnect${params.plus ? "+" : ""} ?`,
"new window": "nouvelle fenêtre"
/* spell-checker: enable */
}
});

addFranceConnectButtonTranslations({
"lang": "en",
"messages": {
"what is service": ({ plus }) => `What's FranceConnect${plus ? "+" : ""} ?`,
"new window": "new window"
}
});

export { addFranceConnectButtonTranslations };
Loading