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.
This commit is contained in:
Emmanuel Pelletier
2024-05-07 17:17:18 +02:00
parent 3c2a6fd0cf
commit 2a12f312c2
13 changed files with 133 additions and 35 deletions

View File

@@ -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",

View File

@@ -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",

View File

@@ -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
},
}),
],
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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: (
<Homepage
lasuiteApiUrl={import.meta.env.VITE_LASUITE_API_URL}
entity="Gouvernement"
tagline={tagline}
serviceName={name}
serviceId={id}
logo={`/logos/${id}.svg`}
homepageUrl="/"
footerOptions={{
description: "Un service de la Direction interministérielle du numérique",
sitemapUrl: "/sitemap",
a11yUrl: "/accessibilite",
a11yLevel: "non compliant",
termsUrl: "/mentions-legales",
privacyUrl: "/donnees-personnelles",
}}
>
{content || <Proconnect url="#" />}
</Homepage>
<StylesProvider>
<Homepage
lasuiteApiUrl={import.meta.env.VITE_LASUITE_API_URL}
entity="Gouvernement"
tagline={tagline}
serviceName={`${name} - styles ${styles}`}
serviceId={id}
logo={`/logos/${id}.svg`}
homepageUrl="/"
footerOptions={{
description: "Un service de la Direction interministérielle du numérique",
sitemapUrl: "/sitemap",
a11yUrl: "/accessibilite",
a11yLevel: "non compliant",
termsUrl: "/mentions-legales",
privacyUrl: "/donnees-personnelles",
}}
>
{content || <Proconnect url="#" />}
</Homepage>
</StylesProvider>
),
}
}
@@ -54,14 +63,17 @@ const routes = [
serviceHomepage({
id: "resana",
content: <EmailOrProconnect proconnectUrl="#" />,
styles: "full",
}),
serviceHomepage({
id: "messagerie",
content: <Email />,
styles: "full",
}),
serviceHomepage({
id: "tchap",
content: <Email />,
styles: "standalone",
}),
serviceHomepage({
id: "france-transfert",
@@ -75,9 +87,11 @@ const routes = [
</p>
</div>
),
styles: "standalone",
}),
serviceHomepage({
id: "equipes",
styles: "standalone",
}),
{
path: "/gaufre",

View File

@@ -1,3 +1,4 @@
body {
margin: 0;
padding: 0;
}

View File

@@ -1,3 +1,3 @@
@import "./required-dsfr.css";
@import "./prefixed-dsfr.css";
@import "./homepage.css";
@import "./gaufre.css";

View File

@@ -2,6 +2,10 @@
--lasuite-primary: #000091;
}
.lasuite {
box-sizing: border-box;
}
@media (min-width: 78em) {
.lasuite-container,
.fr-container.lasuite-container {

View File

@@ -0,0 +1,2 @@
@import "./fonts.css";
@import "./dsfr.css";

View File

@@ -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"),

View File

@@ -64,13 +64,21 @@ vous l'utilisiez déjà ou non dans votre projet, un fichier CSS différent est
</TabItem>
</Tabs>
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