@elabs-ai/components-ui 4.0.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 (449) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +78 -0
  3. package/dist/index.d.ts +4497 -0
  4. package/dist/index.js +15192 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/lib/cn.d.ts +10 -0
  7. package/dist/lib/cn.js +20 -0
  8. package/dist/lib/cn.js.map +1 -0
  9. package/package.json +106 -0
  10. package/src/blocks/sidebar-02/app-sidebar.tsx +160 -0
  11. package/src/blocks/sidebar-02/logo.tsx +18 -0
  12. package/src/blocks/sidebar-02/nav-main.tsx +13 -0
  13. package/src/blocks/sidebar-02/nav-notifications.tsx +7 -0
  14. package/src/blocks/sidebar-02/sidebar-02.stories.tsx +23 -0
  15. package/src/blocks/sidebar-02/team-switcher.tsx +7 -0
  16. package/src/blocks/sidebar-04/app-sidebar.tsx +236 -0
  17. package/src/blocks/sidebar-04/mail-context.tsx +29 -0
  18. package/src/blocks/sidebar-04/nav-user.tsx +6 -0
  19. package/src/blocks/sidebar-04/sidebar-04.stories.tsx +150 -0
  20. package/src/blocks/sidebar-05/app-sidebar.tsx +375 -0
  21. package/src/blocks/sidebar-05/sidebar-05.stories.tsx +18 -0
  22. package/src/blocks/sidebar-05/team-switcher.tsx +6 -0
  23. package/src/blocks-comparison-table.stories.tsx +165 -0
  24. package/src/components/accordion/accordion.stories.tsx +71 -0
  25. package/src/components/accordion/accordion.test.tsx +18 -0
  26. package/src/components/accordion/accordion.tsx +49 -0
  27. package/src/components/accordion/index.ts +1 -0
  28. package/src/components/advanced-group/advanced-group.stories.tsx +143 -0
  29. package/src/components/advanced-group/advanced-group.test.tsx +81 -0
  30. package/src/components/advanced-group/advanced-group.tsx +111 -0
  31. package/src/components/advanced-group/index.ts +2 -0
  32. package/src/components/alert/alert.stories.tsx +156 -0
  33. package/src/components/alert/alert.test.tsx +57 -0
  34. package/src/components/alert/alert.tsx +91 -0
  35. package/src/components/alert/index.ts +8 -0
  36. package/src/components/alert-dialog/alert-dialog.stories.tsx +73 -0
  37. package/src/components/alert-dialog/alert-dialog.tsx +104 -0
  38. package/src/components/alert-dialog/index.ts +12 -0
  39. package/src/components/app-shell/app-shell.stories.tsx +61 -0
  40. package/src/components/app-shell/app-shell.tsx +31 -0
  41. package/src/components/app-shell/index.ts +1 -0
  42. package/src/components/app-sidebar/app-sidebar.stories.tsx +121 -0
  43. package/src/components/app-sidebar/app-sidebar.test.tsx +55 -0
  44. package/src/components/app-sidebar/app-sidebar.tsx +53 -0
  45. package/src/components/app-sidebar/index.ts +1 -0
  46. package/src/components/aspect-ratio/aspect-ratio.stories.tsx +46 -0
  47. package/src/components/aspect-ratio/aspect-ratio.tsx +3 -0
  48. package/src/components/aspect-ratio/index.ts +1 -0
  49. package/src/components/attribution-panel/attribution-panel.stories.tsx +83 -0
  50. package/src/components/attribution-panel/attribution-panel.test.tsx +123 -0
  51. package/src/components/attribution-panel/attribution-panel.tsx +191 -0
  52. package/src/components/attribution-panel/attribution-types.ts +44 -0
  53. package/src/components/attribution-panel/attributions.generated.ts +1422 -0
  54. package/src/components/attribution-panel/index.ts +3 -0
  55. package/src/components/avatar/avatar.stories.tsx +45 -0
  56. package/src/components/avatar/avatar.test.tsx +13 -0
  57. package/src/components/avatar/avatar.tsx +46 -0
  58. package/src/components/avatar/index.ts +1 -0
  59. package/src/components/badge/badge.stories.tsx +44 -0
  60. package/src/components/badge/badge.test.tsx +10 -0
  61. package/src/components/badge/badge.tsx +28 -0
  62. package/src/components/badge/index.ts +1 -0
  63. package/src/components/bento-grid/bento-grid.stories.tsx +165 -0
  64. package/src/components/bento-grid/bento-grid.test.tsx +119 -0
  65. package/src/components/bento-grid/bento-grid.tsx +339 -0
  66. package/src/components/bento-grid/index.ts +9 -0
  67. package/src/components/bounded-number/bounded-number.stories.tsx +118 -0
  68. package/src/components/bounded-number/bounded-number.test.tsx +73 -0
  69. package/src/components/bounded-number/bounded-number.tsx +110 -0
  70. package/src/components/bounded-number/index.ts +1 -0
  71. package/src/components/breadcrumb/breadcrumb.stories.tsx +47 -0
  72. package/src/components/breadcrumb/breadcrumb.test.tsx +17 -0
  73. package/src/components/breadcrumb/breadcrumb.tsx +86 -0
  74. package/src/components/breadcrumb/index.ts +9 -0
  75. package/src/components/button/button.stories.tsx +70 -0
  76. package/src/components/button/button.test.tsx +50 -0
  77. package/src/components/button/button.tsx +63 -0
  78. package/src/components/button/index.ts +1 -0
  79. package/src/components/button-group/button-group.stories.tsx +45 -0
  80. package/src/components/button-group/button-group.tsx +70 -0
  81. package/src/components/button-group/index.ts +6 -0
  82. package/src/components/calendar/calendar.stories.tsx +95 -0
  83. package/src/components/calendar/calendar.test.tsx +140 -0
  84. package/src/components/calendar/calendar.tsx +70 -0
  85. package/src/components/calendar/index.ts +1 -0
  86. package/src/components/card/card.stories.tsx +438 -0
  87. package/src/components/card/card.test.tsx +153 -0
  88. package/src/components/card/card.tsx +332 -0
  89. package/src/components/card/index.ts +13 -0
  90. package/src/components/carousel/carousel.stories.tsx +102 -0
  91. package/src/components/carousel/carousel.tsx +225 -0
  92. package/src/components/carousel/index.ts +8 -0
  93. package/src/components/change-review/change-review.stories.tsx +288 -0
  94. package/src/components/change-review/change-review.test.tsx +261 -0
  95. package/src/components/change-review/change-review.tsx +637 -0
  96. package/src/components/change-review/index.ts +23 -0
  97. package/src/components/checkbox/checkbox.stories.tsx +119 -0
  98. package/src/components/checkbox/checkbox.test.tsx +40 -0
  99. package/src/components/checkbox/checkbox.tsx +34 -0
  100. package/src/components/checkbox/index.ts +1 -0
  101. package/src/components/collapsible/collapsible.stories.tsx +62 -0
  102. package/src/components/collapsible/collapsible.tsx +22 -0
  103. package/src/components/collapsible/index.ts +1 -0
  104. package/src/components/collapsible-panel/index.ts +8 -0
  105. package/src/components/collapsible-panel/use-collapsible-panel.test.tsx +93 -0
  106. package/src/components/collapsible-panel/use-collapsible-panel.ts +159 -0
  107. package/src/components/color-picker/color-picker.stories.tsx +93 -0
  108. package/src/components/color-picker/color-picker.test.tsx +79 -0
  109. package/src/components/color-picker/color-picker.tsx +341 -0
  110. package/src/components/color-picker/index.ts +1 -0
  111. package/src/components/combobox/combobox.stories.tsx +154 -0
  112. package/src/components/combobox/combobox.test.tsx +246 -0
  113. package/src/components/combobox/combobox.tsx +210 -0
  114. package/src/components/combobox/index.ts +1 -0
  115. package/src/components/command/command.stories.tsx +371 -0
  116. package/src/components/command/command.test.tsx +280 -0
  117. package/src/components/command/command.tsx +301 -0
  118. package/src/components/command/index.ts +13 -0
  119. package/src/components/confirm-dialog/confirm-dialog.stories.tsx +183 -0
  120. package/src/components/confirm-dialog/confirm-dialog.test.tsx +147 -0
  121. package/src/components/confirm-dialog/confirm-dialog.tsx +146 -0
  122. package/src/components/confirm-dialog/index.ts +2 -0
  123. package/src/components/context-menu/context-menu.stories.tsx +70 -0
  124. package/src/components/context-menu/context-menu.tsx +172 -0
  125. package/src/components/context-menu/index.ts +16 -0
  126. package/src/components/copyable-value/copyable-value.stories.tsx +80 -0
  127. package/src/components/copyable-value/copyable-value.test.tsx +128 -0
  128. package/src/components/copyable-value/copyable-value.tsx +101 -0
  129. package/src/components/copyable-value/index.ts +1 -0
  130. package/src/components/date-picker/date-picker.stories.tsx +42 -0
  131. package/src/components/date-picker/date-picker.tsx +49 -0
  132. package/src/components/date-picker/index.ts +1 -0
  133. package/src/components/date-range-picker/date-range-picker.stories.tsx +151 -0
  134. package/src/components/date-range-picker/date-range-picker.test.tsx +62 -0
  135. package/src/components/date-range-picker/date-range-picker.tsx +126 -0
  136. package/src/components/date-range-picker/index.ts +6 -0
  137. package/src/components/descriptions/descriptions.stories.tsx +43 -0
  138. package/src/components/descriptions/descriptions.test.tsx +41 -0
  139. package/src/components/descriptions/descriptions.tsx +95 -0
  140. package/src/components/descriptions/index.ts +6 -0
  141. package/src/components/dialog/dialog.stories.tsx +728 -0
  142. package/src/components/dialog/dialog.test.tsx +285 -0
  143. package/src/components/dialog/dialog.tsx +340 -0
  144. package/src/components/dialog/index.ts +18 -0
  145. package/src/components/dialog/use-dialog-dismiss-guard.test.tsx +80 -0
  146. package/src/components/dialog/use-dialog-dismiss-guard.ts +109 -0
  147. package/src/components/drawer/drawer.stories.tsx +76 -0
  148. package/src/components/drawer/drawer.tsx +85 -0
  149. package/src/components/drawer/index.ts +11 -0
  150. package/src/components/dropdown-menu/dropdown-menu.stories.tsx +176 -0
  151. package/src/components/dropdown-menu/dropdown-menu.test.tsx +109 -0
  152. package/src/components/dropdown-menu/dropdown-menu.tsx +179 -0
  153. package/src/components/dropdown-menu/index.ts +17 -0
  154. package/src/components/empty-state/empty-state.stories.tsx +54 -0
  155. package/src/components/empty-state/empty-state.test.tsx +11 -0
  156. package/src/components/empty-state/empty-state.tsx +30 -0
  157. package/src/components/empty-state/index.ts +1 -0
  158. package/src/components/error-state/error-state.stories.tsx +51 -0
  159. package/src/components/error-state/error-state.tsx +27 -0
  160. package/src/components/error-state/index.ts +1 -0
  161. package/src/components/expand-dialog/expand-dialog.stories.tsx +246 -0
  162. package/src/components/expand-dialog/expand-dialog.test.tsx +185 -0
  163. package/src/components/expand-dialog/expand-dialog.tsx +387 -0
  164. package/src/components/expand-dialog/index.ts +11 -0
  165. package/src/components/field-row/field-row.stories.tsx +114 -0
  166. package/src/components/field-row/field-row.test.tsx +188 -0
  167. package/src/components/field-row/field-row.tsx +115 -0
  168. package/src/components/field-row/index.ts +1 -0
  169. package/src/components/file-upload/file-upload.stories.tsx +183 -0
  170. package/src/components/file-upload/file-upload.test.tsx +169 -0
  171. package/src/components/file-upload/file-upload.tsx +459 -0
  172. package/src/components/file-upload/index.ts +13 -0
  173. package/src/components/form/form.stories.tsx +71 -0
  174. package/src/components/form/form.tsx +141 -0
  175. package/src/components/form/index.ts +10 -0
  176. package/src/components/hover-card/hover-card.stories.tsx +57 -0
  177. package/src/components/hover-card/hover-card.tsx +25 -0
  178. package/src/components/hover-card/index.ts +1 -0
  179. package/src/components/icon-button/icon-button.stories.tsx +108 -0
  180. package/src/components/icon-button/icon-button.test.tsx +93 -0
  181. package/src/components/icon-button/icon-button.tsx +139 -0
  182. package/src/components/icon-button/index.ts +1 -0
  183. package/src/components/input/index.ts +1 -0
  184. package/src/components/input/input.stories.tsx +44 -0
  185. package/src/components/input/input.test.tsx +19 -0
  186. package/src/components/input/input.tsx +26 -0
  187. package/src/components/input-group/index.ts +11 -0
  188. package/src/components/input-group/input-group.stories.tsx +87 -0
  189. package/src/components/input-group/input-group.test.tsx +48 -0
  190. package/src/components/input-group/input-group.tsx +170 -0
  191. package/src/components/input-otp/index.ts +1 -0
  192. package/src/components/input-otp/input-otp.stories.tsx +66 -0
  193. package/src/components/input-otp/input-otp.tsx +68 -0
  194. package/src/components/kbd/index.ts +1 -0
  195. package/src/components/kbd/kbd.stories.tsx +45 -0
  196. package/src/components/kbd/kbd.tsx +20 -0
  197. package/src/components/key-value-editor/index.ts +1 -0
  198. package/src/components/key-value-editor/key-value-editor.stories.tsx +130 -0
  199. package/src/components/key-value-editor/key-value-editor.test.tsx +231 -0
  200. package/src/components/key-value-editor/key-value-editor.tsx +263 -0
  201. package/src/components/label/index.ts +1 -0
  202. package/src/components/label/label.stories.tsx +35 -0
  203. package/src/components/label/label.tsx +19 -0
  204. package/src/components/link-preview/index.ts +2 -0
  205. package/src/components/link-preview/link-preview.stories.tsx +178 -0
  206. package/src/components/link-preview/link-preview.test.tsx +123 -0
  207. package/src/components/link-preview/link-preview.tsx +194 -0
  208. package/src/components/list-editor/index.ts +1 -0
  209. package/src/components/list-editor/list-editor.stories.tsx +119 -0
  210. package/src/components/list-editor/list-editor.test.tsx +96 -0
  211. package/src/components/list-editor/list-editor.tsx +192 -0
  212. package/src/components/loading-state/index.ts +1 -0
  213. package/src/components/loading-state/loading-state.stories.tsx +38 -0
  214. package/src/components/loading-state/loading-state.tsx +26 -0
  215. package/src/components/locale-provider/index.ts +8 -0
  216. package/src/components/locale-provider/locale-provider.stories.tsx +83 -0
  217. package/src/components/locale-provider/locale-provider.test.tsx +238 -0
  218. package/src/components/locale-provider/locale-provider.tsx +187 -0
  219. package/src/components/locale-provider/messages.ts +300 -0
  220. package/src/components/match-highlight/index.ts +7 -0
  221. package/src/components/match-highlight/match-highlight.stories.tsx +146 -0
  222. package/src/components/match-highlight/match-highlight.test.tsx +202 -0
  223. package/src/components/match-highlight/match-highlight.tsx +145 -0
  224. package/src/components/mention-input/index.ts +28 -0
  225. package/src/components/mention-input/mention-input-mirror.stories.tsx +171 -0
  226. package/src/components/mention-input/mention-input.stories.tsx +463 -0
  227. package/src/components/mention-input/mention-input.test.tsx +617 -0
  228. package/src/components/mention-input/mention-input.tsx +1102 -0
  229. package/src/components/mention-input/mention-value.test.ts +253 -0
  230. package/src/components/mention-input/mention-value.ts +193 -0
  231. package/src/components/menubar/index.ts +17 -0
  232. package/src/components/menubar/menubar.stories.tsx +67 -0
  233. package/src/components/menubar/menubar.tsx +198 -0
  234. package/src/components/metric-card/index.ts +1 -0
  235. package/src/components/metric-card/metric-card.stories.tsx +224 -0
  236. package/src/components/metric-card/metric-card.test.tsx +198 -0
  237. package/src/components/metric-card/metric-card.tsx +229 -0
  238. package/src/components/model-picker/index.ts +11 -0
  239. package/src/components/model-picker/model-picker-state.ts +85 -0
  240. package/src/components/model-picker/model-picker.stories.tsx +128 -0
  241. package/src/components/model-picker/model-picker.test.tsx +241 -0
  242. package/src/components/model-picker/model-picker.tsx +270 -0
  243. package/src/components/nav-main/index.ts +1 -0
  244. package/src/components/nav-main/nav-main.tsx +147 -0
  245. package/src/components/nav-notifications/index.ts +5 -0
  246. package/src/components/nav-notifications/nav-notifications.test.tsx +15 -0
  247. package/src/components/nav-notifications/nav-notifications.tsx +70 -0
  248. package/src/components/nav-user/index.ts +1 -0
  249. package/src/components/nav-user/nav-user.test.tsx +29 -0
  250. package/src/components/nav-user/nav-user.tsx +114 -0
  251. package/src/components/navigation-menu/index.ts +10 -0
  252. package/src/components/navigation-menu/navigation-menu.stories.tsx +92 -0
  253. package/src/components/navigation-menu/navigation-menu.tsx +99 -0
  254. package/src/components/number-input/index.ts +1 -0
  255. package/src/components/number-input/number-input.stories.tsx +120 -0
  256. package/src/components/number-input/number-input.test.tsx +96 -0
  257. package/src/components/number-input/number-input.tsx +219 -0
  258. package/src/components/page-shell/index.ts +1 -0
  259. package/src/components/page-shell/page-shell.stories.tsx +105 -0
  260. package/src/components/page-shell/page-shell.test.tsx +57 -0
  261. package/src/components/page-shell/page-shell.tsx +62 -0
  262. package/src/components/pagination/index.ts +9 -0
  263. package/src/components/pagination/pagination.stories.tsx +57 -0
  264. package/src/components/pagination/pagination.tsx +77 -0
  265. package/src/components/popover/index.ts +1 -0
  266. package/src/components/popover/popover.stories.tsx +28 -0
  267. package/src/components/popover/popover.test.tsx +16 -0
  268. package/src/components/popover/popover.tsx +29 -0
  269. package/src/components/progress/index.ts +1 -0
  270. package/src/components/progress/progress.stories.tsx +80 -0
  271. package/src/components/progress/progress.test.tsx +35 -0
  272. package/src/components/progress/progress.tsx +50 -0
  273. package/src/components/radio-group/index.ts +1 -0
  274. package/src/components/radio-group/radio-group.stories.tsx +75 -0
  275. package/src/components/radio-group/radio-group.test.tsx +16 -0
  276. package/src/components/radio-group/radio-group.tsx +33 -0
  277. package/src/components/rating/index.ts +1 -0
  278. package/src/components/rating/rating.stories.tsx +111 -0
  279. package/src/components/rating/rating.test.tsx +41 -0
  280. package/src/components/rating/rating.tsx +226 -0
  281. package/src/components/resizable/index.ts +1 -0
  282. package/src/components/resizable/resizable.stories.tsx +25 -0
  283. package/src/components/resizable/resizable.tsx +40 -0
  284. package/src/components/reveal/index.ts +7 -0
  285. package/src/components/reveal/reveal.stories.tsx +90 -0
  286. package/src/components/reveal/reveal.test.tsx +43 -0
  287. package/src/components/reveal/reveal.tsx +95 -0
  288. package/src/components/revision-timeline/index.ts +10 -0
  289. package/src/components/revision-timeline/revision-timeline.stories.tsx +785 -0
  290. package/src/components/revision-timeline/revision-timeline.test.tsx +509 -0
  291. package/src/components/revision-timeline/revision-timeline.tsx +1107 -0
  292. package/src/components/scroll-area/index.ts +1 -0
  293. package/src/components/scroll-area/scroll-area.stories.tsx +45 -0
  294. package/src/components/scroll-area/scroll-area.tsx +43 -0
  295. package/src/components/section-header/index.ts +1 -0
  296. package/src/components/section-header/section-header.stories.tsx +20 -0
  297. package/src/components/section-header/section-header.tsx +36 -0
  298. package/src/components/segmented-field/index.ts +5 -0
  299. package/src/components/segmented-field/segmented-field.stories.tsx +121 -0
  300. package/src/components/segmented-field/segmented-field.test.tsx +163 -0
  301. package/src/components/segmented-field/segmented-field.tsx +235 -0
  302. package/src/components/select/index.ts +10 -0
  303. package/src/components/select/select.stories.tsx +106 -0
  304. package/src/components/select/select.test.tsx +98 -0
  305. package/src/components/select/select.tsx +230 -0
  306. package/src/components/separator/index.ts +2 -0
  307. package/src/components/separator/separator.stories.tsx +87 -0
  308. package/src/components/separator/separator.tsx +51 -0
  309. package/src/components/sheet/index.ts +12 -0
  310. package/src/components/sheet/sheet.stories.tsx +65 -0
  311. package/src/components/sheet/sheet.tsx +109 -0
  312. package/src/components/sidebar/index.ts +27 -0
  313. package/src/components/sidebar/sidebar.stories.tsx +69 -0
  314. package/src/components/sidebar/sidebar.test.tsx +43 -0
  315. package/src/components/sidebar/sidebar.tsx +688 -0
  316. package/src/components/skeleton/index.ts +1 -0
  317. package/src/components/skeleton/skeleton.stories.tsx +28 -0
  318. package/src/components/skeleton/skeleton.tsx +15 -0
  319. package/src/components/slider/index.ts +1 -0
  320. package/src/components/slider/slider.stories.tsx +211 -0
  321. package/src/components/slider/slider.test.tsx +168 -0
  322. package/src/components/slider/slider.tsx +136 -0
  323. package/src/components/slider-number/index.ts +1 -0
  324. package/src/components/slider-number/slider-number.stories.tsx +123 -0
  325. package/src/components/slider-number/slider-number.test.tsx +165 -0
  326. package/src/components/slider-number/slider-number.tsx +191 -0
  327. package/src/components/sonner/index.ts +1 -0
  328. package/src/components/sonner/sonner.stories.tsx +124 -0
  329. package/src/components/sonner/sonner.test.tsx +76 -0
  330. package/src/components/sonner/sonner.tsx +101 -0
  331. package/src/components/spinner/index.ts +1 -0
  332. package/src/components/spinner/spinner.stories.tsx +27 -0
  333. package/src/components/spinner/spinner.tsx +19 -0
  334. package/src/components/split-panel/index.ts +1 -0
  335. package/src/components/split-panel/split-panel.stories.tsx +60 -0
  336. package/src/components/split-panel/split-panel.test.tsx +38 -0
  337. package/src/components/split-panel/split-panel.tsx +95 -0
  338. package/src/components/state-panel/index.ts +1 -0
  339. package/src/components/state-panel/state-panel.stories.tsx +80 -0
  340. package/src/components/state-panel/state-panel.test.tsx +64 -0
  341. package/src/components/state-panel/state-panel.tsx +167 -0
  342. package/src/components/status-badge/index.ts +18 -0
  343. package/src/components/status-badge/status-badge.stories.tsx +132 -0
  344. package/src/components/status-badge/status-badge.test.tsx +189 -0
  345. package/src/components/status-badge/status-badge.tsx +330 -0
  346. package/src/components/switch/index.ts +1 -0
  347. package/src/components/switch/switch.stories.tsx +58 -0
  348. package/src/components/switch/switch.test.tsx +12 -0
  349. package/src/components/switch/switch.tsx +29 -0
  350. package/src/components/table/index.ts +10 -0
  351. package/src/components/table/table.stories.tsx +53 -0
  352. package/src/components/table/table.test.tsx +17 -0
  353. package/src/components/table/table.tsx +106 -0
  354. package/src/components/tabs/index.ts +1 -0
  355. package/src/components/tabs/tabs.stories.tsx +267 -0
  356. package/src/components/tabs/tabs.test.tsx +94 -0
  357. package/src/components/tabs/tabs.tsx +170 -0
  358. package/src/components/tag-input/index.ts +1 -0
  359. package/src/components/tag-input/tag-input.stories.tsx +120 -0
  360. package/src/components/tag-input/tag-input.test.tsx +97 -0
  361. package/src/components/tag-input/tag-input.tsx +253 -0
  362. package/src/components/team-switcher/index.ts +1 -0
  363. package/src/components/team-switcher/team-switcher.test.tsx +39 -0
  364. package/src/components/team-switcher/team-switcher.tsx +100 -0
  365. package/src/components/textarea/index.ts +1 -0
  366. package/src/components/textarea/textarea.stories.tsx +42 -0
  367. package/src/components/textarea/textarea.tsx +24 -0
  368. package/src/components/theme-switcher/index.ts +2 -0
  369. package/src/components/theme-switcher/theme-switcher.stories.tsx +146 -0
  370. package/src/components/theme-switcher/theme-switcher.test.tsx +253 -0
  371. package/src/components/theme-switcher/theme-switcher.tsx +280 -0
  372. package/src/components/theme-switcher/use-theme-transition.ts +62 -0
  373. package/src/components/timeline/index.ts +13 -0
  374. package/src/components/timeline/timeline.stories.tsx +99 -0
  375. package/src/components/timeline/timeline.test.tsx +196 -0
  376. package/src/components/timeline/timeline.tsx +188 -0
  377. package/src/components/toggle/index.ts +1 -0
  378. package/src/components/toggle/toggle.stories.tsx +116 -0
  379. package/src/components/toggle/toggle.test.tsx +119 -0
  380. package/src/components/toggle/toggle.tsx +64 -0
  381. package/src/components/toggle-group/index.ts +1 -0
  382. package/src/components/toggle-group/toggle-group.stories.tsx +110 -0
  383. package/src/components/toggle-group/toggle-group.tsx +59 -0
  384. package/src/components/toolbar/index.ts +14 -0
  385. package/src/components/toolbar/toolbar.stories.tsx +126 -0
  386. package/src/components/toolbar/toolbar.test.tsx +73 -0
  387. package/src/components/toolbar/toolbar.tsx +177 -0
  388. package/src/components/tooltip/index.ts +1 -0
  389. package/src/components/tooltip/tooltip.stories.tsx +67 -0
  390. package/src/components/tooltip/tooltip.tsx +27 -0
  391. package/src/components/top-nav/index.ts +1 -0
  392. package/src/components/top-nav/top-nav.stories.tsx +18 -0
  393. package/src/components/top-nav/top-nav.tsx +34 -0
  394. package/src/components/transfer/index.ts +2 -0
  395. package/src/components/transfer/transfer.stories.tsx +162 -0
  396. package/src/components/transfer/transfer.test.tsx +177 -0
  397. package/src/components/transfer/transfer.tsx +481 -0
  398. package/src/components/tree/index.ts +6 -0
  399. package/src/components/tree/tree.stories.tsx +618 -0
  400. package/src/components/tree/tree.test.tsx +699 -0
  401. package/src/components/tree/tree.tsx +1149 -0
  402. package/src/components/tree/use-tree-keyboard.ts +314 -0
  403. package/src/components/tree-select/index.ts +1 -0
  404. package/src/components/tree-select/tree-select.stories.tsx +127 -0
  405. package/src/components/tree-select/tree-select.test.tsx +40 -0
  406. package/src/components/tree-select/tree-select.tsx +137 -0
  407. package/src/components/typography/index.ts +31 -0
  408. package/src/components/typography/prose.test.tsx +48 -0
  409. package/src/components/typography/prose.tsx +194 -0
  410. package/src/components/typography/typography.stories.tsx +244 -0
  411. package/src/components/typography/typography.test.tsx +140 -0
  412. package/src/components/typography/typography.tsx +126 -0
  413. package/src/components/view-toolbar/index.ts +10 -0
  414. package/src/components/view-toolbar/view-toolbar.stories.tsx +342 -0
  415. package/src/components/view-toolbar/view-toolbar.test.tsx +262 -0
  416. package/src/components/view-toolbar/view-toolbar.tsx +320 -0
  417. package/src/components/virtual-select/index.ts +2 -0
  418. package/src/components/virtual-select/use-virtual-listbox.ts +132 -0
  419. package/src/components/virtual-select/virtual-select.stories.tsx +203 -0
  420. package/src/components/virtual-select/virtual-select.test.tsx +106 -0
  421. package/src/components/virtual-select/virtual-select.tsx +361 -0
  422. package/src/components/wizard/index.ts +12 -0
  423. package/src/components/wizard/wizard.stories.tsx +148 -0
  424. package/src/components/wizard/wizard.test.tsx +67 -0
  425. package/src/components/wizard/wizard.tsx +354 -0
  426. package/src/decoration.stories.tsx +236 -0
  427. package/src/index.ts +174 -0
  428. package/src/lib/cn.test.ts +39 -0
  429. package/src/lib/cn.ts +31 -0
  430. package/src/lib/compact-number.ts +61 -0
  431. package/src/lib/document-address.test.ts +75 -0
  432. package/src/lib/document-address.ts +265 -0
  433. package/src/lib/download.ts +93 -0
  434. package/src/lib/file-icon.test.ts +45 -0
  435. package/src/lib/file-icon.ts +58 -0
  436. package/src/lib/file-kind.test.ts +98 -0
  437. package/src/lib/file-kind.ts +303 -0
  438. package/src/lib/file-source.test.ts +217 -0
  439. package/src/lib/file-source.ts +269 -0
  440. package/src/lib/merge-refs.test.ts +37 -0
  441. package/src/lib/merge-refs.ts +17 -0
  442. package/src/lib/streamdown-translations.ts +99 -0
  443. package/src/lib/use-copy-to-clipboard.ts +75 -0
  444. package/src/lib/use-mobile.ts +16 -0
  445. package/src/motion.stories.tsx +266 -0
  446. package/src/taste-profile.test.tsx +252 -0
  447. package/src/templates-object-detail-hub.stories.tsx +466 -0
  448. package/src/templates-screen-states.stories.tsx +306 -0
  449. package/src/templates-settings.stories.tsx +245 -0
