@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.
Files changed (82) hide show
  1. package/README.md +64 -1
  2. package/dist/actions.d.ts +41 -0
  3. package/dist/actions.d.ts.map +1 -0
  4. package/dist/actions.js +24 -0
  5. package/dist/actions.js.map +1 -0
  6. package/dist/catalogue.d.ts +30 -0
  7. package/dist/catalogue.d.ts.map +1 -0
  8. package/dist/catalogue.js +830 -0
  9. package/dist/catalogue.js.map +1 -0
  10. package/dist/content.d.ts +135 -0
  11. package/dist/content.d.ts.map +1 -0
  12. package/dist/content.js +85 -0
  13. package/dist/content.js.map +1 -0
  14. package/dist/css-inline.d.ts +11 -0
  15. package/dist/css-inline.d.ts.map +1 -0
  16. package/dist/css-inline.js +13 -0
  17. package/dist/css-inline.js.map +1 -0
  18. package/dist/cx.d.ts +16 -0
  19. package/dist/cx.d.ts.map +1 -0
  20. package/dist/cx.js +20 -0
  21. package/dist/cx.js.map +1 -0
  22. package/dist/data.d.ts +136 -0
  23. package/dist/data.d.ts.map +1 -0
  24. package/dist/data.js +98 -0
  25. package/dist/data.js.map +1 -0
  26. package/dist/feedback.d.ts +88 -0
  27. package/dist/feedback.d.ts.map +1 -0
  28. package/dist/feedback.js +38 -0
  29. package/dist/feedback.js.map +1 -0
  30. package/dist/forms.d.ts +120 -0
  31. package/dist/forms.d.ts.map +1 -0
  32. package/dist/forms.js +60 -0
  33. package/dist/forms.js.map +1 -0
  34. package/dist/icon-data.d.ts +6 -0
  35. package/dist/icon-data.d.ts.map +1 -0
  36. package/dist/icon-data.js +77 -0
  37. package/dist/icon-data.js.map +1 -0
  38. package/dist/icon.d.ts +33 -0
  39. package/dist/icon.d.ts.map +1 -0
  40. package/dist/icon.js +13 -0
  41. package/dist/icon.js.map +1 -0
  42. package/dist/index.d.ts +13 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +41 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/layout.d.ts +166 -0
  47. package/dist/layout.d.ts.map +1 -0
  48. package/dist/layout.js +120 -0
  49. package/dist/layout.js.map +1 -0
  50. package/dist/navigation.d.ts +150 -0
  51. package/dist/navigation.d.ts.map +1 -0
  52. package/dist/navigation.js +104 -0
  53. package/dist/navigation.js.map +1 -0
  54. package/dist/overlays.d.ts +68 -0
  55. package/dist/overlays.d.ts.map +1 -0
  56. package/dist/overlays.js +114 -0
  57. package/dist/overlays.js.map +1 -0
  58. package/dist/surfaces.d.ts +61 -0
  59. package/dist/surfaces.d.ts.map +1 -0
  60. package/dist/surfaces.js +54 -0
  61. package/dist/surfaces.js.map +1 -0
  62. package/dist/typography.d.ts +89 -0
  63. package/dist/typography.d.ts.map +1 -0
  64. package/dist/typography.js +76 -0
  65. package/dist/typography.js.map +1 -0
  66. package/package.json +36 -4
  67. package/react/actions.tsx +138 -0
  68. package/react/catalogue.tsx +1644 -0
  69. package/react/content.tsx +313 -0
  70. package/react/css-inline.ts +14 -0
  71. package/react/cx.ts +20 -0
  72. package/react/data.tsx +300 -0
  73. package/react/feedback.tsx +229 -0
  74. package/react/forms.tsx +383 -0
  75. package/react/icon-data.ts +114 -0
  76. package/react/icon.tsx +84 -0
  77. package/react/index.ts +42 -0
  78. package/react/layout.tsx +347 -0
  79. package/react/navigation.tsx +329 -0
  80. package/react/overlays.tsx +237 -0
  81. package/react/surfaces.tsx +161 -0
  82. package/react/typography.tsx +220 -0
