Skip to content

feat: Ajout des pictogrammes du DSFR đź’Ž #413

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
May 28, 2025
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
16 changes: 11 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"scripts": {
"prepare": "patch-package",
"build": "ts-node -T scripts/build",
"rebuild-picto": "ts-node -T scripts/picto-builder.ts src/picto/pictogrammes-svg src/picto",
"_link": "ts-node -T scripts/link-in-integration-apps.ts",
"link-external": "ts-node -T scripts/link-in-external-project.ts",
"start-cra": "yarn build && yarn _link && ((tsc -w -p src) & (cd test/integration/cra && yarn start))",
Expand Down Expand Up @@ -63,6 +64,8 @@
],
"homepage": "https://github.com/codegouvfr/react-dsfr",
"dependencies": {
"react": "^19.1.0",
"react-dom": "^19.1.0",
"tsafe": "^1.8.5",
"yargs-parser": "^21.1.1"
},
Expand All @@ -75,11 +78,11 @@
}
},
"devDependencies": {
"@gouvfr/dsfr-chart": "^1.0.0",
"@babel/core": "^7.20.2",
"@emotion/react": "^11.10.4",
"@emotion/styled": "^11.10.4",
"@gouvfr/dsfr": "1.13.2",
"@gouvfr/dsfr-chart": "^1.0.0",
"@mui/icons-material": "^5.14.18",
"@mui/material": "^5.14.18",
"@storybook/addon-a11y": "^6.5.16",
Expand All @@ -94,6 +97,7 @@
"@tanstack/react-virtual": "^3.0.0-beta.39",
"@types/css": "^0.0.33",
"@types/memoizee": "^0.4.8",
"@types/mustache": "^4.2.6",
"@types/node": "^18.7.18",
"@types/react": "18.0.21",
"@types/react-dom": "18.0.6",
Expand All @@ -110,20 +114,22 @@
"husky": "^4.3.8",
"lint-staged": "^11.0.0",
"memoizee": "^0.4.15",
"mustache": "^4.2.0",
"next": "13.5.1",
"parse-numeric-range": "^1.3.0",
"patch-package": "^8.0.0",
"powerhooks": "^0.22.0",
"prettier": "^2.3.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"remixicon": "^4.2.0",
"storybook-dark-mode": "^1.1.2",
"svgo": "^3.3.2",
"ts-node": "^10.9.1",
"tss-react": "^4.9.1",
"type-route": "^1.0.1",
"typescript": "^4.9.1",
"vitest": "^0.24.3",
"patch-package": "^8.0.0"
"vitest": "^0.24.3"
},
"main": "dist/fr/index.js",
"types": "dist/fr/index.d.ts",
Expand Down
158 changes: 158 additions & 0 deletions scripts/picto-builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import path from "path";
import fs from "fs/promises";
import glob from "fast-glob";
import { optimize } from "svgo";
import Mustache from "mustache";

async function ensureDir(dir: string): Promise<void> {
try {
await fs.mkdir(dir, { recursive: true });
} catch (err) {
if ((err as NodeJS.ErrnoException).code !== "EEXIST") {
throw err;
}
}
}

function extractSvgInnerContent(svg: string): string {
const match = svg.match(/<svg[^>]*>([\s\S]*?)<\/svg>/);
if (!match) {
throw new Error("Invalid SVG: cannot extract content");
}
return match[1].trim();
}

async function cleanSvgContent(svgData: string): Promise<string> {
const result = optimize(svgData, {
multipass: true,
floatPrecision: 3,
plugins: [
"removeComments",
"removeMetadata",
"removeTitle",
"removeDesc",
"removeUselessDefs",
"removeEmptyAttrs",
"removeHiddenElems",
"removeEmptyText",
"convertShapeToPath",
"convertColors",
"cleanupAttrs",
"minifyStyles"
]
});

if ("data" in result) {
return result.data
.replace(/fill-opacity=/g, "fillOpacity=")
.replace(/clip-rule=/g, "clipRule=")
.replace(/fill-rule=/g, "fillRule=")
.replace(/stroke-linecap=/g, "strokeLinecap=")
.replace(/stroke-linejoin=/g, "strokeLinejoin=")
.replace(/stroke-width=/g, "strokeWidth=")
.replace(/xlink:href=/g, "xlinkHref=");
}

throw new Error("SVGO optimization failed");
}

function pascalCaseName(filename: string): string {
return filename
.replace(/\.svg$/, "")
.split(/[\s-_]+/)
.map(s => s.charAt(0).toUpperCase() + s.slice(1))
.join("");
}

