@zevra/ui 0.4.0 → 0.5.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/README.md +64 -1
- package/dist/actions.d.ts +41 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +24 -0
- package/dist/actions.js.map +1 -0
- package/dist/catalogue.d.ts +30 -0
- package/dist/catalogue.d.ts.map +1 -0
- package/dist/catalogue.js +830 -0
- package/dist/catalogue.js.map +1 -0
- package/dist/content.d.ts +135 -0
- package/dist/content.d.ts.map +1 -0
- package/dist/content.js +85 -0
- package/dist/content.js.map +1 -0
- package/dist/css-inline.d.ts +11 -0
- package/dist/css-inline.d.ts.map +1 -0
- package/dist/css-inline.js +13 -0
- package/dist/css-inline.js.map +1 -0
- package/dist/cx.d.ts +16 -0
- package/dist/cx.d.ts.map +1 -0
- package/dist/cx.js +20 -0
- package/dist/cx.js.map +1 -0
- package/dist/data.d.ts +136 -0
- package/dist/data.d.ts.map +1 -0
- package/dist/data.js +98 -0
- package/dist/data.js.map +1 -0
- package/dist/feedback.d.ts +88 -0
- package/dist/feedback.d.ts.map +1 -0
- package/dist/feedback.js +38 -0
- package/dist/feedback.js.map +1 -0
- package/dist/forms.d.ts +120 -0
- package/dist/forms.d.ts.map +1 -0
- package/dist/forms.js +60 -0
- package/dist/forms.js.map +1 -0
- package/dist/icon-data.d.ts +6 -0
- package/dist/icon-data.d.ts.map +1 -0
- package/dist/icon-data.js +77 -0
- package/dist/icon-data.js.map +1 -0
- package/dist/icon.d.ts +33 -0
- package/dist/icon.d.ts.map +1 -0
- package/dist/icon.js +13 -0
- package/dist/icon.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/layout.d.ts +166 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +120 -0
- package/dist/layout.js.map +1 -0
- package/dist/navigation.d.ts +150 -0
- package/dist/navigation.d.ts.map +1 -0
- package/dist/navigation.js +104 -0
- package/dist/navigation.js.map +1 -0
- package/dist/overlays.d.ts +68 -0
- package/dist/overlays.d.ts.map +1 -0
- package/dist/overlays.js +114 -0
- package/dist/overlays.js.map +1 -0
- package/dist/surfaces.d.ts +61 -0
- package/dist/surfaces.d.ts.map +1 -0
- package/dist/surfaces.js +54 -0
- package/dist/surfaces.js.map +1 -0
- package/dist/typography.d.ts +89 -0
- package/dist/typography.d.ts.map +1 -0
- package/dist/typography.js +76 -0
- package/dist/typography.js.map +1 -0
- package/package.json +36 -4
- package/react/actions.tsx +138 -0
- package/react/catalogue.tsx +1644 -0
- package/react/content.tsx +313 -0
- package/react/css-inline.ts +14 -0
- package/react/cx.ts +20 -0
- package/react/data.tsx +300 -0
- package/react/feedback.tsx +229 -0
- package/react/forms.tsx +383 -0
- package/react/icon-data.ts +114 -0
- package/react/icon.tsx +84 -0
- package/react/index.ts +42 -0
- package/react/layout.tsx +347 -0
- package/react/navigation.tsx +329 -0
- package/react/overlays.tsx +237 -0
- package/react/surfaces.tsx +161 -0
- package/react/typography.tsx +220 -0
|
@@ -0,0 +1,1644 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
// Catalogue — la planche de tous les composants, avec un exemple rendu et le
|
|
4
|
+
// code qui le produit.
|
|
5
|
+
//
|
|
6
|
+
// Il est PUBLIÉ avec le paquet, pas cantonné aux tests, et c'est délibéré :
|
|
7
|
+
// · la page « Design system » de l'admin le rend tel quel, donc la
|
|
8
|
+
// documentation ne peut pas dériver des composants — c'est le même code ;
|
|
9
|
+
// · tests/react.test.js le rend en HTML et vérifie que chaque classe émise
|
|
10
|
+
// existe dans le CSS. Un composant absent du catalogue n'est pas couvert :
|
|
11
|
+
// c'est le rappel le plus efficace qu'on ait trouvé pour le tenir complet.
|
|
12
|
+
//
|
|
13
|
+
// « use client » parce que les enrobages ont besoin d'état pour se démontrer.
|
|
14
|
+
// Ce module n'est jamais importé par le barrel : une app qui ne l'importe pas
|
|
15
|
+
// ne l'embarque pas.
|
|
16
|
+
|
|
17
|
+
import { useState, type ReactNode } from 'react'
|
|
18
|
+
import { Button, ButtonLink, ButtonRow, LinkArrow } from './actions.js'
|
|
19
|
+
import {
|
|
20
|
+
Card, Check, Divider, Ent, EntText, Faq, FaqItem, Glass, Inset, MiniStat,
|
|
21
|
+
MiniStatItem, Pill, Price, Quote, Src, SrcRow, StatCard, Tag, UiPreview,
|
|
22
|
+
} from './content.js'
|
|
23
|
+
import {
|
|
24
|
+
Avatar, Break, Cell, Chip, ChipRow, Cite, CiteRow, FileType, KV, KVRow,
|
|
25
|
+
List, ListMain, ListName, ListRow, Meta, MetaRow, Row, Table, TableHead,
|
|
26
|
+
Trunc, Unwrap,
|
|
27
|
+
} from './data.js'
|
|
28
|
+
import {
|
|
29
|
+
Alert, Badge, Caret, Dot, Empty, Loading, Progress, Status,
|
|
30
|
+
} from './feedback.js'
|
|
31
|
+
import {
|
|
32
|
+
Checkbox, Drop, Field, Input, Radio, Search, Seg, SegGroup, Select,
|
|
33
|
+
SelectAnchor, SelectTrigger, Switch, Textarea,
|
|
34
|
+
} from './forms.js'
|
|
35
|
+
import { ICONES, Icon, IconButton, NOMS_ICONES } from './icon.js'
|
|
36
|
+
import {
|
|
37
|
+
CTA, Col, Footer, FooterMini, Grid, Grid12, Gutter, Hero, Nav, NavLink,
|
|
38
|
+
NavLinks, NavLogo, ProofBar, SectionHead, SectionRule, SheetSection, Stat,
|
|
39
|
+
Stats, Step, Steps,
|
|
40
|
+
} from './layout.js'
|
|
41
|
+
import {
|
|
42
|
+
ChromeButton, CitePanel, Crumbs, Menu, MenuItem, Pager, PagerArrow,
|
|
43
|
+
PagerRank, PagerStep, Scroll, SideGroup, SideLink, Sidebar, SidebarFoot,
|
|
44
|
+
SidebarHead, Tabs, Topbar, TopbarTitle,
|
|
45
|
+
} from './navigation.js'
|
|
46
|
+
import { Drawer, Modal, Toast, Toasts } from './overlays.js'
|
|
47
|
+
import { Band, Fade, Glyph, GridFloor, Halo, PanelNavy, TextOnDark, anim } from './surfaces.js'
|
|
48
|
+
import {
|
|
49
|
+
Body, CardTitle, Display, Em, H1, H2, H3, Heading, Kicker, Lead, Link, Mono, Note,
|
|
50
|
+
Rule, Spec, Tagline,
|
|
51
|
+
} from './typography.js'
|
|
52
|
+
|
|
53
|
+
export interface Exemple {
|
|
54
|
+
/** Identifiant stable — sert d'ancre dans la page de consultation. */
|
|
55
|
+
id: string
|
|
56
|
+
nom: string
|
|
57
|
+
/** Ce que le composant fait, et surtout quand NE PAS l'employer. */
|
|
58
|
+
resume: string
|
|
59
|
+
/** Le code qui produit `rendu`, à copier tel quel. */
|
|
60
|
+
code: string
|
|
61
|
+
rendu: ReactNode
|
|
62
|
+
/** Rendu sur la bande manifeste plutôt que sur papier. */
|
|
63
|
+
surSombre?: boolean
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface GroupeCatalogue {
|
|
67
|
+
id: string
|
|
68
|
+
nom: string
|
|
69
|
+
resume: string
|
|
70
|
+
/** `applicatif` : interfaces internes. `vitrine` : pages publiques.
|
|
71
|
+
* `commun` : les deux. */
|
|
72
|
+
couche: 'commun' | 'applicatif' | 'vitrine'
|
|
73
|
+
/** Le module CSS à charger pour ce groupe. */
|
|
74
|
+
css: string
|
|
75
|
+
exemples: Exemple[]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* ── Démonstrations qui ont besoin d'état ─────────────────────────── */
|
|
79
|
+
|
|
80
|
+
function DemoSwitch() {
|
|
81
|
+
const [on, setOn] = useState(true)
|
|
82
|
+
return <Switch on={on} texte={on ? 'ACTIVÉ' : 'DÉSACTIVÉ'} onClick={() => setOn(!on)} />
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function DemoSeg() {
|
|
86
|
+
const [actif, setActif] = useState('tous')
|
|
87
|
+
return (
|
|
88
|
+
<SegGroup>
|
|
89
|
+
{[
|
|
90
|
+
['tous', 'Tous', 128],
|
|
91
|
+
['contrats', 'Contrats', 42],
|
|
92
|
+
['jugements', 'Jugements', 9],
|
|
93
|
+
].map(([id, label, n]) => (
|
|
94
|
+
<Seg
|
|
95
|
+
key={id as string}
|
|
96
|
+
on={actif === id}
|
|
97
|
+
compte={n as number}
|
|
98
|
+
onClick={() => setActif(id as string)}
|
|
99
|
+
>
|
|
100
|
+
{label as string}
|
|
101
|
+
</Seg>
|
|
102
|
+
))}
|
|
103
|
+
</SegGroup>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function DemoTabs() {
|
|
108
|
+
const [actif, setActif] = useState<'apercu' | 'sources'>('apercu')
|
|
109
|
+
return (
|
|
110
|
+
<Tabs
|
|
111
|
+
actif={actif}
|
|
112
|
+
onChange={setActif}
|
|
113
|
+
onglets={[
|
|
114
|
+
{ id: 'apercu', label: 'Aperçu' },
|
|
115
|
+
{ id: 'sources', label: 'Sources', compte: 12 },
|
|
116
|
+
]}
|
|
117
|
+
/>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function DemoMenu() {
|
|
122
|
+
const [ouvert, setOuvert] = useState(false)
|
|
123
|
+
const [valeur, setValeur] = useState('Toute la base')
|
|
124
|
+
return (
|
|
125
|
+
<SelectAnchor>
|
|
126
|
+
<SelectTrigger label="PÉRIMÈTRE" value={valeur} onClick={() => setOuvert(!ouvert)} />
|
|
127
|
+
{ouvert && (
|
|
128
|
+
<Menu>
|
|
129
|
+
{['Toute la base', 'Contrats', 'Jugements'].map((v) => (
|
|
130
|
+
<MenuItem
|
|
131
|
+
key={v}
|
|
132
|
+
actif={v === valeur}
|
|
133
|
+
onClick={() => {
|
|
134
|
+
setValeur(v)
|
|
135
|
+
setOuvert(false)
|
|
136
|
+
}}
|
|
137
|
+
>
|
|
138
|
+
{v}
|
|
139
|
+
</MenuItem>
|
|
140
|
+
))}
|
|
141
|
+
</Menu>
|
|
142
|
+
)}
|
|
143
|
+
</SelectAnchor>
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function DemoDrawer() {
|
|
148
|
+
const [ouvert, setOuvert] = useState(false)
|
|
149
|
+
return (
|
|
150
|
+
<>
|
|
151
|
+
<Button variante="fantome" compact onClick={() => setOuvert(true)}>
|
|
152
|
+
Ouvrir le tiroir
|
|
153
|
+
</Button>
|
|
154
|
+
<Drawer
|
|
155
|
+
ouvert={ouvert}
|
|
156
|
+
onClose={() => setOuvert(false)}
|
|
157
|
+
fermeture={<ChromeButton aria-label="Fermer" onClick={() => setOuvert(false)}>×</ChromeButton>}
|
|
158
|
+
>
|
|
159
|
+
<Sidebar>
|
|
160
|
+
<SidebarHead>
|
|
161
|
+
<Mono ton="label">ZEVRA</Mono>
|
|
162
|
+
</SidebarHead>
|
|
163
|
+
<SideGroup titre="Espace">
|
|
164
|
+
<SideLink href="#" courant num="01">
|
|
165
|
+
Bibliothèque
|
|
166
|
+
</SideLink>
|
|
167
|
+
<SideLink href="#" num="02">
|
|
168
|
+
Répertoire
|
|
169
|
+
</SideLink>
|
|
170
|
+
</SideGroup>
|
|
171
|
+
</Sidebar>
|
|
172
|
+
</Drawer>
|
|
173
|
+
</>
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function DemoModal() {
|
|
178
|
+
const [ouvert, setOuvert] = useState(false)
|
|
179
|
+
return (
|
|
180
|
+
<>
|
|
181
|
+
<Button variante="fantome" compact onClick={() => setOuvert(true)}>
|
|
182
|
+
Ouvrir la modale
|
|
183
|
+
</Button>
|
|
184
|
+
<Modal
|
|
185
|
+
ouvert={ouvert}
|
|
186
|
+
onClose={() => setOuvert(false)}
|
|
187
|
+
titre="Supprimer l'organisation"
|
|
188
|
+
danger
|
|
189
|
+
pied={
|
|
190
|
+
<ButtonRow>
|
|
191
|
+
<Button variante="fantome" onClick={() => setOuvert(false)}>
|
|
192
|
+
Annuler
|
|
193
|
+
</Button>
|
|
194
|
+
<Button onClick={() => setOuvert(false)}>Supprimer</Button>
|
|
195
|
+
</ButtonRow>
|
|
196
|
+
}
|
|
197
|
+
>
|
|
198
|
+
<Body>
|
|
199
|
+
Les 4 812 documents indexés et leurs annotations seront détruits. Cette
|
|
200
|
+
action est irréversible.
|
|
201
|
+
</Body>
|
|
202
|
+
</Modal>
|
|
203
|
+
</>
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function DemoDrop() {
|
|
208
|
+
const [survol, setSurvol] = useState(false)
|
|
209
|
+
return (
|
|
210
|
+
<Drop
|
|
211
|
+
survol={survol}
|
|
212
|
+
titre="Déposer des documents"
|
|
213
|
+
spec="PDF, DOCX · 25 Mo par fichier"
|
|
214
|
+
parcourir="Parcourir les fichiers"
|
|
215
|
+
onDragOver={(e) => {
|
|
216
|
+
e.preventDefault()
|
|
217
|
+
setSurvol(true)
|
|
218
|
+
}}
|
|
219
|
+
onDragLeave={() => setSurvol(false)}
|
|
220
|
+
onDrop={(e) => {
|
|
221
|
+
e.preventDefault()
|
|
222
|
+
setSurvol(false)
|
|
223
|
+
}}
|
|
224
|
+
/>
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/* ── Le catalogue ─────────────────────────────────────────────────── */
|
|
229
|
+
|
|
230
|
+
export const CATALOGUE: GroupeCatalogue[] = [
|
|
231
|
+
{
|
|
232
|
+
id: 'typographie',
|
|
233
|
+
nom: 'Typographie',
|
|
234
|
+
couche: 'commun',
|
|
235
|
+
css: 'base.css',
|
|
236
|
+
resume:
|
|
237
|
+
"Titres, corps, kickers, labels mono. La TAILLE est un choix de mise en page, la BALISE un choix de structure : d'où la prop « as », qui les sépare.",
|
|
238
|
+
exemples: [
|
|
239
|
+
{
|
|
240
|
+
id: 'headings',
|
|
241
|
+
nom: 'Titres',
|
|
242
|
+
resume:
|
|
243
|
+
'Six paliers. Une page n’a qu’un <h1>, mais peut avoir plusieurs blocs à sa taille — passer « as » plutôt que de casser le plan du document.',
|
|
244
|
+
code: `<H1>La recherche juridique, sourcée</H1>
|
|
245
|
+
<H2 as="h3">Ce que voit votre équipe</H2>
|
|
246
|
+
<H3>Indexation continue</H3>
|
|
247
|
+
<CardTitle>Bibliothèque</CardTitle>`,
|
|
248
|
+
rendu: (
|
|
249
|
+
<div style={{ display: 'grid', gap: 12 }}>
|
|
250
|
+
<H1>La recherche juridique, sourcée</H1>
|
|
251
|
+
<H2 as="h3">Ce que voit votre équipe</H2>
|
|
252
|
+
<H3>Indexation continue</H3>
|
|
253
|
+
<CardTitle>Bibliothèque</CardTitle>
|
|
254
|
+
</div>
|
|
255
|
+
),
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
id: 'display',
|
|
259
|
+
nom: 'Display',
|
|
260
|
+
resume:
|
|
261
|
+
'Chiffre ou mot posé pour sa présence, HORS de la hiérarchie des titres — annoncé comme titre de section, il pollue le plan du document.',
|
|
262
|
+
code: `<Display>4 812</Display>`,
|
|
263
|
+
rendu: <Display>4 812</Display>,
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
id: 'corps',
|
|
267
|
+
nom: 'Corps et chapô',
|
|
268
|
+
resume:
|
|
269
|
+
'Trois densités de corps, plus le chapô qui suit un titre de section et la note de bas de bloc.',
|
|
270
|
+
code: `<Lead>Chaque réponse cite ses sources.</Lead>
|
|
271
|
+
<Body>Le moteur relit l’intégralité des pièces déposées…</Body>
|
|
272
|
+
<Body taille="dense">Variante resserrée, pour une carte.</Body>
|
|
273
|
+
<Note>Hébergement souverain, données chiffrées au repos.</Note>`,
|
|
274
|
+
rendu: (
|
|
275
|
+
<div style={{ display: 'grid', gap: 10 }}>
|
|
276
|
+
<Lead>Chaque réponse cite ses sources.</Lead>
|
|
277
|
+
<Body>
|
|
278
|
+
Le moteur relit l’intégralité des pièces déposées, y compris les annexes
|
|
279
|
+
scannées, et rattache chaque affirmation au passage qui la fonde.
|
|
280
|
+
</Body>
|
|
281
|
+
<Body taille="dense">Variante resserrée, pour une carte.</Body>
|
|
282
|
+
<Note>Hébergement souverain, données chiffrées au repos.</Note>
|
|
283
|
+
</div>
|
|
284
|
+
),
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
id: 'kicker',
|
|
288
|
+
nom: 'Kicker et filet',
|
|
289
|
+
resume:
|
|
290
|
+
'Le filet de spectre 56×2 devant un sur-titre. PREMIER des trois emplois autorisés du spectre — jamais en fond, jamais en texte.',
|
|
291
|
+
code: `<Kicker>Méthode</Kicker>
|
|
292
|
+
<Kicker variante="stack">Sécurité</Kicker>
|
|
293
|
+
<Spec petit />`,
|
|
294
|
+
rendu: (
|
|
295
|
+
<div style={{ display: 'grid', gap: 14 }}>
|
|
296
|
+
<Kicker>Méthode</Kicker>
|
|
297
|
+
<Kicker variante="stack">Sécurité</Kicker>
|
|
298
|
+
</div>
|
|
299
|
+
),
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
id: 'mono',
|
|
303
|
+
nom: 'Label mono',
|
|
304
|
+
resume:
|
|
305
|
+
'Capitales espacées, RÉSERVÉES aux libellés. Une date ou un nom de fichier en capitales espacées devient illisible : pour ceux-là c’est « Meta ».',
|
|
306
|
+
code: `<Mono ton="label">PÉRIMÈTRE</Mono>
|
|
307
|
+
<Mono ton="ink">12 SOURCES</Mono>
|
|
308
|
+
<Mono ton="muted" micro>V2.4.1</Mono>`,
|
|
309
|
+
rendu: (
|
|
310
|
+
<div style={{ display: 'flex', gap: 18, alignItems: 'baseline', flexWrap: 'wrap' }}>
|
|
311
|
+
<Mono ton="label">PÉRIMÈTRE</Mono>
|
|
312
|
+
<Mono ton="ink">12 SOURCES</Mono>
|
|
313
|
+
<Mono ton="muted" micro>
|
|
314
|
+
V2.4.1
|
|
315
|
+
</Mono>
|
|
316
|
+
</div>
|
|
317
|
+
),
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
id: 'texte-inline',
|
|
321
|
+
nom: 'Dans le fil du texte',
|
|
322
|
+
resume:
|
|
323
|
+
'Mise en valeur, lien, signature de marque, filet neutre. « Em » a un pendant « surSombre » parce que l’indigo d’accent devient illisible sur la bande manifeste.',
|
|
324
|
+
code: `<Body>Une réponse <Em>toujours sourcée</Em>, jamais inventée.</Body>
|
|
325
|
+
<Link href="/methode">Voir la méthode</Link>
|
|
326
|
+
<Tagline>L’intelligence documentaire souveraine</Tagline>
|
|
327
|
+
<Rule />`,
|
|
328
|
+
rendu: (
|
|
329
|
+
<div style={{ display: 'grid', gap: 10 }}>
|
|
330
|
+
<Body>
|
|
331
|
+
Une réponse <Em>toujours sourcée</Em>, jamais inventée.
|
|
332
|
+
</Body>
|
|
333
|
+
<Link href="#">Voir la méthode</Link>
|
|
334
|
+
<Tagline>L’intelligence documentaire souveraine</Tagline>
|
|
335
|
+
<Rule />
|
|
336
|
+
</div>
|
|
337
|
+
),
|
|
338
|
+
},
|
|
339
|
+
],
|
|
340
|
+
},
|
|
341
|
+
|
|
342
|
+
{
|
|
343
|
+
id: 'actions',
|
|
344
|
+
nom: 'Actions',
|
|
345
|
+
couche: 'commun',
|
|
346
|
+
css: 'components.css',
|
|
347
|
+
resume:
|
|
348
|
+
"Trois variantes, et UNE SEULE pleine par page : c'est ce qui fait que l'action principale se voit. Un écran à deux boutons pleins n'en a plus aucun.",
|
|
349
|
+
exemples: [
|
|
350
|
+
{
|
|
351
|
+
id: 'boutons',
|
|
352
|
+
nom: 'Boutons',
|
|
353
|
+
resume:
|
|
354
|
+
'« plein » pour l’action principale, « fantôme » pour l’alternative, « encre » sur une surface claire où l’indigo se perdrait.',
|
|
355
|
+
code: `<ButtonRow>
|
|
356
|
+
<Button>Demander une démonstration</Button>
|
|
357
|
+
<Button variante="fantome">En savoir plus</Button>
|
|
358
|
+
<Button variante="encre">Se connecter</Button>
|
|
359
|
+
</ButtonRow>`,
|
|
360
|
+
rendu: (
|
|
361
|
+
<ButtonRow>
|
|
362
|
+
<Button>Demander une démonstration</Button>
|
|
363
|
+
<Button variante="fantome">En savoir plus</Button>
|
|
364
|
+
<Button variante="encre">Se connecter</Button>
|
|
365
|
+
</ButtonRow>
|
|
366
|
+
),
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
id: 'boutons-gabarits',
|
|
370
|
+
nom: 'Gabarits',
|
|
371
|
+
resume:
|
|
372
|
+
'« compact » dans une barre d’outils ou un pied de carte ; « bloc » pour un formulaire mobile, où l’action doit être atteignable au pouce.',
|
|
373
|
+
code: `<Button compact>Enregistrer</Button>
|
|
374
|
+
<Button bloc>Se connecter</Button>
|
|
375
|
+
<ButtonLink href="/tarifs" variante="fantome">Voir les tarifs</ButtonLink>`,
|
|
376
|
+
rendu: (
|
|
377
|
+
<div style={{ display: 'grid', gap: 10, maxWidth: 280 }}>
|
|
378
|
+
<ButtonRow>
|
|
379
|
+
<Button compact>Enregistrer</Button>
|
|
380
|
+
<ButtonLink href="#" variante="fantome" compact>
|
|
381
|
+
Voir les tarifs
|
|
382
|
+
</ButtonLink>
|
|
383
|
+
</ButtonRow>
|
|
384
|
+
<Button bloc>Se connecter</Button>
|
|
385
|
+
</div>
|
|
386
|
+
),
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
id: 'lien-fleche',
|
|
390
|
+
nom: 'Lien fléché',
|
|
391
|
+
resume:
|
|
392
|
+
'La flèche fait partie du composant, pas du contenu : recopiée à la main, elle a produit trois tracés différents.',
|
|
393
|
+
code: `<LinkArrow href="/methode">Comment ça marche</LinkArrow>`,
|
|
394
|
+
rendu: <LinkArrow href="#">Comment ça marche</LinkArrow>,
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
id: 'icon-btn',
|
|
398
|
+
nom: 'Bouton d’icône',
|
|
399
|
+
resume:
|
|
400
|
+
'Le libellé accessible est OBLIGATOIRE, et s’écrit pour l’ACTION (« Fermer le panneau ») et non pour le dessin (« Croix »).',
|
|
401
|
+
code: `<IconButton icone="fermer" label="Fermer le panneau" />
|
|
402
|
+
<IconButton icone="telecharger" label="Télécharger le document" />`,
|
|
403
|
+
rendu: (
|
|
404
|
+
<div style={{ display: 'flex', gap: 6 }}>
|
|
405
|
+
<IconButton icone={ICONES[0]!} label={NOMS_ICONES[ICONES[0]!]} />
|
|
406
|
+
<IconButton icone={ICONES[1]!} label={NOMS_ICONES[ICONES[1]!]} />
|
|
407
|
+
<IconButton icone={ICONES[2]!} label={NOMS_ICONES[ICONES[2]!]} />
|
|
408
|
+
</div>
|
|
409
|
+
),
|
|
410
|
+
},
|
|
411
|
+
],
|
|
412
|
+
},
|
|
413
|
+
|
|
414
|
+
{
|
|
415
|
+
id: 'saisie',
|
|
416
|
+
nom: 'Saisie',
|
|
417
|
+
couche: 'applicatif',
|
|
418
|
+
css: 'forms.css',
|
|
419
|
+
resume:
|
|
420
|
+
"Le liage intitulé ↔ contrôle est EXPLICITE : même valeur en « htmlFor » sur le champ et en « id » sur le contrôle. Sans useId, ces composants restent rendus au serveur.",
|
|
421
|
+
exemples: [
|
|
422
|
+
{
|
|
423
|
+
id: 'champ',
|
|
424
|
+
nom: 'Champ',
|
|
425
|
+
resume:
|
|
426
|
+
'L’aide et l’erreur occupent la même place : la seconde efface la première, sinon les deux se chevauchent.',
|
|
427
|
+
code: `<Field label="ADRESSE E-MAIL" htmlFor="mail" help="Votre adresse professionnelle.">
|
|
428
|
+
<Input id="mail" type="email" placeholder="prenom.nom@cabinet.fr" />
|
|
429
|
+
</Field>`,
|
|
430
|
+
rendu: (
|
|
431
|
+
<div style={{ display: 'grid', gap: 16, maxWidth: 380 }}>
|
|
432
|
+
<Field label="ADRESSE E-MAIL" htmlFor="ex-mail" help="Votre adresse professionnelle.">
|
|
433
|
+
<Input id="ex-mail" type="email" placeholder="prenom.nom@cabinet.fr" />
|
|
434
|
+
</Field>
|
|
435
|
+
<Field
|
|
436
|
+
label="IDENTIFIANT D’ORGANISATION"
|
|
437
|
+
htmlFor="ex-org"
|
|
438
|
+
erreur="Cet identifiant n’existe pas."
|
|
439
|
+
>
|
|
440
|
+
<Input id="ex-org" mono erreur defaultValue="zvr-8841-x" aria-describedby="ex-org-err" />
|
|
441
|
+
</Field>
|
|
442
|
+
<Field
|
|
443
|
+
label="MOT DE PASSE"
|
|
444
|
+
htmlFor="ex-mdp"
|
|
445
|
+
action={<Link href="#">Oublié ?</Link>}
|
|
446
|
+
>
|
|
447
|
+
<Input id="ex-mdp" type="password" defaultValue="········" />
|
|
448
|
+
</Field>
|
|
449
|
+
</div>
|
|
450
|
+
),
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
id: 'select',
|
|
454
|
+
nom: 'Sélection',
|
|
455
|
+
resume:
|
|
456
|
+
'<select> NATIF : la flèche, l’ouverture au clavier et l’annonce viennent du navigateur. C’est le motif de choix par défaut.',
|
|
457
|
+
code: `<Field label="RÔLE" htmlFor="role">
|
|
458
|
+
<Select id="role"><option>Administrateur</option>…</Select>
|
|
459
|
+
</Field>`,
|
|
460
|
+
rendu: (
|
|
461
|
+
<div style={{ maxWidth: 260 }}>
|
|
462
|
+
<Field label="RÔLE" htmlFor="ex-role">
|
|
463
|
+
<Select id="ex-role" defaultValue="Rédacteur">
|
|
464
|
+
<option>Administrateur</option>
|
|
465
|
+
<option>Rédacteur</option>
|
|
466
|
+
<option>Lecture seule</option>
|
|
467
|
+
</Select>
|
|
468
|
+
</Field>
|
|
469
|
+
</div>
|
|
470
|
+
),
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
id: 'textarea',
|
|
474
|
+
nom: 'Zone de texte',
|
|
475
|
+
resume:
|
|
476
|
+
'Redimensionnable verticalement SEULEMENT : en libre, l’utilisateur l’élargit hors de sa colonne et casse la mise en page.',
|
|
477
|
+
code: `<Textarea placeholder="Contexte de la demande…" />`,
|
|
478
|
+
rendu: (
|
|
479
|
+
<div style={{ maxWidth: 380 }}>
|
|
480
|
+
<Textarea placeholder="Contexte de la demande…" />
|
|
481
|
+
</div>
|
|
482
|
+
),
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
id: 'recherche',
|
|
486
|
+
nom: 'Recherche',
|
|
487
|
+
resume:
|
|
488
|
+
'Cadre + loupe + champ nu. Deux marquages au clavier (l’anneau du champ ET le filet du cadre) : assumé — supprimer l’un rend le champ invisible, l’autre prive la souris de retour.',
|
|
489
|
+
code: `<Search placeholder="Rechercher un document…" />`,
|
|
490
|
+
rendu: (
|
|
491
|
+
<div style={{ maxWidth: 380 }}>
|
|
492
|
+
<Search placeholder="Rechercher un document…" />
|
|
493
|
+
</div>
|
|
494
|
+
),
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
id: 'menu-select',
|
|
498
|
+
nom: 'Déclencheur de menu',
|
|
499
|
+
resume:
|
|
500
|
+
'Menu MAISON, à réserver aux filtres. À poser dans « SelectAnchor » : c’est l’enveloppe qui ancre le menu, pas le déclencheur.',
|
|
501
|
+
code: `<SelectAnchor>
|
|
502
|
+
<SelectTrigger label="PÉRIMÈTRE" value="Toute la base" onClick={…} />
|
|
503
|
+
{ouvert && <Menu><MenuItem actif>Toute la base</MenuItem>…</Menu>}
|
|
504
|
+
</SelectAnchor>`,
|
|
505
|
+
rendu: <DemoMenu />,
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
id: 'bascules',
|
|
509
|
+
nom: 'Bascules',
|
|
510
|
+
resume:
|
|
511
|
+
'Contrôles natifs (appearance:none seulement) : clavier, focus visible et annonce viennent du navigateur, pas d’un <div> à réoutiller.',
|
|
512
|
+
code: `<Checkbox defaultChecked /> <Radio name="x" defaultChecked />
|
|
513
|
+
<Switch on={on} texte="ACTIVÉ" onClick={…} />`,
|
|
514
|
+
rendu: (
|
|
515
|
+
<div style={{ display: 'flex', gap: 20, alignItems: 'center', flexWrap: 'wrap' }}>
|
|
516
|
+
<label style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
|
|
517
|
+
<Checkbox defaultChecked /> <span className="zv-body zv-body--dense">Anonymiser</span>
|
|
518
|
+
</label>
|
|
519
|
+
<label style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
|
|
520
|
+
<Radio name="ex-r" defaultChecked /> <span className="zv-body zv-body--dense">Tout</span>
|
|
521
|
+
</label>
|
|
522
|
+
<DemoSwitch />
|
|
523
|
+
</div>
|
|
524
|
+
),
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
id: 'puces',
|
|
528
|
+
nom: 'Puces de filtre',
|
|
529
|
+
resume:
|
|
530
|
+
'Le seul mécanisme de filtrage du système. Sélection DOUCE par défaut ; la variante « plein » est bornée à un groupe par page.',
|
|
531
|
+
code: `<SegGroup>
|
|
532
|
+
<Seg on compte={128} onClick={…}>Tous</Seg>
|
|
533
|
+
<Seg compte={42} onClick={…}>Contrats</Seg>
|
|
534
|
+
</SegGroup>`,
|
|
535
|
+
rendu: <DemoSeg />,
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
id: 'depot',
|
|
539
|
+
nom: 'Zone de dépôt',
|
|
540
|
+
resume:
|
|
541
|
+
'role="button" + tabIndex posés par le composant : sans eux la page d’import est inatteignable au clavier. Remettre value="" après chaque sélection.',
|
|
542
|
+
code: `<Drop titre="Déposer des documents" spec="PDF, DOCX · 25 Mo"
|
|
543
|
+
parcourir="Parcourir les fichiers" survol={survol} … />`,
|
|
544
|
+
rendu: <DemoDrop />,
|
|
545
|
+
},
|
|
546
|
+
],
|
|
547
|
+
},
|
|
548
|
+
|
|
549
|
+
{
|
|
550
|
+
id: 'retours',
|
|
551
|
+
nom: 'Retours',
|
|
552
|
+
couche: 'applicatif',
|
|
553
|
+
css: 'feedback.css',
|
|
554
|
+
resume:
|
|
555
|
+
"Rien n'est interactif ici. ⚠️ Ni roue, ni squelette, ni infobulle : le CSS n'en a aucun, parce que le relevé de production n'en contenait aucun. Le travail en cours se dit par « Loading », qui nomme ce qu'on attend.",
|
|
556
|
+
exemples: [
|
|
557
|
+
{
|
|
558
|
+
id: 'alertes',
|
|
559
|
+
nom: 'Bandeaux',
|
|
560
|
+
resume:
|
|
561
|
+
'Le ton est OBLIGATOIRE : sans lui, le bandeau hérite d’un filet en currentColor, soit un trait d’encre autour d’un fond blanc.',
|
|
562
|
+
code: `<Alert ton="info">Les documents sont indexés en continu.</Alert>
|
|
563
|
+
<Alert ton="err" taille="sm">Identifiants incorrects.</Alert>
|
|
564
|
+
<Alert ton="warn" taille="lg">Le moteur est en charge…</Alert>`,
|
|
565
|
+
rendu: (
|
|
566
|
+
<div style={{ display: 'grid', gap: 10 }}>
|
|
567
|
+
<Alert ton="info">Les documents sont indexés en continu.</Alert>
|
|
568
|
+
<Alert ton="ok" taille="sm">Organisation enregistrée.</Alert>
|
|
569
|
+
<Alert ton="warn">Trois pièces n’ont pas pu être océrisées.</Alert>
|
|
570
|
+
<Alert ton="err" taille="sm">Identifiants incorrects.</Alert>
|
|
571
|
+
<Alert ton="dash">Aucune source ne couvre cette période.</Alert>
|
|
572
|
+
</div>
|
|
573
|
+
),
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
id: 'badges',
|
|
577
|
+
nom: 'Étiquettes',
|
|
578
|
+
resume:
|
|
579
|
+
'Les capitales s’écrivent dans le CONTENU, pas en CSS : transformé par le navigateur, « INDEXÉ » se casse selon la locale.',
|
|
580
|
+
code: `<Badge ton="ok">INDEXÉ</Badge>
|
|
581
|
+
<Badge ton="warn" gabarit="micro">WARN</Badge>
|
|
582
|
+
<Badge ton="accent" pastille>IA DÉDIÉE</Badge>`,
|
|
583
|
+
rendu: (
|
|
584
|
+
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' }}>
|
|
585
|
+
<Badge>ADMIN</Badge>
|
|
586
|
+
<Badge ton="accent" pastille>IA DÉDIÉE</Badge>
|
|
587
|
+
<Badge ton="ok">INDEXÉ</Badge>
|
|
588
|
+
<Badge ton="warn" gabarit="micro">WARN</Badge>
|
|
589
|
+
<Badge ton="err" gabarit="micro">ERROR</Badge>
|
|
590
|
+
<Badge ton="ok" gabarit="state">OK</Badge>
|
|
591
|
+
</div>
|
|
592
|
+
),
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
id: 'pastilles',
|
|
596
|
+
nom: 'Pastilles et statut',
|
|
597
|
+
resume:
|
|
598
|
+
'Sans état, la pastille reste GRISE : un statut ajouté côté serveur et pas encore côté interface ne doit pas passer pour un succès. La pastille CARRÉE marque une source, pas un état.',
|
|
599
|
+
code: `<Status etat="indexe">Indexé · 42 pages</Status>
|
|
600
|
+
<Dot etat="erreur" /> <Dot carree /> <Dot taille="lg" etat="ok" />`,
|
|
601
|
+
rendu: (
|
|
602
|
+
<div style={{ display: 'grid', gap: 10 }}>
|
|
603
|
+
<Status etat="indexe">Indexé · 42 pages</Status>
|
|
604
|
+
<Status etat="indexation">Indexation en cours</Status>
|
|
605
|
+
<Status etat="erreur">Fichier illisible : page 3 corrompue</Status>
|
|
606
|
+
<Status>Statut inconnu</Status>
|
|
607
|
+
</div>
|
|
608
|
+
),
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
id: 'absence',
|
|
612
|
+
nom: 'Absence',
|
|
613
|
+
resume:
|
|
614
|
+
'⚠️ DEUX objets. Sans titre : la LIGNE d’absence, un filtre trop étroit, l’en-tête du tableau reste visible. Avec titre et « page » : l’ÉCRAN d’absence, la collection est vide. Les confondre affiche un écran de bienvenue à chaque filtre strict.',
|
|
615
|
+
code: `<Empty>Aucun document ne correspond à ces filtres.</Empty>
|
|
616
|
+
|
|
617
|
+
<Empty page titre="Aucun document" action={<Button compact>Importer</Button>}>
|
|
618
|
+
Déposez vos premières pièces pour lancer l’indexation.
|
|
619
|
+
</Empty>`,
|
|
620
|
+
rendu: (
|
|
621
|
+
<div style={{ display: 'grid', gap: 16 }}>
|
|
622
|
+
<Empty>Aucun document ne correspond à ces filtres.</Empty>
|
|
623
|
+
<Empty page titre="Aucun document" action={<Button compact>Importer</Button>}>
|
|
624
|
+
Déposez vos premières pièces pour lancer l’indexation.
|
|
625
|
+
</Empty>
|
|
626
|
+
</div>
|
|
627
|
+
),
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
id: 'attente',
|
|
631
|
+
nom: 'Travail en cours',
|
|
632
|
+
resume:
|
|
633
|
+
'STATIQUE : la pastille ne bat pas. Une roue dit « attendez » ; cette phrase dit sur quoi, sur combien, et qu’il ne se passe rien d’anormal.',
|
|
634
|
+
code: `<Loading>Recherche dans 4 812 documents anonymisés…</Loading>
|
|
635
|
+
<Progress valeur={0.62} label="Indexation" />
|
|
636
|
+
<span>…réponse en cours<Caret /></span>`,
|
|
637
|
+
rendu: (
|
|
638
|
+
<div style={{ display: 'grid', gap: 12 }}>
|
|
639
|
+
<Loading>Recherche dans 4 812 documents anonymisés…</Loading>
|
|
640
|
+
<Progress valeur={0.62} label="Indexation" />
|
|
641
|
+
<span className="zv-body zv-body--dense">
|
|
642
|
+
La clause de non-concurrence est limitée à vingt-quatre mois
|
|
643
|
+
<Caret />
|
|
644
|
+
</span>
|
|
645
|
+
</div>
|
|
646
|
+
),
|
|
647
|
+
},
|
|
648
|
+
],
|
|
649
|
+
},
|
|
650
|
+
|
|
651
|
+
{
|
|
652
|
+
id: 'contenu',
|
|
653
|
+
nom: 'Contenu',
|
|
654
|
+
couche: 'commun',
|
|
655
|
+
css: 'components.css',
|
|
656
|
+
resume:
|
|
657
|
+
'Surfaces, encarts, étiquettes éditoriales et blocs de preuve. ⚠️ Deux règles de charte y sont opposables : jamais de fausse citation, jamais de lorem dans un aperçu produit.',
|
|
658
|
+
exemples: [
|
|
659
|
+
{
|
|
660
|
+
id: 'cartes',
|
|
661
|
+
nom: 'Cartes',
|
|
662
|
+
resume:
|
|
663
|
+
'« Glass » ne fonctionne QUE devant un halo ou une scène : posée sur du papier nu, elle disparaît.',
|
|
664
|
+
code: `<Card padding="large">…</Card>
|
|
665
|
+
<Card etat="ok">…</Card>
|
|
666
|
+
<Card flottante>…</Card>`,
|
|
667
|
+
rendu: (
|
|
668
|
+
<div style={{ display: 'grid', gap: 12, gridTemplateColumns: 'repeat(auto-fit,minmax(180px,1fr))' }}>
|
|
669
|
+
<Card>
|
|
670
|
+
<CardTitle>Bibliothèque</CardTitle>
|
|
671
|
+
<Body taille="dense">4 812 pièces indexées.</Body>
|
|
672
|
+
</Card>
|
|
673
|
+
<Card etat="ok">
|
|
674
|
+
<Body taille="dense">Sauvegarde vérifiée il y a 2 h.</Body>
|
|
675
|
+
</Card>
|
|
676
|
+
<Card etat="err">
|
|
677
|
+
<Body taille="dense">Trois pièces illisibles.</Body>
|
|
678
|
+
</Card>
|
|
679
|
+
</div>
|
|
680
|
+
),
|
|
681
|
+
},
|
|
682
|
+
{
|
|
683
|
+
id: 'encarts',
|
|
684
|
+
nom: 'Encarts',
|
|
685
|
+
resume:
|
|
686
|
+
'DEUX objets longtemps confondus : la citation d’interface, et l’extrait de document (« discret » : fond panel, très aéré).',
|
|
687
|
+
code: `<Inset>La demande est recevable.</Inset>
|
|
688
|
+
<Inset discret>« … le délai de préavis court à compter … »</Inset>`,
|
|
689
|
+
rendu: (
|
|
690
|
+
<div style={{ display: 'grid', gap: 10 }}>
|
|
691
|
+
<Inset>
|
|
692
|
+
<Body taille="dense">Trois arrêts postérieurs confirment cette lecture.</Body>
|
|
693
|
+
</Inset>
|
|
694
|
+
<Inset discret>
|
|
695
|
+
<Body taille="dense">
|
|
696
|
+
« Le délai de préavis court à compter de la première présentation de la
|
|
697
|
+
lettre recommandée. »
|
|
698
|
+
</Body>
|
|
699
|
+
</Inset>
|
|
700
|
+
</div>
|
|
701
|
+
),
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
id: 'etiquettes',
|
|
705
|
+
nom: 'Étiquettes éditoriales',
|
|
706
|
+
resume:
|
|
707
|
+
'⚠️ Trois objets qui se ressemblent et ne se remplacent pas : « Pill » (pastille pleine), « Tag » (kicker mono), « Src » (puce de source).',
|
|
708
|
+
code: `<Pill>Nouveau</Pill>
|
|
709
|
+
<Tag etat="ok">CONFORME</Tag>
|
|
710
|
+
<SrcRow><Src>BOSS</Src><Src>Judilibre</Src><Src attenue>+ 11</Src></SrcRow>`,
|
|
711
|
+
rendu: (
|
|
712
|
+
<div style={{ display: 'grid', gap: 12 }}>
|
|
713
|
+
<div style={{ display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' }}>
|
|
714
|
+
<Pill>Nouveau</Pill>
|
|
715
|
+
<Tag>MÉTHODE</Tag>
|
|
716
|
+
<Tag etat="ok">CONFORME</Tag>
|
|
717
|
+
<Tag etat="err">ÉCART</Tag>
|
|
718
|
+
</div>
|
|
719
|
+
<SrcRow>
|
|
720
|
+
<Src>BOSS</Src>
|
|
721
|
+
<Src>Judilibre</Src>
|
|
722
|
+
<Src>KALI</Src>
|
|
723
|
+
<Src attenue>+ 11</Src>
|
|
724
|
+
</SrcRow>
|
|
725
|
+
</div>
|
|
726
|
+
),
|
|
727
|
+
},
|
|
728
|
+
{
|
|
729
|
+
id: 'entites',
|
|
730
|
+
nom: 'Entités pseudonymisées',
|
|
731
|
+
resume:
|
|
732
|
+
'Le paragraphe hôte respire davantage : sans lui, les jetons se télescopent d’une ligne à l’autre.',
|
|
733
|
+
code: `<Body><EntText>Le contrat lie <Ent>SOCIÉTÉ_A</Ent> à <Ent>M. B</Ent>…</EntText></Body>`,
|
|
734
|
+
rendu: (
|
|
735
|
+
<Body>
|
|
736
|
+
<EntText>
|
|
737
|
+
Le contrat lie <Ent>SOCIÉTÉ_A</Ent> à <Ent>M. B</Ent>, domicilié{' '}
|
|
738
|
+
<Ent>ADRESSE_1</Ent>, pour une durée de trente-six mois.
|
|
739
|
+
</EntText>
|
|
740
|
+
</Body>
|
|
741
|
+
),
|
|
742
|
+
},
|
|
743
|
+
{
|
|
744
|
+
id: 'preuve',
|
|
745
|
+
nom: 'Aperçu d’interface',
|
|
746
|
+
resume:
|
|
747
|
+
'⚠️ RÈGLE DE CHARTE : toujours de VRAIES données réalistes, jamais de lorem — c’est ce bloc qui porte la crédibilité de la page.',
|
|
748
|
+
code: `<UiPreview question="Quelle est la durée de préavis ?"
|
|
749
|
+
sources="12 sources" puces={<SrcRow>…</SrcRow>}>
|
|
750
|
+
Trois mois pour les cadres…
|
|
751
|
+
</UiPreview>`,
|
|
752
|
+
rendu: (
|
|
753
|
+
<UiPreview
|
|
754
|
+
question="Quelle durée de préavis s’applique aux cadres ?"
|
|
755
|
+
sources="12 sources"
|
|
756
|
+
puces={
|
|
757
|
+
<SrcRow>
|
|
758
|
+
<Src>Convention Syntec</Src>
|
|
759
|
+
<Src>Cass. soc. 2019</Src>
|
|
760
|
+
</SrcRow>
|
|
761
|
+
}
|
|
762
|
+
>
|
|
763
|
+
Trois mois, sauf stipulation plus favorable du contrat de travail. La
|
|
764
|
+
convention Syntec ne prévoit pas de réduction en cas de dispense.
|
|
765
|
+
</UiPreview>
|
|
766
|
+
),
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
id: 'stats-carte',
|
|
770
|
+
nom: 'Statistiques de carte',
|
|
771
|
+
resume:
|
|
772
|
+
'Paire au pied d’une carte produit, ou carte autonome. ⚠️ Ne pas confondre avec « Stats » (layout), qui est la rangée de chiffres d’une page vitrine : celle-ci vit DANS une carte.',
|
|
773
|
+
code: `<MiniStat>
|
|
774
|
+
<MiniStatItem valeur="4 812">documents</MiniStatItem>
|
|
775
|
+
<MiniStatItem valeur="1,2 s">latence</MiniStatItem>
|
|
776
|
+
</MiniStat>
|
|
777
|
+
|
|
778
|
+
<StatCard valeur="99,95 %">disponibilité sur 90 jours</StatCard>`,
|
|
779
|
+
rendu: (
|
|
780
|
+
<div style={{ display: 'grid', gap: 12 }}>
|
|
781
|
+
<MiniStat>
|
|
782
|
+
<MiniStatItem valeur="4 812">documents</MiniStatItem>
|
|
783
|
+
<MiniStatItem valeur="1,2 s">latence médiane</MiniStatItem>
|
|
784
|
+
</MiniStat>
|
|
785
|
+
<StatCard valeur="99,95 %">disponibilité sur 90 jours</StatCard>
|
|
786
|
+
</div>
|
|
787
|
+
),
|
|
788
|
+
},
|
|
789
|
+
{
|
|
790
|
+
id: 'faq',
|
|
791
|
+
nom: 'FAQ',
|
|
792
|
+
resume:
|
|
793
|
+
'⚠️ Cinq questions par page au MAXIMUM. Au-delà, ce n’est plus une FAQ, c’est une documentation mal rangée.',
|
|
794
|
+
code: `<Faq>
|
|
795
|
+
<FaqItem question="Où sont hébergées les données ?">En France, chez OVHcloud.</FaqItem>
|
|
796
|
+
</Faq>`,
|
|
797
|
+
rendu: (
|
|
798
|
+
<Faq>
|
|
799
|
+
<FaqItem question="Où sont hébergées les données ?">
|
|
800
|
+
En France, sur une infrastructure OVHcloud dédiée, sans réplication hors
|
|
801
|
+
Union européenne.
|
|
802
|
+
</FaqItem>
|
|
803
|
+
<FaqItem question="Le modèle apprend-il sur nos documents ?">
|
|
804
|
+
Non. Les pièces déposées ne servent qu’à répondre à vos requêtes et ne
|
|
805
|
+
sont jamais versées à un entraînement.
|
|
806
|
+
</FaqItem>
|
|
807
|
+
</Faq>
|
|
808
|
+
),
|
|
809
|
+
},
|
|
810
|
+
{
|
|
811
|
+
id: 'prix',
|
|
812
|
+
nom: 'Tarifs',
|
|
813
|
+
resume:
|
|
814
|
+
'« recommande » pose le bandeau indigo. Trois cartes par page au plus : au-delà, la comparaison ne se fait plus d’un coup d’œil.',
|
|
815
|
+
code: `<Price montant="490 €" unite="HT · par mois" recommande
|
|
816
|
+
note="Engagement 12 mois">…</Price>`,
|
|
817
|
+
rendu: (
|
|
818
|
+
<div style={{ display: 'grid', gap: 12, gridTemplateColumns: 'repeat(auto-fit,minmax(190px,1fr))' }}>
|
|
819
|
+
<Price montant="190 €" unite="HT / mois" note="Jusqu’à 3 utilisateurs">
|
|
820
|
+
<CardTitle>Cabinet</CardTitle>
|
|
821
|
+
</Price>
|
|
822
|
+
<Price montant="490 €" unite="HT / mois" recommande note="Engagement 12 mois">
|
|
823
|
+
<CardTitle>Étude</CardTitle>
|
|
824
|
+
</Price>
|
|
825
|
+
</div>
|
|
826
|
+
),
|
|
827
|
+
},
|
|
828
|
+
{
|
|
829
|
+
id: 'filets-check',
|
|
830
|
+
nom: 'Filets et coche',
|
|
831
|
+
resume:
|
|
832
|
+
'⚠️ « Check » n’est PAS un contrôle : c’est une coche décorative en tête de liste d’arguments. Le contrôle, c’est « Checkbox ».',
|
|
833
|
+
code: `<Divider /> <Divider marque />
|
|
834
|
+
<li><Check /> Réponses sourcées</li>`,
|
|
835
|
+
rendu: (
|
|
836
|
+
<div style={{ display: 'grid', gap: 10 }}>
|
|
837
|
+
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
|
|
838
|
+
<Check />
|
|
839
|
+
<span className="zv-body zv-body--dense">Chaque réponse cite ses sources</span>
|
|
840
|
+
</div>
|
|
841
|
+
<Divider />
|
|
842
|
+
<Divider marque />
|
|
843
|
+
</div>
|
|
844
|
+
),
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
id: 'citation',
|
|
848
|
+
nom: 'Citation client',
|
|
849
|
+
resume:
|
|
850
|
+
'⚠️ RÈGLE DE CHARTE : jamais de fausse citation. Tant qu’un verbatim n’a pas été recueilli, ce bloc reste EN RÉSERVE — un placeholder crédible finit toujours par partir en production.',
|
|
851
|
+
code: `<Band><Quote auteur="— Nom, cabinet">Verbatim réel uniquement.</Quote></Band>`,
|
|
852
|
+
surSombre: true,
|
|
853
|
+
rendu: (
|
|
854
|
+
<Band>
|
|
855
|
+
<Quote auteur="— en attente d’un verbatim recueilli">
|
|
856
|
+
Ce bloc reste en réserve tant qu’aucune citation réelle n’a été
|
|
857
|
+
recueillie.
|
|
858
|
+
</Quote>
|
|
859
|
+
</Band>
|
|
860
|
+
),
|
|
861
|
+
},
|
|
862
|
+
],
|
|
863
|
+
},
|
|
864
|
+
|
|
865
|
+
{
|
|
866
|
+
id: 'donnees',
|
|
867
|
+
nom: 'Données',
|
|
868
|
+
couche: 'applicatif',
|
|
869
|
+
css: 'data.css',
|
|
870
|
+
resume:
|
|
871
|
+
"Le tableau n'est PAS un <table> : c'est une grille CSS, parce qu'une même rangée doit se rendre en colonnes au-dessus du point de rupture et en carte empilée en dessous, sans dupliquer le balisage.",
|
|
872
|
+
exemples: [
|
|
873
|
+
{
|
|
874
|
+
id: 'tableau',
|
|
875
|
+
nom: 'Tableau',
|
|
876
|
+
resume:
|
|
877
|
+
'Le gabarit de colonnes se pose UNE FOIS sur le cadre ; l’en-tête et les lignes en héritent. Recopié aux deux endroits, on l’oublie à l’un et les intitulés se décalent des valeurs sans que rien ne casse visiblement.',
|
|
878
|
+
code: `<Table gabarit="minmax(0,2fr) 110px 90px">
|
|
879
|
+
<TableHead>
|
|
880
|
+
<Cell>Document</Cell><Cell>État</Cell><Cell registre="num">Pages</Cell>
|
|
881
|
+
</TableHead>
|
|
882
|
+
<Row href="/doc/1">
|
|
883
|
+
<Cell registre="nom"><Trunc>Contrat-cadre.pdf</Trunc></Cell>
|
|
884
|
+
<Cell label="État"><Status etat="indexe">Indexé</Status></Cell>
|
|
885
|
+
<Cell registre="num" label="Pages" unite="p.">42</Cell>
|
|
886
|
+
</Row>
|
|
887
|
+
</Table>`,
|
|
888
|
+
rendu: (
|
|
889
|
+
<Table gabarit="minmax(0,2fr) 120px 90px">
|
|
890
|
+
<TableHead>
|
|
891
|
+
<Cell>Document</Cell>
|
|
892
|
+
<Cell>État</Cell>
|
|
893
|
+
<Cell registre="num">Pages</Cell>
|
|
894
|
+
</TableHead>
|
|
895
|
+
{[
|
|
896
|
+
['Contrat-cadre-Syntec.pdf', 'indexe', 'Indexé', 42],
|
|
897
|
+
['Assignation-2024-114.docx', 'indexation', 'Indexation', 8],
|
|
898
|
+
['Annexe-3-scannee.pdf', 'erreur', 'Page 3 corrompue', 3],
|
|
899
|
+
].map(([nom, etat, libelle, pages]) => (
|
|
900
|
+
<Row key={nom as string}>
|
|
901
|
+
<Cell registre="nom">
|
|
902
|
+
<Trunc>{nom as string}</Trunc>
|
|
903
|
+
</Cell>
|
|
904
|
+
<Cell label="État">
|
|
905
|
+
<Status etat={etat as 'indexe'}>{libelle as string}</Status>
|
|
906
|
+
</Cell>
|
|
907
|
+
<Cell registre="num" label="Pages" unite="p.">
|
|
908
|
+
{pages as number}
|
|
909
|
+
</Cell>
|
|
910
|
+
</Row>
|
|
911
|
+
))}
|
|
912
|
+
</Table>
|
|
913
|
+
),
|
|
914
|
+
},
|
|
915
|
+
{
|
|
916
|
+
id: 'unwrap',
|
|
917
|
+
nom: 'Dégroupage',
|
|
918
|
+
resume:
|
|
919
|
+
'La primitive structurelle du module : un groupe qui rassemble deux ou trois cellules pour l’empilement mobile, puis s’efface (display: contents) et rend ses enfants DIRECTEMENT à la grille. ⚠️ Il n’exprime QUE ce sens-là.',
|
|
920
|
+
code: `<Row>
|
|
921
|
+
<Unwrap><Cell registre="nom">…</Cell><Cell label="Auteur">…</Cell></Unwrap>
|
|
922
|
+
<Cell registre="num">…</Cell>
|
|
923
|
+
</Row>`,
|
|
924
|
+
rendu: (
|
|
925
|
+
<Table gabarit="minmax(0,2fr) 120px 80px">
|
|
926
|
+
<Row>
|
|
927
|
+
<Unwrap>
|
|
928
|
+
<Cell registre="nom">Mémoire en défense</Cell>
|
|
929
|
+
<Cell label="Auteur">
|
|
930
|
+
<Meta>M. Dubois</Meta>
|
|
931
|
+
</Cell>
|
|
932
|
+
</Unwrap>
|
|
933
|
+
<Cell registre="num" label="Pages">
|
|
934
|
+
17
|
|
935
|
+
</Cell>
|
|
936
|
+
</Row>
|
|
937
|
+
</Table>
|
|
938
|
+
),
|
|
939
|
+
},
|
|
940
|
+
{
|
|
941
|
+
id: 'liste',
|
|
942
|
+
nom: 'Liste sans colonnes',
|
|
943
|
+
resume: 'Même cadre que le tableau, mais rien à aligner : ni en-tête, ni gabarit.',
|
|
944
|
+
code: `<List>
|
|
945
|
+
<ListRow>
|
|
946
|
+
<ListMain><FileType format="pdf" /><ListName>…</ListName></ListMain>
|
|
947
|
+
<Meta>2,4 Mo</Meta>
|
|
948
|
+
</ListRow>
|
|
949
|
+
</List>`,
|
|
950
|
+
rendu: (
|
|
951
|
+
<List>
|
|
952
|
+
<ListRow>
|
|
953
|
+
<ListMain>
|
|
954
|
+
<FileType format="pdf" />
|
|
955
|
+
<ListName>Contrat-cadre-Syntec.pdf</ListName>
|
|
956
|
+
</ListMain>
|
|
957
|
+
<Meta>2,4 Mo</Meta>
|
|
958
|
+
</ListRow>
|
|
959
|
+
<ListRow upload>
|
|
960
|
+
<ListMain>
|
|
961
|
+
<FileType format="docx" />
|
|
962
|
+
<ListName>Assignation-2024-114.docx</ListName>
|
|
963
|
+
</ListMain>
|
|
964
|
+
<Progress valeur={0.4} label="Envoi" />
|
|
965
|
+
</ListRow>
|
|
966
|
+
</List>
|
|
967
|
+
),
|
|
968
|
+
},
|
|
969
|
+
{
|
|
970
|
+
id: 'meta',
|
|
971
|
+
nom: 'Métadonnées',
|
|
972
|
+
resume:
|
|
973
|
+
'Second registre mono, en BAS DE CASSE : taille, date, latence. Ni capitales ni interlettrage — c’est ce qui le distingue de « Mono », et une date en capitales espacées devient illisible.',
|
|
974
|
+
code: `<MetaRow><Meta>12 mars 2026</Meta><Meta taille="sm">1,2 s</Meta></MetaRow>`,
|
|
975
|
+
rendu: (
|
|
976
|
+
<MetaRow>
|
|
977
|
+
<Meta taille="md">12 mars 2026</Meta>
|
|
978
|
+
<Meta>14:07:22</Meta>
|
|
979
|
+
<Meta taille="sm">1,24 s</Meta>
|
|
980
|
+
<Meta discret>2,4 Mo</Meta>
|
|
981
|
+
</MetaRow>
|
|
982
|
+
),
|
|
983
|
+
},
|
|
984
|
+
{
|
|
985
|
+
id: 'kv',
|
|
986
|
+
nom: 'Clé-valeur',
|
|
987
|
+
resume:
|
|
988
|
+
'⚠️ La CLÉ est en mono, en accent, et se coupe ; la VALEUR est en texte courant, en gris, et ne se coupe jamais. Inverser les deux change ce que l’œil balaie en premier.',
|
|
989
|
+
code: `<KV variante="grid">
|
|
990
|
+
<KVRow cle="ORGANISATION">Cabinet Duflot & Associés</KVRow>
|
|
991
|
+
<KVRow cle="IDENTIFIANT"><Break>zvr-8841-x-3c9f2a</Break></KVRow>
|
|
992
|
+
</KV>`,
|
|
993
|
+
rendu: (
|
|
994
|
+
<KV variante="grid">
|
|
995
|
+
<KVRow cle="ORGANISATION">Cabinet Duflot & Associés</KVRow>
|
|
996
|
+
<KVRow cle="IDENTIFIANT">
|
|
997
|
+
<Break>zvr-8841-x-3c9f2a</Break>
|
|
998
|
+
</KVRow>
|
|
999
|
+
<KVRow cle="FORMULE">Étude · 12 postes</KVRow>
|
|
1000
|
+
</KV>
|
|
1001
|
+
),
|
|
1002
|
+
},
|
|
1003
|
+
{
|
|
1004
|
+
id: 'avatar-chip',
|
|
1005
|
+
nom: 'Pastilles et catégories',
|
|
1006
|
+
resume:
|
|
1007
|
+
'Avatar CARRÉ, jamais rond : le rond est un usage de réseau social. ⚠️ Sa teinte dit la NATURE de l’entité — la changer, c’est changer le sens.',
|
|
1008
|
+
code: `<Avatar ton="accent">MD</Avatar>
|
|
1009
|
+
<ChipRow><Chip>Droit social</Chip><Chip variante="outline">2024</Chip></ChipRow>`,
|
|
1010
|
+
rendu: (
|
|
1011
|
+
<div style={{ display: 'grid', gap: 12 }}>
|
|
1012
|
+
<div style={{ display: 'flex', gap: 8 }}>
|
|
1013
|
+
<Avatar ton="accent">MD</Avatar>
|
|
1014
|
+
<Avatar ton="ink">JL</Avatar>
|
|
1015
|
+
<Avatar ton="soft">AB</Avatar>
|
|
1016
|
+
<Avatar ton="soft" petit>
|
|
1017
|
+
ZV
|
|
1018
|
+
</Avatar>
|
|
1019
|
+
</div>
|
|
1020
|
+
<ChipRow>
|
|
1021
|
+
<Chip>Droit social</Chip>
|
|
1022
|
+
<Chip>Convention collective</Chip>
|
|
1023
|
+
<Chip variante="mono">2024-114</Chip>
|
|
1024
|
+
<Chip variante="outline">Archivé</Chip>
|
|
1025
|
+
</ChipRow>
|
|
1026
|
+
</div>
|
|
1027
|
+
),
|
|
1028
|
+
},
|
|
1029
|
+
{
|
|
1030
|
+
id: 'citations-src',
|
|
1031
|
+
nom: 'Citations',
|
|
1032
|
+
resume:
|
|
1033
|
+
'Référence cliquable, dans le fil du texte ou groupée en pied de réponse : elle ouvre la feuille de source.',
|
|
1034
|
+
code: `<CiteRow><Cite>1</Cite><Cite>2</Cite></CiteRow>`,
|
|
1035
|
+
rendu: (
|
|
1036
|
+
<CiteRow>
|
|
1037
|
+
<Cite>Convention Syntec, art. 15</Cite>
|
|
1038
|
+
<Cite>Cass. soc., 4 déc. 2019</Cite>
|
|
1039
|
+
</CiteRow>
|
|
1040
|
+
),
|
|
1041
|
+
},
|
|
1042
|
+
{
|
|
1043
|
+
id: 'formats',
|
|
1044
|
+
nom: 'Format de fichier',
|
|
1045
|
+
resume:
|
|
1046
|
+
'⚠️ Ces deux couleurs ne viennent PAS de la charte : ce sont celles que le monde associe à ces formats, et les détourner rendrait les vignettes illisibles au premier coup d’œil. Seul usage autorisé : la pastille de format.',
|
|
1047
|
+
code: `<FileType format="pdf" /> <FileType format="docx" />`,
|
|
1048
|
+
rendu: (
|
|
1049
|
+
<div style={{ display: 'flex', gap: 8 }}>
|
|
1050
|
+
<FileType format="pdf" />
|
|
1051
|
+
<FileType format="docx" />
|
|
1052
|
+
</div>
|
|
1053
|
+
),
|
|
1054
|
+
},
|
|
1055
|
+
],
|
|
1056
|
+
},
|
|
1057
|
+
|
|
1058
|
+
{
|
|
1059
|
+
id: 'navigation',
|
|
1060
|
+
nom: 'Navigation',
|
|
1061
|
+
couche: 'applicatif',
|
|
1062
|
+
css: 'navigation.css',
|
|
1063
|
+
resume:
|
|
1064
|
+
"Rien n'a d'état propre : l'entrée courante, la page courante et l'item sélectionné viennent de l'appelant — c'est ce qui permet à une colonne de navigation Next de rester rendue au serveur.",
|
|
1065
|
+
exemples: [
|
|
1066
|
+
{
|
|
1067
|
+
id: 'sidebar',
|
|
1068
|
+
nom: 'Colonne latérale',
|
|
1069
|
+
resume:
|
|
1070
|
+
'Une colonne, un filet, rien d’autre. « courant » pose aria-current="page" en plus de la classe : sans lui, l’entrée courante est marquée pour l’œil et muette au lecteur d’écran.',
|
|
1071
|
+
code: `<Sidebar>
|
|
1072
|
+
<SidebarHead>…</SidebarHead>
|
|
1073
|
+
<SideGroup titre="Espace">
|
|
1074
|
+
<SideLink href="/bibliotheque" courant num="01">Bibliothèque</SideLink>
|
|
1075
|
+
</SideGroup>
|
|
1076
|
+
<SidebarFoot>…</SidebarFoot>
|
|
1077
|
+
</Sidebar>`,
|
|
1078
|
+
rendu: (
|
|
1079
|
+
<Sidebar style={{ height: 260 }}>
|
|
1080
|
+
<SidebarHead>
|
|
1081
|
+
<Mono ton="ink">ZEVRA</Mono>
|
|
1082
|
+
</SidebarHead>
|
|
1083
|
+
<SideGroup titre="Espace">
|
|
1084
|
+
<SideLink href="#" courant num="01">
|
|
1085
|
+
Bibliothèque
|
|
1086
|
+
</SideLink>
|
|
1087
|
+
<SideLink href="#" num="02">
|
|
1088
|
+
Répertoire
|
|
1089
|
+
</SideLink>
|
|
1090
|
+
<SideLink href="#" num="03">
|
|
1091
|
+
Import
|
|
1092
|
+
</SideLink>
|
|
1093
|
+
</SideGroup>
|
|
1094
|
+
<SidebarFoot>
|
|
1095
|
+
<Meta discret>v2.4.1</Meta>
|
|
1096
|
+
</SidebarFoot>
|
|
1097
|
+
</Sidebar>
|
|
1098
|
+
),
|
|
1099
|
+
},
|
|
1100
|
+
{
|
|
1101
|
+
id: 'topbar',
|
|
1102
|
+
nom: 'Barre supérieure',
|
|
1103
|
+
resume:
|
|
1104
|
+
'⚠️ Elle REMPLACE la colonne sous le point de rupture : masquée au-dessus, elle n’apparaît que dans le bloc mobile du CSS. Ne jamais la conditionner en JavaScript — le serveur ne connaît pas la largeur de l’écran.',
|
|
1105
|
+
code: `<Topbar>
|
|
1106
|
+
<TopbarBurger aria-label="Ouvrir la navigation">☰</TopbarBurger>
|
|
1107
|
+
<TopbarLogo><img src="/logo.svg" alt="" /></TopbarLogo>
|
|
1108
|
+
<TopbarTitle>Bibliothèque</TopbarTitle>
|
|
1109
|
+
</Topbar>`,
|
|
1110
|
+
rendu: (
|
|
1111
|
+
<Topbar style={{ display: 'flex', position: 'static' }}>
|
|
1112
|
+
<TopbarTitle>Bibliothèque</TopbarTitle>
|
|
1113
|
+
</Topbar>
|
|
1114
|
+
),
|
|
1115
|
+
},
|
|
1116
|
+
{
|
|
1117
|
+
id: 'crumbs',
|
|
1118
|
+
nom: 'Fil d’Ariane',
|
|
1119
|
+
resume:
|
|
1120
|
+
'⚠️ Le fil ENTIER est cliquable — c’est un seul nœud, pas une liste de liens. Le DERNIER segment est plus sombre : c’est celui qu’on lit, le reste n’est qu’un chemin.',
|
|
1121
|
+
code: `<Crumbs href="/dossiers/2024" segments={['Dossiers', '2024', 'Duflot']} />`,
|
|
1122
|
+
rendu: <Crumbs href="#" segments={['Dossiers', '2024', 'Duflot & Associés']} />,
|
|
1123
|
+
},
|
|
1124
|
+
{
|
|
1125
|
+
id: 'onglets',
|
|
1126
|
+
nom: 'Onglets',
|
|
1127
|
+
resume:
|
|
1128
|
+
'⚠️ Il n’existe AUCUNE classe d’onglet dans le CSS : une rangée d’onglets EST une rangée de puces. Ce composant n’ajoute que l’ARIA, pas un style de plus. L’onglet souligné reste à arbitrer avec la DA.',
|
|
1129
|
+
code: `<Tabs actif={actif} onChange={setActif} onglets={[
|
|
1130
|
+
{ id: 'apercu', label: 'Aperçu' },
|
|
1131
|
+
{ id: 'sources', label: 'Sources', compte: 12 },
|
|
1132
|
+
]} />`,
|
|
1133
|
+
rendu: <DemoTabs />,
|
|
1134
|
+
},
|
|
1135
|
+
{
|
|
1136
|
+
id: 'pagination',
|
|
1137
|
+
nom: 'Pagination',
|
|
1138
|
+
resume:
|
|
1139
|
+
'⚠️ Une flèche désactivée reste ATTEIGNABLE au clavier (aria-disabled, pas disabled) : sinon elle disparaît du parcours sans être annoncée.',
|
|
1140
|
+
code: `<Pager>
|
|
1141
|
+
<PagerArrow aria-label="Page précédente" desactive>‹</PagerArrow>
|
|
1142
|
+
<PagerRank>p. 12 / 31</PagerRank>
|
|
1143
|
+
<PagerArrow aria-label="Page suivante">›</PagerArrow>
|
|
1144
|
+
</Pager>
|
|
1145
|
+
|
|
1146
|
+
<Pager pied>
|
|
1147
|
+
<Meta>128 documents</Meta>
|
|
1148
|
+
<PagerStep>‹ Précédent · Suivant ›</PagerStep>
|
|
1149
|
+
</Pager>`,
|
|
1150
|
+
rendu: (
|
|
1151
|
+
<div style={{ display: 'grid', gap: 12 }}>
|
|
1152
|
+
<Pager>
|
|
1153
|
+
<PagerArrow aria-label="Page précédente" desactive>
|
|
1154
|
+
‹
|
|
1155
|
+
</PagerArrow>
|
|
1156
|
+
<PagerRank>p. 12 / 31</PagerRank>
|
|
1157
|
+
<PagerArrow aria-label="Page suivante">›</PagerArrow>
|
|
1158
|
+
</Pager>
|
|
1159
|
+
<Pager pied>
|
|
1160
|
+
<Meta>128 documents</Meta>
|
|
1161
|
+
<PagerStep>‹ Précédent · Suivant ›</PagerStep>
|
|
1162
|
+
</Pager>
|
|
1163
|
+
</div>
|
|
1164
|
+
),
|
|
1165
|
+
},
|
|
1166
|
+
{
|
|
1167
|
+
id: 'cite-panel',
|
|
1168
|
+
nom: 'Feuille de source',
|
|
1169
|
+
resume: 'Le panneau qu’ouvre une citation : l’extrait, sa référence, ses actions.',
|
|
1170
|
+
code: `<CitePanel titre="Convention Syntec, art. 15"
|
|
1171
|
+
actions={<ChromeButton aria-label="Fermer">×</ChromeButton>}
|
|
1172
|
+
pied={<Button compact bloc>Ouvrir le document</Button>}>
|
|
1173
|
+
…
|
|
1174
|
+
</CitePanel>`,
|
|
1175
|
+
rendu: (
|
|
1176
|
+
<CitePanel
|
|
1177
|
+
titre="Convention Syntec, art. 15"
|
|
1178
|
+
actions={<ChromeButton aria-label="Fermer la feuille">×</ChromeButton>}
|
|
1179
|
+
pied={<Button compact>Ouvrir le document</Button>}
|
|
1180
|
+
style={{ position: 'static', width: 'auto', height: 'auto' }}
|
|
1181
|
+
>
|
|
1182
|
+
<Body taille="dense">
|
|
1183
|
+
« Le délai de préavis est de trois mois pour les salariés relevant de la
|
|
1184
|
+
position 3. »
|
|
1185
|
+
</Body>
|
|
1186
|
+
</CitePanel>
|
|
1187
|
+
),
|
|
1188
|
+
},
|
|
1189
|
+
{
|
|
1190
|
+
id: 'defilement',
|
|
1191
|
+
nom: 'Zone défilante',
|
|
1192
|
+
resume:
|
|
1193
|
+
'⚠️ Porte min-height: 0 ET min-width: 0. Sans les deux, un enfant flex refuse de rétrécir sous son contenu : la zone déborde et c’est la PAGE qui part en défilement horizontal. La faute la plus fréquente de ce gabarit.',
|
|
1194
|
+
code: `<Scroll habillee>…</Scroll>`,
|
|
1195
|
+
rendu: (
|
|
1196
|
+
<Scroll habillee style={{ height: 90, border: '1px solid var(--line)', padding: 10 }}>
|
|
1197
|
+
<Body taille="dense">
|
|
1198
|
+
L’habillage de barre est OPT-IN : sur macOS et iOS, habiller la barre la
|
|
1199
|
+
rend permanente et elle mange une colonne. Par défaut, la barre native
|
|
1200
|
+
du système est conservée.
|
|
1201
|
+
</Body>
|
|
1202
|
+
</Scroll>
|
|
1203
|
+
),
|
|
1204
|
+
},
|
|
1205
|
+
],
|
|
1206
|
+
},
|
|
1207
|
+
|
|
1208
|
+
{
|
|
1209
|
+
id: 'enrobages',
|
|
1210
|
+
nom: 'Enrobages',
|
|
1211
|
+
couche: 'commun',
|
|
1212
|
+
css: 'overlays.css',
|
|
1213
|
+
resume:
|
|
1214
|
+
"Le seul module marqué « use client ». Tout ce qui vit ici vole le regard à la page : il faut Échap, un piège de focus et un verrou de défilement — rien de cela ne s'écrit en CSS. Le hook « useOverlay » est exporté pour les enrobages maison.",
|
|
1215
|
+
exemples: [
|
|
1216
|
+
{
|
|
1217
|
+
id: 'tiroir',
|
|
1218
|
+
nom: 'Tiroir',
|
|
1219
|
+
resume:
|
|
1220
|
+
'⚠️ Le tiroir n’existe QUE sous le point de rupture : au-dessus, la navigation est une colonne et il n’y a rien à ouvrir. Réduis la fenêtre pour le voir.',
|
|
1221
|
+
code: `<Drawer ouvert={ouvert} onClose={() => setOuvert(false)}
|
|
1222
|
+
fermeture={<ChromeButton aria-label="Fermer" onClick={…}>×</ChromeButton>}>
|
|
1223
|
+
<Sidebar>…</Sidebar>
|
|
1224
|
+
</Drawer>`,
|
|
1225
|
+
rendu: <DemoDrawer />,
|
|
1226
|
+
},
|
|
1227
|
+
{
|
|
1228
|
+
id: 'modale',
|
|
1229
|
+
nom: 'Modale',
|
|
1230
|
+
resume:
|
|
1231
|
+
'Fermée, elle n’est pas rendue du tout : son contenu — un formulaire, le plus souvent — ne doit pas rester dans le DOM à l’état fantôme. « danger » met l’avertissement dans le filet HAUT, pas dans un fond rouge qui rendrait le texte pénible à lire.',
|
|
1232
|
+
code: `<Modal ouvert={ouvert} onClose={…} titre="Supprimer" danger
|
|
1233
|
+
pied={<ButtonRow>…</ButtonRow>}>
|
|
1234
|
+
…
|
|
1235
|
+
</Modal>`,
|
|
1236
|
+
rendu: <DemoModal />,
|
|
1237
|
+
},
|
|
1238
|
+
{
|
|
1239
|
+
id: 'toast',
|
|
1240
|
+
nom: 'Notification',
|
|
1241
|
+
resume:
|
|
1242
|
+
'⚠️ Ne remplace PAS « Alert » : une erreur de formulaire se dit à côté du champ, jamais dans un coin qui disparaît. Réserver à ce qui est vrai APRÈS COUP — « copié », « enregistré », « envoyé ».',
|
|
1243
|
+
code: `<Toasts>
|
|
1244
|
+
<Toast ton="ok">Lien copié.</Toast>
|
|
1245
|
+
</Toasts>`,
|
|
1246
|
+
rendu: (
|
|
1247
|
+
<div style={{ display: 'grid', gap: 8 }}>
|
|
1248
|
+
<Toast ton="ok">Lien copié dans le presse-papiers.</Toast>
|
|
1249
|
+
<Toast>Brouillon enregistré.</Toast>
|
|
1250
|
+
<Toast ton="err">L’envoi a échoué. Réessayez.</Toast>
|
|
1251
|
+
</div>
|
|
1252
|
+
),
|
|
1253
|
+
},
|
|
1254
|
+
],
|
|
1255
|
+
},
|
|
1256
|
+
|
|
1257
|
+
{
|
|
1258
|
+
id: 'icones',
|
|
1259
|
+
nom: 'Icônes',
|
|
1260
|
+
couche: 'commun',
|
|
1261
|
+
css: 'icons.css',
|
|
1262
|
+
resume:
|
|
1263
|
+
"34 icônes TRACÉES, jamais pleines : trait 2, bouts arrondis, grille de 24. Elles héritent de la couleur du texte — c'est ce qui leur permet de fonctionner sur papier comme sur la bande manifeste sans déclinaison. Le tracé est inliné : rien à résoudre côté bundler, rien à autoriser côté CSP.",
|
|
1264
|
+
exemples: [
|
|
1265
|
+
{
|
|
1266
|
+
id: 'jeu',
|
|
1267
|
+
nom: 'Le jeu complet',
|
|
1268
|
+
resume:
|
|
1269
|
+
'Sans « titre », l’icône est DÉCORATIVE et masquée aux lecteurs d’écran — le bon défaut : accolée à un libellé qui la répète, elle crée une annonce double.',
|
|
1270
|
+
code: `<Icon nom="recherche" />
|
|
1271
|
+
<Icon nom="document" taille="lg" />
|
|
1272
|
+
<Icon nom="alerte" titre="Alerte" />`,
|
|
1273
|
+
rendu: (
|
|
1274
|
+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 14 }}>
|
|
1275
|
+
{ICONES.map((id) => (
|
|
1276
|
+
<span
|
|
1277
|
+
key={id}
|
|
1278
|
+
title={NOMS_ICONES[id]}
|
|
1279
|
+
style={{ display: 'grid', gap: 4, justifyItems: 'center', width: 64 }}
|
|
1280
|
+
>
|
|
1281
|
+
<Icon nom={id} taille="lg" />
|
|
1282
|
+
<span className="zv-meta zv-meta--sm zv-trunc" style={{ maxWidth: 64 }}>
|
|
1283
|
+
{id}
|
|
1284
|
+
</span>
|
|
1285
|
+
</span>
|
|
1286
|
+
))}
|
|
1287
|
+
</div>
|
|
1288
|
+
),
|
|
1289
|
+
},
|
|
1290
|
+
{
|
|
1291
|
+
id: 'tailles',
|
|
1292
|
+
nom: 'Tailles',
|
|
1293
|
+
resume:
|
|
1294
|
+
'Le trait s’épaissit optiquement quand l’icône rétrécit : « sm » et « xl » le compensent. Une rangée de tailles mêlées sans cette compensation paraît déséquilibrée.',
|
|
1295
|
+
code: `<Icon nom="document" taille="sm" /> <Icon nom="document" />
|
|
1296
|
+
<Icon nom="document" taille="lg" /> <Icon nom="document" taille="xl" />`,
|
|
1297
|
+
rendu: (
|
|
1298
|
+
<div style={{ display: 'flex', gap: 14, alignItems: 'center' }}>
|
|
1299
|
+
<Icon nom={ICONES[0]!} taille="sm" />
|
|
1300
|
+
<Icon nom={ICONES[0]!} />
|
|
1301
|
+
<Icon nom={ICONES[0]!} taille="lg" />
|
|
1302
|
+
<Icon nom={ICONES[0]!} taille="xl" />
|
|
1303
|
+
<Icon nom={ICONES[0]!} taille="lg" discret />
|
|
1304
|
+
</div>
|
|
1305
|
+
),
|
|
1306
|
+
},
|
|
1307
|
+
],
|
|
1308
|
+
},
|
|
1309
|
+
|
|
1310
|
+
{
|
|
1311
|
+
id: 'mise-en-page',
|
|
1312
|
+
nom: 'Mise en page',
|
|
1313
|
+
couche: 'vitrine',
|
|
1314
|
+
css: 'layout.css',
|
|
1315
|
+
resume:
|
|
1316
|
+
"Gouttières, grille 12, sections, frise d'étapes, pieds de page. Une application n'a aucune raison de charger ce module. Les cinq étages d'empilement y sont nommés une fois pour toutes : un z-index écrit à la main finit toujours par passer devant ce qu'il ne fallait pas.",
|
|
1317
|
+
exemples: [
|
|
1318
|
+
{
|
|
1319
|
+
id: 'gouttiere',
|
|
1320
|
+
nom: 'Gouttière',
|
|
1321
|
+
resume:
|
|
1322
|
+
'La largeur de colonne du système. Toute la vitrine s’aligne dessus : un bloc qui pose sa propre largeur maximale se décale de tous les autres.',
|
|
1323
|
+
code: `<Page>
|
|
1324
|
+
<Gutter>…</Gutter>
|
|
1325
|
+
<Gutter variante="wide">…</Gutter>
|
|
1326
|
+
<Gutter variante="chrome"><Nav>…</Nav></Gutter>
|
|
1327
|
+
</Page>`,
|
|
1328
|
+
rendu: (
|
|
1329
|
+
<Gutter style={{ border: '1px dashed var(--accent-line)', padding: 12 }}>
|
|
1330
|
+
<Body taille="dense">Contenu aligné sur la colonne du système.</Body>
|
|
1331
|
+
</Gutter>
|
|
1332
|
+
),
|
|
1333
|
+
},
|
|
1334
|
+
{
|
|
1335
|
+
id: 'grille',
|
|
1336
|
+
nom: 'Grilles',
|
|
1337
|
+
resume:
|
|
1338
|
+
'⚠️ Seules les portées 3, 4, 6, 8 et 9 existent : ce sont les découpes employées par la vitrine. En ajouter une revient à ajouter une mise en page — c’est une décision de DA, pas une utilité.',
|
|
1339
|
+
code: `<Grid12>
|
|
1340
|
+
<Col span={8}>…</Col>
|
|
1341
|
+
<Col span={4}>…</Col>
|
|
1342
|
+
</Grid12>
|
|
1343
|
+
|
|
1344
|
+
<Grid colonnes={3}>…</Grid>`,
|
|
1345
|
+
rendu: (
|
|
1346
|
+
<div style={{ display: 'grid', gap: 12 }}>
|
|
1347
|
+
<Grid12>
|
|
1348
|
+
<Col span={8}>
|
|
1349
|
+
<Card>
|
|
1350
|
+
<Body taille="dense">span 8</Body>
|
|
1351
|
+
</Card>
|
|
1352
|
+
</Col>
|
|
1353
|
+
<Col span={4}>
|
|
1354
|
+
<Card>
|
|
1355
|
+
<Body taille="dense">span 4</Body>
|
|
1356
|
+
</Card>
|
|
1357
|
+
</Col>
|
|
1358
|
+
</Grid12>
|
|
1359
|
+
<Grid colonnes={3}>
|
|
1360
|
+
{['Indexer', 'Interroger', 'Vérifier'].map((t) => (
|
|
1361
|
+
<Card key={t}>
|
|
1362
|
+
<Body taille="dense">{t}</Body>
|
|
1363
|
+
</Card>
|
|
1364
|
+
))}
|
|
1365
|
+
</Grid>
|
|
1366
|
+
</div>
|
|
1367
|
+
),
|
|
1368
|
+
},
|
|
1369
|
+
{
|
|
1370
|
+
id: 'sections',
|
|
1371
|
+
nom: 'Sections',
|
|
1372
|
+
resume:
|
|
1373
|
+
'Le bloc « aside » tombe SOUS le titre en dessous du point de rupture — c’est le CSS qui le fait, pas une condition de rendu.',
|
|
1374
|
+
code: `<SectionHead aside={<LinkArrow href="/methode">Tout voir</LinkArrow>}>
|
|
1375
|
+
<Kicker>Méthode</Kicker>
|
|
1376
|
+
<H2>Trois étapes, rien de plus</H2>
|
|
1377
|
+
</SectionHead>
|
|
1378
|
+
<SectionRule />`,
|
|
1379
|
+
rendu: (
|
|
1380
|
+
<div>
|
|
1381
|
+
<SectionHead aside={<LinkArrow href="#">Tout voir</LinkArrow>}>
|
|
1382
|
+
<Kicker>Méthode</Kicker>
|
|
1383
|
+
<Heading taille="h2-sm">Trois étapes, rien de plus</Heading>
|
|
1384
|
+
</SectionHead>
|
|
1385
|
+
<SectionRule />
|
|
1386
|
+
</div>
|
|
1387
|
+
),
|
|
1388
|
+
},
|
|
1389
|
+
{
|
|
1390
|
+
id: 'sheet-section',
|
|
1391
|
+
nom: 'Section de document',
|
|
1392
|
+
resume: 'Un intitulé mono, un filet, du corps — pour un document long.',
|
|
1393
|
+
code: `<SheetSection titre="ENGAGEMENTS">…</SheetSection>`,
|
|
1394
|
+
rendu: (
|
|
1395
|
+
<SheetSection titre="ENGAGEMENTS">
|
|
1396
|
+
<Body taille="dense">
|
|
1397
|
+
Les données restent hébergées en France et ne sont jamais versées à un
|
|
1398
|
+
entraînement.
|
|
1399
|
+
</Body>
|
|
1400
|
+
</SheetSection>
|
|
1401
|
+
),
|
|
1402
|
+
},
|
|
1403
|
+
{
|
|
1404
|
+
id: 'nav-vitrine',
|
|
1405
|
+
nom: 'Navigation vitrine',
|
|
1406
|
+
resume:
|
|
1407
|
+
'⚠️ « NavLink » est la navigation de VITRINE ; l’entrée applicative s’appelle « SideLink ». Les deux ne doivent jamais se recouvrir. La rangée disparaît sous le point de rupture, remplacée par le burger.',
|
|
1408
|
+
code: `<Nav>
|
|
1409
|
+
<NavLogo>…</NavLogo>
|
|
1410
|
+
<NavLinks>
|
|
1411
|
+
<NavLink href="/produit">Produit</NavLink>
|
|
1412
|
+
<NavLink href="/connexion" discret>Se connecter</NavLink>
|
|
1413
|
+
</NavLinks>
|
|
1414
|
+
<NavBurger aria-label="Ouvrir le menu">…</NavBurger>
|
|
1415
|
+
</Nav>`,
|
|
1416
|
+
rendu: (
|
|
1417
|
+
<Nav style={{ border: '1px solid var(--hairline)', padding: '0 12px' }}>
|
|
1418
|
+
<NavLogo>
|
|
1419
|
+
<Mono ton="ink">ZEVRA</Mono>
|
|
1420
|
+
</NavLogo>
|
|
1421
|
+
<NavLinks>
|
|
1422
|
+
<NavLink href="#">Produit</NavLink>
|
|
1423
|
+
<NavLink href="#">Méthode</NavLink>
|
|
1424
|
+
<NavLink href="#" discret>
|
|
1425
|
+
Se connecter
|
|
1426
|
+
</NavLink>
|
|
1427
|
+
</NavLinks>
|
|
1428
|
+
</Nav>
|
|
1429
|
+
),
|
|
1430
|
+
},
|
|
1431
|
+
{
|
|
1432
|
+
id: 'etapes',
|
|
1433
|
+
nom: 'Frise d’étapes',
|
|
1434
|
+
resume:
|
|
1435
|
+
'Le point de méthode est le TROISIÈME et dernier emploi autorisé du spectre (avec le filet de kicker et la barre de progression). Jamais en fond, jamais en texte.',
|
|
1436
|
+
code: `<Steps avancement={0.66}>
|
|
1437
|
+
<Step>Indexer</Step>
|
|
1438
|
+
<Step teinte="violet">Interroger</Step>
|
|
1439
|
+
<Step teinte="cyan">Vérifier</Step>
|
|
1440
|
+
</Steps>`,
|
|
1441
|
+
rendu: (
|
|
1442
|
+
<Steps avancement={0.66}>
|
|
1443
|
+
<Step>
|
|
1444
|
+
<Body taille="dense">Indexer</Body>
|
|
1445
|
+
</Step>
|
|
1446
|
+
<Step teinte="violet">
|
|
1447
|
+
<Body taille="dense">Interroger</Body>
|
|
1448
|
+
</Step>
|
|
1449
|
+
<Step teinte="cyan">
|
|
1450
|
+
<Body taille="dense">Vérifier</Body>
|
|
1451
|
+
</Step>
|
|
1452
|
+
</Steps>
|
|
1453
|
+
),
|
|
1454
|
+
},
|
|
1455
|
+
{
|
|
1456
|
+
id: 'stats-page',
|
|
1457
|
+
nom: 'Ligne de statistiques',
|
|
1458
|
+
resume: 'La rangée de chiffres d’une page vitrine, sous un hero ou dans une bande.',
|
|
1459
|
+
code: `<Stats>
|
|
1460
|
+
<Stat valeur="4 812">documents indexés</Stat>
|
|
1461
|
+
<Stat valeur="1,2 s">latence médiane</Stat>
|
|
1462
|
+
</Stats>`,
|
|
1463
|
+
rendu: (
|
|
1464
|
+
<Stats>
|
|
1465
|
+
<Stat valeur="4 812">documents indexés</Stat>
|
|
1466
|
+
<Stat valeur="1,2 s">latence médiane</Stat>
|
|
1467
|
+
<Stat valeur="100 %">réponses sourcées</Stat>
|
|
1468
|
+
</Stats>
|
|
1469
|
+
),
|
|
1470
|
+
},
|
|
1471
|
+
{
|
|
1472
|
+
id: 'hero-cta',
|
|
1473
|
+
nom: 'Blocs centrés',
|
|
1474
|
+
resume: 'Le hero d’ouverture et le bloc d’appel à l’action de fin de page.',
|
|
1475
|
+
code: `<Hero><Kicker>…</Kicker><H1>…</H1><Lead>…</Lead></Hero>
|
|
1476
|
+
<CTA><Heading taille="h2-cta">…</Heading><Button>…</Button></CTA>`,
|
|
1477
|
+
rendu: (
|
|
1478
|
+
<CTA style={{ padding: '24px 0' }}>
|
|
1479
|
+
<Heading2Cta />
|
|
1480
|
+
<ButtonRow>
|
|
1481
|
+
<Button>Demander une démonstration</Button>
|
|
1482
|
+
</ButtonRow>
|
|
1483
|
+
</CTA>
|
|
1484
|
+
),
|
|
1485
|
+
},
|
|
1486
|
+
{
|
|
1487
|
+
id: 'pieds',
|
|
1488
|
+
nom: 'Pieds de page',
|
|
1489
|
+
resume:
|
|
1490
|
+
'⚠️ « ProofBar » ne cite que de VRAIS clients : un nom inventé dans une bande de preuve sociale est un faux, pas un placeholder.',
|
|
1491
|
+
code: `<Footer marque={<><Mono ton="ink">ZEVRA</Mono><Tagline>…</Tagline></>}>
|
|
1492
|
+
…
|
|
1493
|
+
</Footer>
|
|
1494
|
+
<FooterMini logo={<Mono ton="ink">ZEVRA</Mono>}>© 2026</FooterMini>`,
|
|
1495
|
+
rendu: (
|
|
1496
|
+
<FooterMini logo={<Mono ton="ink">ZEVRA</Mono>}>
|
|
1497
|
+
<Note>© 2026 Zevra · Hébergé en France</Note>
|
|
1498
|
+
</FooterMini>
|
|
1499
|
+
),
|
|
1500
|
+
},
|
|
1501
|
+
{
|
|
1502
|
+
id: 'visibilite',
|
|
1503
|
+
nom: 'Visibilité',
|
|
1504
|
+
resume:
|
|
1505
|
+
'⚠️ Rendu par le CSS, PAS par JavaScript : le serveur ne connaît pas la largeur de l’écran, et conditionner le rendu produit un décalage d’hydratation. Les deux nœuds sont posés, le CSS en masque un.',
|
|
1506
|
+
code: `<OnlyDesktop><NavLinks>…</NavLinks></OnlyDesktop>
|
|
1507
|
+
<OnlyMobile><NavBurger aria-label="Menu">…</NavBurger></OnlyMobile>`,
|
|
1508
|
+
rendu: (
|
|
1509
|
+
<div style={{ display: 'flex', gap: 10 }}>
|
|
1510
|
+
<span className="zv-only-desktop">
|
|
1511
|
+
<Badge ton="accent">VISIBLE ≥ 768</Badge>
|
|
1512
|
+
</span>
|
|
1513
|
+
<span className="zv-only-mobile">
|
|
1514
|
+
<Badge ton="accent">VISIBLE < 768</Badge>
|
|
1515
|
+
</span>
|
|
1516
|
+
</div>
|
|
1517
|
+
),
|
|
1518
|
+
},
|
|
1519
|
+
],
|
|
1520
|
+
},
|
|
1521
|
+
|
|
1522
|
+
{
|
|
1523
|
+
id: 'surfaces',
|
|
1524
|
+
nom: 'Surfaces',
|
|
1525
|
+
couche: 'vitrine',
|
|
1526
|
+
css: 'surfaces.css + motion.css',
|
|
1527
|
+
resume:
|
|
1528
|
+
"Ce qui distingue une page Zevra d'une page blanche bien composée. ⚠️ Tout y est DÉCORATIF : posé en absolu derrière le contenu, sans capter le pointeur, masqué aux lecteurs d'écran — et suppose un parent « position: relative; overflow: hidden ».",
|
|
1529
|
+
exemples: [
|
|
1530
|
+
{
|
|
1531
|
+
id: 'bande',
|
|
1532
|
+
nom: 'Bande manifeste',
|
|
1533
|
+
resume:
|
|
1534
|
+
'⚠️ LA surface sombre du système. Les surfaces sombres se font avec elle et --accent-on-dark, JAMAIS en inversant les neutres : les contrastes ont été réglés dans ce sens-là et pas dans l’autre.',
|
|
1535
|
+
code: `<Band lueur filigrane={<Glyph variante="watermark">…</Glyph>}>
|
|
1536
|
+
<Kicker surSombre>Souveraineté</Kicker>
|
|
1537
|
+
<H2>Vos pièces ne quittent pas la France.</H2>
|
|
1538
|
+
</Band>`,
|
|
1539
|
+
surSombre: true,
|
|
1540
|
+
rendu: (
|
|
1541
|
+
<Band lueur>
|
|
1542
|
+
<Kicker surSombre>Souveraineté</Kicker>
|
|
1543
|
+
<Heading taille="h2-sm">Vos pièces ne quittent pas la France.</Heading>
|
|
1544
|
+
<Body>
|
|
1545
|
+
Infrastructure dédiée, chiffrement au repos, aucun transfert hors Union
|
|
1546
|
+
européenne.
|
|
1547
|
+
</Body>
|
|
1548
|
+
<div style={{ display: 'flex', gap: 8, marginTop: 12 }}>
|
|
1549
|
+
<Tag surSombre etat="ok">CHIFFRÉ</Tag>
|
|
1550
|
+
<Tag surSombre>OVHCLOUD · GRA</Tag>
|
|
1551
|
+
</div>
|
|
1552
|
+
</Band>
|
|
1553
|
+
),
|
|
1554
|
+
},
|
|
1555
|
+
{
|
|
1556
|
+
id: 'panneau-navy',
|
|
1557
|
+
nom: 'Panneau sombre',
|
|
1558
|
+
resume:
|
|
1559
|
+
'Plus petit qu’une bande : une carte, un encart. « TextOnDark » repeint un texte isolé — « Band » le fait déjà pour son contenu courant, l’ajouter dedans ne sert à rien.',
|
|
1560
|
+
code: `<PanelNavy><TextOnDark>…</TextOnDark></PanelNavy>`,
|
|
1561
|
+
surSombre: true,
|
|
1562
|
+
rendu: (
|
|
1563
|
+
<PanelNavy>
|
|
1564
|
+
<TextOnDark>
|
|
1565
|
+
<Mono ton="on-dark">CHIFFREMENT</Mono>
|
|
1566
|
+
<Body>AES-256 au repos, TLS 1.3 en transit.</Body>
|
|
1567
|
+
</TextOnDark>
|
|
1568
|
+
</PanelNavy>
|
|
1569
|
+
),
|
|
1570
|
+
},
|
|
1571
|
+
{
|
|
1572
|
+
id: 'halo',
|
|
1573
|
+
nom: 'Halo, sol, voile',
|
|
1574
|
+
resume:
|
|
1575
|
+
'Le violet et le cyan du spectre, très dilués. Ce n’est PAS un emploi du spectre en aplat : c’est une lumière — elle ne compte pas dans les trois emplois autorisés.',
|
|
1576
|
+
code: `<section style={{ position: 'relative', overflow: 'hidden' }}>
|
|
1577
|
+
<Halo place="hero" />
|
|
1578
|
+
<GridFloor />
|
|
1579
|
+
<Fade blanc />
|
|
1580
|
+
…
|
|
1581
|
+
</section>`,
|
|
1582
|
+
rendu: (
|
|
1583
|
+
<div
|
|
1584
|
+
style={{
|
|
1585
|
+
position: 'relative',
|
|
1586
|
+
overflow: 'hidden',
|
|
1587
|
+
height: 150,
|
|
1588
|
+
display: 'grid',
|
|
1589
|
+
placeItems: 'center',
|
|
1590
|
+
}}
|
|
1591
|
+
>
|
|
1592
|
+
<Halo place="core" />
|
|
1593
|
+
<GridFloor carte />
|
|
1594
|
+
<Fade blanc />
|
|
1595
|
+
<Mono ton="label" style={{ position: 'relative' }}>
|
|
1596
|
+
HALO · SOL · VOILE
|
|
1597
|
+
</Mono>
|
|
1598
|
+
</div>
|
|
1599
|
+
),
|
|
1600
|
+
},
|
|
1601
|
+
{
|
|
1602
|
+
id: 'glyphe',
|
|
1603
|
+
nom: 'Glyphe',
|
|
1604
|
+
resume:
|
|
1605
|
+
'Enveloppe d’un logo produit posé comme motif. Le glyphe lui-même est une image du paquet (@zevra/ui/logos/…), pas un tracé du CSS.',
|
|
1606
|
+
code: `<Glyph variante="deep"><img src="…/logos/memoire-glyph-orig.png" alt="" /></Glyph>`,
|
|
1607
|
+
rendu: (
|
|
1608
|
+
<Glyph>
|
|
1609
|
+
<Icon nom={ICONES[0]!} taille="xl" />
|
|
1610
|
+
</Glyph>
|
|
1611
|
+
),
|
|
1612
|
+
},
|
|
1613
|
+
{
|
|
1614
|
+
id: 'mouvement',
|
|
1615
|
+
nom: 'Mouvement',
|
|
1616
|
+
resume:
|
|
1617
|
+
'⚠️ Les neuf animations sont pilotées AU DÉFILEMENT et réservées à la vitrine : le chrome applicatif n’anime rien. « prefers-reduced-motion » les coupe toutes, globalement, depuis base.css.',
|
|
1618
|
+
code: `<div className={anim('rise')}>…</div>
|
|
1619
|
+
<div className={anim('pop', 3)}>…</div>
|
|
1620
|
+
<div className={anim('converge', 'tr')}>…</div>`,
|
|
1621
|
+
rendu: (
|
|
1622
|
+
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
|
1623
|
+
{(['rise', 'pop', 'filet', 'glyph-in', 'converge', 'breathe', 'drift', 'dot-active', 'progress'] as const).map(
|
|
1624
|
+
(nom) => (
|
|
1625
|
+
<span key={nom} className={anim(nom)}>
|
|
1626
|
+
<Chip variante="mono">{nom}</Chip>
|
|
1627
|
+
</span>
|
|
1628
|
+
),
|
|
1629
|
+
)}
|
|
1630
|
+
</div>
|
|
1631
|
+
),
|
|
1632
|
+
},
|
|
1633
|
+
],
|
|
1634
|
+
},
|
|
1635
|
+
]
|
|
1636
|
+
|
|
1637
|
+
function Heading2Cta() {
|
|
1638
|
+
return <Heading taille="h2-cta">Voir Zevra sur vos propres pièces.</Heading>
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
/** Tous les exemples à plat — pratique pour une recherche ou un test. */
|
|
1642
|
+
export const EXEMPLES: (Exemple & { groupe: string })[] = CATALOGUE.flatMap((g) =>
|
|
1643
|
+
g.exemples.map((e) => ({ ...e, groupe: g.id })),
|
|
1644
|
+
)
|