@zevra/ui 0.22.0 → 0.24.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 +58 -0
- package/dist/catalogue.d.ts.map +1 -1
- package/dist/catalogue.js +30 -1
- package/dist/catalogue.js.map +1 -1
- package/dist/css-inline.d.ts.map +1 -1
- package/dist/css-inline.js +1 -1
- package/dist/css-inline.js.map +1 -1
- package/dist/icon-data.d.ts +1 -1
- package/dist/icon-data.d.ts.map +1 -1
- package/dist/icon-data.js +4 -0
- package/dist/icon-data.js.map +1 -1
- package/dist/layout.d.ts +69 -0
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +54 -0
- package/dist/layout.js.map +1 -1
- package/icons/index.json +12 -0
- package/icons/sprite.svg +2 -0
- package/package.json +1 -1
- package/react/catalogue.tsx +69 -1
- package/react/css-inline.ts +1 -1
- package/react/icon-data.ts +6 -0
- package/react/layout.tsx +183 -0
- package/src/layout.css +23 -0
- package/src/navigation.css +250 -0
package/react/icon-data.ts
CHANGED
|
@@ -6,6 +6,7 @@ export type IconId =
|
|
|
6
6
|
| 'activite'
|
|
7
7
|
| 'aide'
|
|
8
8
|
| 'app-couches'
|
|
9
|
+
| 'applications'
|
|
9
10
|
| 'archiver'
|
|
10
11
|
| 'attention'
|
|
11
12
|
| 'avancer'
|
|
@@ -30,6 +31,7 @@ export type IconId =
|
|
|
30
31
|
| 'diplome'
|
|
31
32
|
| 'dossier'
|
|
32
33
|
| 'eclair'
|
|
34
|
+
| 'empreinte'
|
|
33
35
|
| 'envoyer'
|
|
34
36
|
| 'epingle'
|
|
35
37
|
| 'equipe'
|
|
@@ -78,6 +80,7 @@ export const CORPS: Record<IconId, string> = {
|
|
|
78
80
|
'activite': "<path d=\"M3.33 12 H8 L10.67 5.33 L14 18.67 L16 12 H20.67\" />",
|
|
79
81
|
'aide': "<circle cx=\"12\" cy=\"12\" r=\"8.67\" /><path d=\"M9.33 9.33 A2.67 2.67 0 0 1 14.53 10.13 C14.53 11.87 12 12.27 12 14\" /><rect x=\"11.2\" y=\"16.27\" width=\"1.6\" height=\"1.6\" fill=\"currentColor\" stroke=\"none\" />",
|
|
80
82
|
'app-couches': "<path d=\"M12 2.67 L20 6.67 L12 10.67 L4 6.67 Z\" /><path d=\"M4 12 L12 16 L20 12\" /><path d=\"M4 17.33 L12 21.33 L20 17.33\" />",
|
|
83
|
+
'applications': "<rect x=\"4\" y=\"4\" width=\"6.67\" height=\"6.67\" fill=\"currentColor\" stroke=\"none\" /><rect x=\"13.33\" y=\"4\" width=\"6.67\" height=\"6.67\" fill=\"currentColor\" stroke=\"none\" /><rect x=\"4\" y=\"13.33\" width=\"6.67\" height=\"6.67\" fill=\"currentColor\" stroke=\"none\" /><rect x=\"13.33\" y=\"13.33\" width=\"6.67\" height=\"6.67\" fill=\"currentColor\" stroke=\"none\" />",
|
|
81
84
|
'archiver': "<rect x=\"3.33\" y=\"4\" width=\"17.33\" height=\"4.67\" /><path d=\"M5.33 8.67 V18.93 A1.07 1.07 0 0 0 6.4 20 H17.6 A1.07 1.07 0 0 0 18.67 18.93 V8.67\" /><path d=\"M9.33 12.67 H14.67\" />",
|
|
82
85
|
'attention': "<path d=\"M10.67 3.87 L2.53 17.87 A1.55 1.55 0 0 0 3.87 20.19 H20.13 A1.55 1.55 0 0 0 21.47 17.87 L13.33 3.87 A1.54 1.54 0 0 0 10.67 3.87 Z\" /><path d=\"M12 9.33 V13.33\" /><rect x=\"11.2\" y=\"15.73\" width=\"1.6\" height=\"1.6\" fill=\"currentColor\" stroke=\"none\" />",
|
|
83
86
|
'avancer': "<path d=\"M4 6 L11 12 L4 18 Z\" fill=\"currentColor\" stroke=\"none\" /><path d=\"M13 6 L20 12 L13 18 Z\" fill=\"currentColor\" stroke=\"none\" />",
|
|
@@ -102,6 +105,7 @@ export const CORPS: Record<IconId, string> = {
|
|
|
102
105
|
'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
106
|
'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
107
|
'eclair': "<path d=\"M13.33 2.67 L5.33 13.33 H12 L10.67 21.33 L18.67 10.67 H12 Z\" />",
|
|
108
|
+
'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
109
|
'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
110
|
'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
111
|
'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\" />",
|
|
@@ -153,6 +157,7 @@ export const NOMS: Record<IconId, string> = {
|
|
|
153
157
|
'activite': "Activité (pouls)",
|
|
154
158
|
'aide': "Aide",
|
|
155
159
|
'app-couches': "Application (couches)",
|
|
160
|
+
'applications': "Applications (lanceur)",
|
|
156
161
|
'archiver': "Archiver",
|
|
157
162
|
'attention': "Attention (avertissement)",
|
|
158
163
|
'avancer': "Avancer",
|
|
@@ -177,6 +182,7 @@ export const NOMS: Record<IconId, string> = {
|
|
|
177
182
|
'diplome': "Diplôme (Campus)",
|
|
178
183
|
'dossier': "Dossier",
|
|
179
184
|
'eclair': "Éclair (action rapide)",
|
|
185
|
+
'empreinte': "Empreinte (biométrie, passkey)",
|
|
180
186
|
'envoyer': "Envoyer",
|
|
181
187
|
'epingle': "Épingle (lieu)",
|
|
182
188
|
'equipe': "Équipe (membres)",
|
package/react/layout.tsx
CHANGED
|
@@ -13,6 +13,9 @@ import type {
|
|
|
13
13
|
ReactNode,
|
|
14
14
|
} from 'react'
|
|
15
15
|
import { cx } from './cx.js'
|
|
16
|
+
// `icons` est un module COMMUN aux deux couches : l'importer ici ne tire pas
|
|
17
|
+
// la couche applicative derrière la vitrine.
|
|
18
|
+
import { Icon } from './icon.js'
|
|
16
19
|
|
|
17
20
|
type Div = Omit<HTMLAttributes<HTMLDivElement>, 'className'>
|
|
18
21
|
type Span = Omit<HTMLAttributes<HTMLSpanElement>, 'className'>
|
|
@@ -593,3 +596,183 @@ export function Carousel({ label, className, ...reste }: CarouselProps) {
|
|
|
593
596
|
export function CarouselItem({ className, ...reste }: Div & { className?: string }) {
|
|
594
597
|
return <div className={cx('zv-carousel__item', className)} {...reste} />
|
|
595
598
|
}
|
|
599
|
+
|
|
600
|
+
/** La zone de droite de la barre : lanceur et compte.
|
|
601
|
+
*
|
|
602
|
+
* ⚠️ À poser À CÔTÉ de `NavLinks`, jamais DEDANS. `NavLinks` disparaît sous
|
|
603
|
+
* le point de rupture (et sous 1150px en `nav--riche`), remplacée par le
|
|
604
|
+
* burger : un compte placé dedans s'évanouit sur téléphone, et l'on ne peut
|
|
605
|
+
* plus se connecter — c'est pourtant la première chose qu'on y fait. */
|
|
606
|
+
export function NavActions({ className, ...reste }: Div & { className?: string }) {
|
|
607
|
+
return <div className={cx('zv-nav__actions', className)} {...reste} />
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/* ─── Lanceur d'applications ───────────────────────────────────────── */
|
|
611
|
+
|
|
612
|
+
/** Le lanceur : passer d'un produit Zevra à l'autre, depuis n'importe lequel.
|
|
613
|
+
* Il se pose À DROITE de la barre, à côté du compte — c'est la convention
|
|
614
|
+
* que tout le monde connaît, et ce n'est pas le moment d'en inventer une.
|
|
615
|
+
*
|
|
616
|
+
* Ouvert au survol et au focus en CSS seul : la barre reste rendue au
|
|
617
|
+
* serveur, sans rien à hydrater. Une app qui préfère l'ouverture au CLIC
|
|
618
|
+
* (la convention des lanceurs) tient l'état elle-même et passe `ouvert` —
|
|
619
|
+
* un état se dit en prop, il ne se peint pas depuis l'app. */
|
|
620
|
+
export function Lanceur({
|
|
621
|
+
label = 'Applications Zevra',
|
|
622
|
+
ouvert,
|
|
623
|
+
className,
|
|
624
|
+
children,
|
|
625
|
+
...reste
|
|
626
|
+
}: Div & { label?: string; ouvert?: boolean; className?: string; children?: ReactNode }) {
|
|
627
|
+
return (
|
|
628
|
+
<span className={cx('zv-lanceur', ouvert && 'zv-lanceur--ouvert', className)} {...reste}>
|
|
629
|
+
<button
|
|
630
|
+
type="button"
|
|
631
|
+
className="zv-lanceur__btn"
|
|
632
|
+
aria-label={label}
|
|
633
|
+
aria-haspopup="true"
|
|
634
|
+
aria-expanded={ouvert}
|
|
635
|
+
>
|
|
636
|
+
<Icon nom="applications" taille="lg" />
|
|
637
|
+
</button>
|
|
638
|
+
<span className="zv-lanceur__panneau">
|
|
639
|
+
<span className="zv-menu">
|
|
640
|
+
<span className="zv-lanceur__apps">{children}</span>
|
|
641
|
+
</span>
|
|
642
|
+
</span>
|
|
643
|
+
</span>
|
|
644
|
+
)
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
export interface LanceurAppProps
|
|
648
|
+
extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'className'> {
|
|
649
|
+
nom: string
|
|
650
|
+
/** Le glyphe du produit — une image du paquet (`@zevra/ui/logos/…`). Sans
|
|
651
|
+
* lui, la tuile porte l'initiale : une image qui ne charge pas ne doit
|
|
652
|
+
* pas laisser un trou. */
|
|
653
|
+
glyphe?: string
|
|
654
|
+
/** Le produit où l'on se trouve déjà : pose `aria-current="page"`. Sans
|
|
655
|
+
* lui, on ne sait pas d'où l'on part. */
|
|
656
|
+
courant?: boolean
|
|
657
|
+
className?: string
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
export function LanceurApp({ nom, glyphe, courant, className, ...reste }: LanceurAppProps) {
|
|
661
|
+
return (
|
|
662
|
+
<a
|
|
663
|
+
aria-current={courant ? 'page' : undefined}
|
|
664
|
+
className={cx('zv-lanceur__app', className)}
|
|
665
|
+
{...reste}
|
|
666
|
+
>
|
|
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
|
+
)}
|
|
674
|
+
<span className="zv-lanceur__nom">{nom}</span>
|
|
675
|
+
</a>
|
|
676
|
+
)
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/* ─── Zone de compte ───────────────────────────────────────────────── */
|
|
680
|
+
|
|
681
|
+
export interface CompteProps extends Div {
|
|
682
|
+
/** L'utilisateur connecté. ⚠️ ABSENT = visiteur : la zone existe quand
|
|
683
|
+
* même, avec l'invite de connexion. Une barre où l'on ne peut pas se
|
|
684
|
+
* connecter est une barre où l'on ne revient pas. */
|
|
685
|
+
utilisateur?: { nom: string; email?: string; initiales?: string }
|
|
686
|
+
/** Ouverture au clic, tenue par l'app. Voir `Lanceur`. */
|
|
687
|
+
ouvert?: boolean
|
|
688
|
+
label?: string
|
|
689
|
+
className?: string
|
|
690
|
+
/** Les entrées du panneau — des `CompteLien`. */
|
|
691
|
+
children?: ReactNode
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/** La zone de compte de la barre. Connecté, elle porte la pastille
|
|
695
|
+
* d'initiales et le menu ; visiteur, la silhouette et l'invite de connexion.
|
|
696
|
+
* Dans les DEUX cas elle est là : c'est ce qui fait qu'on sait toujours où
|
|
697
|
+
* se connecter.
|
|
698
|
+
*
|
|
699
|
+
* ⚠️ Le panneau n'est pas un `role="menu"` : ce rôle promet une navigation
|
|
700
|
+
* aux flèches que le CSS seul ne peut pas tenir. Ce sont des liens dans un
|
|
701
|
+
* panneau, atteignables par la tabulation — une promesse tenue vaut mieux
|
|
702
|
+
* qu'un rôle exact et menteur. */
|
|
703
|
+
export function Compte({
|
|
704
|
+
utilisateur,
|
|
705
|
+
ouvert,
|
|
706
|
+
label,
|
|
707
|
+
className,
|
|
708
|
+
children,
|
|
709
|
+
...reste
|
|
710
|
+
}: CompteProps) {
|
|
711
|
+
const initiales =
|
|
712
|
+
utilisateur?.initiales ??
|
|
713
|
+
utilisateur?.nom
|
|
714
|
+
.split(/[\s&]+/)
|
|
715
|
+
.slice(0, 2)
|
|
716
|
+
.map((m) => m.slice(0, 1).toUpperCase())
|
|
717
|
+
.join('')
|
|
718
|
+
|
|
719
|
+
return (
|
|
720
|
+
<span className={cx('zv-compte', ouvert && 'zv-compte--ouvert', className)} {...reste}>
|
|
721
|
+
<button
|
|
722
|
+
type="button"
|
|
723
|
+
className="zv-compte__btn"
|
|
724
|
+
aria-label={label ?? (utilisateur ? `Compte de ${utilisateur.nom}` : 'Se connecter')}
|
|
725
|
+
aria-haspopup="true"
|
|
726
|
+
aria-expanded={ouvert}
|
|
727
|
+
>
|
|
728
|
+
{utilisateur ? (
|
|
729
|
+
<span className="zv-avatar zv-avatar--accent" aria-hidden="true">
|
|
730
|
+
{initiales}
|
|
731
|
+
</span>
|
|
732
|
+
) : (
|
|
733
|
+
<span className="zv-avatar zv-avatar--soft" aria-hidden="true">
|
|
734
|
+
<Icon nom="utilisateur" taille="sm" />
|
|
735
|
+
</span>
|
|
736
|
+
)}
|
|
737
|
+
{utilisateur && <span className="zv-compte__nom">{utilisateur.nom}</span>}
|
|
738
|
+
</button>
|
|
739
|
+
<span className="zv-compte__panneau">
|
|
740
|
+
<span className="zv-menu">
|
|
741
|
+
<span className="zv-compte__tete">
|
|
742
|
+
<span className="zv-compte__tete-nom">
|
|
743
|
+
{utilisateur ? utilisateur.nom : 'Vous n’êtes pas connecté'}
|
|
744
|
+
</span>
|
|
745
|
+
{utilisateur?.email && (
|
|
746
|
+
<span className="zv-compte__tete-mail">{utilisateur.email}</span>
|
|
747
|
+
)}
|
|
748
|
+
</span>
|
|
749
|
+
{children}
|
|
750
|
+
</span>
|
|
751
|
+
</span>
|
|
752
|
+
</span>
|
|
753
|
+
)
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/** Une entrée du panneau de compte. Rendue en `<a>` avec `href`, en
|
|
757
|
+
* `<button>` sans — se déconnecter est une ACTION, pas une navigation, et
|
|
758
|
+
* un lien qui n'en est pas un se copie, s'ouvre dans un onglet et casse. */
|
|
759
|
+
export function CompteLien({
|
|
760
|
+
sortie,
|
|
761
|
+
className,
|
|
762
|
+
href,
|
|
763
|
+
...reste
|
|
764
|
+
}: Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'className'> & {
|
|
765
|
+
/** Détache l'entrée par un filet : la déconnexion, en pied de panneau. */
|
|
766
|
+
sortie?: boolean
|
|
767
|
+
className?: string
|
|
768
|
+
}) {
|
|
769
|
+
const classes = cx('zv-menu__item', sortie && 'zv-compte__sortie', className)
|
|
770
|
+
if (href) return <a href={href} className={classes} {...reste} />
|
|
771
|
+
return (
|
|
772
|
+
<button
|
|
773
|
+
type="button"
|
|
774
|
+
className={classes}
|
|
775
|
+
{...(reste as Omit<HTMLAttributes<HTMLButtonElement>, 'className'>)}
|
|
776
|
+
/>
|
|
777
|
+
)
|
|
778
|
+
}
|
package/src/layout.css
CHANGED
|
@@ -236,6 +236,29 @@
|
|
|
236
236
|
text-decoration: none;
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
+
/* ─── Zone d'actions de la barre ────────────────────────────────────────
|
|
240
|
+
* Le lanceur et le compte, à droite. Elle ne fait PAS partie de
|
|
241
|
+
* `.zv-nav__links` et c'est tout l'intérêt : cette rangée-là disparaît sous
|
|
242
|
+
* le point de rupture (et sous 1150px pour `.zv-nav--riche`), remplacée par
|
|
243
|
+
* le burger. Le compte, lui, doit rester — sinon on ne peut plus se
|
|
244
|
+
* connecter depuis un téléphone, et c'est la première chose qu'on y fait.
|
|
245
|
+
*
|
|
246
|
+
* Elle se pose APRÈS `.zv-nav__links` dans le balisage, et avant le burger. */
|
|
247
|
+
.zv-nav__actions {
|
|
248
|
+
display: flex;
|
|
249
|
+
align-items: center;
|
|
250
|
+
gap: 2px;
|
|
251
|
+
flex: none;
|
|
252
|
+
/* Décolle du dernier lien de la rangée quand les deux coexistent. */
|
|
253
|
+
margin-left: 10px;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
@media (max-width: 767px) {
|
|
257
|
+
.zv-nav__actions {
|
|
258
|
+
margin-left: 0;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
239
262
|
/* Bouton burger de la barre vitrine.
|
|
240
263
|
*
|
|
241
264
|
* ⚠️ HORS RELEVÉ des planches vitrine, qui sont toutes des rendus figés à
|
package/src/navigation.css
CHANGED
|
@@ -450,6 +450,16 @@
|
|
|
450
450
|
background: var(--hairline);
|
|
451
451
|
}
|
|
452
452
|
|
|
453
|
+
/* ⚠️ CONTRE-ORDRE. Une entrée de menu rendue en <a> (une navigation, par
|
|
454
|
+
* opposition à une action) hérite du soulignement par défaut du navigateur,
|
|
455
|
+
* là où la même entrée en <button> n'a rien : dans un même panneau,
|
|
456
|
+
* « Mon compte » se soulignait et « Se déconnecter » non. Les deux doivent
|
|
457
|
+
* se rendre à l'identique — c'est la BALISE qui change, pas l'apparence. */
|
|
458
|
+
a.zv-menu__item,
|
|
459
|
+
a.zv-menu__item:hover {
|
|
460
|
+
text-decoration: none;
|
|
461
|
+
}
|
|
462
|
+
|
|
453
463
|
/* ⚠️ Option SURVOLÉE AU CLAVIER — ajoutée le 25/08/2026 avec la sélection
|
|
454
464
|
* dessinée. Ce n'est ni :hover (la souris n'est pas dessus) ni :focus (le
|
|
455
465
|
* focus reste sur le déclencheur, c'est aria-activedescendant qui porte
|
|
@@ -1018,3 +1028,243 @@
|
|
|
1018
1028
|
min-height: var(--tap);
|
|
1019
1029
|
}
|
|
1020
1030
|
}
|
|
1031
|
+
|
|
1032
|
+
/* ─── Lanceur d'applications, et zone de compte ─────────────────────────
|
|
1033
|
+
*
|
|
1034
|
+
* ⚠️ CE BLOC AUSSI DOIT RESTER APRÈS `.zv-menu`, pour la même raison que
|
|
1035
|
+
* `.zv-navdd` : les deux panneaux portent `.zv-menu` et lui reprennent son
|
|
1036
|
+
* display et son ancrage à spécificité égale — c'est l'ORDRE qui tranche.
|
|
1037
|
+
*
|
|
1038
|
+
* Les deux vivent À DROITE de la barre, côte à côte : le lanceur pour passer
|
|
1039
|
+
* d'un produit à l'autre, le compte pour savoir qui l'on est. C'est la
|
|
1040
|
+
* convention que tout le monde connaît, et ce n'est pas le moment d'en
|
|
1041
|
+
* inventer une autre.
|
|
1042
|
+
*
|
|
1043
|
+
* ─── L'ÉTAT ────────────────────────────────────────────────────────────
|
|
1044
|
+
* Ouverts au survol ET au focus (`:focus-within`), en CSS seul : la barre
|
|
1045
|
+
* reste rendue au serveur, sans une ligne à hydrater — même mécanique que
|
|
1046
|
+
* `.zv-navdd`. Une app qui préfère l'ouverture au CLIC (la convention des
|
|
1047
|
+
* lanceurs) pose `.zv-lanceur--ouvert` / `.zv-compte--ouvert` : la prop
|
|
1048
|
+
* `ouvert` du composant les rend. C'est la règle du dépôt — un état se dit
|
|
1049
|
+
* en prop, il ne se peint pas depuis l'app.
|
|
1050
|
+
*/
|
|
1051
|
+
|
|
1052
|
+
.zv-lanceur,
|
|
1053
|
+
.zv-compte {
|
|
1054
|
+
position: relative;
|
|
1055
|
+
display: inline-flex;
|
|
1056
|
+
flex: none;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
/* Le déclencheur du lanceur : la cible tactile du paquet, sans cadre — le
|
|
1060
|
+
* chrome n'est pas une action. */
|
|
1061
|
+
.zv-lanceur__btn {
|
|
1062
|
+
display: inline-flex;
|
|
1063
|
+
align-items: center;
|
|
1064
|
+
justify-content: center;
|
|
1065
|
+
width: var(--tap);
|
|
1066
|
+
height: var(--tap);
|
|
1067
|
+
padding: 0;
|
|
1068
|
+
border: 0;
|
|
1069
|
+
background: none;
|
|
1070
|
+
color: var(--ink-2);
|
|
1071
|
+
cursor: pointer;
|
|
1072
|
+
transition: color 140ms var(--zv-easing, cubic-bezier(0.2, 0.7, 0.2, 1));
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
.zv-lanceur:hover .zv-lanceur__btn,
|
|
1076
|
+
.zv-lanceur:focus-within .zv-lanceur__btn {
|
|
1077
|
+
color: var(--accent);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
/* Le déclencheur du compte : la pastille d'initiales, cliquable. Elle garde
|
|
1081
|
+
* son carré — le rond ici serait un usage de réseau social, et la pastille
|
|
1082
|
+
* d'initiales du paquet est carrée depuis le premier jour (data.css). */
|
|
1083
|
+
.zv-compte__btn {
|
|
1084
|
+
display: inline-flex;
|
|
1085
|
+
align-items: center;
|
|
1086
|
+
gap: 8px;
|
|
1087
|
+
padding: 0;
|
|
1088
|
+
border: 0;
|
|
1089
|
+
background: none;
|
|
1090
|
+
font-family: inherit;
|
|
1091
|
+
cursor: pointer;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
/* Le nom, à côté de la pastille. Il tombe sous le point de rupture : une
|
|
1095
|
+
* barre de téléphone n'a pas la place d'un nom, et la pastille suffit. */
|
|
1096
|
+
.zv-compte__nom {
|
|
1097
|
+
font-size: var(--app);
|
|
1098
|
+
font-weight: 500;
|
|
1099
|
+
color: var(--ink-2);
|
|
1100
|
+
white-space: nowrap;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
/* ─── Les deux panneaux ─────────────────────────────────────────────────
|
|
1104
|
+
* Ancrés à DROITE et non centrés : ils vivent au bord droit de la fenêtre,
|
|
1105
|
+
* et un panneau centré sur son bouton en sortirait. Le pont invisible
|
|
1106
|
+
* (::before) couvre l'interstice, sans quoi le survol se rompt en descendant
|
|
1107
|
+
* vers le panneau et celui-ci se referme sous le curseur. */
|
|
1108
|
+
|
|
1109
|
+
.zv-lanceur__panneau,
|
|
1110
|
+
.zv-compte__panneau {
|
|
1111
|
+
position: absolute;
|
|
1112
|
+
top: 100%;
|
|
1113
|
+
right: 0;
|
|
1114
|
+
left: auto;
|
|
1115
|
+
translate: none;
|
|
1116
|
+
padding-top: 12px;
|
|
1117
|
+
display: none;
|
|
1118
|
+
z-index: 60;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/* ⚠️ Le `.zv-menu` intérieur est LUI-MÊME en position absolue (il est fait
|
|
1122
|
+
* pour s'ancrer seul sous un déclencheur). Imbriqué dans un panneau qui
|
|
1123
|
+
* porte déjà l'ancrage, il se positionnait une SECONDE fois — à partir du
|
|
1124
|
+
* bord droit du panneau, donc vers la droite, et la grille sortait de
|
|
1125
|
+
* l'écran sur téléphone. Sur grand écran la place restante le masquait :
|
|
1126
|
+
* c'est le rendu à 390px qui l'a montré. Ici le panneau ancre, le menu ne
|
|
1127
|
+
* fournit plus que sa surface (filet, fond, ombre). */
|
|
1128
|
+
.zv-lanceur__panneau .zv-menu,
|
|
1129
|
+
.zv-compte__panneau .zv-menu {
|
|
1130
|
+
position: static;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
.zv-lanceur__panneau::before,
|
|
1134
|
+
.zv-compte__panneau::before {
|
|
1135
|
+
content: '';
|
|
1136
|
+
position: absolute;
|
|
1137
|
+
inset: 0 0 auto 0;
|
|
1138
|
+
height: 12px;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
.zv-lanceur:hover .zv-lanceur__panneau,
|
|
1142
|
+
.zv-lanceur:focus-within .zv-lanceur__panneau,
|
|
1143
|
+
.zv-lanceur--ouvert .zv-lanceur__panneau,
|
|
1144
|
+
.zv-compte:hover .zv-compte__panneau,
|
|
1145
|
+
.zv-compte:focus-within .zv-compte__panneau,
|
|
1146
|
+
.zv-compte--ouvert .zv-compte__panneau {
|
|
1147
|
+
display: flex;
|
|
1148
|
+
flex-direction: column;
|
|
1149
|
+
}
|
|
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. */
|
|
1154
|
+
|
|
1155
|
+
.zv-lanceur__apps {
|
|
1156
|
+
display: grid;
|
|
1157
|
+
grid-template-columns: repeat(3, 92px);
|
|
1158
|
+
gap: 4px;
|
|
1159
|
+
padding: 6px;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
.zv-lanceur__app {
|
|
1163
|
+
display: flex;
|
|
1164
|
+
flex-direction: column;
|
|
1165
|
+
align-items: center;
|
|
1166
|
+
gap: 8px;
|
|
1167
|
+
padding: 14px 6px;
|
|
1168
|
+
text-align: center;
|
|
1169
|
+
text-decoration: none;
|
|
1170
|
+
color: var(--ink-2);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
/* Même contre-ordre que .zv-sidelink : sans lui, la tuile vire au bleu
|
|
1174
|
+
* souligné dans une page qui applique le style de lien éditorial. */
|
|
1175
|
+
.zv-lanceur__app:hover {
|
|
1176
|
+
background: var(--hairline);
|
|
1177
|
+
color: var(--ink-2);
|
|
1178
|
+
text-decoration: none;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
.zv-lanceur__app:focus-visible {
|
|
1182
|
+
outline-offset: -2px;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
/* L'application COURANTE reste marquée : sans elle, on ne sait pas d'où
|
|
1186
|
+
* l'on part. aria-current="page" est le marquage attendu ; la classe existe
|
|
1187
|
+
* pour que l'état se pose aussi sans routeur. */
|
|
1188
|
+
.zv-lanceur__app--on,
|
|
1189
|
+
.zv-lanceur__app[aria-current='page'] {
|
|
1190
|
+
background: var(--accent-soft);
|
|
1191
|
+
color: var(--accent);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
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. */
|
|
1196
|
+
.zv-lanceur__glyphe {
|
|
1197
|
+
display: block;
|
|
1198
|
+
width: 28px;
|
|
1199
|
+
height: 28px;
|
|
1200
|
+
object-fit: contain;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
.zv-lanceur__nom {
|
|
1204
|
+
font-size: var(--app-sm);
|
|
1205
|
+
font-weight: 500;
|
|
1206
|
+
line-height: 1.3;
|
|
1207
|
+
/* Un nom de produit trop long ne doit pas élargir la colonne : il se coupe
|
|
1208
|
+
* plutôt que de déformer la grille. */
|
|
1209
|
+
overflow-wrap: anywhere;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
/* ─── L'en-tête du panneau de compte ────────────────────────────────────
|
|
1213
|
+
* Qui l'on est, avant ce qu'on peut faire. Non connecté, il porte l'invite
|
|
1214
|
+
* de connexion : la zone de compte existe TOUJOURS, connecté ou non — une
|
|
1215
|
+
* barre où l'on ne peut pas se connecter est une barre où l'on ne revient
|
|
1216
|
+
* pas. */
|
|
1217
|
+
|
|
1218
|
+
.zv-compte__tete {
|
|
1219
|
+
display: flex;
|
|
1220
|
+
flex-direction: column;
|
|
1221
|
+
gap: 2px;
|
|
1222
|
+
border-bottom: 1px solid var(--hairline);
|
|
1223
|
+
padding: 12px 14px;
|
|
1224
|
+
margin-bottom: 4px;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
.zv-compte__tete-nom {
|
|
1228
|
+
font-size: var(--app);
|
|
1229
|
+
font-weight: 600;
|
|
1230
|
+
color: var(--ink);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
.zv-compte__tete-mail {
|
|
1234
|
+
font-family: var(--font-mono);
|
|
1235
|
+
font-size: var(--mono);
|
|
1236
|
+
color: var(--label);
|
|
1237
|
+
overflow-wrap: anywhere;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
.zv-compte__panneau .zv-menu {
|
|
1241
|
+
min-width: 240px;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
/* Le pied du panneau : la déconnexion, séparée de ce qui précède. Elle ne
|
|
1245
|
+
* prend PAS l'encre d'erreur — se déconnecter n'est pas un incident. */
|
|
1246
|
+
.zv-compte__sortie {
|
|
1247
|
+
border-top: 1px solid var(--hairline);
|
|
1248
|
+
margin-top: 4px;
|
|
1249
|
+
padding-top: 4px;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
@media (max-width: 767px) {
|
|
1253
|
+
/* Le nom tombe : la pastille suffit à dire qui l'on est, et la barre n'a
|
|
1254
|
+
* plus la place. */
|
|
1255
|
+
.zv-compte__nom {
|
|
1256
|
+
display: none;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
/* Les deux panneaux sont plafonnés à l'écran, comme le menu et le
|
|
1260
|
+
* popover : au bord droit d'un téléphone, un panneau de 300px pousserait
|
|
1261
|
+
* la page en défilement horizontal. */
|
|
1262
|
+
.zv-lanceur__panneau .zv-menu,
|
|
1263
|
+
.zv-compte__panneau .zv-menu {
|
|
1264
|
+
max-width: calc(100vw - 32px);
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
.zv-lanceur__apps {
|
|
1268
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
1269
|
+
}
|
|
1270
|
+
}
|