first commit:

- we have a static astro website under /website. It has the
implementation docs of the homepage/gaufre templates, and it handles the
few API endpoints (the gaufre js, backgrounds, logos)
- we have a vite app under /packages/integration. It has the react
components generating the homepage and the gaufre button, and their css.
Its used to generate an npm package
This commit is contained in:
Emmanuel Pelletier
2024-04-22 11:19:08 +02:00
commit d9859f1564
136 changed files with 17496 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 475 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,15 @@
<svg width="479.815" height="127.447" viewBox="0 0 479.815 127.447" fill="none" version="1.1" id="svg31" xmlns="http://www.w3.org/2000/svg">
<path d="M41.6279 84.5078c0 23.5822 15.9791 41.4232 37.3945 41.4232.9652 0 1.918-.037 2.8521-.112V98.6872c-6.7939-1.2143-11.5453-6.8998-11.5453-14.1794 0-7.2797 4.7514-12.9713 11.5453-14.1856V43.1964c-.9341-.0809-1.8869-.1183-2.8521-.1183-21.4154 0-37.3945 17.8472-37.3945 41.4297z" fill="#000091" id="path1"/>
<path d="M296.15 45.9783h-27.929v79.1297h27.929Z" fill="#e1000f" id="path2"/>
<path d="M119.531 43.0781H91.6016v82.0289h27.9294z" fill="#000091" id="path3"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="m27.8544 12.3591.0748 111.7099H0V12.3591Z" fill="#000091" id="path4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M164.459 61.6758c7.292 0 13.339 2.6404 19.703 6.2086l11.95-17.0689c-7.759-5.1187-18.003-10.0819-32.743-10.0819-2.341 0-4.627.1308-6.837.3924v23.2213c.685-1.918 4.047-2.6715 7.927-2.6715z" fill="#e1000f" id="path5"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M167.019 72.5548c-5.96-1.1334-10.313-2.1547-11.022-4.4525v24.6846c.635.1308 1.264.2616 1.874.3862 6.825 1.3949 11.477 2.4846 11.477 5.4301 0 .4297-.131.8094-.318 1.1706.193.3612.318.7412.318 1.1772 0 2.634-3.724 4.035-9.466 4.035-1.338 0-2.621-.106-3.885-.249v20.986c1.606.131 3.256.205 4.975.205 20.17 0 35.838-9.465 35.838-27.6175 0-19.5472-17.686-23.4268-29.791-25.7557z" fill="#e1000f" id="path6"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M157.871 93.173c-.61-.1245-1.239-.2554-1.874-.3861v2.3414c.635.137 1.264.2679 1.874.3924 5.822 1.1894 10.038 2.167 11.159 4.2531.187-.3612.318-.741.318-1.1707 0-2.9454-4.652-4.0352-11.477-5.4301z" fill="#e1000f" id="path7"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M440.715 102.34c-5.512 0-9.952-2.13-12.872-5.8474V124.44c3.823.941 7.859 1.482 12.093 1.482 14.117 0 26.534-5.43 35.066-14.272l-18.308-16.1351c-2.946 3.1011-8.687 6.8251-15.979 6.8251z" fill="#e1000f" id="path8"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M437.919 44.5889c-3.5 0-6.857.4109-10.076 1.1146v27.406c2.428-3.1136 5.785-4.9382 9.764-4.9382 5.586 0 10.4 3.5682 12.417 9.7767h-22.181v15.9791h51.35c.467-2.0114.622-5.1188.622-7.2921 0-24.3546-18.003-42.0461-41.896-42.0461z" fill="#e1000f" id="path9"/>
<path d="M342.946 45.9783h-27.929v79.1297h27.929z" fill="#e1000f" id="path10"/>
<path d="M342.946 0h-27.929v27.8369h27.929z" fill="#e1000f" id="path11"/>
<path d="M390.218 27.8362h-27.929v18.6193h-.162V71.9h.162v25.2887c0 17.2243 8.999 30.2583 30.414 30.2583 6.209 0 11.172-1.09 15.979-3.575V97.8115c-2.796 1.3949-6.202 2.6345-9.932 2.6345-5.891 0-8.532-3.2572-8.532-7.9152V71.9h18.464V46.4555h-18.464z" fill="#e1000f" id="path12"/>
<path d="M251.305 98.9288c-6.203 0-10.393-4.1909-10.393-10.5489V44.9387h-27.93v47.3207c0 20.3256 15.207 33.6706 38.323 33.6706 2.721 0 5.324-.206 7.815-.56V95.6782c-1.831 2.0487-4.527 3.2506-7.815 3.2506z" fill="#e1000f" id="path13"/>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

View File

@@ -0,0 +1,8 @@
export default function Aside({ type = "note", title = "", children }) {
return (
<div className={`starlight-aside starlight-aside--${type}`}>
<p className="starlight-aside__title">{title}</p>
<section className="starlight-aside__content">{children}</section>
</div>
)
}

View File

@@ -0,0 +1,24 @@
import { useEffect } from "react"
import * as Prism from "prismjs"
import "prismjs/components/prism-jsx"
import "@/styles/prism.css"
export default function Code({ children, language, fixedHeight = false }) {
useEffect(() => {
Prism.highlightAll()
}, [children, language])
return (
<div className={fixedHeight ? "language-fixedheight react-code" : "react-code"}>
<pre>
<code className={`language-${language}`}>{children}</code>
</pre>
<button
onClick={() => {
navigator.clipboard.writeText(children)
}}
>
Copier
</button>
</div>
)
}

View File

