@zevra/ui 0.28.0 → 0.29.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.
@@ -34,3 +34,14 @@ export const NOMS_PRODUITS: Record<GlypheId, string> = {
34
34
  'supernovia': "Supernovia",
35
35
  'zevra': "Zevra",
36
36
  }
37
+
38
+ /** Ce que chaque produit FAIT, en deux mots — pour le lanceur d'apps. */
39
+ export const ACCROCHES_PRODUITS: Record<GlypheId, string> = {
40
+ 'campus': "Formation",
41
+ 'lexform': "Anonymisation",
42
+ 'mcp': "Connecteur",
43
+ 'memoire': "Recherche IA",
44
+ 'plume': "Éditeur",
45
+ 'supernovia': "Contenu sourcé",
46
+ 'zevra': "La suite",
47
+ }
package/react/glyphes.ts CHANGED
@@ -14,10 +14,16 @@
14
14
  // elle n'existe pas encore (retirée le 24/08, à regénérer), et ces glyphes
15
15
  // ne doivent donc pas descendre sous cette taille.
16
16
 
17
- import { GLYPHES, MOT_ZEVRA, NOMS_PRODUITS, type GlypheId } from './glyph-data.js'
17
+ import {
18
+ ACCROCHES_PRODUITS,
19
+ GLYPHES,
20
+ MOT_ZEVRA,
21
+ NOMS_PRODUITS,
22
+ type GlypheId,
23
+ } from './glyph-data.js'
18
24
 
19
25
  export type { GlypheId }
20
- export { GLYPHES, MOT_ZEVRA, NOMS_PRODUITS }
26
+ export { ACCROCHES_PRODUITS, GLYPHES, MOT_ZEVRA, NOMS_PRODUITS }
21
27
 
22
28
  /** Les sept identifiants, triés. */
23
29
  export const PRODUITS = Object.keys(GLYPHES) as GlypheId[]