async function generateComponent(svgPath: string, outputDir: string): Promise<string> {
const svgName = path.basename(svgPath);
const componentName = pascalCaseName(svgName);
const outputFileName = svgName.replace(/\.svg$/, ".tsx");

const svgData = await fs.readFile(svgPath, "utf8");
const cleanedSvg = extractSvgInnerContent(await cleanSvgContent(svgData));

const template = `import React from 'react';
import { createIcon } from './utils/IconWrapper';

export default createIcon(
<>
{{& svgContent }}
</>,
"{{componentName}}"
);
`;

const componentCode = Mustache.render(template, {
componentName,
svgContent: cleanedSvg
});

const outPath = path.join(outputDir, outputFileName);
await ensureDir(path.dirname(outPath));
await fs.writeFile(outPath, componentCode, "utf8");
return outPath;
}

async function generateIndex(outputDir: string): Promise<string> {
const files = await glob(`${outputDir}/*.tsx`);
const filePath = path.join(outputDir, "index.ts");

const exports = files
.map(f => {
const base = path.basename(f, ".tsx");
return `export { default as ${pascalCaseName(base)} } from './${base}';`;
})
.join("\n");

await fs.writeFile(filePath, exports);
return filePath;
}

async function generateTypes(outputDir: string): Promise<string> {
const files = await glob(`${outputDir}/*.tsx`);
const iconNames = files.map(f => pascalCaseName(path.basename(f, ".tsx")));
const filePath = path.join(outputDir, "index.d.ts");

const header = `import { IconWrapper } from './utils/IconWrapper';\n\ntype SvgIconComponent = typeof IconWrapper;\n\n`;
const lines = iconNames.map(name => `export const ${name}: SvgIconComponent;`);

const content = `${header}${lines.join("\n")}\n`;

await fs.writeFile(filePath, content, "utf8");
return filePath;
}

export async function build(srcDir: string, outDir: string): Promise<string[]> {
await ensureDir(outDir);
const files = [];

const svgFiles = await glob(`${srcDir}/**/*.svg`);

for (const svgPath of svgFiles) {
files.push(await generateComponent(svgPath, outDir));
}

files.push(await generateIndex(outDir));
files.push(await generateTypes(outDir));
return files;
}

export async function main(argv: string[]) {
if (argv.length < 2) {
console.error("Usage: node build-icons.js <source-svg-folder> <output-icon-folder>");
process.exit(1);
}

const [srcDir, outDir] = argv;

try {
await build(srcDir, outDir);
} catch (err) {
process.exit(1);
}
}

if (require.main === module) {
main(process.argv.slice(2));
}
36 changes: 36 additions & 0 deletions src/picto/Accessibility.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from "react";
import { createIcon } from "./utils/IconWrapper";

