Skip to content

Commit 98a2757

Browse files
committed
Merge branch 'main' of github.com:codegouvfr/react-dsfr into feat/range-and-segmented
2 parents 28b736b + dbc7c4f commit 98a2757

File tree

13 files changed

+350
-85
lines changed

13 files changed

+350
-85
lines changed

README.fr.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ Ce module est une boîte à outils avancée qui tire parti de [@gouvfr/dsfr](htt
4343
- [x] Exactement le même aspect et ressenti qu'avec [@gouvfr/dsfr](https://www.npmjs.com/package/@gouvfr/dsfr).
4444
- [x] Pas de [flash blanc lors du rechargement dans la configuration SSR](https://github.com/codegouvfr/@codegouvfr/react-dsfr/issues/2#issuecomment-1257263480).
4545
- [x] La plupart des composants sont prêts pour les composants serveur. Les autres sont étiquetés avec `"use client";`
46-
- [x] [Intégration parfaite avec tous les principaux frameworks React : Next.js (PagesDir et AppDir), Create React App, Vue](https://react-dsfr.codegouv.studio/).
46+
- [x] [Intégration parfaite avec tous les principaux frameworks React : Next.js (PagesDir et AppDir), Create React App, Vite](https://react-dsfr.codegouv.studio/).
4747
- [x] (Presque) Tous [les composants](https://www.systeme-de-design.gouv.fr/elements-d-interface) sont [implémentés](https://components.react-dsfr.codegouv.studio/)
4848
- [x] Trois distributions modulables, choisissez les composants que vous importez. (Ce n'est pas tout dans un gros bundle .js)
4949
- [x] Intégration optionnelle avec [MUI](https://mui.com/). Si vous utilisez des composants MUI, ils seront

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ This module is an advanced toolkit that leverages [@gouvfr/dsfr](https://github.
4545
- [x] Exactly the same look and feel than with [@gouvfr/dsfr](https://www.npmjs.com/package/@gouvfr/dsfr).
4646
- [x] No [white flash when reloading in SSR setup](https://github.com/codegouvfr/@codegouvfr/react-dsfr/issues/2#issuecomment-1257263480).
4747
- [x] Most components are server component ready. The others are labeled with `"use client";`
48-
- [x] [Perfect integration with all major React framework: Next.js (PagesDir and AppDir), Create React App, Vue](https://react-dsfr.codegouv.studio/).
48+
- [x] [Perfect integration with all major React framework: Next.js (PagesDir and AppDir), Create React App, Vite](https://react-dsfr.codegouv.studio/).
4949
- [x] (Almost) All [the components](https://www.systeme-de-design.gouv.fr/elements-d-interface) are [implemented](https://components.react-dsfr.codegouv.studio/)
5050
- [x] Three shakable distribution, cherry pick the components you import. (It's not all in a big .js bundle)
5151
- [x] Optional integration with [MUI](https://mui.com/). If you use MUI components they will

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@codegouvfr/react-dsfr",
3-
"version": "1.2.2",
3+
"version": "1.5.0",
44
"description": "French State Design System React integration library",
55
"repository": {
66
"type": "git",
@@ -66,7 +66,7 @@
6666
"@babel/core": "^7.20.2",
6767
"@emotion/react": "^11.10.4",
6868
"@emotion/styled": "^11.10.4",
69-
"@gouvfr/dsfr": "1.11.0",
69+
"@gouvfr/dsfr": "1.11.1",
7070
"@mui/icons-material": "^5.14.18",
7171
"@mui/material": "^5.14.18",
7272
"@storybook/addon-a11y": "^6.5.16",

src/Modal/useIsModalOpen.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,35 @@
33
import { useEffect, useState } from "react";
44
import { assert } from "tsafe/assert";
55
import { symToStr } from "tsafe/symToStr";
6-
7-
export function useIsModalOpen(modal: { isOpenedByDefault: boolean; id: string }): boolean {
6+
import { useConstCallback } from "../tools/powerhooks/useConstCallback";
7+
8+
export function useIsModalOpen(
9+
modal: { isOpenedByDefault: boolean; id: string },
10+
callbacks?: {
11+
onConceal?: () => void;
12+
onDisclose?: () => void;
13+
}
14+
): boolean {
815
const { id, isOpenedByDefault } = modal;
916

1017
const [isModalOpen, setIsModalOpen] = useState(isOpenedByDefault);
1118

19+
const getCurrentCallbacks = useConstCallback(() => callbacks);
20+
1221
useEffect(() => {
1322
const cleanups: (() => void)[] = [];
1423

1524
const observeDialogHtmlElement = (element: HTMLElement) => {
16-
const onConceal = () => setIsModalOpen(false);
17-
const onDisclose = () => setIsModalOpen(true);
25+
const onConceal = () => {
26+
setIsModalOpen(false);
27+
setTimeout(() => {
28+
getCurrentCallbacks()?.onConceal?.();
29+
}, 0);
30+
};
31+
const onDisclose = () => {
32+
setIsModalOpen(true);
33+
getCurrentCallbacks()?.onDisclose?.();
34+
};
1835

1936
element.addEventListener("dsfr.conceal", onConceal);
2037
element.addEventListener("dsfr.disclose", onDisclose);

src/bin/copy-dsfr-to-public.ts

Lines changed: 74 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -29,65 +29,80 @@ import type { Equals } from "tsafe";
2929
return undefined;
3030
})();
3131

32-
const publicDirPath =
33-
viteConfigFilePath !== undefined
34-
? await (async function getVitePublicDirPath() {
35-
const viteConfig = fs.readFileSync(viteConfigFilePath).toString("utf8");
36-
37-
if (!viteConfig.includes("publicDir")) {
38-
return pathJoin(projectDirPath, "public");
39-
}
40-
41-
const [, afterPublicDir] = viteConfig.split(/\s["']?publicDir["']?\s*:/);
42-
43-
for (let indexEnd = 0; indexEnd < afterPublicDir.length; indexEnd++) {
44-
const {
45-
default: path,
46-
basename,
47-
dirname,
48-
delimiter,
49-
extname,
50-
format,
51-
isAbsolute,
52-
join,
53-
normalize,
54-
parse,
55-
posix,
56-
relative,
57-
resolve,
58-
sep,
59-
toNamespacedPath,
60-
win32,
61-
...rest
62-
} = await import("path");
63-
assert<Equals<keyof typeof rest, never>>();
64-
65-
const part = afterPublicDir
66-
.substring(0, indexEnd)
67-
.replace(/__dirname/g, `"${projectDirPath}"`);
68-
69-
let candidate: string;
70-
71-
try {
72-
candidate = eval(part);
73-
} catch {
74-
continue;
75-
}
76-
77-
if (typeof candidate !== "string") {
78-
continue;
79-
}
80-
81-
return candidate;
82-
}
83-
84-
console.error(
85-
`Can't parse the vite configuration please open an issue about it ${getRepoIssueUrl()}`
86-
);
87-
88-
process.exit(-1);
89-
})()
90-
: pathJoin(projectDirPath, "public");
32+
const publicDirPath = await (async () => {
33+
command_line_argument: {
34+
const arg = process.argv[2];
35+
36+
if (arg === undefined) {
37+
break command_line_argument;
38+
}
39+
40+
return arg;
41+
}
42+
43+
read_from_vite_config: {
44+
if (viteConfigFilePath === undefined) {
45+
break read_from_vite_config;
46+
}
47+
48+
const viteConfig = fs.readFileSync(viteConfigFilePath).toString("utf8");
49+
50+
if (!viteConfig.includes("publicDir")) {
51+
return pathJoin(projectDirPath, "public");
52+
}
53+
54+
const [, afterPublicDir] = viteConfig.split(/\s["']?publicDir["']?\s*:/);
55+
56+
for (let indexEnd = 0; indexEnd < afterPublicDir.length; indexEnd++) {
57+
const {
58+
default: path,
59+
basename,
60+
dirname,
61+
delimiter,
62+
extname,
63+
format,
64+
isAbsolute,
65+
join,
66+
normalize,
67+
parse,
68+
posix,
69+
relative,
70+
resolve,
71+
sep,
72+
toNamespacedPath,
73+
win32,
74+
...rest
75+
} = await import("path");
76+
assert<Equals<keyof typeof rest, never>>();
77+
78+
const part = afterPublicDir
79+
.substring(0, indexEnd)
80+
.replace(/__dirname/g, `"${projectDirPath}"`);
81+
82+
let candidate: string;
83+
84+
try {
85+
candidate = eval(part);
86+
} catch {
87+
continue;
88+
}
89+
90+
if (typeof candidate !== "string") {
91+
continue;
92+
}
93+
94+
return candidate;
95+
}
96+
97+
console.error(
98+
`Can't parse the vite configuration please open an issue about it ${getRepoIssueUrl()}`
99+
);
100+
101+
process.exit(-1);
102+
}
103+
104+
return pathJoin(projectDirPath, "public");
105+
})();
91106

92107
edit_gitignore: {
93108
const gitignoreFilePath = pathJoin(projectDirPath, ".gitignore");

src/consentManagement/Placeholder.tsx

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import React, { forwardRef, memo } from "react";
2+
import { fr } from "../fr";
3+
import { cx } from "../tools/cx";
4+
import { Equals, assert } from "tsafe";
5+
import { createComponentI18nApi } from "../i18n";
6+
import { symToStr } from "tsafe/symToStr";
7+
8+
export type PlaceholderProps = {
9+
className?: string;
10+
style?: React.CSSProperties;
11+
titleAs?: "span" | `h${1 | 2 | 3 | 4 | 5 | 6}`;
12+
classes?: Partial<Record<"root" | "title" | "description" | "button", string>>;
13+
title: string;
14+
description: string;
15+
onGranted: () => void;
16+
};
17+
18+
export const Placeholder = memo(
19+
forwardRef<HTMLDivElement, PlaceholderProps>((props, ref) => {
20+
const { t } = useTranslation();
21+
const {
22+
className,
23+
titleAs: HtmlTitleTag = "h4",
24+
classes = {},
25+
style,
26+
title,
27+
description,
28+
onGranted,
29+
...rest
30+
} = props;
31+
assert<Equals<keyof typeof rest, never>>();
32+
return (
33+
<div
34+
className={cx(fr.cx("fr-consent-placeholder"), classes.root, className)}
35+
ref={ref}
36+
style={style}
37+
{...rest}
38+
>
39+
<HtmlTitleTag className={cx(fr.cx("fr-h6", "fr-mb-2v"), classes.title)}>
40+
{title}
41+
</HtmlTitleTag>
42+
<p className={cx(fr.cx("fr-mb-6v"), classes.title)}>{description}</p>
43+
<button
44+
className={cx(fr.cx("fr-btn"), classes.button)}
45+
title={description}
46+
onClick={onGranted}
47+
>
48+
{t("enable message")}
49+
</button>
50+
</div>
51+
);
52+
})
53+
);
54+
55+
const { useTranslation, addPlaceholderTranslations } = createComponentI18nApi({
56+
"componentName": symToStr({ Placeholder }),
57+
"frMessages": {
58+
/* spell-checker: disable */
59+
"enable message": "Autoriser"
60+
/* spell-checker: enable */
61+
}
62+
});
63+
64+
addPlaceholderTranslations({
65+
"lang": "en",
66+
"messages": {
67+
"enable message": "Authorize"
68+
}
69+
});
70+
71+
addPlaceholderTranslations({
72+
"lang": "es",
73+
"messages": {
74+
/* spell-checker: disable */
75+
"enable message": "Permitir"
76+
/* spell-checker: enable */
77+
}
78+
});
79+
80+
export { addPlaceholderTranslations };

stories/ConsentManagement.stories.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { sectionName } from "./sectionName";
33
import { getStoryFactory } from "./getStory";
44
import { createConsentManagement } from "../dist/consentManagement";
55
import { localStorageKeyPrefix } from "../dist/consentManagement/createConsentManagement";
6+
import { Placeholder } from "../dist/consentManagement/Placeholder";
67
import { Footer } from "../dist/Footer";
78
import { Button } from "../dist/Button";
89
import { fr } from "../dist/fr";
@@ -263,7 +264,7 @@ const {
263264
});
264265

265266
function Story() {
266-
const { finalityConsent } = useConsent();
267+
const { finalityConsent, assumeConsent } = useConsent();
267268

268269
return (
269270
<>
@@ -272,6 +273,14 @@ function Story() {
272273
) : (
273274
<pre>{JSON.stringify({ finalityConsent }, null, 2)}</pre>
274275
)}
276+
{finalityConsent && finalityConsent.analytics === false && (
277+
<Placeholder
278+
title="Analytics are not enabled"
279+
description="We use cookies to measure the audience of our site and improve its content."
280+
onGranted={() => assumeConsent("analytics")}
281+
titleAs="span"
282+
/>
283+
)}
275284
<Button
276285
onClick={() => {
277286
Object.keys(localStorage)

stories/Modal.stories.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,25 @@ function Home(){
4646
{/* You can also call modal.close() to programmatically close the modal */}
4747
</>
4848
);
49-
);
49+
}
50+
\`\`\`
51+
52+
You can also register callbacks to be called when the modal is opened or closed:
5053
54+
\`\`\`tsx
55+
useIsModalOpen(
56+
modal,
57+
{
58+
onClose: ()=> console.log("modal closed"),
59+
onOpen: ()=> console.log("modal opened")
60+
}
61+
);
5162
\`\`\`
5263
64+
To create a Dialog component, something that you would use to ask the user a question, like "Do you want to proceed?", you can implement this pattern:
65+
- [Component](https://github.com/codegouvfr/react-dsfr/blob/main/test/integration/cra/src/MyDialog.tsx),
66+
- [Usage](https://github.com/codegouvfr/react-dsfr/blob/d5c0f304ed3416c8d10bba83e7a075a304d2caa0/test/integration/cra/src/Home.tsx#L117-L132).
67+
5368
`,
5469
"argTypes": {
5570
"title": {
@@ -113,6 +128,7 @@ function Template(args: ModalProps) {
113128
return (
114129
<>
115130
<Button nativeButtonProps={modal.buttonProps}>Open modal (stateless approach)</Button>
131+
&nbsp; - or - &nbsp;
116132
<Button onClick={() => modal.open()}>Open modal with modal.open()</Button>
117133
<modal.Component {...args} />
118134
</>

test/integration/cra/public/index.html

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
<meta charset="utf-8" />
66
<meta name="viewport" content="width=device-width, initial-scale=1" />
77
<meta name="description" content="Web site created using create-react-app" />
8-
<meta http-equiv="Content-Security-Policy"
9-
content="require-trusted-types-for 'script'; trusted-types react-dsfr react-dsfr-asap" />
108

119
<link rel="apple-touch-icon" href="%PUBLIC_URL%/dsfr/favicon/apple-touch-icon.png" />
1210
<link rel="icon" href="%PUBLIC_URL%/dsfr/favicon/favicon.svg" type="image/svg+xml" />

0 commit comments

Comments
 (0)