cursedbelt 5.32.0 → 5.34.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 (195) hide show
  1. package/dist/react/Toast.d.ts +1 -1
  2. package/dist/react/Toast.d.ts.map +1 -1
  3. package/dist/react/Toast.js +19 -4
  4. package/dist/react/Toast.js.map +1 -1
  5. package/dist/react/components/Button.d.ts +16 -1
  6. package/dist/react/components/Button.d.ts.map +1 -1
  7. package/dist/react/components/Button.js +34 -5
  8. package/dist/react/components/Button.js.map +1 -1
  9. package/dist/react/components/ChoiceGroup.d.ts +20 -1
  10. package/dist/react/components/ChoiceGroup.d.ts.map +1 -1
  11. package/dist/react/components/ChoiceGroup.js +15 -2
  12. package/dist/react/components/ChoiceGroup.js.map +1 -1
  13. package/dist/react/components/CopyButton.d.ts +10 -1
  14. package/dist/react/components/CopyButton.d.ts.map +1 -1
  15. package/dist/react/components/CopyButton.js +2 -2
  16. package/dist/react/components/CopyButton.js.map +1 -1
  17. package/dist/react/components/DataTable/DataTable.d.ts +27 -2
  18. package/dist/react/components/DataTable/DataTable.d.ts.map +1 -1
  19. package/dist/react/components/DataTable/DataTable.js +168 -44
  20. package/dist/react/components/DataTable/DataTable.js.map +1 -1
  21. package/dist/react/components/DataTable/RowActions.d.ts +4 -1
  22. package/dist/react/components/DataTable/RowActions.d.ts.map +1 -1
  23. package/dist/react/components/DataTable/RowActions.js +2 -2
  24. package/dist/react/components/DataTable/RowActions.js.map +1 -1
  25. package/dist/react/components/DataTable/columnPriority.d.ts +30 -0
  26. package/dist/react/components/DataTable/columnPriority.d.ts.map +1 -0
  27. package/dist/react/components/DataTable/columnPriority.js +45 -0
  28. package/dist/react/components/DataTable/columnPriority.js.map +1 -0
  29. package/dist/react/components/DataTable/types.d.ts +14 -0
  30. package/dist/react/components/DataTable/types.d.ts.map +1 -1
  31. package/dist/react/components/DataTable/types.js.map +1 -1
  32. package/dist/react/components/EmptyState.d.ts.map +1 -1
  33. package/dist/react/components/EmptyState.js +2 -2
  34. package/dist/react/components/EmptyState.js.map +1 -1
  35. package/dist/react/components/ErrorCard.d.ts +13 -1
  36. package/dist/react/components/ErrorCard.d.ts.map +1 -1
  37. package/dist/react/components/ErrorCard.js +3 -2
  38. package/dist/react/components/ErrorCard.js.map +1 -1
  39. package/dist/react/components/Field.d.ts +37 -12
  40. package/dist/react/components/Field.d.ts.map +1 -1
  41. package/dist/react/components/Field.js +19 -8
  42. package/dist/react/components/Field.js.map +1 -1
  43. package/dist/react/components/Menu.d.ts +58 -0
  44. package/dist/react/components/Menu.d.ts.map +1 -0
  45. package/dist/react/components/Menu.js +86 -0
  46. package/dist/react/components/Menu.js.map +1 -0
  47. package/dist/react/components/Modal.d.ts +1 -0
  48. package/dist/react/components/Modal.d.ts.map +1 -1
  49. package/dist/react/components/Modal.js +3 -2
  50. package/dist/react/components/Modal.js.map +1 -1
  51. package/dist/react/components/SaveIndicator.d.ts +36 -0
  52. package/dist/react/components/SaveIndicator.d.ts.map +1 -0
  53. package/dist/react/components/SaveIndicator.js +48 -0
  54. package/dist/react/components/SaveIndicator.js.map +1 -0
  55. package/dist/react/components/SelectionBar.d.ts +26 -0
  56. package/dist/react/components/SelectionBar.d.ts.map +1 -0
  57. package/dist/react/components/SelectionBar.js +21 -0
  58. package/dist/react/components/SelectionBar.js.map +1 -0
  59. package/dist/react/components/Switch.d.ts +16 -2
  60. package/dist/react/components/Switch.d.ts.map +1 -1
  61. package/dist/react/components/Switch.js +10 -3
  62. package/dist/react/components/Switch.js.map +1 -1
  63. package/dist/react/components/Tooltip.d.ts +1 -1
  64. package/dist/react/components/Tooltip.d.ts.map +1 -1
  65. package/dist/react/components/Tooltip.js +14 -1
  66. package/dist/react/components/Tooltip.js.map +1 -1
  67. package/dist/react/components/TooltipImpl.d.ts.map +1 -1
  68. package/dist/react/components/TooltipImpl.js +37 -7
  69. package/dist/react/components/TooltipImpl.js.map +1 -1
  70. package/dist/react/components/UnstyledButton.d.ts +37 -0
  71. package/dist/react/components/UnstyledButton.d.ts.map +1 -0
  72. package/dist/react/components/UnstyledButton.js +46 -0
  73. package/dist/react/components/UnstyledButton.js.map +1 -0
  74. package/dist/react/components/auth/SignInCard.d.ts +15 -1
  75. package/dist/react/components/auth/SignInCard.d.ts.map +1 -1
  76. package/dist/react/components/auth/SignInCard.js +6 -2
  77. package/dist/react/components/auth/SignInCard.js.map +1 -1
  78. package/dist/react/components/dialog.d.ts.map +1 -1
  79. package/dist/react/components/dialog.js +35 -3
  80. package/dist/react/components/dialog.js.map +1 -1
  81. package/dist/react/hooks/useCopyToClipboard.d.ts +10 -2
  82. package/dist/react/hooks/useCopyToClipboard.d.ts.map +1 -1
  83. package/dist/react/hooks/useCopyToClipboard.js +13 -4
  84. package/dist/react/hooks/useCopyToClipboard.js.map +1 -1
  85. package/dist/react/icons.d.ts +12 -1
  86. package/dist/react/icons.d.ts.map +1 -1
  87. package/dist/react/icons.js +12 -22
  88. package/dist/react/icons.js.map +1 -1
  89. package/dist/react/index.d.ts +8 -4
  90. package/dist/react/index.d.ts.map +1 -1
  91. package/dist/react/index.js +7 -3
  92. package/dist/react/index.js.map +1 -1
  93. package/dist/react/lib/hoverIntent.d.ts +21 -0
  94. package/dist/react/lib/hoverIntent.d.ts.map +1 -1
  95. package/dist/react/lib/hoverIntent.js +24 -0
  96. package/dist/react/lib/hoverIntent.js.map +1 -1
  97. package/dist/react/lib/openDialogs.d.ts +5 -0
  98. package/dist/react/lib/openDialogs.d.ts.map +1 -0
  99. package/dist/react/lib/openDialogs.js +45 -0
  100. package/dist/react/lib/openDialogs.js.map +1 -0
  101. package/dist/styles-areas/analytics.css +16 -16
  102. package/dist/styles-areas/calendar.css +7 -7
  103. package/dist/styles-areas/chat.css +9 -10
  104. package/dist/styles-areas/core.css +69 -62
  105. package/dist/styles-areas/data-table.css +16 -16
  106. package/dist/styles-areas/disk-usage.css +7 -7
  107. package/dist/styles-areas/emoji.css +3 -3
  108. package/dist/styles-areas/fields.css +10 -10
  109. package/dist/styles-areas/filter-rail.css +7 -8
  110. package/dist/styles-areas/layout-engine.css +5 -6
  111. package/dist/styles-areas/master-detail.css +6 -7
  112. package/dist/styles-areas/media-gallery.css +2 -2
  113. package/dist/styles-areas/media.css +2 -2
  114. package/dist/styles-areas/nav.css +17 -18
  115. package/dist/styles-areas/notifications.css +5 -5
  116. package/dist/styles-areas/overlays.css +5 -5
  117. package/dist/styles-areas/sharing.css +2 -2
  118. package/dist/styles-areas/stats.css +4 -4
  119. package/dist/styles-areas/wizard.css +3 -3
  120. package/dist/styles-areas/workbench.css +18 -19
  121. package/dist/styles-utilities.css +72 -0
  122. package/dist/testing/tooltip.d.ts.map +1 -1
  123. package/dist/testing/tooltip.js +8 -18
  124. package/dist/testing/tooltip.js.map +1 -1
  125. package/package.json +81 -3
  126. package/scripts/guardrails-baseline.json +1 -1
  127. package/scripts/styleAreas.ts +4 -0
  128. package/src/react/Toast.spec.tsx +17 -1
  129. package/src/react/Toast.tsx +20 -3
  130. package/src/react/components/Button.spec.tsx +49 -1
  131. package/src/react/components/Button.tsx +63 -6
  132. package/src/react/components/ChoiceGroup.spec.tsx +32 -0
  133. package/src/react/components/ChoiceGroup.tsx +78 -23
  134. package/src/react/components/CopyButton.spec.tsx +35 -0
  135. package/src/react/components/CopyButton.tsx +11 -2
  136. package/src/react/components/DataTable/DataTable.spec.tsx +16 -0
  137. package/src/react/components/DataTable/DataTable.tsx +266 -37
  138. package/src/react/components/DataTable/RowActions.tsx +5 -2
  139. package/src/react/components/DataTable/columnPriority.spec.ts +97 -0
  140. package/src/react/components/DataTable/columnPriority.ts +54 -0
  141. package/src/react/components/DataTable/types.ts +14 -0
  142. package/src/react/components/DataTable/virtualize.spec.tsx +47 -2
  143. package/src/react/components/EmptyState.spec.tsx +7 -0
  144. package/src/react/components/EmptyState.tsx +3 -0
  145. package/src/react/components/ErrorCard.spec.tsx +32 -0
  146. package/src/react/components/ErrorCard.tsx +17 -1
  147. package/src/react/components/Field.spec.tsx +61 -0
  148. package/src/react/components/Field.tsx +84 -27
  149. package/src/react/components/Menu.spec.tsx +90 -0
  150. package/src/react/components/Menu.tsx +204 -0
  151. package/src/react/components/Modal.spec.tsx +84 -0
  152. package/src/react/components/Modal.tsx +4 -1
  153. package/src/react/components/SaveIndicator.spec.tsx +35 -0
  154. package/src/react/components/SaveIndicator.tsx +85 -0
  155. package/src/react/components/SelectionBar.spec.tsx +32 -0
  156. package/src/react/components/SelectionBar.tsx +49 -0
  157. package/src/react/components/Switch.spec.tsx +25 -0
  158. package/src/react/components/Switch.tsx +49 -4
  159. package/src/react/components/Tooltip.spec.tsx +183 -2
  160. package/src/react/components/Tooltip.tsx +14 -2
  161. package/src/react/components/TooltipImpl.tsx +42 -8
  162. package/src/react/components/UnstyledButton.spec.tsx +61 -0
  163. package/src/react/components/UnstyledButton.tsx +83 -0
  164. package/src/react/components/auth/SignInCard.spec.tsx +34 -0
  165. package/src/react/components/auth/SignInCard.tsx +36 -3
  166. package/src/react/components/dialog.tsx +49 -9
  167. package/src/react/hooks/useCopyToClipboard.ts +17 -4
  168. package/src/react/icons.spec.ts +32 -1
  169. package/src/react/icons.tsx +59 -0
  170. package/src/react/index.ts +23 -2
  171. package/src/react/lib/hoverIntent.ts +28 -0
  172. package/src/react/lib/openDialogs.ts +48 -0
  173. package/src/styles-areas/analytics.css +16 -16
  174. package/src/styles-areas/calendar.css +7 -7
  175. package/src/styles-areas/chat.css +9 -10
  176. package/src/styles-areas/core.css +69 -62
  177. package/src/styles-areas/data-table.css +16 -16
  178. package/src/styles-areas/disk-usage.css +7 -7
  179. package/src/styles-areas/emoji.css +3 -3
  180. package/src/styles-areas/fields.css +10 -10
  181. package/src/styles-areas/filter-rail.css +7 -8
  182. package/src/styles-areas/layout-engine.css +5 -6
  183. package/src/styles-areas/master-detail.css +6 -7
  184. package/src/styles-areas/media-gallery.css +2 -2
  185. package/src/styles-areas/media.css +2 -2
  186. package/src/styles-areas/nav.css +17 -18
  187. package/src/styles-areas/notifications.css +5 -5
  188. package/src/styles-areas/overlays.css +5 -5
  189. package/src/styles-areas/sharing.css +2 -2
  190. package/src/styles-areas/stats.css +4 -4
  191. package/src/styles-areas/wizard.css +3 -3
  192. package/src/styles-areas/workbench.css +18 -19
  193. package/src/styles-utilities.css +72 -0
  194. package/src/testing/tooltip.spec.ts +19 -0
  195. package/src/testing/tooltip.ts +17 -1