@@ -0,0 +1,237 @@
import { useEffect, useState } from "react"
import { renderToStaticMarkup } from "react-dom/server"
import services from "@/data/services.json"
import Code from "./Code"
import {
Homepage,
HomepageEmail,
HomepageEmailOrProconnect,
HomepageProconnect,
} from "@gouvfr-lasuite/integration"
import * as prettier from "prettier/standalone"
import prettierHtml from "prettier/plugins/html"
const defaultFormData = {
serviceId: "",
serviceName: "Service",
tagline: "**Service**, un outil sécurisé <br>pour les agents de l'État",
entity: "Gouvernement",
homepageType: "proconnect",
}
const homepageTypes = {
proconnect: {
label: "ProConnect uniquement",
importCode: "HomepageProconnect",
componentCode: `<HomepageProconnect url="~~replace~~" />`,
Component: <HomepageProconnect url="~~replace~~" />,
},
"email-or-proconnect": {
label: "E-mail + ProConnect",
importCode: "HomepageEmailOrProconnect",
componentCode: `<HomepageEmailOrProconnect proconnectUrl="~~replace~~" emailForm={{ action: "~~replace~~" }} />`,
Component: (
<HomepageEmailOrProconnect
proconnectUrl="~~replace~~"
emailForm={{ action: "~~replace~~" }}
/>
),
},
email: {
label: "E-mail uniquement",
importCode: "HomepageEmail",
componentCode: `<HomepageEmail action="~~replace~~" />`,
Component: <HomepageEmail action="~~replace~~" />,
},
custom: {
label: "Autre",
importCode: null,
componentCode: `~~replace~~`,
Component: null,
},
}
export default function HomepageGenerator() {
const [codeData, setCodeData] = useState<any>(defaultFormData)
const [htmlMarkup, setHtmlMarkup] = useState<string>("")
useEffect(() => {
getHTMLMarkup(codeData).then((html) => {
setHtmlMarkup(html)
})
}, [codeData])
return (
<div style={{ margin: "1.5rem 0" }}>
<form
className="react-form not-content"
onChange={(e) => {
const formData = new FormData(e.currentTarget)
setCodeData({
...Object.fromEntries(formData),
serviceId:
formData.get("serviceId") && formData.get("serviceId") !== "other"
? formData.get("serviceId")
: undefined,
})
}}
>
<div className="react-form-input">
<label htmlFor="serviceId">Service</label>
<select
name="serviceId"
id="serviceId"
defaultValue={defaultFormData.serviceId}
onChange={(e) => {
const serviceId = e.currentTarget.value
const service = services.find((service) => service.id === serviceId)
;(document.querySelector("#tagline") as HTMLInputElement).value = (
service || defaultFormData
).tagline
;(document.querySelector("#entity") as HTMLInputElement).value = (
service || defaultFormData
).entity
;(document.querySelector("#serviceName") as HTMLInputElement).value = service
? service.name
: defaultFormData.serviceName
;(document.querySelector("#homepageType") as HTMLInputElement).value = (
service || defaultFormData
).homepageType
}}
>
<option value="">Choisir votre service pour pré-remplir les champs</option>
{services.map((service) => (
<option key={service.id} value={service.id}>
{service.name}
</option>
))}
<option value="other">Autre</option>
</select>
</div>
<div className="react-form-input">
<label htmlFor="serviceName">Nom du service</label>
<input
type="text"
name="serviceName"
id="serviceName"
defaultValue={defaultFormData.serviceName}
/>
</div>
<input type="hidden" name="entity" id="entity" defaultValue={defaultFormData.entity} />
<div className="react-form-input">
<label htmlFor="tagline">
Phrase d'accroche
<span>
Mettre le texte entre ** pour l'écrire en gras, usage de <code>&lt;br&gt;</code>{" "}
possible
</span>
</label>
<input type="text" name="tagline" id="tagline" defaultValue={defaultFormData.tagline} />
</div>
<div className="react-form-input">
<label htmlFor="homepageType">Type de connexion</label>
<select name="homepageType" id="homepageType">
<option value="">Choisir</option>
{Object.keys(homepageTypes).map((key) => (
<option key={key} value={key}>
{homepageTypes[key].label}
</option>
))}
</select>
</div>
</form>
{!!codeData && (
<>
<div>
<h2 style={{ marginTop: "1.5em" }}>Code React correspondant</h2>
<Code language="jsx">{getReactMarkup(codeData)}</Code>
</div>
<div>
<h2 style={{ marginTop: "1.5em" }}>Code HTML correspondant</h2>
<Code language="html" fixedHeight>
{htmlMarkup}
</Code>
</div>
</>
)}
</div>
)
}
const getReactMarkup = (codeData) => {
return `import { Homepage${
codeData.homepageType && homepageTypes[codeData.homepageType].importCode
? `, ${homepageTypes[codeData.homepageType].importCode}`
: ""
} } from "@gouvfr-lasuite/integration"
export default function MyHomepage() {
return (
<Homepage
lasuiteApiUrl="${import.meta.env.PUBLIC_LASUITE_API_URL}"
entity="${codeData.entity}"
tagline="${codeData.tagline}"
serviceName="${codeData.serviceName}" ${
!!codeData.serviceId
? `
serviceId="${codeData.serviceId}"`
: ""
}
logo="~~replace~~"
homepageUrl="/"
footerOptions={{
description: "Un service de la Direction interministérielle du numérique",
sitemapUrl: "~~replace~~",
a11yUrl: "~~replace~~",
a11yLevel: "~~replace~~",
termsUrl: "~~replace~~",
privacyUrl: "~~replace~~",
}}
>
${codeData.homepageType ? homepageTypes[codeData.homepageType].componentCode : ""}
</Homepage>
)
}`
}
const getHTMLMarkup = (codeData) => {
const Component = (
<Homepage
lasuiteApiUrl={import.meta.env.PUBLIC_LASUITE_API_URL}
entity={codeData.entity}
tagline={codeData.tagline}
serviceName={codeData.serviceName}
serviceId={codeData.serviceId}
logo={
codeData.serviceId
? `${import.meta.env.PUBLIC_LASUITE_API_URL}/api/logos/v1/${codeData.serviceId}.svg`
: "~~replace~~"
}
homepageUrl="/"
footerOptions={{
description: "Un service de la Direction interministérielle du numérique",
sitemapUrl: "~~replace~~",
a11yUrl: "~~replace~~",
a11yLevel: "~~replace~~" as "non compliant",
termsUrl: "~~replace~~",
privacyUrl: "~~replace~~",
}}
>
{codeData.homepageType ? homepageTypes[codeData.homepageType].Component : ""}
</Homepage>
)
const markup = renderToStaticMarkup(Component)
return prettier.format(markup, {
parser: "html",
plugins: [prettierHtml],
printWidth: 100,
tabWidth: 4,
bracketSameLine: false,
htmlWhitespaceSensitivity: "ignore",
})
}

