@evinvest/uikit 0.32.0 → 0.33.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 (288) hide show
  1. package/README.md +21 -10
  2. package/dist/components/atomic/accordion.js +2 -1
  3. package/dist/components/atomic/accordion.js.map +1 -1
  4. package/dist/components/atomic/alert.js +4 -4
  5. package/dist/components/atomic/alert.js.map +1 -1
  6. package/dist/components/atomic/badge.js +4 -3
  7. package/dist/components/atomic/badge.js.map +1 -1
  8. package/dist/components/atomic/breadcrumb.js +7 -9
  9. package/dist/components/atomic/breadcrumb.js.map +1 -1
  10. package/dist/components/atomic/button.js +4 -3
  11. package/dist/components/atomic/button.js.map +1 -1
  12. package/dist/components/atomic/calendar.js +10 -25
  13. package/dist/components/atomic/calendar.js.map +1 -1
  14. package/dist/components/atomic/checkbox.js +2 -1
  15. package/dist/components/atomic/checkbox.js.map +1 -1
  16. package/dist/components/atomic/command.js +5 -20
  17. package/dist/components/atomic/command.js.map +1 -1
  18. package/dist/components/atomic/context-menu.js +6 -13
  19. package/dist/components/atomic/context-menu.js.map +1 -1
  20. package/dist/components/atomic/dialog.js +4 -2
  21. package/dist/components/atomic/dialog.js.map +1 -1
  22. package/dist/components/atomic/dropdown-menu.js +6 -14
  23. package/dist/components/atomic/dropdown-menu.js.map +1 -1
  24. package/dist/components/atomic/hover-card.js.map +1 -1
  25. package/dist/components/atomic/info-tip.js +8 -9
  26. package/dist/components/atomic/info-tip.js.map +1 -1
  27. package/dist/components/atomic/input-otp.js +2 -1
  28. package/dist/components/atomic/input-otp.js.map +1 -1
  29. package/dist/components/atomic/key-value.js +5 -4
  30. package/dist/components/atomic/key-value.js.map +1 -1
  31. package/dist/components/atomic/menubar.js +7 -18
  32. package/dist/components/atomic/menubar.js.map +1 -1
  33. package/dist/components/atomic/native-select.js +1 -1
  34. package/dist/components/atomic/native-select.js.map +1 -1
  35. package/dist/components/atomic/navigation-menu.js +6 -14
  36. package/dist/components/atomic/navigation-menu.js.map +1 -1
  37. package/dist/components/atomic/pagination.js +18 -19
  38. package/dist/components/atomic/pagination.js.map +1 -1
  39. package/dist/components/atomic/popover.js.map +1 -1
  40. package/dist/components/atomic/radio-group.js +5 -4
  41. package/dist/components/atomic/radio-group.js.map +1 -1
  42. package/dist/components/atomic/section-descriptor.js +5 -8
  43. package/dist/components/atomic/section-descriptor.js.map +1 -1
  44. package/dist/components/atomic/select-listbox.js +2 -2
  45. package/dist/components/atomic/select-listbox.js.map +1 -1
  46. package/dist/components/atomic/select.js +5 -13
  47. package/dist/components/atomic/select.js.map +1 -1
  48. package/dist/components/atomic/sheet.js +4 -2
  49. package/dist/components/atomic/sheet.js.map +1 -1
  50. package/dist/components/atomic/sidebar.js +7 -26
  51. package/dist/components/atomic/sidebar.js.map +1 -1
  52. package/dist/components/atomic/slider.js.map +1 -1
  53. package/dist/components/atomic/sonner.js +20 -18
  54. package/dist/components/atomic/sonner.js.map +1 -1
  55. package/dist/components/atomic/spinner.js +6 -3
  56. package/dist/components/atomic/spinner.js.map +1 -1
  57. package/dist/components/atomic/tabs.js.map +1 -1
  58. package/dist/components/atomic/toggle-group.js +2 -2
  59. package/dist/components/atomic/toggle-group.js.map +1 -1
  60. package/dist/components/atomic/toggle.js +3 -6
  61. package/dist/components/atomic/toggle.js.map +1 -1
  62. package/dist/components/atomic/tooltip.js.map +1 -1
  63. package/dist/components/derived/carousel.js +6 -6
  64. package/dist/components/derived/carousel.js.map +1 -1
  65. package/dist/components/derived/command-palette.js +8 -7
  66. package/dist/components/derived/command-palette.js.map +1 -1
  67. package/dist/components/derived/confirm-dialog.js +8 -13
  68. package/dist/components/derived/confirm-dialog.js.map +1 -1
  69. package/dist/components/derived/date-time-picker.js +10 -14
  70. package/dist/components/derived/date-time-picker.js.map +1 -1
  71. package/dist/components/derived/field.js.map +1 -1
  72. package/dist/components/derived/form.js +0 -1
  73. package/dist/components/derived/form.js.map +1 -1
  74. package/dist/components/derived/icon-button.js.map +1 -1
  75. package/dist/components/derived/item.js +0 -1
  76. package/dist/components/derived/item.js.map +1 -1
  77. package/dist/components/derived/popover-form.js +4 -3
  78. package/dist/components/derived/popover-form.js.map +1 -1
  79. package/dist/components/derived/settings.js +4 -4
  80. package/dist/components/derived/settings.js.map +1 -1
  81. package/dist/components/shell/account-menu-profile.js +1 -1
  82. package/dist/components/shell/account-menu-profile.js.map +1 -1
  83. package/dist/components/shell/account-settings-dialog.js +3 -2
  84. package/dist/components/shell/account-settings-dialog.js.map +1 -1
  85. package/dist/components/shell/aside.js +6 -4
  86. package/dist/components/shell/aside.js.map +1 -1
  87. package/dist/components/shell/auth-wall.js +3 -5
  88. package/dist/components/shell/auth-wall.js.map +1 -1
  89. package/dist/components/shell/bottom-tab-bar.js +3 -3
  90. package/dist/components/shell/bottom-tab-bar.js.map +1 -1
  91. package/dist/components/shell/chat-composer.js +9 -7
  92. package/dist/components/shell/chat-composer.js.map +1 -1
  93. package/dist/components/shell/chat-message.js.map +1 -1
  94. package/dist/components/shell/chat-typing.js +2 -1
  95. package/dist/components/shell/chat-typing.js.map +1 -1
  96. package/dist/components/shell/footer.js +26 -24
  97. package/dist/components/shell/footer.js.map +1 -1
  98. package/dist/components/shell/header-controls.js +17 -15
  99. package/dist/components/shell/header-controls.js.map +1 -1
  100. package/dist/components/shell/hotbar.js +158 -0
  101. package/dist/components/shell/hotbar.js.map +1 -0
  102. package/dist/components/shell/logo.js +3 -2
  103. package/dist/components/shell/logo.js.map +1 -1
  104. package/dist/components/shell/mobile-app-bar.js +4 -4
  105. package/dist/components/shell/mobile-app-bar.js.map +1 -1
  106. package/dist/components/shell/nav-collapse.js +6 -5
  107. package/dist/components/shell/nav-collapse.js.map +1 -1
  108. package/dist/components/shell/resource-error.js +2 -2
  109. package/dist/components/shell/resource-error.js.map +1 -1
  110. package/dist/components/shell/section-nav.js +3 -3
  111. package/dist/components/shell/section-nav.js.map +1 -1
  112. package/dist/components/shell/shell-icons.js +7 -22
  113. package/dist/components/shell/shell-icons.js.map +1 -1
  114. package/dist/components/shell/shell-nav.js +4 -4
  115. package/dist/components/shell/shell-nav.js.map +1 -1
  116. package/dist/components/shell/smart-header.js.map +1 -1
  117. package/dist/components/shell/status-screen.js +35 -35
  118. package/dist/components/shell/status-screen.js.map +1 -1
  119. package/dist/components/shell/system-banner.js +6 -6
  120. package/dist/components/shell/system-banner.js.map +1 -1
  121. package/dist/components/shell/workspace-tab.js +2 -1
  122. package/dist/components/shell/workspace-tab.js.map +1 -1
  123. package/dist/generated/accent.js +10 -10
  124. package/dist/generated/accent.js.map +1 -1
  125. package/dist/generated/accordion.js +6 -2
  126. package/dist/generated/accordion.js.map +1 -1
  127. package/dist/generated/account-list.js +4 -4
  128. package/dist/generated/account-list.js.map +1 -1
  129. package/dist/generated/account-settings-dialog.js +6 -2
  130. package/dist/generated/account-settings-dialog.js.map +1 -1
  131. package/dist/generated/alert-dialog.js +1 -1
  132. package/dist/generated/alert-dialog.js.map +1 -1
  133. package/dist/generated/alert.js +3 -10
  134. package/dist/generated/alert.js.map +1 -1
  135. package/dist/generated/aside.js +7 -0
  136. package/dist/generated/aside.js.map +1 -1
  137. package/dist/generated/badge.js +2 -11
  138. package/dist/generated/badge.js.map +1 -1
  139. package/dist/generated/band.js +10 -10
  140. package/dist/generated/band.js.map +1 -1
  141. package/dist/generated/breadcrumb.js +1 -1
  142. package/dist/generated/breadcrumb.js.map +1 -1
  143. package/dist/generated/button-group.js +3 -3
  144. package/dist/generated/button-group.js.map +1 -1
  145. package/dist/generated/button.js +4 -20
  146. package/dist/generated/button.js.map +1 -1
  147. package/dist/generated/calendar.js +5 -5
  148. package/dist/generated/calendar.js.map +1 -1
  149. package/dist/generated/card.js +1 -1
  150. package/dist/generated/card.js.map +1 -1
  151. package/dist/generated/carousel.js +1 -1
  152. package/dist/generated/carousel.js.map +1 -1
  153. package/dist/generated/chat-composer.js +9 -2
  154. package/dist/generated/chat-composer.js.map +1 -1
  155. package/dist/generated/chat-message.js +25 -19
  156. package/dist/generated/chat-message.js.map +1 -1
  157. package/dist/generated/checkbox.js +1 -1
  158. package/dist/generated/checkbox.js.map +1 -1
  159. package/dist/generated/command-palette.js +10 -1
  160. package/dist/generated/command-palette.js.map +1 -1
  161. package/dist/generated/command.js +8 -6
  162. package/dist/generated/command.js.map +1 -1
  163. package/dist/generated/confirm-dialog.js +8 -8
  164. package/dist/generated/confirm-dialog.js.map +1 -1
  165. package/dist/generated/context-menu.js +7 -5
  166. package/dist/generated/context-menu.js.map +1 -1
  167. package/dist/generated/copy.js +97 -0
  168. package/dist/generated/copy.js.map +1 -0
  169. package/dist/generated/date-time-picker.js +10 -0
  170. package/dist/generated/date-time-picker.js.map +1 -1
  171. package/dist/generated/dialog.js +2 -2
  172. package/dist/generated/dialog.js.map +1 -1
  173. package/dist/generated/drawer.js +6 -6
  174. package/dist/generated/drawer.js.map +1 -1
  175. package/dist/generated/dropdown-menu.js +7 -5
  176. package/dist/generated/dropdown-menu.js.map +1 -1
  177. package/dist/generated/empty.js +5 -5
  178. package/dist/generated/empty.js.map +1 -1
  179. package/dist/generated/field.js +9 -7
  180. package/dist/generated/field.js.map +1 -1
  181. package/dist/generated/footer.js +45 -0
  182. package/dist/generated/footer.js.map +1 -0
  183. package/dist/generated/header-controls.js +5 -10
  184. package/dist/generated/header-controls.js.map +1 -1
  185. package/dist/generated/hover-card.js +1 -1
  186. package/dist/generated/hover-card.js.map +1 -1
  187. package/dist/generated/icon-button.js +4 -2
  188. package/dist/generated/icon-button.js.map +1 -1
  189. package/dist/generated/icons.js +36 -0
  190. package/dist/generated/icons.js.map +1 -0
  191. package/dist/generated/info-tip.js +5 -0
  192. package/dist/generated/info-tip.js.map +1 -0
  193. package/dist/generated/input-group.js +10 -10
  194. package/dist/generated/input-group.js.map +1 -1
  195. package/dist/generated/input-otp.js +2 -2
  196. package/dist/generated/input-otp.js.map +1 -1
  197. package/dist/generated/input.js +4 -4
  198. package/dist/generated/input.js.map +1 -1
  199. package/dist/generated/item.js +11 -11
  200. package/dist/generated/item.js.map +1 -1
  201. package/dist/generated/kbd.js +1 -1
  202. package/dist/generated/kbd.js.map +1 -1
  203. package/dist/generated/key-value.js +7 -1
  204. package/dist/generated/key-value.js.map +1 -1
  205. package/dist/generated/label.js +1 -1
  206. package/dist/generated/label.js.map +1 -1
  207. package/dist/generated/logo.js +9 -0
  208. package/dist/generated/logo.js.map +1 -0
  209. package/dist/generated/menu.js +7 -0
  210. package/dist/generated/menu.js.map +1 -0
  211. package/dist/generated/menubar.js +10 -8
  212. package/dist/generated/menubar.js.map +1 -1
  213. package/dist/generated/navigation-menu.js +11 -5
  214. package/dist/generated/navigation-menu.js.map +1 -1
  215. package/dist/generated/orientation.js +5 -0
  216. package/dist/generated/orientation.js.map +1 -0
  217. package/dist/generated/pagination.js +15 -0
  218. package/dist/generated/pagination.js.map +1 -0
  219. package/dist/generated/popover-form.js +5 -0
  220. package/dist/generated/popover-form.js.map +1 -1
  221. package/dist/generated/popover.js +1 -1
  222. package/dist/generated/popover.js.map +1 -1
  223. package/dist/generated/primitives.js +7 -0
  224. package/dist/generated/primitives.js.map +1 -0
  225. package/dist/generated/radio-group.js +5 -1
  226. package/dist/generated/radio-group.js.map +1 -1
  227. package/dist/generated/section-descriptor.js +7 -0
  228. package/dist/generated/section-descriptor.js.map +1 -0
  229. package/dist/generated/select.js +16 -10
  230. package/dist/generated/select.js.map +1 -1
  231. package/dist/generated/settings.js +2 -7
  232. package/dist/generated/settings.js.map +1 -1
  233. package/dist/generated/sheet.js +1 -1
  234. package/dist/generated/sheet.js.map +1 -1
  235. package/dist/generated/sidebar.js +24 -12
  236. package/dist/generated/sidebar.js.map +1 -1
  237. package/dist/generated/size.js +12 -0
  238. package/dist/generated/size.js.map +1 -0
  239. package/dist/generated/skeleton.js +1 -1
  240. package/dist/generated/skeleton.js.map +1 -1
  241. package/dist/generated/slider.js +2 -2
  242. package/dist/generated/slider.js.map +1 -1
  243. package/dist/generated/slot.js +5 -0
  244. package/dist/generated/slot.js.map +1 -0
  245. package/dist/generated/smart-header.js +2 -0
  246. package/dist/generated/smart-header.js.map +1 -1
  247. package/dist/generated/sonner.js +11 -13
  248. package/dist/generated/sonner.js.map +1 -1
  249. package/dist/generated/spinner.js +5 -0
  250. package/dist/generated/spinner.js.map +1 -0
  251. package/dist/generated/status-screen.js +27 -0
  252. package/dist/generated/status-screen.js.map +1 -0
  253. package/dist/generated/switch.js +2 -2
  254. package/dist/generated/switch.js.map +1 -1
  255. package/dist/generated/system-banner.js +6 -16
  256. package/dist/generated/system-banner.js.map +1 -1
  257. package/dist/generated/table.js +9 -9
  258. package/dist/generated/table.js.map +1 -1
  259. package/dist/generated/tabs.js +2 -2
  260. package/dist/generated/tabs.js.map +1 -1
  261. package/dist/generated/textarea.js +1 -1
  262. package/dist/generated/textarea.js.map +1 -1
  263. package/dist/generated/toggle-group.js +9 -0
  264. package/dist/generated/toggle-group.js.map +1 -0
  265. package/dist/generated/toggle.js +1 -9
  266. package/dist/generated/toggle.js.map +1 -1
  267. package/dist/generated/tone.js +12 -0
  268. package/dist/generated/tone.js.map +1 -0
  269. package/dist/generated/tooltip.js +1 -1
  270. package/dist/generated/tooltip.js.map +1 -1
  271. package/dist/generated/workspace-tab.js +3 -3
  272. package/dist/generated/workspace-tab.js.map +1 -1
  273. package/dist/icons/icon-shapes.js +10 -0
  274. package/dist/icons/icon-shapes.js.map +1 -0
  275. package/dist/icons/select-chevron.js +2 -1
  276. package/dist/icons/select-chevron.js.map +1 -1
  277. package/dist/icons/ui-icons.js +3 -57
  278. package/dist/icons/ui-icons.js.map +1 -1
  279. package/dist/index.d.ts +203 -155
  280. package/dist/index.js +1 -0
  281. package/dist/lib/cn.js +1 -0
  282. package/dist/lib/cn.js.map +1 -1
  283. package/dist/primitives/use-floating.js.map +1 -1
  284. package/dist/primitives/use-roving-focus.js.map +1 -1
  285. package/package.json +1 -1
  286. package/styles/ev.css +13 -76
  287. package/styles/theme.css +133 -66
  288. package/styles/tokens.css +157 -149