@@ -18,14 +18,14 @@
18
18
  * after an app's responsive variant (task 2124). An app that imports every area gets exactly
19
19
  * what `cursedbelt/styles-utilities.css` emits — proved rule by rule in
20
20
  * src/stylesAreas.spec.ts, which is also what fails when this file is stale.
21
- * 63 candidate(s).
21
+ * 62 candidate(s).
22
22
  *
23
23
  * Written by scripts/generateAreaStyles.ts (`bun run styles:areas`), which
24
24
  * `bun run build` runs. The map of what is in which area, and why the areas are
25
25
  * import closures rather than a hand-written list, is scripts/styleAreas.ts.
26
26
  */
27
- @source inline("[&_svg]:size-3 border-field-error border-l border-r collapse cursor-not-allowed");
28
- @source inline("data-[ending-style]:opacity-0 data-[ending-style]:scale-95 data-[highlighted]:bg-accent/10");
27
+ @source inline("[&_svg]:size-3 border-field-error border-l border-r collapse data-[ending-style]:opacity-0");
28
+ @source inline("data-[ending-style]:scale-95 data-[highlighted]:bg-accent/10");
29
29
  @source inline("data-[highlighted]:text-accent-strong data-[starting-style]:opacity-0");
30
30
  @source inline("data-[starting-style]:scale-95 data-[state=closed]:slide-out-to-bottom");
