@zevra/ui 0.50.0 → 0.51.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 +83 -0
- 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/overlays.d.ts.map +1 -1
- package/dist/overlays.js +9 -3
- package/dist/overlays.js.map +1 -1
- package/dist/partenaires-data.d.ts +8 -0
- package/dist/partenaires-data.d.ts.map +1 -0
- package/dist/partenaires-data.js +11 -0
- package/dist/partenaires-data.js.map +1 -0
- package/dist/partenaires.d.ts +29 -0
- package/dist/partenaires.d.ts.map +1 -0
- package/dist/partenaires.js +29 -0
- package/dist/partenaires.js.map +1 -0
- package/dist/select-menu.d.ts.map +1 -1
- package/dist/select-menu.js +66 -5
- package/dist/select-menu.js.map +1 -1
- package/logos/partenaires/docusign.webp +0 -0
- package/logos/partenaires/google.svg +1 -0
- package/logos/partenaires/henrri.webp +0 -0
- package/logos/partenaires/index.json +7 -0
- package/logos/partenaires/maileva.webp +0 -0
- package/logos/partenaires/microsoft.svg +1 -0
- package/package.json +6 -2
- package/react/css-inline.ts +1 -1
- package/react/overlays.tsx +9 -3
- package/react/partenaires-data.ts +18 -0
- package/react/partenaires.tsx +55 -0
- package/react/select-menu.tsx +71 -5
- package/src/forms.css +18 -3
- package/src/icons.css +33 -0
- package/src/tokens.css +13 -1
- package/tokens/index.js +3 -0
package/react/overlays.tsx
CHANGED
|
@@ -66,8 +66,14 @@ export function useOverlay({ ouvert, onClose, panneau, verrouille = true }: UseO
|
|
|
66
66
|
|
|
67
67
|
// ⚠️ La valeur PRÉCÉDENTE est restaurée, pas remise à '' : une page qui
|
|
68
68
|
// gérait déjà son propre défilement serait cassée par un reset aveugle.
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
// ⚠️ Sur `html`, JAMAIS sur `body` (DECISIONS, 03/11/2026) : overlays.css
|
|
70
|
+
// verrouille déjà `html` ; un `overflow` de plus sur `body` ne remonte
|
|
71
|
+
// alors plus au viewport, `body` devient un conteneur de défilement à
|
|
72
|
+
// zéro, et toute colonne `sticky` de la page saute en haut de `body`,
|
|
73
|
+
// hors de l'écran.
|
|
74
|
+
const racine = docu.documentElement
|
|
75
|
+
const overflowPrecedent = verrouille ? racine.style.overflow : null
|
|
76
|
+
if (verrouille) racine.style.overflow = 'hidden'
|
|
71
77
|
|
|
72
78
|
const premier = cible?.querySelector<HTMLElement>(FOCUSABLES)
|
|
73
79
|
premier?.focus()
|
|
@@ -99,7 +105,7 @@ export function useOverlay({ ouvert, onClose, panneau, verrouille = true }: UseO
|
|
|
99
105
|
docu.addEventListener('keydown', onKey)
|
|
100
106
|
return () => {
|
|
101
107
|
docu.removeEventListener('keydown', onKey)
|
|
102
|
-
if (overflowPrecedent !== null)
|
|
108
|
+
if (overflowPrecedent !== null) racine.style.overflow = overflowPrecedent
|
|
103
109
|
origine.current?.focus?.()
|
|
104
110
|
}
|
|
105
111
|
}, [ouvert, panneau, verrouille])
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// GÉNÉRÉ par scripts/build-partenaires-data.mjs : ne pas éditer à la main.
|
|
2
|
+
// Source : logos/partenaires/ (provenance de chaque fichier dans index.json).
|
|
3
|
+
|
|
4
|
+
export type PartenaireId =
|
|
5
|
+
| 'docusign'
|
|
6
|
+
| 'google'
|
|
7
|
+
| 'henrri'
|
|
8
|
+
| 'maileva'
|
|
9
|
+
| 'microsoft'
|
|
10
|
+
|
|
11
|
+
/** Les logos des services partenaires, inlinés, et le nom de la marque. */
|
|
12
|
+
export const LOGOS_PARTENAIRES: Record<PartenaireId, { src: string; nom: string; plein: boolean }> = {
|
|
13
|
+
'docusign': { src: 'data:image/webp;base64,UklGRkgFAABXRUJQVlA4WAoAAAAQAAAAXwAAXwAAQUxQSA8CAAABoETbtmnbmXvv+227btu2bf9ftq3U0lKybZRSYylF27ad3Hv3WjPO+efPOGktIiYAxUfMetGcZbRPXnnw6n2TmgNIEeUNaPMajSWuvXb9qrZACqWJGFJzL4m75UySb1/QC0jlGfAty/Kz7jmTn13YGTGUZeB35fqpZfLNTUCSRXomb+uIpIu0zOeHIgkj6/xgBirKmFlbjqSM5t+OQVJG4/t9EJUx8+GmMShj5qVI0pg5H0ma+ZPNY1DGzH1I0szfax+CMmYeQZJm/kKTEJQx+5JQ0ca7EaQ5P+kGacxcJ67Oy4AQC67EwQKcT1Uiiu8p4cue6DhkYLGDB6ysCXBfOuf1774t+JsqBWYefJfSM6+jefEKjPc6pTufproP5X0p7q/Q78Q535P3gjjjQ+IybxVX5/nijFu1OasT1b3bSpvxQWir85g2d07XZnylubbMSxClmU1CUmZ8pEEIyjK3IUGY+TvtQlCWuQsJwsyfbBKDMM+chwRhmTcgBmHGl9rHAF1uX49Bgi4zLkOCLjfuRIKuTG5Hgiyv87NlqECVZ/LhQUgQZZn84kQTJMhxN8vZyeoN/YEIPb/4+Hm9gRQgqPb5u0/fc2h8QyBG/FYFzuqqnj0iAKSI367h254AYkoBRYoYHCsBRYsYiIj/bXIJ3ww4G3QBXh1yNh6nnXPG19ogFDfyUfdzzO3FWYgoHgBWUDggEgMAAPAVAJ0BKmAAYAA+MRaJQqIhIRgLHHAgAwSxAGk3AL9J/ADaEepfhb+0n+k5JvurythyOoP8z+QH9m+iHor8Rf/DfZL3APMB+nP7K9iD0AP1262b0AP2j9Kz2Mf29/4f/A+Af9c//Q0gNJ3MG8aGoH+qPoAdSB+laEZgR3iv2AwhLHYdOsaluNDFnQs241z2Fa2xKZeHggqLHWCUIMBqnZhLegQGZzHLVNkmuG7OTFK3HtKDTGh1hIAA/v7usEQurJYDnzZuaEty0/pLuG8V+gCnix0ijD36wHNsySlro1acCpUC+MSWl0HIEUAQwJJBGeStHf/b/90fKi/dlvxq0s/Oh3p/zG8b4f6gf2yWdCpOX67m20mGgL/Qh/Dd72O8/jIvkRrOosvlsgyLJ5Hutr/A/n1RiWIUACnzAmzS2JCQOxy3JC8j+qT4ct2BGmy0j/vSpG6/1UBazxDlbu839JqhBeVpIoEoOlMz9DAXp/C70ApMAUY3PE64Ya7XYcb523P7yKke+cYCpaKC3uYzK0HIncy5Pszu+rH1zNhFOtcPdBAj1tZfRLImjY4cKx4WKythwc7NPXiZ3apAytZ1/T7Wuq1SwE7h5ZDhNwiKT/UHCUeo6jWVwLT3QfXbtRVBr5zGOhiHi2KsvHafx/JAMRXUPDAjaKTc2mYuTOVIMH7pym1qbzgIIC1uAsMdkWoLsIVRJa47UrYL1+gX149HTyTVSgV0xhCqJn2pz5Pg7Br85XWPW8IQK78lKaYfqL0UCq9g+bB2l1lCq3rJWcDRwA+EFVV/jVZ7xdPGdpyWA0UL5EO3wmlQt3Qbi5ONgtnXobZwFWsHY3lWlHPYgBWOBWXx4DoT31P2So9qTu8Ab2hOq9UZdinjJg1b4g/f/gfWpNMxnpYyhKB5ITQmj8aPYokyqInXP/ydHbOrJebrwcuZ9JD/SIs+dfv+mY5X8sJW/ApFjv/u0EQoDA3HsxHwx7pJbo8+IsMz8Ij3ut/ZQkTTiQsNDkvLTQnCSyiU/2rHYQz4GFWLMTG5qFZVrNFRq2AAAA==', nom: "DocuSign", plein: false },
|
|
14
|
+
'google': { src: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI0Ij48cGF0aCBkPSJNMjIuNTYgMTIuMjVjMC0uNzgtLjA3LTEuNTMtLjItMi4yNUgxMnY0LjI2aDUuOTJjLS4yNiAxLjM3LTEuMDQgMi41My0yLjIxIDMuMzF2Mi43N2gzLjU3YzIuMDgtMS45MiAzLjI4LTQuNzQgMy4yOC04LjA5eiIgZmlsbD0iIzQyODVGNCIvPjxwYXRoIGQ9Ik0xMiAyM2MyLjk3IDAgNS40Ni0uOTggNy4yOC0yLjY2bC0zLjU3LTIuNzdjLS45OC42Ni0yLjIzIDEuMDYtMy43MSAxLjA2LTIuODYgMC01LjI5LTEuOTMtNi4xNi00LjUzSDIuMTh2Mi44NEMzLjk5IDIwLjUzIDcuNyAyMyAxMiAyM3oiIGZpbGw9IiMzNEE4NTMiLz48cGF0aCBkPSJNNS44NCAxNC4wOWMtLjIyLS42Ni0uMzUtMS4zNi0uMzUtMi4wOXMuMTMtMS40My4zNS0yLjA5VjcuMDdIMi4xOEMxLjQzIDguNTUgMSAxMC4yMiAxIDEycy40MyAzLjQ1IDEuMTggNC45M2wyLjg1LTIuMjIuODEtLjYyeiIgZmlsbD0iI0ZCQkMwNSIvPjxwYXRoIGQ9Ik0xMiA1LjM4YzEuNjIgMCAzLjA2LjU2IDQuMjEgMS42NGwzLjE1LTMuMTVDMTcuNDUgMi4wOSAxNC45NyAxIDEyIDEgNy43IDEgMy45OSAzLjQ3IDIuMTggNy4wN2wzLjY2IDIuODRjLjg3LTIuNiAzLjMtNC41MyA2LjE2LTQuNTN6IiBmaWxsPSIjRUE0MzM1Ii8+PHBhdGggZD0iTTEgMWgyMnYyMkgxeiIgZmlsbD0ibm9uZSIvPjwvc3ZnPg==', nom: "Google Workspace", plein: false },
|
|
15
|
+
'henrri': { src: 'data:image/webp;base64,UklGRrgFAABXRUJQVlA4IKwFAACQHgCdASpgAGAAPjEWiUMiISEXiPTYIAMEsgBqYri/APxu7lyn/Wvxp/LH5i63/PvvZyasu+Z14h+e/5/7dPrN6DPsr9wD9O/8H1APMB+sv7Te0f+rnuA/Tv2AP6Z/lPWA9SP+sf7f2AP1h9NX9mfg3/b39xvadvGT8HysiQ37H/oeH/YR3orIXET2zHIBfSP8Z52vP00bPQvsI/yX+vb6ahQciI39yrPWkj5LEo8aImSVYgK7JqT+zpAddnCZPSLMXeOmYMszqDj62U8IhaM3INGjflWeF2bD83ozTB6i0tRgp2/ZbCnsvFFJaBUW4VW3ptUkW2uAaQ9s8bgAAP7xTYVq77cxtvoUT8iX8iX8oEWUOLh+Tf4FFYPh9CJ/rcey/1W+9K3RmjH0Q2ToquREs5KySoANutbkzjl4dZrpCpHdkcoXQuKfZe10q0MVoS9xFefQ1VogpVds4908KQ4G1AZHX5PjjP+4mMp08dO3f21xrpfsHeC5GA1X+xZgUeulXVaH6PnTTxMbOf+08tvEuViAib0GCa3gSideogTUymOtQXKjDSRKjeIt0DoVOpgMnBn9BeFPjrF1hnBkaV32SaiI2gBc3HUM2gll+USLRnJbZ97R7xnaKD8nkZ2+XGIGeQQzhuD4RNVNpfD/rEKuzDwqAmc5KCu7GGL/Vi22thly5L2NSBem5b1qs1HEz68Oa++gqBG0zTO4JN1hkfJtP/aKxQCurZSx+R/+/9L3on//427rufZ/pqqwDjYprGeexcST5Me+Re/T+9so12oPxTDlUXaiHqGid1o1dbwaJflAE6VaWVuXBZjycDLnjaQkBz/APqfkHfJQnb1IycGnrR4V7MCZl3/z09VjDu3a1yRTZsP3Ut73QYiV4KFvNqpj7ty70rk3u2IYc68Mn8PKOp1P842CSGgRvMDuWDFiA6w88G1SdVOTCHqsB0Q7L0T9/y09//TvNfv3wgvMGTw1DxUOMekHK+wj3s3XMSUJjV9gI3uL9ToNbMIQHF/ENmTj9Ftf88prMgB1//9nBo0eFFe++l0j54Q84D2+ZyYkY2I2ylLOryhVwCs/cCGYlszPE2fHJpJAGcxWigRKlw+UEQpuu3GEsGAphOD3q/sR9xs0x5IbvwNblE8LeFUx2i/0KDtQtXXxdBa+BXnW09zk1WXAOXRmeDD9ukzB1KlXKOujMD5+ZclfXirEcfnQ2OzH+Qdyg6QzolQTdMnmlEbsDmP/EVpoAaFWnHldj26VvH3FbXR1bwdl5rmdKXfDbLxTDEi0dvvJl9LCyW5wA9aNBt1r+K9/bPCUw1Fqmo/HKZPSrid0kCYDG08EZnW74/Fxxb8DTNzjo0FMAT0GLruQg30oMCfQtRfQnqAGxzvc+eLqTylsC96DHmcl0bvON02eR1yNlHdE05/Svnk3oq+3XF6rED/8ddsVQuherFVxnbQGtCdb7clCJBadVNZo6R2G91R/8S4k68URD/pS7lyS6Plxaij69rWRnwnVE3LgZywwsLH264BZsyz05nfl7fyQdgnq3dRGwfNCE0xELt/VvvAKwhlantrqrh5yBIINoRZFlKeXgkT0To5Z5/N5LSOuw0dO0KDv2MK/FVcFQC9sq0oEl/MPT7EXzqKcN3SvaYGHVM6ed6Wh9I3YFh3MZu6YEa30GmI5fY7AwKRsKEPjEgwGX3k++Wk5rwi5mIMWwNS49XG1zwC8mbUG5R+WMnn4Yc/cIWsDjcwqLOTHb42zuk6+OSzBVVKYvnSh5KL2gmN+RdDCqmF6mbdNPEFB9KXh39TE1NYKWJ1QRF2PLEYALWOvrHuFbgZ72YihavKilRZ1dynXEegZITqWnR+J1ZMQbpv2km6OPbJG5dChRj48a8WITagCZgoRaYMi3mrkrvYHB4QXYmxrkSGYhhnTyOn+zvAK7kMPiwAAAAA=', nom: "Henrri", plein: true },
|
|
16
|
+
'maileva': { src: 'data:image/webp;base64,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', nom: "Maileva", plein: false },
|
|
17
|
+
'microsoft': { src: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMyAyMyI+PHBhdGggZmlsbD0iI2YzZjNmMyIgZD0iTTAgMGgyM3YyM0gweiIvPjxwYXRoIGZpbGw9IiNmMzUzMjUiIGQ9Ik0xIDFoMTB2MTBIMXoiLz48cGF0aCBmaWxsPSIjODFiYzA2IiBkPSJNMTIgMWgxMHYxMEgxMnoiLz48cGF0aCBmaWxsPSIjMDVhNmYwIiBkPSJNMSAxMmgxMHYxMEgxeiIvPjxwYXRoIGZpbGw9IiNmZmJhMDgiIGQ9Ik0xMiAxMmgxMHYxMEgxMnoiLz48L3N2Zz4=', nom: "Microsoft 365", plein: false },
|
|
18
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Les logos des services qu'une app branche : Microsoft 365, Google
|
|
2
|
+
// Workspace, DocuSign, Henrri, Maileva. Premier usage : la Marketplace de
|
|
3
|
+
// Tésope (03/11/2026), où chaque extension se reconnaît à sa marque.
|
|
4
|
+
//
|
|
5
|
+
// ⚠️ ENTRÉE SÉPARÉE (`@zevra/ui/react/partenaires`) : les logos y sont
|
|
6
|
+
// inlinés, le barrel React n'en porte pas un octet. Voir
|
|
7
|
+
// scripts/build-partenaires-data.mjs.
|
|
8
|
+
//
|
|
9
|
+
// Ce sont les marques des ÉDITEURS, en couleur, telles qu'ils les publient :
|
|
10
|
+
// on ne les recolore pas, on ne les redessine pas. Un service sans logo ici
|
|
11
|
+
// garde son `IconTile` : on n'invente pas de marque.
|
|
12
|
+
|
|
13
|
+
import { cx } from './cx.js'
|
|
14
|
+
import { LOGOS_PARTENAIRES, type PartenaireId } from './partenaires-data.js'
|
|
15
|
+
|
|
16
|
+
export type { PartenaireId }
|
|
17
|
+
|
|
18
|
+
/** Les services dont le paquet porte le logo. */
|
|
19
|
+
export const PARTENAIRES = Object.keys(LOGOS_PARTENAIRES) as PartenaireId[]
|
|
20
|
+
|
|
21
|
+
/** Le paquet a-t-il le logo de ce service ? Sinon, l'app garde son `IconTile`. */
|
|
22
|
+
export const aUnLogo = (id: string): id is PartenaireId => id in LOGOS_PARTENAIRES
|
|
23
|
+
|
|
24
|
+
export interface LogoPartenaireProps {
|
|
25
|
+
/** Un service dont le paquet porte le logo. */
|
|
26
|
+
id?: PartenaireId
|
|
27
|
+
/**
|
|
28
|
+
* Une image du paquet À LA PLACE d'un service partenaire : le glyphe d'un
|
|
29
|
+
* produit Zevra (`GLYPHES.memoire` de `@zevra/ui/glyphes`) quand le
|
|
30
|
+
* service branché est l'un des nôtres. Jamais une image de l'app : un logo
|
|
31
|
+
* se range dans le paquet, pas dans un public/.
|
|
32
|
+
*/
|
|
33
|
+
src?: string
|
|
34
|
+
/** Avec `src` : l'image couvre toute la tuile (icône d'application à fond plein). */
|
|
35
|
+
plein?: boolean
|
|
36
|
+
/** Nom lu par un lecteur d'écran. Par défaut, celui de la marque. Vide : décoratif (le nom est écrit à côté). */
|
|
37
|
+
titre?: string
|
|
38
|
+
className?: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* La tuile d'un service : même gabarit que `IconTile` (40×40), fond blanc
|
|
43
|
+
* au filet, le logo au centre. Une icône d'application à fond plein
|
|
44
|
+
* (Henrri) occupe toute la tuile.
|
|
45
|
+
*/
|
|
46
|
+
export function LogoPartenaire({ id, src, plein, titre, className }: LogoPartenaireProps) {
|
|
47
|
+
const l = id ? LOGOS_PARTENAIRES[id] : { src: src ?? '', nom: '', plein: Boolean(plein) }
|
|
48
|
+
const alt = titre ?? l.nom
|
|
49
|
+
return (
|
|
50
|
+
<span className={cx('zv-icon-tile', 'zv-icon-tile--logo', l.plein && 'zv-icon-tile--logo-plein', className)}
|
|
51
|
+
role={alt ? 'img' : undefined} aria-label={alt || undefined} aria-hidden={alt ? undefined : true}>
|
|
52
|
+
<img src={l.src} alt="" />
|
|
53
|
+
</span>
|
|
54
|
+
)
|
|
55
|
+
}
|
package/react/select-menu.tsx
CHANGED
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
// liste système est plus rapide à manipuler que n'importe quelle liste
|
|
21
21
|
// maison, et où le clavier logiciel s'adapte tout seul.
|
|
22
22
|
|
|
23
|
-
import { useCallback, useEffect, useId, useRef, useState } from 'react'
|
|
23
|
+
import { useCallback, useEffect, useId, useLayoutEffect, useRef, useState } from 'react'
|
|
24
|
+
import type { CSSProperties } from 'react'
|
|
24
25
|
import type { ReactNode } from 'react'
|
|
25
26
|
import { cx } from './cx.js'
|
|
26
27
|
import { Icon } from './icon.js'
|
|
@@ -118,13 +119,70 @@ export function SelectMenu<T extends string = string>({
|
|
|
118
119
|
return () => doc.removeEventListener('pointerdown', dehors)
|
|
119
120
|
}, [ouvert])
|
|
120
121
|
|
|
122
|
+
// La place de la liste à l'écran (DECISIONS, 03/11/2026) : en `fixed`, elle
|
|
123
|
+
// échappe à tout conteneur qui rogne (un tableau qui défile, une carte en
|
|
124
|
+
// `overflow: hidden`). Calculée d'après la boîte du déclencheur, et
|
|
125
|
+
// recalculée quand la page ou un conteneur défile, ou que la fenêtre change.
|
|
126
|
+
const [place, setPlace] = useState<CSSProperties | null>(null)
|
|
127
|
+
useLayoutEffect(() => {
|
|
128
|
+
if (!ouvert) {
|
|
129
|
+
setPlace(null)
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
const vue = declencheur.current?.ownerDocument.defaultView ?? window
|
|
133
|
+
const ECART = 5
|
|
134
|
+
const MARGE = 8
|
|
135
|
+
function placer(e?: Event) {
|
|
136
|
+
// Le défilement de la liste ELLE-MÊME ne la déplace pas : sans ce
|
|
137
|
+
// filtre, chaque tour de molette dans une longue liste la replaçait.
|
|
138
|
+
if (e && e.target === liste.current) return
|
|
139
|
+
const d = declencheur.current
|
|
140
|
+
if (!d) return
|
|
141
|
+
const r = d.getBoundingClientRect()
|
|
142
|
+
// La largeur AVANT la mesure : sans elle, la liste vaut encore 100 % de
|
|
143
|
+
// la fenêtre au premier passage, et le garde-fou de droite la plaque à
|
|
144
|
+
// gauche de l'écran.
|
|
145
|
+
liste.current?.style.setProperty('--zv-menu-largeur', `${r.width}px`)
|
|
146
|
+
const hauteur = liste.current?.scrollHeight ?? 0
|
|
147
|
+
const largeur = Math.max(liste.current?.offsetWidth ?? 0, r.width)
|
|
148
|
+
const dessous = vue.innerHeight - r.bottom - ECART - MARGE
|
|
149
|
+
const dessus = r.top - ECART - MARGE
|
|
150
|
+
// Au-dessus seulement si la liste ne tient pas dessous ET qu'il y a
|
|
151
|
+
// davantage de place au-dessus : sinon on garde le sens attendu.
|
|
152
|
+
const enHaut = hauteur > dessous && dessus > dessous
|
|
153
|
+
const dispo = Math.max(120, enHaut ? dessus : dessous)
|
|
154
|
+
const visible = Math.min(hauteur, dispo)
|
|
155
|
+
setPlace({
|
|
156
|
+
position: 'fixed',
|
|
157
|
+
top: enHaut ? r.top - ECART - visible : r.bottom + ECART,
|
|
158
|
+
// Jamais hors de la fenêtre à droite.
|
|
159
|
+
left: Math.max(MARGE, Math.min(r.left, vue.innerWidth - largeur - MARGE)),
|
|
160
|
+
maxHeight: dispo,
|
|
161
|
+
'--zv-menu-largeur': `${r.width}px`,
|
|
162
|
+
} as CSSProperties)
|
|
163
|
+
}
|
|
164
|
+
placer()
|
|
165
|
+
vue.addEventListener('scroll', placer, true)
|
|
166
|
+
vue.addEventListener('resize', placer)
|
|
167
|
+
return () => {
|
|
168
|
+
vue.removeEventListener('scroll', placer, true)
|
|
169
|
+
vue.removeEventListener('resize', placer)
|
|
170
|
+
}
|
|
171
|
+
}, [ouvert, options.length])
|
|
172
|
+
|
|
121
173
|
// L'option active reste visible quand on parcourt une liste plus haute que
|
|
122
|
-
// son cadre.
|
|
174
|
+
// son cadre. `place` : la liste n'est bornée (hauteur maximale) qu'une fois
|
|
175
|
+
// placée ; avant, il n'y a rien à faire défiler, et une liste longue
|
|
176
|
+
// s'ouvrait en haut (« Algérie ») au lieu de l'option choisie (« France »).
|
|
177
|
+
// Sur le PASSAGE à « placée », pas sur chaque nouvelle place : la place
|
|
178
|
+
// change à chaque défilement de la page, et ramener l'option active à
|
|
179
|
+
// chaque fois contrarierait qui parcourt la liste.
|
|
180
|
+
const placee = place !== null
|
|
123
181
|
useEffect(() => {
|
|
124
|
-
if (!ouvert) return
|
|
182
|
+
if (!ouvert || !placee) return
|
|
125
183
|
const el = liste.current?.children[actif] as HTMLElement | undefined
|
|
126
184
|
el?.scrollIntoView({ block: 'nearest' })
|
|
127
|
-
}, [ouvert, actif])
|
|
185
|
+
}, [ouvert, actif, placee])
|
|
128
186
|
|
|
129
187
|
function suivant(depuis: number, pas: number): number {
|
|
130
188
|
const n = options.length
|
|
@@ -221,7 +279,15 @@ export function SelectMenu<T extends string = string>({
|
|
|
221
279
|
</button>
|
|
222
280
|
|
|
223
281
|
{ouvert && (
|
|
224
|
-
<div
|
|
282
|
+
<div
|
|
283
|
+
ref={liste}
|
|
284
|
+
id={idListe}
|
|
285
|
+
role="listbox"
|
|
286
|
+
className="zv-menu zv-menu--place"
|
|
287
|
+
// Tant que la place n'est pas calculée (premier rendu), la liste est
|
|
288
|
+
// posée mais invisible : elle ne clignote pas à sa place d'origine.
|
|
289
|
+
style={place ?? { position: 'fixed', visibility: 'hidden' }}
|
|
290
|
+
>
|
|
225
291
|
{options.map((o, i) => (
|
|
226
292
|
<button
|
|
227
293
|
key={o.valeur}
|
package/src/forms.css
CHANGED
|
@@ -383,8 +383,21 @@
|
|
|
383
383
|
}
|
|
384
384
|
|
|
385
385
|
.zv-select-anchor--block .zv-menu {
|
|
386
|
-
|
|
387
|
-
|
|
386
|
+
/* En `fixed` (DECISIONS, 03/11/2026), 100 % vaudrait la fenêtre : la
|
|
387
|
+
* largeur du déclencheur arrive par `--zv-menu-largeur`. La liste en est
|
|
388
|
+
* le MINIMUM, pas la mesure : sous un déclencheur calé sur son contenu
|
|
389
|
+
* (« Membre »), l'option longue (« Administrateur ») débordait. Elle prend
|
|
390
|
+
* la largeur de ses options, jamais plus que la fenêtre. */
|
|
391
|
+
width: max-content;
|
|
392
|
+
min-width: var(--zv-menu-largeur, 100%);
|
|
393
|
+
max-width: calc(100vw - 16px);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/* La liste de SelectMenu, placée par le composant (`fixed`, haut, gauche,
|
|
397
|
+
* hauteur maximale) : elle défile au-delà de la place disponible. */
|
|
398
|
+
.zv-menu--place {
|
|
399
|
+
overflow-y: auto;
|
|
400
|
+
overscroll-behavior: contain;
|
|
388
401
|
}
|
|
389
402
|
|
|
390
403
|
/* ─── Déclencheur de sélection DESSINÉE ─────────────────────────────────
|
|
@@ -585,6 +598,8 @@
|
|
|
585
598
|
height: 20px;
|
|
586
599
|
flex: none;
|
|
587
600
|
border: 1px solid var(--line);
|
|
601
|
+
/* Carrée par défaut, en gélule sous `zv-arrondi` (tokens.css). */
|
|
602
|
+
border-radius: var(--r-switch);
|
|
588
603
|
background: var(--white);
|
|
589
604
|
/* La courbe est celle du paquet (motion.css, `--zv-easing`), recopiée en
|
|
590
605
|
* clair : forms.css ne dépend pas de motion.css et n'a pas à en dépendre
|
|
@@ -619,7 +634,7 @@
|
|
|
619
634
|
inset-inline-start: 2px;
|
|
620
635
|
width: 14px;
|
|
621
636
|
height: 14px;
|
|
622
|
-
border-radius:
|
|
637
|
+
border-radius: var(--r-switch-knob);
|
|
623
638
|
background: var(--label);
|
|
624
639
|
transition:
|
|
625
640
|
inset-inline-start 150ms cubic-bezier(0.2, 0.7, 0.2, 1),
|
package/src/icons.css
CHANGED
|
@@ -132,6 +132,39 @@
|
|
|
132
132
|
height: 40px;
|
|
133
133
|
flex: none;
|
|
134
134
|
border: 1px solid var(--accent-line);
|
|
135
|
+
/* Suit l'option arrondie (`zv-arrondi`, 03/11/2026) : nul par défaut. */
|
|
136
|
+
border-radius: var(--r-pill);
|
|
135
137
|
background: var(--accent-soft);
|
|
136
138
|
color: var(--accent);
|
|
137
139
|
}
|
|
140
|
+
|
|
141
|
+
/* La tuile d'un SERVICE (DECISIONS, « Les logos des partenaires »,
|
|
142
|
+
* 03/11/2026). HORS RELEVÉ. Même gabarit que la tuile d'icône (40×40, même
|
|
143
|
+
* filet), mais sur papier blanc : la marque de l'éditeur garde ses couleurs,
|
|
144
|
+
* le fond indigo pâle les aurait salies. Le logo occupe 60 % de la tuile,
|
|
145
|
+
* l'air autour est celui des icônes tracées. Une icône d'application à fond
|
|
146
|
+
* plein (Henrri) prend toute la tuile : un carré coloré dans un carré blanc
|
|
147
|
+
* se lirait comme un cadre de trop. */
|
|
148
|
+
.zv-icon-tile--logo {
|
|
149
|
+
overflow: hidden;
|
|
150
|
+
border-color: var(--line);
|
|
151
|
+
background: var(--white);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.zv-icon-tile--logo img {
|
|
155
|
+
display: block;
|
|
156
|
+
width: 60%;
|
|
157
|
+
height: 60%;
|
|
158
|
+
object-fit: contain;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/* À fond plein, le filet ferait un cadre autour du cadre de la marque. */
|
|
162
|
+
.zv-icon-tile--logo-plein {
|
|
163
|
+
border-color: transparent;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.zv-icon-tile--logo-plein img {
|
|
167
|
+
width: 100%;
|
|
168
|
+
height: 100%;
|
|
169
|
+
object-fit: cover;
|
|
170
|
+
}
|
package/src/tokens.css
CHANGED
|
@@ -107,6 +107,9 @@
|
|
|
107
107
|
--r-pill: 0;
|
|
108
108
|
--r-panel: 0;
|
|
109
109
|
--r-card: 0;
|
|
110
|
+
/* L'interrupteur : carré ici, en gélule sous `zv-arrondi` (03/11/2026). */
|
|
111
|
+
--r-switch: 0;
|
|
112
|
+
--r-switch-knob: 0;
|
|
110
113
|
|
|
111
114
|
/* Ombres */
|
|
112
115
|
--shadow-card: 0 2px 10px rgba(11, 16, 32, 0.05);
|
|
@@ -183,18 +186,27 @@
|
|
|
183
186
|
* lui seul. `zv-vif` remet un élément au carré dans une zone arrondie.
|
|
184
187
|
*
|
|
185
188
|
* Légèrement, et c'est voulu : à peu près la moitié du relevé d'avant le
|
|
186
|
-
* 25/08. Le rond de forme (pastille, radio
|
|
189
|
+
* 25/08. Le rond de forme (pastille, radio) n'en dépend pas.
|
|
190
|
+
*
|
|
191
|
+
* L'interrupteur, lui, en dépend depuis le 03/11/2026 (DECISIONS,
|
|
192
|
+
* « L'interrupteur suit l'option arrondie ») : carré en angle vif, piste en
|
|
193
|
+
* gélule et bouton rond sous `zv-arrondi`. Deux tokens à part, parce qu'un
|
|
194
|
+
* rayon de 3 px sur une piste de 20 px ne se lit ni carré ni rond. */
|
|
187
195
|
.zv-arrondi {
|
|
188
196
|
--r-btn: 4px;
|
|
189
197
|
--r-pill: 3px;
|
|
190
198
|
--r-panel: 6px;
|
|
191
199
|
--r-card: 8px;
|
|
200
|
+
--r-switch: 999px;
|
|
201
|
+
--r-switch-knob: 50%;
|
|
192
202
|
}
|
|
193
203
|
.zv-vif {
|
|
194
204
|
--r-btn: 0;
|
|
195
205
|
--r-pill: 0;
|
|
196
206
|
--r-panel: 0;
|
|
197
207
|
--r-card: 0;
|
|
208
|
+
--r-switch: 0;
|
|
209
|
+
--r-switch-knob: 0;
|
|
198
210
|
}
|
|
199
211
|
/* L'enveloppe `Angles` : elle ne porte que les tokens, sans ajouter de
|
|
200
212
|
* boîte à la mise en page (l'héritage passe par l'arbre, pas par la boîte). */
|
package/tokens/index.js
CHANGED