@evinvest/uikit 0.13.0 → 0.14.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evinvest/uikit",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Dep-light React UI kit: shadcn-semantics components with zero @radix-ui / cva, mirroring the ev_lib::uikit Rust feature. Ships the shared design tokens.",
5
5
  "license": "BlueOak-1.0.0",
6
6
  "publishConfig": {
@@ -17,8 +17,7 @@
17
17
  "types": "./dist/index.d.ts",
18
18
  "import": "./dist/index.js"
19
19
  },
20
- "./styles/tokens.css": "./styles/tokens.css",
21
- "./styles/tokens-legacy.css": "./styles/tokens-legacy.css"
20
+ "./styles/tokens.css": "./styles/tokens.css"
22
21
  },
23
22
  "types": "./dist/index.d.ts",
24
23
  "main": "./dist/index.js",
package/styles/tokens.css CHANGED
@@ -17,6 +17,7 @@
17
17
  * accent-trace · accent-debug · accent-info · accent-warn · accent-error
18
18
  * SCALARS radius · control-* · display-scale · page-* · shadows · fonts
19
19
  * CHARTS chart-1 … chart-5
20
+ * MARK brand-mark · brand-aspect — supplied, never defined here
20
21
  *
21
22
  * Custom properties inherit, so a scope class on a `<section>` re-themes its
22
23
  * subtree: `bg-card text-ink` is correct on both polarities with no prop. EV is
@@ -351,6 +352,17 @@
351
352
  --page-max: 90rem; /* 1440px */
352
353
  --page-px: 1rem; /* 16px — mobile gutter, scales up below */
353
354
 
355
+ /* ── the mark ────────────────────────────────────────────────── *
356
+ * `--brand-mark` (a url() at your own SVG) and `--brand-aspect` (its ratio)
357
+ * are what `<Logo>` masks. Deliberately undefined: the kit ships no artwork,
358
+ * and a default here would put EV's crown on somebody else's 404. Undeclared,
359
+ * `mask-image` is invalid at computed-value time and the mark paints as a
360
+ * solid block — loud, which is the point. Declare both beside the palette:
361
+ *
362
+ * --brand-mark: url("/assets/logo.svg");
363
+ * --brand-aspect: 387 / 335;
364
+ */
365
+
354
366
  /* shell offset — viewport space reserved by whatever shell hosts the app.
355
367
  Standalone apps get 0; a hosting shell overrides on :root. Size
356
368
  viewport-bound surfaces with calc(100dvh - var(--ev-shell-offset, 0px)). */