View File

@@ -0,0 +1,6 @@
import { defineCollection } from "astro:content"
import { docsSchema } from "@astrojs/starlight/schema"
export const collections = {
docs: defineCollection({ schema: docsSchema() }),
}

View File

@@ -0,0 +1,103 @@
---
title: La Gaufre
sidebar:
order: 30
---
import { Aside } from "@astrojs/starlight/components"
import assetGaufre from "./gaufre.png"
import { Code } from "@astrojs/starlight/components"
import gaufreHtml from "@gouvfr-lasuite/integration/dist/html/gaufre.html?raw"
![](./gaufre.png)
Le bouton "Gaufre" est un élément d'interface commun à tous les services de La Suite numérique. Il
permet aux internautes de facilement passer d'un service de La Suite à un autre.
Pour intégrer la Gaufre, il y a trois étapes :
- intégrer le HTML du bouton
- ajouter le fichier CSS nécessaire
- ajouter le fichier JS chargeant le widget au clic du bouton
:::note
Ce guide est à suivre pour intégrer la Gaufre sur toute page autre que
[votre page d'accueil](../homepage). Le gabarit de page d'accueil inclut déjà le bouton.
:::
## Règles d'utilisation
Le bouton Gaufre est destiné à être présent dans le coin supérieur droit de la page de votre page
web afin d'être toujours placé au même endroit quelque soit le service.
## Installation
### 1. HTML
#### Avec React
Si vous utilisez React, utilisez le composant `Gaufre` fourni par le paquet :
```jsx
import { Gaufre } from "@gouvfr-lasuite/integration"
function MonComposant() {
return <Gaufre />
}
```
:::note
Même en utilisant React, vous devrez charger le CSS et le JS manuellement. Ce petit travail
supplémentaire est fait pour vous donner plus de contrôle sur le chargement des assets suivant votre
stack technique.
:::
#### Sans React
Si vous n'utilisez pas React, utilisez le HTML présent dans le paquet :
<Code code={gaufreHtml} lang="html" title="@gouvfr-lasuite/integration/dist/html/gaufre.html" />
### 2. CSS
Le CSS nécessaire est présent dans `@gouvfr-lasuite/integration/dist/css/gaufre.css`. Il est à
inclure dans votre projet comme n'importe quel fichier CSS.
Suivant votre stack technique, vous pouvez peut-être inclure directement le CSS depuis les
dépendances. Par exemple avec _vite_ :
<Code
code={`@import "@gouvfr-lasuite/integration/dist/css/gaufre.css";`}
lang="css"
title="vos/styles/globaux/app.css"
/>
Si vous n'utilisez pas de _bundler_ particulier et que avez plutôt décidé à l'installation de
[copier les assets](/guides/getting-started/#gestion-des-assets), vous pouvez inclure le CSS
directement via une balise `link` comme tout fichier CSS.
### 3. JS
Après avoir ajouté le bouton, il est nécessaire de le faire fonctionner. Pour ça, chargez ce fichier
JS externe qui s'occupe d'afficher le widget au clic du bouton. Vous pouvez ajouter ce code dans
votre `<head>` :
<Code
code={`<script id="lasuite-gaufre-script" async defer src="${import.meta.env.PUBLIC_LASUITE_API_URL}/api/v1/gaufre.js"></script>`}
lang="html"
/>
:::note
Pour que le script fonctionne il est nécessaire qu'il ait un id `lasuite-gaufre-script`. Ne
l'enlevez pas en copiant l'exemple !
:::
## Exemple
[La Gaufre : exemple HTML](/examples/gaufre/html).

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -0,0 +1,103 @@
---
title: Démarrer
sidebar:
order: 0
---
import { Tabs, TabItem } from "@astrojs/starlight/components"
Utilisez les composants d'interface prêt-à-l'emploi pour votre service de La Suite numérique.
## Présentation
Nous mettons à disposition plusieurs composants.
### Gabarit de page d'accueil
![](./homepage.png)
Le gabarit de page d'accueil permet de rapidement intégrer dans le code de votre service une page
d'accueil suivant un visuel commun avec tous les autres services de La Suite.
### _La Gaufre_, le bouton des services
![](./gaufre.png)
Le bouton de La Suite numérique permet aux internautes de facilement passer d'un service de La Suite
à un autre.
Tous ces éléments sont disponibles via :
- des gabarits HTML, utilisables au choix via des **composants React** ou directement via des
**fichiers .html**,
- **du CSS** nécessaire à afficher correctement les gabarits HTML,
- et une **API web** exposant plusieurs _endpoints_.
## Installation
### Via NPM
Le plus simple pour accéder au code est d'installer le paquet npm avec votre gestionnaire de paquets
préféré :
<Tabs>
<TabItem label="npm">
```sh
npm install @gouvfr-lasuite/integration
```
</TabItem>
<TabItem label="Yarn">
```sh
yarn add @gouvfr-lasuite/integration
```
</TabItem>
<TabItem label="pnpm">
```sh
pnpm add @gouvfr-lasuite/integration
```
</TabItem>
</Tabs>
### Manuellement
À la place d'utiliser npm, vous pouvez télécharger directement les fichiers depuis le dépôt sur
GitHub :
@TODO
### Gestion des assets
Quand vous voudrez utiliser les assets fournis par le paquet, vous aurez peut-être besoin de copier
les assets dans un dossier accessible par votre serveur web, suivant votre stack technique.
Tous les fichiers exposés par le paquet npm sont dans son dossier `dist/`.
Si vous utilisez déjà _webpack_, _vite_ ou alternative, vous pourrez importer les fichiers CSS
directement depuis votre code comme tout autre CSS venant des dépendances. Par exemple avec _vite_ :
```js
// dans un fichier JS :
import '@gouvfr-lasuite/integration/dist/gaufre.css';
// ou dans un fichier CSS :
@import "@gouvfr-lasuite/integration/dist/gaufre.css";
```
Si vous n'utilisez pas de bundler, une façon simple de rendre accessible le code du paquet est
d'avoir un script qui copie pour vous les fichiers nécessaires dans un dossier accessible par votre
serveur web. Par exemple, rajouter ceci dans votre `package.json` copiera les fichiers du paquet
dans un dossier `public/@gouvfr-lasuite/integration` après chaque installation :
```diff lang="json"
"scripts": {
+ "copy-lasuite-assets": "cp -r node_modules/@gouvfr-lasuite/integration/dist/ public/@gouvfr-lasuite/integration",
+ "postinstall": "npm run copy-lasuite-assets"
}
```
Une fois les fichiers installés, vous êtes prêt à utiliser les composants fournis par le paquet !

View File

@@ -0,0 +1,29 @@
---
title: Générateur de page d'accueil
sidebar:
order: 20
---
import HomepageGenerator from "@/components/HomepageGenerator.tsx"
Après avoir [installé le JS et le CSS nécessaires de la page d'accueil](../homepage), vous pouvez
utiliser ce formulaire pour générer facilement la partie HTML.
Séléctionnez votre service dans la liste pour automatiquement générer le template prévu. Vous pouvez
aussi générer un template sans service pré-défini si besoin.
:::caution
Dans les exemples fournis, toutes les parties marquées <code>\~\~replace\~\~</code> sont à remplacer
avec vos données !
:::
:::tip
Le logo à afficher en entête peut être récupéré dans le dossier `dist/logos` du paquet npm si
besoin.
:::
<HomepageGenerator client:load />

View File

@@ -0,0 +1,159 @@
---
title: Gabarit de page d'accueil
sidebar:
order: 10
---
import { Image } from "astro:assets"
import { Code, Tabs, TabItem } from "@astrojs/starlight/components"
import assetHomepage from "./homepage.png"
import homepageHtml from "@gouvfr-lasuite/integration/dist/html/homepage.html?raw"
<p>
<a href={assetHomepage.src} target="_blank">
<Image
src={assetHomepage}
alt="Voir le gabarit de page d'accueil en grand (nouvelle fenêtre)"
/>
</a>
</p>
Le gabarit de page d'accueil vous permet de rapidement intégrer dans le code de votre service une
page d'accueil suivant un visuel commun avec tous les autres services de La Suite.
## Règles d'utilisation
Le gabarit de page d'accueil est un guide à utiliser pour intégrer votre page d'accueil de service.
Si pour des raisons techniques quelconques vous ne pouvez pas reprendre les templates proposés à la
lettre, voici les points importants à retenir :
- la page d'accueil doit contenir [le bouton Gaufre](../gaufre) dans son coin supérieur droit,
- elle doit présenter le nom de votre service à travers une phrase d'accroche suivant un minimum le
visuel proposé,
- elle doit permettre à l'utilisateur de se connecter à votre application,
- elle doit afficher en fond de page
[une des photos communes de La Suite](/reference/api#background)
## Installation
### 1. CSS
Le gabarit se base sur une partie du [DSFR](https://www.systeme-de-design.gouv.fr/). Suivant que
vous l'utilisiez déjà ou non dans votre projet, un fichier CSS différent est à charger.
<Tabs>
<TabItem label="Vous n'utilisez pas le DSFR">
Le fichier CSS à utiliser est :
```
@gouvfr-lasuite/integration/dist/css/homepage-gaufre-dsfr.css
```
Si durant l'installation vous avez décidé de
[copier les assets](../getting-started/#gestion-des-assets), assurez-vous que la police de
caractères Marianne a bien été copiée en suivant le chemin noté dans le CSS.
</TabItem>
<TabItem label="Vous utilisez déjà le DSFR">
Le fichier CSS à utiliser est :
```
@gouvfr-lasuite/integration/dist/css/homepage-gaufre.css
```
</TabItem>
</Tabs>
### 2. JS
Pour faire fonctionner le bouton Gaufre présent en haut à droite de la page, il est nécessaire de
charger un fichier JS externe qui s'occupe d'afficher le widget au clic du bouton.
Vous pouvez ajouter ce code dans le `<head>` de votre page d'accueil uniquement, ou dans le `<head>`
commun à toutes vos pages si vous décidez d'intégrer [La Gaufre](../gaufre) de façon globale sur
votre site :
<Code
code={`<script id="lasuite-gaufre-script" async defer src="${import.meta.env.PUBLIC_LASUITE_API_URL}/api/v1/gaufre.js"></script>`}
lang="html"
/>
:::note
Pour que le script fonctionne il est nécessaire qu'il ait un id `lasuite-gaufre-script`, ne
l'enlevez pas !
:::
### 3. HTML
:::tip
Pour facilement générer le code nécessaire, utilisez
[le générateur de gabarit de page d'accueil](../homepage-generator).
Sinon, suivez les conseils ci-dessous.
:::
#### Avec React
Si vous utilisez React, utilisez le composant `Homepage` fourni par le paquet. Il faut lui passer
plusieurs _props_ pour adapter le contenu à votre service. Et lui passer en enfant le contenu à
afficher dans la partie droite de la page d'accueil.
Plusieurs composants prêt-à-l'emploi sont disponibles pour vous aider à construire rapidement ce
contenu :
- `HomepageEmail` affiche un bloc contenant un formulaire de connexion par e-mail,
- `HomepageEmailOrProconnect` affiche un bloc contenant un formulaire de connexion par e-mail et un
bouton Proconnect,
- `HomepageProconnect` affiche un bloc contenant un bouton de connexion par Proconnect,
Tous les composants sont typés avec TypeScript avec des props commentées. En attendant une
documentation plus complète, vous pouvez vous aider de l'autocomplétion de votre éditeur pour en
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`.
#### Sans React
Si vous n'utilisez pas React, utilisez le HTML présent dans
`@gouvfr-lasuite/integration/dist/html/homepage.html` :
<details>
<summary>Voir le code HTML</summary>
<Code
code={homepageHtml}
lang="html"
title="@gouvfr-lasuite/integration/dist/html/homepage.html"
/>
</details>
Remplacez le contenu de la div `.lasuite-homepage__form-inner` par le contenu propre à votre page
d'accueil. Vous pouvez utiliser du HTML prêt à l'emploi pour construire rapidement ce contenu :
- `@gouvfr-lasuite/integration/dist/html/email.html` affiche un bloc contenant un formulaire de
connexion par e-mail,
- `@gouvfr-lasuite/integration/dist/html/email-or-proconnect.html` affiche un bloc contenant un
formulaire de connexion par e-mail et un bouton Proconnect,
- `@gouvfr-lasuite/integration/dist/html/proconnect.html` affiche un bloc contenant un bouton de
connexion par Proconnect.
:::caution
Dans les gabarits HTML fournis, toutes les parties `~~replace~~` sont à remplacer avec vos données !
:::
:::tip
Vous pouvez récupérer le logo de votre service au format svg dans le paquet npm :
`@gouvfr-lasuite/integration/dist/logos`.
:::

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

View File

@@ -0,0 +1,18 @@
---
title: Intégrations de La Suite numérique
description:
Intégrez facilement les interfaces utilisateurs communes des services de La Suite numérique.
template: splash
hero:
tagline:
Intégrez facilement les interfaces utilisateurs communes des services de <a style="color&#58;
var(--sl-color-text-accent)" href="https://lasuite.numerique.gouv.fr">La Suite numérique</a>.
image:
file: ../../assets/lasuite-logo.svg
alt: ""
actions:
- text: Commencer
link: /guides/getting-started
icon: right-arrow
variant: primary
---

View File

@@ -0,0 +1,90 @@
---
title: Endpoints de l'API web
---
L'API d'intégration de La Suite expose plusieurs endpoints facilitant l'usage des UI communes.
## URL de l'API
```
https://integration.lasuite.numerique.gouv.fr
```
C'est par exemple l'URL à passer comme `lasuiteApiUrl` aux composants React.
## Fond d'écran de page d'accueil
```text "{id}" "{avif,jpg}"
https://integration.lasuite.numerique.gouv.fr/api/backgrounds/v1/{id}.avif
https://integration.lasuite.numerique.gouv.fr/api/backgrounds/v1/{id}.jpg
```
- Méthode : GET
- Retourne : une image en AVIF ou JPG en 1920x1080
- Passer l'[id d'un service](#liste-des-services-de-la-suite) à la place de `{id}`.
💡 Si votre service n'est pas (encore) supporté, vous pouvez utiliser l'id `default`.
Les photos d'arrière-plan de chaque page d'accueil de service changent plusieurs fois par mois à
travers tous les services.
Pour que ce changement se fasse sans avoir à mettre à jour le code des pages d'accueil
régulièrement, un service peut requêter une photo d'arrière-plan avec son identifiant. L'image
exposée sur cette URL change régulièrement.
Les photos sont disponibles à la fois en avif et en jpeg.
### Exemple d'usage
Avec le service Resana :
```html
<picture>
<source
srcset="https://integration.lasuite.numerique.gouv.fr/api/backgrounds/v1/resana.avif"
type="image/avif"
/>
<img
src="https://integration.lasuite.numerique.gouv.fr/api/backgrounds/v1/resana.jpg"
alt=""
width="1920"
height="1080"
/>
</picture>
```
<picture>
<source
srcset="https://integration.lasuite.numerique.gouv.fr/api/backgrounds/v1/resana.avif"
type="image/avif"
/>
<img
src="https://integration.lasuite.numerique.gouv.fr/api/backgrounds/v1/resana.jpg"
alt=""
width="1920"
height="1080"
/>
</picture>
## Widget La Gaufre
```
https://integration.lasuite.numerique.gouv.fr/api/v1/gaufre.js
```
- Méthode : GET
- Retourne : le JavaScript s'occupant d'afficher la popup listant les services de La Suite au clic
sur le bouton [Gaufre](/guides/gaufre)
## Liste des services de La Suite
`https://integration.lasuite.numerique.gouv.fr/api/v1/services.json`
Retourne un tableau JSON listant les services de La Suite numérique. Chaque service a cette
structure :
- `id` l'identifiant du service, utilisé par d'autres APIs,
- `name` le nom du service,
- `url` l'url de la page d'accueil du service.
Ce endpoint sert principalement à retrouver l'`id` correspondant à un service et est là plutôt à
titre informatif.

View File

@@ -0,0 +1,42 @@
[
{
"id": "equipes",
"name": "Equipes",
"url": "https://desk-staging.beta.numerique.gouv.fr/",
"tagline": "**Equipes**, la gestion de groupes <br>centralisée pour tous vos projets",
"homepageType": "proconnect",
"entity": "Gouvernement"
},
{
"id": "france-transfert",
"name": "France Transfert",
"url": "https://francetransfert.numerique.gouv.fr/upload",
"tagline": "**France Transfert** permet denvoyer des fichiers <br>volumineux non sensibles de manière sécurisée <br>à un agent de lEtat ou entre agents",
"homepageType": "custom",
"entity": "Gouvernement"
},
{
"id": "messagerie",
"name": "Messagerie",
"url": "https://webmail.numerique.gouv.fr/appsuite/",
"tagline": "**Messagerie** de l'État <br>le mail simple, centralisé et sécurisé",
"homepageType": "email",
"entity": "Gouvernement"
},
{
"id": "resana",
"name": "Resana",
"url": "https://resana.numerique.gouv.fr/public/",
"tagline": "**Resana** <br>groupes de travail <br>et suite collaborative en ligne",
"homepageType": "email-or-proconnect",
"entity": "Gouvernement"
},
{
"id": "tchap",
"name": "Tchap",
"url": "https://www.tchap.gouv.fr/",
"tagline": "**Tchap** <br>la messagerie <br>instantanée du Secteur Public",
"homepageType": "email",
"entity": "Gouvernement"
}
]

9
website/src/env.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />
interface ImportMetaEnv {
readonly PUBLIC_LASUITE_API_URL: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}

View File

@@ -0,0 +1,218 @@
---
import services from "@/data/services.json"
import { Image } from "astro:assets"
const logos = import.meta.glob<{ default: ImageMetadata }>("/src/assets/logos/*.svg")
---
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8" />
<title>Services de La Suite numérique</title>
<style is:inline>
@font-face {
font-family: Marianne;
src: url("/fonts/Marianne-Regular-subset.woff2") format("woff2");
font-weight: 400;
font-display: swap;
}
@font-face {
font-family: "Marianne fallback";
src: local("Arial");
ascent-override: 103.16%;
descent-override: 23.35%;
line-gap-override: 0%;
size-adjust: 109.64%;
}
* {
box-sizing: border-box;
}
html,
body,
.lasuite-Services {
height: 100vh;
max-height: 22rem;
}
html {
font-size: 100%;
font-family: Marianne, "Marianne fallback", BlinkMacSystemFont, "Segoe UI", "Noto Sans",
Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
}
body {
margin: 0 0 0.5rem;
background: transparent;
}
ul {
margin: 0;
padding: 0;
list-style: none;
}
.fr-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap; /* added line */
border: 0;
display: block;
}
.fr-enlarge-link {
position: relative;
}
.fr-enlarge-link a {
background-image: none;
outline-width: 0;
}
.fr-enlarge-link a::before {
content: "";
display: block;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
outline-offset: 2px;
outline-style: inherit;
outline-color: inherit;
outline-width: 2px;
z-index: 1;
}
.lasuite-Services {
background-color: #01018fcc;
padding: 3px;
width: 19rem;
border-radius: 8px;
filter: drop-shadow(0 4px 3px rgb(0 0 0 / 0.07)) drop-shadow(0 2px 2px rgb(0 0 0 / 0.06));
overflow: hidden;
}
.lasuite-Services-outer {
height: 100%;
border-radius: 6px;
overflow: hidden;
}
.lasuite-Services-inner {
border-radius: 2px;
background-color: white;
height: 100%;
overflow: auto;
}
.lasuite-Service {
display: flex;
align-items: center;
padding: 1rem 2rem;
border-top: 1px solid transparent;
border-bottom: 1px solid transparent;
}
.lasuite-Service:hover,
.lasuite-Service:focus-within {
background-color: #f0f0fa;
border-top: 1px solid #8989cd;
border-bottom: 1px solid #8989cd;
}
.lasuite-Service-icon {
display: flex;
align-items: center;
}
.lasuite-Service-name {
margin-left: 1.5rem;
text-decoration: none;
color: #161616;
}
.lasuite-Service-name:focus {
outline: 0;
}
.scrollbars {
scrollbar-width: thin;
scrollbar-color: #aaa transparent;
}
.scrollbars::-webkit-scrollbar {
width: 5px;
height: 5px;
}
.scrollbars::-webkit-scrollbar-track {
background: 0 0;
}
.scrollbars::-webkit-scrollbar-thumb {
background-color: #ddd;
border-radius: 6px;
}
.scrollbars:not(:hover, :focus) {
scrollbar-color: transparent transparent;
}
.scrollbars:not(:hover, :focus):-webkit-scrollbar-thumb {
background-color: transparent;
}
#lasuite-service-suite-numerique {
font-size: 0.85rem;
opacity: 0.75;
}
</style>
</head>
<body>
<div class="lasuite-Services">
<div class="lasuite-Services-outer">
<h1 class="fr-sr-only">Liste des services de La Suite numérique</h1>
<ul class="lasuite-Services-inner scrollbars">
{
services.map(({ id, name, url }, i) => (
<li>
<div class="lasuite-Services-item lasuite-Service fr-enlarge-link">
<div class="lasuite-Service-icon">
<Image
src={logos[`/src/assets/logos/${id}.svg`]()}
width="40"
height="40"
alt=""
loading="eager"
/>
</div>
<a
target="_parent"
class="lasuite-Service-name"
href={url}
id={`lasuite-service-${id}`}
{...((i === 0 && { autofocus: true }) || {})}
>
{name}
</a>
</div>
</li>
))
}
</ul>
</div>
</div>
<script>
document.addEventListener("keyup", (event) => {
if (event.key === "Escape") {
window.parent.postMessage("lasuite-close-services-iframe", "*")
}
})
</script>
</body>
</html>

View File

@@ -0,0 +1,16 @@
import type { APIContext } from "astro"
import services from "@/data/services.json"
export async function GET({ url }: APIContext) {
const response = new Response(
JSON.stringify(
services.map((service) => ({
id: service.id,
name: service.name,
url: new URL(`/services/${service.id}`, url).toString(),
})),
),
)
response.headers.set("Content-Type", "application/json")
return response
}

View File

@@ -0,0 +1,94 @@
---
import gaufreCssUrl from "@gouvfr-lasuite/integration/dist/css/gaufre.css?url"
---
<!doctype html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<title>Exemple intégration bouton La Gaufre - La Suite numérique</title>
<style is:inline>
.wrapper {
margin: 2rem auto;
font-family: sans-serif;
width: 100%;
max-width: 800px;
}
.example {
margin: 2rem auto;
border: 1px solid #999;
padding: 2rem;
border-radius: 0.5rem;
}
.example > div {
display: flex;
justify-content: space-between;
}
.example > div > h1 {
margin-top: 0;
}
.example > p {
margin-top: 0;
}
</style>
<!-- code à ajouter pour faire marcher le bouton Gaufre -->
<!-- ce fichier buildé correspond à @gouvfr-lasuite/integration/dist/css/gaufre.css -->
<link rel="stylesheet" href={gaufreCssUrl} />
<script
is:inline
id="lasuite-gaufre-script"
async
defer
src={`${import.meta.env.PUBLIC_LASUITE_API_URL}/api/v1/gaufre.js`}></script>
<!-- fin du code à ajouter -->
</head>
<body>
<div class="wrapper">
<p><a href="/guides/gaufre">Retour à la documentation</a></p>
<div class="example">
<div>
<h1>Test de la Gaufre</h1>
<!-- code à copier pour faire marcher le bouton Gaufre -->
<button
type="button"
class="lasuite-gaufre-btn lasuite-gaufre-btn--vanilla js-lasuite-gaufre-btn"
title="Les services de La Suite numérique"
>
Les services de La Suite numérique
</button>
<!-- fin du code à copier -->
</div>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ac diam a libero posuere
ornare facilisis in mi. Nullam eu vulputate augue, in auctor nibh. Praesent ac tempus dui.
Integer vel enim non purus facilisis mattis et vel dolor. Aliquam lacinia elit et massa
faucibus, at dictum risus ornare. Vivamus ultricies magna et gravida consequat. Donec ac
odio finibus, lobortis purus vel, consequat purus. Maecenas convallis vel enim eu
malesuada. Vestibulum elementum maximus massa, a porta erat congue quis. Nunc neque quam,
euismod et malesuada in, bibendum ac ex. Phasellus felis elit, egestas a convallis nec,
malesuada a est. Donec ac urna venenatis lorem aliquet rhoncus in accumsan ipsum.
</p>
<p>
Interdum et malesuada fames ac ante ipsum primis in faucibus. Morbi sed augue elementum,
tempus diam in, euismod purus. Fusce interdum, leo nec blandit eleifend, sapien ligula
egestas quam, quis aliquam ex turpis ut augue. Nullam a neque consectetur, feugiat eros a,
lacinia tortor. Proin imperdiet vehicula justo, eget bibendum tortor gravida a.
Pellentesque sit amet fermentum urna. Ut rutrum eros a ligula dapibus pharetra. In
porttitor arcu in euismod dictum. Aenean vestibulum mi et dignissim rutrum. Phasellus
ultrices ex justo, eu tincidunt metus efficitur non. Curabitur ac lorem ornare, aliquet
neque et, tristique elit. Donec quis turpis sodales, interdum massa fermentum, dictum
magna.
</p>
<p>
Mauris elit risus, facilisis at magna quis, interdum tempor nulla. Ut ac erat eget tellus
ultricies semper. Ut at dictum ante. Lorem ipsum dolor sit amet, consectetur adipiscing
elit. Nam placerat lacinia eros ac convallis. Sed ultricies lectus et pharetra aliquet.
Vestibulum feugiat pulvinar fermentum. Vivamus imperdiet dapibus ornare. Donec venenatis,
lectus id faucibus tempus, sapien urna molestie augue, at egestas enim lectus quis nisi.
</p>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,129 @@
@import "./prism.css";
@font-face {
font-family: Marianne;
src: url(../assets/fonts/Marianne-Regular.woff2) format("woff2");
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: Marianne;
src: url(../assets/fonts/Marianne-Medium.woff2) format("woff2");
font-weight: 500;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: Marianne;
src: url(../assets/fonts/Marianne-Bold.woff2) format("woff2");
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: Marianne_cls_fallback;
src: local("Arial");
ascent-override: 103.16%;
descent-override: 23.35%;
line-gap-override: 0%;
size-adjust: 109.64%;
}
:root {
--sl-font: "Marianne", "Marianne_cls_fallback", ui-sans-serif, system-ui, sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--sl-text-code-sm: var(--sl-text-code);
font-size: 1.125rem;
}
/* Light mode colors. */
:root[data-theme="light"] {
--sl-color-accent-low: #e3e3fd;
--sl-color-accent: #000091;
--sl-color-accent-high: #8585f6;
--sl-color-white: #17181c;
--sl-color-gray-1: #24272f;
--sl-color-gray-2: #353841;
--sl-color-gray-3: #545861;
--sl-color-gray-4: #888b96;
--sl-color-gray-5: #c0c2c7;
--sl-color-gray-6: #edeef3;
--sl-color-gray-7: #f5f6f8;
--sl-color-black: #ffffff;
--sl-color-blue-low: #ececfe;
--sl-color-blue-middle: #cacafb;
--sl-color-blue: #000091;
--sl-color-bg-inline-code: var(--sl-color-blue-low);
}
.action {
border-radius: 0;
font-weight: 500;
padding: 0.5rem 1rem;
}
.sl-markdown-content :is(h1, h2, h3, h4, h5, h6) > a {
color: inherit;
text-decoration: none;
}
.sl-markdown-content :is(h1, h2, h3, h4, h5, h6) > a:hover {
text-decoration: underline;
}
.starlight-aside--note {
border-top: 1px solid var(--sl-color-blue-middle, var(--sl-color-blue));
border-bottom: 1px solid var(--sl-color-blue-middle, var(--sl-color-blue));
border-right: 1px solid var(--sl-color-blue-middle, var(--sl-color-blue));
}
.sl-markdown-content p img {
display: block;
margin: auto;
box-shadow: var(--sl-shadow-sm);
border: 1px solid var(--sl-color-gray-5);
}
.react-form label {
display: block;
}
.react-form label span {
font-size: 0.875rem;
opacity: 0.75;
margin-left: 0.5rem;
}
.react-form-input {
margin-bottom: 1rem;
}
.react-form :is(input, select) {
width: 100%;
}
.sl-markdown-content :is(h1, h2, h3, h4, h5, h6):target {
background-color: #fef7da;
}
.react-code {
position: relative;
}
.react-code button {
position: absolute;
top: 20px;
background-color: var(--sl-color-bg);
color: var(--sl-color-text);
margin: 0 !important;
right: 20px;
border: none;
border-radius: 0.25rem;
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
cursor: pointer;
}

View File

@@ -0,0 +1,158 @@
:root {
--prism-color: #c9d1d9;
--prism-bg: #24292e;
--prism-inline-bg: #343942;
--prism-selection: #234879;
--prism-highlight-bg: #2f2a1e;
--prism-highlight-shadow: #674c16;
--prism-token-comment: #8b949e;
--prism-token-punctuation: #c9d1d9;
--prism-token-tag: #79c0ff;
--prism-token-string: #a5d6ff;
--prism-token-operator: #a5d6ff;
--prism-token-keyword: #a5d6ff;
--prism-token-function: #d2a8ff;
--prism-token-regex: #a8daff;
}
:root[data-theme="light"] {
--prism-selection: #9fc6e9;
--prism-color: #24292f;
--prism-bg: #f5f5fe;
--prism-inline-bg: #eff1f3;
--prism-highlight-bg: #fff8c5;
--prism-highlight-shadow: #eed888;
--prism-token-comment: #6e7781;
--prism-token-punctuation: #24292f;
--prism-token-tag: #0550ae;
--prism-token-string: #0a3069;
--prism-token-operator: #0550ae;
--prism-token-keyword: #cf222e;
--prism-token-function: #8250df;
--prism-token-regex: #0a3069;
}
.expressive-code .frame pre {
background-color: var(--prism-bg) !important;
}
/**
* Github Light/Dark theme for Prism.js
* Based on Github: https://github.com
* @author Katorly
*/
pre[class*="language-"],
code[class*="language-"] {
color: var(--prism-color);
box-shadow: rgba(0, 0, 0, 0.157) 1.8px 1.8px 3.6px 0px;
border: 1px solid rgb(225, 228, 232);
}
pre[class*="language-"]::selection,
code[class*="language-"]::selection,
pre[class*="language-"]::mozselection,
code[class*="language-"]::mozselection {
text-shadow: none;
background: var(--prism-selection);
}
@media print {
pre[class*="language-"],
code[class*="language-"] {
text-shadow: none;
}
}
pre[class*="language-"] {
padding: 1em;
overflow: auto;
background: var(--prism-bg);
}
.language-fixedheight pre[class*="language-"] {
max-height: 500px;
}
:not(pre) > code[class*="language-"] {
padding: 0.1em 0.3em;
border-radius: 0.3em;
color: #24292f;
background: var(--prism-inline-bg);
}
/* Line highlighting */
pre[data-line] {
position: relative;
}
pre[class*="language-"] > code[class*="language-"] {
position: relative;
z-index: 1;
}
.line-highlight {
position: absolute;
left: 0;
right: 0;
padding: inherit 0;
margin-top: 1em;
background: var(--prism-highlight-bg);
box-shadow: inset 5px 0 0 var(--prism-highlight-shadow);
z-index: 0;
pointer-events: none;
line-height: inherit;
white-space: pre;
}
/* Tokens */
.namespace {
opacity: 0.7;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: var(--prism-token-comment);
}
.token.punctuation {
color: var(--prism-token-punctuation);
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: var(--prism-token-tag);
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: var(--prism-token-string);
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: var(--prism-token-operator);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: var(--prism-token-keyword);
}
.token.function {
color: var(--prism-token-function);
}
.token.regex,
.token.important,
.token.variable {
color: var(--prism-token-regex);
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}