@siyahi/ui 0.1.9-next.34 → 0.1.9-next.36

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 (101) hide show
  1. package/DESIGN.md +43 -26
  2. package/README.md +12 -14
  3. package/THIRD_PARTY_NOTICES.md +10 -11
  4. package/agents/examples/app-sidebar.tsx +0 -2
  5. package/agents/examples/chart-panels.tsx +0 -4
  6. package/agents/examples/{section-cards-select.tsx → metric-strip-select.tsx} +5 -5
  7. package/agents/examples/metric-strip-trend.tsx +33 -0
  8. package/agents/examples/overview-dashboard.tsx +4 -4
  9. package/agents/examples/scatter-chart.tsx +0 -1
  10. package/agents/examples/stale-blocks.tsx +9 -9
  11. package/agents/examples/surfaces.tsx +2 -3
  12. package/agents/manifest.json +27 -49
  13. package/dist/components/registry/dashboardcn/kpi-row-card.d.ts +14 -6
  14. package/dist/components/registry/dashboardcn/kpi-row-card.js +49 -32
  15. package/dist/components/registry/niko-table/core/data-table-error-boundary.js +1 -1
  16. package/dist/components/registry/niko-table/filters/table-faceted-filter.js +2 -2
  17. package/dist/components/siyahi/panel-grid.d.ts +5 -6
  18. package/dist/components/siyahi/panel-grid.js +10 -9
  19. package/dist/components/siyahi/scatter-chart.d.ts +3 -3
  20. package/dist/components/siyahi/scatter-chart.js +6 -3
  21. package/dist/components/siyahi/siyahi-provider.d.ts +5 -6
  22. package/dist/components/siyahi/siyahi-provider.js +1 -1
  23. package/dist/components/siyahi/status-badge.js +1 -0
  24. package/dist/components/ui/alert-dialog.d.ts +20 -0
  25. package/dist/components/ui/alert-dialog.js +94 -0
  26. package/dist/components/ui/aspect-ratio.d.ts +6 -0
  27. package/dist/components/ui/aspect-ratio.js +13 -0
  28. package/dist/components/ui/attachment.d.ts +25 -0
  29. package/dist/components/ui/attachment.js +104 -0
  30. package/dist/components/ui/bubble.d.ts +18 -0
  31. package/dist/components/ui/bubble.js +73 -0
  32. package/dist/components/ui/button-group.d.ts +12 -0
  33. package/dist/components/ui/button-group.js +41 -0
  34. package/dist/components/ui/button.d.ts +1 -1
  35. package/dist/components/ui/button.js +1 -1
  36. package/dist/components/ui/carousel.d.ts +30 -0
  37. package/dist/components/ui/carousel.js +131 -0
  38. package/dist/components/ui/checkbox.d.ts +5 -0
  39. package/dist/components/ui/checkbox.js +20 -0
  40. package/dist/components/ui/combobox.d.ts +26 -0
  41. package/dist/components/ui/combobox.js +162 -0
  42. package/dist/components/ui/command.js +1 -1
  43. package/dist/components/ui/direction.d.ts +2 -0
  44. package/dist/components/ui/direction.js +1 -0
  45. package/dist/components/ui/drawer.d.ts +18 -0
  46. package/dist/components/ui/drawer.js +123 -0
  47. package/dist/components/ui/empty.d.ts +13 -0
  48. package/dist/components/ui/empty.js +59 -0
  49. package/dist/components/ui/field.js +1 -1
  50. package/dist/components/ui/hover-card.d.ts +7 -0
  51. package/dist/components/ui/hover-card.js +36 -0
  52. package/dist/components/ui/input-otp.d.ts +13 -0
  53. package/dist/components/ui/input-otp.js +46 -0
  54. package/dist/components/ui/item.d.ts +24 -0
  55. package/dist/components/ui/item.js +121 -0
  56. package/dist/components/ui/kbd.d.ts +5 -0
  57. package/dist/components/ui/kbd.js +19 -0
  58. package/dist/components/ui/marker.d.ts +12 -0
  59. package/dist/components/ui/marker.js +43 -0
  60. package/dist/components/ui/menubar.d.ts +31 -0
  61. package/dist/components/ui/menubar.js +136 -0
  62. package/dist/components/ui/message-scroller.d.ts +12 -0
  63. package/dist/components/ui/message-scroller.js +60 -0
  64. package/dist/components/ui/message.d.ts +12 -0
  65. package/dist/components/ui/message.js +49 -0
  66. package/dist/components/ui/native-select.d.ts +10 -0
  67. package/dist/components/ui/native-select.js +38 -0
  68. package/dist/components/ui/navigation-menu.d.ts +13 -0
  69. package/dist/components/ui/navigation-menu.js +82 -0
  70. package/dist/components/ui/pagination.d.ts +19 -0
  71. package/dist/components/ui/pagination.js +80 -0
  72. package/dist/components/ui/questionnaire.d.ts +21 -0
  73. package/dist/components/ui/questionnaire.js +170 -0
  74. package/dist/components/ui/radio-group.d.ts +7 -0
  75. package/dist/components/ui/radio-group.js +26 -0
  76. package/dist/components/ui/resizable.d.ts +9 -0
  77. package/dist/components/ui/resizable.js +28 -0
  78. package/dist/components/ui/sidebar.js +1 -1
  79. package/dist/components/ui/spinner.d.ts +4 -0
  80. package/dist/components/ui/spinner.js +15 -0
  81. package/dist/components/ui/switch.d.ts +7 -0
  82. package/dist/components/ui/switch.js +19 -0
  83. package/dist/components/ui/toast.d.ts +18 -0
  84. package/dist/components/ui/toast.js +123 -0
  85. package/dist/components/ui/trace.d.ts +20 -0
  86. package/dist/components/ui/trace.js +75 -0
  87. package/dist/index.d.ts +32 -3
  88. package/dist/index.js +35 -5
  89. package/dist/lib/chart-frame.d.ts +1 -1
  90. package/dist/lib/chart-frame.js +3 -1
  91. package/dist/lib/chart-series.d.ts +10 -2
  92. package/dist/lib/chart-series.js +10 -0
  93. package/package.json +5 -3
  94. package/registry.lock.json +0 -8
  95. package/styles/siyahi.css +257 -109
  96. package/agents/examples/section-cards-trend.tsx +0 -32
  97. package/agents/examples/section-cards.tsx +0 -48
  98. package/agents/report-rules.json +0 -7
  99. package/dist/components/registry/dashboard-01/section-cards.d.ts +0 -30
  100. package/dist/components/registry/dashboard-01/section-cards.js +0 -74
  101. package/report.css +0 -146