@@ -0,0 +1,313 @@
1
+ // Contenu — surfaces, badges et blocs éditoriaux. Sans état, donc sans
2
+ // « use client ».
3
+
4
+ import type { HTMLAttributes, ReactNode } from 'react'
5
+ import { cx } from './cx.js'
6
+
7
+ type Div = Omit<HTMLAttributes<HTMLDivElement>, 'className'>
8
+ type Span = Omit<HTMLAttributes<HTMLSpanElement>, 'className'>
9
+
10
+ export interface CardProps extends Div {
11
+ /** Retrait intérieur. Sans, la carte est nue — à toi de poser le tien. */
12
+ padding?: 'aucun' | 'normal' | 'large'
13
+ /** Filet coloré : un état porté par la bordure, pas par un fond. */
14
+ etat?: 'ok' | 'err'
15
+ /** Ombre portée, pour une carte qui flotte au-dessus d'une scène. */
16
+ flottante?: boolean
17
+ className?: string
18
+ }
19
+
20
+ export function Card({
21
+ padding = 'normal',
22
+ etat,
23
+ flottante,
24
+ className,
25
+ ...reste
26
+ }: CardProps) {
27
+ return (
28
+ <div
29
+ className={cx(
30
+ 'zv-card',
31
+ padding === 'normal' && 'zv-card--pad',
32
+ padding === 'large' && 'zv-card--pad-lg',
33
+ etat === 'ok' && 'zv-card--ok',
34
+ etat === 'err' && 'zv-card--err',
35
+ flottante && 'zv-card--float',
36
+ className,
37
+ )}
38
+ {...reste}
39
+ />
40
+ )
41
+ }
42
+
43
+ /** Surface semi-opaque. ⚠️ Ne fonctionne QUE devant un halo ou une scène :
44
+ * posée sur du papier nu, elle disparaît. */
45
+ export function Glass({ className, ...reste }: Div & { className?: string }) {
46
+ return <div className={cx('zv-glass', className)} {...reste} />
47
+ }
48
+
49
+ export interface InsetProps extends Div {
50
+ /** Variante document : fond panel, texte encre, très aérée. */
51
+ discret?: boolean
52
+ /** Poussé en bas de la carte qui l'accueille. */
53
+ enBas?: boolean
54
+ className?: string
55
+ }
56
+
57
+ /** Encart dans une carte. Deux objets distincts au relevé, longtemps
58
+ * confondus : la citation d'interface et l'extrait de document. */
59
+ export function Inset({ discret, enBas, className, ...reste }: InsetProps) {
60
+ return (
61
+ <div
62
+ className={cx(
63
+ 'zv-inset',
64
+ discret && 'zv-inset--quiet',
65
+ enBas && 'zv-inset--bottom',
66
+ className,
67
+ )}
68
+ {...reste}
69
+ />
70
+ )
71
+ }
72
+
73
+ export function Pill({ className, ...reste }: Span & { className?: string }) {
74
+ return <span className={cx('zv-pill', className)} {...reste} />
75
+ }
76
+
77
+ export interface SrcProps extends Span {
78
+ /** Le « + 11 » qui clôt une liste de sources. */
79
+ attenue?: boolean
80
+ className?: string
81
+ }
82
+
83
+ /** Puce de source — BOSS, Judilibre, KALI. */
84
+ export function Src({ attenue, className, ...reste }: SrcProps) {
85
+ return (
86
+ <span
87
+ className={cx('zv-src', attenue && 'zv-src--quiet', className)}
88
+ {...reste}
89
+ />
90
+ )
91
+ }
92
+
93
+ export function SrcRow({ className, ...reste }: Span & { className?: string }) {
94
+ return <span className={cx('zv-src-row', className)} {...reste} />
95
+ }
96
+
97
+ export interface TagProps extends Span {
98
+ etat?: 'ok' | 'err'
99
+ /** Sur la bande manifeste : les états s'éclaircissent, sinon ils virent au noir. */
100
+ surSombre?: boolean
101
+ className?: string
102
+ }
103
+
104
+ export function Tag({ etat, surSombre, className, ...reste }: TagProps) {
105
+ return (
106
+ <span
107
+ className={cx(
108
+ 'zv-tag',
109
+ etat === 'ok' && (surSombre ? 'zv-tag--ok-on-dark' : 'zv-tag--ok'),
110
+ etat === 'err' && (surSombre ? 'zv-tag--err-on-dark' : 'zv-tag--err'),
111
+ className,
112
+ )}
113
+ {...reste}
114
+ />
115
+ )
116
+ }
117
+
118
+ /** Entité pseudonymisée, posée dans le fil du texte. */
119
+ export function Ent({ className, ...reste }: Span & { className?: string }) {
120
+ return <span className={cx('zv-ent', className)} {...reste} />
121
+ }
122
+
123
+ /** Le paragraphe qui accueille des entités : il respire davantage, sinon les
124
+ * jetons se télescopent d'une ligne à l'autre. */
125
+ export function EntText({ className, ...reste }: Span & { className?: string }) {
126
+ return <span className={cx('zv-ent-text', className)} {...reste} />
127
+ }
128
+
129
+ export interface DividerProps extends Div {
130
+ /** Filet plein plutôt que cheveu. */
131
+ marque?: boolean
132
+ surSombre?: boolean
133
+ className?: string
134
+ }
135
+
136
+ export function Divider({ marque, surSombre, className, ...reste }: DividerProps) {
137
+ return (
138
+ <div
139
+ className={cx(
140
+ 'zv-divider',
141
+ marque && 'zv-divider--strong',
142
+ surSombre && 'zv-divider--on-dark',
143
+ className,
144
+ )}
145
+ {...reste}
146
+ />
147
+ )
148
+ }
149
+
150
+ export interface QuoteProps extends Div {
151
+ auteur?: ReactNode
152
+ className?: string
153
+ children?: ReactNode
154
+ }
155
+
156
+ /** ⚠️ RÈGLE DE CHARTE : jamais de fausse citation. Tant qu'un verbatim client
157
+ * n'a pas été recueilli, ce bloc reste en réserve — un placeholder crédible
158
+ * finit toujours par partir en production. */
159
+ export function Quote({ auteur, className, children, ...reste }: QuoteProps) {
160
+ return (
161
+ <div className={cx('zv-quote', className)} {...reste}>
162
+ <span className="zv-spec zv-spec--on-dark" aria-hidden="true" />
163
+ <span className="zv-quote__text">{children}</span>
164
+ {auteur && <span className="zv-mono zv-mono--micro zv-mono--on-dark">{auteur}</span>}
165
+ </div>
166
+ )
167
+ }
168
+
169
+ export interface PriceProps extends Div {
170
+ montant: ReactNode
171
+ /** Le « HT · 1re année » accolé au montant. */
172
+ unite?: ReactNode
173
+ /** La mention sous la carte. */
174
+ note?: ReactNode
175
+ /** La formule recommandée — bandeau indigo. Trois cartes par page au plus. */
176
+ recommande?: boolean
177
+ className?: string
178
+ children?: ReactNode
179
+ }
180
+
181
+ export function Price({
182
+ montant,
183
+ unite,
184
+ note,
185
+ recommande,
186
+ className,
187
+ children,
188
+ ...reste
189
+ }: PriceProps) {
190
+ return (
191
+ <div
192
+ className={cx('zv-price', recommande && 'zv-price--featured', className)}
193
+ {...reste}
194
+ >
195
+ {children}
196
+ <span className="zv-price__amount">
197
+ {montant}
198
+ {unite && <span className="zv-price__unit"> {unite}</span>}
199
+ </span>
200
+ {note && <span className="zv-price__note">{note}</span>}
201
+ </div>
202
+ )
203
+ }
204
+
205
+ /* ─── Statistiques de carte ────────────────────────────────────────── */
206
+
207
+ export function MiniStat({ className, ...reste }: Div & { className?: string }) {
208
+ return <div className={cx('zv-ministat', className)} {...reste} />
209
+ }
210
+
211
+ export interface MiniStatItemProps extends Div {
212
+ valeur: ReactNode
213
+ className?: string
214
+ children?: ReactNode
215
+ }
216
+
217
+ /** Paire de mini-statistiques au pied d'une carte produit. */
218
+ export function MiniStatItem({ valeur, className, children, ...reste }: MiniStatItemProps) {
219
+ return (
220
+ <div className={cx('zv-ministat__item', className)} {...reste}>
221
+ <span className="zv-ministat__value">{valeur}</span>
222
+ {children}
223
+ </div>
224
+ )
225
+ }
226
+
227
+ export interface StatCardProps extends Div {
228
+ valeur: ReactNode
229
+ className?: string
230
+ children?: ReactNode
231
+ }
232
+
233
+ export function StatCard({ valeur, className, children, ...reste }: StatCardProps) {
234
+ return (
235
+ <div className={cx('zv-stat-card', className)} {...reste}>
236
+ <span className="zv-stat-card__value">{valeur}</span>
237
+ {children}
238
+ </div>
239
+ )
240
+ }
241
+
242
+ /* ─── FAQ ──────────────────────────────────────────────────────────── */
243
+
244
+ /** ⚠️ Cinq questions par page au MAXIMUM. Au-delà, ce n'est plus une FAQ,
245
+ * c'est une documentation mal rangée. */
246
+ export function Faq({ className, ...reste }: Div & { className?: string }) {
247
+ return <div className={cx('zv-faq', className)} {...reste} />
248
+ }
249
+
250
+ export interface FaqItemProps extends Div {
251
+ question: ReactNode
252
+ className?: string
253
+ children?: ReactNode
254
+ }
255
+
256
+ /** Rendu ouvert, sans repli : la réponse est le contenu, la cacher derrière
257
+ * un clic n'ajoute rien à cinq questions. Pour un vrai accordéon, c'est un
258
+ * `<details>` — et il n'existe pas de style pour lui dans le paquet. */
259
+ export function FaqItem({ question, className, children, ...reste }: FaqItemProps) {
260
+ return (
261
+ <div className={className} {...reste}>
262
+ <p className="zv-faq__q">{question}</p>
263
+ <p className="zv-faq__a">{children}</p>
264
+ </div>
265
+ )
266
+ }
267
+
268
+ /* ─── Aperçu d'interface ───────────────────────────────────────────── */
269
+
270
+ export interface UiPreviewProps extends Div {
271
+ question: ReactNode
272
+ /** Le compteur de sources, seul élément mis en avant de la réponse. */
273
+ sources?: ReactNode
274
+ /** Les puces de source, sous la réponse. */
275
+ puces?: ReactNode
276
+ className?: string
277
+ children?: ReactNode
278
+ }
279
+
280
+ /** La preuve produit : une question, une réponse, ses sources.
281
+ * ⚠️ RÈGLE DE CHARTE : toujours de VRAIES données réalistes, jamais de
282
+ * lorem — c'est ce bloc qui porte la crédibilité de la page. */
283
+ export function UiPreview({
284
+ question,
285
+ sources,
286
+ puces,
287
+ className,
288
+ children,
289
+ ...reste
290
+ }: UiPreviewProps) {
291
+ return (
292
+ <div className={cx('zv-ui-preview', className)} {...reste}>
293
+ <p className="zv-ui-preview__q">{question}</p>
294
+ <p className="zv-ui-preview__a">{children}</p>
295
+ {sources && <span className="zv-ui-preview__count">{sources}</span>}
296
+ {puces}
297
+ </div>
298
+ )
299
+ }
300
+
301
+ /* ─── Pastille de validation ───────────────────────────────────────── */
302
+
303
+ /** ⚠️ À ne pas confondre avec `Checkbox` (forms) : celle-ci n'est PAS un
304
+ * contrôle, c'est une coche décorative en tête de liste d'arguments. */
305
+ export function Check({ className, ...reste }: Span & { className?: string }) {
306
+ return (
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">
309
+ <polyline points="2.5,6.5 5,9 9.5,3.5" />
310
+ </svg>
311
+ </span>
312
+ )
313
+ }