package/dist/index.js CHANGED
@@ -76,6 +76,7 @@ export { usePendingNavigation } from "./components/shell/use-shell-nav.js";
76
76
  export { ShellNav } from "./components/shell/shell-nav.js";
77
77
  export { NavBadge, NavDot } from "./components/shell/nav-badge.js";
78
78
  export { BottomTabBar } from "./components/shell/bottom-tab-bar.js";
79
+ export { Hotbar } from "./components/shell/hotbar.js";
79
80
  export { TopBar } from "./components/shell/top-bar.js";
80
81
  export { AccountMenu } from "./components/shell/account-menu.js";
81
82
  export { MobileAppBar } from "./components/shell/mobile-app-bar.js";
package/dist/lib/cn.js CHANGED
@@ -2,6 +2,7 @@ import { clsx } from "clsx";
2
2
  import { extendTailwindMerge } from "tailwind-merge";
3
3
  const twMerge = extendTailwindMerge({
4
4
  extend: {
5
+ theme: { radius: ["control", "panel", "chip", "row"] },
5
6
  classGroups: {
6
7
  "font-size": [{ text: ["caption", "body-s", "body", "body-l", "h3", "h2", "h1", "figure-m", "figure-l"] }]
7
8
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/lib/cn.ts"],"sourcesContent":["import { clsx, type ClassValue } from \"clsx\";\nimport { extendTailwindMerge } from \"tailwind-merge\";\n\n// The kit's own type scale (theme.css `--text-*`). tailwind-merge only knows\n// t-shirt sizes as font sizes, so `text-body` would be filed under text colour\n// and dropped by a later `text-ink`. The Rust mirror is `fuse` in ev_lib_classes.\nconst twMerge = extendTailwindMerge({\n extend: {\n classGroups: {\n \"font-size\": [{ text: [\"caption\", \"body-s\", \"body\", \"body-l\", \"h3\", \"h2\", \"h1\", \"figure-m\", \"figure-l\"] }],\n },\n },\n});\n\n/**\n * Fuses class fragments into one string with real Tailwind conflict resolution:\n * `clsx` drops falsy fragments and joins the rest, `tailwind-merge` resolves\n * conflicting utilities so the rightmost wins (`cn(\"p-4\", \"p-2\") === \"p-2\"`). A\n * caller's `className` override, passed last, therefore beats the base classes.\n *\n * `twMerge` parses every utility on every render, and the overwhelmingly common\n * call is one kit constant plus a `className` override that is absent — so that\n * call returns the constant untouched.\n *\n * The Rust mirror is the `cn!` macro in `ev_lib::uikit` (`tailwind_fuse::tw_merge!`).\n */\nexport function cn(...inputs: ClassValue[]): string {\n if (inputs.length === 2 && typeof inputs[0] === \"string\" && !inputs[1]) {\n return inputs[0];\n }\n return twMerge(clsx(inputs));\n}\n"],"mappings":"AAAA,SAAS,YAA6B;AACtC,SAAS,2BAA2B;AAKpC,MAAM,UAAU,oBAAoB;AAAA,EAClC,QAAQ;AAAA,IACN,aAAa;AAAA,MACX,aAAa,CAAC,EAAE,MAAM,CAAC,WAAW,UAAU,QAAQ,UAAU,MAAM,MAAM,MAAM,YAAY,UAAU,EAAE,CAAC;AAAA,IAC3G;AAAA,EACF;AACF,CAAC;AAcM,SAAS,MAAM,QAA8B;AAClD,MAAI,OAAO,WAAW,KAAK,OAAO,OAAO,CAAC,MAAM,YAAY,CAAC,OAAO,CAAC,GAAG;AACtE,WAAO,OAAO,CAAC;AAAA,EACjB;AACA,SAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B;","names":[]}
1
+ {"version":3,"sources":["../../src/lib/cn.ts"],"sourcesContent":["import { clsx, type ClassValue } from \"clsx\";\nimport { extendTailwindMerge } from \"tailwind-merge\";\n\n// The kit's own type scale (theme.css `--text-*`). tailwind-merge only knows\n// t-shirt sizes as font sizes, so `text-body` would be filed under text colour\n// and dropped by a later `text-ink`. The Rust mirror is `fuse` in ev_lib_classes.\nconst twMerge = extendTailwindMerge({\n extend: {\n theme: { radius: [\"control\", \"panel\", \"chip\", \"row\"] },\n classGroups: {\n \"font-size\": [{ text: [\"caption\", \"body-s\", \"body\", \"body-l\", \"h3\", \"h2\", \"h1\", \"figure-m\", \"figure-l\"] }],\n },\n },\n});\n\n/**\n * Fuses class fragments into one string with real Tailwind conflict resolution:\n * `clsx` drops falsy fragments and joins the rest, `tailwind-merge` resolves\n * conflicting utilities so the rightmost wins (`cn(\"p-4\", \"p-2\") === \"p-2\"`). A\n * caller's `className` override, passed last, therefore beats the base classes.\n *\n * `twMerge` parses every utility on every render, and the overwhelmingly common\n * call is one kit constant plus a `className` override that is absent — so that\n * call returns the constant untouched.\n *\n * The Rust mirror is the `cn!` macro in `ev_lib::uikit` (`tailwind_fuse::tw_merge!`).\n */\nexport function cn(...inputs: ClassValue[]): string {\n if (inputs.length === 2 && typeof inputs[0] === \"string\" && !inputs[1]) {\n return inputs[0];\n }\n return twMerge(clsx(inputs));\n}\n"],"mappings":"AAAA,SAAS,YAA6B;AACtC,SAAS,2BAA2B;AAKpC,MAAM,UAAU,oBAAoB;AAAA,EAClC,QAAQ;AAAA,IACN,OAAO,EAAE,QAAQ,CAAC,WAAW,SAAS,QAAQ,KAAK,EAAE;AAAA,IACrD,aAAa;AAAA,MACX,aAAa,CAAC,EAAE,MAAM,CAAC,WAAW,UAAU,QAAQ,UAAU,MAAM,MAAM,MAAM,YAAY,UAAU,EAAE,CAAC;AAAA,IAC3G;AAAA,EACF;AACF,CAAC;AAcM,SAAS,MAAM,QAA8B;AAClD,MAAI,OAAO,WAAW,KAAK,OAAO,OAAO,CAAC,MAAM,YAAY,CAAC,OAAO,CAAC,GAAG;AACtE,WAAO,OAAO,CAAC;AAAA,EACjB;AACA,SAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/primitives/use-floating.ts"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\n\nexport type Side = \"top\" | \"right\" | \"bottom\" | \"left\";\nexport type Align = \"start\" | \"center\" | \"end\";\n\nexport interface FloatingResult {\n floatingRef: React.RefObject<HTMLElement | null>;\n style: React.CSSProperties;\n side: Side;\n align: Align;\n}\n\n// The element sits at its containing block's origin and is moved with the\n// `translate` property, never with `top`/`left`: an absolute box's\n// shrink-to-fit width is \"containing block minus `left`\", so a `w-fit` surface\n// pushed past the viewport's right edge (horizontal scroll, a nested scroller)\n// would collapse to one word per line. `translate` composes with `transform`,\n// so the enter animations (which animate `transform`) are unaffected. The\n// offset never goes through React: `place()` / `sync()` write it straight to\n// the element, so this style prop stays one frozen object.\nconst ABSOLUTE: React.CSSProperties = Object.freeze({ position: \"absolute\", top: 0, left: 0 });\n\nconst POSITIONED = new Set([\"relative\", \"absolute\", \"fixed\", \"sticky\"]);\n\n/**\n * Converts a viewport-relative point into the coordinate space of the\n * floating element's containing block, i.e. the numbers `position: absolute`\n * expects. floating-ui's `strategy: \"absolute\"` does the same walk.\n */\nfunction toContainingBlock(\n floating: HTMLElement,\n x: number,\n y: number,\n): { x: number; y: number; root: boolean } {\n const parent = floating.offsetParent;\n const doc = floating.ownerDocument;\n const root = parent === doc.body || parent === doc.documentElement || parent === null;\n // `offsetParent` reports `body` even when nothing is positioned; only a\n // positioned body/html really forms a containing block.\n const positioned =\n parent instanceof HTMLElement &&\n (!root || POSITIONED.has(doc.defaultView?.getComputedStyle(parent).position ?? \"\"));\n if (!positioned) {\n const win = doc.defaultView;\n return { x: x + (win?.scrollX ?? 0), y: y + (win?.scrollY ?? 0), root: true };\n }\n const p = parent.getBoundingClientRect();\n return {\n x: x - p.left - parent.clientLeft + parent.scrollLeft,\n y: y - p.top - parent.clientTop + parent.scrollTop,\n root: false,\n };\n}\n\ninterface Size2D {\n width: number;\n height: number;\n}\n\n// The layout box, not `getBoundingClientRect()`: the latter reports the box\n// mid-animation (`zoom-in-95` scales it to 0.95 on the first frame), which\n// placed a top-side popover 5 % of its height too low, over the anchor.\nfunction layoutSize(el: HTMLElement): Size2D {\n return { width: el.offsetWidth, height: el.offsetHeight };\n}\n\n// jsdom has no `getAnimations`; there nothing animates.\nfunction hasRunningAnimationAbove(el: Element): boolean {\n for (let node = el.parentElement; node; node = node.parentElement) {\n if (node.getAnimations?.().some((a) => a.playState === \"running\")) return true;\n }\n return false;\n}\n\nfunction viewport(): { vw: number; vh: number } {\n // `clientWidth` excludes the scrollbar, which the floating element cannot\n // sit under anyway; jsdom reports 0 there, hence the fallback.\n const el = document.documentElement;\n return {\n vw: el.clientWidth || window.innerWidth,\n vh: el.clientHeight || window.innerHeight,\n };\n}\n\n/**\n * Positions a floating element against an anchor with `position: absolute` in\n * the coordinate space of its containing block (the `Portal` puts it in\n * `document.body`, so normally document coordinates) — the dep-light stand-in\n * for `@radix-ui/react-popper` / floating-ui. Supports a preferred\n * `side`/`align`, an `offset`, and a single collision flip onto the opposite\n * side when the preferred side overflows the viewport.\n *\n * Two update modes:\n * - `place()` — full pass (flip + clamp into the viewport) on open, on window\n * `resize`, and whenever the anchor or the floating element changes size.\n * - `sync()` — re-derives the coordinates from the anchor with the side and\n * the clamp shift already chosen; runs on every `scroll` (captured on\n * `window`, so nested scroll containers count too).\n *\n * Because the element is absolutely positioned in the document, a document\n * scroll moves it natively with the page — `sync()` then computes the same\n * numbers and is a no-op — so the overlay never lags behind the compositor.\n * Only a nested scroll container changes the coordinates, and there a frame\n * of lag is acceptable. The side is never revisited on scroll: no jumping.\n * A page that scrolls `body` instead of the document (`html { overflow:\n * hidden } body { overflow: auto }`) is such a nested scroller: correct, but\n * with the frame of lag the `fixed` strategy had.\n *\n * Rust's overlays use CSS-only placement (`data-side`) and do not measure; see\n * the README \"Limitations\".\n */\nexport function useFloating(opts: {\n anchorRef: React.RefObject<Element | null>;\n open: boolean;\n side?: Side;\n align?: Align;\n offset?: number;\n}): FloatingResult {\n const { anchorRef, open, side = \"bottom\", align = \"center\", offset = 4 } = opts;\n const floatingRef = React.useRef<HTMLElement | null>(null);\n const [placedSide, setPlacedSide] = React.useState<Side>(side);\n // The last full placement: the side that won the flip and how far the\n // viewport clamp pushed the element off its ideal spot. `sync()` reuses both\n // instead of measuring against the viewport again.\n const placement = React.useRef<{ side: Side; shiftX: number; shiftY: number }>({\n side,\n shiftX: 0,\n shiftY: 0,\n });\n\n React.useLayoutEffect(() => {\n if (!open) return;\n\n function ideal(a: DOMRect, f: Size2D, placed: Side): { top: number; left: number } {\n let top = 0;\n let left = 0;\n const vertical = placed === \"top\" || placed === \"bottom\";\n\n if (placed === \"bottom\") top = a.bottom + offset;\n else if (placed === \"top\") top = a.top - offset - f.height;\n else if (placed === \"right\") left = a.right + offset;\n else left = a.left - offset - f.width;\n\n if (vertical) {\n if (align === \"start\") left = a.left;\n else if (align === \"end\") left = a.right - f.width;\n else left = a.left + a.width / 2 - f.width / 2;\n } else {\n if (align === \"start\") top = a.top;\n else if (align === \"end\") top = a.bottom - f.height;\n else top = a.top + a.height / 2 - f.height / 2;\n }\n return { top, left };\n }\n\n // `contain` keeps the box inside the initial containing block: a box past\n // the document's edge grows the page (a scrollbar shows for as long as the\n // overlay is open). `place()` already clamps against the viewport;\n // `sync()` follows a nested scroller's anchor and only needs this bound.\n function write(floating: HTMLElement, f: Size2D, top: number, left: number, contain: boolean) {\n const p = toContainingBlock(floating, left, top);\n let { x, y } = p;\n if (contain && p.root) {\n const icbWidth = floating.ownerDocument.documentElement.clientWidth || window.innerWidth;\n x = Math.max(0, Math.min(x, icbWidth - f.width));\n y = Math.max(0, y);\n }\n floating.style.translate = `${Math.round(x)}px ${Math.round(y)}px`;\n }\n\n function place() {\n const anchor = anchorRef.current;\n const floating = floatingRef.current;\n if (!anchor || !floating) return;\n\n const a = anchor.getBoundingClientRect();\n const f = layoutSize(floating);\n const { vw, vh } = viewport();\n\n let placed: Side = side;\n if (side === \"bottom\" && a.bottom + offset + f.height > vh && a.top - offset - f.height > 0)\n placed = \"top\";\n else if (side === \"top\" && a.top - offset - f.height < 0 && a.bottom + offset + f.height < vh)\n placed = \"bottom\";\n else if (side === \"right\" && a.right + offset + f.width > vw && a.left - offset - f.width > 0)\n placed = \"left\";\n else if (side === \"left\" && a.left - offset - f.width < 0 && a.right + offset + f.width < vw)\n placed = \"right\";\n\n const { top, left } = ideal(a, f, placed);\n const clampedLeft = Math.max(offset, Math.min(left, vw - f.width - offset));\n const clampedTop = Math.max(offset, Math.min(top, vh - f.height - offset));\n\n placement.current = {\n side: placed,\n shiftX: clampedLeft - left,\n shiftY: clampedTop - top,\n };\n write(floating, f, clampedTop, clampedLeft, false);\n // Only a real change may re-render: this runs on every resize tick.\n setPlacedSide(prev => (prev === placed ? prev : placed));\n }\n\n function sync() {\n const anchor = anchorRef.current;\n const floating = floatingRef.current;\n if (!anchor || !floating) return;\n\n const a = anchor.getBoundingClientRect();\n const f = layoutSize(floating);\n const { side: placed, shiftX, shiftY } = placement.current;\n const { top, left } = ideal(a, f, placed);\n write(floating, f, top + shiftY, left + shiftX, true);\n }\n\n // An anchor inside an entering dialog is measured scaled and shifted by the\n // dialog's animation (`zoom-in-95`), which places the floating element off\n // its anchor for good: no resize or scroll follows the animation's end. So\n // keep re-placing, a frame at a time, until no ancestor animates.\n let frame = 0;\n function follow() {\n frame = 0;\n place();\n if (anchorRef.current && hasRunningAnimationAbove(anchorRef.current)) {\n frame = requestAnimationFrame(follow);\n }\n }\n follow();\n // No rAF: `scroll` already fires at most once per frame, and a synchronous\n // DOM write from the handler lands in that same frame — rAF would cost one.\n window.addEventListener(\"scroll\", sync, true);\n window.addEventListener(\"resize\", place);\n\n let observer: ResizeObserver | undefined;\n if (typeof ResizeObserver !== \"undefined\") {\n observer = new ResizeObserver(place);\n if (anchorRef.current) observer.observe(anchorRef.current);\n if (floatingRef.current) observer.observe(floatingRef.current);\n }\n\n return () => {\n if (frame) cancelAnimationFrame(frame);\n window.removeEventListener(\"scroll\", sync, true);\n window.removeEventListener(\"resize\", place);\n observer?.disconnect();\n };\n }, [open, side, align, offset, anchorRef]);\n\n return { floatingRef, style: ABSOLUTE, side: placedSide, align };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/primitives/use-floating.ts"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport type { Side } from \"../generated/primitives\";\n\nexport type { Side };\nexport type Align = \"start\" | \"center\" | \"end\";\n\nexport interface FloatingResult {\n floatingRef: React.RefObject<HTMLElement | null>;\n style: React.CSSProperties;\n side: Side;\n align: Align;\n}\n\n// The element sits at its containing block's origin and is moved with the\n// `translate` property, never with `top`/`left`: an absolute box's\n// shrink-to-fit width is \"containing block minus `left`\", so a `w-fit` surface\n// pushed past the viewport's right edge (horizontal scroll, a nested scroller)\n// would collapse to one word per line. `translate` composes with `transform`,\n// so the enter animations (which animate `transform`) are unaffected. The\n// offset never goes through React: `place()` / `sync()` write it straight to\n// the element, so this style prop stays one frozen object.\nconst ABSOLUTE: React.CSSProperties = Object.freeze({ position: \"absolute\", top: 0, left: 0 });\n\nconst POSITIONED = new Set([\"relative\", \"absolute\", \"fixed\", \"sticky\"]);\n\n/**\n * Converts a viewport-relative point into the coordinate space of the\n * floating element's containing block, i.e. the numbers `position: absolute`\n * expects. floating-ui's `strategy: \"absolute\"` does the same walk.\n */\nfunction toContainingBlock(\n floating: HTMLElement,\n x: number,\n y: number,\n): { x: number; y: number; root: boolean } {\n const parent = floating.offsetParent;\n const doc = floating.ownerDocument;\n const root = parent === doc.body || parent === doc.documentElement || parent === null;\n // `offsetParent` reports `body` even when nothing is positioned; only a\n // positioned body/html really forms a containing block.\n const positioned =\n parent instanceof HTMLElement &&\n (!root || POSITIONED.has(doc.defaultView?.getComputedStyle(parent).position ?? \"\"));\n if (!positioned) {\n const win = doc.defaultView;\n return { x: x + (win?.scrollX ?? 0), y: y + (win?.scrollY ?? 0), root: true };\n }\n const p = parent.getBoundingClientRect();\n return {\n x: x - p.left - parent.clientLeft + parent.scrollLeft,\n y: y - p.top - parent.clientTop + parent.scrollTop,\n root: false,\n };\n}\n\ninterface Size2D {\n width: number;\n height: number;\n}\n\n// The layout box, not `getBoundingClientRect()`: the latter reports the box\n// mid-animation (`zoom-in-95` scales it to 0.95 on the first frame), which\n// placed a top-side popover 5 % of its height too low, over the anchor.\nfunction layoutSize(el: HTMLElement): Size2D {\n return { width: el.offsetWidth, height: el.offsetHeight };\n}\n\n// jsdom has no `getAnimations`; there nothing animates.\nfunction hasRunningAnimationAbove(el: Element): boolean {\n for (let node = el.parentElement; node; node = node.parentElement) {\n if (node.getAnimations?.().some((a) => a.playState === \"running\")) return true;\n }\n return false;\n}\n\nfunction viewport(): { vw: number; vh: number } {\n // `clientWidth` excludes the scrollbar, which the floating element cannot\n // sit under anyway; jsdom reports 0 there, hence the fallback.\n const el = document.documentElement;\n return {\n vw: el.clientWidth || window.innerWidth,\n vh: el.clientHeight || window.innerHeight,\n };\n}\n\n/**\n * Positions a floating element against an anchor with `position: absolute` in\n * the coordinate space of its containing block (the `Portal` puts it in\n * `document.body`, so normally document coordinates) — the dep-light stand-in\n * for `@radix-ui/react-popper` / floating-ui. Supports a preferred\n * `side`/`align`, an `offset`, and a single collision flip onto the opposite\n * side when the preferred side overflows the viewport.\n *\n * Two update modes:\n * - `place()` — full pass (flip + clamp into the viewport) on open, on window\n * `resize`, and whenever the anchor or the floating element changes size.\n * - `sync()` — re-derives the coordinates from the anchor with the side and\n * the clamp shift already chosen; runs on every `scroll` (captured on\n * `window`, so nested scroll containers count too).\n *\n * Because the element is absolutely positioned in the document, a document\n * scroll moves it natively with the page — `sync()` then computes the same\n * numbers and is a no-op — so the overlay never lags behind the compositor.\n * Only a nested scroll container changes the coordinates, and there a frame\n * of lag is acceptable. The side is never revisited on scroll: no jumping.\n * A page that scrolls `body` instead of the document (`html { overflow:\n * hidden } body { overflow: auto }`) is such a nested scroller: correct, but\n * with the frame of lag the `fixed` strategy had.\n *\n * Rust's overlays use CSS-only placement (`data-side`) and do not measure; see\n * the README \"Limitations\".\n */\nexport function useFloating(opts: {\n anchorRef: React.RefObject<Element | null>;\n open: boolean;\n side?: Side;\n align?: Align;\n offset?: number;\n}): FloatingResult {\n const { anchorRef, open, side = \"bottom\", align = \"center\", offset = 4 } = opts;\n const floatingRef = React.useRef<HTMLElement | null>(null);\n const [placedSide, setPlacedSide] = React.useState<Side>(side);\n // The last full placement: the side that won the flip and how far the\n // viewport clamp pushed the element off its ideal spot. `sync()` reuses both\n // instead of measuring against the viewport again.\n const placement = React.useRef<{ side: Side; shiftX: number; shiftY: number }>({\n side,\n shiftX: 0,\n shiftY: 0,\n });\n\n React.useLayoutEffect(() => {\n if (!open) return;\n\n function ideal(a: DOMRect, f: Size2D, placed: Side): { top: number; left: number } {\n let top = 0;\n let left = 0;\n const vertical = placed === \"top\" || placed === \"bottom\";\n\n if (placed === \"bottom\") top = a.bottom + offset;\n else if (placed === \"top\") top = a.top - offset - f.height;\n else if (placed === \"right\") left = a.right + offset;\n else left = a.left - offset - f.width;\n\n if (vertical) {\n if (align === \"start\") left = a.left;\n else if (align === \"end\") left = a.right - f.width;\n else left = a.left + a.width / 2 - f.width / 2;\n } else {\n if (align === \"start\") top = a.top;\n else if (align === \"end\") top = a.bottom - f.height;\n else top = a.top + a.height / 2 - f.height / 2;\n }\n return { top, left };\n }\n\n // `contain` keeps the box inside the initial containing block: a box past\n // the document's edge grows the page (a scrollbar shows for as long as the\n // overlay is open). `place()` already clamps against the viewport;\n // `sync()` follows a nested scroller's anchor and only needs this bound.\n function write(floating: HTMLElement, f: Size2D, top: number, left: number, contain: boolean) {\n const p = toContainingBlock(floating, left, top);\n let { x, y } = p;\n if (contain && p.root) {\n const icbWidth = floating.ownerDocument.documentElement.clientWidth || window.innerWidth;\n x = Math.max(0, Math.min(x, icbWidth - f.width));\n y = Math.max(0, y);\n }\n floating.style.translate = `${Math.round(x)}px ${Math.round(y)}px`;\n }\n\n function place() {\n const anchor = anchorRef.current;\n const floating = floatingRef.current;\n if (!anchor || !floating) return;\n\n const a = anchor.getBoundingClientRect();\n const f = layoutSize(floating);\n const { vw, vh } = viewport();\n\n let placed: Side = side;\n if (side === \"bottom\" && a.bottom + offset + f.height > vh && a.top - offset - f.height > 0)\n placed = \"top\";\n else if (side === \"top\" && a.top - offset - f.height < 0 && a.bottom + offset + f.height < vh)\n placed = \"bottom\";\n else if (side === \"right\" && a.right + offset + f.width > vw && a.left - offset - f.width > 0)\n placed = \"left\";\n else if (side === \"left\" && a.left - offset - f.width < 0 && a.right + offset + f.width < vw)\n placed = \"right\";\n\n const { top, left } = ideal(a, f, placed);\n const clampedLeft = Math.max(offset, Math.min(left, vw - f.width - offset));\n const clampedTop = Math.max(offset, Math.min(top, vh - f.height - offset));\n\n placement.current = {\n side: placed,\n shiftX: clampedLeft - left,\n shiftY: clampedTop - top,\n };\n write(floating, f, clampedTop, clampedLeft, false);\n // Only a real change may re-render: this runs on every resize tick.\n setPlacedSide(prev => (prev === placed ? prev : placed));\n }\n\n function sync() {\n const anchor = anchorRef.current;\n const floating = floatingRef.current;\n if (!anchor || !floating) return;\n\n const a = anchor.getBoundingClientRect();\n const f = layoutSize(floating);\n const { side: placed, shiftX, shiftY } = placement.current;\n const { top, left } = ideal(a, f, placed);\n write(floating, f, top + shiftY, left + shiftX, true);\n }\n\n // An anchor inside an entering dialog is measured scaled and shifted by the\n // dialog's animation (`zoom-in-95`), which places the floating element off\n // its anchor for good: no resize or scroll follows the animation's end. So\n // keep re-placing, a frame at a time, until no ancestor animates.\n let frame = 0;\n function follow() {\n frame = 0;\n place();\n if (anchorRef.current && hasRunningAnimationAbove(anchorRef.current)) {\n frame = requestAnimationFrame(follow);\n }\n }\n follow();\n // No rAF: `scroll` already fires at most once per frame, and a synchronous\n // DOM write from the handler lands in that same frame — rAF would cost one.\n window.addEventListener(\"scroll\", sync, true);\n window.addEventListener(\"resize\", place);\n\n let observer: ResizeObserver | undefined;\n if (typeof ResizeObserver !== \"undefined\") {\n observer = new ResizeObserver(place);\n if (anchorRef.current) observer.observe(anchorRef.current);\n if (floatingRef.current) observer.observe(floatingRef.current);\n }\n\n return () => {\n if (frame) cancelAnimationFrame(frame);\n window.removeEventListener(\"scroll\", sync, true);\n window.removeEventListener(\"resize\", place);\n observer?.disconnect();\n };\n }, [open, side, align, offset, anchorRef]);\n\n return { floatingRef, style: ABSOLUTE, side: placedSide, align };\n}\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/primitives/use-roving-focus.ts"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\n\n/**\n * Arrow-key roving focus over a set of items — the dep-light core of menus,\n * tabs, radio-groups and toolbars (replacing `@radix-ui/react-roving-focus`).\n * Returns an `onKeyDown` to spread on the container and the active index; items\n * should set `tabIndex={index === activeIndex ? 0 : -1}`.\n *\n * `count` may be a getter: containers whose items are only known from the DOM\n * (menus with conditionally rendered or disabled items, inline sub-menus) pass\n * one so the bound is read at keydown time — never during render, which keeps\n * the hook safe under SSR.\n *\n * Mirrors the keyboard navigation Rust drives with a focused-index signal.\n */\nexport function useRovingFocus(opts: {\n count: number | (() => number);\n orientation?: \"horizontal\" | \"vertical\" | \"both\";\n loop?: boolean;\n initial?: number;\n}): {\n activeIndex: number;\n setActiveIndex: (i: number) => void;\n onKeyDown: (event: React.KeyboardEvent) => void;\n} {\n const { count, orientation = \"vertical\", loop = true, initial = 0 } = opts;\n const [activeIndex, setActiveIndex] = React.useState(initial);\n\n const resolveCount = () => (typeof count === \"function\" ? count() : count);\n\n const next = (dir: 1 | -1) => {\n const total = resolveCount();\n if (total <= 0) return;\n setActiveIndex((i) => {\n // Clamp first: the item set may have shrunk since the index was set.\n let n = Math.min(Math.max(i, 0), total - 1) + dir;\n if (n < 0) n = loop ? total - 1 : 0;\n else if (n >= total) n = loop ? 0 : total - 1;\n return n;\n });\n };\n\n const onKeyDown = (event: React.KeyboardEvent) => {\n const horiz = orientation === \"horizontal\" || orientation === \"both\";\n const vert = orientation === \"vertical\" || orientation === \"both\";\n if (vert && event.key === \"ArrowDown\") {\n event.preventDefault();\n next(1);\n } else if (vert && event.key === \"ArrowUp\") {\n event.preventDefault();\n next(-1);\n } else if (horiz && event.key === \"ArrowRight\") {\n event.preventDefault();\n next(1);\n } else if (horiz && event.key === \"ArrowLeft\") {\n event.preventDefault();\n next(-1);\n } else if (event.key === \"Home\") {\n event.preventDefault();\n setActiveIndex(0);\n } else if (event.key === \"End\") {\n event.preventDefault();\n const total = resolveCount();\n if (total > 0) setActiveIndex(total - 1);\n }\n };\n\n return { activeIndex, setActiveIndex, onKeyDown };\n}\n"],"mappings":";AAEA,YAAY,WAAW;AAehB,SAAS,eAAe,MAS7B;AACA,QAAM,EAAE,OAAO,cAAc,YAAY,OAAO,MAAM,UAAU,EAAE,IAAI;AACtE,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,OAAO;AAE5D,QAAM,eAAe,MAAO,OAAO,UAAU,aAAa,MAAM,IAAI;AAEpE,QAAM,OAAO,CAAC,QAAgB;AAC5B,UAAM,QAAQ,aAAa;AAC3B,QAAI,SAAS,EAAG;AAChB,mBAAe,CAAC,MAAM;AAEpB,UAAI,IAAI,KAAK,IAAI,KAAK,IAAI,GAAG,CAAC,GAAG,QAAQ,CAAC,IAAI;AAC9C,UAAI,IAAI,EAAG,KAAI,OAAO,QAAQ,IAAI;AAAA,eACzB,KAAK,MAAO,KAAI,OAAO,IAAI,QAAQ;AAC5C,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAEA,QAAM,YAAY,CAAC,UAA+B;AAChD,UAAM,QAAQ,gBAAgB,gBAAgB,gBAAgB;AAC9D,UAAM,OAAO,gBAAgB,cAAc,gBAAgB;AAC3D,QAAI,QAAQ,MAAM,QAAQ,aAAa;AACrC,YAAM,eAAe;AACrB,WAAK,CAAC;AAAA,IACR,WAAW,QAAQ,MAAM,QAAQ,WAAW;AAC1C,YAAM,eAAe;AACrB,WAAK,EAAE;AAAA,IACT,WAAW,SAAS,MAAM,QAAQ,cAAc;AAC9C,YAAM,eAAe;AACrB,WAAK,CAAC;AAAA,IACR,WAAW,SAAS,MAAM,QAAQ,aAAa;AAC7C,YAAM,eAAe;AACrB,WAAK,EAAE;AAAA,IACT,WAAW,MAAM,QAAQ,QAAQ;AAC/B,YAAM,eAAe;AACrB,qBAAe,CAAC;AAAA,IAClB,WAAW,MAAM,QAAQ,OAAO;AAC9B,YAAM,eAAe;AACrB,YAAM,QAAQ,aAAa;AAC3B,UAAI,QAAQ,EAAG,gBAAe,QAAQ,CAAC;AAAA,IACzC;AAAA,EACF;AAEA,SAAO,EAAE,aAAa,gBAAgB,UAAU;AAClD;","names":[]}
1
+ {"version":3,"sources":["../../src/primitives/use-roving-focus.ts"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport type { RovingOrientation } from \"../generated/primitives\";\n\n/**\n * Arrow-key roving focus over a set of items — the dep-light core of menus,\n * tabs, radio-groups and toolbars (replacing `@radix-ui/react-roving-focus`).\n * Returns an `onKeyDown` to spread on the container and the active index; items\n * should set `tabIndex={index === activeIndex ? 0 : -1}`.\n *\n * `count` may be a getter: containers whose items are only known from the DOM\n * (menus with conditionally rendered or disabled items, inline sub-menus) pass\n * one so the bound is read at keydown time — never during render, which keeps\n * the hook safe under SSR.\n *\n * Mirrors the keyboard navigation Rust drives with a focused-index signal.\n */\nexport function useRovingFocus(opts: {\n count: number | (() => number);\n orientation?: RovingOrientation;\n loop?: boolean;\n initial?: number;\n}): {\n activeIndex: number;\n setActiveIndex: (i: number) => void;\n onKeyDown: (event: React.KeyboardEvent) => void;\n} {\n const { count, orientation = \"vertical\", loop = true, initial = 0 } = opts;\n const [activeIndex, setActiveIndex] = React.useState(initial);\n\n const resolveCount = () => (typeof count === \"function\" ? count() : count);\n\n const next = (dir: 1 | -1) => {\n const total = resolveCount();\n if (total <= 0) return;\n setActiveIndex((i) => {\n // Clamp first: the item set may have shrunk since the index was set.\n let n = Math.min(Math.max(i, 0), total - 1) + dir;\n if (n < 0) n = loop ? total - 1 : 0;\n else if (n >= total) n = loop ? 0 : total - 1;\n return n;\n });\n };\n\n const onKeyDown = (event: React.KeyboardEvent) => {\n const horiz = orientation === \"horizontal\" || orientation === \"both\";\n const vert = orientation === \"vertical\" || orientation === \"both\";\n if (vert && event.key === \"ArrowDown\") {\n event.preventDefault();\n next(1);\n } else if (vert && event.key === \"ArrowUp\") {\n event.preventDefault();\n next(-1);\n } else if (horiz && event.key === \"ArrowRight\") {\n event.preventDefault();\n next(1);\n } else if (horiz && event.key === \"ArrowLeft\") {\n event.preventDefault();\n next(-1);\n } else if (event.key === \"Home\") {\n event.preventDefault();\n setActiveIndex(0);\n } else if (event.key === \"End\") {\n event.preventDefault();\n const total = resolveCount();\n if (total > 0) setActiveIndex(total - 1);\n }\n };\n\n return { activeIndex, setActiveIndex, onKeyDown };\n}\n"],"mappings":";AAEA,YAAY,WAAW;AAgBhB,SAAS,eAAe,MAS7B;AACA,QAAM,EAAE,OAAO,cAAc,YAAY,OAAO,MAAM,UAAU,EAAE,IAAI;AACtE,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,OAAO;AAE5D,QAAM,eAAe,MAAO,OAAO,UAAU,aAAa,MAAM,IAAI;AAEpE,QAAM,OAAO,CAAC,QAAgB;AAC5B,UAAM,QAAQ,aAAa;AAC3B,QAAI,SAAS,EAAG;AAChB,mBAAe,CAAC,MAAM;AAEpB,UAAI,IAAI,KAAK,IAAI,KAAK,IAAI,GAAG,CAAC,GAAG,QAAQ,CAAC,IAAI;AAC9C,UAAI,IAAI,EAAG,KAAI,OAAO,QAAQ,IAAI;AAAA,eACzB,KAAK,MAAO,KAAI,OAAO,IAAI,QAAQ;AAC5C,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAEA,QAAM,YAAY,CAAC,UAA+B;AAChD,UAAM,QAAQ,gBAAgB,gBAAgB,gBAAgB;AAC9D,UAAM,OAAO,gBAAgB,cAAc,gBAAgB;AAC3D,QAAI,QAAQ,MAAM,QAAQ,aAAa;AACrC,YAAM,eAAe;AACrB,WAAK,CAAC;AAAA,IACR,WAAW,QAAQ,MAAM,QAAQ,WAAW;AAC1C,YAAM,eAAe;AACrB,WAAK,EAAE;AAAA,IACT,WAAW,SAAS,MAAM,QAAQ,cAAc;AAC9C,YAAM,eAAe;AACrB,WAAK,CAAC;AAAA,IACR,WAAW,SAAS,MAAM,QAAQ,aAAa;AAC7C,YAAM,eAAe;AACrB,WAAK,EAAE;AAAA,IACT,WAAW,MAAM,QAAQ,QAAQ;AAC/B,YAAM,eAAe;AACrB,qBAAe,CAAC;AAAA,IAClB,WAAW,MAAM,QAAQ,OAAO;AAC9B,YAAM,eAAe;AACrB,YAAM,QAAQ,aAAa;AAC3B,UAAI,QAAQ,EAAG,gBAAe,QAAQ,CAAC;AAAA,IACzC;AAAA,EACF;AAEA,SAAO,EAAE,aAAa,gBAAgB,UAAU;AAClD;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evinvest/uikit",
3
- "version": "0.32.0",
3
+ "version": "0.33.0",
4
4
  "description": "Dep-light React UI kit: shadcn-semantics components with zero @radix-ui / cva, mirroring the ev_lib::uikit Rust feature. Ships the shared design tokens.",
5
5
  "license": "BlueOak-1.0.0",
6
6
  "publishConfig": {
package/styles/ev.css CHANGED
@@ -39,7 +39,6 @@
39
39
  --background: #0d0d0f;
40
40
  --card: #151518;
41
41
  --secondary: #1c1c20;
42
- --on-secondary: var(--ink);
43
42
  --popover: #252529;
44
43
  --muted: #070709;
45
44
  --hover: #e6e1d30f;
@@ -63,13 +62,11 @@
63
62
 
64
63
  /* brand and primary. A fill and an ink are different jobs (docs/spec/accents.md):
65
64
  * --primary carries white at 4.62:1 and reads as a shape on every plane (3.30:1
66
- * on the popover); --primary-ink is the teal that reads as text (4.96:1 there).
67
- * Figma's hover #18998b is 3.52:1 under white, so #127c71 (5.06:1) is pinned. */
65
+ * on the popover); --primary-ink is the teal that reads as text (4.96:1 there). */
68
66
  --brand: #0d1627;
69
67
  --primary: #138377;
70
68
  --on-primary: #ffffff;
71
69
  --primary-ink: #35a395;
72
- --primary-hover: #127c71;
73
70
  --primary-border: #35a3958c;
74
71
 
75
72
  /* status. Fills carry the dark label (white fails 3:1–3.4:1 on these); the
@@ -95,29 +92,6 @@
95
92
  --chart-4: #e68aaf;
96
93
  --chart-5: #2e9e5c;
97
94
  --chart-6: #988eef;
98
-
99
- --button-primary-bg: #e6e1d300;
100
- --button-primary-hover: #e6e1d314;
101
- --button-primary-border: #e6e1d399;
102
- --button-primary-ink: #e6e1d3;
103
- --button-danger-bg: #f05b5200;
104
- --button-danger-hover: #f05b521a;
105
- --button-danger-border: #f05b52;
106
- --button-danger-ink: var(--accent-error);
107
-
108
- /* chips are outlines: a transparent fill and the label's hue at 50 % */
109
- --chip-neutral-bg: var(--hover);
110
- --chip-neutral-border: var(--border);
111
- --chip-primary-bg: #35a39500;
112
- --chip-primary-border: #35a3958c;
113
- --chip-positive-bg: #2e9e5c00;
114
- --chip-positive-border: #4abf7680;
115
- --chip-info-bg: #4990d800;
116
- --chip-info-border: #6ca6e480;
117
- --chip-warn-bg: #f5c84e00;
118
- --chip-warn-border: #f5c84e80;
119
- --chip-error-bg: #f05b5200;
120
- --chip-error-border: #f05b5280;
121
95
  }
122
96
 
123
97
  /* ── u2 Light ────────────────────────────────────────────────── */
@@ -127,7 +101,6 @@
127
101
  --background: #efebdf;
128
102
  --card: #f6f3eb;
129
103
  --secondary: #fbfaf6;
130
- --on-secondary: var(--ink);
131
104
  --popover: #ffffff;
132
105
  --muted: #e6e1d3;
133
106
  --hover: #0d0d0f0d;
@@ -149,7 +122,6 @@
149
122
  --primary: #138377;
150
123
  --on-primary: #ffffff;
151
124
  --primary-ink: #116f65;
152
- --primary-hover: #116f65;
153
125
  --primary-border: #116f658c;
154
126
 
155
127
  /* Figma's positive #228a4e carries neither label at 4.5:1 (white 4.36, dark
@@ -178,29 +150,6 @@
178
150
  --chart-4: #b74679;
179
151
  --chart-5: #228a4e;
180
152
  --chart-6: #6d5acc;
181
-
182
- --button-primary-bg: #0d0d0f00;
183
- --button-primary-hover: #0d0d0f0f;
184
- --button-primary-border: #0d0d0fa6;
185
- --button-primary-ink: #080e1a;
186
- --button-danger-bg: #db383400;
187
- --button-danger-hover: #db383414;
188
- --button-danger-border: #db3834;
189
- --button-danger-ink: var(--accent-error);
190
-
191
- /* the solid hue at 50 % (warning 60 %), where Dark uses the ink's */
192
- --chip-neutral-bg: var(--hover);
193
- --chip-neutral-border: var(--border);
194
- --chip-primary-bg: #13837700;
195
- --chip-primary-border: #116f658c;
196
- --chip-positive-bg: #228a4e00;
197
- --chip-positive-border: #228a4e80;
198
- --chip-info-bg: #2d79c200;
199
- --chip-info-border: #2d79c280;
200
- --chip-warn-bg: #a77f1200;
201
- --chip-warn-border: #a77f1299;
202
- --chip-error-bg: #db383400;
203
- --chip-error-border: #db383480;
204
153
  }
205
154
 
206
155
  /* ── Oldschool ───────────────────────────────────────────────── *
@@ -220,7 +169,6 @@
220
169
  --background: #070d18;
221
170
  --card: #16233c;
222
171
  --secondary: #0d1526;
223
- --on-secondary: var(--ink);
224
172
  --popover: #1d2c49;
225
173
  --muted: #26375c;
226
174
  --hover: #e6e1d312;
@@ -242,7 +190,6 @@
242
190
  --primary: #138377;
243
191
  --on-primary: #ffffff;
244
192
  --primary-ink: #35a395;
245
- --primary-hover: #127c71;
246
193
  --primary-border: #35a39500;
247
194
 
248
195
  --positive: #2e9e5c;
@@ -267,31 +214,21 @@
267
214
  --chart-5: #2e9e5c;
268
215
  --chart-6: #988eef;
269
216
 
270
- /* filled buttons. The danger label stays dark: white on #f05b52 is 3.32:1. */
271
- --button-primary-bg: #138377;
272
- --button-primary-hover: #127c71;
273
- --button-primary-border: #138377;
274
- --button-primary-ink: #ffffff;
275
- --button-danger-bg: #f05b52;
276
- --button-danger-hover: #f06c61;
277
- --button-danger-border: #f05b52;
278
- --button-danger-ink: #070d18;
217
+ /* danger is a fill */
218
+ --button-danger-bg: var(--accent-error);
219
+ --button-danger-hover: var(--accent-error-hover);
220
+ --button-danger-border: var(--accent-error);
221
+ --button-danger-ink: var(--on-accent-error);
279
222
 
280
- /* chips are tinted pills with no edge */
281
- --chip-neutral-bg: var(--hover);
282
- --chip-neutral-border: #e6e1d300;
223
+ /* tints are pills with no edge */
224
+ --tint-fill: 18%;
225
+ --tint-edge: 0%;
226
+ --tint-neutral-edge: transparent;
283
227
  /* Figma tints the accent chip 16 %, which leaves its teal label at 4.03:1 on
284
228
  * the card; 8 % holds 4.56:1 */
285
- --chip-primary-bg: #35a39514;
286
- --chip-primary-border: #35a39500;
287
- --chip-positive-bg: #2e9e5c2e;
288
- --chip-positive-border: #4abf7600;
289
- --chip-info-bg: #4990d82e;
290
- --chip-info-border: #6ca6e400;
291
- --chip-warn-bg: #f5c84e29;
292
- --chip-warn-border: #f5c84e00;
293
- --chip-error-bg: #f05b522e;
294
- --chip-error-border: #f06c6100;
229
+ --tint-primary-fill: color-mix(in srgb, var(--primary-ink) 8%, transparent);
230
+ /* at 18 % the info label reads 4.43:1 on its fill; 16 % clears 4.5:1 */
231
+ --tint-info-fill: color-mix(in srgb, var(--accent-info-ink) 16%, transparent);
295
232
 
296
233
  --radius: 0.5rem;
297
234
  --control-radius: 0.375rem;
package/styles/theme.css CHANGED
@@ -664,6 +664,11 @@
664
664
  --radius-lg: var(--radius);
665
665
  --radius-xl: calc(var(--radius) + 4px);
666
666
  --radius-2xl: calc(var(--radius) + 8px);
667
+ /* the roles the kit's tables name; the steps above are the consumer's */
668
+ --radius-control: var(--control-radius);
669
+ --radius-panel: var(--radius);
670
+ --radius-chip: var(--chip-radius);
671
+ --radius-row: calc(var(--radius) - 4px); /* concentric inside a panel's p-1 */
667
672
 
668
673
  /* The names stay, the values are per theme: u2 is flat (`--depth-*` is a
669
674
  * zero shadow), Oldschool brings the ladder back in ev.css. */
@@ -671,25 +676,25 @@
671
676
  --shadow-elevated: var(--depth-elevated);
672
677
  --shadow-overlay: var(--depth-overlay);
673
678
 
674
- /* Type scale: size / line-height in pairs, the Figma text styles. */
675
- --text-caption: 0.6875rem;
676
- --text-caption--line-height: 0.875rem;
677
- --text-body-s: 0.75rem;
678
- --text-body-s--line-height: 1rem;
679
- --text-body: 0.8125rem;
680
- --text-body--line-height: 1.125rem;
681
- --text-body-l: 0.875rem;
682
- --text-body-l--line-height: 1.25rem;
683
- --text-h3: 0.875rem;
684
- --text-h3--line-height: 1.25rem;
685
- --text-h2: 1.5rem;
686
- --text-h2--line-height: 1.875rem;
687
- --text-h1: 2rem;
688
- --text-h1--line-height: 2.375rem;
689
- --text-figure-m: 1.25rem;
690
- --text-figure-m--line-height: 1.5rem;
691
- --text-figure-l: 1.75rem;
692
- --text-figure-l--line-height: 2rem;
679
+ /* Type scale: two even-step ramps off the geometry rule's inputs (docs/spec/roles.md). */
680
+ --text-caption: calc(var(--body-size) - 2 * var(--body-step));
681
+ --text-caption--line-height: calc(var(--body-leading) - 4 * var(--body-step));
682
+ --text-body-s: calc(var(--body-size) - var(--body-step));
683
+ --text-body-s--line-height: calc(var(--body-leading) - 2 * var(--body-step));
684
+ --text-body: var(--body-size);
685
+ --text-body--line-height: var(--body-leading);
686
+ --text-body-l: calc(var(--body-size) + var(--body-step));
687
+ --text-body-l--line-height: calc(var(--body-leading) + 2 * var(--body-step));
688
+ --text-h3: calc(var(--body-size) + var(--body-step));
689
+ --text-h3--line-height: calc(var(--body-leading) + 2 * var(--body-step));
690
+ --text-h2: calc((var(--display-base) + 2 * var(--display-step)) * var(--display-scale));
691
+ --text-h2--line-height: calc((var(--display-base) + 2 * var(--display-step) + var(--display-leading)) * var(--display-scale));
692
+ --text-h1: calc((var(--display-base) + 4 * var(--display-step)) * var(--display-scale));
693
+ --text-h1--line-height: calc((var(--display-base) + 4 * var(--display-step) + var(--display-leading)) * var(--display-scale));
694
+ --text-figure-m: calc((var(--display-base) + var(--display-step)) * var(--display-scale));
695
+ --text-figure-m--line-height: calc((var(--display-base) + var(--display-step) + var(--figure-leading)) * var(--display-scale));
696
+ --text-figure-l: calc((var(--display-base) + 3 * var(--display-step)) * var(--display-scale));
697
+ --text-figure-l--line-height: calc((var(--display-base) + 3 * var(--display-step) + var(--figure-leading)) * var(--display-scale));
693
698
 
694
699
  /* The families are parameters, apart from the colour contract: a brand scope
695
700
  * sets `--brand-font-sans`, `--brand-font-serif` and `--brand-font-mono`
@@ -748,38 +753,51 @@
748
753
  --color-positive-ink: var(--positive-ink);
749
754
  --color-accent-trace: var(--accent-trace);
750
755
  --color-on-accent-trace: var(--on-accent-trace);
756
+ --color-accent-trace-hover: var(--accent-trace-hover);
751
757
  --color-accent-debug: var(--accent-debug);
752
758
  --color-on-accent-debug: var(--on-accent-debug);
759
+ --color-accent-debug-hover: var(--accent-debug-hover);
753
760
  --color-accent-info: var(--accent-info);
754
761
  --color-on-accent-info: var(--on-accent-info);
762
+ --color-accent-info-hover: var(--accent-info-hover);
755
763
  --color-accent-info-ink: var(--accent-info-ink);
756
764
  --color-accent-warn: var(--accent-warn);
757
765
  --color-on-accent-warn: var(--on-accent-warn);
766
+ --color-accent-warn-hover: var(--accent-warn-hover);
758
767
  --color-accent-error: var(--accent-error);
759
768
  --color-on-accent-error: var(--on-accent-error);
769
+ --color-accent-error-hover: var(--accent-error-hover);
760
770
 
761
- /* component faces: derived below, pinned per theme by EV, so `bg-button-primary-bg`
762
- * and `border-chip-error-border` are real utilities the class merge understands */
763
- --color-button-primary-bg: var(--button-primary-bg);
764
- --color-button-primary-hover: var(--button-primary-hover);
765
- --color-button-primary-border: var(--button-primary-border);
766
- --color-button-primary-ink: var(--button-primary-ink);
771
+ /* component faces: derived below, pinned per theme by EV, so `bg-button-danger-bg`
772
+ * and `border-tint-error-edge` are real utilities the class merge understands */
767
773
  --color-button-danger-bg: var(--button-danger-bg);
768
774
  --color-button-danger-hover: var(--button-danger-hover);
769
775
  --color-button-danger-border: var(--button-danger-border);
770
776
  --color-button-danger-ink: var(--button-danger-ink);
771
- --color-chip-neutral-bg: var(--chip-neutral-bg);
772
- --color-chip-neutral-border: var(--chip-neutral-border);
773
- --color-chip-primary-bg: var(--chip-primary-bg);
774
- --color-chip-primary-border: var(--chip-primary-border);
775
- --color-chip-positive-bg: var(--chip-positive-bg);
776
- --color-chip-positive-border: var(--chip-positive-border);
777
- --color-chip-info-bg: var(--chip-info-bg);
778
- --color-chip-info-border: var(--chip-info-border);
779
- --color-chip-warn-bg: var(--chip-warn-bg);
780
- --color-chip-warn-border: var(--chip-warn-border);
781
- --color-chip-error-bg: var(--chip-error-bg);
782
- --color-chip-error-border: var(--chip-error-border);
777
+ --color-tint-neutral-fill: var(--tint-neutral-fill);
778
+ --color-tint-neutral-edge: var(--tint-neutral-edge);
779
+ --color-tint-neutral-wash: var(--tint-neutral-wash);
780
+ --color-tint-primary-fill: var(--tint-primary-fill);
781
+ --color-tint-primary-edge: var(--tint-primary-edge);
782
+ --color-tint-primary-wash: var(--tint-primary-wash);
783
+ --color-tint-positive-fill: var(--tint-positive-fill);
784
+ --color-tint-positive-edge: var(--tint-positive-edge);
785
+ --color-tint-positive-wash: var(--tint-positive-wash);
786
+ --color-tint-info-fill: var(--tint-info-fill);
787
+ --color-tint-info-edge: var(--tint-info-edge);
788
+ --color-tint-info-wash: var(--tint-info-wash);
789
+ --color-tint-warn-fill: var(--tint-warn-fill);
790
+ --color-tint-warn-edge: var(--tint-warn-edge);
791
+ --color-tint-warn-wash: var(--tint-warn-wash);
792
+ --color-tint-error-fill: var(--tint-error-fill);
793
+ --color-tint-error-edge: var(--tint-error-edge);
794
+ --color-tint-error-wash: var(--tint-error-wash);
795
+ --color-tint-trace-fill: var(--tint-trace-fill);
796
+ --color-tint-trace-edge: var(--tint-trace-edge);
797
+ --color-tint-trace-wash: var(--tint-trace-wash);
798
+ --color-tint-debug-fill: var(--tint-debug-fill);
799
+ --color-tint-debug-edge: var(--tint-debug-edge);
800
+ --color-tint-debug-wash: var(--tint-debug-wash);
783
801
 
784
802
  /* charts — distinguishable hues carrying no significance */
785
803
  --color-chart-1: var(--chart-1);
@@ -819,7 +837,14 @@
819
837
  * side — ink over white needs ~47% to clear it, dark lands near 4:1. */
820
838
  --input: color-mix(in srgb, var(--ink) 48%, transparent);
821
839
  --ring: var(--primary-ink);
822
- --primary-hover: color-mix(in srgb, var(--primary) 95%, black);
840
+ --on-secondary: var(--ink);
841
+ /* a solid fill's hover moves away from its label, so the label only gains contrast */
842
+ --primary-hover: color-mix(in srgb, rgb(from var(--on-primary) calc(255 - r) calc(255 - g) calc(255 - b)) var(--tint-hover), var(--primary));
843
+ --accent-trace-hover: color-mix(in srgb, rgb(from var(--on-accent-trace) calc(255 - r) calc(255 - g) calc(255 - b)) var(--tint-hover), var(--accent-trace));
844
+ --accent-debug-hover: color-mix(in srgb, rgb(from var(--on-accent-debug) calc(255 - r) calc(255 - g) calc(255 - b)) var(--tint-hover), var(--accent-debug));
845
+ --accent-info-hover: color-mix(in srgb, rgb(from var(--on-accent-info) calc(255 - r) calc(255 - g) calc(255 - b)) var(--tint-hover), var(--accent-info));
846
+ --accent-warn-hover: color-mix(in srgb, rgb(from var(--on-accent-warn) calc(255 - r) calc(255 - g) calc(255 - b)) var(--tint-hover), var(--accent-warn));
847
+ --accent-error-hover: color-mix(in srgb, rgb(from var(--on-accent-error) calc(255 - r) calc(255 - g) calc(255 - b)) var(--tint-hover), var(--accent-error));
823
848
  --primary-border: color-mix(in srgb, var(--primary-ink) 55%, transparent);
824
849
  /* the quiet status roles as text on a surface: a fill is a shape, and as text
825
850
  * it reads far below AA on a light plane. warn and error have none: they are
@@ -834,31 +859,40 @@
834
859
  * same scopes, listed in another order so the contract reader finds one rule
835
860
  * per selector. */
836
861
  :where([data-theme], [data-brand], .light, .dark, :root) {
837
- /* primary is an outline button, danger the same in the error role */
838
- --button-primary-bg: transparent;
839
- --button-primary-hover: var(--hover);
840
- --button-primary-border: color-mix(in srgb, var(--ink) 60%, transparent);
841
- --button-primary-ink: var(--ink);
862
+ /* danger is an outline in the error role */
842
863
  --button-danger-bg: transparent;
843
- --button-danger-hover: color-mix(in srgb, var(--accent-error) 10%, transparent);
864
+ --button-danger-hover: color-mix(in srgb, var(--accent-error) var(--tint-hover), transparent);
844
865
  --button-danger-border: var(--accent-error);
845
866
  --button-danger-ink: var(--accent-error);
846
867
 
847
- /* chips (Badge, status pills). The source is the colour the label wears, so
848
- * the edge and the label are one hue; --chip-fill and --chip-edge are in the
849
- * geometry. */
850
- --chip-neutral-bg: var(--hover);
851
- --chip-neutral-border: var(--border);
852
- --chip-primary-bg: color-mix(in srgb, var(--primary-ink) var(--chip-fill), transparent);
853
- --chip-primary-border: color-mix(in srgb, var(--primary-ink) var(--chip-edge), transparent);
854
- --chip-positive-bg: color-mix(in srgb, var(--positive-ink) var(--chip-fill), transparent);
855
- --chip-positive-border: color-mix(in srgb, var(--positive-ink) var(--chip-edge), transparent);
856
- --chip-info-bg: color-mix(in srgb, var(--accent-info-ink) var(--chip-fill), transparent);
857
- --chip-info-border: color-mix(in srgb, var(--accent-info-ink) var(--chip-edge), transparent);
858
- --chip-warn-bg: color-mix(in srgb, var(--accent-warn) var(--chip-fill), transparent);
859
- --chip-warn-border: color-mix(in srgb, var(--accent-warn) var(--chip-edge), transparent);
860
- --chip-error-bg: color-mix(in srgb, var(--accent-error) var(--chip-fill), transparent);
861
- --chip-error-border: color-mix(in srgb, var(--accent-error) var(--chip-edge), transparent);
868
+ /* a tone worn as a tint: a chip's fill and edge are the tone's ink at
869
+ * --tint-fill and --tint-edge, so the edge and the label are one hue, and an
870
+ * area's wash is it at --tint-wash. warn and error are their own ink
871
+ * (docs/spec/accents.md). */
872
+ --tint-neutral-fill: var(--hover);
873
+ --tint-neutral-edge: var(--border);
874
+ --tint-neutral-wash: transparent;
875
+ --tint-primary-fill: color-mix(in srgb, var(--primary-ink) var(--tint-fill), transparent);
876
+ --tint-primary-edge: color-mix(in srgb, var(--primary-ink) var(--tint-edge), transparent);
877
+ --tint-primary-wash: color-mix(in srgb, var(--primary-ink) var(--tint-wash), transparent);
878
+ --tint-positive-fill: color-mix(in srgb, var(--positive-ink) var(--tint-fill), transparent);
879
+ --tint-positive-edge: color-mix(in srgb, var(--positive-ink) var(--tint-edge), transparent);
880
+ --tint-positive-wash: color-mix(in srgb, var(--positive-ink) var(--tint-wash), transparent);
881
+ --tint-info-fill: color-mix(in srgb, var(--accent-info-ink) var(--tint-fill), transparent);
882
+ --tint-info-edge: color-mix(in srgb, var(--accent-info-ink) var(--tint-edge), transparent);
883
+ --tint-info-wash: color-mix(in srgb, var(--accent-info-ink) var(--tint-wash), transparent);
884
+ --tint-warn-fill: color-mix(in srgb, var(--accent-warn) var(--tint-fill), transparent);
885
+ --tint-warn-edge: color-mix(in srgb, var(--accent-warn) var(--tint-edge), transparent);
886
+ --tint-warn-wash: color-mix(in srgb, var(--accent-warn) var(--tint-wash), transparent);
887
+ --tint-error-fill: color-mix(in srgb, var(--accent-error) var(--tint-fill), transparent);
888
+ --tint-error-edge: color-mix(in srgb, var(--accent-error) var(--tint-edge), transparent);
889
+ --tint-error-wash: color-mix(in srgb, var(--accent-error) var(--tint-wash), transparent);
890
+ --tint-trace-fill: color-mix(in srgb, var(--accent-trace) var(--tint-fill), transparent);
891
+ --tint-trace-edge: color-mix(in srgb, var(--accent-trace) var(--tint-edge), transparent);
892
+ --tint-trace-wash: color-mix(in srgb, var(--accent-trace) var(--tint-wash), transparent);
893
+ --tint-debug-fill: color-mix(in srgb, var(--accent-debug) var(--tint-fill), transparent);
894
+ --tint-debug-edge: color-mix(in srgb, var(--accent-debug) var(--tint-edge), transparent);
895
+ --tint-debug-wash: color-mix(in srgb, var(--accent-debug) var(--tint-wash), transparent);
862
896
  }
863
897
 
864
898
  /* Geometry. A `[data-brand]` scope is a theme root of its own, so it restarts
@@ -868,22 +902,37 @@
868
902
  --radius: 0.375rem; /* panels and menus */
869
903
  /* control geometry, so a consumer reshapes every button/input at once */
870
904
  --control-radius: 0.25rem;
905
+ --control-h: 2.25rem;
906
+ --control-step: 0.25rem;
907
+ --control-inset: 1rem; /* the md rung's side inset; every other rung's moves with its height */
871
908
  --chip-radius: 0.25rem;
872
- /* how much of its ink a chip's fill and edge take, for a brand that pins
873
- * nothing: an outline by default, a tint where a theme raises the fill */
874
- --chip-fill: 0%;
875
- --chip-edge: 50%;
909
+ --disabled-opacity: 0.5;
910
+ /* how much of its ink a tint's fill and edge take: an outline by default, a
911
+ * tinted pill where a theme raises the fill; and a tone's pointed-at row or face */
912
+ --tint-fill: 0%;
913
+ --tint-edge: 50%;
914
+ --tint-hover: 10%;
915
+ /* how much of its ink an area in a tone (a banner, an alert) is washed with:
916
+ * an area needs a body to read as content, where a chip reads as an outline.
917
+ * At 10 % Light's positive and warn labels fall under their floor. */
918
+ --tint-wash: 6%;
876
919
  /* u2 is flat: depth is surface steps and hairlines. Never `none` — inside
877
920
  * Tailwind's comma-separated box-shadow list it invalidates the whole
878
921
  * declaration, and the focus ring goes with it. */
879
922
  --depth-card: 0 0 #0000;
880
923
  --depth-elevated: 0 0 #0000;
881
924
  --depth-overlay: 0 0 #0000;
882
- --control-py: 0.5rem;
883
- /* the Xl (call-to-action) button reads all three */
925
+ /* the Xl (call-to-action) rung's inset and text */
884
926
  --control-px: 2rem;
885
927
  --control-text: 1rem;
886
- /* multiplies the display type scale (uikit <Display>) */
928
+ /* the type ramps: text steps 1 px, display steps 4 px, a scale over the display tier */
929
+ --body-size: 0.8125rem;
930
+ --body-step: 0.0625rem;
931
+ --body-leading: 1.125rem;
932
+ --display-base: 1rem;
933
+ --display-step: 0.25rem;
934
+ --display-leading: 0.375rem; /* h1, h2 */
935
+ --figure-leading: 0.25rem; /* figure-m, figure-l */
887
936
  --display-scale: 1;
888
937
  /* the vertical rhythm of a full-bleed <Section>; `tight` is the sub-page one */
889
938
  --band-py: 2.25rem;
@@ -927,6 +976,24 @@
927
976
  */
928
977
  }
929
978
 
979
+ /* The control height and inset ramps a `Size` step names, re-derived on every
980
+ * scope that may set `--control-h`. Pin one step to bend the ramp. */
981
+ :where(:root, [data-brand], [data-theme]) {
982
+ --control-h-2xs: calc(var(--control-h) - 3 * var(--control-step));
983
+ --control-h-xs: calc(var(--control-h) - 2 * var(--control-step));
984
+ --control-h-sm: calc(var(--control-h) - var(--control-step));
985
+ --control-h-md: var(--control-h);
986
+ --control-h-lg: calc(var(--control-h) + var(--control-step));
987
+ --control-h-xl: calc(var(--control-h) + 3 * var(--control-step));
988
+ --control-h-touch: max(var(--control-h-lg), 2.75rem); /* the 44 px pointer-target floor (WCAG 2.5.5) */
989
+ --control-inset-xs: calc(var(--control-h-xs) - var(--control-h) + var(--control-inset));
990
+ --control-inset-sm: calc(var(--control-h-sm) - var(--control-h) + var(--control-inset));
991
+ --control-inset-md: var(--control-inset);
992
+ --control-inset-lg: calc(var(--control-h-lg) - var(--control-h) + var(--control-inset));
993
+ --control-inset-xl: var(--control-px);
994
+ --control-inset-touch: calc(var(--control-h-touch) - var(--control-h) + var(--control-inset));
995
+ }
996
+
930
997
  :root {
931
998
  /* shell offset — viewport space reserved by whatever shell hosts the app.
932
999
  Standalone apps get 0; a hosting shell overrides on :root. Size