@zevra/ui 0.9.0 → 0.11.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/react/actions.tsx CHANGED
@@ -11,6 +11,7 @@ import type {
11
11
  ReactNode,
12
12
  } from 'react'
13
13
  import { cx } from './cx.js'
14
+ import { Icon, type IconId } from './icon.js'
14
15
 
15
16
  /** Variantes de bouton. Une seule pleine par écran — c'est la règle de
16
17
  * charte, et « danger » COMPTE comme la pleine : la règle compte les
@@ -33,6 +34,17 @@ export interface ButtonProps
33
34
  compact?: boolean
34
35
  /** Pleine largeur — l'appel à l'action d'un écran mobile. */
35
36
  bloc?: boolean
37
+ /** L'icône du jeu, posée devant le libellé (16 px, couleur héritée).
38
+ * La fournir en prop plutôt qu'en enfant évite les tracés recopiés à la
39
+ * main — la faute qui avait produit trois flèches différentes. */
40
+ icone?: IconId
41
+ /** …ou après lui : « Étape suivante › ». Pour un LIEN qui avance, c'est
42
+ * LinkArrow — la flèche animée appartient au lien, pas au bouton. */
43
+ iconeApres?: IconId
44
+ /** Bouton d'action à icône SEULE (48×48, 40×40 en compact) : l'envoi
45
+ * d'un composeur, l'action unique d'une barre. ⚠️ aria-label OBLIGATOIRE,
46
+ * écrit pour l'ACTION (« Envoyer le message »), pas pour le dessin. */
47
+ carre?: boolean
36
48
  /** Attente pendant l'action : pose aria-busy et aria-disabled, et AVALE
37
49
  * le clic — sans poser `disabled`, qui sortirait le bouton du parcours
38
50
  * clavier au moment précis où l'attente doit s'annoncer.
@@ -50,11 +62,15 @@ export function Button({
50
62
  variante = 'plein',
51
63
  compact,
52
64
  bloc,
65
+ icone,
66
+ iconeApres,
67
+ carre,
53
68
  occupe,
54
69
  className,
55
70
  type = 'button',
56
71
  disabled,
57
72
  onClick,
73
+ children,
58
74
  ...reste
59
75
  }: ButtonProps) {
60
76
  return (
@@ -76,10 +92,15 @@ export function Button({
76
92
  VARIANTE_BOUTON[variante],
77
93
  compact && 'zv-btn--sm',
78
94
  bloc && 'zv-btn--block',
95
+ carre && 'zv-btn--carre',
79
96
  className,
80
97
  )}
81
98
  {...reste}
82
- />
99
+ >
100
+ {icone && <Icon nom={icone} />}
101
+ {children}
102
+ {iconeApres && <Icon nom={iconeApres} />}
103
+ </button>
83
104
  )
84
105
  }
85
106
 
@@ -88,6 +109,10 @@ export interface ButtonLinkProps
88
109
  variante?: BoutonVariante
89
110
  compact?: boolean
90
111
  bloc?: boolean
112
+ icone?: IconId
113
+ iconeApres?: IconId
114
+ /** ⚠️ Comme sur Button : aria-label OBLIGATOIRE, écrit pour l'ACTION. */
115
+ carre?: boolean
91
116
  className?: string
92
117
  }
93
118
 
@@ -97,7 +122,11 @@ export function ButtonLink({
97
122
  variante = 'plein',
98
123
  compact,
99
124
  bloc,
125
+ icone,
126
+ iconeApres,
127
+ carre,
100
128
  className,
129
+ children,
101
130
  ...reste
102
131
  }: ButtonLinkProps) {
103
132
  return (
@@ -106,10 +135,15 @@ export function ButtonLink({
106
135
  VARIANTE_BOUTON[variante],
107
136
  compact && 'zv-btn--sm',
108
137
  bloc && 'zv-btn--block',
138
+ carre && 'zv-btn--carre',
109
139
  className,
110
140
  )}
111
141
  {...reste}
112
- />
142
+ >
143
+ {icone && <Icon nom={icone} />}
144
+ {children}
145
+ {iconeApres && <Icon nom={iconeApres} />}
146
+ </a>
113
147
  )
114
148
  }
115
149
 
@@ -34,7 +34,7 @@ import {
34
34
  Checkbox, Drop, Field, Input, InputAffix, Radio, Range, Search, Seg,
35
35
  SegGroup, Select, SelectAnchor, SelectTrigger, Switch, Textarea,
36
36
  } from './forms.js'
37
- import { ICONES, Icon, IconButton, NOMS_ICONES } from './icon.js'
37
+ import { ICONES, Icon, IconButton, IconTile, NOMS_ICONES } from './icon.js'
38
38
  import {
39
39
  Banner, CTA, Carousel, CarouselItem, Col, Footer, FooterMini, Grid, Grid12,
40
40
  Gutter, Hero, Nav, NavLink, NavLinks, NavLogo, ProofBar, SectionHead,
@@ -489,6 +489,24 @@ export const CATALOGUE: GroupeCatalogue[] = [
489
489
  </ButtonRow>
490
490
  ),
491
491
  },