@@ -0,0 +1,354 @@
1
+ "use client";
2
+ // a2ui.exposed: no
3
+
4
+ import {
5
+ createContext,
6
+ forwardRef,
7
+ use,
8
+ useCallback,
9
+ useEffect,
10
+ useId,
11
+ useMemo,
12
+ useRef,
13
+ useState,
14
+ type HTMLAttributes,
15
+ type ReactNode,
16
+ } from "react";
17
+ import { cva, type VariantProps } from "class-variance-authority";
18
+ import { Check } from "lucide-react";
19
+ import { cn } from "../../lib/cn";
20
+ import { Button } from "../button";
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Types & context
24
+ // ---------------------------------------------------------------------------
25
+
26
+ export interface WizardStepMeta {
27
+ /** Stable id for the step. */
28
+ id: string;
29
+ /** Title shown in the step indicator. */
30
+ title: string;
31
+ /** Optional supporting text shown under the title. */
32
+ description?: string;
33
+ }
34
+
35
+ /** A step validator — return false (or a rejected/`false` promise) to block advance. */
36
+ export type WizardValidator = () => boolean | Promise<boolean>;
37
+
38
+ interface WizardContextValue {
39
+ baseId: string;
40
+ step: number;
41
+ steps: WizardStepMeta[];
42
+ count: number;
43
+ isFirst: boolean;
44
+ isLast: boolean;
45
+ validating: boolean;
46
+ goTo: (index: number) => void;
47
+ next: () => void;
48
+ prev: () => void;
49
+ setActiveValidator: (validator: WizardValidator | undefined) => void;
50
+ orientation: "horizontal" | "vertical";
51
+ }
52
+
53
+ const WizardContext = createContext<WizardContextValue | null>(null);
54
+
55
+ function useWizard(): WizardContextValue {
56
+ const ctx = use(WizardContext);
57
+ if (!ctx) throw new Error("Wizard subcomponents must be used inside <Wizard>.");
58
+ return ctx;
59
+ }
60
+
61
+ const wizardVariants = cva("flex gap-6", {
62
+ variants: {
63
+ orientation: {
64
+ horizontal: "flex-col",
65
+ vertical: "flex-row",
66
+ },
67
+ },
68
+ defaultVariants: {
69
+ orientation: "horizontal",
70
+ },
71
+ });
72
+
73
+ // ---------------------------------------------------------------------------
74
+ // Wizard (Provider / root)
75
+ // ---------------------------------------------------------------------------
76
+
77
+ export interface WizardProps
78
+ extends Omit<HTMLAttributes<HTMLDivElement>, "onChange">, VariantProps<typeof wizardVariants> {
79
+ /** The ordered step metadata used to render the indicator. */
80
+ steps: WizardStepMeta[];
81
+ /** Controlled active step index. */
82
+ step?: number;
83
+ /** Uncontrolled initial step index. Default 0. */
84
+ defaultStep?: number;
85
+ /** Called whenever the active step changes. */
86
+ onStepChange?: (step: number) => void;
87
+ children?: ReactNode;
88
+ }
89
+
90
+ /**
91
+ * Wizard — a multi-step flow with a step indicator, per-step validation gating,
92
+ * and Back/Next/Finish navigation.
93
+ *
94
+ * Controlled via `step`/`onStepChange`; uncontrolled via `defaultStep`. State is
95
+ * lifted into the provider and exposed to `WizardSteps`/`WizardStep`/`WizardNav`
96
+ * via context. `Next` runs the active step's `validate` (sync or async) before
97
+ * advancing and stays enabled until validation runs.
98
+ */
99
+ export const Wizard = forwardRef<HTMLDivElement, WizardProps>(function Wizard(
100
+ {
101
+ steps,
102
+ step: stepProp,
103
+ defaultStep = 0,
104
+ onStepChange,
105
+ orientation = "horizontal",
106
+ className,
107
+ children,
108
+ ...props
109
+ },
110
+ ref,
111
+ ) {
112
+ const baseId = useId();
113
+ const isControlled = stepProp !== undefined;
114
+ const [internalStep, setInternalStep] = useState(defaultStep);
115
+ const [validating, setValidating] = useState(false);
116
+ const activeValidator = useRef<WizardValidator | undefined>(undefined);
117
+
118
+ const count = steps.length;
119
+ const step = Math.min(isControlled ? (stepProp ?? 0) : internalStep, Math.max(0, count - 1));
120
+
121
+ const commit = useCallback(
122
+ (next: number) => {
123
+ const clamped = Math.min(Math.max(0, next), Math.max(0, count - 1));
124
+ if (!isControlled) setInternalStep(clamped);
125
+ onStepChange?.(clamped);
126
+ },
127
+ [isControlled, onStepChange, count],
128
+ );
129
+
130
+ const goTo = useCallback(
131
+ (index: number) => {
132
+ // Backward / same navigation never requires validation.
133
+ if (index <= step) {
134
+ commit(index);
135
+ }
136
+ },
137
+ [step, commit],
138
+ );
139
+
140
+ const next = useCallback(() => {
141
+ const validator = activeValidator.current;
142
+ const advance = () => commit(step + 1);
143
+ if (!validator) {
144
+ advance();
145
+ return;
146
+ }
147
+ const result = validator();
148
+ if (typeof result === "boolean") {
149
+ if (result) advance();
150
+ return;
151
+ }
152
+ setValidating(true);
153
+ result
154
+ .then((ok) => {
155
+ if (ok) advance();
156
+ })
157
+ .finally(() => setValidating(false));
158
+ }, [step, commit]);
159
+
160
+ const prev = useCallback(() => commit(step - 1), [step, commit]);
161
+
162
+ const setActiveValidator = useCallback((validator: WizardValidator | undefined) => {
163
+ activeValidator.current = validator;
164
+ }, []);
165
+
166
+ const ctx = useMemo<WizardContextValue>(
167
+ () => ({
168
+ baseId,
169
+ step,
170
+ steps,
171
+ count,
172
+ isFirst: step === 0,
173
+ isLast: step === count - 1,
174
+ validating,
175
+ goTo,
176
+ next,
177
+ prev,
178
+ setActiveValidator,
179
+ orientation: orientation ?? "horizontal",
180
+ }),
181
+ [baseId, step, steps, count, validating, goTo, next, prev, setActiveValidator, orientation],
182
+ );
183
+
184
+ return (
185
+ <WizardContext value={ctx}>
186
+ <div
187
+ ref={ref}
188
+ data-slot="wizard"
189
+ className={cn(wizardVariants({ orientation }), className)}
190
+ {...props}
191
+ >
192
+ {children}
193
+ </div>
194
+ </WizardContext>
195
+ );
196
+ });
197
+
198
+ // ---------------------------------------------------------------------------
199
+ // WizardSteps (the indicator)
200
+ // ---------------------------------------------------------------------------
201
+
202
+ export type WizardStepsProps = HTMLAttributes<HTMLOListElement>;
203
+
204
+ /** WizardSteps — the ordered step indicator. Reads steps from context. */
205
+ export const WizardSteps = forwardRef<HTMLOListElement, WizardStepsProps>(function WizardSteps(
206
+ { className, ...props },
207
+ ref,
208
+ ) {
209
+ const { baseId, step, steps, orientation, goTo } = useWizard();
210
+ return (
211
+ <ol
212
+ ref={ref}
213
+ className={cn(
214
+ "flex gap-2",
215
+ orientation === "vertical" ? "flex-col" : "flex-row flex-wrap items-center",
216
+ className,
217
+ )}
218
+ {...props}
219
+ >
220
+ {steps.map((meta, index) => {
221
+ const isActive = index === step;
222
+ const isComplete = index < step;
223
+ const isClickable = index <= step;
224
+ return (
225
+ <li
226
+ key={meta.id}
227
+ id={`${baseId}-step-${index}`}
228
+ aria-current={isActive ? "step" : undefined}
229
+ className={cn("flex items-center gap-2", orientation === "horizontal" && "min-w-0")}
230
+ >
231
+ <button
232
+ type="button"
233
+ disabled={!isClickable}
234
+ onClick={() => goTo(index)}
235
+ className={cn(
236
+ "flex size-7 shrink-0 items-center justify-center rounded-full border text-xs font-medium tabular-nums",
237
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
238
+ isActive && "border-primary bg-primary text-primary-foreground",
239
+ // #399 — the step NUMBER is text on a `/10` wash, not plate ink
240
+ // and not a mark: `-text` rung. The border keeps the fill rung.
241
+ isComplete && "border-primary bg-primary/10 text-primary-text",
242
+ !isActive && !isComplete && "border-border-strong text-muted-foreground",
243
+ isClickable ? "cursor-pointer" : "cursor-default",
244
+ )}
245
+ >
246
+ {isComplete ? <Check className="size-4" aria-hidden="true" /> : index + 1}
247
+ </button>
248
+ <span className="flex min-w-0 flex-col">
249
+ <span
250
+ className={cn(
251
+ "truncate text-sm font-medium",
252
+ isActive ? "text-foreground" : "text-muted-foreground",
253
+ )}
254
+ >
255
+ {meta.title}
256
+ </span>
257
+ {meta.description && (
258
+ <span className="truncate text-xs text-muted-foreground">{meta.description}</span>
259
+ )}
260
+ </span>
261
+ </li>
262
+ );
263
+ })}
264
+ </ol>
265
+ );
266
+ });
267
+
268
+ // ---------------------------------------------------------------------------
269
+ // WizardStep (per-step content + validator)
270
+ // ---------------------------------------------------------------------------
271
+
272
+ export interface WizardStepProps extends HTMLAttributes<HTMLDivElement> {
273
+ /** This step's index (0-based) — must match its position in `steps`. */
274
+ step: number;
275
+ /** Run before advancing past this step. Return false to block. */
276
+ validate?: WizardValidator;
277
+ }
278
+
279
+ /**
280
+ * WizardStep — renders its children only when it is the active step, and
281
+ * registers its `validate` with the wizard while active. Receives focus on
282
+ * activation for keyboard users.
283
+ */
284
+ export const WizardStep = forwardRef<HTMLDivElement, WizardStepProps>(function WizardStep(
285
+ { step, validate, className, children, ...props },
286
+ ref,
287
+ ) {
288
+ const { step: activeStep, baseId, setActiveValidator } = useWizard();
289
+ const isActive = step === activeStep;
290
+ const regionRef = useRef<HTMLDivElement>(null);
291
+
292
+ useEffect(() => {
293
+ if (!isActive) return;
294
+ setActiveValidator(validate);
295
+ // Move focus to the newly-revealed step for keyboard users.
296
+ regionRef.current?.focus();
297
+ return () => setActiveValidator(undefined);
298
+ }, [isActive, validate, setActiveValidator]);
299
+
300
+ if (!isActive) return null;
301
+
302
+ return (
303
+ <div
304
+ ref={(node) => {
305
+ regionRef.current = node;
306
+ if (typeof ref === "function") ref(node);
307
+ else if (ref) ref.current = node;
308
+ }}
309
+ role="group"
310
+ aria-labelledby={`${baseId}-step-${step}`}
311
+ tabIndex={-1}
312
+ className={cn("min-w-0 flex-1 focus-visible:outline-none", className)}
313
+ {...props}
314
+ >
315
+ {children}
316
+ </div>
317
+ );
318
+ });
319
+
320
+ // ---------------------------------------------------------------------------
321
+ // WizardNav (Back / Next / Finish)
322
+ // ---------------------------------------------------------------------------
323
+
324
+ export interface WizardNavProps extends HTMLAttributes<HTMLDivElement> {
325
+ backLabel?: string;
326
+ nextLabel?: string;
327
+ finishLabel?: string;
328
+ /** Called when Finish is pressed on the last step. */
329
+ onFinish?: () => void;
330
+ }
331
+
332
+ /** WizardNav — Back / Next (or Finish on the last step) controls. */
333
+ export const WizardNav = forwardRef<HTMLDivElement, WizardNavProps>(function WizardNav(
334
+ { backLabel = "Back", nextLabel = "Next", finishLabel = "Finish", onFinish, className, ...props },
335
+ ref,
336
+ ) {
337
+ const { isFirst, isLast, validating, next, prev } = useWizard();
338
+ return (
339
+ <div ref={ref} className={cn("flex items-center justify-between gap-2", className)} {...props}>
340
+ <Button type="button" variant="outline" onClick={prev} disabled={isFirst}>
341
+ {backLabel}
342
+ </Button>
343
+ {isLast ? (
344
+ <Button type="button" onClick={onFinish}>
345
+ {finishLabel}
346
+ </Button>
347
+ ) : (
348
+ <Button type="button" onClick={next} disabled={validating}>
349
+ {validating ? "Checking…" : nextLabel}
350
+ </Button>
351
+ )}
352
+ </div>
353
+ );
354
+ });
@@ -0,0 +1,236 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { expect } from "storybook/test";
3
+ import { DecorationProvider, type DecorationLevel } from "@elabs-ai/components-tokens";
4
+ import { Button } from "./components/button";
5
+ import { Card, CardContent, CardHeader, CardTitle } from "./components/card";
6
+ import { Badge } from "./components/badge";
7
+ import { Input } from "./components/input";
8
+
9
+ /**
10
+ * Foundation/Decoration — the decoration DIAL (`--decoration`, 0–10), orthogonal
11
+ * to color. The SAME real `@elabs-ai/components-ui` components are rendered across the ramp.
12
+ *
13
+ * WHAT THE DIAL TOUCHES: backgrounds, and nothing else. It fades a drafting
14
+ * sheet in behind the page (faded, never flat — see `Fade` below) and, at 8–10,
15
+ * squares the large radii and goes shadowless. Charts additionally swap flat
16
+ * series fills for patterns at 8–10.
17
+ *
18
+ * WHAT IT NEVER TOUCHES: the inside of a control. The buttons, inputs and badges
19
+ * below render identically at decoration 10 and at 0 — no hatch, no
20
+ * drawn-not-filled plates. Texture inside an input is noise where someone is
21
+ * reading their own typing, and re-inking the six role fills collapsed them to
22
+ * one appearance, which used to force a whole compensating non-colour channel.
23
+ *
24
+ * It is hue-INDEPENDENT — flip the **theme** toolbar to see the dial on green,
25
+ * navy, light paper, etc. Any story can be swept via the **Decoration** toolbar
26
+ * global (or `globals=decoration:<0..10>`).
27
+ * Set it in code via `<DecorationProvider level={n}>`, a `data-decoration="n"`
28
+ * attribute, or `ThemeProvider`/`useDecoration` (document-level).
29
+ */
30
+ const meta = {
31
+ title: "Foundations/Decoration",
32
+ tags: ["autodocs"],
33
+ parameters: { layout: "padded" },
34
+ } satisfies Meta;
35
+ export default meta;
36
+
37
+ type Story = StoryObj<typeof meta>;
38
+
39
+ function Sample() {
40
+ return (
41
+ <Card className="w-64">
42
+ <CardHeader>
43
+ <CardTitle>Revenue</CardTitle>
44
+ </CardHeader>
45
+ <CardContent className="space-y-3">
46
+ <p className="text-display font-semibold tracking-tight text-foreground">$48.2M</p>
47
+ <div className="flex gap-2">
48
+ <Badge>healthy</Badge>
49
+ <Badge variant="warning">degraded</Badge>
50
+ </div>
51
+ <Input placeholder="Filter services…" />
52
+ <div className="flex gap-2">
53
+ <Button>Save</Button>
54
+ <Button variant="outline">Cancel</Button>
55
+ </div>
56
+ </CardContent>
57
+ </Card>
58
+ );
59
+ }
60
+
61
+ const LEVELS: DecorationLevel[] = [0, 4, 8, 10];
62
+
63
+ export const Dial: Story = {
64
+ render: () => (
65
+ <div className="flex flex-wrap items-start gap-8">
66
+ {LEVELS.map((level) => (
67
+ <div key={level} className="space-y-2">
68
+ <p className="font-mono text-meta uppercase tracking-wider text-muted-foreground">
69
+ decoration {level}
70
+ </p>
71
+ <DecorationProvider level={level}>
72
+ <Sample />
73
+ </DecorationProvider>
74
+ </div>
75
+ ))}
76
+ </div>
77
+ ),
78
+ };
79
+
80
+ const FADES = ["top", "bottom", "edges", "center"] as const;
81
+
82
+ /**
83
+ * The GROUND FADE (`data-decoration-fade`) — an opt-in region gesture that fades
84
+ * the ambient graph paper out instead of ruling a region edge to edge. It paints
85
+ * the SAME `--deco-grid` on a decorative `::before` layer and masks THAT, so the ink
86
+ * still rides the dial (inert at decoration 0) and the region's own content is
87
+ * never masked — the text in each panel stays at full opacity.
88
+ *
89
+ * The fade OWNS its region's ground. Opaque surfaces are never ruled themselves —
90
+ * the ambient sheet is painted once, behind the page — so a nested surface can't
91
+ * punch a crisp, full-strength rectangle into the field that was just faded out
92
+ * (the third panel below nests a `bg-card`).
93
+ *
94
+ * Budget: it spends the region's one focal drafting gesture. For a one-off fade on
95
+ * a single element, use Tailwind's own `mask-t-from-*` / `mask-radial-*` utilities.
96
+ *
97
+ * Shipped in anger on `Patterns/Templates/Marketing`, whose hero band carries
98
+ * `data-decoration-fade="top"` so the sheet fades in behind the headline instead of
99
+ * starting on a hard ruled edge — that template, not this matrix, is the surface to
100
+ * judge the gesture on.
101
+ *
102
+ * Sweep it with the **Decoration** toolbar (this story pins the dial to 10 so the
103
+ * gesture is visible on every theme; at 0 the mask has nothing to reveal).
104
+ */
105
+ export const Fade: Story = {
106
+ globals: { decoration: "10" },
107
+ render: () => (
108
+ <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
109
+ {FADES.map((fade) => (
110
+ <div
111
+ key={fade}
112
+ data-decoration-fade={fade}
113
+ data-testid={`fade-${fade}`}
114
+ className="min-h-40 rounded-lg bg-card p-4 text-card-foreground"
115
+ >
116
+ <p className="text-subtitle font-medium">{fade}</p>
117
+ <p className="text-caption text-muted-foreground">
118
+ Content stays fully opaque — only the ground fades.
119
+ </p>
120
+ {fade === "edges" ? (
121
+ <div
122
+ data-testid="fade-nested-surface"
123
+ className="mt-3 rounded-md bg-card p-2 text-caption text-muted-foreground"
124
+ >
125
+ A nested surface does not re-rule the faded ground.
126
+ </div>
127
+ ) : null}
128
+ </div>
129
+ ))}
130
+ </div>
131
+ ),
132
+ /**
133
+ * Locks the #257 contract that a static reading of the CSS cannot: the mask
134
+ * belongs to a decorative `::before` layer, so the host's CHILDREN are never
135
+ * faded and the layer never swallows a click — and the faded region is ruled
136
+ * exactly once, host and descendants alike.
137
+ */
138
+ play: async ({ canvas }) => {
139
+ const host = canvas.getByTestId("fade-edges");
140
+
141
+ // The host itself is unmasked — otherwise its text would fade with the grid.
142
+ await expect(getComputedStyle(host).maskImage).toBe("none");
143
+
144
+ // The decorative layer exists, paints the dial-driven grid, and is inert.
145
+ const layer = getComputedStyle(host, "::before");
146
+ await expect(layer.content).not.toBe("none");
147
+ await expect(layer.pointerEvents).toBe("none");
148
+ await expect(layer.backgroundImage).toContain("gradient");
149
+
150
+ // Every child renders at full opacity.
151
+ for (const child of Array.from(host.children)) {
152
+ await expect(getComputedStyle(child).opacity).toBe("1");
153
+ }
154
+
155
+ // The faded host is not ALSO ruled crisply…
156
+ await expect(getComputedStyle(host).backgroundImage).toBe("none");
157
+ // …and neither is a surface nested inside it (no crisp patch in a faded field).
158
+ const nested = canvas.getByTestId("fade-nested-surface");
159
+ await expect(getComputedStyle(nested).backgroundImage).toBe("none");
160
+ },
161
+ };
162
+
163
+ /**
164
+ * The AMBIENT GROUND, as an executable lock. The sheet is painted exactly once —
165
+ * on a fixed, masked layer behind the page — so this asserts the three properties
166
+ * a static reading of the CSS cannot: the layer exists and carries the dial-driven
167
+ * grid, it is MASKED (never flat), and no opaque surface re-rules it on top.
168
+ *
169
+ * The dial is set from the toolbar global, which writes `data-decoration` on the
170
+ * preview root exactly as `ThemeProvider` does — a `DecorationProvider` wrapper
171
+ * would NOT reach `body`, which is why `Dial` above cannot stand in for this.
172
+ */
173
+ export const AmbientGround: Story = {
174
+ globals: { decoration: "10" },
175
+ render: () => (
176
+ <Card className="w-64" data-testid="ground-card">
177
+ <CardHeader>
178
+ <CardTitle>On the sheet</CardTitle>
179
+ </CardHeader>
180
+ <CardContent>
181
+ <p className="text-caption text-muted-foreground">
182
+ An opaque panel covers the ground; it never re-rules it.
183
+ </p>
184
+ </CardContent>
185
+ </Card>
186
+ ),
187
+ play: async () => {
188
+ const ground = getComputedStyle(document.body, "::before");
189
+
190
+ // The sheet exists and carries the dial-driven grid…
191
+ await expect(ground.content).not.toBe("none");
192
+ await expect(ground.backgroundImage).toContain("gradient");
193
+ await expect(ground.position).toBe("fixed");
194
+ await expect(ground.pointerEvents).toBe("none");
195
+
196
+ // …and it is never flat: the layer itself is masked.
197
+ await expect(ground.maskImage).not.toBe("none");
198
+
199
+ // The host is unmasked, so the page's own content is never faded with it.
200
+ await expect(getComputedStyle(document.body).maskImage).toBe("none");
201
+ },
202
+ };
203
+
204
+ /**
205
+ * The control invariant, as an executable lock: at FULL decoration the dial must
206
+ * not have painted anything into a control. A `Button` keeps its solid role fill
207
+ * (no `background-image`), an `Input` keeps a clean ground, and a `Badge` keeps
208
+ * its own tone rather than collapsing to a drawn outline.
209
+ *
210
+ * This is the story to run when someone reintroduces a "drawn-not-filled" rule:
211
+ * it fails on the hatch, not on a screenshot review three weeks later.
212
+ */
213
+ export const ControlsAreUntouched: Story = {
214
+ globals: { decoration: "10" },
215
+ render: () => (
216
+ <div data-testid="controls" className="flex flex-wrap items-center gap-3">
217
+ <Button data-testid="deco-button">Save</Button>
218
+ <Button variant="outline">Cancel</Button>
219
+ <Badge variant="warning">degraded</Badge>
220
+ <Input data-testid="deco-input" placeholder="Filter services…" className="w-48" />
221
+ </div>
222
+ ),
223
+ play: async ({ canvas }) => {
224
+ const button = canvas.getByTestId("deco-button");
225
+ const input = canvas.getByTestId("deco-input");
226
+
227
+ // No hatch, no texture: the dial paints backgrounds, never controls.
228
+ await expect(getComputedStyle(button).backgroundImage).toBe("none");
229
+ await expect(getComputedStyle(input).backgroundImage).toBe("none");
230
+
231
+ // And the role fill is still a real, opaque plate — not the old
232
+ // "transparent ground + hairline" drawn control.
233
+ await expect(getComputedStyle(button).backgroundColor).not.toBe("rgba(0, 0, 0, 0)");
234
+ await expect(getComputedStyle(button).backgroundColor).not.toBe("transparent");
235
+ },
236
+ };