export default createIcon(
<>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M40 19C37.7909 19 36 20.7909 36 23C36 25.2091 37.7909 27 40 27C42.2091 27 44 25.2091 44 23C44 20.7909 42.2091 19 40 19ZM34 23C34 19.6863 36.6863 17 40 17C43.3137 17 46 19.6863 46 23C46 26.3137 43.3137 29 40 29C36.6863 29 34 26.3137 34 23Z"
fill="#E1000F"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M14.724 15.3102C15.105 15.7101 15.0896 16.343 14.6898 16.724C9.33505 21.8252 6 29.0223 6 37C6 37.5523 5.55228 38 5 38C4.44772 38 4 37.5523 4 37C4 28.4527 7.57577 20.7389 13.3102 15.2759C13.7101 14.895 14.3431 14.9103 14.724 15.3102Z"
fill="#E1000F"
/>
<path
d="M70 43C70 43.5523 69.5523 44 69 44C68.4477 44 68 43.5523 68 43C68 42.4477 68.4477 42 69 42C69.5523 42 70 42.4477 70 43Z"
fill="#E1000F"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M57.0422 29.0319C58.5952 28.7867 60 29.987 60 31.5594C60 32.7018 59.2427 33.7058 58.1442 34.0197L46.7253 37.2822C46.296 37.4049 46 37.7973 46 38.2437V44.5911L50.2161 58.9259C50.7758 60.8287 49.3493 62.7351 47.3659 62.7351C46.1274 62.7351 45.0189 61.9669 44.5841 60.8074L40 48.5831L35.4159 60.8074C34.9811 61.9669 33.8726 62.7351 32.6341 62.7351C30.6507 62.7351 29.2242 60.8287 29.7839 58.9259L34 44.5911V38.2437C34 37.7973 33.704 37.4049 33.2747 37.2822L21.8558 34.0197C20.7573 33.7058 20 32.7018 20 31.5594C20 29.987 21.4048 28.7867 22.9579 29.0319L39.844 31.6981C39.9474 31.7144 40.0526 31.7144 40.156 31.6981L57.0422 29.0319ZM58 31.5594C58 31.216 57.6932 30.9539 57.3541 31.0074L40.4679 33.6737C40.1579 33.7226 39.8421 33.7226 39.5321 33.6737L22.6459 31.0074C22.3068 30.9539 22 31.216 22 31.5594C22 31.8088 22.1654 32.0281 22.4053 32.0966L33.8242 35.3592C35.1121 35.7271 36 36.9043 36 38.2437V44.7351C36 44.8307 35.9863 44.9257 35.9594 45.0173L31.7026 59.4902C31.5197 60.1121 31.9859 60.7351 32.6341 60.7351C33.0389 60.7351 33.4011 60.4841 33.5433 60.1051L39.0637 45.384C39.21 44.9937 39.5832 44.7351 40 44.7351C40.4168 44.7351 40.79 44.9937 40.9363 45.384L46.4567 60.1051C46.5989 60.4841 46.9611 60.7351 47.3659 60.7351C48.0141 60.7351 48.4803 60.1121 48.2974 59.4902L44.0406 45.0173C44.0137 44.9257 44 44.8307 44 44.7351V38.2437C44 36.9043 44.8879 35.7271 46.1758 35.3592L57.5947 32.0966C57.8346 32.0281 58 31.8088 58 31.5594Z"
fill="#000091"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M40 12C24.536 12 12 24.536 12 40C12 55.464 24.536 68 40 68C45.9202 68 51.4086 66.1638 55.9304 63.0298C56.3843 62.7152 57.0073 62.8281 57.3219 63.282C57.6365 63.7359 57.5236 64.359 57.0696 64.6736C52.224 68.032 46.3403 70 40 70C23.4315 70 10 56.5685 10 40C10 23.4315 23.4315 10 40 10C56.5685 10 70 23.4315 70 40C70 40.5523 69.5523 41 69 41C68.4477 41 68 40.5523 68 40C68 24.536 55.464 12 40 12ZM67.2206 50.0748C67.7316 50.2845 67.9758 50.8686 67.7662 51.3796C67.4035 52.2638 66.9998 53.1268 66.5574 53.9662C65.4782 56.0144 64.1691 57.9222 62.6647 59.6555C62.3027 60.0726 61.6711 60.1172 61.254 59.7552C60.8369 59.3932 60.7923 58.7616 61.1543 58.3445C62.559 56.7261 63.7809 54.9451 64.788 53.0338C65.2008 52.2505 65.5775 51.4453 65.9159 50.6204C66.1255 50.1095 66.7097 49.8652 67.2206 50.0748Z"
fill="#000091"
/>
</>,
"Accessibility"
);
66 changes: 66 additions & 0 deletions src/picto/Airport.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React from "react";
import { createIcon } from "./utils/IconWrapper";