package/styles/siyahi.css CHANGED
@@ -24,6 +24,21 @@
24
24
  */
25
25
  @custom-variant dark (@media (prefers-color-scheme: dark));
26
26
 
27
+ /*
28
+ * Nothing casts a drop shadow (DESIGN.md, "Floating surfaces lie flat"): every shadow size is transparent, so
29
+ * shadow-md and the rest draw nothing, on a component added later as on the ones here. The sizes stay defined, so
30
+ * a stock shadow class is still a known utility to Tailwind and the lint.
31
+ */
32
+ @theme {
33
+ --shadow-2xs: 0 0 #0000;
34
+ --shadow-xs: 0 0 #0000;
35
+ --shadow-sm: 0 0 #0000;
36
+ --shadow-md: 0 0 #0000;
37
+ --shadow-lg: 0 0 #0000;
38
+ --shadow-xl: 0 0 #0000;
39
+ --shadow-2xl: 0 0 #0000;
40
+ }
41
+
27
42
  @theme inline {
28
43
  --font-sans: "Geist Variable", "Geist Fallback", ui-sans-serif, system-ui, sans-serif;
29
44
  --font-mono: "Geist Mono Variable", "Geist Mono Fallback", ui-monospace, monospace;
@@ -160,7 +175,7 @@
160
175
  --chart-1: oklch(0.4 0.17 275);
161
176
  --chart-2: oklch(0.66 0.13 70);
162
177
  --chart-3: oklch(0.38 0.13 335);
163
- --chart-4: oklch(0.65 0.11 235);
178
+ --chart-4: oklch(0.64 0.11 235);
164
179
  --chart-5: oklch(0.36 0.06 205);
165
180
  --sidebar: oklch(0.985 0 0);
166
181
  --sidebar-foreground: oklch(0.145 0 0);
@@ -174,8 +189,8 @@
174
189
  @variant dark {
175
190
  /*
176
191
  * The canvas lifts just off black and text sits a step below white:
177
- * near-white on near-black (19:1) blurs for many readers, and with the
178
- * cards' grey gone on a ruled page the canvas is what text sits on.
192
+ * near-white on near-black (19:1) blurs for many readers, and on an open
193
+ * page, where cards have no fill, the canvas is what text sits on.
179
194
  * Claude Code (#181818, about #e8e8e8 text) and T3 Code (#0a0a0a, about
180
195
  * #bebebe) both narrow that gap; this sits between them, at 14.9:1.
181
196
  * Cards sit one step above the canvas, so data reads as raised, as the
@@ -310,7 +325,7 @@
310
325
 
311
326
  /*
312
327
  * A component that lifts a part of itself with a z-index is a stacking context
313
- * of its own: SectionCards' picker button, an avatar's badge, a calendar's
328
+ * of its own: MetricStrip's picker buttons, an avatar's badge, a calendar's
314
329
  * days, a focused toggle, a code block's buttons, a price chart's layers. The
315
330
  * lifted part then ranks only against the rest of its component, and can never
316
331
  * sit over, or take a tap from, a page's sticky header or section strip (also
@@ -325,7 +340,13 @@
325
340
  [data-slot="calendar"],
326
341
  [data-slot="toggle-group"],
327
342
  [data-slot="code-block"],
328
- [data-slot="price-chart"]
343
+ [data-slot="price-chart"],
344
+ [data-slot="button-group"],
345
+ [data-slot="input-otp-group"],
346
+ [data-slot="resizable-panel-group"],
347
+ [data-slot="attachment"],
348
+ [data-slot="bubble"],
349
+ [data-slot="questionnaire"]
329
350
  ) {
330
351
  isolation: isolate;
331
352
  }
@@ -409,11 +430,20 @@
409
430
  [role="alertdialog"],
410
431
  [role="menu"],
411
432
  [role="listbox"],
412
- [data-slot="input"],
413
- [data-slot="textarea"],
414
- [data-slot="select-trigger"],
415
- [data-slot="input-group-control"],
416
- [data-slot="command-input"]
433
+ input:not(
434
+ [type="checkbox"],
435
+ [type="radio"],
436
+ [type="range"],
437
+ [type="hidden"],
438
+ [type="file"],
439
+ [type="button"],
440
+ [type="submit"],
441
+ [type="reset"],
442
+ [type="color"]
443
+ ),
444
+ textarea,
445
+ select,
446
+ [data-slot="select-trigger"]
417
447
  ),
418
448
  [data-slot="slider-thumb"]:has(input:focus-visible) {
419
449
  outline: none;
@@ -421,14 +451,74 @@
421
451
  inset 0 0 0 2px var(--ring),
422
452
  inset 0 0 0 4px var(--background);
423
453
  }
424
- :is([data-slot="input"], [data-slot="textarea"], [data-slot="select-trigger"]):focus-visible,
425
- [data-slot="input-group"]:has(:is([data-slot="input-group-control"], [data-slot="command-input"]):focus-visible) {
454
+ /*
455
+ * A field is what HTML makes one, a text input, a textarea or a select, plus Base UI's select trigger, so a field in
456
+ * a component added later is covered without naming it. A field inside a box that holds it (an input group, a
457
+ * combobox's chips) shows its state on that box.
458
+ */
459
+ :is(
460
+ input:not(
461
+ [type="checkbox"],
462
+ [type="radio"],
463
+ [type="range"],
464
+ [type="hidden"],
465
+ [type="file"],
466
+ [type="button"],
467
+ [type="submit"],
468
+ [type="reset"],
469
+ [type="color"]
470
+ ),
471
+ textarea,
472
+ select,
473
+ [data-slot="select-trigger"]
474
+ ):not(:is([data-slot="input-group"], [data-slot="combobox-chips"]) *):focus-visible,
475
+ :is([data-slot="input-group"], [data-slot="combobox-chips"]):has(
476
+ :is(
477
+ input:not(
478
+ [type="checkbox"],
479
+ [type="radio"],
480
+ [type="range"],
481
+ [type="hidden"],
482
+ [type="file"],
483
+ [type="button"],
484
+ [type="submit"],
485
+ [type="reset"],
486
+ [type="color"]
487
+ ),
488
+ textarea,
489
+ select,
490
+ [data-slot="select-trigger"]
491
+ ):focus-visible
492
+ ) {
426
493
  outline: none;
427
494
  border-color: var(--ring);
428
495
  box-shadow: inset 0 0 0 1px var(--ring);
429
496
  }
430
- :is([data-slot="input"], [data-slot="textarea"], [data-slot="select-trigger"])[aria-invalid="true"]:focus-visible,
431
- [data-slot="input-group"]:has([data-slot="input-group-control"][aria-invalid="true"]:focus-visible) {
497
+ :is(
498
+ input:not(
499
+ [type="checkbox"],
500
+ [type="radio"],
501
+ [type="range"],
502
+ [type="hidden"],
503
+ [type="file"],
504
+ [type="button"],
505
+ [type="submit"],
506
+ [type="reset"],
507
+ [type="color"]
508
+ ),
509
+ textarea,
510
+ select,
511
+ [data-slot="select-trigger"]
512
+ ):not(:is([data-slot="input-group"], [data-slot="combobox-chips"]) *)[aria-invalid="true"]:focus-visible,
513
+ /* Names the field as the focus rule above does, so this rule outranks it rather than losing on specificity. */
514
+ :is([data-slot="input-group"], [data-slot="combobox-chips"]):has(
515
+ :is(
516
+ input:not([type="checkbox"], [type="radio"], [type="range"], [type="hidden"], [type="file"], [type="button"], [type="submit"], [type="reset"], [type="color"]),
517
+ textarea,
518
+ select,
519
+ [data-slot="select-trigger"]
520
+ )[aria-invalid="true"]:focus-visible
521
+ ) {
432
522
  border-color: var(--destructive);
433
523
  box-shadow: inset 0 0 0 1px var(--destructive);
434
524
  }
@@ -460,17 +550,43 @@ svg :is(rect, circle, path)[tabindex]:focus-visible,
460
550
  [class~="group/button"][class~="text-destructive"]:focus-visible,
461
551
  [data-slot="code-block"][data-variant="destructive"]:has(> [data-slot="code-block-content"]:focus-visible),
462
552
  :is(
463
- [data-slot="input"],
464
- [data-slot="textarea"],
465
- [data-slot="select-trigger"],
466
- [class~="group/button"]
467
- )[aria-invalid="true"],
468
- [data-slot="input-group"]:has([data-slot][aria-invalid="true"]) {
553
+ input:not(
554
+ [type="checkbox"],
555
+ [type="radio"],
556
+ [type="range"],
557
+ [type="hidden"],
558
+ [type="file"],
559
+ [type="button"],
560
+ [type="submit"],
561
+ [type="reset"],
562
+ [type="color"]
563
+ ),
564
+ textarea,
565
+ select,
566
+ [data-slot="select-trigger"]
567
+ ):not(:is([data-slot="input-group"], [data-slot="combobox-chips"]) *)[aria-invalid="true"],
568
+ [class~="group/button"][aria-invalid="true"],
569
+ :is([data-slot="input-group"], [data-slot="combobox-chips"]):has([aria-invalid="true"]) {
469
570
  border-color: var(--destructive);
470
571
  }
471
572
  /* An invalid field also takes a faint wash of the red, so it reads as the one to fix before its message is read. */
472
- :is([data-slot="input"], [data-slot="textarea"], [data-slot="select-trigger"])[aria-invalid="true"],
473
- [data-slot="input-group"]:has([data-slot][aria-invalid="true"]) {
573
+ :is(
574
+ input:not(
575
+ [type="checkbox"],
576
+ [type="radio"],
577
+ [type="range"],
578
+ [type="hidden"],
579
+ [type="file"],
580
+ [type="button"],
581
+ [type="submit"],
582
+ [type="reset"],
583
+ [type="color"]
584
+ ),
585
+ textarea,
586
+ select,
587
+ [data-slot="select-trigger"]
588
+ ):not(:is([data-slot="input-group"], [data-slot="combobox-chips"]) *)[aria-invalid="true"],
589
+ :is([data-slot="input-group"], [data-slot="combobox-chips"]):has([aria-invalid="true"]) {
474
590
  background-color: color-mix(in oklab, var(--destructive) var(--field-wash), var(--card));
475
591
  }
476
592
 
@@ -485,48 +601,86 @@ svg :is(rect, circle, path)[tabindex]:focus-visible,
485
601
  @apply font-mono text-2xs tracking-wider text-muted-foreground uppercase;
486
602
  }
487
603
 
488
- /* A group's label in a menu, a select's list or the command palette is a label over its items, set as one. */
604
+ /*
605
+ * A group's label in a menu (dropdown, context, menubar), a select's or a combobox's list or the
606
+ * command palette is a label over its items, set as one.
607
+ */
489
608
  :is(
490
- [data-slot="dropdown-menu-label"],
491
- [data-slot="context-menu-label"],
609
+ [data-slot$="menu-label"],
610
+ [data-slot="menubar-label"],
492
611
  [data-slot="select-label"],
612
+ [data-slot="combobox-label"],
493
613
  [data-slot="command-group"] [cmdk-group-heading]
494
614
  ) {
495
615
  @apply font-mono text-2xs tracking-wider text-muted-foreground uppercase;
496
616
  }
497
617
 
498
618
  /*
499
- * A menu, a select's list, a popover, a dialog, a sheet and a chart's tooltip read as a sheet of the page, not a
500
- * bubble floating over it: no drop shadow, and the card's hairline for an edge (stock rings them fainter).
619
+ * A floating surface reads as a sheet of the page, not a bubble over it: no shadow (the theme has none) and the
620
+ * card's hairline for an edge. Stock shadcn edges a menu, a list, a popover, a dialog or a card with a fainter ring
621
+ * (ring-foreground/10, also behind a variant such as data-open:); it takes the hairline wherever it appears, on
622
+ * a component added later too. The
623
+ * chart's tooltip, which Recharts draws, is named.
501
624
  */
502
- :is(
503
- [data-slot="dropdown-menu-content"],
504
- [data-slot="dropdown-menu-sub-content"],
505
- [data-slot="context-menu-content"],
506
- [data-slot="context-menu-sub-content"],
507
- [data-slot="select-content"],
508
- [data-slot="popover-content"],
509
- [data-slot="dialog-content"],
510
- [data-slot="sheet-content"],
511
- [data-slot="chart"] .recharts-tooltip-wrapper > div
512
- ) {
513
- --tw-shadow: 0 0 #0000;
625
+ [class*="ring-foreground/10"] {
514
626
  --tw-ring-color: var(--border);
627
+ }
628
+ [data-slot="chart"] .recharts-tooltip-wrapper > div {
515
629
  border-color: var(--border);
516
630
  }
517
631
 
518
632
  /*
519
- * Square as every box is: progress, a slider's track and thumb, a scrollbar's thumb, and the colour chips core's
520
- * chart tooltip and legend draw at a fixed 2px, which no radius token reaches.
633
+ * Square corners everywhere (DESIGN.md): every element and its ::before and ::after are square, whatever rounding
634
+ * a component asks for (rounded-full, rounded-[4px], hover:rounded-full, after:rounded-full), on a component added
635
+ * later too. Only what is round by nature stays round, by what it is rather than its classes: an avatar, a radio
636
+ * and a radio's dot (a questionnaire's radio choice too), a status dot and a spinner. A chart draws its round marks
637
+ * in SVG, which this does not touch.
638
+ */
639
+ :not(
640
+ [data-slot^="avatar"],
641
+ [data-slot="message-avatar"],
642
+ [role="radio"],
643
+ [role="radio"] *,
644
+ [data-type="radio"] [data-slot$="indicator"],
645
+ [data-type="radio"] [data-slot$="indicator-dot"],
646
+ [data-slot="status-dot"],
647
+ .animate-spin
648
+ ),
649
+ :not(
650
+ [data-slot^="avatar"],
651
+ [data-slot="message-avatar"],
652
+ [role="radio"],
653
+ [role="radio"] *,
654
+ [data-type="radio"] [data-slot$="indicator"],
655
+ [data-type="radio"] [data-slot$="indicator-dot"],
656
+ [data-slot="status-dot"],
657
+ .animate-spin
658
+ )::before,
659
+ :not(
660
+ [data-slot^="avatar"],
661
+ [data-slot="message-avatar"],
662
+ [role="radio"],
663
+ [role="radio"] *,
664
+ [data-type="radio"] [data-slot$="indicator"],
665
+ [data-type="radio"] [data-slot$="indicator-dot"],
666
+ [data-slot="status-dot"],
667
+ .animate-spin
668
+ )::after {
669
+ border-radius: 0;
670
+ }
671
+
672
+ /*
673
+ * Muted text is already the faintest that reads at 4.5:1 (contrast.mjs). Stock shadcn fades it further on some
674
+ * parts (text-muted-foreground/70 on a trace's description), under that bar, so a faded muted class reads as the
675
+ * muted colour itself.
521
676
  */
522
677
  :is(
523
- [data-slot="progress-track"],
524
- [data-slot="slider-track"],
525
- [data-slot="slider-thumb"],
526
- [data-slot="scroll-area-thumb"],
527
- [data-slot="chart"] :is(.recharts-tooltip-wrapper, .recharts-legend-wrapper) [class~="rounded-[2px]"]
678
+ [class~="text-muted-foreground/50"],
679
+ [class~="text-muted-foreground/60"],
680
+ [class~="text-muted-foreground/70"],
681
+ [class~="text-muted-foreground/80"]
528
682
  ) {
529
- border-radius: 0;
683
+ color: var(--muted-foreground);
530
684
  }
531
685
 
532
686
  /*
@@ -603,13 +757,13 @@ svg :is(rect, circle, path)[tabindex]:focus-visible,
603
757
  }
604
758
 
605
759
  /*
606
- * An exception the page marks, a stat tile (SectionCard's tone), a MetricStrip
607
- * figure (its item's tone) or a table row (<TableRow data-tone="negative">, a
608
- * DataTableBody's getRowTone), takes a faint wash of its colour over the card,
609
- * and a tile or figure an outline of it, so the one figure needing attention
610
- * stands out of a run of neutral ones. Unlayered, to outrank a row's hover band.
760
+ * An exception the page marks, a MetricStrip figure (its item's tone) or a
761
+ * table row (<TableRow data-tone="negative">, a DataTableBody's getRowTone),
762
+ * takes a faint wash of its colour over the card, and a figure an outline of
763
+ * it, so the one figure needing attention stands out of a run of neutral
764
+ * ones. Unlayered, to outrank a row's hover band.
611
765
  */
612
- :is([data-slot="card"], [data-slot="metric-strip-item"], [data-slot="table-row"]) {
766
+ :is([data-slot="metric-strip-item"], [data-slot="table-row"]) {
613
767
  &[data-tone="negative"] {
614
768
  --exception: var(--negative);
615
769
  }
@@ -617,24 +771,22 @@ svg :is(rect, circle, path)[tabindex]:focus-visible,
617
771
  --exception: var(--warning);
618
772
  }
619
773
  }
620
- :is([data-slot="card"], [data-slot="metric-strip-item"]):is([data-tone="negative"], [data-tone="warning"]) {
621
- background-color: color-mix(in oklab, var(--exception) var(--wash), var(--card));
622
- }
623
- [data-slot="card"]:is([data-tone="negative"], [data-tone="warning"]) {
624
- --tw-ring-color: color-mix(in oklab, var(--exception) var(--wash-edge), transparent);
625
- }
626
774
  /*
627
- * A figure's outline lies on the four hairlines around it, as a tile's ring does: its neighbours' rules before and
628
- * above it, and its own end and bottom rules, which sit outside the padding box the inset counts from.
775
+ * A MetricStrip figure has no padding of its own, so its text lines up with its neighbours'; its wash and outline
776
+ * reach 8px past it, into the gap, as a picked figure's outline does. --figure-wash is the wash the whole box takes.
629
777
  */
630
- [data-slot="metric-strip-item"]:is([data-tone="negative"], [data-tone="warning"]) {
631
- position: relative;
632
- &::after {
633
- content: "";
634
- position: absolute;
635
- inset: -1px;
636
- border: 1px solid color-mix(in oklab, var(--exception) var(--wash-edge), transparent);
637
- pointer-events: none;
778
+ [data-slot="metric-strip-item"] {
779
+ --figure-reach: calc(var(--spacing) * 2);
780
+ &:is([data-tone="negative"], [data-tone="warning"]) {
781
+ --figure-wash: color-mix(in oklab, var(--exception) var(--wash), var(--card));
782
+ background-color: var(--figure-wash);
783
+ box-shadow: 0 0 0 var(--figure-reach) var(--figure-wash);
784
+ outline: 1px solid color-mix(in oklab, var(--exception) var(--wash-edge), transparent);
785
+ outline-offset: var(--figure-reach);
786
+ }
787
+ &[data-picked] {
788
+ outline: 2px solid var(--primary);
789
+ outline-offset: var(--figure-reach);
638
790
  }
639
791
  }
640
792
  /* A pinned cell paints the card under itself (niko-table's lib/styles), so it lays the row's wash over that. */
@@ -676,11 +828,10 @@ svg :is(rect, circle, path)[tabindex]:focus-visible,
676
828
  }
677
829
 
678
830
  /*
679
- * A data table inside a card drops its own border: the card's ring or the
680
- * ruled page's hairlines already frame it. On an open page, with no frame,
681
- * it keeps its border.
831
+ * A data table inside a boxed card drops its own border: the card's ring
832
+ * already frames it. On an open page, with no frame, it keeps its border.
682
833
  */
683
- @scope (:root, [data-surface="ruled"], [data-surface="card"]) to ([data-surface="open"]) {
834
+ @scope ([data-surface="card"]) to ([data-surface="open"]) {
684
835
  [data-slot="card"] [data-slot="table-container"] {
685
836
  border-color: transparent;
686
837
  }
@@ -734,21 +885,22 @@ svg :is(rect, circle, path)[tabindex]:focus-visible,
734
885
  }
735
886
 
736
887
  /*
737
- * A block the page marks stale (stale on a chart card or a SectionCards tile)
738
- * loses its colour: its chart, sparkline and badge turn grey, and a tile's
739
- * figure and badge turn muted. The chart and sparkline also draw at 65%, so a
888
+ * A block the page marks stale (stale on a chart card or a MetricStrip figure)
889
+ * loses its colour: its chart, sparkline and badge turn grey, and a figure
890
+ * turns muted. The chart and sparkline also draw at 65%, so a
740
891
  * dark slot's line does not read heavier than a fresh one: a stale block is
741
892
  * meant to be harder to read, so its marks are exempt from the 3:1 mark bar.
742
893
  * The small "Stale · 3 Nov" note under the title and the figure keep their
743
894
  * text contrast.
744
895
  */
745
- [data-slot="card"][data-stale] :is([data-slot="chart"], [data-slot="sparkline"], [data-slot="badge"]) {
896
+ :is([data-slot="card"], [data-slot="metric-strip-item"])[data-stale]
897
+ :is([data-slot="chart"], [data-slot="sparkline"], [data-slot="badge"], [data-slot="delta"]) {
746
898
  filter: grayscale(1);
747
899
  }
748
- [data-slot="card"][data-stale] :is([data-slot="chart"], [data-slot="sparkline"]) {
900
+ :is([data-slot="card"], [data-slot="metric-strip-item"])[data-stale] :is([data-slot="chart"], [data-slot="sparkline"]) {
749
901
  opacity: 0.65;
750
902
  }
751
- [data-slot="section-card"] [data-slot="card"][data-stale] :is([data-slot="card-title"], [data-slot="badge"]) {
903
+ [data-slot="metric-strip-item"][data-stale] [data-slot="metric-strip-value"] {
752
904
  color: var(--muted-foreground);
753
905
  }
754
906
 
@@ -808,6 +960,7 @@ div[data-slot="sidebar"]:not([data-side], [data-mobile]) {
808
960
  [data-slot="toggle"],
809
961
  [data-slot="toggle-group-item"],
810
962
  [data-slot="select-trigger"],
963
+ [data-slot="native-select"],
811
964
  [data-slot="tabs-trigger"],
812
965
  [data-slot="input-group-button"]
813
966
  ) {
@@ -847,44 +1000,39 @@ div[data-slot="sidebar"]:not([data-side], [data-mobile]) {
847
1000
  }
848
1001
 
849
1002
  /*
850
- * Surfaces, from SiyahiProvider's surface prop. Ruled, the default, keeps
851
- * every card's fill and drops its ring: PanelGrid and SectionCards fill their
852
- * grid with the card colour and draw hairlines between the blocks, with a rule
853
- * above and below the group and none on its left and right edges, and a card
854
- * that stands alone, such as a MetricStrip or a table's card, takes those
855
- * rules above and below itself. Card is a filled square card with a hairline
856
- * ring and no shadow, 8px from the next, for a page of separate blocks. Open
857
- * takes the box off every core Card: no fill, no ring, no tinted footer band,
858
- * and no padding, so a block's content sits level with the page's text. Each
859
- * scope stops at a provider that says otherwise. Unlayered, like the rules
860
- * above, to outrank the core components' utility classes; a picked
861
- * SectionCards tile keeps its ring, which is important, and an exception
862
- * keeps its outline, which is more specific.
1003
+ * Surfaces, from SiyahiProvider's surface prop. Open, the default, takes the
1004
+ * box off every core Card: no fill, no ring, no tinted footer band, and no
1005
+ * padding, so a block's content sits level with the page's text and space,
1006
+ * not a box or a rule, parts it from the next. Card is a filled square card
1007
+ * with a hairline ring and no shadow, 8px from the next, for a page of
1008
+ * separate blocks. Each scope stops at a provider that says otherwise.
1009
+ * Unlayered, like the rules above, to outrank the core components' utility
1010
+ * classes.
863
1011
  */
864
- [data-slot="card"] {
865
- --tw-ring-color: var(--border);
1012
+ /*
1013
+ * An open card renames --card to the page colour for everything inside it, and a custom property inherits past a
1014
+ * scope's end, so a card provider nested in an open card would fill its boxes with the page colour. --card-fill keeps
1015
+ * the theme's card colour, set once at the root, and each card provider takes it back.
1016
+ */
1017
+ :root {
1018
+ --card-fill: var(--card);
866
1019
  }
867
-
868
- @scope (:root, [data-surface="ruled"]) to ([data-surface="card"], [data-surface="open"]) {
869
- [data-slot="card"] {
870
- --tw-ring-color: transparent;
871
- }
872
- /*
873
- * A card inside a panel or a tile's cell, or nested in another card, is ruled by what holds it; only a holder
874
- * inside this scope counts, so a ruled provider inside a card starts its own lone cards afresh.
875
- */
876
- [data-slot="card"]:not(
877
- :scope :is([data-slot="panel"], [data-slot="section-card"], [data-slot="card"]) [data-slot="card"]
878
- ) {
879
- border-block: 1px solid var(--border);
880
- }
1020
+ [data-surface="card"] {
1021
+ --card: var(--card-fill);
881
1022
  }
882
1023
 
883
- @scope ([data-surface="open"]) to ([data-surface="ruled"], [data-surface="card"]) {
1024
+ @scope (:root, [data-surface="open"]) to ([data-surface="card"]) {
884
1025
  [data-slot="table-container"] {
885
1026
  --table-fill: var(--background);
886
1027
  }
1028
+ /*
1029
+ * With no fill, what lies under a card's marks is the page, so --card names the page colour inside it: a forecast's
1030
+ * halos, a dot's ring, a bar's tint and an exception's wash mix into the canvas, not into a grey no box shows.
1031
+ */
887
1032
  [data-slot="card"] {
1033
+ --card: var(--background);
1034
+ /* No box, so no edge to clip at: a figure's wash or picker reaching past its text into the gap stays whole. */
1035
+ overflow: visible;
888
1036
  background: none;
889
1037
  --tw-ring-color: transparent;
890
1038
  --card-spacing: 0px;
@@ -1,32 +0,0 @@
1
- import { Delta, SectionCards, Sparkline, useFormat, type SectionCard } from "@siyahi/ui";
2
-
3
- export default function SectionCardsTrendExample() {
4
- const { money, moneyCompact } = useFormat();
5
- const items: SectionCard[] = [
6
- {
7
- label: "Revenue",
8
- value: money(4512300),
9
- badge: <Delta value={0.031} />,
10
- trend: <Sparkline data={[3820000, 3950000, 4110000, 4020000, 4380000, 4512300]} />,
11
- detail: "Month-end, last six months",
12
- },
13
- {
14
- label: "Cash flow this year",
15
- value: moneyCompact(-61200),
16
- badge: <Delta value={-61200} format="money" />,
17
- // A run of losses: the meaning colour, not a chart slot.
18
- trend: <Sparkline data={[0, -8200, -14100, -30400, -52800, -61200]} color="var(--negative)" />,
19
- detail: "Running total since April",
20
- // The one figure on the page that needs a look, marked by the page: a falling cost would not be.
21
- tone: "negative",
22
- },
23
- {
24
- label: "Gift cards this year",
25
- value: moneyCompact(38400),
26
- badge: <Delta value={0.18} />,
27
- trend: <Sparkline data={[4100, 0, 12800, 3300, 9900, 8300]} variant="line" curve="linear" />,
28
- detail: "Sold each month",
29
- },
30
- ];
31
- return <SectionCards items={items} />;
32
- }
@@ -1,48 +0,0 @@
1
- import { Delta, SectionCards, type SectionCard, useFormat } from "@siyahi/ui";
2
-
3
- export default function SectionCardsExample() {
4
- const { money, moneyCompact } = useFormat();
5
- const items: SectionCard[] = [
6
- {
7
- label: "Revenue",
8
- value: money(4512300),
9
- badge: <Delta value={0.031} />,
10
- headline: "Up for the third month",
11
- detail: "Paid orders, less refunds",
12
- },
13
- {
14
- label: "Target",
15
- value: money(4025000),
16
- headline: "Set at the start of the quarter",
17
- detail: "Across 4 stores",
18
- },
19
- {
20
- label: "Ahead of target",
21
- value: moneyCompact(487300),
22
- badge: <Delta value={0.121} />,
23
- headline: "Gross margin 14.2%",
24
- detail: "Since April",
25
- },
26
- {
27
- label: "Cash flow this year",
28
- value: moneyCompact(-61200),
29
- badge: <Delta value={-61200} format="money" />,
30
- headline: "Two large inventory buys",
31
- detail: "Before tax",
32
- },
33
- {
34
- label: "Gift cards this year",
35
- value: moneyCompact(38400),
36
- badge: <Delta value={0.18} />,
37
- headline: "Sold in 11 stores",
38
- detail: "Paid at purchase",
39
- },
40
- ];
41
- return (
42
- <div className="flex flex-col gap-6">
43
- <SectionCards items={items.slice(0, 3)} />
44
- <SectionCards items={items.slice(0, 4)} />
45
- <SectionCards items={items} />
46
- </div>
47
- );
48
- }
@@ -1,7 +0,0 @@
1
- [
2
- "Never paint html or body beyond the base styles report.css sets.",
3
- "Fluid width. No outer card and no banner title.",
4
- "A panel with its own background gets 16px padding and var(--radius).",
5
- "Charts get fixed pixel heights.",
6
- "Never use 100vh."
7
- ]
@@ -1,30 +0,0 @@
1
- import * as React$1 from "react";
2
- //#region src/components/registry/dashboard-01/section-cards.d.ts
3
- export interface SectionCard {
4
- /** Muted label above the value. */
5
- label: string;
6
- value: React$1.ReactNode;
7
- /** Outline badge, top right; usually a <Delta />. */
8
- badge?: React$1.ReactNode;
9
- /** A small chart under the value, usually a <Sparkline /> of the recent run. */
10
- trend?: React$1.ReactNode;
11
- /** Bold first footer line. */
12
- headline?: React$1.ReactNode;
13
- /** Muted second footer line. */
14
- detail?: React$1.ReactNode;
15
- /** In a picker, a tile that cannot be picked, such as one with nothing in it: faded, and its button disabled. */
16
- disabled?: boolean | undefined;
17
- /** Marks the tile as the exception on the page: a faint wash of the colour and an outline of it. */
18
- tone?: "negative" | "warning" | undefined;
19
- /** Marks the tile's data as old, with since when, such as "3 Nov": the tile greys out and says so. */
20
- stale?: React$1.ReactNode;
21
- }
22
- export interface SectionCardsProps {
23
- items: SectionCard[];
24
- /** The label of the picked tile. */
25
- selected?: string | undefined;
26
- /** Makes each tile one button, so keep tile content non-interactive; gets the label of the tile picked. */
27
- onSelect?: ((label: string) => void) | undefined;
28
- }
29
- export declare function SectionCards({ items, selected, onSelect }: SectionCardsProps): React$1.JSX.Element;
30
- //#endregion