492
+ {
493
+ id: 'boutons-icones',
494
+ nom: 'Boutons à icône',
495
+ resume:
496
+ 'L’icône vient du jeu (16 px) et hérite de la couleur du libellé — en prop, jamais recopiée à la main. « carre » est l’action à icône SEULE : aria-label OBLIGATOIRE, écrit pour l’ACTION. Ne pas confondre avec IconButton (chrome nu, 40 px) ni ChromeButton (croix bordée) : le carré est une action, il en a le poids.',
497
+ code: `<Button icone="telecharger">Télécharger le rapport</Button>
498
+ <Button variante="fantome" iconeApres="chevron-droit">Étape suivante</Button>
499
+ <Button carre variante="encre" icone="envoyer" aria-label="Envoyer le message" />`,
500
+ rendu: (
501
+ <ButtonRow>
502
+ <Button icone="telecharger">Télécharger le rapport</Button>
503
+ <Button variante="fantome" iconeApres="chevron-droit">
504
+ Étape suivante
505
+ </Button>
506
+ <Button carre variante="encre" icone="envoyer" aria-label="Envoyer le message" />
507
+ </ButtonRow>
508
+ ),
509
+ },
492
510
  {
493
511
  id: 'lien-fleche',
494
512
  nom: 'Lien fléché',
@@ -1322,16 +1340,19 @@ export const CATALOGUE: GroupeCatalogue[] = [
1322
1340
  id: 'sidebar',
1323
1341
  nom: 'Colonne latérale',
1324
1342
  resume:
1325
- '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.',
1343
+ '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. « num » accepte un rang mono OU un glyphe (le ⚙ du relevé), sur la même colonne de 16 px — jamais les deux.',
1326
1344
  code: `<Sidebar>
1327
1345
  <SidebarHead>…</SidebarHead>
1328
1346
  <SideGroup titre="Espace">
1329
1347
  <SideLink href="/bibliotheque" courant num="01">Bibliothèque</SideLink>
1330
1348
  </SideGroup>
1349
+ <SideGroup titre="Organisation">
1350
+ <SideLink href="/admin" num={<Icon nom="reglages" />}>Administration</SideLink>
1351
+ </SideGroup>
1331
1352
  <SidebarFoot>…</SidebarFoot>
1332
1353
  </Sidebar>`,
1333
1354
  rendu: (
1334
- <Sidebar style={{ height: 260 }}>
1355
+ <Sidebar style={{ height: 300 }}>
1335
1356
  <SidebarHead>
1336
1357
  <Mono ton="ink">ZEVRA</Mono>
1337
1358
  </SidebarHead>
@@ -1346,6 +1367,11 @@ export const CATALOGUE: GroupeCatalogue[] = [
1346
1367
  Import
1347
1368
  </SideLink>
1348
1369
  </SideGroup>
1370
+ <SideGroup titre="Organisation">
1371
+ <SideLink href="#" num={<Icon nom="reglages" />}>
1372
+ Administration
1373
+ </SideLink>
1374
+ </SideGroup>
1349
1375
  <SidebarFoot>
1350
1376
  <Meta discret>v2.4.1</Meta>
1351
1377
  </SidebarFoot>
@@ -1537,7 +1563,7 @@ export const CATALOGUE: GroupeCatalogue[] = [
1537
1563
  couche: 'commun',
1538
1564
  css: 'icons.css',
1539
1565
  resume:
1540
- "55 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.",
1566
+ "55 icônes TRACÉES, jamais pleines : trait 2, grille de 24, BOUTS FRANCS et jonctions vives — le trait carré de la charte, points d'encre carrés compris. 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.",
1541
1567
  exemples: [
1542
1568
  {
1543
1569
  id: 'jeu',
@@ -1581,6 +1607,33 @@ export const CATALOGUE: GroupeCatalogue[] = [
1581
1607
  </div>
1582
1608
  ),
1583
1609
  },
1610
+ {
1611
+ id: 'tuile',
1612
+ nom: 'Tuile d’icône',
1613
+ resume:
1614
+ 'La pastille carrée devant un argument de la vitrine — liste de garanties, grille d’atouts. DÉCORATIVE par défaut : c’est le libellé voisin qui nomme, un titre ne se pose que si elle est seule. UNE par argument, jamais en décor isolé — une tuile sans libellé est un rébus.',
1615
+ code: `<IconTile nom="bouclier-verifie" />
1616
+ <IconTile nom="balance" />`,
1617
+ rendu: (
1618
+ <div style={{ display: 'grid', gap: 14, maxWidth: 380 }}>
1619
+ {(
1620
+ [
1621
+ ['bouclier-verifie', 'Hébergement souverain', 'Infrastructure dédiée, en France.'],
1622
+ ['balance', 'Sources officielles', 'BOSS, Judilibre, KALI — datées et citées.'],
1623
+ ['cadenas', 'Zéro réutilisation', 'Vos pièces n’entraînent aucun modèle.'],
1624
+ ] as const
1625
+ ).map(([nom, titre, texte]) => (
1626
+ <div key={nom} style={{ display: 'flex', gap: 14, alignItems: 'flex-start' }}>
1627
+ <IconTile nom={nom} />
1628
+ <div>
1629
+ <CardTitle as="p">{titre}</CardTitle>
1630
+ <Body taille="dense">{texte}</Body>
1631
+ </div>
1632
+ </div>
1633
+ ))}
1634
+ </div>
1635
+ ),
1636
+ },
1584
1637
  ],
1585
1638
  },
1586
1639
 
@@ -1966,24 +2019,28 @@ export const CATALOGUE: GroupeCatalogue[] = [
1966
2019
  id: 'bande',
1967
2020
  nom: 'Bande manifeste',
1968
2021
  resume:
1969
- '⚠️ 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.',
2022
+ '⚠️ LA surface sombre du système. Les surfaces sombres se font avec elle et --accent-on-dark, JAMAIS en inversant les neutres. La bande ne porte AUCUNE gouttière propre : c’est la page qui la donne — poser un Gutter dedans, sinon le contenu colle au bord.',
1970
2023
  code: `<Band lueur filigrane={<Glyph variante="watermark">…</Glyph>}>
1971
- <Kicker surSombre>Souveraineté</Kicker>
1972
- <H2>Vos pièces ne quittent pas la France.</H2>
2024
+ <Gutter>
2025
+ <Kicker surSombre>Souveraineté</Kicker>
2026
+ <H2>Vos pièces ne quittent pas la France.</H2>
2027
+ </Gutter>
1973
2028
  </Band>`,
1974
2029
  surSombre: true,
1975
2030
  rendu: (
1976
2031
  <Band lueur>
1977
- <Kicker surSombre>Souveraineté</Kicker>
1978
- <Heading taille="h2-sm">Vos pièces ne quittent pas la France.</Heading>
1979
- <Body>
1980
- Infrastructure dédiée, chiffrement au repos, aucun transfert hors Union
1981
- européenne.
1982
- </Body>
1983
- <div style={{ display: 'flex', gap: 8, marginTop: 12 }}>
1984
- <Tag surSombre etat="ok">CHIFFRÉ</Tag>
1985
- <Tag surSombre>OVHCLOUD · GRA</Tag>
1986
- </div>
2032
+ <Gutter>
2033
+ <Kicker surSombre>Souveraineté</Kicker>
2034
+ <Heading taille="h2-sm">Vos pièces ne quittent pas la France.</Heading>
2035
+ <Body>
2036
+ Infrastructure dédiée, chiffrement au repos, aucun transfert hors Union
2037
+ européenne.
2038
+ </Body>
2039
+ <div style={{ display: 'flex', gap: 8, marginTop: 12 }}>
2040
+ <Tag surSombre etat="ok">CHIFFRÉ</Tag>
2041
+ <Tag surSombre>OVHCLOUD · GRA</Tag>
2042
+ </div>
2043
+ </Gutter>
1987
2044
  </Band>
1988
2045
  ),
1989
2046
  },
@@ -2049,8 +2106,9 @@ export const CATALOGUE: GroupeCatalogue[] = [
2049
2106
  id: 'mouvement',
2050
2107
  nom: 'Mouvement',
2051
2108
  resume:
2052
- '⚠️ 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.',
2109
+ '⚠️ Les neuf animations sont pilotées AU DÉFILEMENT et réservées à la vitrine : le chrome applicatif n’anime rien. « rise · doux » n’est pas une dixième : c’est l’entrée DOSÉE des blocs de pages intérieures (12 px), la home gardant l’entrée pleine. « prefers-reduced-motion » les coupe toutes, globalement, depuis base.css.',
2053
2110
  code: `<div className={anim('rise')}>…</div>
2111
+ <div className={anim('rise', 'doux')}>…</div> {/* pages intérieures */}
2054
2112
  <div className={anim('pop', 3)}>…</div>
2055
2113
  <div className={anim('converge', 'tr')}>…</div>`,
2056
2114
  rendu: (
@@ -2062,6 +2120,9 @@ export const CATALOGUE: GroupeCatalogue[] = [
2062
2120
  </span>
2063
2121
  ),
2064
2122
  )}
2123
+ <span className={anim('rise', 'doux')}>
2124
+ <Chip variante="mono">rise · doux</Chip>
2125
+ </span>
2065
2126
  </div>
2066
2127
  ),
2067
2128
  },
package/react/content.tsx CHANGED
@@ -305,7 +305,7 @@ export function UiPreview({
305
305
  export function Check({ className, ...reste }: Span & { className?: string }) {
306
306
  return (
307
307
  <span className={cx('zv-check', className)} aria-hidden="true" {...reste}>
308
- <svg viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" width="12" height="12">
308
+ <svg viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="square" strokeLinejoin="miter" width="12" height="12">
309
309
  <polyline points="2.5,6.5 5,9 9.5,3.5" />
310
310
  </svg>
311
311
  </span>