export default createIcon(
<>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M45.5126 31.9072C45.3237 31.3882 44.7499 31.1206 44.2309 31.3095L41.653 32.2478C41.6362 32.2293 41.6185 32.2113 41.6001 32.1938L36.7289 27.5817C36.4538 27.3213 36.0552 27.2386 35.6993 27.3682L31.9405 28.7362C31.2844 28.975 31.0688 29.7956 31.5228 30.3261L36.8076 36.5016C36.721 36.7241 36.7152 36.9745 36.7997 37.2068L37.4838 39.0861L37.5321 39.1976C37.6961 39.52 38.0242 39.7321 38.392 39.7436L43.7745 39.9129L43.9013 39.9089C43.9854 39.9008 44.0683 39.8821 44.148 39.8531L71.3991 29.9345L71.628 29.8481C73.8022 28.9971 74.8396 27.692 74.2295 26.0158C73.2784 23.4028 68.1061 22.6196 65.8438 23.443L57.4177 26.5099L46.4808 24.2634C46.2995 24.2261 46.1114 24.2399 45.9375 24.3032L41.2391 26.0133C40.375 26.3278 40.3577 27.5436 41.2124 27.8826L52.4058 32.3219C52.6335 32.4122 52.8864 32.4158 53.1165 32.3321L61.5737 29.2539C62.5213 28.909 62.4206 27.5375 61.4329 27.3346L61.1558 27.2777L66.5279 25.3224L66.606 25.299C66.7542 25.2618 66.9906 25.2332 67.2807 25.2169L67.6525 25.2035C68.2422 25.1928 68.9617 25.2264 69.6107 25.3246C71.1249 25.5539 72.1186 26.0637 72.3501 26.6999L72.3681 26.7591C72.4644 27.1542 72.0762 27.5597 70.715 28.0551L43.6455 37.9076L39.1305 37.7652L38.8898 37.1037L41.3763 34.4768L44.9149 33.1889L45.0222 33.1427C45.4698 32.9183 45.688 32.3891 45.5126 31.9072ZM39.7646 33.2713L38.1422 34.9849L34.2568 30.4441L36.0779 29.7813L39.7646 33.2713ZM57.5682 28.5827L46.3541 26.2793L44.393 26.9931L52.788 30.3226L57.5682 28.5827Z"
fill="#E1000F"
/>
<path
d="M17 26C17.5523 26 18 25.5523 18 25C18 24.4477 17.5523 24 17 24C16.4477 24 16 24.4477 16 25C16 25.5523 16.4477 26 17 26Z"
fill="#E1000F"
/>
<path
d="M23 26C23.5523 26 24 25.5523 24 25C24 24.4477 23.5523 24 23 24C22.4477 24 22 24.4477 22 25C22 25.5523 22.4477 26 23 26Z"
fill="#E1000F"
/>
<path
d="M36.6838 42.0513C37.2077 41.8767 37.774 42.1598 37.9487 42.6838C38.1109 43.1703 37.8783 43.6933 37.4247 43.9054L37.3162 43.9487L31.3162 45.9487C30.7923 46.1233 30.226 45.8402 30.0513 45.3162C29.8891 44.8297 30.1217 44.3067 30.5753 44.0946L30.6838 44.0513L36.6838 42.0513Z"
fill="#E1000F"
/>
<path
d="M52.6838 40.0513C53.2077 39.8767 53.774 40.1598 53.9487 40.6838C54.1109 41.1703 53.8783 41.6933 53.4247 41.9054L53.3162 41.9487L41.3162 45.9487C40.7923 46.1233 40.226 45.8402 40.0513 45.3162C39.8891 44.8297 40.1217 44.3067 40.5753 44.0946L40.6838 44.0513L52.6838 40.0513Z"
fill="#E1000F"
/>
<path
d="M67 66C67.5523 66 68 65.5523 68 65C68 64.4477 67.5523 64 67 64C66.4477 64 66 64.4477 66 65C66 65.5523 66.4477 66 67 66Z"
fill="#E1000F"
/>
<path
d="M64 65C64 65.5523 63.5523 66 63 66C62.4477 66 62 65.5523 62 65C62 64.4477 62.4477 64 63 64C63.5523 64 64 64.4477 64 65Z"
fill="#E1000F"
/>
<path
d="M49 62C49.5523 62 50 61.5523 50 61C50 60.4477 49.5523 60 49 60C48.4477 60 48 60.4477 48 61C48 61.5523 48.4477 62 49 62Z"
fill="#E1000F"
/>
<path
d="M55 60C55.5523 60 56 60.4477 56 61C56 61.5128 55.614 61.9355 55.1166 61.9933L55 62H53C52.4477 62 52 61.5523 52 61C52 60.4872 52.386 60.0645 52.8834 60.0067L53 60H55Z"
fill="#E1000F"
/>
<path
d="M46 61C46 60.4477 45.5523 60 45 60H41L40.8834 60.0067C40.386 60.0645 40 60.4872 40 61C40 61.5523 40.4477 62 41 62H45L45.1166 61.9933C45.614 61.9355 46 61.5128 46 61Z"
fill="#E1000F"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M16.8668 12.007C15.7111 12.131 15.7111 13.869 16.8668 13.993L17 14H18V16H13L12.8668 16.007C11.7111 16.131 11.7111 17.869 12.8668 17.993L13 18H14.381L15.3816 20H11C10.3494 20 9.87207 20.6114 10.0299 21.2425L12.0299 29.2425C12.1411 29.6877 12.5411 30 13 30H15.9982L15.9947 31.8906L15.973 35.3293L15.9521 37.2993L15.9041 40.4269L15.8674 42.2099L15.7932 45.0251L15.7221 47.1364L15.6621 48.6374L15.5492 50.9819L15.4473 52.7157L15.3643 53.9332L15.2755 55.0796L15.1811 56.1546L15.081 57.1584L14.9753 58.0906L14.9017 58.6724L14.8256 59.2223L14.7471 59.7404L14.6744 60.1787C14.5502 60.8958 14.4174 61.5031 14.2763 62H13L12.8834 62.0067C12.386 62.0645 12 62.4872 12 63C12 63.5523 12.4477 64 13 64H14.9767C14.9921 64.0004 15.0076 64.0004 15.0229 64H24.9771C24.9924 64.0004 25.0079 64.0004 25.0233 64H27L27.1166 63.9933C27.614 63.9355 28 63.5128 28 63C28 62.4477 27.5523 62 27 62H25.7237C25.5826 61.5031 25.4498 60.8958 25.3256 60.1787L25.2529 59.7404L25.1744 59.2223L25.0983 58.6724L24.9888 57.7878L24.885 56.8317L24.8189 56.1546L24.7553 55.4458L24.6647 54.3232L24.5797 53.1295L24.5005 51.8646L24.427 50.5288L24.3379 48.6374L24.2779 47.1364L24.1907 44.4777L24.1198 41.6234L24.0748 39.1992L24.0479 37.2993L24.027 35.3293L24.0053 31.8906L24.0018 30H27C27.4589 30 27.8589 29.6877 27.9701 29.2425L29.9701 21.2425C30.1279 20.6114 29.6506 20 29 20H24.618L25.618 18H27L27.1332 17.993C28.3317 17.8644 28.2874 16 27 16H22V14H23L23.1332 13.993C24.3317 13.8644 24.2874 12 23 12H17L16.8668 12.007ZM23.6624 62H16.338L16.3533 61.938L16.44 61.5517L16.5242 61.1396L16.6461 60.4733L16.7244 59.9969L16.8003 59.4946L16.8738 58.9666L16.9797 58.126L17.0802 57.2273L17.1754 56.2702L17.2653 55.2547L17.3223 54.5452L17.4293 53.0483L17.5034 51.8572L17.5941 50.1777L17.6561 48.8495L17.7307 46.987L17.796 45.0197L17.839 43.4752L17.8884 41.324L17.9195 39.6414L17.96 36.7051L17.9901 32.9636L17.999 30H22L22.0098 32.9326L22.0329 36.0319L22.0788 39.5381L22.1204 41.7469L22.1709 43.8528L22.2146 45.3649L22.2806 47.2914L22.3557 49.1156L22.4179 50.4166L22.5087 52.0622L22.5828 53.2295L22.662 54.3396L22.7176 55.048L22.8358 56.3886L22.8983 57.0209L22.9965 57.9219L23.0998 58.7661L23.1715 59.2973L23.2456 59.8033L23.322 60.2841L23.4007 60.7398L23.4818 61.1703L23.6078 61.7689L23.6624 62ZM13.781 28L12.281 22H16.9799C16.9938 22.0003 17.0077 22.0003 17.0217 22H22.9783C22.9923 22.0003 23.0062 22.0003 23.0201 22H27.718L26.218 28H13.781ZM16.618 18L17.618 20H22.3818L23.381 18H16.618Z"
fill="#000091"
/>
<path
d="M38 67C38 66.4477 37.5523 66 37 66H9L8.88338 66.0067C8.38604 66.0645 8 66.4872 8 67C8 67.5523 8.44772 68 9 68H37L37.1166 67.9933C37.614 67.9355 38 67.5128 38 67Z"
fill="#000091"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M59.9933 56.8834C59.9355 56.386 59.5128 56 59 56H50V54H51L51.1332 53.993C52.2889 53.869 52.2889 52.131 51.1332 52.007L51 52H41L40.8668 52.007C39.7111 52.131 39.7111 53.869 40.8668 53.993L41 54H42V56H33L32.8834 56.0067C32.386 56.0645 32 56.4872 32 57V61L32.0067 61.1166C32.0645 61.614 32.4872 62 33 62L33.1166 61.9933C33.614 61.9355 34 61.5128 34 61V58H58V66H43.4L43.2834 66.0067C42.786 66.0645 42.4 66.4872 42.4 67C42.4 67.5523 42.8477 68 43.4 68H59L59.1166 67.9933C59.614 67.9355 60 67.5128 60 67V62H70V67L70.0067 67.1166C70.0645 67.614 70.4872 68 71 68C71.5523 68 72 67.5523 72 67V61L71.9933 60.8834C71.9355 60.386 71.5128 60 71 60H60V57L59.9933 56.8834ZM48 56V54H44V56H48Z"
fill="#000091"
/>
</>,
"Airport"
);
Loading