@mostajs/wagon-ui 0.2.0 → 0.3.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/CHANGELOG.md +11 -0
- package/llms.txt +1 -0
- package/package.json +1 -1
- package/src/index.js +32 -18
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
**Auteur** : Dr Hamid MADANI <drmdh@msn.com>
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
|
|
7
|
+
## 0.3.0 — 2026-10-02
|
|
8
|
+
|
|
9
|
+
- **Palette Studio** (palette.amia.fr) : les jetons portent désormais les noms de la palette (`--brand`, `--link`,
|
|
10
|
+
`--accent`, `--text`, `--text-muted`, `--surface-alt`, `--text-on-brand`…), les anciens noms en dérivent.
|
|
11
|
+
**Le texte ne prend jamais `--accent`** (souvent illisible : 2,65:1 pour #F472B6) — liens et titres prennent `--link`.
|
|
12
|
+
- Option `theme: () => css` : le thème du projet, relu à chaque page et posé après la feuille de base ; refusé s'il
|
|
13
|
+
sort de `:root{…}` (aucune balise, aucun sélecteur).
|
|
14
|
+
- Option `entete: 'barre'` : l'en-tête des pages prend le style de la documentation (bande de marque).
|
|
15
|
+
- Essais T-WUI-13 à 15, écrits avant le code, chacun confronté à un sabotage.
|
|
16
|
+
- ⚠️ Rupture mineure : `--accent` désignait la couleur des liens ; il désigne désormais l'ornement (comme dans la palette).
|
|
6
17
|
## 0.2.0 — 2026-10-02
|
|
7
18
|
|
|
8
19
|
- **Affichage PC, tablette, mobile** : en-tête repliable derrière un **menu hamburger** (sans script : case à
|
package/llms.txt
CHANGED
|
@@ -31,3 +31,4 @@ Rendre les écrans communs à tout SaaS, identiques d'un produit à l'autre, san
|
|
|
31
31
|
- Une ressource sans `parPeriode` s'affiche « libellé : n ».
|
|
32
32
|
|
|
33
33
|
- 0.2 : `pageDocumentation({ lang, onglets, onglet, sections, actif, titre, sousTitre, contenu, sommaire, markdown, version, base, recherche, q })`, `rechercheDocumentation({ lang, q, resultats })` ; menu hamburger sans script dans `page()`.
|
|
34
|
+
- 0.3 : `creerWagonUi({ …, theme: () => ':root{…}', entete: 'simple'|'barre' })` ; jetons aux noms de Palette Studio ; le texte prend `--link`, jamais `--accent`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mostajs/wagon-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Les pages du wagon d'un SaaS — mise en page (langues, pied légal, en-têtes pour les moteurs), accueil, tarifs, inscription, connexion (mot de passe ou code), espace client (palier, jauges, clés, factures, gestion de l'abonnement), administration (paliers, usage), pages légales — en fr, en, ar. Extraites de mdseal, sans ce qu'il vend : l'identité et les ressources mesurées viennent de la configuration. Vues pures, zéro dépendance.",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"author": "Dr Hamid MADANI <drmdh@msn.com>",
|
package/src/index.js
CHANGED
|
@@ -39,23 +39,27 @@ const lien = (href) => {
|
|
|
39
39
|
const NBSP = ' ';
|
|
40
40
|
const euros = (centimes) => `${(Number(centimes) || 0) / 100}${NBSP}€`;
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
// 0.3 : les jetons portent les NOMS de Palette Studio (palette.amia.fr) — un thème qui les redéfinit habille le site
|
|
43
|
+
// entier ; les anciens noms en dérivent par var(), donc le suivent. Contrastes des replis vérifiés sur blanc.
|
|
44
|
+
const DERIVES = '--line:#e2e8f0;--text-muted:#475569;--surface-alt:#f1f5f9;--hover:#eef2f9;--field-border:#cbd5e0;'
|
|
45
|
+
+ '--success-bg:#f0fdf4;--success-text:#166534;--error-bg:#fef2f2;--error-text:#991b1b;'
|
|
46
|
+
+ '--doux:var(--text-muted);--fond-doux:var(--surface-alt);--survol:var(--hover);--bordure:var(--field-border);'
|
|
47
|
+
+ '--ink:var(--text);--ornement:var(--accent);--succes-fond:var(--success-bg);--succes-texte:var(--success-text);--succes-bord:#bbf7d0;'
|
|
48
|
+
+ '--erreur-fond:var(--error-bg);--erreur-texte:var(--error-text);--erreur-bord:#fecaca';
|
|
45
49
|
const REPLI = { brand: '#06284A', lien: '#2c5282', ornement: '#2c5282', encre: '#1a1a1a', surface: '#fff' };
|
|
46
50
|
const COULEUR = /^#[0-9a-fA-F]{3,8}$/;
|
|
47
51
|
|
|
48
52
|
/** La feuille de style de mdseal, sans ce qui servait la charge utile. Aucune couleur en dur. */
|
|
49
53
|
function style(c) {
|
|
50
|
-
return `:root{--brand:${c.brand};--
|
|
54
|
+
return `:root{--brand:${c.brand};--link:${c.lien};--accent:${c.ornement};--text:${c.encre};--surface:${c.surface};--bg:${c.surface};--text-on-brand:#ffffff;${DERIVES}}
|
|
51
55
|
*{box-sizing:border-box} body{font:15px/1.6 system-ui,sans-serif;margin:0;color:var(--ink);background:var(--surface)}
|
|
52
56
|
header{display:flex;align-items:center;gap:1.2rem;flex-wrap:wrap;max-width:920px;margin:0 auto;padding:1rem;border-bottom:3px solid var(--ornement)}
|
|
53
57
|
.brand{font-weight:800;font-size:1.25rem;color:var(--brand);text-decoration:none}
|
|
54
|
-
nav{display:flex;gap:.3rem;flex-wrap:wrap} nav a{color:var(--
|
|
58
|
+
nav{display:flex;gap:.3rem;flex-wrap:wrap} nav a{color:var(--link);text-decoration:none;padding:.3em .7em;border-radius:6px} nav a.on,nav a:hover{background:var(--survol)}
|
|
55
59
|
main{max-width:920px;margin:0 auto;padding:0 1rem 2rem}
|
|
56
|
-
h1{color:var(--brand)} h2{color:var(--
|
|
60
|
+
h1{color:var(--brand)} h2{color:var(--link);border-bottom:1px solid var(--line);padding-bottom:.2em;margin-top:1.6em}
|
|
57
61
|
h3{color:var(--brand);font-size:1.02rem;margin:1.2em 0 .3em}
|
|
58
|
-
fieldset{border:1px solid var(--line);border-radius:8px;margin:.8em 0;padding:.6em 1em} legend{color:var(--
|
|
62
|
+
fieldset{border:1px solid var(--line);border-radius:8px;margin:.8em 0;padding:.6em 1em} legend{color:var(--link);font-weight:700}
|
|
59
63
|
input[type=text],input[type=email],input[type=password],input[type=number]{padding:5px 8px;border:1px solid var(--bordure);border-radius:6px;font-size:14px}
|
|
60
64
|
input[type=text],input[type=email],input[type=password]{min-width:15em} input[type=number]{width:7em}
|
|
61
65
|
.champs{display:flex;flex-wrap:wrap;gap:.8em 1.4em;align-items:flex-end}
|
|
@@ -82,14 +86,14 @@ pre,code,kbd,samp{direction:ltr;text-align:left;unicode-bidi:isolate} [dir="rtl"
|
|
|
82
86
|
.jauges{list-style:none;padding:0} .jauges li{margin:.5em 0}
|
|
83
87
|
.barre{display:inline-block;vertical-align:middle;width:9em;height:.55em;background:var(--line);border-radius:99px;margin-inline-start:.6em;overflow:hidden}
|
|
84
88
|
.barre i{display:block;height:100%;background:var(--ornement)}
|
|
85
|
-
.moi{margin-inline-start:auto;font-size:.9em} .moi a{color:var(--
|
|
86
|
-
.langues{margin-inline-start:.8em;font-size:.9em;color:var(--doux)} .langues a{color:var(--
|
|
89
|
+
.moi{margin-inline-start:auto;font-size:.9em} .moi a{color:var(--link);text-decoration:none}
|
|
90
|
+
.langues{margin-inline-start:.8em;font-size:.9em;color:var(--doux)} .langues a{color:var(--link);text-decoration:none} .langues b{color:var(--brand)}
|
|
87
91
|
.hint{color:var(--doux);font-size:.9em} footer{max-width:920px;margin:2rem auto 0;padding:1rem;border-top:1px solid var(--line);color:var(--doux);font-size:.9em}
|
|
88
|
-
footer a{color:var(--
|
|
92
|
+
footer a{color:var(--link)}
|
|
89
93
|
.menu{display:flex;align-items:center;gap:1.2rem;flex:1 1 auto;flex-wrap:wrap}
|
|
90
94
|
.bascule{position:absolute;opacity:0;width:1px;height:1px;pointer-events:none}
|
|
91
95
|
.burger{display:none;margin-inline-start:auto;font-size:1.5rem;line-height:1;cursor:pointer;padding:.2em .45em;border:1px solid var(--line);border-radius:6px;color:var(--brand);user-select:none}
|
|
92
|
-
.bascule:focus-visible+.burger{outline:2px solid var(--
|
|
96
|
+
.bascule:focus-visible+.burger{outline:2px solid var(--link);outline-offset:2px}
|
|
93
97
|
table{display:block;max-width:100%;overflow-x:auto}
|
|
94
98
|
img,pre{max-width:100%} pre{overflow-x:auto}
|
|
95
99
|
@media (max-width: 760px){
|
|
@@ -102,8 +106,16 @@ img,pre{max-width:100%} pre{overflow-x:auto}
|
|
|
102
106
|
input[type=text],input[type=email],input[type=password]{min-width:0;width:100%}
|
|
103
107
|
.champs label{flex:1 1 100%}
|
|
104
108
|
.grille-tarifs{grid-template-columns:1fr}
|
|
105
|
-
}`;
|
|
106
109
|
}
|
|
110
|
+
header.barre{max-width:none;background:var(--brand);border-bottom:0;padding:.8rem max(1rem,calc((100% - 920px)/2))}
|
|
111
|
+
header.barre .brand,header.barre nav a,header.barre .moi a,header.barre .langues a,header.barre .langues b,header.barre .langues{color:var(--text-on-brand)}
|
|
112
|
+
header.barre nav a{opacity:.9} header.barre nav a.on,header.barre nav a:hover{background:rgba(255,255,255,.16);opacity:1}
|
|
113
|
+
header.barre .burger{color:var(--text-on-brand);border-color:rgba(255,255,255,.4)}
|
|
114
|
+
header.barre + main{padding-top:1.2rem}`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Un thème de Palette Studio n'est accepté que s'il ne fait que redéfinir des jetons dans :root{…}. */
|
|
118
|
+
const THEME_SAIN = /^\s*:root\s*\{[^{}<]*\}\s*$/;
|
|
107
119
|
|
|
108
120
|
/** La documentation (0.2) : la mise en page des sites de référence d'API — onglets, barre latérale, sommaire. */
|
|
109
121
|
const STYLE_DOC = `body{background:var(--surface)} .doc-tete{display:flex;align-items:center;gap:1rem;flex-wrap:wrap;padding:.8rem 1.2rem;background:var(--brand);color:var(--surface)}
|
|
@@ -117,9 +129,9 @@ const STYLE_DOC = `body{background:var(--surface)} .doc-tete{display:flex;align-
|
|
|
117
129
|
.laterale h3{font-size:.78rem;letter-spacing:.06em;text-transform:uppercase;color:var(--doux);margin:1.4em 0 .4em} .laterale h3:first-child{margin-top:0}
|
|
118
130
|
.laterale ul{list-style:none;margin:0;padding:0} .laterale a{display:block;padding:.35em .6em;border-radius:6px;color:var(--ink);text-decoration:none} .laterale a.on{background:var(--fond-doux);font-weight:600} .laterale a:hover{background:var(--survol)}
|
|
119
131
|
.doc-corps{padding:1.6rem 2.2rem 3rem;min-width:0} .doc-corps h1{margin:.1em 0 .2em;font-size:2.1rem} .doc-corps .lede{font-size:1.12rem;color:var(--doux);margin:.2em 0 1em}
|
|
120
|
-
.doc-corps hr{border:0;border-top:1px solid var(--line);margin:1.2em 0 1.6em} .doc-corps a.md{float:inline-end;font-size:.88em;border:1px solid var(--line);border-radius:6px;padding:.3em .7em;text-decoration:none;color:var(--
|
|
132
|
+
.doc-corps hr{border:0;border-top:1px solid var(--line);margin:1.2em 0 1.6em} .doc-corps a.md{float:inline-end;font-size:.88em;border:1px solid var(--line);border-radius:6px;padding:.3em .7em;text-decoration:none;color:var(--link)}
|
|
121
133
|
.sommaire{padding:1.6rem 1rem;position:sticky;top:0;align-self:start;font-size:.9em} .sommaire p{color:var(--doux);margin:0 0 .4em}
|
|
122
|
-
.sommaire ul{list-style:none;margin:0;padding:0;border-inline-start:2px solid var(--line)} .sommaire a{display:block;padding:.25em .8em;color:var(--doux);text-decoration:none} .sommaire a:hover{color:var(--
|
|
134
|
+
.sommaire ul{list-style:none;margin:0;padding:0;border-inline-start:2px solid var(--line)} .sommaire a{display:block;padding:.25em .8em;color:var(--doux);text-decoration:none} .sommaire a:hover{color:var(--link)}
|
|
123
135
|
.doc-barre .burger{color:var(--surface);border-color:rgba(255,255,255,.4)}
|
|
124
136
|
@media (max-width: 1100px){ .doc{grid-template-columns:15rem minmax(0,1fr)} .sommaire{display:none} }
|
|
125
137
|
@media (max-width: 760px){
|
|
@@ -144,13 +156,15 @@ const DOCUMENTS_LEGAUX = Object.freeze([['mentions-legales', 'mentions'], ['cgu'
|
|
|
144
156
|
* parPeriode?: string|object, sansLimite?: string|object, aucun?: string|object }>} [o.ressources]
|
|
145
157
|
* @param {object} [o.textes] surcharges des libellés, par langue : { fr: { tarifs: { note: '…' } } }
|
|
146
158
|
*/
|
|
147
|
-
export function creerWagonUi({ marque, couleurs = {}, langues = ['fr', 'en', 'ar'], liens = [], ressources = [], textes = {} } = {}) {
|
|
159
|
+
export function creerWagonUi({ marque, couleurs = {}, langues = ['fr', 'en', 'ar'], liens = [], ressources = [], textes = {}, theme = null, entete = 'simple' } = {}) {
|
|
148
160
|
if (!String(marque?.nom ?? '').trim()) throw new Error('@mostajs/wagon-ui : `marque.nom` requis — le nom du service');
|
|
149
161
|
if (!String(marque?.auteur ?? '').trim()) throw new Error('@mostajs/wagon-ui : `marque.auteur` requis — qui édite le service');
|
|
150
162
|
const LANGUES = langues.filter((l) => CATALOGUES[l]);
|
|
151
163
|
if (!LANGUES.length) throw new Error('@mostajs/wagon-ui : aucune langue connue (fr, en, ar)');
|
|
152
164
|
const C = Object.fromEntries(Object.entries({ ...REPLI, ...couleurs }).map(([k, v]) => [k, COULEUR.test(String(v)) ? v : REPLI[k]]));
|
|
153
165
|
const STYLE = style(C);
|
|
166
|
+
/** Le thème courant (Palette Studio), relu à chaque page ; rejeté s'il sort de :root{…}. */
|
|
167
|
+
const themeCss = () => { try { const t = typeof theme === 'function' ? String(theme() ?? '') : ''; return THEME_SAIN.test(t) ? t : ''; } catch { return ''; } };
|
|
154
168
|
|
|
155
169
|
/** Le traducteur d'une langue : surcharges de l'application, puis le catalogue, puis le français. */
|
|
156
170
|
function t(lang) {
|
|
@@ -191,8 +205,8 @@ export function creerWagonUi({ marque, couleurs = {}, langues = ['fr', 'en', 'ar
|
|
|
191
205
|
: `<a href="${chemin}?lang=${l}" hreflang="${l}" lang="${l}" rel="alternate">${esc(LIBELLES_LANGUES[l])}</a>`)).join(' · ')}</span>`;
|
|
192
206
|
return `<!doctype html><html lang="${L}" dir="${dirDe(L)}"><head><meta charset="utf-8">
|
|
193
207
|
<meta name="viewport" content="width=device-width, initial-scale=1"><title>${esc(titre)} — ${nom}</title>
|
|
194
|
-
${tete}<style>${STYLE}</style
|
|
195
|
-
<header><a class="brand" href="/">${marque.symbole ? `${esc(marque.symbole)} ` : ''}${nom}</a><input type="checkbox" id="wagon-menu" class="bascule"><label for="wagon-menu" class="burger" aria-label="${esc(tr('layout.menu'))}">☰</label><div class="menu"><nav>${nav.map(([h, l]) => `<a href="${lien(h)}"${actif === h ? ' class="on"' : ''}>${esc(l)}</a>`).join('')}</nav><span class="moi"><a href="${connecte ? '/compte' : '/connexion'}">${esc(tr(connecte ? 'espace.compte' : 'espace.seConnecter'))}</a>${connecte ? ` · <a href="/deconnexion">${esc(tr('espace.seDeconnecter'))}</a>` : ''}</span>${choixLangue}</div></header>
|
|
208
|
+
${tete}<style>${STYLE}</style>${themeCss() ? `<style id="theme-palette">${themeCss()}</style>` : ''}</head><body>
|
|
209
|
+
<header${entete === 'barre' ? ' class="barre"' : ''}><a class="brand" href="/">${marque.symbole ? `${esc(marque.symbole)} ` : ''}${nom}</a><input type="checkbox" id="wagon-menu" class="bascule"><label for="wagon-menu" class="burger" aria-label="${esc(tr('layout.menu'))}">☰</label><div class="menu"><nav>${nav.map(([h, l]) => `<a href="${lien(h)}"${actif === h ? ' class="on"' : ''}>${esc(l)}</a>`).join('')}</nav><span class="moi"><a href="${connecte ? '/compte' : '/connexion'}">${esc(tr(connecte ? 'espace.compte' : 'espace.seConnecter'))}</a>${connecte ? ` · <a href="/deconnexion">${esc(tr('espace.seDeconnecter'))}</a>` : ''}</span>${choixLangue}</div></header>
|
|
196
210
|
<main>${contenu}</main>
|
|
197
211
|
<footer><b>${nom}</b>${slogan ? ` — ${esc(slogan)}` : ''} · ${esc(tr('layout.auteur'))} : <b>${esc(marque.auteur)}</b>${marque.courriel ? ` <${esc(marque.courriel)}>` : ''} · ${esc(marque.licence ?? 'AGPL-3.0')} · ${esc(tr('layout.propulse'))} <code>@mostajs/*</code><br><span class="legal">${DOCUMENTS_LEGAUX.map(([h, k]) => `<a href="/${h}">${esc(tr(`legal.${k}`))}</a>`).join(' · ')}</span></footer>
|
|
198
212
|
</body></html>`;
|
|
@@ -450,7 +464,7 @@ ${anonyme ? `<h2>${esc(tr('usage.anonyme'))}</h2>
|
|
|
450
464
|
: `<a href="${lien(p.href)}"${p.href === actif ? ' class="on" aria-current="page"' : ''}>${esc(p.titre)}</a>`);
|
|
451
465
|
return `<!doctype html><html lang="${L}" dir="${dirDe(L)}"><head><meta charset="utf-8">
|
|
452
466
|
<meta name="viewport" content="width=device-width, initial-scale=1"><title>${esc(titre)} — ${esc(tr('doc.documentation'))} ${nom}</title>
|
|
453
|
-
${canonique ? `<link rel="canonical" href="${esc(canonique)}">${sousTitre ? `<meta name="description" content="${esc(sousTitre)}">` : ''}` : ''}<style>${STYLE}${STYLE_DOC}</style
|
|
467
|
+
${canonique ? `<link rel="canonical" href="${esc(canonique)}">${sousTitre ? `<meta name="description" content="${esc(sousTitre)}">` : ''}` : ''}<style>${STYLE}${STYLE_DOC}</style>${themeCss() ? `<style id="theme-palette">${themeCss()}</style>` : ''}</head><body>
|
|
454
468
|
<header class="doc-tete"><a class="brand" href="/">${marque.symbole ? `${esc(marque.symbole)} ` : ''}${nom}</a><span class="doc-mot">${esc(tr('doc.documentation'))}</span>${version ? `<span class="version">${esc(version)}</span>` : ''}<span class="moi"><a href="/">${esc(tr('doc.accueil'))}</a></span>${choixLangue}</header>
|
|
455
469
|
<input type="checkbox" id="doc-menu" class="bascule">
|
|
456
470
|
<div class="doc-barre"><label for="doc-menu" class="burger" aria-label="${esc(tr('layout.menu'))}">☰</label><nav class="onglets">${onglets.map((o) => `<a href="${lien(o.href)}"${o.href === onglet ? ' class="on"' : ''}>${esc(dansLangue(o.libelle, L))}</a>`).join('')}</nav><form class="recherche" method="get" action="${lien(recherche)}"><input type="search" name="q" value="${esc(q)}" placeholder="${esc(tr('doc.rechercher'))}" aria-label="${esc(tr('doc.rechercher'))}"></form></div>
|