@ai-matrx/design-system 0.8.0 → 0.10.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/dist/styles.css CHANGED
@@ -190,6 +190,480 @@
190
190
  }
191
191
  }
192
192
 
193
+ /* Collapsible open/close. The SAME rule as the accordion above and a separate
194
+ one on purpose: Radix publishes a DIFFERENT measured variable per primitive
195
+ (`--radix-collapsible-content-height`), and the whole reason this ships here
196
+ is that two matrx-frontend collapsibles reached for the ACCORDION keyframes
197
+ and therefore interpolated to a variable that is never set on a collapsible
198
+ — no animation, no error, for as long as they existed (census row 19f).
199
+ Six more used host-only `slide-down`/`slide-up` keyframes that were keyed
200
+ correctly and existed in exactly one app's globals.css: the same C26 silent
201
+ host contract, wearing a working coat. One duration (200ms) for both
202
+ primitives, so a collapsible and an accordion on the same screen agree.
203
+ Colourless and inert until an element carries the class. */
204
+ @keyframes matrx-collapsible-down {
205
+ from {
206
+ height: 0;
207
+ }
208
+ to {
209
+ height: var(--radix-collapsible-content-height);
210
+ }
211
+ }
212
+ @keyframes matrx-collapsible-up {
213
+ from {
214
+ height: var(--radix-collapsible-content-height);
215
+ }
216
+ to {
217
+ height: 0;
218
+ }
219
+ }
220
+ .matrx-collapsible-content[data-state="open"] {
221
+ animation: matrx-collapsible-down 200ms ease-out;
222
+ }
223
+ .matrx-collapsible-content[data-state="closed"] {
224
+ animation: matrx-collapsible-up 200ms ease-out;
225
+ }
226
+ @media (prefers-reduced-motion: reduce) {
227
+ .matrx-collapsible-content[data-state="open"],
228
+ .matrx-collapsible-content[data-state="closed"] {
229
+ animation: none;
230
+ }
231
+ }
232
+
233
+ /* ═══════════════════════════════════════════════════════════════════════
234
+ THE MOTION LAYER (0.10.0) — every overlay animation this package emits.
235
+
236
+ THE DOCTRINE (FEATURE.md): a primitive that leaves motion to the host has
237
+ not shipped motion, it has shipped a coin flip. 0.8.0 and 0.9.0 closed that
238
+ for Accordion and Collapsible one primitive at a time. The sweep that
239
+ followed found the SAME shape on ten more, and far worse than a missing
240
+ keyframe: every floating surface in this package — Dialog, AlertDialog,
241
+ Sheet, CommandDialog, Popover, DropdownMenu, ContextMenu, HoverCard,
242
+ Select, Tooltip — animated with `animate-in` / `animate-out` /
243
+ `fade-in-0` / `zoom-in-95` / `slide-in-from-*-2`. Those are not Tailwind
244
+ utilities. They come from the `tailwindcss-animate` plugin (or its v4
245
+ successor `tw-animate-css`), which is a HOST dependency this package never
246
+ declared and cannot declare — a Tailwind plugin is loaded by the host's CSS
247
+ entry, not by a node_modules package.
248
+
249
+ Measured on built output, 2026-09-07, before this file owned any of it:
250
+
251
+ matrx-frontend `@import "tw-animate-css"` in globals.css → animated
252
+ aidream/dashboard no plugin → 0 rules
253
+ aidream/workflow-studio no plugin → 0 rules
254
+ matrx-extend `tailwindcss-animate` in package.json but
255
+ NEVER LOADED (it is a Tailwind v3 plugin and
256
+ extend is on v4 with no `@plugin` directive)
257
+ → 0 rules
258
+
259
+ Three consumers out of four. `grep -c 'animate-in\|slide-in-from'` on each
260
+ app's built stylesheet returned 0. Every dialog, dropdown, popover, tooltip
261
+ and sheet in the two aidream apps and the Chrome extension appeared
262
+ instantly, with no animation and no error, for as long as they existed.
263
+ The one host that worked, worked by accident of its own CSS entry.
264
+
265
+ So the package owns it. Below: package-defined keyframes, applied through
266
+ `matrx-`-prefixed classes the components put on the DOM themselves, timed
267
+ from `--matrx-motion-*` tokens, silenced by `prefers-reduced-motion`, and
268
+ asserted by `styles.test.ts` + the packed-tarball canary.
269
+
270
+ WHERE THE LINE IS. This layer owns KEYFRAME ANIMATION — motion that either
271
+ happens or silently does not. It deliberately does NOT own Tailwind's
272
+ `transition-*` / `duration-*` / `ease-*` utilities that the primitives also
273
+ carry (a hover colour easing, the progress bar's width, the close button's
274
+ opacity). Those are CORE Tailwind, present in every Tailwind host, and
275
+ their absence degrades to an instant-but-correct change rather than to a
276
+ surface that lies about its state. Re-implementing them here would mean
277
+ shipping a copy of Tailwind.
278
+
279
+ UNLAYERED on purpose, exactly like the accordion and collapsible rules
280
+ above: a host that still loads `tw-animate-css` would otherwise emit a
281
+ competing `animation` from `@layer utilities` and win, giving that one host
282
+ different motion from everyone else — the coin flip again, wearing a
283
+ working coat. Unlayered means every consumer gets identical motion. The
284
+ sanctioned escape is the `animated={false}` prop on the component, not a
285
+ className.
286
+ ═══════════════════════════════════════════════════════════════════════ */
287
+
288
+ /* --- scrim ------------------------------------------------------------- */
289
+ @keyframes matrx-fade-in {
290
+ from {
291
+ opacity: 0;
292
+ }
293
+ to {
294
+ opacity: 1;
295
+ }
296
+ }
297
+ @keyframes matrx-fade-out {
298
+ from {
299
+ opacity: 1;
300
+ }
301
+ to {
302
+ opacity: 0;
303
+ }
304
+ }
305
+ .matrx-motion-overlay[data-state="open"] {
306
+ animation: matrx-fade-in var(--matrx-motion-duration-base)
307
+ var(--matrx-motion-ease-out);
308
+ }
309
+ .matrx-motion-overlay[data-state="closed"] {
310
+ animation: matrx-fade-out var(--matrx-motion-duration-base)
311
+ var(--matrx-motion-ease-in);
312
+ }
313
+
314
+ /* --- centred dialog card ----------------------------------------------
315
+ The card is positioned by `translate-x-[-50%] translate-y-[-50%]`
316
+ utilities, and an animation REPLACES `transform` for its whole duration —
317
+ which is why the shadcn original had to re-state the centring inside the
318
+ motion (`slide-in-from-left-1/2 slide-in-from-top-[48%]`). Owning the
319
+ keyframes lets us just say it: the card rises 2% and scales up from 95%,
320
+ ending exactly on the resting transform so nothing jumps at the end. */
321
+ @keyframes matrx-dialog-in {
322
+ from {
323
+ opacity: 0;
324
+ transform: translate(-50%, -48%) scale(0.95);
325
+ }
326
+ to {
327
+ opacity: 1;
328
+ transform: translate(-50%, -50%) scale(1);
329
+ }
330
+ }
331
+ @keyframes matrx-dialog-out {
332
+ from {
333
+ opacity: 1;
334
+ transform: translate(-50%, -50%) scale(1);
335
+ }
336
+ to {
337
+ opacity: 0;
338
+ transform: translate(-50%, -48%) scale(0.95);
339
+ }
340
+ }
341
+ .matrx-motion-dialog[data-state="open"] {
342
+ animation: matrx-dialog-in var(--matrx-motion-duration-base)
343
+ var(--matrx-motion-ease-out);
344
+ }
345
+ .matrx-motion-dialog[data-state="closed"] {
346
+ animation: matrx-dialog-out var(--matrx-motion-duration-base)
347
+ var(--matrx-motion-ease-in);
348
+ }
349
+
350
+ /* --- edge sheets -------------------------------------------------------
351
+ Five named classes rather than one `data-side` rule, because a Sheet's side
352
+ is a `sheetVariants` variant on the element's own class list — Radix
353
+ publishes no `data-side` on a Dialog-based sheet. The mobile Dialog /
354
+ CommandDialog bottom sheet reuses `-bottom`. */
355
+ @keyframes matrx-slide-in-top {
356
+ from {
357
+ transform: translateY(-100%);
358
+ }
359
+ to {
360
+ transform: translateY(0);
361
+ }
362
+ }
363
+ @keyframes matrx-slide-out-top {
364
+ from {
365
+ transform: translateY(0);
366
+ }
367
+ to {
368
+ transform: translateY(-100%);
369
+ }
370
+ }
371
+ @keyframes matrx-slide-in-bottom {
372
+ from {
373
+ transform: translateY(100%);
374
+ }
375
+ to {
376
+ transform: translateY(0);
377
+ }
378
+ }
379
+ @keyframes matrx-slide-out-bottom {
380
+ from {
381
+ transform: translateY(0);
382
+ }
383
+ to {
384
+ transform: translateY(100%);
385
+ }
386
+ }
387
+ @keyframes matrx-slide-in-left {
388
+ from {
389
+ transform: translateX(-100%);
390
+ }
391
+ to {
392
+ transform: translateX(0);
393
+ }
394
+ }
395
+ @keyframes matrx-slide-out-left {
396
+ from {
397
+ transform: translateX(0);
398
+ }
399
+ to {
400
+ transform: translateX(-100%);
401
+ }
402
+ }
403
+ @keyframes matrx-slide-in-right {
404
+ from {
405
+ transform: translateX(100%);
406
+ }
407
+ to {
408
+ transform: translateX(0);
409
+ }
410
+ }
411
+ @keyframes matrx-slide-out-right {
412
+ from {
413
+ transform: translateX(0);
414
+ }
415
+ to {
416
+ transform: translateX(100%);
417
+ }
418
+ }
419
+ .matrx-motion-sheet-top[data-state="open"] {
420
+ animation: matrx-slide-in-top var(--matrx-motion-duration-sheet)
421
+ var(--matrx-motion-ease-out);
422
+ }
423
+ .matrx-motion-sheet-top[data-state="closed"] {
424
+ animation: matrx-slide-out-top var(--matrx-motion-duration-slow)
425
+ var(--matrx-motion-ease-in);
426
+ }
427
+ .matrx-motion-sheet-bottom[data-state="open"] {
428
+ animation: matrx-slide-in-bottom var(--matrx-motion-duration-sheet)
429
+ var(--matrx-motion-ease-out);
430
+ }
431
+ .matrx-motion-sheet-bottom[data-state="closed"] {
432
+ animation: matrx-slide-out-bottom var(--matrx-motion-duration-slow)
433
+ var(--matrx-motion-ease-in);
434
+ }
435
+ .matrx-motion-sheet-left[data-state="open"] {
436
+ animation: matrx-slide-in-left var(--matrx-motion-duration-sheet)
437
+ var(--matrx-motion-ease-out);
438
+ }
439
+ .matrx-motion-sheet-left[data-state="closed"] {
440
+ animation: matrx-slide-out-left var(--matrx-motion-duration-slow)
441
+ var(--matrx-motion-ease-in);
442
+ }
443
+ .matrx-motion-sheet-right[data-state="open"] {
444
+ animation: matrx-slide-in-right var(--matrx-motion-duration-sheet)
445
+ var(--matrx-motion-ease-out);
446
+ }
447
+ .matrx-motion-sheet-right[data-state="closed"] {
448
+ animation: matrx-slide-out-right var(--matrx-motion-duration-slow)
449
+ var(--matrx-motion-ease-in);
450
+ }
451
+ /* A `side="center"` Sheet has no edge to come from, so it fades like a
452
+ dialog scrim rather than sliding from nowhere. */
453
+ .matrx-motion-sheet-center[data-state="open"] {
454
+ animation: matrx-fade-in var(--matrx-motion-duration-sheet)
455
+ var(--matrx-motion-ease-out);
456
+ }
457
+ .matrx-motion-sheet-center[data-state="closed"] {
458
+ animation: matrx-fade-out var(--matrx-motion-duration-slow)
459
+ var(--matrx-motion-ease-in);
460
+ }
461
+ /* The mobile Dialog / CommandDialog bottom sheet: slides AND fades, at the
462
+ dialog's 200ms rather than the Sheet's 500ms, because it is a dialog
463
+ wearing sheet geometry and it opens from a tap, not from a nav gesture. */
464
+ @keyframes matrx-bottom-sheet-in {
465
+ from {
466
+ opacity: 0;
467
+ transform: translateY(100%);
468
+ }
469
+ to {
470
+ opacity: 1;
471
+ transform: translateY(0);
472
+ }
473
+ }
474
+ @keyframes matrx-bottom-sheet-out {
475
+ from {
476
+ opacity: 1;
477
+ transform: translateY(0);
478
+ }
479
+ to {
480
+ opacity: 0;
481
+ transform: translateY(100%);
482
+ }
483
+ }
484
+ .matrx-motion-bottom-sheet[data-state="open"] {
485
+ animation: matrx-bottom-sheet-in var(--matrx-motion-duration-base)
486
+ var(--matrx-motion-ease-out);
487
+ }
488
+ .matrx-motion-bottom-sheet[data-state="closed"] {
489
+ animation: matrx-bottom-sheet-out var(--matrx-motion-duration-base)
490
+ var(--matrx-motion-ease-in);
491
+ }
492
+
493
+ /* --- poppers -----------------------------------------------------------
494
+ Popover, DropdownMenu, ContextMenu, HoverCard, Select and Tooltip all
495
+ position through Radix's popper, which publishes `data-side` on the content
496
+ and `--radix-popper-transform-origin` — the point on the surface that faces
497
+ the trigger. Reading that origin is a correctness fix the `zoom-in-95`
498
+ original never had: the surface now grows OUT OF its anchor instead of out
499
+ of its own centre, so a menu below a button no longer appears to expand
500
+ upward into the button.
501
+
502
+ `data-side` says where the surface sits, so it travels FROM the trigger:
503
+ a surface on the `bottom` starts slightly above where it lands. Exit fades
504
+ and shrinks without sliding — matching the originals, and the right
505
+ behaviour, since a surface leaving should not appear to move somewhere. */
506
+ @keyframes matrx-popper-in-from-top {
507
+ from {
508
+ opacity: 0;
509
+ transform: scale(0.95) translateY(calc(-1 * var(--matrx-motion-popper-travel)));
510
+ }
511
+ to {
512
+ opacity: 1;
513
+ transform: scale(1) translateY(0);
514
+ }
515
+ }
516
+ @keyframes matrx-popper-in-from-bottom {
517
+ from {
518
+ opacity: 0;
519
+ transform: scale(0.95) translateY(var(--matrx-motion-popper-travel));
520
+ }
521
+ to {
522
+ opacity: 1;
523
+ transform: scale(1) translateY(0);
524
+ }
525
+ }
526
+ @keyframes matrx-popper-in-from-left {
527
+ from {
528
+ opacity: 0;
529
+ transform: scale(0.95) translateX(calc(-1 * var(--matrx-motion-popper-travel)));
530
+ }
531
+ to {
532
+ opacity: 1;
533
+ transform: scale(1) translateX(0);
534
+ }
535
+ }
536
+ @keyframes matrx-popper-in-from-right {
537
+ from {
538
+ opacity: 0;
539
+ transform: scale(0.95) translateX(var(--matrx-motion-popper-travel));
540
+ }
541
+ to {
542
+ opacity: 1;
543
+ transform: scale(1) translateX(0);
544
+ }
545
+ }
546
+ @keyframes matrx-popper-out {
547
+ from {
548
+ opacity: 1;
549
+ transform: scale(1);
550
+ }
551
+ to {
552
+ opacity: 0;
553
+ transform: scale(0.95);
554
+ }
555
+ }
556
+ /* THE RIGHT VARIABLE, PER PRIMITIVE. Radix publishes the anchor-facing origin
557
+ under the shared popper name AND under a per-primitive alias, and which one
558
+ is present depends on the primitive — `Select` sets
559
+ `--radix-select-content-transform-origin`, `Tooltip` sets
560
+ `--radix-tooltip-content-transform-origin`, and so on. Reusing one
561
+ primitive's variable on another is the exact silent death census row 19f
562
+ recorded (accordion keyframes on a collapsible panel), so this states the
563
+ whole chain rather than betting on one name. If none resolves, the origin
564
+ falls back to the element's centre — degraded, never broken. */
565
+ .matrx-motion-popper {
566
+ transform-origin: var(
567
+ --radix-popper-transform-origin,
568
+ var(
569
+ --radix-select-content-transform-origin,
570
+ var(
571
+ --radix-tooltip-content-transform-origin,
572
+ var(
573
+ --radix-dropdown-menu-content-transform-origin,
574
+ var(
575
+ --radix-context-menu-content-transform-origin,
576
+ var(
577
+ --radix-hover-card-content-transform-origin,
578
+ var(--radix-popover-content-transform-origin, center)
579
+ )
580
+ )
581
+ )
582
+ )
583
+ )
584
+ );
585
+ }
586
+ /* A surface rendered BELOW the trigger enters from above it, and so on. The
587
+ bare `[data-state=open]` rule is the fallback for the frame before the
588
+ popper has measured a side (and for `Select` in `item-aligned` position,
589
+ which publishes no side at all). */
590
+ .matrx-motion-popper[data-state="open"] {
591
+ animation: matrx-popper-in-from-top var(--matrx-motion-duration-fast)
592
+ var(--matrx-motion-ease-out);
593
+ }
594
+ .matrx-motion-popper[data-state="open"][data-side="bottom"] {
595
+ animation-name: matrx-popper-in-from-top;
596
+ }
597
+ .matrx-motion-popper[data-state="open"][data-side="top"] {
598
+ animation-name: matrx-popper-in-from-bottom;
599
+ }
600
+ .matrx-motion-popper[data-state="open"][data-side="right"] {
601
+ animation-name: matrx-popper-in-from-left;
602
+ }
603
+ .matrx-motion-popper[data-state="open"][data-side="left"] {
604
+ animation-name: matrx-popper-in-from-right;
605
+ }
606
+ .matrx-motion-popper[data-state="closed"] {
607
+ animation: matrx-popper-out var(--matrx-motion-duration-fast)
608
+ var(--matrx-motion-ease-in);
609
+ }
610
+
611
+ /* --- the two indefinite loops -----------------------------------------
612
+ `animate-pulse` and `animate-spin` ARE core Tailwind, so they survive in a
613
+ Tailwind host — but a Skeleton that does not pulse is indistinguishable
614
+ from an empty grey box, and a spinner that does not spin claims the work
615
+ has stopped. Both are load-bearing lies in a non-Tailwind consumer, and
616
+ both are four lines. The package owns them. */
617
+ @keyframes matrx-pulse {
618
+ 0%,
619
+ 100% {
620
+ opacity: 1;
621
+ }
622
+ 50% {
623
+ opacity: 0.5;
624
+ }
625
+ }
626
+ @keyframes matrx-spin {
627
+ to {
628
+ transform: rotate(360deg);
629
+ }
630
+ }
631
+ .matrx-pulse {
632
+ animation: matrx-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
633
+ }
634
+ .matrx-spin {
635
+ animation: matrx-spin 1s linear infinite;
636
+ }
637
+
638
+ /* --- the accessibility switch -----------------------------------------
639
+ ONE block for the whole motion layer. A surface still appears and
640
+ disappears — it just does so without travelling. The two indefinite loops
641
+ are the exception that proves the rule: a Skeleton and a spinner say
642
+ "still working" with motion and nothing else, so silencing them entirely
643
+ would remove the information. They slow down instead of stopping.
644
+
645
+ Deliberately NOT a `--matrx-motion-*` token: a user's OS-level reduced
646
+ motion preference is not an org setting a host may override. */
647
+ @media (prefers-reduced-motion: reduce) {
648
+ .matrx-motion-overlay[data-state],
649
+ .matrx-motion-dialog[data-state],
650
+ .matrx-motion-sheet-top[data-state],
651
+ .matrx-motion-sheet-bottom[data-state],
652
+ .matrx-motion-sheet-left[data-state],
653
+ .matrx-motion-sheet-right[data-state],
654
+ .matrx-motion-sheet-center[data-state],
655
+ .matrx-motion-bottom-sheet[data-state],
656
+ .matrx-motion-popper[data-state] {
657
+ animation: none;
658
+ }
659
+ .matrx-pulse {
660
+ animation-duration: 4s;
661
+ }
662
+ .matrx-spin {
663
+ animation-duration: 3s;
664
+ }
665
+ }
666
+
193
667
  /* ────────────────────────────────────────────────────────────────────────
194
668
  Scroll fade — the "there is more below" cue. UNLAYERED on purpose.
195
669
  Driven by `useScrollFade`, which sets the data attributes ONLY on edges
package/dist/tokens.css CHANGED
@@ -117,6 +117,31 @@
117
117
  0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04);
118
118
  --matrx-glass-shadow-lg:
119
119
  0 4px 16px rgba(0, 0, 0, 0.08), 0 2px 6px rgba(0, 0, 0, 0.04);
120
+
121
+ /* MOTION (0.10.0). Every package-owned animation in styles.css reads its
122
+ duration and easing from here, so a host retimes the whole system by
123
+ redeclaring four values — and an org that wants calmer motion sets them
124
+ once instead of overriding twenty rules. They are NOT the accessibility
125
+ switch: `prefers-reduced-motion: reduce` silences the animations
126
+ outright in styles.css, regardless of these values.
127
+
128
+ The numbers are the ones the primitives already carried as Tailwind
129
+ `duration-*` utilities before the package owned the keyframes:
130
+ poppers/tooltips 150ms, dialogs and overlays 200ms, a sheet closing
131
+ 300ms and opening 500ms. */
132
+ --matrx-motion-duration-fast: 150ms;
133
+ --matrx-motion-duration-base: 200ms;
134
+ --matrx-motion-duration-slow: 300ms;
135
+ --matrx-motion-duration-sheet: 500ms;
136
+ /* Enters decelerate (the surface arrives and settles); exits accelerate
137
+ (it leaves and is gone). Linear on both — which is what
138
+ `tailwindcss-animate` defaulted to — reads mechanical at these
139
+ durations. */
140
+ --matrx-motion-ease-out: cubic-bezier(0, 0, 0.2, 1);
141
+ --matrx-motion-ease-in: cubic-bezier(0.4, 0, 1, 1);
142
+ /* The distance a popper travels toward its anchor as it opens. 0.5rem is
143
+ the `slide-in-from-*-2` the primitives carried. */
144
+ --matrx-motion-popper-travel: 0.5rem;
120
145
  }
121
146
 
122
147
  /* Dark theme. Matches the `.dark`-class convention (Tailwind v4 hosts wire
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-matrx/design-system",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "description": "AI Matrx semantic React primitives shared across Vite, Next.js, desktop, and customer-built applications.",
5
5
  "type": "module",
6
6
  "license": "MIT",