@@ -1,446 +0,0 @@
1
- /* AUTO-GENERATED by `nix run .#gen` from the repo-root tokens-legacy.css — do not edit. */
2
- /*
3
- * The pre-0.10 token vocabulary, layered over the current one.
4
- *
5
- * Import this instead of `tokens.css` while a consumer still writes
6
- * `text-main-mist`, `bg-sidebar`, `text-muted-foreground` and friends in its own
7
- * markup. It re-declares every old utility name and re-points the current tokens
8
- * at the old values, so a kit component styled `bg-card text-ink` renders exactly
9
- * what `bg-card text-card-foreground` rendered.
10
- *
11
- * Paired with the `legacy` Cargo feature and the `./styles/tokens-legacy.css`
12
- * export of `@evinvest/uikit`. Both go away together once every consumer has
13
- * moved; see docs/spec/accents.md for the model that replaces this.
14
- */
15
-
16
- /*
17
- * Shared design tokens — the theme contract for the `ev_lib::uikit` (Rust) and
18
- * `@evinvest/uikit` (TypeScript) components. Import this file from any Tailwind v4
19
- * entrypoint. Single source for both ports — `@evinvest/uikit` copies it into
20
- * `styles/tokens.css` from its `prepare` script.
21
- *
22
- * The values below are EV's. A non-EV consumer writes its own sheet against the
23
- * same names; the class tables reference nothing else.
24
- *
25
- * SCOPE :root · .dark · .light — a consumer defines one or both
26
- * SURFACES background · card · popover · muted · hover
27
- * INK ink · ink-mid · ink-soft
28
- * LINES border · input · ring
29
- * ROLES {role} and on-{role} for the ones that get filled:
30
- * brand · primary · secondary · positive ·
31
- * accent-trace · accent-debug · accent-info · accent-warn · accent-error
32
- * SCALARS radius · control-* · display-scale · page-* · shadows · fonts
33
- * CHARTS chart-1 … chart-5
34
- *
35
- * Custom properties inherit, so a scope class on a `<section>` re-themes its
36
- * subtree: `bg-card text-ink` is correct on both polarities with no prop. EV is
37
- * single-polarity, so `:root` and `.dark` carry the same block and `.light` is
38
- * undefined — a `Section(Light)` is then a no-op here, which is what a
39
- * single-polarity palette should do.
40
- *
41
- * Accents are ordered by significance, not by meaning; see docs/spec/accents.md.
42
- * Their values are read outside CSS (gitops mirrors them into Discord embed
43
- * bars), so they stay plain hex — no color-mix.
44
- *
45
- * Surface ladder — dark UI reads as unfinished when every plane is the same
46
- * value, so the steps below are deliberate and measured; keep them apart if you
47
- * retune. background #070d18 → secondary #0d1526 (1.07:1) → card #16233c (1.24:1
48
- * vs background) → popover #1d2c49 (1.13:1 vs card) → muted #26375c. Elevation
49
- * rides the same ladder via --shadow-card / -elevated / -overlay.
50
- */
51
-
52
- /*
53
- * Toast (sonner) enter/exit motion, shared by both ports. Imported by the token
54
- * sheets so the lifecycle ships with the theme contract.
55
- */
56
-
57
- /* ── Toast stacking (Sonner-style), shared by both ports via data-stack ──────
58
- Both `ev_lib::uikit` (Dioxus) and `@evinvest/uikit` (React) set data-stack on the
59
- toaster and drive the same machine: toasts are absolutely stacked at the
60
- pinned edge, pile up collapsed (front three peeking, each lifted + scaled by
61
- --index and clamped to --front-height), and spread to --offset on hover /
62
- keyboard focus (pure CSS — no JS state). Vars: the toaster sets --front-height
63
- + --gap; each toast sets --index, --z-index, --offset, --initial-height. React
64
- measures real heights; Dioxus can't (host-only), so it feeds a constant height
65
- — collapse is exact, expansion is uniform. The enter is a keyframe (plays on
66
- DOM insertion, so it works in both ports without a JS mount flip),
67
- `data-state=closed` slides out, and the node is dropped on the transitionend. */
68
-
69
- [data-slot="toaster"][data-stack] [data-slot="toast"] {
70
- position: absolute;
71
- --y: translateY(0);
72
- opacity: 1;
73
- z-index: var(--z-index, 1);
74
- transform: var(--y) translateX(var(--swipe-x, 0px));
75
- /* a plain easeOut (no overshoot) so a restack settles instead of bouncing
76
- back past its slot */
77
- transition:
78
- transform 350ms cubic-bezier(0.21, 0.8, 0.4, 1),
79
- opacity 350ms,
80
- height 350ms;
81
- /* the enter is a keyframe (plays on DOM insertion in both ports — no JS mount
82
- flip, whose pre-paint timing the Dioxus effect couldn't drive). It animates
83
- the independent `translate` property, NOT `transform`, so it *composes* with
84
- the stack transform instead of overriding it: a toast pushed back mid-enter
85
- keeps repositioning smoothly instead of fighting the keyframe (rapid-fire
86
- lag). */
87
- animation: ev-toast-enter 350ms cubic-bezier(0.21, 0.8, 0.4, 1) backwards;
88
- }
89
- @keyframes ev-toast-enter {
90
- from {
91
- opacity: 0;
92
- translate: 0 var(--enter-y, 100%);
93
- }
94
- to {
95
- opacity: 1;
96
- translate: 0 0;
97
- }
98
- }
99
-
100
- /* pin to the toaster's edge; set the lift sign + the off-screen enter offset */
101
- [data-slot="toaster"][data-stack][data-y-position="bottom"] [data-slot="toast"] {
102
- bottom: 0;
103
- --lift: -1;
104
- --enter-y: 100%;
105
- }
106
- [data-slot="toaster"][data-stack][data-y-position="top"] [data-slot="toast"] {
107
- top: 0;
108
- --lift: 1;
109
- --enter-y: -100%;
110
- }
111
-
112
- /* closing: slide back off + fade out; the node is dropped on its transitionend */
113
- [data-slot="toaster"][data-stack] [data-slot="toast"][data-state="closed"] {
114
- --y: translateY(var(--enter-y));
115
- opacity: 0;
116
- }
117
-
118
- /* collapsed: back toasts lift + shrink by depth, clamp to the front height */
119
- [data-slot="toaster"][data-stack]
120
- [data-slot="toast"][data-state="open"][data-front="false"] {
121
- --y: translateY(calc(var(--lift) * var(--gap) * var(--index)))
122
- scale(calc(1 - var(--index) * 0.05));
123
- height: var(--front-height);
124
- }
125
- [data-slot="toaster"][data-stack] [data-slot="toast"][data-front="false"] > * {
126
- opacity: 0;
127
- transition: opacity 300ms;
128
- }
129
- /* only the front three show while collapsed */
130
- [data-slot="toaster"][data-stack] [data-slot="toast"][data-visible="false"] {
131
- opacity: 0;
132
- pointer-events: none;
133
- }
134
-
135
- /* expanded (hover / keyboard focus): spread to the offset, real height, content
136
- + overflow back */
137
- [data-slot="toaster"][data-stack]:hover [data-slot="toast"][data-state="open"],
138
- [data-slot="toaster"][data-stack]:focus-within
139
- [data-slot="toast"][data-state="open"] {
140
- --y: translateY(calc(var(--lift) * var(--offset)));
141
- height: var(--initial-height);
142
- }
143
- [data-slot="toaster"][data-stack]:hover [data-slot="toast"] > *,
144
- [data-slot="toaster"][data-stack]:focus-within [data-slot="toast"] > *,
145
- [data-slot="toaster"][data-stack]:hover [data-slot="toast"][data-visible="false"],
146
- [data-slot="toaster"][data-stack]:focus-within
147
- [data-slot="toast"][data-visible="false"] {
148
- opacity: 1;
149
- pointer-events: auto;
150
- }
151
-
152
- /* a transparent strip over the gap so the hover area stays contiguous between
153
- spread toasts (otherwise the pointer falls through the gap and it collapses) */
154
- [data-slot="toaster"][data-stack] [data-slot="toast"]::after {
155
- content: "";
156
- position: absolute;
157
- left: 0;
158
- width: 100%;
159
- height: calc(var(--gap) + 1px);
160
- }
161
- [data-slot="toaster"][data-stack][data-y-position="bottom"]
162
- [data-slot="toast"]::after {
163
- bottom: 100%;
164
- }
165
- [data-slot="toaster"][data-stack][data-y-position="top"]
166
- [data-slot="toast"]::after {
167
- top: 100%;
168
- }
169
-
170
- /* 1:1 finger tracking during a swipe — kill the transition */
171
- [data-slot="toaster"][data-stack] [data-slot="toast"][data-swiping="true"] {
172
- transition: none;
173
- }
174
-
175
- /* auto-dismiss without a host timer: a no-op "life" animation runs for --life,
176
- then its `animationend` fires (the toast closes itself). Hovering/focusing the
177
- stack pauses just that animation (animation-play-state's 2nd slot), so the
178
- countdown holds — and resumes with the elapsed time preserved — while the
179
- enter (1st slot) keeps running. Persistent toasts set data-autodismiss=false,
180
- so they get no life animation. (The React port uses setTimeout instead.) */
181
- @keyframes ev-toast-life {
182
- from {
183
- --ev-life: 0;
184
- }
185
- to {
186
- --ev-life: 1;
187
- }
188
- }
189
- [data-slot="toaster"][data-stack]
190
- [data-slot="toast"][data-autodismiss="true"][data-state="open"] {
191
- animation:
192
- ev-toast-enter 350ms cubic-bezier(0.21, 0.8, 0.4, 1) backwards,
193
- ev-toast-life var(--life) linear;
194
- }
195
- [data-slot="toaster"][data-stack]:hover
196
- [data-slot="toast"][data-autodismiss="true"],
197
- [data-slot="toaster"][data-stack]:focus-within
198
- [data-slot="toast"][data-autodismiss="true"] {
199
- animation-play-state: running, paused;
200
- }
201
-
202
- /* reduced motion: opacity-only enter + near-instant transform (still fires
203
- transitionend, so exit removal works), no height animation */
204
- @keyframes ev-toast-fade-in {
205
- from {
206
- opacity: 0;
207
- }
208
- }
209
- @media (prefers-reduced-motion: reduce) {
210
- [data-slot="toaster"][data-stack] [data-slot="toast"] {
211
- transition:
212
- transform 1ms linear,
213
- opacity 200ms;
214
- animation: ev-toast-fade-in 200ms;
215
- }
216
- /* keep the auto-dismiss life animation under reduced motion */
217
- [data-slot="toaster"][data-stack]
218
- [data-slot="toast"][data-autodismiss="true"][data-state="open"] {
219
- animation:
220
- ev-toast-fade-in 200ms,
221
- ev-toast-life var(--life) linear;
222
- }
223
- }
224
-
225
- @theme inline {
226
- --radius-sm: calc(var(--radius) - 4px);
227
- --radius-md: calc(var(--radius) - 2px);
228
- --radius-lg: var(--radius);
229
- --radius-xl: calc(var(--radius) + 4px);
230
- --radius-2xl: calc(var(--radius) + 8px);
231
-
232
- /* Depth has to be a ladder, not a single drop shadow: on a dark theme the
233
- * value steps alone are too close to separate a resting card from a menu
234
- * from a modal, so each tier gets its own spread + opacity. */
235
- --shadow-card: 0 1px 2px 0 rgb(0 0 0 / 0.30);
236
- --shadow-elevated:
237
- 0 4px 12px -2px rgb(0 0 0 / 0.40),
238
- 0 2px 4px -2px rgb(0 0 0 / 0.30);
239
- --shadow-overlay: 0 16px 48px -12px rgb(0 0 0 / 0.60);
240
-
241
- /* The leading var() carries its own fallback: next/font (landing) sets
242
- * `--font-inter` / `--font-playfair`; a Rust consumer `@font-face`s the same
243
- * families itself. Without the in-var fallback an undefined leading var would
244
- * invalidate the whole `font-family` and drop the browser to its default — so
245
- * it must never resolve to guaranteed-invalid. */
246
- --font-sans: var(--font-inter, "Inter"), ui-sans-serif, system-ui, sans-serif;
247
- --font-serif: var(--font-playfair, "Playfair Display"), ui-serif, Georgia, serif;
248
- --font-mono: var(--font-inter, "Inter"), ui-monospace, monospace;
249
- --font-display: var(--font-playfair, "Playfair Display"), ui-serif, Georgia, serif;
250
-
251
- /* surfaces */
252
- --color-background: var(--background);
253
- --color-card: var(--card);
254
- --color-popover: var(--popover);
255
- --color-muted: var(--muted);
256
- --color-hover: var(--hover);
257
-
258
- /* ink — hierarchy, loudest first */
259
- --color-ink: var(--ink);
260
- --color-ink-mid: var(--ink-mid);
261
- --color-ink-soft: var(--ink-soft);
262
-
263
- /* lines */
264
- --color-border: var(--border);
265
- --color-input: var(--input);
266
- --color-ring: var(--ring);
267
-
268
- /* roles */
269
- --color-brand: var(--brand);
270
- --color-primary: var(--primary);
271
- --color-on-primary: var(--on-primary);
272
- --color-secondary: var(--secondary);
273
- --color-on-secondary: var(--on-secondary);
274
- --color-positive: var(--positive);
275
- --color-on-positive: var(--on-positive);
276
- --color-accent-trace: var(--accent-trace);
277
- --color-on-accent-trace: var(--on-accent-trace);
278
- --color-accent-debug: var(--accent-debug);
279
- --color-on-accent-debug: var(--on-accent-debug);
280
- --color-accent-info: var(--accent-info);
281
- --color-on-accent-info: var(--on-accent-info);
282
- --color-accent-warn: var(--accent-warn);
283
- --color-on-accent-warn: var(--on-accent-warn);
284
- --color-accent-error: var(--accent-error);
285
- --color-on-accent-error: var(--on-accent-error);
286
-
287
- /* charts — distinguishable hues carrying no significance */
288
- --color-chart-1: var(--chart-1);
289
- --color-chart-2: var(--chart-2);
290
- --color-chart-3: var(--chart-3);
291
- --color-chart-4: var(--chart-4);
292
- --color-chart-5: var(--chart-5);
293
- }
294
-
295
- :root,
296
- .dark {
297
- /* ── surfaces ────────────────────────────────────────────────── */
298
- --background: #070d18;
299
- --card: #16233c;
300
- --popover: #1d2c49;
301
- --muted: #26375c;
302
- /* the menu-item tint: a surface state, so it is named for the state */
303
- --hover: color-mix(in srgb, var(--ink) 8%, transparent);
304
-
305
- /* ── ink ─────────────────────────────────────────────────────── */
306
- --ink: #e6e1d3;
307
- --ink-mid: color-mix(in srgb, var(--ink) 82%, transparent);
308
- /* the most-used secondary text token, so it has to clear AA on its own */
309
- --ink-soft: color-mix(in srgb, var(--ink) 65%, transparent);
310
-
311
- /* ── lines ───────────────────────────────────────────────────── */
312
- --border: color-mix(in srgb, var(--ink) 14%, transparent);
313
- --input: color-mix(in srgb, var(--ink) 20%, transparent);
314
- --ring: #2a9d8f;
315
-
316
- /* ── roles ───────────────────────────────────────────────────── */
317
- --brand: #001e4e;
318
- /* teal, not the brand navy: navy filled a Button/Switch at ~1.1:1 against
319
- * these surfaces, i.e. an invisible control, and consumers hardcoded teal to
320
- * get around it. The brand navy still lives in --brand. */
321
- --primary: #2a9d8f;
322
- --on-primary: #070d18;
323
- --secondary: #0d1526;
324
- --on-secondary: var(--ink);
325
- /* valence, not significance: a success reads as good while staying quiet */
326
- --positive: #2e9e5b;
327
- --on-positive: #070d18;
328
- --accent-trace: #6b7fa0;
329
- --on-accent-trace: #070d18;
330
- --accent-debug: #2a9d8f;
331
- --on-accent-debug: #070d18;
332
- --accent-info: #4a90d9;
333
- --on-accent-info: #070d18;
334
- --accent-warn: #f2c94c;
335
- --on-accent-warn: #070d18;
336
- /* lifted off the brand red #da251d, which failed AA on a card. This one value
337
- * carries both the fill and the text; the vivid brand red is a gitops
338
- * constant, not a UI token. */
339
- --accent-error: #ef5b52;
340
- --on-accent-error: #070d18;
341
-
342
- --chart-1: #2a9d8f;
343
- --chart-2: #2e9e5b;
344
- --chart-3: #f2c94c;
345
- --chart-4: #e58aae;
346
- --chart-5: #4a90d9;
347
-
348
- /* ── scalars ─────────────────────────────────────────────────── */
349
- --radius: 0.375rem; /* 6px on mobile; bumped to 8px ≥ sm (see @media below) */
350
- /* control geometry, so a consumer reshapes every button/input at once */
351
- --control-radius: calc(var(--radius) - 2px);
352
- --control-py: 0.5rem;
353
- /* the Xl (call-to-action) button reads all three */
354
- --control-px: 2rem;
355
- --control-text: 1rem;
356
- /* multiplies the display type scale (uikit <Display>) */
357
- --display-scale: 1;
358
- /* the vertical rhythm of a full-bleed <Section>; `tight` is the sub-page one */
359
- --band-py: 2.25rem;
360
- --band-py-tight: 2rem;
361
-
362
- --sidebar-width: 14rem;
363
-
364
- /* page container — responsive gutter + max width (uikit <Container>) */
365
- --page-max: 90rem; /* 1440px */
366
- --page-px: 1rem; /* 16px — mobile gutter, scales up below */
367
-
368
- /* shell offset — viewport space reserved by whatever shell hosts the app.
369
- Standalone apps get 0; a hosting shell overrides on :root. Size
370
- viewport-bound surfaces with calc(100dvh - var(--ev-shell-offset, 0px)). */
371
- --ev-shell-offset: 0px;
372
- }
373
-
374
- /* Mobile-first scale-ups: roomier gutters + slightly larger radii on bigger
375
- viewports. Lives here (not per-component) so every consumer scales alike. */
376
- @media (min-width: 640px) {
377
- :root {
378
- --radius: 0.5rem;
379
- --page-px: 1.5rem;
380
- }
381
- }
382
-
383
- @media (min-width: 768px) {
384
- :root {
385
- --band-py: 4rem;
386
- --band-py-tight: 3.5rem;
387
- }
388
- }
389
-
390
- @media (min-width: 1024px) {
391
- :root {
392
- --page-px: 3rem;
393
- }
394
- }
395
-
396
- @theme inline {
397
- --color-foreground: var(--ink);
398
- --color-muted-foreground: var(--ink-soft);
399
- --color-card-foreground: var(--ink);
400
- --color-popover-foreground: var(--ink);
401
- --color-primary-foreground: var(--on-primary);
402
- --color-secondary-foreground: var(--on-secondary);
403
- --color-accent: var(--hover);
404
- --color-accent-foreground: var(--ink);
405
- --color-destructive: var(--accent-error);
406
- --color-destructive-foreground: var(--on-accent-error);
407
-
408
- /* the sidebar namespace was eight aliases of tokens that already existed */
409
- --color-sidebar: var(--secondary);
410
- --color-sidebar-foreground: var(--ink);
411
- --color-sidebar-primary: var(--primary);
412
- --color-sidebar-primary-foreground: var(--on-primary);
413
- --color-sidebar-accent: var(--hover);
414
- --color-sidebar-accent-foreground: var(--ink);
415
- --color-sidebar-border: var(--border);
416
- --color-sidebar-ring: var(--ring);
417
-
418
- --color-main-brand: var(--brand);
419
- --color-main-black: var(--background);
420
- --color-main-surface: var(--secondary);
421
- --color-main-card: var(--card);
422
- --color-main-mist: #e6e1d3;
423
- --color-main-accent-t1: var(--accent-debug);
424
- --color-main-accent-t2: var(--positive);
425
- --color-main-accent-t3: var(--accent-warn);
426
- --color-main-accent-t4: var(--chart-4);
427
-
428
- --color-level-trace: var(--accent-trace);
429
- --color-level-debug: var(--accent-debug);
430
- --color-level-info: var(--accent-info);
431
- --color-level-warn: var(--accent-warn);
432
- --color-level-error: #da251d;
433
- }
434
-
435
- /* The seven tokens whose value moved. Everything else in `tokens.css` already
436
- * carries its pre-0.10 value. */
437
- :root,
438
- .dark {
439
- --ink: white;
440
- --ink-mid: #e6e1d3;
441
- --ink-soft: color-mix(in srgb, #e6e1d3 65%, transparent);
442
- --hover: color-mix(in srgb, #e6e1d3 8%, transparent);
443
- --border: color-mix(in srgb, #e6e1d3 14%, transparent);
444
- --input: color-mix(in srgb, #e6e1d3 20%, transparent);
445
- --on-secondary: white;
446
- }