From 2a12f312c2578c97e069ca93381c0416ddef5cb4 Mon Sep 17 00:00:00 2001 From: Emmanuel Pelletier Date: Tue, 7 May 2024 17:17:18 +0200 Subject: [PATCH] integration/styles: better style scopingwhen using embedded dsfr - the CSS that included the DSFR excerpt we needed styled some base html tags directly. It might collide with a service CSS already there. Now a postcss plugin applies a CSS prefix to everything - we output in the lib two DSFR file versions: one with prefixed selectors, one raw. --- packages/integration/package-lock.json | 10 +++ packages/integration/package.json | 1 + packages/integration/postcss.config.mjs | 16 +++++ .../integration/src/components/StylesFull.tsx | 20 ++++++ .../src/components/StylesStandalone.tsx | 21 ++++++ packages/integration/src/dev.tsx | 64 +++++++++++-------- packages/integration/src/styles/dev.css | 1 + .../integration/src/styles/homepage-full.css | 2 +- packages/integration/src/styles/homepage.css | 4 ++ .../{required-dsfr.css => prefixed-dsfr.css} | 0 packages/integration/src/styles/raw-dsfr.css | 2 + packages/integration/vite.css-config.ts | 3 +- website/src/content/docs/guides/homepage.mdx | 24 ++++--- 13 files changed, 133 insertions(+), 35 deletions(-) create mode 100644 packages/integration/src/components/StylesFull.tsx create mode 100644 packages/integration/src/components/StylesStandalone.tsx rename packages/integration/src/styles/{required-dsfr.css => prefixed-dsfr.css} (100%) create mode 100644 packages/integration/src/styles/raw-dsfr.css diff --git a/packages/integration/package-lock.json b/packages/integration/package-lock.json index e4d00a5..292f31f 100644 --- a/packages/integration/package-lock.json +++ b/packages/integration/package-lock.json @@ -20,6 +20,7 @@ "import-single-ts": "^1.0.3", "patch-package": "^8.0.0", "postcss": "^8.4.38", + "postcss-prefix-selector": "^1.16.1", "prettier": "^3.2.5", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -3217,6 +3218,15 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-prefix-selector": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/postcss-prefix-selector/-/postcss-prefix-selector-1.16.1.tgz", + "integrity": "sha512-Umxu+FvKMwlY6TyDzGFoSUnzW+NOfMBLyC1tAkIjgX+Z/qGspJeRjVC903D7mx7TuBpJlwti2ibXtWuA7fKMeQ==", + "dev": true, + "peerDependencies": { + "postcss": ">4 <9" + } + }, "node_modules/postcss-selector-parser": { "version": "6.0.16", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", diff --git a/packages/integration/package.json b/packages/integration/package.json index 82ac902..1c304af 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -50,6 +50,7 @@ "import-single-ts": "^1.0.3", "patch-package": "^8.0.0", "postcss": "^8.4.38", + "postcss-prefix-selector": "^1.16.1", "prettier": "^3.2.5", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/packages/integration/postcss.config.mjs b/packages/integration/postcss.config.mjs index d2f4a75..189cef2 100644 --- a/packages/integration/postcss.config.mjs +++ b/packages/integration/postcss.config.mjs @@ -1,4 +1,5 @@ import purgecss from "@fullhuman/postcss-purgecss" +import prefixSelector from "postcss-prefix-selector" import autoprefixer from "autoprefixer" export default { @@ -9,5 +10,20 @@ export default { variables: true, }), autoprefixer(), + prefixSelector({ + prefix: ":where(.lasuite)", + transform: function (prefix, selector, prefixedSelector, filePath, rule) { + if (filePath.includes("dev.css") || filePath.includes("raw-dsfr.css")) { + return selector + } + if (selector.includes(".lasuite") || selector === "html" || selector === ":root") { + return selector + } + if (selector === "body") { + return `.lasuite` + } + return prefixedSelector + }, + }), ], } diff --git a/packages/integration/src/components/StylesFull.tsx b/packages/integration/src/components/StylesFull.tsx new file mode 100644 index 0000000..07c3b86 --- /dev/null +++ b/packages/integration/src/components/StylesFull.tsx @@ -0,0 +1,20 @@ +import { ReactNode, useEffect } from "react" +import fullStyles from "../styles/homepage-full.css?inline" +import devStyles from "../styles/dev.css?inline" + +export const StylesFull = ({ children }: { children: ReactNode }) => { + useEffect(() => { + if (document.querySelector("#styles-full")) { + return + } + const style = document.createElement("style") + style.id = "styles-full" + style.innerHTML = fullStyles + devStyles + document.head.appendChild(style) + return () => { + document.querySelector("#styles-full")?.remove() + } + }, []) + + return children +} diff --git a/packages/integration/src/components/StylesStandalone.tsx b/packages/integration/src/components/StylesStandalone.tsx new file mode 100644 index 0000000..941eb69 --- /dev/null +++ b/packages/integration/src/components/StylesStandalone.tsx @@ -0,0 +1,21 @@ +import { ReactNode, useEffect } from "react" +import dsfrStyles from "../styles/raw-dsfr.css?inline" +import homepageStyles from "../styles/homepage-gaufre.css?inline" +import devStyles from "../styles/dev.css?inline" + +export const StylesStandalone = ({ children }: { children: ReactNode }) => { + useEffect(() => { + if (document.querySelector("#styles-standalone")) { + return + } + const style = document.createElement("style") + style.id = "styles-standalone" + style.innerHTML = dsfrStyles + homepageStyles + devStyles + document.head.appendChild(style) + return () => { + document.querySelector("#styles-standalone")?.remove() + } + }, []) + + return children +} diff --git a/packages/integration/src/dev.tsx b/packages/integration/src/dev.tsx index 18c36e1..5bf623e 100644 --- a/packages/integration/src/dev.tsx +++ b/packages/integration/src/dev.tsx @@ -8,14 +8,19 @@ import { Gaufre } from "./components/Gaufre" import { EmailOrProconnect } from "./components/Homepage/EmailOrProconnect" import { Email } from "./components/Homepage/Email" import { Proconnect } from "./components/Homepage/Proconnect" +import { StylesFull } from "./components/StylesFull" +import { StylesStandalone } from "./components/StylesStandalone" import services from "../../../website/src/data/services.json" -import "./styles/dsfr.css" -import "./styles/homepage.css" -import "./styles/gaufre.css" -import "./styles/dev.css" - -const serviceHomepage = ({ id, content }: { content?: ReactNode; id: string }) => { +const serviceHomepage = ({ + id, + content, + styles = "standalone", +}: { + content?: ReactNode + id: string + styles: "standalone" | "full" +}) => { const service = services.find(({ id: itemId }) => itemId === id) if (!service) { console.log(`Service ${id} not found, exiting`) @@ -23,29 +28,33 @@ const serviceHomepage = ({ id, content }: { content?: ReactNode; id: string }) = } const { name, tagline } = service + const StylesProvider = styles === "standalone" ? StylesStandalone : StylesFull + return { path: `/homepage-template-${id}`, label: `Homepage ${name}`, component: ( - - {content || } - + + + {content || } + + ), } } @@ -54,14 +63,17 @@ const routes = [ serviceHomepage({ id: "resana", content: , + styles: "full", }), serviceHomepage({ id: "messagerie", content: , + styles: "full", }), serviceHomepage({ id: "tchap", content: , + styles: "standalone", }), serviceHomepage({ id: "france-transfert", @@ -75,9 +87,11 @@ const routes = [

), + styles: "standalone", }), serviceHomepage({ id: "equipes", + styles: "standalone", }), { path: "/gaufre", diff --git a/packages/integration/src/styles/dev.css b/packages/integration/src/styles/dev.css index 293d3b1..ea1e941 100644 --- a/packages/integration/src/styles/dev.css +++ b/packages/integration/src/styles/dev.css @@ -1,3 +1,4 @@ body { margin: 0; + padding: 0; } diff --git a/packages/integration/src/styles/homepage-full.css b/packages/integration/src/styles/homepage-full.css index daade4e..88e0ccc 100644 --- a/packages/integration/src/styles/homepage-full.css +++ b/packages/integration/src/styles/homepage-full.css @@ -1,3 +1,3 @@ -@import "./required-dsfr.css"; +@import "./prefixed-dsfr.css"; @import "./homepage.css"; @import "./gaufre.css"; diff --git a/packages/integration/src/styles/homepage.css b/packages/integration/src/styles/homepage.css index eefb0e4..ecf63d9 100644 --- a/packages/integration/src/styles/homepage.css +++ b/packages/integration/src/styles/homepage.css @@ -2,6 +2,10 @@ --lasuite-primary: #000091; } +.lasuite { + box-sizing: border-box; +} + @media (min-width: 78em) { .lasuite-container, .fr-container.lasuite-container { diff --git a/packages/integration/src/styles/required-dsfr.css b/packages/integration/src/styles/prefixed-dsfr.css similarity index 100% rename from packages/integration/src/styles/required-dsfr.css rename to packages/integration/src/styles/prefixed-dsfr.css diff --git a/packages/integration/src/styles/raw-dsfr.css b/packages/integration/src/styles/raw-dsfr.css new file mode 100644 index 0000000..7d8c7e1 --- /dev/null +++ b/packages/integration/src/styles/raw-dsfr.css @@ -0,0 +1,2 @@ +@import "./fonts.css"; +@import "./dsfr.css"; diff --git a/packages/integration/vite.css-config.ts b/packages/integration/vite.css-config.ts index 7e084d5..e3826ba 100644 --- a/packages/integration/vite.css-config.ts +++ b/packages/integration/vite.css-config.ts @@ -15,7 +15,8 @@ export default defineConfig({ emptyOutDir: false, rollupOptions: { input: { - "css-required-dsfr": resolve(__dirname, "src/styles/required-dsfr.css"), + "css-prefixed-dsfr": resolve(__dirname, "src/styles/prefixed-dsfr.css"), + "css-raw-dsfr": resolve(__dirname, "src/styles/raw-dsfr.css"), "css-homepage": resolve(__dirname, "src/styles/homepage.css"), "css-gaufre": resolve(__dirname, "src/styles/gaufre.css"), "css-homepage-full": resolve(__dirname, "src/styles/homepage-full.css"), diff --git a/website/src/content/docs/guides/homepage.mdx b/website/src/content/docs/guides/homepage.mdx index 2576b90..ceb787e 100644 --- a/website/src/content/docs/guides/homepage.mdx +++ b/website/src/content/docs/guides/homepage.mdx @@ -64,13 +64,21 @@ vous l'utilisiez déjà ou non dans votre projet, un fichier CSS différent est -Si vous voulez finement gérer vos styles car vous utilisez le bouton Gaufre sur tout votre site, vous pouvez choisir parmi tous les fichiers CSS présents : +Si vous voulez finement gérer vos styles car vous utilisez le bouton Gaufre sur tout votre site, +vous pouvez choisir parmi tous les fichiers CSS présents : -- `css/homepage-full.css` : contient l'extrait de DSFR nécessaire + les styles de la homepage + les styles de la gaufre, -- `css/homepage-gaufre.css` : contient les styles de la homepage + les styles de la gaufre (sans DSFR), +- `css/homepage-full.css` : contient l'extrait de DSFR nécessaire + les styles de la homepage + les + styles de la gaufre, +- `css/homepage-gaufre.css` : contient les styles de la homepage + les styles de la gaufre (sans + DSFR), - `css/homepage.css` : contient les styles de la homepage (sans DSFR ni gaufre), - `css/gaufre.css` : contient les styles de la gaufre (sans DSFR ni homepage), -- `css/required-dsfr.css` : contient l'extrait de DSFR nécessaire (rien d'autre), +- `css/prefixed-dsfr.css` : contient l'extrait de DSFR nécessaire (rien d'autre), + +Note : l'extrait de DSFR est toujours transformé pour que tous les sélecteurs soient derrière un +sélecteur `:where(.lasuite)`. Ceci permet d'intégrer le DSFR sans risque de conflit avec d'autres +styles, et sans impacter la spécifité du sélecteur. Si vous préférez utiliser un extrait de DSFR non +transformé, utilisez `css/raw-dsfr.css`. ### 2. JS @@ -124,10 +132,10 @@ savoir plus sur chaque composant. ##### Traduction -Si vous utilisez les composants React et que vous avez besoin de traduire votre page d'accueil, -vous pouvez envelopper les composants avec le provider `LaSuiteTranslationsProvider` provenant du -paquet, et lui passer en props vos `translations`. Les traductions françaises sont exportées en -tant que `frTranslations`. +Si vous utilisez les composants React et que vous avez besoin de traduire votre page d'accueil, vous +pouvez envelopper les composants avec le provider `LaSuiteTranslationsProvider` provenant du paquet, +et lui passer en props vos `translations`. Les traductions françaises sont exportées en tant que +`frTranslations`. #### Sans React