package/react/layout.tsx CHANGED
@@ -619,11 +619,22 @@ export function NavActions({ className, ...reste }: Div & { className?: string }
619
619
  * un état se dit en prop, il ne se peint pas depuis l'app. */
620
620
  export function Lanceur({
621
621
  label = 'Applications Zevra',
622
+ titre = 'Applications',
622
623
  ouvert,
623
624
  className,
624
625
  children,
625
626
  ...reste
626
- }: Div & { label?: string; ouvert?: boolean; className?: string; children?: ReactNode }) {
627
+ }: Div & {
628
+ label?: string
629
+ /** L'intitulé du panneau, avec le filet de spectre. Le passer à `null`
630
+ * pour un panneau nu — mais c'est ce filet qui fait qu'un lanceur
631
+ * ressemble à Zevra plutôt qu'à n'importe quel autre. */
632
+ titre?: ReactNode
633
+ ouvert?: boolean
634
+ className?: string
635
+ /** Des `LanceurApp`, éventuellement précédés de `LanceurGroupe`. */
636
+ children?: ReactNode
637
+ }) {
627
638
  return (
628
639
  <span className={cx('zv-lanceur', ouvert && 'zv-lanceur--ouvert', className)} {...reste}>
629
640
  <button
@@ -637,17 +648,41 @@ export function Lanceur({
637
648
  </button>
638
649
  <span className="zv-lanceur__panneau">
639
650
  <span className="zv-menu">
640
- <span className="zv-lanceur__apps">{children}</span>
651
+ {titre && (
652
+ <span className="zv-lanceur__tete">
653
+ <span className="zv-spec" aria-hidden="true" />
654
+ <span className="zv-mono zv-mono--label">{titre}</span>
655
+ </span>
656
+ )}
657
+ {children}
641
658
  </span>
642
659
  </span>
643
660
  </span>
644
661
  )
645
662
  }
646
663
 
664
+ /** Un intertitre de famille dans le panneau (« Suite Zevra »). Facultatif :
665
+ * avec une seule famille il n'apporte rien, et le panneau s'ouvre plus vite
666
+ * sans lui. */
667
+ export function LanceurGroupe({ className, ...reste }: Span & { className?: string }) {
668
+ return <span className={cx('zv-lanceur__groupe', className)} {...reste} />
669
+ }
670
+
671
+ /** La grille de tuiles. Elle enveloppe les `LanceurApp` d'une même famille —
672
+ * c'est ce qui permet à un panneau d'en porter plusieurs. */
673
+ export function LanceurApps({ className, ...reste }: Span & { className?: string }) {
674
+ return <span className={cx('zv-lanceur__apps', className)} {...reste} />
675
+ }
676
+
647
677
  export interface LanceurAppProps
648
678
  extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'className'> {
649
679
  nom: string
650
- /** Le glyphe du produit — une image du paquet (`@zevra/ui/logos/…`). Sans
680
+ /** Ce que le produit FAIT, en deux mots (« Recherche IA »,
681
+ * « Anonymisation »). C'est elle qui rend la liste lisible à quelqu'un
682
+ * qui ne connaît pas encore la suite : un nom de marque seul ne dit rien
683
+ * à un client qui découvre. */
684
+ accroche?: string
685
+ /** Le glyphe du produit — `GLYPHES.memoire` de `@zevra/ui/glyphes`. Sans
651
686
  * lui, la tuile porte l'initiale : une image qui ne charge pas ne doit
652
687
  * pas laisser un trou. */
653
688
  glyphe?: string
@@ -657,21 +692,31 @@ export interface LanceurAppProps
657
692
  className?: string
658
693
  }
659
694
 
660
- export function LanceurApp({ nom, glyphe, courant, className, ...reste }: LanceurAppProps) {
695
+ export function LanceurApp({
696
+ nom,
697
+ accroche,
698
+ glyphe,
699
+ courant,
700
+ className,
701
+ ...reste
702
+ }: LanceurAppProps) {
661
703
  return (
662
704
  <a
663
705
  aria-current={courant ? 'page' : undefined}
664
706
  className={cx('zv-lanceur__app', className)}
665
707
  {...reste}
666
708
  >
667
- {glyphe ? (
668
- <img src={glyphe} alt="" className="zv-lanceur__glyphe" />
669
- ) : (
670
- <span className="zv-avatar zv-avatar--soft" aria-hidden="true">
671
- {nom.slice(0, 1).toUpperCase()}
672
- </span>
673
- )}
709
+ <span className="zv-lanceur__cadre">
710
+ {glyphe ? (
711
+ <img src={glyphe} alt="" className="zv-lanceur__glyphe" />
712
+ ) : (
713
+ <span className="zv-mono zv-mono--label" aria-hidden="true">
714
+ {nom.slice(0, 1).toUpperCase()}
715
+ </span>
716
+ )}
717
+ </span>
674
718
  <span className="zv-lanceur__nom">{nom}</span>
719
+ {accroche && <span className="zv-lanceur__accroche">{accroche}</span>}
675
720
  </a>
676
721
  )
677
722
  }
@@ -1148,23 +1148,66 @@ a.zv-menu__item:hover {
1148
1148
  flex-direction: column;
1149
1149
  }
1150
1150
 
1151
- /* ─── La grille d'applications ──────────────────────────────────────────
1152
- * Trois par rangée : au-delà, les noms se coupent ; en deçà, le panneau
1153
- * s'allonge en colonne et cesse de se lire d'un coup d'œil. */
1151
+ /* ─── Le panneau d'applications ─────────────────────────────────────────
1152
+ * QUATRE par rangée : sept produits tiennent alors en deux rangées pleines
1153
+ * (4 + 3), là où trois colonnes en laissaient une seule et bancale sur la
1154
+ * dernière ligne.
1155
+ *
1156
+ * ⚠️ PAS DE CHAMP DE RECHERCHE. Sept produits se lisent d'un coup d'œil ;
1157
+ * une recherche y ajouterait un contrôle à servir, un état à tenir et un
1158
+ * clavier à ouvrir sur téléphone, pour trouver ce qui est déjà visible. Le
1159
+ * jour où la suite dépasse la douzaine, c'est une autre conversation. */
1160
+
1161
+ .zv-lanceur__tete {
1162
+ display: flex;
1163
+ align-items: center;
1164
+ gap: 12px;
1165
+ padding: 14px 14px 10px;
1166
+ }
1167
+
1168
+ /* Le filet de spectre, PREMIER de ses trois emplois autorisés : c'est la
1169
+ * signature de la marque, et c'est elle qui fait qu'un panneau d'apps
1170
+ * ressemble à Zevra plutôt qu'à n'importe quel lanceur. */
1171
+ .zv-lanceur__tete .zv-spec {
1172
+ flex: none;
1173
+ }
1174
+
1175
+ /* Un intertitre par famille (« Suite Zevra », « Vos espaces »). Facultatif :
1176
+ * avec une seule famille, il n'apporte rien et le panneau s'ouvre plus vite
1177
+ * sans lui. */
1178
+ .zv-lanceur__groupe {
1179
+ display: block;
1180
+ padding: 6px 14px 4px;
1181
+ font-family: var(--font-mono);
1182
+ font-size: var(--mono);
1183
+ font-weight: 600;
1184
+ letter-spacing: 0.12em;
1185
+ text-transform: uppercase;
1186
+ color: var(--label);
1187
+ }
1188
+
1189
+ .zv-lanceur__groupe + .zv-lanceur__apps {
1190
+ padding-top: 0;
1191
+ }
1154
1192
 
1155
1193
  .zv-lanceur__apps {
1156
1194
  display: grid;
1157
- grid-template-columns: repeat(3, 92px);
1158
- gap: 4px;
1159
- padding: 6px;
1195
+ grid-template-columns: repeat(4, 104px);
1196
+ /* 6px et pas 2 : « MCP Juridique » remplit sa colonne, et à 2px son nom
1197
+ * venait toucher celui de la tuile voisine. */
1198
+ gap: 6px;
1199
+ padding: 6px 8px 10px;
1160
1200
  }
1161
1201
 
1162
1202
  .zv-lanceur__app {
1163
1203
  display: flex;
1164
1204
  flex-direction: column;
1165
1205
  align-items: center;
1166
- gap: 8px;
1167
- padding: 14px 6px;
1206
+ /* 4px, et le nom reprend 4px de marge : le nom et son accroche forment un
1207
+ * bloc SERRÉ, détaché du cadre. À gap uniforme, les trois lignes flottaient
1208
+ * à égale distance et le nom ne se rattachait plus à rien. */
1209
+ gap: 4px;
1210
+ padding: 12px 6px;
1168
1211
  text-align: center;
1169
1212
  text-decoration: none;
1170
1213
  color: var(--ink-2);
@@ -1191,8 +1234,34 @@ a.zv-menu__item:hover {
1191
1234
  color: var(--accent);
1192
1235
  }
1193
1236
 
1194
- /* Le glyphe du produit. 28px : la taille de .zv-icon--xl, pour qu'une tuile
1195
- * à image et une tuile à icône tracée se posent sur la même ligne. */
1237
+ /* Le glyphe vit dans un CADRE, et pas nu sur le fond : c'est lui qui donne
1238
+ * à la tuile son assise et qui permet au survol de répondre autrement que
1239
+ * par un aplat. Angle vif, comme tout le reste. */
1240
+ .zv-lanceur__cadre {
1241
+ display: flex;
1242
+ align-items: center;
1243
+ justify-content: center;
1244
+ width: 46px;
1245
+ height: 46px;
1246
+ border: 1px solid var(--line);
1247
+ background: var(--white);
1248
+ transition:
1249
+ border-color 140ms var(--zv-easing, cubic-bezier(0.2, 0.7, 0.2, 1)),
1250
+ background-color 140ms var(--zv-easing, cubic-bezier(0.2, 0.7, 0.2, 1));
1251
+ }
1252
+
1253
+ .zv-lanceur__app:hover .zv-lanceur__cadre {
1254
+ border-color: var(--accent-line);
1255
+ background: var(--white);
1256
+ }
1257
+
1258
+ .zv-lanceur__app--on .zv-lanceur__cadre,
1259
+ .zv-lanceur__app[aria-current='page'] .zv-lanceur__cadre {
1260
+ border-color: var(--accent-line);
1261
+ }
1262
+
1263
+ /* 28px : la taille de .zv-icon--xl, pour qu'une tuile à image et une tuile à
1264
+ * icône tracée se posent sur la même ligne. */
1196
1265
  .zv-lanceur__glyphe {
1197
1266
  display: block;
1198
1267
  width: 28px;
@@ -1201,14 +1270,35 @@ a.zv-menu__item:hover {
1201
1270
  }
1202
1271
 
1203
1272
  .zv-lanceur__nom {
1273
+ margin-top: 4px;
1204
1274
  font-size: var(--app-sm);
1205
- font-weight: 500;
1275
+ font-weight: 600;
1206
1276
  line-height: 1.3;
1277
+ color: var(--ink);
1207
1278
  /* Un nom de produit trop long ne doit pas élargir la colonne : il se coupe
1208
1279
  * plutôt que de déformer la grille. */
1209
1280
  overflow-wrap: anywhere;
1210
1281
  }
1211
1282
 
1283
+ /* L'accroche : ce que le produit FAIT, en deux mots. C'est elle qui rend la
1284
+ * liste lisible à quelqu'un qui ne connaît pas encore la suite — un nom de
1285
+ * marque seul ne dit rien à un client qui découvre. */
1286
+ .zv-lanceur__accroche {
1287
+ font-size: var(--mono);
1288
+ line-height: 1.3;
1289
+ color: var(--muted);
1290
+ /* PAS de `overflow-wrap: anywhere` ici, contrairement au nom : un nom de
1291
+ * marque qu'on coupe reste reconnaissable, une accroche coupée en plein
1292
+ * mot ne se lit plus. Si elle ne tient pas, c'est la colonne qui est trop
1293
+ * étroite — et c'est la colonne qu'on corrige. */
1294
+ text-wrap: balance;
1295
+ }
1296
+
1297
+ .zv-lanceur__app--on .zv-lanceur__nom,
1298
+ .zv-lanceur__app[aria-current='page'] .zv-lanceur__nom {
1299
+ color: var(--accent);
1300
+ }
1301
+
1212
1302
  /* ─── L'en-tête du panneau de compte ────────────────────────────────────
1213
1303
  * Qui l'on est, avant ce qu'on peut faire. Non connecté, il porte l'invite
1214
1304
  * de connexion : la zone de compte existe TOUJOURS, connecté ou non — une
@@ -1264,7 +1354,21 @@ a.zv-menu__item:hover {
1264
1354
  max-width: calc(100vw - 32px);
1265
1355
  }
1266
1356
 
1357
+ /* DEUX colonnes, et pas trois. Sur un iPhone SE (320px), trois colonnes
1358
+ * donnent 63px par tuile : « Anonymisation » s'y coupait en plein mot et
1359
+ * « Connecteur » perdait son r sur la ligne suivante. À deux, chaque tuile
1360
+ * dispose d'une centaine de pixels — assez pour son accroche, qui est
1361
+ * précisément ce dont un client a besoin sur le petit écran où il connaît
1362
+ * le moins la suite. Sept produits font alors quatre rangées, et le
1363
+ * panneau défile. */
1267
1364
  .zv-lanceur__apps {
1268
- grid-template-columns: repeat(3, minmax(0, 1fr));
1365
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1366
+ }
1367
+
1368
+ /* Quatre rangées dépassent la hauteur d'un téléphone : le panneau se
1369
+ * plafonne et défile, plutôt que de sortir sous le pli sans le dire. */
1370
+ .zv-lanceur__panneau .zv-menu {
1371
+ max-height: calc(100vh - 96px);
1372
+ overflow-y: auto;
1269
1373
  }
1270
1374
  }