31
31
  @source inline("data-[state=closed]:slide-out-to-left data-[state=closed]:slide-out-to-right");
@@ -18,7 +18,7 @@
18
18
  * after an app's responsive variant (task 2124). An app that imports every area gets exactly
19
19
  * what `cursedbelt/styles-utilities.css` emits — proved rule by rule in
20
20
  * src/stylesAreas.spec.ts, which is also what fails when this file is stale.
21
- * 106 candidate(s).
21
+ * 103 candidate(s).
22
22
  *
23
23
  * Written by scripts/generateAreaStyles.ts (`bun run styles:areas`), which
24
24
  * `bun run build` runs. The map of what is in which area, and why the areas are
@@ -26,21 +26,20 @@
26
26
  */
27
27
  @source inline("-my-0.5 [&::-webkit-scrollbar]:hidden [&_svg]:size-3.5 [scrollbar-width:none] bg-accent/15");
28
28
  @source inline("bg-accent/40 bg-accent/8 bg-border-strong bg-card-overlay/85 bg-muted/40 bg-muted/60");
29
- @source inline("bg-success/8 border-0 border-2 border-8 border-accent border-accent-solid border-accent/70");
30
- @source inline("border-border-strong/60 border-dashed border-l border-r border-solid bottom-6 cursor-not-allowed");
31
- @source inline("data-[state=closed]:slide-out-to-bottom data-[state=closed]:slide-out-to-left");
32
- @source inline("data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-bottom");
33
- @source inline("data-[state=open]:slide-in-from-left data-[state=open]:slide-in-from-right duration-150 flex-row");
34
- @source inline("h-3.5 h-40 h-[420px] hover:bg-card-overlay hover:bg-muted-foreground/10 hover:bg-muted/80");
35
- @source inline("hover:border-border-strong hover:border-field-ring hover:shadow-popover italic left-0");
36
- @source inline("max-h-[40dvh] max-h-[65dvh] max-h-[85dvh] max-h-full min-h min-h-20 min-h-[2.25rem] min-h-[44px]");
37
- @source inline("min-h-dvh min-h-full min-w-40 min-w-[5rem] opacity-100 pb-0.5 pb-4");
38
- @source inline("pb-[max(env(safe-area-inset-bottom,0px),0.5rem)] peer-focus peer-focus:outline-none");
39
- @source inline("peer-focus:ring-2 peer-focus:ring-field-ring/40 peer-focus:rounded-lg pl-1 pl-3");
40
- @source inline("pointer-coarse:h-12 pointer-coarse:w-12 resize-none right-4 ring-1 ring-4 ring-accent/20");
41
- @source inline("ring-accent/30 ring-accent/40 ring-border ring-inset ring-ring rotate-90 rounded-[2.5rem]");
42
- @source inline("rounded-l-xl rounded-r-xl rounded-t-xl scale-[1.01] self-center self-stretch shrink-[2] sm:w-56");
43
- @source inline("text-muted-foreground/60 text-tone-amber-ink text-tone-cyan-ink text-tone-emerald-ink");
44
- @source inline("text-tone-orange-ink text-tone-purple-ink text-tone-rose-ink text-tone-sky-ink");
45
- @source inline("text-tone-slate-ink text-xl transition-[opacity,transform] translate-y-0 translate-y-2 w-1/2");
46
- @source inline("w-2/3 w-3.5 w-[352px]");
29
+ @source inline("bg-success/8 border-2 border-8 border-accent-solid border-accent/70 border-border-strong/60");
30
+ @source inline("border-dashed border-l border-r border-solid bottom-6 data-[state=closed]:slide-out-to-bottom");
31
+ @source inline("data-[state=closed]:slide-out-to-left data-[state=closed]:slide-out-to-right");
32
+ @source inline("data-[state=open]:slide-in-from-bottom data-[state=open]:slide-in-from-left");
33
+ @source inline("data-[state=open]:slide-in-from-right duration-150 flex-row h-3.5 h-40 h-[420px]");
34
+ @source inline("hover:bg-card-overlay hover:bg-muted-foreground/10 hover:bg-muted/80 hover:border-border-strong");
35
+ @source inline("hover:border-field-ring hover:shadow-popover italic left-0 max-h-[40dvh] max-h-[65dvh]");
36
+ @source inline("max-h-[85dvh] max-h-full min-h min-h-20 min-h-[2.25rem] min-h-[44px] min-h-dvh min-h-full");
37
+ @source inline("min-w-40 min-w-[5rem] opacity-100 pb-0.5 pb-4 pb-[max(env(safe-area-inset-bottom,0px),0.5rem)]");
38
+ @source inline("peer-focus peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-field-ring/40");
39
+ @source inline("peer-focus:rounded-lg pl-1 pl-3 pointer-coarse:h-12 pointer-coarse:w-12 resize-none right-4");
40
+ @source inline("ring-1 ring-4 ring-accent/20 ring-accent/30 ring-accent/40 ring-border ring-inset ring-ring");
41
+ @source inline("rotate-90 rounded-[2.5rem] rounded-l-xl rounded-r-xl rounded-t-xl scale-[1.01] self-center");
42
+ @source inline("self-stretch shrink-[2] sm:w-56 text-muted-foreground/60 text-tone-amber-ink text-tone-cyan-ink");
43
+ @source inline("text-tone-emerald-ink text-tone-orange-ink text-tone-purple-ink text-tone-rose-ink");
44
+ @source inline("text-tone-sky-ink text-tone-slate-ink text-xl transition-[opacity,transform] translate-y-0");
45
+ @source inline("translate-y-2 w-1/2 w-2/3 w-3.5 w-[352px]");
@@ -797,6 +797,9 @@
797
797
  .min-h-full {
798
798
  min-height: 100%;
799
799
  }
