@zevra/ui 0.21.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/DECISIONS.md CHANGED
@@ -1179,3 +1179,72 @@ rien intercepter.
1179
1179
 
1180
1180
  Aucun changement de gabarit : le `::after` est en absolu sous une barre déjà
1181
1181
  fixée, il ne prend pas de place dans le flux.
1182
+
1183
+ ---
1184
+
1185
+ ## Les e-mails (27/08/2026)
1186
+
1187
+ Le paquet gagne des gabarits d'e-mail (`@zevra/ui/email`). Ils étaient
1188
+ jusqu'ici refaits par chaque produit, hors charte — fond crème, angles
1189
+ ronds, surligné jaune, titres en serif : rien de tout cela n'est dans le
1190
+ système.
1191
+
1192
+ ### La dérogation à la règle première, et pourquoi elle est imposée
1193
+
1194
+ « Le CSS est la source de vérité, les composants n'en sont qu'une façade » —
1195
+ sauf ici. Ce n'est pas un choix, c'est le support :
1196
+
1197
+ - il n'y a **aucune feuille de style** à charger dans un e-mail ;
1198
+ - Outlook et Gmail **suppriment les propriétés personnalisées** : un
1199
+ `var(--accent)` s'y rend en couleur nulle, donc en texte noir sur fond
1200
+ transparent ;
1201
+ - ni flexbox ni grid ne sont fiables : la mise en page est en **tables** ;
1202
+ - les **dégradés** ne se rendent pas dans Outlook.
1203
+
1204
+ Les valeurs sont donc écrites en clair — mais tirées du **miroir JS des
1205
+ tokens**, que `tokens/index.js` destinait déjà explicitement à cet usage
1206
+ (« couleurs de thème d'un e-mail ») et que `tests/tokens.test.js` tient
1207
+ aligné sur le CSS. Aucune seconde source n'apparaît. Le détour par
1208
+ `react/email-tokens.ts` (généré) est imposé par le `rootDir` du tsconfig.
1209
+
1210
+ ### Ce que le support fait perdre, et ce qu'on met à la place
1211
+
1212
+ | Dans le système | Dans l'e-mail | Pourquoi |
1213
+ |---|---|---|
1214
+ | Bouton plein en dégradé indigo | Aplat `--accent-deep` | Les dégradés ne se rendent pas dans Outlook |
1215
+ | Filet de spectre en `linear-gradient` | Trois aplats de 19 px | Idem — une bande vide sinon |
1216
+ | `padding` sur le `<a>` du bouton | Table + `bgcolor` | Outlook ignore le padding d'un `<a>` |
1217
+ | Largeur `--page-max` | 600 px | Le volet de lecture d'Outlook |
1218
+
1219
+ Ce qui NE change pas : l'angle vif, le papier, l'encre, les trois familles,
1220
+ un seul bouton plein par message, et le filet de spectre comme signature —
1221
+ premier de ses trois emplois autorisés.
1222
+
1223
+ ### Le mode sombre est refusé
1224
+
1225
+ `color-scheme: light`, explicitement. La charte est claire par nature :
1226
+ l'inversion automatique d'Apple Mail retourne les neutres **sans toucher aux
1227
+ aplats**, et l'indigo du bouton se retrouverait sur un fond devenu noir. Même
1228
+ règle que pour les surfaces sombres du système — elles se font avec la bande
1229
+ manifeste, jamais en inversant les neutres.
1230
+
1231
+ ### Deux règles qui ne sont pas décoratives
1232
+
1233
+ **La marque s'écrit en toutes lettres, même avec un logo.** Une image sur
1234
+ deux est bloquée par défaut ; un e-mail qui ne dit pas de qui il vient part à
1235
+ la corbeille. D'où `marque` obligatoire, et le logo en option.
1236
+
1237
+ **Le preheader est un contenu, pas un détail.** Sans lui, le client affiche
1238
+ les premiers mots du HTML dans la liste des messages — c'est la seule ligne
1239
+ dont on dispose pour convaincre d'ouvrir.
1240
+
1241
+ ### Un piège rencontré, et gardé
1242
+
1243
+ Les piles de polices du paquet contiennent des **guillemets doubles**
1244
+ (`"Public Sans", …`) et tout s'écrit dans un `style="…"` délimité par ces
1245
+ mêmes guillemets : l'attribut se referme au premier guillemet interne et le
1246
+ nœud perd **tout** son style. L'e-mail entier repassait en Times, sans
1247
+ couleurs, avec un libellé de bouton blanc devenu illisible sur l'aplat
1248
+ indigo. Cela ne se voit pas à la relecture — cela s'est vu au rendu. Les
1249
+ piles sont converties en guillemets simples, et un test refuse désormais
1250
+ tout attribut `style` contenant un guillemet double.
@@ -0,0 +1,56 @@
1
+ export declare const COULEURS: {
2
+ readonly ink: "#0b1020";
3
+ readonly ink2: "#4a5468";
4
+ readonly muted: "#5a637a";
5
+ readonly label: "#8a93a8";
6
+ readonly line: "#e3e6ef";
7
+ readonly hairline: "#eef1f8";
8
+ readonly paper: "#f5f6fa";
9
+ readonly panel: "#fbfcfe";
10
+ readonly white: "#ffffff";
11
+ readonly accentDeep: "#1e1b8c";
12
+ readonly accent: "#2a2aa8";
13
+ readonly accentLine: "#c9d6f5";
14
+ readonly accentSoft: "#eaf1ff";
15
+ readonly violet: "#8a3af8";
16
+ readonly iris: "#5848f8";
17
+ readonly bleu: "#2f6ef8";
18
+ readonly cyan: "#14c2f8";
19
+ readonly lavande: "#b8a8f8";
20
+ readonly accentOnDark: "#a5b4fc";
21
+ readonly ok: "#1c6b4a";
22
+ readonly okSoft: "#e6f3ec";
23
+ readonly okLine: "#bfdccb";
24
+ readonly warn: "#b98a1f";
25
+ readonly warnSoft: "#faf3e2";
26
+ readonly warnLine: "#ebdcb4";
27
+ readonly warnInk: "#8a6d1e";
28
+ readonly err: "#b5202c";
29
+ readonly errSoft: "#fdece9";
30
+ readonly errLine: "#efc9cd";
31
+ readonly fmtPdf: "#c4362b";
32
+ readonly fmtDocx: "#2a5ba8";
33
+ };
34
+ export declare const POLICES: {
35
+ readonly display: "\"Outfit\", \"Helvetica Neue\", Arial, sans-serif";
36
+ readonly texte: "\"Public Sans\", \"Helvetica Neue\", Arial, sans-serif";
37
+ readonly mono: "\"Space Grotesk\", \"Menlo\", monospace";
38
+ readonly code: "ui-monospace, \"SF Mono\", \"Menlo\", \"Consolas\", monospace";
39
+ };
40
+ export declare const TAILLES: {
41
+ readonly h1: "76px";
42
+ readonly h2: "56px";
43
+ readonly h3: "38px";
44
+ readonly cardTitle: "24px";
45
+ readonly lead: "18px";
46
+ readonly body: "15px";
47
+ readonly appLg: "14px";
48
+ readonly app: "13px";
49
+ readonly appSm: "12px";
50
+ readonly appH2: "20px";
51
+ readonly appH3: "16px";
52
+ readonly monoLg: "11px";
53
+ readonly mono: "10px";
54
+ readonly monoSm: "9px";
55
+ };
56
+ //# sourceMappingURL=email-tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-tokens.d.ts","sourceRoot":"","sources":["../react/email-tokens.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,QAAQ;aACnB,GAAK,EAAE,SAAS;aAChB,IAAM,EAAE,SAAS;aACjB,KAAO,EAAE,SAAS;aAClB,KAAO,EAAE,SAAS;aAClB,IAAM,EAAE,SAAS;aACjB,QAAU,EAAE,SAAS;aACrB,KAAO,EAAE,SAAS;aAClB,KAAO,EAAE,SAAS;aAClB,KAAO,EAAE,SAAS;aAClB,UAAY,EAAE,SAAS;aACvB,MAAQ,EAAE,SAAS;aACnB,UAAY,EAAE,SAAS;aACvB,UAAY,EAAE,SAAS;aACvB,MAAQ,EAAE,SAAS;aACnB,IAAM,EAAE,SAAS;aACjB,IAAM,EAAE,SAAS;aACjB,IAAM,EAAE,SAAS;aACjB,OAAS,EAAE,SAAS;aACpB,YAAc,EAAE,SAAS;aACzB,EAAI,EAAE,SAAS;aACf,MAAQ,EAAE,SAAS;aACnB,MAAQ,EAAE,SAAS;aACnB,IAAM,EAAE,SAAS;aACjB,QAAU,EAAE,SAAS;aACrB,QAAU,EAAE,SAAS;aACrB,OAAS,EAAE,SAAS;aACpB,GAAK,EAAE,SAAS;aAChB,OAAS,EAAE,SAAS;aACpB,OAAS,EAAE,SAAS;aACpB,MAAQ,EAAE,SAAS;aACnB,OAAS,EAAE,SAAS;CACZ,CAAA;AAEV,eAAO,MAAM,OAAO;aAClB,OAAS,EAAE,mDAAmD;aAC9D,KAAO,EAAE,wDAAwD;aACjE,IAAM,EAAE,yCAAyC;aACjD,IAAM,EAAE,+DAA+D;CAC/D,CAAA;AAEV,eAAO,MAAM,OAAO;aAClB,EAAI,EAAE,MAAM;aACZ,EAAI,EAAE,MAAM;aACZ,EAAI,EAAE,MAAM;aACZ,SAAW,EAAE,MAAM;aACnB,IAAM,EAAE,MAAM;aACd,IAAM,EAAE,MAAM;aACd,KAAO,EAAE,MAAM;aACf,GAAK,EAAE,MAAM;aACb,KAAO,EAAE,MAAM;aACf,KAAO,EAAE,MAAM;aACf,KAAO,EAAE,MAAM;aACf,MAAQ,EAAE,MAAM;aAChB,IAAM,EAAE,MAAM;aACd,MAAQ,EAAE,KAAK;CACP,CAAA"}
@@ -0,0 +1,58 @@
1
+ // GÉNÉRÉ par scripts/build-email-tokens.mjs — ne pas éditer à la main.
2
+ // Source de vérité : tokens/index.js, lui-même miroir de src/tokens.css.
3
+ export const COULEURS = {
4
+ "ink": "#0b1020",
5
+ "ink2": "#4a5468",
6
+ "muted": "#5a637a",
7
+ "label": "#8a93a8",
8
+ "line": "#e3e6ef",
9
+ "hairline": "#eef1f8",
10
+ "paper": "#f5f6fa",
11
+ "panel": "#fbfcfe",
12
+ "white": "#ffffff",
13
+ "accentDeep": "#1e1b8c",
14
+ "accent": "#2a2aa8",
15
+ "accentLine": "#c9d6f5",
16
+ "accentSoft": "#eaf1ff",
17
+ "violet": "#8a3af8",
18
+ "iris": "#5848f8",
19
+ "bleu": "#2f6ef8",
20
+ "cyan": "#14c2f8",
21
+ "lavande": "#b8a8f8",
22
+ "accentOnDark": "#a5b4fc",
23
+ "ok": "#1c6b4a",
24
+ "okSoft": "#e6f3ec",
25
+ "okLine": "#bfdccb",
26
+ "warn": "#b98a1f",
27
+ "warnSoft": "#faf3e2",
28
+ "warnLine": "#ebdcb4",
29
+ "warnInk": "#8a6d1e",
30
+ "err": "#b5202c",
31
+ "errSoft": "#fdece9",
32
+ "errLine": "#efc9cd",
33
+ "fmtPdf": "#c4362b",
34
+ "fmtDocx": "#2a5ba8"
35
+ };
36
+ export const POLICES = {
37
+ "display": "\"Outfit\", \"Helvetica Neue\", Arial, sans-serif",
38
+ "texte": "\"Public Sans\", \"Helvetica Neue\", Arial, sans-serif",
39
+ "mono": "\"Space Grotesk\", \"Menlo\", monospace",
40
+ "code": "ui-monospace, \"SF Mono\", \"Menlo\", \"Consolas\", monospace"
41
+ };
42
+ export const TAILLES = {
43
+ "h1": "76px",
44
+ "h2": "56px",
45
+ "h3": "38px",
46
+ "cardTitle": "24px",
47
+ "lead": "18px",
48
+ "body": "15px",
49
+ "appLg": "14px",
50
+ "app": "13px",
51
+ "appSm": "12px",
52
+ "appH2": "20px",
53
+ "appH3": "16px",
54
+ "monoLg": "11px",
55
+ "mono": "10px",
56
+ "monoSm": "9px"
57
+ };
58
+ //# sourceMappingURL=email-tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-tokens.js","sourceRoot":"","sources":["../react/email-tokens.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,yEAAyE;AAEzE,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,KAAK,EAAE,SAAS;IAChB,MAAM,EAAE,SAAS;IACjB,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS;IAClB,MAAM,EAAE,SAAS;IACjB,UAAU,EAAE,SAAS;IACrB,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS;IAClB,YAAY,EAAE,SAAS;IACvB,QAAQ,EAAE,SAAS;IACnB,YAAY,EAAE,SAAS;IACvB,YAAY,EAAE,SAAS;IACvB,QAAQ,EAAE,SAAS;IACnB,MAAM,EAAE,SAAS;IACjB,MAAM,EAAE,SAAS;IACjB,MAAM,EAAE,SAAS;IACjB,SAAS,EAAE,SAAS;IACpB,cAAc,EAAE,SAAS;IACzB,IAAI,EAAE,SAAS;IACf,QAAQ,EAAE,SAAS;IACnB,QAAQ,EAAE,SAAS;IACnB,MAAM,EAAE,SAAS;IACjB,UAAU,EAAE,SAAS;IACrB,UAAU,EAAE,SAAS;IACrB,SAAS,EAAE,SAAS;IACpB,KAAK,EAAE,SAAS;IAChB,SAAS,EAAE,SAAS;IACpB,SAAS,EAAE,SAAS;IACpB,QAAQ,EAAE,SAAS;IACnB,SAAS,EAAE,SAAS;CACZ,CAAA;AAEV,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,SAAS,EAAE,mDAAmD;IAC9D,OAAO,EAAE,wDAAwD;IACjE,MAAM,EAAE,yCAAyC;IACjD,MAAM,EAAE,+DAA+D;CAC/D,CAAA;AAEV,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,MAAM;IACZ,WAAW,EAAE,MAAM;IACnB,MAAM,EAAE,MAAM;IACd,MAAM,EAAE,MAAM;IACd,OAAO,EAAE,MAAM;IACf,KAAK,EAAE,MAAM;IACb,OAAO,EAAE,MAAM;IACf,OAAO,EAAE,MAAM;IACf,OAAO,EAAE,MAAM;IACf,QAAQ,EAAE,MAAM;IAChB,MAAM,EAAE,MAAM;IACd,QAAQ,EAAE,KAAK;CACP,CAAA"}
@@ -0,0 +1,77 @@
1
+ /** Échappe ce qui entre dans le HTML. Un objet d'e-mail vient souvent d'une
2
+ * base de données : un nom de client avec une esperluette casserait le
3
+ * document, et un `<` bien placé y injecterait du balisage. */
4
+ export declare function echappe(texte: string): string;
5
+ /** Sur-titre : le filet de spectre suivi du label en capitales.
6
+ * PREMIER des trois emplois autorisés du spectre. Le dégradé est rendu en
7
+ * TROIS APLATS de 19px (violet, bleu, cyan) : un `linear-gradient` ne se
8
+ * rend pas dans Outlook, qui afficherait une bande vide. */
9
+ export declare function emailKicker(texte: string): string;
10
+ /** Le titre du message. Un seul par e-mail : c'est la promesse de l'objet,
11
+ * tenue à l'ouverture. */
12
+ export declare function emailTitre(texte: string): string;
13
+ /** Un paragraphe. Le HTML d'enrichissement (`<strong>`, `<a>`) est admis :
14
+ * c'est du texte rédigé, pas une saisie utilisateur — l'échapper
15
+ * afficherait des chevrons. Passer par `echappe` toute valeur qui vient
16
+ * d'une base. */
17
+ export declare function emailTexte(html: string): string;
18
+ /** L'action. UN SEUL bouton plein par e-mail, comme par page — c'est ce qui
19
+ * fait qu'on sait où cliquer.
20
+ * Construit en table : le `padding` d'un `<a>` est ignoré par Outlook, qui
21
+ * rendrait un lien nu au milieu du message. Aplat indigo et non dégradé,
22
+ * pour la même raison. Angle vif, comme partout. */
23
+ export declare function emailBouton({ href, libelle }: {
24
+ href: string;
25
+ libelle: string;
26
+ }): string;
27
+ /** Le lien secondaire, sous le bouton : l'autre chemin, pour qui ne veut pas
28
+ * du premier. Souligné — dans un e-mail, la couleur seule ne suffit pas à
29
+ * annoncer un lien. */
30
+ export declare function emailLienSecondaire({ href, libelle }: {
31
+ href: string;
32
+ libelle: string;
33
+ }): string;
34
+ /** La mention discrète : ce qu'on lit après avoir décidé. */
35
+ export declare function emailNote(html: string): string;
36
+ /** L'encart : un extrait, un récapitulatif de commande, un code. */
37
+ export declare function emailEncart(html: string): string;
38
+ /** Le filet de séparation. */
39
+ export declare function emailFilet(): string;
40
+ export interface EmailOptions {
41
+ /** Repris en `<title>`. Ce n'est PAS l'objet du message : l'objet se pose
42
+ * à l'envoi, dans l'en-tête SMTP. */
43
+ titre: string;
44
+ /** Le texte gris qui suit l'objet dans la liste des messages. Sans lui,
45
+ * le client y affiche les premiers mots du HTML — souvent « Voir cet
46
+ * e-mail dans votre navigateur », ce qui gâche la seule ligne dont on
47
+ * dispose pour convaincre d'ouvrir. */
48
+ preheader?: string;
49
+ /** Le nom de la marque, écrit en toutes lettres. ⚠️ OBLIGATOIRE même avec
50
+ * un logo : une image sur deux est bloquée par défaut, et un e-mail qui
51
+ * ne dit pas de qui il vient part à la corbeille. */
52
+ marque: string;
53
+ /** L'URL ABSOLUE du logo — une image d'e-mail ne se résout pas depuis un
54
+ * chemin relatif, et les data-URI sont bloquées par Gmail. */
55
+ logo?: {
56
+ src: string;
57
+ hauteur?: number;
58
+ };
59
+ /** Les blocs, concaténés. */
60
+ contenu: string;
61
+ /** Sous la carte : mentions légales, désinscription. */
62
+ pied?: string;
63
+ lang?: string;
64
+ }
65
+ /**
66
+ * Assemble un e-mail complet.
67
+ *
68
+ * ⚠️ Le mode sombre est REFUSÉ explicitement (`color-scheme: light`) : la
69
+ * charte est claire par nature, et l'inversion automatique d'Apple Mail
70
+ * retourne les neutres sans toucher aux aplats — l'indigo du bouton
71
+ * resterait sur un fond devenu noir, et les contrastes réglés dans un sens
72
+ * partiraient dans l'autre. C'est la même règle que pour les surfaces
73
+ * sombres du système : elles se font avec la bande manifeste, jamais en
74
+ * inversant les neutres.
75
+ */
76
+ export declare function email({ titre, preheader, marque, logo, contenu, pied, lang, }: EmailOptions): string;
77
+ //# sourceMappingURL=email.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../react/email.ts"],"names":[],"mappings":"AAmDA;;gEAEgE;AAChE,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAM7C;AAID;;;6DAG6D;AAC7D,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOjD;AAED;2BAC2B;AAC3B,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED;;;kBAGkB;AAClB,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED;;;;qDAIqD;AACrD,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAKxF;AAED;;wBAEwB;AACxB,wBAAgB,mBAAmB,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAEhG;AAED,6DAA6D;AAC7D,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,oEAAoE;AACpE,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIhD;AAED,8BAA8B;AAC9B,wBAAgB,UAAU,IAAI,MAAM,CAGnC;AAID,MAAM,WAAW,YAAY;IAC3B;0CACsC;IACtC,KAAK,EAAE,MAAM,CAAA;IACb;;;4CAGwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;0DAEsD;IACtD,MAAM,EAAE,MAAM,CAAA;IACd;mEAC+D;IAC/D,IAAI,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACxC,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAA;IACf,wDAAwD;IACxD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,KAAK,CAAC,EACpB,KAAK,EACL,SAAS,EACT,MAAM,EACN,IAAI,EACJ,OAAO,EACP,IAAI,EACJ,IAAW,GACZ,EAAE,YAAY,GAAG,MAAM,CA8CvB"}
package/dist/email.js ADDED
@@ -0,0 +1,166 @@
1
+ // Gabarits d'e-mail « Encre & Papier ».
2
+ //
3
+ // ⚠️ CE MODULE N'EST PAS DU REACT ET N'EMPLOIE PAS LE CSS DU PAQUET. Il rend
4
+ // des chaînes de HTML. C'est la seule dérogation à la règle première du dépôt
5
+ // (« le CSS est la source de vérité, les composants n'en sont qu'une
6
+ // façade »), et elle est imposée par le support, pas choisie :
7
+ //
8
+ // · il n'y a pas de feuille de style à charger dans un e-mail ;
9
+ // · Outlook et Gmail SUPPRIMENT les propriétés personnalisées : un
10
+ // `var(--accent)` s'y rend en couleur nulle, donc en texte noir sur fond
11
+ // transparent. Les valeurs sont donc écrites en clair — mais tirées du
12
+ // miroir JS des tokens, que tests/tokens.test.js tient aligné sur le CSS ;
13
+ // · ni flexbox ni grid ne sont fiables : la mise en page est en TABLES ;
14
+ // · les dégradés ne se rendent pas dans Outlook — le bouton plein perd le
15
+ // sien et devient un aplat, le filet de spectre devient trois aplats.
16
+ //
17
+ // Il vit dans react/ parce que c'est la racine de compilation du paquet
18
+ // (`rootDir`), pas parce qu'il en dépend : il n'importe pas une ligne de
19
+ // React et n'émet aucune classe `zv-`.
20
+ //
21
+ // ⚠️ Ce module ÉCHAPPE à ta feuille de style : ce qui n'est pas ici ne
22
+ // s'affichera pas. Ne pas y ajouter de classes en espérant qu'elles soient
23
+ // stylées.
24
+ import { COULEURS, POLICES as PILES } from './email-tokens.js';
25
+ /** ⚠️ Les piles de polices du paquet contiennent des GUILLEMETS DOUBLES
26
+ * (`"Public Sans", …`), et tout ici s'écrit dans un attribut `style="…"`
27
+ * délimité par ces mêmes guillemets : l'attribut se refermerait au premier
28
+ * guillemet interne, et TOUT le style du nœud serait perdu. En HTML, un
29
+ * guillemet simple est équivalent à l'intérieur d'une valeur de police.
30
+ * Cette faute ne se voit pas à la relecture — elle se voit au rendu, où
31
+ * l'e-mail entier repasse en Times sans style. */
32
+ const pourAttribut = (pile) => pile.replace(/"/g, "'");
33
+ const POLICES = {
34
+ display: pourAttribut(PILES.display),
35
+ texte: pourAttribut(PILES.texte),
36
+ mono: pourAttribut(PILES.mono),
37
+ };
38
+ /* ─── Réglages communs ─────────────────────────────────────────────── */
39
+ /** 600px : la largeur qui traverse tous les clients depuis vingt ans, dont
40
+ * le volet de lecture d'Outlook. Ce n'est pas --page-max, et ça n'a rien à
41
+ * voir : c'est une contrainte du support. */
42
+ const LARGEUR = 600;
43
+ const CORPS = `font-family:${POLICES.texte};font-size:15px;line-height:1.6;color:${COULEURS.muted};`;
44
+ const TITRE = `font-family:${POLICES.display};font-weight:300;letter-spacing:-0.02em;color:${COULEURS.ink};`;
45
+ /** Échappe ce qui entre dans le HTML. Un objet d'e-mail vient souvent d'une
46
+ * base de données : un nom de client avec une esperluette casserait le
47
+ * document, et un `<` bien placé y injecterait du balisage. */
48
+ export function echappe(texte) {
49
+ return texte
50
+ .replace(/&/g, '&amp;')
51
+ .replace(/</g, '&lt;')
52
+ .replace(/>/g, '&gt;')
53
+ .replace(/"/g, '&quot;');
54
+ }
55
+ /* ─── Blocs ────────────────────────────────────────────────────────── */
56
+ /** Sur-titre : le filet de spectre suivi du label en capitales.
57
+ * PREMIER des trois emplois autorisés du spectre. Le dégradé est rendu en
58
+ * TROIS APLATS de 19px (violet, bleu, cyan) : un `linear-gradient` ne se
59
+ * rend pas dans Outlook, qui afficherait une bande vide. */
60
+ export function emailKicker(texte) {
61
+ const brin = (couleur) => `<td width="19" height="2" style="background:${couleur};font-size:0;line-height:0;">&nbsp;</td>`;
62
+ return `<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin:0 0 14px;"><tr>
63
+ <td><table role="presentation" cellpadding="0" cellspacing="0" border="0"><tr>${brin(COULEURS.violet)}${brin(COULEURS.bleu)}${brin(COULEURS.cyan)}</tr></table></td>
64
+ <td style="padding-left:12px;font-family:${POLICES.mono};font-size:11px;font-weight:500;letter-spacing:0.2em;text-transform:uppercase;color:${COULEURS.accent};">${echappe(texte)}</td>
65
+ </tr></table>`;
66
+ }
67
+ /** Le titre du message. Un seul par e-mail : c'est la promesse de l'objet,
68
+ * tenue à l'ouverture. */
69
+ export function emailTitre(texte) {
70
+ return `<h1 style="${TITRE}font-size:26px;line-height:1.2;margin:0 0 18px;">${echappe(texte)}</h1>`;
71
+ }
72
+ /** Un paragraphe. Le HTML d'enrichissement (`<strong>`, `<a>`) est admis :
73
+ * c'est du texte rédigé, pas une saisie utilisateur — l'échapper
74
+ * afficherait des chevrons. Passer par `echappe` toute valeur qui vient
75
+ * d'une base. */
76
+ export function emailTexte(html) {
77
+ return `<p style="${CORPS}margin:0 0 16px;">${html}</p>`;
78
+ }
79
+ /** L'action. UN SEUL bouton plein par e-mail, comme par page — c'est ce qui
80
+ * fait qu'on sait où cliquer.
81
+ * Construit en table : le `padding` d'un `<a>` est ignoré par Outlook, qui
82
+ * rendrait un lien nu au milieu du message. Aplat indigo et non dégradé,
83
+ * pour la même raison. Angle vif, comme partout. */
84
+ export function emailBouton({ href, libelle }) {
85
+ return `<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin:26px 0;"><tr>
86
+ <td align="center" bgcolor="${COULEURS.accentDeep}" style="background:${COULEURS.accentDeep};">
87
+ <a href="${echappe(href)}" style="display:inline-block;padding:15px 30px;font-family:${POLICES.texte};font-size:15px;font-weight:600;color:${COULEURS.white};text-decoration:none;">${echappe(libelle)}</a>
88
+ </td></tr></table>`;
89
+ }
90
+ /** Le lien secondaire, sous le bouton : l'autre chemin, pour qui ne veut pas
91
+ * du premier. Souligné — dans un e-mail, la couleur seule ne suffit pas à
92
+ * annoncer un lien. */
93
+ export function emailLienSecondaire({ href, libelle }) {
94
+ return `<p style="${CORPS}font-size:14px;margin:0 0 16px;"><a href="${echappe(href)}" style="color:${COULEURS.accent};text-decoration:underline;">${echappe(libelle)}</a></p>`;
95
+ }
96
+ /** La mention discrète : ce qu'on lit après avoir décidé. */
97
+ export function emailNote(html) {
98
+ return `<p style="font-family:${POLICES.texte};font-size:13px;line-height:1.55;color:${COULEURS.label};margin:0 0 12px;">${html}</p>`;
99
+ }
100
+ /** L'encart : un extrait, un récapitulatif de commande, un code. */
101
+ export function emailEncart(html) {
102
+ return `<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="margin:0 0 18px;"><tr>
103
+ <td style="background:${COULEURS.panel};border:1px solid ${COULEURS.hairline};border-left:2px solid ${COULEURS.accentLine};padding:14px 18px;font-family:${POLICES.texte};font-size:13px;line-height:1.6;color:${COULEURS.ink};">${html}</td>
104
+ </tr></table>`;
105
+ }
106
+ /** Le filet de séparation. */
107
+ export function emailFilet() {
108
+ return `<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="margin:22px 0;"><tr>
109
+ <td height="1" style="background:${COULEURS.line};font-size:0;line-height:0;">&nbsp;</td></tr></table>`;
110
+ }
111
+ /**
112
+ * Assemble un e-mail complet.
113
+ *
114
+ * ⚠️ Le mode sombre est REFUSÉ explicitement (`color-scheme: light`) : la
115
+ * charte est claire par nature, et l'inversion automatique d'Apple Mail
116
+ * retourne les neutres sans toucher aux aplats — l'indigo du bouton
117
+ * resterait sur un fond devenu noir, et les contrastes réglés dans un sens
118
+ * partiraient dans l'autre. C'est la même règle que pour les surfaces
119
+ * sombres du système : elles se font avec la bande manifeste, jamais en
120
+ * inversant les neutres.
121
+ */
122
+ export function email({ titre, preheader, marque, logo, contenu, pied, lang = 'fr', }) {
123
+ const enTete = logo
124
+ ? `<img src="${echappe(logo.src)}" height="${logo.hauteur ?? 28}" alt="${echappe(marque)}" style="display:block;border:0;height:${logo.hauteur ?? 28}px;width:auto;">`
125
+ : `<span style="font-family:${POLICES.display};font-weight:600;font-size:20px;letter-spacing:-0.02em;color:${COULEURS.ink};">${echappe(marque)}</span>`;
126
+ return `<!doctype html>
127
+ <html lang="${lang}">
128
+ <head>
129
+ <meta charset="utf-8">
130
+ <meta name="viewport" content="width=device-width,initial-scale=1">
131
+ <meta name="color-scheme" content="light">
132
+ <meta name="supported-color-schemes" content="light">
133
+ <title>${echappe(titre)}</title>
134
+ <style>
135
+ /* Le seul <style> du document, et il ne porte QUE du responsive : ce que
136
+ Gmail en supprime ne fait rien perdre, tout le reste est en ligne. */
137
+ @media (max-width:620px) {
138
+ .zv-carte { padding:26px 20px !important; }
139
+ .zv-marge { padding:20px 12px !important; }
140
+ }
141
+ </style>
142
+ </head>
143
+ <body style="margin:0;padding:0;width:100%;background:${COULEURS.paper};-webkit-text-size-adjust:100%;">
144
+ <div style="display:none;max-height:0;overflow:hidden;opacity:0;">${preheader ? echappe(preheader) : ''}</div>
145
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:${COULEURS.paper};">
146
+ <tr><td align="center" class="zv-marge" style="padding:32px 16px;">
147
+ <table role="presentation" width="${LARGEUR}" cellpadding="0" cellspacing="0" border="0" style="width:100%;max-width:${LARGEUR}px;">
148
+
149
+ <tr><td style="padding:0 0 20px;">${enTete}</td></tr>
150
+
151
+ <tr><td class="zv-carte" bgcolor="${COULEURS.white}" style="background:${COULEURS.white};border:1px solid ${COULEURS.line};padding:34px 36px;">
152
+ ${contenu}
153
+ </td></tr>
154
+
155
+ ${pied
156
+ ? `<tr><td style="padding:20px 4px 0;font-family:${POLICES.texte};font-size:12px;line-height:1.6;color:${COULEURS.label};">${pied}</td></tr>`
157
+ : ''}
158
+
159
+ </table>
160
+ </td></tr>
161
+ </table>
162
+ </body>
163
+ </html>
164
+ `;
165
+ }
166
+ //# sourceMappingURL=email.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email.js","sourceRoot":"","sources":["../react/email.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- export type IconId = 'accueil' | 'activite' | 'aide' | 'app-couches' | 'archiver' | 'attention' | 'avancer' | 'balance' | 'base-de-donnees' | 'bouclier-verifie' | 'cadenas' | 'cadrer' | 'calendrier' | 'chevron-bas' | 'chevron-droit' | 'chevron-gauche' | 'chevron-haut' | 'citation' | 'coche' | 'copier' | 'corbeille' | 'courriel' | 'cube' | 'deconnexion' | 'depot-fichier' | 'diplome' | 'dossier' | 'eclair' | 'envoyer' | 'epingle' | 'equipe' | 'etincelles' | 'etoile' | 'fermer' | 'fichier' | 'filtre' | 'globe' | 'graphique' | 'gras' | 'horloge' | 'image' | 'imprimante' | 'info' | 'italique' | 'lecture' | 'lien-externe' | 'liste-a-puces' | 'livre-ouvert' | 'menu' | 'moins' | 'muet' | 'oeil' | 'oeil-barre' | 'pause' | 'plein-ecran' | 'plus' | 'recherche' | 'registre' | 'reglages' | 'rembobiner' | 'reseau' | 'signet' | 'son' | 'taches' | 'telecharger' | 'telephone' | 'tri' | 'trois-points' | 'utilisateur' | 'video';
1
+ export type IconId = 'accueil' | 'activite' | 'aide' | 'app-couches' | 'archiver' | 'attention' | 'avancer' | 'balance' | 'base-de-donnees' | 'bouclier-verifie' | 'cadenas' | 'cadrer' | 'calendrier' | 'chevron-bas' | 'chevron-droit' | 'chevron-gauche' | 'chevron-haut' | 'citation' | 'coche' | 'copier' | 'corbeille' | 'courriel' | 'cube' | 'deconnexion' | 'depot-fichier' | 'diplome' | 'dossier' | 'eclair' | 'empreinte' | 'envoyer' | 'epingle' | 'equipe' | 'etincelles' | 'etoile' | 'fermer' | 'fichier' | 'filtre' | 'globe' | 'graphique' | 'gras' | 'horloge' | 'image' | 'imprimante' | 'info' | 'italique' | 'lecture' | 'lien-externe' | 'liste-a-puces' | 'livre-ouvert' | 'menu' | 'moins' | 'muet' | 'oeil' | 'oeil-barre' | 'pause' | 'plein-ecran' | 'plus' | 'recherche' | 'registre' | 'reglages' | 'rembobiner' | 'reseau' | 'signet' | 'son' | 'taches' | 'telecharger' | 'telephone' | 'tri' | 'trois-points' | 'utilisateur' | 'video';
2
2
  export declare const CORPS: Record<IconId, string>;
3
3
  /** Nom lisible de chaque icône — sert de titre accessible par défaut à
4
4
  * `IconButton`, et de libellé dans la page de consultation de l'admin. */
@@ -1 +1 @@
1
- {"version":3,"file":"icon-data.d.ts","sourceRoot":"","sources":["../react/icon-data.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,MAAM,GACd,SAAS,GACT,UAAU,GACV,MAAM,GACN,aAAa,GACb,UAAU,GACV,WAAW,GACX,SAAS,GACT,SAAS,GACT,iBAAiB,GACjB,kBAAkB,GAClB,SAAS,GACT,QAAQ,GACR,YAAY,GACZ,aAAa,GACb,eAAe,GACf,gBAAgB,GAChB,cAAc,GACd,UAAU,GACV,OAAO,GACP,QAAQ,GACR,WAAW,GACX,UAAU,GACV,MAAM,GACN,aAAa,GACb,eAAe,GACf,SAAS,GACT,SAAS,GACT,QAAQ,GACR,SAAS,GACT,SAAS,GACT,QAAQ,GACR,YAAY,GACZ,QAAQ,GACR,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,OAAO,GACP,WAAW,GACX,MAAM,GACN,SAAS,GACT,OAAO,GACP,YAAY,GACZ,MAAM,GACN,UAAU,GACV,SAAS,GACT,cAAc,GACd,eAAe,GACf,cAAc,GACd,MAAM,GACN,OAAO,GACP,MAAM,GACN,MAAM,GACN,YAAY,GACZ,OAAO,GACP,aAAa,GACb,MAAM,GACN,WAAW,GACX,UAAU,GACV,UAAU,GACV,YAAY,GACZ,QAAQ,GACR,QAAQ,GACR,KAAK,GACL,QAAQ,GACR,aAAa,GACb,WAAW,GACX,KAAK,GACL,cAAc,GACd,aAAa,GACb,OAAO,CAAA;AAEX,eAAO,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAuExC,CAAA;AAED;2EAC2E;AAC3E,eAAO,MAAM,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAuEvC,CAAA"}
1
+ {"version":3,"file":"icon-data.d.ts","sourceRoot":"","sources":["../react/icon-data.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,MAAM,GACd,SAAS,GACT,UAAU,GACV,MAAM,GACN,aAAa,GACb,UAAU,GACV,WAAW,GACX,SAAS,GACT,SAAS,GACT,iBAAiB,GACjB,kBAAkB,GAClB,SAAS,GACT,QAAQ,GACR,YAAY,GACZ,aAAa,GACb,eAAe,GACf,gBAAgB,GAChB,cAAc,GACd,UAAU,GACV,OAAO,GACP,QAAQ,GACR,WAAW,GACX,UAAU,GACV,MAAM,GACN,aAAa,GACb,eAAe,GACf,SAAS,GACT,SAAS,GACT,QAAQ,GACR,WAAW,GACX,SAAS,GACT,SAAS,GACT,QAAQ,GACR,YAAY,GACZ,QAAQ,GACR,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,OAAO,GACP,WAAW,GACX,MAAM,GACN,SAAS,GACT,OAAO,GACP,YAAY,GACZ,MAAM,GACN,UAAU,GACV,SAAS,GACT,cAAc,GACd,eAAe,GACf,cAAc,GACd,MAAM,GACN,OAAO,GACP,MAAM,GACN,MAAM,GACN,YAAY,GACZ,OAAO,GACP,aAAa,GACb,MAAM,GACN,WAAW,GACX,UAAU,GACV,UAAU,GACV,YAAY,GACZ,QAAQ,GACR,QAAQ,GACR,KAAK,GACL,QAAQ,GACR,aAAa,GACb,WAAW,GACX,KAAK,GACL,cAAc,GACd,aAAa,GACb,OAAO,CAAA;AAEX,eAAO,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAwExC,CAAA;AAED;2EAC2E;AAC3E,eAAO,MAAM,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAwEvC,CAAA"}
package/dist/icon-data.js CHANGED
@@ -29,6 +29,7 @@ export const CORPS = {
29
29
  'diplome': "<path d=\"M2.67 9.33 L12 4.67 L21.33 9.33 L12 14 Z\" /><path d=\"M6.67 11.6 V16 C6.67 17.47 9.07 18.67 12 18.67 C14.93 18.67 17.33 17.47 17.33 16 V11.6\" /><path d=\"M21.33 9.33 V14.67\" />",
30
30
  'dossier': "<path d=\"M1.41 6.64a1.69 1.69 0 0 1 1.69-1.69h4.8l2.12 2.4h10.87a1.69 1.69 0 0 1 1.69 1.69v9.88a1.69 1.69 0 0 1-1.69 1.69H3.11A1.69 1.69 0 0 1 1.41 20.33V6.64Z\" />",
31
31
  'eclair': "<path d=\"M13.33 2.67 L5.33 13.33 H12 L10.67 21.33 L18.67 10.67 H12 Z\" />",
32
+ 'empreinte': "<path d=\"M4 18.67 V14.67 A8 9.33 0 0 1 20 14.67 V17.33\" /><path d=\"M8 17.33 A4 5.33 0 0 1 16 17.33\" /><path d=\"M12 13.33 V20\" />",
32
33
  'envoyer': "<path d=\"M21.33 2.67 L10.67 13.33\" /><path d=\"M21.33 2.67 L14.67 21.33 L10.67 13.33 L2.67 9.33 Z\" />",
33
34
  'epingle': "<path d=\"M12 21.33 C7.56 16.89 5.33 13.42 5.33 10.67 A6.67 6.67 0 0 1 18.67 10.67 C18.67 13.42 16.44 16.89 12 21.33 Z\" /><circle cx=\"12\" cy=\"10.67\" r=\"2.4\" />",
34
35
  'equipe': "<circle cx=\"9.33\" cy=\"8\" r=\"3.33\" /><path d=\"M3.33 20 C3.33 15.33 6 13.33 9.33 13.33 C12.67 13.33 15.33 15.33 15.33 20\" /><path d=\"M16 5.33 A2.93 2.93 0 0 1 16 11.07\" /><path d=\"M17.33 13.6 C19.73 14.27 20.67 16.4 20.67 20\" />",
@@ -103,6 +104,7 @@ export const NOMS = {
103
104
  'diplome': "Diplôme (Campus)",
104
105
  'dossier': "Dossier",
105
106
  'eclair': "Éclair (action rapide)",
107
+ 'empreinte': "Empreinte (biométrie, passkey)",
106
108
  'envoyer': "Envoyer",
107
109
  'epingle': "Épingle (lieu)",
108
110
  'equipe': "Équipe (membres)",
@@ -1 +1 @@
1
- {"version":3,"file":"icon-data.js","sourceRoot":"","sources":["../react/icon-data.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"icon-data.js","sourceRoot":"","sources":["../react/icon-data.ts"],"names":[],"mappings":"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"}
package/icons/index.json CHANGED
@@ -170,6 +170,12 @@
170
170
  "viewBox": "0 0 24 24",
171
171
  "corps": "<path d=\"M13.33 2.67 L5.33 13.33 H12 L10.67 21.33 L18.67 10.67 H12 Z\" />"
172
172
  },
173
+ {
174
+ "id": "empreinte",
175
+ "nom": "Empreinte (biométrie, passkey)",
176
+ "viewBox": "0 0 24 24",
177
+ "corps": "<path d=\"M4 18.67 V14.67 A8 9.33 0 0 1 20 14.67 V17.33\" /><path d=\"M8 17.33 A4 5.33 0 0 1 16 17.33\" /><path d=\"M12 13.33 V20\" />"
178
+ },
173
179
  {
174
180
  "id": "envoyer",
175
181
  "nom": "Envoyer",
package/icons/sprite.svg CHANGED
@@ -27,6 +27,7 @@
27
27
  <symbol id="zv-diplome" viewBox="0 0 24 24"><path d="M2.67 9.33 L12 4.67 L21.33 9.33 L12 14 Z" /><path d="M6.67 11.6 V16 C6.67 17.47 9.07 18.67 12 18.67 C14.93 18.67 17.33 17.47 17.33 16 V11.6" /><path d="M21.33 9.33 V14.67" /></symbol>
28
28
  <symbol id="zv-dossier" viewBox="0 0 24 24"><path d="M1.41 6.64a1.69 1.69 0 0 1 1.69-1.69h4.8l2.12 2.4h10.87a1.69 1.69 0 0 1 1.69 1.69v9.88a1.69 1.69 0 0 1-1.69 1.69H3.11A1.69 1.69 0 0 1 1.41 20.33V6.64Z" /></symbol>
29
29
  <symbol id="zv-eclair" viewBox="0 0 24 24"><path d="M13.33 2.67 L5.33 13.33 H12 L10.67 21.33 L18.67 10.67 H12 Z" /></symbol>
30
+ <symbol id="zv-empreinte" viewBox="0 0 24 24"><path d="M4 18.67 V14.67 A8 9.33 0 0 1 20 14.67 V17.33" /><path d="M8 17.33 A4 5.33 0 0 1 16 17.33" /><path d="M12 13.33 V20" /></symbol>
30
31
  <symbol id="zv-envoyer" viewBox="0 0 24 24"><path d="M21.33 2.67 L10.67 13.33" /><path d="M21.33 2.67 L14.67 21.33 L10.67 13.33 L2.67 9.33 Z" /></symbol>
31
32
  <symbol id="zv-epingle" viewBox="0 0 24 24"><path d="M12 21.33 C7.56 16.89 5.33 13.42 5.33 10.67 A6.67 6.67 0 0 1 18.67 10.67 C18.67 13.42 16.44 16.89 12 21.33 Z" /><circle cx="12" cy="10.67" r="2.4" /></symbol>
32
33
  <symbol id="zv-equipe" viewBox="0 0 24 24"><circle cx="9.33" cy="8" r="3.33" /><path d="M3.33 20 C3.33 15.33 6 13.33 9.33 13.33 C12.67 13.33 15.33 15.33 15.33 20" /><path d="M16 5.33 A2.93 2.93 0 0 1 16 11.07" /><path d="M17.33 13.6 C19.73 14.27 20.67 16.4 20.67 20" /></symbol>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zevra/ui",
3
- "version": "0.21.0",
3
+ "version": "0.23.0",
4
4
  "description": "Design system Zevra « Encre & Papier » — tokens, CSS des composants et logos officiels.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -37,6 +37,10 @@
37
37
  "types": "./dist/css-inline.d.ts",
38
38
  "default": "./dist/css-inline.js"
39
39
  },
40
+ "./email": {
41
+ "types": "./dist/email.d.ts",
42
+ "default": "./dist/email.js"
43
+ },
40
44
  "./react/table": {
41
45
  "types": "./dist/data-table.d.ts",
42
46
  "default": "./dist/data-table.js"
@@ -74,7 +78,7 @@
74
78
  "DECISIONS.md"
75
79
  ],
76
80
  "scripts": {
77
- "build": "node scripts/build-icon-react.mjs && node scripts/build-css-bundle.mjs && tsc -p tsconfig.json",
81
+ "build": "node scripts/build-icon-react.mjs && node scripts/build-css-bundle.mjs && node scripts/build-email-tokens.mjs && tsc -p tsconfig.json",
78
82
  "test": "npm run build && vitest run",
79
83
  "prepublishOnly": "npm run build",
80
84
  "storybook": "npm run build && node scripts/build-stories.mjs && storybook dev -p 6006 --no-open",
@@ -0,0 +1,60 @@
1
+ // GÉNÉRÉ par scripts/build-email-tokens.mjs — ne pas éditer à la main.
2
+ // Source de vérité : tokens/index.js, lui-même miroir de src/tokens.css.
3
+
4
+ export const COULEURS = {
5
+ "ink": "#0b1020",
6
+ "ink2": "#4a5468",
7
+ "muted": "#5a637a",
8
+ "label": "#8a93a8",
9
+ "line": "#e3e6ef",
10
+ "hairline": "#eef1f8",
11
+ "paper": "#f5f6fa",
12
+ "panel": "#fbfcfe",
13
+ "white": "#ffffff",
14
+ "accentDeep": "#1e1b8c",
15
+ "accent": "#2a2aa8",
16
+ "accentLine": "#c9d6f5",
17
+ "accentSoft": "#eaf1ff",
18
+ "violet": "#8a3af8",
19
+ "iris": "#5848f8",
20
+ "bleu": "#2f6ef8",
21
+ "cyan": "#14c2f8",
22
+ "lavande": "#b8a8f8",
23
+ "accentOnDark": "#a5b4fc",
24
+ "ok": "#1c6b4a",
25
+ "okSoft": "#e6f3ec",
26
+ "okLine": "#bfdccb",
27
+ "warn": "#b98a1f",
28
+ "warnSoft": "#faf3e2",
29
+ "warnLine": "#ebdcb4",
30
+ "warnInk": "#8a6d1e",
31
+ "err": "#b5202c",
32
+ "errSoft": "#fdece9",
33
+ "errLine": "#efc9cd",
34
+ "fmtPdf": "#c4362b",
35
+ "fmtDocx": "#2a5ba8"
36
+ } as const
37
+
38
+ export const POLICES = {
39
+ "display": "\"Outfit\", \"Helvetica Neue\", Arial, sans-serif",
40
+ "texte": "\"Public Sans\", \"Helvetica Neue\", Arial, sans-serif",
41
+ "mono": "\"Space Grotesk\", \"Menlo\", monospace",
42
+ "code": "ui-monospace, \"SF Mono\", \"Menlo\", \"Consolas\", monospace"
43
+ } as const
44
+
45
+ export const TAILLES = {
46
+ "h1": "76px",
47
+ "h2": "56px",
48
+ "h3": "38px",
49
+ "cardTitle": "24px",
50
+ "lead": "18px",
51
+ "body": "15px",
52
+ "appLg": "14px",
53
+ "app": "13px",
54
+ "appSm": "12px",
55
+ "appH2": "20px",
56
+ "appH3": "16px",
57
+ "monoLg": "11px",
58
+ "mono": "10px",
59
+ "monoSm": "9px"
60
+ } as const
package/react/email.ts ADDED
@@ -0,0 +1,219 @@
1
+ // Gabarits d'e-mail « Encre & Papier ».
2
+ //
3
+ // ⚠️ CE MODULE N'EST PAS DU REACT ET N'EMPLOIE PAS LE CSS DU PAQUET. Il rend
4
+ // des chaînes de HTML. C'est la seule dérogation à la règle première du dépôt
5
+ // (« le CSS est la source de vérité, les composants n'en sont qu'une
6
+ // façade »), et elle est imposée par le support, pas choisie :
7
+ //
8
+ // · il n'y a pas de feuille de style à charger dans un e-mail ;
9
+ // · Outlook et Gmail SUPPRIMENT les propriétés personnalisées : un
10
+ // `var(--accent)` s'y rend en couleur nulle, donc en texte noir sur fond
11
+ // transparent. Les valeurs sont donc écrites en clair — mais tirées du
12
+ // miroir JS des tokens, que tests/tokens.test.js tient aligné sur le CSS ;
13
+ // · ni flexbox ni grid ne sont fiables : la mise en page est en TABLES ;
14
+ // · les dégradés ne se rendent pas dans Outlook — le bouton plein perd le
15
+ // sien et devient un aplat, le filet de spectre devient trois aplats.
16
+ //
17
+ // Il vit dans react/ parce que c'est la racine de compilation du paquet
18
+ // (`rootDir`), pas parce qu'il en dépend : il n'importe pas une ligne de
19
+ // React et n'émet aucune classe `zv-`.
20
+ //
21
+ // ⚠️ Ce module ÉCHAPPE à ta feuille de style : ce qui n'est pas ici ne
22
+ // s'affichera pas. Ne pas y ajouter de classes en espérant qu'elles soient
23
+ // stylées.
24
+
25
+ import { COULEURS, POLICES as PILES } from './email-tokens.js'
26
+
27
+ /** ⚠️ Les piles de polices du paquet contiennent des GUILLEMETS DOUBLES
28
+ * (`"Public Sans", …`), et tout ici s'écrit dans un attribut `style="…"`
29
+ * délimité par ces mêmes guillemets : l'attribut se refermerait au premier
30
+ * guillemet interne, et TOUT le style du nœud serait perdu. En HTML, un
31
+ * guillemet simple est équivalent à l'intérieur d'une valeur de police.
32
+ * Cette faute ne se voit pas à la relecture — elle se voit au rendu, où
33
+ * l'e-mail entier repasse en Times sans style. */
34
+ const pourAttribut = (pile: string) => pile.replace(/"/g, "'")
35
+
36
+ const POLICES = {
37
+ display: pourAttribut(PILES.display),
38
+ texte: pourAttribut(PILES.texte),
39
+ mono: pourAttribut(PILES.mono),
40
+ }
41
+
42
+ /* ─── Réglages communs ─────────────────────────────────────────────── */
43
+
44
+ /** 600px : la largeur qui traverse tous les clients depuis vingt ans, dont
45
+ * le volet de lecture d'Outlook. Ce n'est pas --page-max, et ça n'a rien à
46
+ * voir : c'est une contrainte du support. */
47
+ const LARGEUR = 600
48
+
49
+ const CORPS = `font-family:${POLICES.texte};font-size:15px;line-height:1.6;color:${COULEURS.muted};`
50
+ const TITRE = `font-family:${POLICES.display};font-weight:300;letter-spacing:-0.02em;color:${COULEURS.ink};`
51
+
52
+ /** Échappe ce qui entre dans le HTML. Un objet d'e-mail vient souvent d'une
53
+ * base de données : un nom de client avec une esperluette casserait le
54
+ * document, et un `<` bien placé y injecterait du balisage. */
55
+ export function echappe(texte: string): string {
56
+ return texte
57
+ .replace(/&/g, '&amp;')
58
+ .replace(/</g, '&lt;')
59
+ .replace(/>/g, '&gt;')
60
+ .replace(/"/g, '&quot;')
61
+ }
62
+
63
+ /* ─── Blocs ────────────────────────────────────────────────────────── */
64
+
65
+ /** Sur-titre : le filet de spectre suivi du label en capitales.
66
+ * PREMIER des trois emplois autorisés du spectre. Le dégradé est rendu en
67
+ * TROIS APLATS de 19px (violet, bleu, cyan) : un `linear-gradient` ne se
68
+ * rend pas dans Outlook, qui afficherait une bande vide. */
69
+ export function emailKicker(texte: string): string {
70
+ const brin = (couleur: string) =>
71
+ `<td width="19" height="2" style="background:${couleur};font-size:0;line-height:0;">&nbsp;</td>`
72
+ return `<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin:0 0 14px;"><tr>
73
+ <td><table role="presentation" cellpadding="0" cellspacing="0" border="0"><tr>${brin(COULEURS.violet)}${brin(COULEURS.bleu)}${brin(COULEURS.cyan)}</tr></table></td>
74
+ <td style="padding-left:12px;font-family:${POLICES.mono};font-size:11px;font-weight:500;letter-spacing:0.2em;text-transform:uppercase;color:${COULEURS.accent};">${echappe(texte)}</td>
75
+ </tr></table>`
76
+ }
77
+
78
+ /** Le titre du message. Un seul par e-mail : c'est la promesse de l'objet,
79
+ * tenue à l'ouverture. */
80
+ export function emailTitre(texte: string): string {
81
+ return `<h1 style="${TITRE}font-size:26px;line-height:1.2;margin:0 0 18px;">${echappe(texte)}</h1>`
82
+ }
83
+
84
+ /** Un paragraphe. Le HTML d'enrichissement (`<strong>`, `<a>`) est admis :
85
+ * c'est du texte rédigé, pas une saisie utilisateur — l'échapper
86
+ * afficherait des chevrons. Passer par `echappe` toute valeur qui vient
87
+ * d'une base. */
88
+ export function emailTexte(html: string): string {
89
+ return `<p style="${CORPS}margin:0 0 16px;">${html}</p>`
90
+ }
91
+
92
+ /** L'action. UN SEUL bouton plein par e-mail, comme par page — c'est ce qui
93
+ * fait qu'on sait où cliquer.
94
+ * Construit en table : le `padding` d'un `<a>` est ignoré par Outlook, qui
95
+ * rendrait un lien nu au milieu du message. Aplat indigo et non dégradé,
96
+ * pour la même raison. Angle vif, comme partout. */
97
+ export function emailBouton({ href, libelle }: { href: string; libelle: string }): string {
98
+ return `<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin:26px 0;"><tr>
99
+ <td align="center" bgcolor="${COULEURS.accentDeep}" style="background:${COULEURS.accentDeep};">
100
+ <a href="${echappe(href)}" style="display:inline-block;padding:15px 30px;font-family:${POLICES.texte};font-size:15px;font-weight:600;color:${COULEURS.white};text-decoration:none;">${echappe(libelle)}</a>
101
+ </td></tr></table>`
102
+ }
103
+
104
+ /** Le lien secondaire, sous le bouton : l'autre chemin, pour qui ne veut pas
105
+ * du premier. Souligné — dans un e-mail, la couleur seule ne suffit pas à
106
+ * annoncer un lien. */
107
+ export function emailLienSecondaire({ href, libelle }: { href: string; libelle: string }): string {
108
+ return `<p style="${CORPS}font-size:14px;margin:0 0 16px;"><a href="${echappe(href)}" style="color:${COULEURS.accent};text-decoration:underline;">${echappe(libelle)}</a></p>`
109
+ }
110
+
111
+ /** La mention discrète : ce qu'on lit après avoir décidé. */
112
+ export function emailNote(html: string): string {
113
+ return `<p style="font-family:${POLICES.texte};font-size:13px;line-height:1.55;color:${COULEURS.label};margin:0 0 12px;">${html}</p>`
114
+ }
115
+
116
+ /** L'encart : un extrait, un récapitulatif de commande, un code. */
117
+ export function emailEncart(html: string): string {
118
+ return `<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="margin:0 0 18px;"><tr>
119
+ <td style="background:${COULEURS.panel};border:1px solid ${COULEURS.hairline};border-left:2px solid ${COULEURS.accentLine};padding:14px 18px;font-family:${POLICES.texte};font-size:13px;line-height:1.6;color:${COULEURS.ink};">${html}</td>
120
+ </tr></table>`
121
+ }
122
+
123
+ /** Le filet de séparation. */
124
+ export function emailFilet(): string {
125
+ return `<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="margin:22px 0;"><tr>
126
+ <td height="1" style="background:${COULEURS.line};font-size:0;line-height:0;">&nbsp;</td></tr></table>`
127
+ }
128
+
129
+ /* ─── Le gabarit ───────────────────────────────────────────────────── */
130
+
131
+ export interface EmailOptions {
132
+ /** Repris en `<title>`. Ce n'est PAS l'objet du message : l'objet se pose
133
+ * à l'envoi, dans l'en-tête SMTP. */
134
+ titre: string
135
+ /** Le texte gris qui suit l'objet dans la liste des messages. Sans lui,
136
+ * le client y affiche les premiers mots du HTML — souvent « Voir cet
137
+ * e-mail dans votre navigateur », ce qui gâche la seule ligne dont on
138
+ * dispose pour convaincre d'ouvrir. */
139
+ preheader?: string
140
+ /** Le nom de la marque, écrit en toutes lettres. ⚠️ OBLIGATOIRE même avec
141
+ * un logo : une image sur deux est bloquée par défaut, et un e-mail qui
142
+ * ne dit pas de qui il vient part à la corbeille. */
143
+ marque: string
144
+ /** L'URL ABSOLUE du logo — une image d'e-mail ne se résout pas depuis un
145
+ * chemin relatif, et les data-URI sont bloquées par Gmail. */
146
+ logo?: { src: string; hauteur?: number }
147
+ /** Les blocs, concaténés. */
148
+ contenu: string
149
+ /** Sous la carte : mentions légales, désinscription. */
150
+ pied?: string
151
+ lang?: string
152
+ }
153
+
154
+ /**
155
+ * Assemble un e-mail complet.
156
+ *
157
+ * ⚠️ Le mode sombre est REFUSÉ explicitement (`color-scheme: light`) : la
158
+ * charte est claire par nature, et l'inversion automatique d'Apple Mail
159
+ * retourne les neutres sans toucher aux aplats — l'indigo du bouton
160
+ * resterait sur un fond devenu noir, et les contrastes réglés dans un sens
161
+ * partiraient dans l'autre. C'est la même règle que pour les surfaces
162
+ * sombres du système : elles se font avec la bande manifeste, jamais en
163
+ * inversant les neutres.
164
+ */
165
+ export function email({
166
+ titre,
167
+ preheader,
168
+ marque,
169
+ logo,
170
+ contenu,
171
+ pied,
172
+ lang = 'fr',
173
+ }: EmailOptions): string {
174
+ const enTete = logo
175
+ ? `<img src="${echappe(logo.src)}" height="${logo.hauteur ?? 28}" alt="${echappe(marque)}" style="display:block;border:0;height:${logo.hauteur ?? 28}px;width:auto;">`
176
+ : `<span style="font-family:${POLICES.display};font-weight:600;font-size:20px;letter-spacing:-0.02em;color:${COULEURS.ink};">${echappe(marque)}</span>`
177
+
178
+ return `<!doctype html>
179
+ <html lang="${lang}">
180
+ <head>
181
+ <meta charset="utf-8">
182
+ <meta name="viewport" content="width=device-width,initial-scale=1">
183
+ <meta name="color-scheme" content="light">
184
+ <meta name="supported-color-schemes" content="light">
185
+ <title>${echappe(titre)}</title>
186
+ <style>
187
+ /* Le seul <style> du document, et il ne porte QUE du responsive : ce que
188
+ Gmail en supprime ne fait rien perdre, tout le reste est en ligne. */
189
+ @media (max-width:620px) {
190
+ .zv-carte { padding:26px 20px !important; }
191
+ .zv-marge { padding:20px 12px !important; }
192
+ }
193
+ </style>
194
+ </head>
195
+ <body style="margin:0;padding:0;width:100%;background:${COULEURS.paper};-webkit-text-size-adjust:100%;">
196
+ <div style="display:none;max-height:0;overflow:hidden;opacity:0;">${preheader ? echappe(preheader) : ''}</div>
197
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:${COULEURS.paper};">
198
+ <tr><td align="center" class="zv-marge" style="padding:32px 16px;">
199
+ <table role="presentation" width="${LARGEUR}" cellpadding="0" cellspacing="0" border="0" style="width:100%;max-width:${LARGEUR}px;">
200
+
201
+ <tr><td style="padding:0 0 20px;">${enTete}</td></tr>
202
+
203
+ <tr><td class="zv-carte" bgcolor="${COULEURS.white}" style="background:${COULEURS.white};border:1px solid ${COULEURS.line};padding:34px 36px;">
204
+ ${contenu}
205
+ </td></tr>
206
+
207
+ ${
208
+ pied
209
+ ? `<tr><td style="padding:20px 4px 0;font-family:${POLICES.texte};font-size:12px;line-height:1.6;color:${COULEURS.label};">${pied}</td></tr>`
210
+ : ''
211
+ }
212
+
213
+ </table>
214
+ </td></tr>
215
+ </table>
216
+ </body>
217
+ </html>
218
+ `
219
+ }
@@ -30,6 +30,7 @@ export type IconId =
30
30
  | 'diplome'
31
31
  | 'dossier'
32
32
  | 'eclair'
33
+ | 'empreinte'
33
34
  | 'envoyer'
34
35
  | 'epingle'
35
36
  | 'equipe'
@@ -102,6 +103,7 @@ export const CORPS: Record<IconId, string> = {
102
103
  'diplome': "<path d=\"M2.67 9.33 L12 4.67 L21.33 9.33 L12 14 Z\" /><path d=\"M6.67 11.6 V16 C6.67 17.47 9.07 18.67 12 18.67 C14.93 18.67 17.33 17.47 17.33 16 V11.6\" /><path d=\"M21.33 9.33 V14.67\" />",
103
104
  'dossier': "<path d=\"M1.41 6.64a1.69 1.69 0 0 1 1.69-1.69h4.8l2.12 2.4h10.87a1.69 1.69 0 0 1 1.69 1.69v9.88a1.69 1.69 0 0 1-1.69 1.69H3.11A1.69 1.69 0 0 1 1.41 20.33V6.64Z\" />",
104
105
  'eclair': "<path d=\"M13.33 2.67 L5.33 13.33 H12 L10.67 21.33 L18.67 10.67 H12 Z\" />",
106
+ 'empreinte': "<path d=\"M4 18.67 V14.67 A8 9.33 0 0 1 20 14.67 V17.33\" /><path d=\"M8 17.33 A4 5.33 0 0 1 16 17.33\" /><path d=\"M12 13.33 V20\" />",
105
107
  'envoyer': "<path d=\"M21.33 2.67 L10.67 13.33\" /><path d=\"M21.33 2.67 L14.67 21.33 L10.67 13.33 L2.67 9.33 Z\" />",
106
108
  'epingle': "<path d=\"M12 21.33 C7.56 16.89 5.33 13.42 5.33 10.67 A6.67 6.67 0 0 1 18.67 10.67 C18.67 13.42 16.44 16.89 12 21.33 Z\" /><circle cx=\"12\" cy=\"10.67\" r=\"2.4\" />",
107
109
  'equipe': "<circle cx=\"9.33\" cy=\"8\" r=\"3.33\" /><path d=\"M3.33 20 C3.33 15.33 6 13.33 9.33 13.33 C12.67 13.33 15.33 15.33 15.33 20\" /><path d=\"M16 5.33 A2.93 2.93 0 0 1 16 11.07\" /><path d=\"M17.33 13.6 C19.73 14.27 20.67 16.4 20.67 20\" />",
@@ -177,6 +179,7 @@ export const NOMS: Record<IconId, string> = {
177
179
  'diplome': "Diplôme (Campus)",
178
180
  'dossier': "Dossier",
179
181
  'eclair': "Éclair (action rapide)",
182
+ 'empreinte': "Empreinte (biométrie, passkey)",
180
183
  'envoyer': "Envoyer",
181
184
  'epingle': "Épingle (lieu)",
182
185
  'equipe': "Équipe (membres)",