800
+ .w-0\! {
801
+ width: 0px !important;
802
+ }
800
803
  .w-0\.5 {
801
804
  width: calc(var(--spacing) * 0.5);
802
805
  }
@@ -1004,6 +1007,9 @@
1004
1007
  .min-w-0 {
1005
1008
  min-width: 0px;
1006
1009
  }
1010
+ .min-w-0\! {
1011
+ min-width: 0px !important;
1012
+ }
1007
1013
  .min-w-3\.5 {
1008
1014
  min-width: calc(var(--spacing) * 3.5);
1009
1015
  }
@@ -1634,6 +1640,9 @@
1634
1640
  .border-border {
1635
1641
  border-color: var(--border);
1636
1642
  }
1643
+ .border-border\! {
1644
+ border-color: var(--border) !important;
1645
+ }
1637
1646
  .border-border-strong {
1638
1647
  border-color: var(--border-strong);
1639
1648
  }
@@ -2033,6 +2042,12 @@
2033
2042
  background-color: color-mix(in oklab, var(--muted-foreground) 50%, transparent);
2034
2043
  }
2035
2044
  }
2045
+ .bg-muted-foreground\/60 {
2046
+ background-color: var(--muted-foreground);
2047
+ @supports (color: color-mix(in lab, red, red)) {
2048
+ background-color: color-mix(in oklab, var(--muted-foreground) 60%, transparent);
2049
+ }
2050
+ }
2036
2051
  .bg-muted\/20 {
2037
2052
  background-color: var(--muted);
2038
2053
  @supports (color: color-mix(in lab, red, red)) {
@@ -2066,6 +2081,9 @@
2066
2081
  .bg-popover {
2067
2082
  background-color: var(--popover);
2068
2083
  }
2084
+ .bg-popover\! {
2085
+ background-color: var(--popover) !important;
2086
+ }
2069
2087
  .bg-purple-100 {
2070
2088
  background-color: var(--color-purple-100);
2071
2089
  }
@@ -2282,6 +2300,9 @@
2282
2300
  .p-0 {
2283
2301
  padding: 0px;
2284
2302
  }
2303
+ .p-0\! {
2304
+ padding: 0px !important;
2305
+ }
2285
2306
  .p-0\.5 {
2286
2307
  padding: calc(var(--spacing) * 0.5);
2287
2308
  }
@@ -2471,6 +2492,9 @@
2471
2492
  .pl-3 {
2472
2493
  padding-left: calc(var(--spacing) * 3);
2473
2494
  }
2495
+ .pl-4 {
2496
+ padding-left: calc(var(--spacing) * 4);
2497
+ }
2474
2498
  .pl-5 {
2475
2499
  padding-left: calc(var(--spacing) * 5);
2476
2500
  }
@@ -2630,6 +2654,9 @@
2630
2654
  .break-all {
2631
2655
  word-break: break-all;
2632
2656
  }
2657
+ .whitespace-normal {
2658
+ white-space: normal;
2659
+ }
2633
2660
  .whitespace-nowrap {
2634
2661
  white-space: nowrap;
2635
2662
  }
@@ -2708,6 +2735,9 @@
2708
2735
  .text-muted-foreground {
2709
2736
  color: var(--muted-foreground);
2710
2737
  }
2738
+ .text-muted-foreground\! {
2739
+ color: var(--muted-foreground) !important;
2740
+ }
2711
2741
  .text-muted-foreground\/50 {
2712
2742
  color: var(--muted-foreground);
2713
2743
  @supports (color: color-mix(in lab, red, red)) {
@@ -3547,6 +3577,9 @@
3547
3577
  .hover\:text-foreground:hover {
3548
3578
  color: var(--foreground);
3549
3579
  }
3580
+ .hover\:text-foreground\!:hover {
3581
+ color: var(--foreground) !important;
3582
+ }
3550
3583
  .hover\:text-warning:hover {
3551
3584
  color: var(--warning);
3552
3585
  }
@@ -3630,6 +3663,12 @@
3630
3663
  .focus-visible\:bg-destructive-solid:focus-visible {
3631
3664
  background-color: var(--destructive-solid);
3632
3665
  }
3666
+ .focus-visible\:bg-destructive\/10:focus-visible {
3667
+ background-color: var(--destructive);
3668
+ @supports (color: color-mix(in lab, red, red)) {
3669
+ background-color: color-mix(in oklab, var(--destructive) 10%, transparent);
3670
+ }
3671
+ }
3633
3672
  .focus-visible\:bg-muted:focus-visible {
3634
3673
  background-color: var(--muted);
3635
3674
  }
@@ -3752,6 +3791,9 @@
3752
3791
  .disabled\:hover\:border-border:disabled:hover {
3753
3792
  border-color: var(--border);
3754
3793
  }
3794
+ .disabled\:hover\:bg-transparent:disabled:hover {
3795
+ background-color: transparent;
3796
+ }
3755
3797
  .disabled\:hover\:text-muted-foreground:disabled:hover {
3756
3798
  color: var(--muted-foreground);
3757
3799
  }
@@ -3762,9 +3804,36 @@
3762
3804
  .aria-disabled\:pointer-events-none[aria-disabled="true"] {
3763
3805
  pointer-events: none;
3764
3806
  }
3807
+ .aria-disabled\:cursor-not-allowed[aria-disabled="true"] {
3808
+ cursor: not-allowed;
3809
+ }
3765
3810
  .aria-disabled\:opacity-30[aria-disabled="true"] {
3766
3811
  opacity: 30%;
3767
3812
  }
3813
+ .aria-disabled\:opacity-50[aria-disabled="true"] {
3814
+ opacity: 50%;
3815
+ }
3816
+ @media (hover: hover) {
3817
+ .aria-disabled\:hover\:bg-transparent[aria-disabled="true"]:hover {
3818
+ background-color: transparent;
3819
+ }
3820
+ }
3821
+ .aria-pressed\:border[aria-pressed="true"] {
3822
+ border-style: var(--tw-border-style);
3823
+ border-width: 1px;
3824
+ }
3825
+ .aria-pressed\:border-accent[aria-pressed="true"] {
3826
+ border-color: var(--accent);
3827
+ }
3828
+ .aria-pressed\:bg-accent\/10[aria-pressed="true"] {
3829
+ background-color: var(--accent);
3830
+ @supports (color: color-mix(in lab, red, red)) {
3831
+ background-color: color-mix(in oklab, var(--accent) 10%, transparent);
3832
+ }
3833
+ }
3834
+ .aria-pressed\:text-accent[aria-pressed="true"] {
3835
+ color: var(--accent);
3836
+ }
3768
3837
  .aria-\[invalid\=true\]\:border-field-error[aria-invalid="true"] {
3769
3838
  border-color: var(--field-error);
3770
3839
  }
@@ -3915,6 +3984,9 @@
3915
3984
  --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
3916
3985
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
3917
3986
  }
3987
+ .data-\[state\=checked\]\:ring-accent[data-state="checked"] {
3988
+ --tw-ring-color: var(--accent);
3989
+ }
3918
3990
  .data-\[state\=checked\]\:ring-foreground[data-state="checked"] {
3919
3991
  --tw-ring-color: var(--foreground);
3920
3992
  }
@@ -1 +1 @@
1
- {"version":3,"file":"tooltip.d.ts","sourceRoot":"","sources":["../../src/testing/tooltip.ts"],"names":[],"mappings":"AA+BA,wEAAwE;AACxE,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,WAAW,GAAG,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CAQvF;AAED;;;;;;GAMG;AACH,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,WAAW,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAY/E"}
1
+ {"version":3,"file":"tooltip.d.ts","sourceRoot":"","sources":["../../src/testing/tooltip.ts"],"names":[],"mappings":"AA6CA,wEAAwE;AACxE,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,WAAW,GAAG,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CASvF;AAED;;;;;;GAMG;AACH,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,WAAW,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAa/E"}
@@ -1,21 +1,9 @@
1
- /**
2
- * Reading a cursedbelt `Tooltip` from a test.
3
- *
4
- * Two things about `Tooltip` make a naive assertion wrong, and both are load-bearing
5
- * rather than incidental (see `react/components/Tooltip.tsx`):
6
- *
7
- * 1. The Radix implementation is a dynamic `import()`, so nothing is wired at first
8
- * paint. A test must WAIT for the upgrade.
9
- * 2. The upgrade re-parents the trigger under Radix's `Trigger`, which React
10
- * reconciles as a remount — so a node captured before the upgrade is detached.
11
- * Every helper here re-queries rather than holding a reference.
12
- *
13
- * The reason a spec cares at all: `title=""` used to be the fleet's hover hint, and
14
- * `DESIGN.md` §4 forbids it (the OS draws it after a second, in its own box, and a
15
- * phone never draws it). A spec that asserted `getAttribute('title')` was asserting
16
- * the defect; these helpers are what it should assert instead.
17
- */
18
- import { fireEvent, waitFor } from "@testing-library/react";
1
+ let cached;
2
+ async function testingLibraryReact() {
3
+ if (!cached)
4
+ cached = await import("@testing-library/react");
5
+ return cached;
6
+ }
19
7
  /**
20
8
  * Radix renders the bubble's content TWICE — once visibly and once inside a
21
9
  * `VisuallyHidden` node that becomes the trigger's accessible description — so the
@@ -30,6 +18,7 @@ function deduplicateAriaCopy(text) {
30
18
  }
31
19
  /** Wait until the lazy implementation has replaced the bare trigger. */
32
20
  export async function tooltipReady(find) {
21
+ const { waitFor } = await testingLibraryReact();
33
22
  await waitFor(() => {
34
23
  const el = find();
35
24
  if (!el?.getAttribute("data-state"))
@@ -48,6 +37,7 @@ export async function tooltipReady(find) {
48
37
  * hover is dispatched.
49
38
  */
50
39
  export async function hoverHint(find) {
40
+ const { fireEvent, waitFor } = await testingLibraryReact();
51
41
  const el = await tooltipReady(find);
52
42
  // Radix opens on `pointermove` over the trigger (not `pointerenter`, which React
53
43
  // synthesizes and therefore never sees from a direct dispatch).
@@ -1 +1 @@
1
- {"version":3,"file":"tooltip.js","sourceRoot":"","sources":["../../src/testing/tooltip.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAE5D;;;;;GAKG;AACH,SAAS,mBAAmB,CAAC,IAAY;IACxC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;IAC7B,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC9E,CAAC;AAED,wEAAwE;AACxE,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAA8B;IAChE,MAAM,OAAO,CAAC,GAAG,EAAE;QAClB,MAAM,EAAE,GAAG,IAAI,EAAE,CAAC;QAClB,IAAI,CAAC,EAAE,EAAE,YAAY,CAAC,YAAY,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;IAC/E,CAAC,CAAC,CAAC;IACH,MAAM,EAAE,GAAG,IAAI,EAAE,CAAC;IAClB,IAAI,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACxD,OAAO,EAAE,CAAC;AACX,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAA8B;IAC7D,MAAM,EAAE,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,CAAC;IACpC,iFAAiF;IACjF,gEAAgE;IAChE,SAAS,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC;IACpD,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,MAAM,OAAO,CAAC,GAAG,EAAE;QAClB,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,mBAAmB,CAAC,CAAC;QAC3D,IAAI,CAAC,MAAM,EAAE,WAAW;YAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;QACnE,IAAI,GAAG,mBAAmB,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IACH,OAAO,IAAI,CAAC;AACb,CAAC"}
1
+ {"version":3,"file":"tooltip.js","sourceRoot":"","sources":["../../src/testing/tooltip.ts"],"names":[],"mappings":"AA2BA,IAAI,MAAuC,CAAC;AAC5C,KAAK,UAAU,mBAAmB;IACjC,IAAI,CAAC,MAAM;QAAE,MAAM,GAAG,MAAM,MAAM,CAAC,wBAAwB,CAAC,CAAC;IAC7D,OAAO,MAAM,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,SAAS,mBAAmB,CAAC,IAAY;IACxC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;IAC7B,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC9E,CAAC;AAED,wEAAwE;AACxE,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAA8B;IAChE,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,mBAAmB,EAAE,CAAC;IAChD,MAAM,OAAO,CAAC,GAAG,EAAE;QAClB,MAAM,EAAE,GAAG,IAAI,EAAE,CAAC;QAClB,IAAI,CAAC,EAAE,EAAE,YAAY,CAAC,YAAY,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;IAC/E,CAAC,CAAC,CAAC;IACH,MAAM,EAAE,GAAG,IAAI,EAAE,CAAC;IAClB,IAAI,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACxD,OAAO,EAAE,CAAC;AACX,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAA8B;IAC7D,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,MAAM,mBAAmB,EAAE,CAAC;IAC3D,MAAM,EAAE,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,CAAC;IACpC,iFAAiF;IACjF,gEAAgE;IAChE,SAAS,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC;IACpD,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,MAAM,OAAO,CAAC,GAAG,EAAE;QAClB,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,mBAAmB,CAAC,CAAC;QAC3D,IAAI,CAAC,MAAM,EAAE,WAAW;YAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;QACnE,IAAI,GAAG,mBAAmB,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IACH,OAAO,IAAI,CAAC;AACb,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursedbelt",
3
- "version": "5.32.0",
3
+ "version": "5.34.0",
4
4
  "license": "ISC",
5
5
  "type": "module",
6
6
  "description": "The React design system of the cursedbelt split — components, styles, theme. cursedbelt-core below it; server tier in cursedbelt-server.",
@@ -128,6 +128,12 @@
128
128
  "source": "./src/react/analytics/index.ts",
129
129
  "import": "./dist/react/analytics/index.js"
130
130
  },
131
+ "./react/badge": {
132
+ "types": "./dist/react/components/Badge.d.ts",
133
+ "bun": "./src/react/components/Badge.tsx",
134
+ "source": "./src/react/components/Badge.tsx",
135
+ "import": "./dist/react/components/Badge.js"
136
+ },
131
137
  "./react/button": {
132
138
  "types": "./dist/react/components/Button.d.ts",
133
139
  "bun": "./src/react/components/Button.tsx",
@@ -158,6 +164,12 @@
158
164
  "source": "./src/react/clipboard/index.ts",
159
165
  "import": "./dist/react/clipboard/index.js"
160
166
  },
167
+ "./react/code-chip": {
168
+ "types": "./dist/react/components/CodeChip.d.ts",
169
+ "bun": "./src/react/components/CodeChip.tsx",
170
+ "source": "./src/react/components/CodeChip.tsx",
171
+ "import": "./dist/react/components/CodeChip.js"
172
+ },
161
173
  "./react/code-editor": {
162
174
  "types": "./dist/react/code-editor/index.d.ts",
163
175
  "bun": "./src/react/code-editor/index.ts",
@@ -206,12 +218,30 @@
206
218
  "source": "./src/react/components/DropdownMenu.tsx",
207
219
  "import": "./dist/react/components/DropdownMenu.js"
208
220
  },
221
+ "./react/empty-state": {
222
+ "types": "./dist/react/components/EmptyState.d.ts",
223
+ "bun": "./src/react/components/EmptyState.tsx",
224
+ "source": "./src/react/components/EmptyState.tsx",
225
+ "import": "./dist/react/components/EmptyState.js"
226
+ },
209
227
  "./react/engagement-beacon": {
210
228
  "types": "./dist/react/engagement/index.d.ts",
211
229
  "bun": "./src/react/engagement/index.ts",
212
230
  "source": "./src/react/engagement/index.ts",
213
231
  "import": "./dist/react/engagement/index.js"
214
232
  },
233
+ "./react/error-card": {
234
+ "types": "./dist/react/components/ErrorCard.d.ts",
235
+ "bun": "./src/react/components/ErrorCard.tsx",
236
+ "source": "./src/react/components/ErrorCard.tsx",
237
+ "import": "./dist/react/components/ErrorCard.js"
238
+ },
239
+ "./react/field": {
240
+ "types": "./dist/react/components/Field.d.ts",
241
+ "bun": "./src/react/components/Field.tsx",
242
+ "source": "./src/react/components/Field.tsx",
243
+ "import": "./dist/react/components/Field.js"
244
+ },
215
245
  "./react/file-tree": {
216
246
  "types": "./dist/react/file-tree/index.d.ts",
217
247
  "bun": "./src/react/file-tree/index.ts",
@@ -242,6 +272,12 @@
242
272
  "source": "./src/react/components/IconButton.tsx",
243
273
  "import": "./dist/react/components/IconButton.js"
244
274
  },
275
+ "./react/icons": {
276
+ "types": "./dist/react/icons.d.ts",
277
+ "bun": "./src/react/icons.tsx",
278
+ "source": "./src/react/icons.tsx",
279
+ "import": "./dist/react/icons.js"
280
+ },
245
281
  "./react/info-hint": {
246
282
  "types": "./dist/react/components/InfoHint.d.ts",
247
283
  "bun": "./src/react/components/InfoHint.tsx",
@@ -398,6 +434,36 @@
398
434
  "source": "./src/react/hooks/useTableSort.ts",
399
435
  "import": "./dist/react/hooks/useTableSort.js"
400
436
  },
437
+ "./react/menu": {
438
+ "types": "./dist/react/components/Menu.d.ts",
439
+ "bun": "./src/react/components/Menu.tsx",
440
+ "source": "./src/react/components/Menu.tsx",
441
+ "import": "./dist/react/components/Menu.js"
442
+ },
443
+ "./react/save-indicator": {
444
+ "types": "./dist/react/components/SaveIndicator.d.ts",
445
+ "bun": "./src/react/components/SaveIndicator.tsx",
446
+ "source": "./src/react/components/SaveIndicator.tsx",
447
+ "import": "./dist/react/components/SaveIndicator.js"
448
+ },
449
+ "./react/selection-bar": {
450
+ "types": "./dist/react/components/SelectionBar.d.ts",
451
+ "bun": "./src/react/components/SelectionBar.tsx",
452
+ "source": "./src/react/components/SelectionBar.tsx",
453
+ "import": "./dist/react/components/SelectionBar.js"
454
+ },
455
+ "./react/unstyled-button": {
456
+ "types": "./dist/react/components/UnstyledButton.d.ts",
457
+ "bun": "./src/react/components/UnstyledButton.tsx",
458
+ "source": "./src/react/components/UnstyledButton.tsx",
459
+ "import": "./dist/react/components/UnstyledButton.js"
460
+ },
461
+ "./react/modal": {
462
+ "types": "./dist/react/components/Modal.d.ts",
463
+ "bun": "./src/react/components/Modal.tsx",
464
+ "source": "./src/react/components/Modal.tsx",
465
+ "import": "./dist/react/components/Modal.js"
466
+ },
401
467
  "./react/multi-select-field": {
402
468
  "types": "./dist/react/components/MultiSelectField.d.ts",
403
469
  "bun": "./src/react/components/MultiSelectField.tsx",
@@ -434,6 +500,18 @@
434
500
  "source": "./src/react/components/Switch.tsx",
435
501
  "import": "./dist/react/components/Switch.js"
436
502
  },
503
+ "./react/toast": {
504
+ "types": "./dist/react/Toast.d.ts",
505
+ "bun": "./src/react/Toast.tsx",
506
+ "source": "./src/react/Toast.tsx",
507
+ "import": "./dist/react/Toast.js"
508
+ },
509
+ "./react/tooltip": {
510
+ "types": "./dist/react/components/Tooltip.d.ts",
511
+ "bun": "./src/react/components/Tooltip.tsx",
512
+ "source": "./src/react/components/Tooltip.tsx",
513
+ "import": "./dist/react/components/Tooltip.js"
514
+ },
437
515
  "./react/upload-direct": {
438
516
  "types": "./dist/react/lib/directUpload.d.ts",
439
517
  "bun": "./src/react/lib/directUpload.ts",
@@ -725,8 +803,8 @@
725
803
  "@types/react-dom": "^19.2.3",
726
804
  "@vidstack/react": "1.15.6",
727
805
  "@vitejs/plugin-react": "^5.2.0",
728
- "cursedbelt-server": "^4.40.1",
729
- "cursedops": "^0.11.0",
806
+ "cursedbelt-server": "^4.40.3",
807
+ "cursedops": "^0.12.0",
730
808
  "diff": "^9.0.0",
731
809
  "emoji-mart": "^5.6.0",
732
810
  "hls.js": "^1.5.0",
@@ -19,7 +19,7 @@
19
19
  "no-hand-rolled-scroll-memory": 0,
20
20
  "no-hand-rolled-surface": 0,
21
21
  "no-inline-style": 76,
22
- "opt-out-count": 267,
22
+ "opt-out-count": 269,
23
23
  "opt-out-needs-cause": 258
24
24
  },
25
25
  "cursedshell": {
@@ -173,6 +173,7 @@ export const AREAS: readonly Area[] = [
173
173
  'components/IconButton.tsx',
174
174
  'components/InfoHint.tsx',
175
175
  'components/Input.tsx',
176
+ 'components/Menu.tsx',
176
177
  'components/Modal.tsx',
177
178
  'components/OnlineDot.tsx',
178
179
  'components/Page.tsx',
@@ -196,6 +197,9 @@ export const AREAS: readonly Area[] = [
196
197
  'components/Tooltip.tsx',
197
198
  'components/TooltipImpl.tsx',
198
199
  'components/TruncatedText.tsx',
200
+ 'components/UnstyledButton.tsx',
201
+ 'components/SaveIndicator.tsx',
202
+ 'components/SelectionBar.tsx',
199
203
  'components/dialog.tsx',
200
204
  'components/layout.tsx',
201
205
  'components/revealOnHover.ts',
@@ -1,5 +1,5 @@
1
1
  import { afterEach, describe, expect, test } from 'bun:test';
2
- import { cleanup, render } from '@testing-library/react';
2
+ import { act, cleanup, render, waitFor } from '@testing-library/react';
3
3
  import { Toaster, toast } from './Toast.js';
4
4
 
5
5
  afterEach(cleanup);
@@ -15,4 +15,20 @@ describe('Toast', () => {
15
15
  test('Toaster mounts without throwing', () => {
16
16
  expect(() => render(<Toaster />)).not.toThrow();
17
17
  });
18
+
19
+ test("the toaster answers sonner's own colour variables with the belt's tokens", async () => {
20
+ render(<Toaster />);
21
+ act(() => {
22
+ toast('Saved');
23
+ });
24
+ const toaster = await waitFor(() => {
25
+ const found = document.querySelector<HTMLElement>('[data-sonner-toaster]');
26
+ if (!found) throw new Error('no toaster yet');
27
+ return found;
28
+ });
29
+ // sonner's theme sets these on the same element; an inline value is what outranks it.
30
+ expect(toaster.style.getPropertyValue('--normal-bg')).toBe('var(--popover)');
31
+ expect(toaster.style.getPropertyValue('--normal-border')).toBe('var(--border)');
32
+ expect(toaster.style.getPropertyValue('--normal-text')).toBe('var(--popover-foreground)');
33
+ });
18
34
  });
@@ -17,19 +17,36 @@ export { toast };
17
17
 
18
18
  export type ToasterProps = React.ComponentProps<typeof SonnerToaster>;
19
19
 
20
- export function Toaster(props: ToasterProps) {
20
+ /**
21
+ * 🔴 sonner paints a toast from its OWN variables (`--normal-bg`, `--normal-border`,
22
+ * `--normal-text`), set per `data-sonner-theme` on the toaster — and its rule outranks the
23
+ * `bg-popover`/`border-border` classes below wherever the app's utilities are not layered
24
+ * above it. Measured in desk, 2026-09-30 (forge task 2413): a white toast on the dark theme,
25
+ * because sonner's theme defaults to light. Answering the variables with the belt's tokens on
26
+ * the toaster itself makes the toast follow the app's theme whatever sonner thinks it is.
27
+ */
28
+ const TOKEN_VARS = {
29
+ '--normal-bg': 'var(--popover)',
30
+ '--normal-border': 'var(--border)',
31
+ '--normal-text': 'var(--popover-foreground)',
32
+ } as React.CSSProperties;
33
+
34
+ export function Toaster({ style, ...props }: ToasterProps) {
21
35
  return (
22
36
  <SonnerToaster
37
+ style={{ ...TOKEN_VARS, ...style }}
23
38
  position='bottom-right'
24
39
  closeButton
25
40
  toastOptions={{
26
41
  classNames: {
27
42
  toast: 'rounded-lg border border-border bg-popover text-foreground shadow-overlay',
28
43
  title: 'text-sm font-medium',
29
- description: 'text-xs text-muted-foreground',
44
+ // `!` because sonner colours these two with element rules of its own (`#3f3f3f` for a
45
+ // description, its grey scale for the close button) that no variable reaches.
46
+ description: 'text-xs text-muted-foreground!',
30
47
  actionButton: 'bg-accent-solid text-white text-xs px-2 py-1 rounded-sm',
31
48
  cancelButton: 'bg-muted text-foreground text-xs px-2 py-1 rounded-sm',
32
- closeButton: 'border-border bg-popover text-muted-foreground hover:text-foreground',
49
+ closeButton: 'border-border! bg-popover! text-muted-foreground! hover:text-foreground!',
33
50
  error: 'border-destructive/50 [&_[data-icon]]:text-destructive',
34
51
  success: 'border-success/50 [&_[data-icon]]:text-success',
35
52
  warning: 'border-warning/50 [&_[data-icon]]:text-warning',
@@ -1,5 +1,5 @@
1
1
  import { afterEach, describe, expect, test } from 'bun:test';
2
- import { cleanup, render } from '@testing-library/react';
2
+ import { cleanup, fireEvent, render } from '@testing-library/react';
3
3
  import { createRef } from 'react';
4
4
  import { Button } from './Button.js';
5
5
  import { ButtonGroup } from './ButtonGroup.js';
@@ -119,6 +119,54 @@ describe('Button', () => {
119
119
  expect((getByRole('button') as HTMLButtonElement).disabled).toBe(true);
120
120
  });
121
121
 
122
+ test('unavailableReason REFUSES the press but stays focusable: aria-disabled, not disabled', () => {
123
+ let clicks = 0;
124
+ let keys = 0;
125
+ const { getByRole } = render(
126
+ <Button tooltip='Sync' unavailableReason='Sync needs the Mac' onClick={() => clicks++} onKeyDown={() => keys++}>
127
+ Sync
128
+ </Button>,
129
+ );
130
+ const btn = getByRole('button') as HTMLButtonElement;
131
+ expect(btn.disabled).toBe(false); // still in the tab order, still hoverable
132
+ expect(btn.getAttribute('aria-disabled')).toBe('true');
133
+ btn.click();
134
+ expect(clicks).toBe(0);
135
+ fireEvent.keyDown(btn, { key: 'Enter' });
136
+ fireEvent.keyDown(btn, { key: ' ' });
137
+ expect(keys).toBe(0);
138
+ fireEvent.keyDown(btn, { key: 'Tab' }); // every other key still reaches the caller
139
+ expect(keys).toBe(1);
140
+ btn.focus();
141
+ expect(document.activeElement).toBe(btn);
142
+ });
143
+
144
+ test('an empty unavailableReason is no refusal, and disabled wins over one', () => {
145
+ let clicks = 0;
146
+ const { getByRole, rerender } = render(
147
+ <Button tooltip='Go' unavailableReason=' ' onClick={() => clicks++}>
148
+ Go
149
+ </Button>,
150
+ );
151
+ getByRole('button').click();
152
+ expect(clicks).toBe(1);
153
+ expect(getByRole('button').getAttribute('aria-disabled')).toBeNull();
154
+ rerender(
155
+ <Button tooltip='Go' unavailableReason='Busy' disabled onClick={() => clicks++}>
156
+ Go
157
+ </Button>,
158
+ );
159
+ expect((getByRole('button') as HTMLButtonElement).disabled).toBe(true);
160
+ expect(getByRole('button').getAttribute('aria-disabled')).toBeNull();
161
+ });
162
+
163
+ test('a pressed toggle and a refused control are drawn by the variant classes', () => {
164
+ const cls = render(<Button tooltip='B'>B</Button>).getByRole('button').className;
165
+ expect(cls).toContain('aria-pressed:border-accent');
166
+ expect(cls).toContain('aria-disabled:opacity-50');
167
+ expect(cls).not.toContain('aria-disabled:pointer-events-none'); // the reason must stay reachable
168
+ });
169
+
122
170
  test('asChild styles the child element directly (no nested button element, no span wrapper)', () => {
123
171
  const { container, getByRole } = render(
124
172
  <Button tooltip='Action' asChild variant='primary'>