@sableui/react 0.6.2 → 0.7.1

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 (642) hide show
  1. package/AGENTS.md +2 -2
  2. package/CHANGELOG.md +315 -0
  3. package/CONTRIBUTING.md +16 -10
  4. package/DESIGN.md +3 -3
  5. package/README.md +3 -3
  6. package/bin/sable-lint.mjs +18 -0
  7. package/bin/sable.mjs +470 -62
  8. package/dist/blocks/api-keys-block.js +44 -0
  9. package/dist/blocks/api-keys-block.meta.js +21 -0
  10. package/dist/blocks/chat-thread-block.js +17 -0
  11. package/dist/blocks/chat-thread-block.meta.js +20 -0
  12. package/dist/blocks/command-palette-block.js +24 -0
  13. package/dist/blocks/command-palette-block.meta.js +21 -0
  14. package/dist/blocks/cta-band-block.js +27 -0
  15. package/dist/blocks/cta-band-block.meta.js +20 -0
  16. package/dist/blocks/danger-zone-block.js +39 -0
  17. package/dist/blocks/danger-zone-block.meta.js +21 -0
  18. package/dist/blocks/device-comparison-block.js +69 -0
  19. package/dist/blocks/device-comparison-block.meta.js +21 -0
  20. package/dist/blocks/event-card-block.js +40 -0
  21. package/dist/blocks/event-card-block.meta.js +21 -0
  22. package/dist/blocks/faq-block.js +38 -0
  23. package/dist/blocks/faq-block.meta.js +20 -0
  24. package/dist/blocks/feature-grid-block.js +38 -0
  25. package/dist/blocks/feature-grid-block.meta.js +21 -0
  26. package/dist/blocks/feedback-form-block.js +30 -0
  27. package/dist/blocks/feedback-form-block.meta.js +21 -0
  28. package/dist/blocks/goal-progress-block.js +24 -0
  29. package/dist/blocks/goal-progress-block.meta.js +21 -0
  30. package/dist/blocks/hero-block.js +33 -0
  31. package/dist/blocks/hero-block.meta.js +21 -0
  32. package/dist/blocks/index.js +162 -1964
  33. package/dist/blocks/invite-team-block.js +36 -0
  34. package/dist/blocks/invite-team-block.meta.js +20 -0
  35. package/dist/blocks/invoice-history-block.js +41 -0
  36. package/dist/blocks/invoice-history-block.meta.js +21 -0
  37. package/dist/blocks/kpi-sparkline-block.js +29 -0
  38. package/dist/blocks/kpi-sparkline-block.meta.js +20 -0
  39. package/dist/blocks/media-player-block.js +68 -0
  40. package/dist/blocks/media-player-block.meta.js +21 -0
  41. package/dist/blocks/newsletter-block.js +35 -0
  42. package/dist/blocks/newsletter-block.meta.js +21 -0
  43. package/dist/blocks/not-found-block.js +27 -0
  44. package/dist/blocks/not-found-block.meta.js +20 -0
  45. package/dist/blocks/notification-list-block.js +32 -0
  46. package/dist/blocks/notification-list-block.meta.js +21 -0
  47. package/dist/blocks/notification-settings-block.js +25 -0
  48. package/dist/blocks/notification-settings-block.meta.js +20 -0
  49. package/dist/blocks/order-tracking-block.js +26 -0
  50. package/dist/blocks/order-tracking-block.meta.js +20 -0
  51. package/dist/blocks/pricing-card-block.js +46 -0
  52. package/dist/blocks/pricing-card-block.meta.js +21 -0
  53. package/dist/blocks/pricing-table-block.js +88 -0
  54. package/dist/blocks/pricing-table-block.meta.js +20 -0
  55. package/dist/blocks/product-card-block.js +26 -0
  56. package/dist/blocks/product-card-block.meta.js +21 -0
  57. package/dist/blocks/profile-card-block.js +58 -0
  58. package/dist/blocks/profile-card-block.meta.js +21 -0
  59. package/dist/blocks/profile-form-block.js +70 -0
  60. package/dist/blocks/profile-form-block.meta.js +21 -0
  61. package/dist/blocks/revenue-breakdown-block.js +38 -0
  62. package/dist/blocks/revenue-breakdown-block.meta.js +21 -0
  63. package/dist/blocks/sign-in-block.js +38 -0
  64. package/dist/blocks/sign-in-block.meta.js +21 -0
  65. package/dist/blocks/sign-up-block.js +48 -0
  66. package/dist/blocks/sign-up-block.meta.js +21 -0
  67. package/dist/blocks/site-footer-block.js +54 -0
  68. package/dist/blocks/site-footer-block.meta.js +20 -0
  69. package/dist/blocks/stat-row-block.js +28 -0
  70. package/dist/blocks/stat-row-block.meta.js +21 -0
  71. package/dist/blocks/steps-block.js +37 -0
  72. package/dist/blocks/steps-block.meta.js +20 -0
  73. package/dist/blocks/task-list-block.js +52 -0
  74. package/dist/blocks/task-list-block.meta.js +21 -0
  75. package/dist/blocks/team-members-block.js +49 -0
  76. package/dist/blocks/team-members-block.meta.js +21 -0
  77. package/dist/blocks/testimonial-block.js +49 -0
  78. package/dist/blocks/testimonial-block.meta.js +20 -0
  79. package/dist/blocks/traffic-sources-block.js +39 -0
  80. package/dist/blocks/traffic-sources-block.meta.js +21 -0
  81. package/dist/blocks/traffic-trend-block.js +61 -0
  82. package/dist/blocks/traffic-trend-block.meta.js +21 -0
  83. package/dist/blocks/upload-list-block.js +39 -0
  84. package/dist/blocks/upload-list-block.meta.js +21 -0
  85. package/dist/blocks/verify-code-block.js +28 -0
  86. package/dist/blocks/verify-code-block.meta.js +21 -0
  87. package/dist/blocks/wallet-block.js +48 -0
  88. package/dist/blocks/wallet-block.meta.js +21 -0
  89. package/dist/components/accordion/accordion.js +63 -0
  90. package/dist/components/accordion/accordion.meta.js +155 -0
  91. package/dist/components/accordion/index.js +15 -0
  92. package/dist/components/app-header/app-header.js +30 -0
  93. package/dist/components/app-header/app-header.meta.js +88 -0
  94. package/dist/components/app-header/index.js +7 -0
  95. package/dist/components/app-shell/app-shell-dock.js +37 -0
  96. package/dist/components/app-shell/app-shell.js +67 -0
  97. package/dist/components/app-shell/app-shell.meta.js +176 -0
  98. package/dist/components/app-shell/index.js +7 -0
  99. package/dist/components/area-chart/area-chart.js +10 -0
  100. package/dist/components/area-chart/area-chart.meta.js +218 -0
  101. package/dist/components/area-chart/index.js +7 -0
  102. package/dist/components/attachment/attachment.js +176 -0
  103. package/dist/components/attachment/attachment.meta.js +179 -0
  104. package/dist/components/attachment/attachment.variants.js +47 -0
  105. package/dist/components/attachment/index.js +7 -0
  106. package/dist/components/avatar/avatar.js +192 -0
  107. package/dist/components/avatar/avatar.meta.js +233 -0
  108. package/dist/components/avatar/avatar.variants.js +44 -0
  109. package/dist/components/avatar/index.js +22 -0
  110. package/dist/components/badge/badge.js +26 -0
  111. package/dist/components/badge/badge.meta.js +171 -0
  112. package/dist/components/badge/badge.variants.js +103 -0
  113. package/dist/components/badge/index.js +11 -0
  114. package/dist/components/banner/banner.js +76 -0
  115. package/dist/components/banner/banner.meta.js +172 -0
  116. package/dist/components/banner/banner.variants.js +44 -0
  117. package/dist/components/banner/index.js +9 -0
  118. package/dist/components/bar-chart/bar-chart.js +10 -0
  119. package/dist/components/bar-chart/bar-chart.meta.js +245 -0
  120. package/dist/components/bar-chart/index.js +7 -0
  121. package/dist/components/bottom-nav/bottom-nav.js +63 -0
  122. package/dist/components/bottom-nav/bottom-nav.meta.js +95 -0
  123. package/dist/components/bottom-nav/index.js +11 -0
  124. package/dist/components/breadcrumbs/breadcrumbs.js +90 -0
  125. package/dist/components/breadcrumbs/breadcrumbs.meta.js +111 -0
  126. package/dist/components/breadcrumbs/index.js +17 -0
  127. package/dist/components/browser/browser.js +39 -0
  128. package/dist/components/browser/browser.meta.js +80 -0
  129. package/dist/components/browser/index.js +7 -0
  130. package/dist/components/bubble/bubble.js +110 -0
  131. package/dist/components/bubble/bubble.meta.js +158 -0
  132. package/dist/components/bubble/bubble.variants.js +90 -0
  133. package/dist/components/bubble/index.js +13 -0
  134. package/dist/components/button/button.js +87 -0
  135. package/dist/components/button/button.meta.js +302 -0
  136. package/dist/components/button/button.variants.js +137 -0
  137. package/dist/components/button/index.js +9 -0
  138. package/dist/components/calendar/calendar.js +89 -0
  139. package/dist/components/calendar/calendar.meta.js +136 -0
  140. package/dist/components/calendar/index.js +7 -0
  141. package/dist/components/card/card.js +203 -0
  142. package/dist/components/card/card.meta.js +212 -0
  143. package/dist/components/card/index.js +23 -0
  144. package/dist/components/carousel/carousel.js +428 -0
  145. package/dist/components/carousel/carousel.meta.js +100 -0
  146. package/dist/components/carousel/index.js +7 -0
  147. package/dist/components/chat-composer/chat-composer.js +174 -0
  148. package/dist/components/chat-composer/chat-composer.meta.js +241 -0
  149. package/dist/components/chat-composer/chat-composer.variants.js +17 -0
  150. package/dist/components/chat-composer/index.js +7 -0
  151. package/dist/components/checkbox/checkbox-mark.js +28 -0
  152. package/dist/components/checkbox/checkbox.js +30 -0
  153. package/dist/components/checkbox/checkbox.meta.js +127 -0
  154. package/dist/components/checkbox/index.js +7 -0
  155. package/dist/components/checkbox-card/checkbox-card.js +94 -0
  156. package/dist/components/checkbox-card/checkbox-card.meta.js +154 -0
  157. package/dist/components/checkbox-card/checkbox-card.variants.js +43 -0
  158. package/dist/components/checkbox-card/index.js +11 -0
  159. package/dist/components/code/code.js +19 -0
  160. package/dist/components/code/code.meta.js +71 -0
  161. package/dist/components/code/index.js +7 -0
  162. package/dist/components/code-block/code-block.js +84 -0
  163. package/dist/components/code-block/code-block.meta.js +103 -0
  164. package/dist/components/code-block/index.js +8 -0
  165. package/dist/components/collapsible/collapsible.js +38 -0
  166. package/dist/components/collapsible/collapsible.meta.js +117 -0
  167. package/dist/components/collapsible/index.js +15 -0
  168. package/dist/components/color-field/color-field.js +293 -0
  169. package/dist/components/color-field/color-field.meta.js +142 -0
  170. package/dist/components/color-field/color.js +69 -0
  171. package/dist/components/color-field/index.js +15 -0
  172. package/dist/components/combobox/combobox.js +486 -0
  173. package/dist/components/combobox/combobox.meta.js +217 -0
  174. package/dist/components/combobox/index.js +37 -0
  175. package/dist/components/command-menu/command-menu-trigger.js +122 -0
  176. package/dist/components/command-menu/command-menu.js +433 -0
  177. package/dist/components/command-menu/command-menu.meta.js +279 -0
  178. package/dist/components/command-menu/index.js +21 -0
  179. package/dist/components/context-card/context-card.js +62 -0
  180. package/dist/components/context-card/context-card.meta.js +106 -0
  181. package/dist/components/context-card/index.js +17 -0
  182. package/dist/components/context-menu/context-menu.js +92 -0
  183. package/dist/components/context-menu/context-menu.meta.js +138 -0
  184. package/dist/components/context-menu/index.js +23 -0
  185. package/dist/components/data-table/data-table.js +142 -0
  186. package/dist/components/data-table/data-table.meta.js +146 -0
  187. package/dist/components/data-table/index.js +9 -0
  188. package/dist/components/date-picker/date-picker.js +196 -0
  189. package/dist/components/date-picker/date-picker.meta.js +164 -0
  190. package/dist/components/date-picker/index.js +7 -0
  191. package/dist/components/date-tile/date-tile.js +29 -0
  192. package/dist/components/date-tile/date-tile.meta.js +72 -0
  193. package/dist/components/date-tile/index.js +7 -0
  194. package/dist/components/destructive-action-modal/destructive-action-modal.js +130 -0
  195. package/dist/components/destructive-action-modal/destructive-action-modal.meta.js +136 -0
  196. package/dist/components/destructive-action-modal/index.js +9 -0
  197. package/dist/components/dialog/dialog.js +207 -0
  198. package/dist/components/dialog/dialog.meta.js +239 -0
  199. package/dist/components/dialog/index.js +45 -0
  200. package/dist/components/disclosure-picker/disclosure-picker.js +417 -0
  201. package/dist/components/disclosure-picker/disclosure-picker.meta.js +213 -0
  202. package/dist/components/disclosure-picker/index.js +21 -0
  203. package/dist/components/empty-state/empty-state.js +52 -0
  204. package/dist/components/empty-state/empty-state.meta.js +158 -0
  205. package/dist/components/empty-state/index.js +7 -0
  206. package/dist/components/entity/entity.js +70 -0
  207. package/dist/components/entity/entity.meta.js +108 -0
  208. package/dist/components/entity/entity.variants.js +16 -0
  209. package/dist/components/entity/index.js +7 -0
  210. package/dist/components/feedback/feedback.js +93 -0
  211. package/dist/components/feedback/feedback.meta.js +109 -0
  212. package/dist/components/feedback/index.js +7 -0
  213. package/dist/components/fieldset/fieldset.js +80 -0
  214. package/dist/components/fieldset/fieldset.meta.js +160 -0
  215. package/dist/components/fieldset/index.js +17 -0
  216. package/dist/components/file-dropzone/file-dropzone.js +193 -0
  217. package/dist/components/file-dropzone/file-dropzone.meta.js +210 -0
  218. package/dist/components/file-dropzone/file-dropzone.variants.js +26 -0
  219. package/dist/components/file-dropzone/index.js +13 -0
  220. package/dist/components/font-picker/font-picker.js +77 -0
  221. package/dist/components/font-picker/font-picker.meta.js +119 -0
  222. package/dist/components/font-picker/index.js +7 -0
  223. package/dist/components/form/form.js +145 -0
  224. package/dist/components/form/form.meta.js +142 -0
  225. package/dist/components/form/index.js +11 -0
  226. package/dist/components/gliding-highlight/gliding-highlight.js +50 -0
  227. package/dist/components/gliding-highlight/gliding-highlight.meta.js +135 -0
  228. package/dist/components/gliding-highlight/index.js +9 -0
  229. package/dist/components/gradient-art/gradient-art.js +168 -0
  230. package/dist/components/gradient-art/gradient-art.meta.js +81 -0
  231. package/dist/components/gradient-art/index.js +7 -0
  232. package/dist/components/gradient-art/renderer.js +336 -0
  233. package/dist/components/inline-status/index.js +7 -0
  234. package/dist/components/inline-status/inline-status.js +33 -0
  235. package/dist/components/inline-status/inline-status.meta.js +83 -0
  236. package/dist/components/inline-status/inline-status.variants.js +18 -0
  237. package/dist/components/input/index.js +7 -0
  238. package/dist/components/input/input.js +33 -0
  239. package/dist/components/input/input.meta.js +155 -0
  240. package/dist/components/kbd/index.js +8 -0
  241. package/dist/components/kbd/kbd.js +54 -0
  242. package/dist/components/kbd/kbd.meta.js +95 -0
  243. package/dist/components/label/index.js +7 -0
  244. package/dist/components/label/label.js +51 -0
  245. package/dist/components/label/label.meta.js +98 -0
  246. package/dist/components/line-chart/index.js +7 -0
  247. package/dist/components/line-chart/line-chart.js +10 -0
  248. package/dist/components/line-chart/line-chart.meta.js +222 -0
  249. package/dist/components/marker/index.js +7 -0
  250. package/dist/components/marker/marker.js +39 -0
  251. package/dist/components/marker/marker.meta.js +132 -0
  252. package/dist/components/marker/marker.variants.js +25 -0
  253. package/dist/components/marquee/index.js +7 -0
  254. package/dist/components/marquee/marquee.js +146 -0
  255. package/dist/components/marquee/marquee.meta.js +106 -0
  256. package/dist/components/media-image/index.js +7 -0
  257. package/dist/components/media-image/media-image.js +56 -0
  258. package/dist/components/media-image/media-image.meta.js +92 -0
  259. package/dist/components/media-tile/index.js +7 -0
  260. package/dist/components/media-tile/media-tile.js +135 -0
  261. package/dist/components/media-tile/media-tile.meta.js +133 -0
  262. package/dist/components/media-tile/media-tile.variants.js +48 -0
  263. package/dist/components/menu/index.js +33 -0
  264. package/dist/components/menu/menu.js +168 -0
  265. package/dist/components/menu/menu.meta.js +176 -0
  266. package/dist/components/message/index.js +7 -0
  267. package/dist/components/message/message.js +83 -0
  268. package/dist/components/message/message.meta.js +150 -0
  269. package/dist/components/message/message.variants.js +41 -0
  270. package/dist/components/message-scroller/index.js +9 -0
  271. package/dist/components/message-scroller/message-scroller.js +300 -0
  272. package/dist/components/message-scroller/message-scroller.meta.js +172 -0
  273. package/dist/components/meter/index.js +20 -0
  274. package/dist/components/meter/meter.js +112 -0
  275. package/dist/components/meter/meter.meta.js +227 -0
  276. package/dist/components/meter/meter.variants.js +61 -0
  277. package/dist/components/middle-truncate/index.js +7 -0
  278. package/dist/components/middle-truncate/middle-truncate.js +28 -0
  279. package/dist/components/middle-truncate/middle-truncate.meta.js +77 -0
  280. package/dist/components/note/index.js +9 -0
  281. package/dist/components/note/note.js +73 -0
  282. package/dist/components/note/note.meta.js +112 -0
  283. package/dist/components/note/note.variants.js +22 -0
  284. package/dist/components/notification-badge/index.js +15 -0
  285. package/dist/components/notification-badge/notification-badge.js +48 -0
  286. package/dist/components/notification-badge/notification-badge.meta.js +107 -0
  287. package/dist/components/notification-badge/notification-badge.variants.js +57 -0
  288. package/dist/components/number-field/index.js +7 -0
  289. package/dist/components/number-field/number-field.js +70 -0
  290. package/dist/components/number-field/number-field.meta.js +109 -0
  291. package/dist/components/otp-field/index.js +7 -0
  292. package/dist/components/otp-field/otp-field.js +70 -0
  293. package/dist/components/otp-field/otp-field.meta.js +197 -0
  294. package/dist/components/overflow-menu/index.js +7 -0
  295. package/dist/components/overflow-menu/overflow-menu.js +26 -0
  296. package/dist/components/overflow-menu/overflow-menu.meta.js +100 -0
  297. package/dist/components/page-header/index.js +7 -0
  298. package/dist/components/page-header/page-header.js +34 -0
  299. package/dist/components/page-header/page-header.meta.js +104 -0
  300. package/dist/components/page-loader/index.js +7 -0
  301. package/dist/components/page-loader/page-loader.js +37 -0
  302. package/dist/components/page-loader/page-loader.meta.js +85 -0
  303. package/dist/components/pagination/index.js +7 -0
  304. package/dist/components/pagination/pagination.js +105 -0
  305. package/dist/components/pagination/pagination.meta.js +141 -0
  306. package/dist/components/panel-group/index.js +10 -0
  307. package/dist/components/panel-group/panel-group.js +168 -0
  308. package/dist/components/panel-group/panel-group.meta.js +153 -0
  309. package/dist/components/panel-section/index.js +9 -0
  310. package/dist/components/panel-section/panel-section.js +115 -0
  311. package/dist/components/panel-section/panel-section.meta.js +156 -0
  312. package/dist/components/phone/index.js +7 -0
  313. package/dist/components/phone/phone.js +50 -0
  314. package/dist/{chunk-KFZ43RKU.js → components/phone/phone.meta.js} +3 -50
  315. package/dist/{chunk-NEQNXI2G.js → components/phone/status-icons.js} +4 -4
  316. package/dist/components/pie-chart/index.js +7 -0
  317. package/dist/components/pie-chart/pie-chart.js +191 -0
  318. package/dist/components/pie-chart/pie-chart.meta.js +210 -0
  319. package/dist/components/pill/index.js +9 -0
  320. package/dist/components/pill/pill.js +24 -0
  321. package/dist/components/pill/pill.meta.js +108 -0
  322. package/dist/components/pill/pill.variants.js +65 -0
  323. package/dist/components/popover/index.js +23 -0
  324. package/dist/components/popover/popover.js +100 -0
  325. package/dist/components/popover/popover.meta.js +136 -0
  326. package/dist/components/progress/index.js +19 -0
  327. package/dist/components/progress/progress.js +70 -0
  328. package/dist/components/progress/progress.meta.js +171 -0
  329. package/dist/components/progress/progress.variants.js +17 -0
  330. package/dist/components/radar-chart/index.js +7 -0
  331. package/dist/components/radar-chart/radar-chart.js +161 -0
  332. package/dist/components/radar-chart/radar-chart.meta.js +204 -0
  333. package/dist/components/radial-chart/index.js +7 -0
  334. package/dist/components/radial-chart/radial-chart.js +175 -0
  335. package/dist/components/radial-chart/radial-chart.meta.js +216 -0
  336. package/dist/components/radio/index.js +8 -0
  337. package/dist/components/radio/radio.js +39 -0
  338. package/dist/components/radio/radio.meta.js +104 -0
  339. package/dist/components/radio-card/index.js +7 -0
  340. package/dist/components/radio-card/radio-card.js +81 -0
  341. package/dist/components/radio-card/radio-card.meta.js +144 -0
  342. package/dist/components/radio-card/radio-card.variants.js +43 -0
  343. package/dist/components/relative-time/index.js +7 -0
  344. package/dist/components/relative-time/relative-time.js +47 -0
  345. package/dist/components/relative-time/relative-time.meta.js +77 -0
  346. package/dist/components/scroller/index.js +7 -0
  347. package/dist/components/scroller/scroller.js +111 -0
  348. package/dist/components/scroller/scroller.meta.js +118 -0
  349. package/dist/components/scrub-field/index.js +7 -0
  350. package/dist/components/scrub-field/scrub-field.js +124 -0
  351. package/dist/components/scrub-field/scrub-field.meta.js +139 -0
  352. package/dist/components/search-input/index.js +7 -0
  353. package/dist/components/search-input/search-input.js +91 -0
  354. package/dist/components/search-input/search-input.meta.js +129 -0
  355. package/dist/components/section-header/index.js +7 -0
  356. package/dist/components/section-header/section-header.js +39 -0
  357. package/dist/components/section-header/section-header.meta.js +174 -0
  358. package/dist/components/segmented-bar/index.js +14 -0
  359. package/dist/components/segmented-bar/segmented-bar.js +26 -0
  360. package/dist/components/segmented-bar/segmented-bar.meta.js +108 -0
  361. package/dist/components/segmented-bar/segmented-bar.variants.js +39 -0
  362. package/dist/components/select/index.js +25 -0
  363. package/dist/components/select/select.js +191 -0
  364. package/dist/components/select/select.meta.js +189 -0
  365. package/dist/components/separator/index.js +7 -0
  366. package/dist/components/separator/separator.js +42 -0
  367. package/dist/components/separator/separator.meta.js +84 -0
  368. package/dist/components/settings-menu/index.js +11 -0
  369. package/dist/components/settings-menu/settings-menu.js +101 -0
  370. package/dist/components/settings-menu/settings-menu.meta.js +177 -0
  371. package/dist/components/settings-menu/settings-menu.variants.js +15 -0
  372. package/dist/components/sheet/index.js +29 -0
  373. package/dist/components/sheet/sheet.js +121 -0
  374. package/dist/components/sheet/sheet.meta.js +167 -0
  375. package/dist/components/sheet/sheet.variants.js +21 -0
  376. package/dist/components/show-more/index.js +7 -0
  377. package/dist/components/show-more/show-more.js +87 -0
  378. package/dist/components/show-more/show-more.meta.js +79 -0
  379. package/dist/components/side-nav/index.js +13 -0
  380. package/dist/components/side-nav/side-nav.js +75 -0
  381. package/dist/components/side-nav/side-nav.meta.js +139 -0
  382. package/dist/components/side-nav/side-nav.variants.js +34 -0
  383. package/dist/components/sidebar/index.js +21 -0
  384. package/dist/components/sidebar/sidebar.js +196 -0
  385. package/dist/components/sidebar/sidebar.meta.js +118 -0
  386. package/dist/components/skeleton/index.js +7 -0
  387. package/dist/components/skeleton/skeleton.js +16 -0
  388. package/dist/components/skeleton/skeleton.meta.js +90 -0
  389. package/dist/components/slider/index.js +21 -0
  390. package/dist/components/slider/slider.js +115 -0
  391. package/dist/components/slider/slider.meta.js +214 -0
  392. package/dist/components/sparkline/index.js +7 -0
  393. package/dist/components/sparkline/sparkline.js +84 -0
  394. package/dist/components/sparkline/sparkline.meta.js +147 -0
  395. package/dist/components/spinner/index.js +7 -0
  396. package/dist/components/spinner/spinner.js +59 -0
  397. package/dist/components/spinner/spinner.meta.js +118 -0
  398. package/dist/components/status-bar/index.js +9 -0
  399. package/dist/components/status-bar/status-bar.js +40 -0
  400. package/dist/components/status-bar/status-bar.meta.js +117 -0
  401. package/dist/components/status-dot/index.js +9 -0
  402. package/dist/components/status-dot/status-dot.js +40 -0
  403. package/dist/components/status-dot/status-dot.meta.js +113 -0
  404. package/dist/components/status-dot/status-dot.variants.js +28 -0
  405. package/dist/components/stepper/index.js +17 -0
  406. package/dist/components/stepper/stepper.js +161 -0
  407. package/dist/components/stepper/stepper.meta.js +190 -0
  408. package/dist/components/stepper/stepper.variants.js +72 -0
  409. package/dist/components/streaming-text/index.js +7 -0
  410. package/dist/components/streaming-text/streaming-text.js +55 -0
  411. package/dist/components/streaming-text/streaming-text.meta.js +147 -0
  412. package/dist/components/switch/index.js +7 -0
  413. package/dist/components/switch/switch.js +26 -0
  414. package/dist/components/switch/switch.meta.js +133 -0
  415. package/dist/components/table/index.js +23 -0
  416. package/dist/components/table/table.js +117 -0
  417. package/dist/components/table/table.meta.js +143 -0
  418. package/dist/components/tabs/index.js +17 -0
  419. package/dist/components/tabs/tabs.js +58 -0
  420. package/dist/components/tabs/tabs.meta.js +120 -0
  421. package/dist/components/textarea/index.js +7 -0
  422. package/dist/components/textarea/textarea.js +38 -0
  423. package/dist/components/textarea/textarea.meta.js +117 -0
  424. package/dist/components/theme-selector/index.js +11 -0
  425. package/dist/components/theme-selector/theme-selector.js +145 -0
  426. package/dist/components/theme-selector/theme-selector.meta.js +181 -0
  427. package/dist/components/theme-switcher/index.js +7 -0
  428. package/dist/components/theme-switcher/theme-switcher.js +106 -0
  429. package/dist/components/theme-switcher/theme-switcher.meta.js +99 -0
  430. package/dist/components/thinking-orb/index.js +9 -0
  431. package/dist/components/thinking-orb/thinking-orb.js +126 -0
  432. package/dist/components/thinking-orb/thinking-orb.meta.js +142 -0
  433. package/dist/components/thinking-orb/thinking-orb.render.js +252 -0
  434. package/dist/components/toast/index.js +29 -0
  435. package/dist/components/toast/toast.js +254 -0
  436. package/dist/components/toast/toast.meta.js +261 -0
  437. package/dist/components/toggle/index.js +13 -0
  438. package/dist/components/toggle/toggle.js +51 -0
  439. package/dist/components/toggle/toggle.meta.js +129 -0
  440. package/dist/components/toolbar/index.js +27 -0
  441. package/dist/components/toolbar/toolbar.js +80 -0
  442. package/dist/components/toolbar/toolbar.meta.js +272 -0
  443. package/dist/components/toolbar/toolbar.variants.js +16 -0
  444. package/dist/components/tooltip/index.js +21 -0
  445. package/dist/components/tooltip/tooltip.js +74 -0
  446. package/dist/components/tooltip/tooltip.meta.js +156 -0
  447. package/dist/components/tree/index.js +12 -0
  448. package/dist/components/tree/tree.js +364 -0
  449. package/dist/components/tree/tree.meta.js +334 -0
  450. package/dist/components/video/index.js +7 -0
  451. package/dist/components/video/video.js +59 -0
  452. package/dist/components/video/video.meta.js +81 -0
  453. package/dist/components/workspace/index.js +9 -0
  454. package/dist/components/workspace/workspace.js +79 -0
  455. package/dist/components/workspace/workspace.meta.js +183 -0
  456. package/dist/foundations/accessibility.js +107 -0
  457. package/dist/foundations/index.js +25 -792
  458. package/dist/foundations/motion.js +135 -0
  459. package/dist/foundations/scrolling.js +62 -0
  460. package/dist/foundations/shape.js +113 -0
  461. package/dist/foundations/spacing.js +121 -0
  462. package/dist/foundations/surfaces.js +107 -0
  463. package/dist/foundations/types.js +0 -0
  464. package/dist/foundations/typography.js +152 -0
  465. package/dist/icon/Icon.js +30 -0
  466. package/dist/icon/IconProvider.js +27 -0
  467. package/dist/icon/compose.js +13 -0
  468. package/dist/icon/index.d.ts +13 -5
  469. package/dist/icon/index.js +13 -2
  470. package/dist/icon/registry.js +0 -0
  471. package/dist/icon-sets/brands.generated.js +123 -0
  472. package/dist/icon-sets/brands.js +10 -123
  473. package/dist/icon-sets/reicon.d.ts +3 -1
  474. package/dist/icon-sets/reicon.js +209 -1
  475. package/dist/index.d.ts +618 -34
  476. package/dist/index.js +211 -11351
  477. package/dist/ios/chrome.js +36 -0
  478. package/dist/ios/index.js +19 -315
  479. package/dist/ios/liquid-glass.js +103 -0
  480. package/dist/ios/native-app.js +46 -0
  481. package/dist/ios/navigation.js +63 -0
  482. package/dist/ios/phone-frame.js +64 -0
  483. package/dist/ios/preview-theme.js +83 -0
  484. package/dist/ios/preview.js +10 -137
  485. package/dist/ios/sheet.js +85 -0
  486. package/dist/ios/theme.js +19 -0
  487. package/dist/layout/AspectBox.js +27 -0
  488. package/dist/layout/Bleed.js +15 -0
  489. package/dist/layout/Center.js +21 -0
  490. package/dist/layout/Columns.js +70 -0
  491. package/dist/layout/Grid.js +50 -0
  492. package/dist/layout/GridOverlay.js +41 -0
  493. package/dist/layout/Page.js +11 -0
  494. package/dist/layout/Region.js +39 -0
  495. package/dist/layout/Row.js +45 -0
  496. package/dist/layout/SafeArea.js +26 -0
  497. package/dist/layout/Spacer.js +9 -0
  498. package/dist/layout/SplitPane.js +55 -0
  499. package/dist/layout/Stack.js +42 -0
  500. package/dist/layout/_responsive.js +16 -0
  501. package/dist/layout/_safeArea.js +18 -0
  502. package/dist/layout/index.d.ts +30 -8
  503. package/dist/layout/index.js +14 -2
  504. package/dist/lib/anchored-scroll.js +7 -0
  505. package/dist/lib/bleed.js +7 -0
  506. package/dist/lib/chart/cartesian.js +365 -0
  507. package/dist/lib/chart/chart-frame.js +163 -0
  508. package/dist/lib/chart/chart-legend.js +13 -0
  509. package/dist/lib/chart/chart-tooltip.js +38 -0
  510. package/dist/lib/chart/format.js +24 -0
  511. package/dist/lib/chart/geometry.js +114 -0
  512. package/dist/lib/chart/motion.js +34 -0
  513. package/dist/lib/chart/scale.js +72 -0
  514. package/dist/lib/chart/types.js +45 -0
  515. package/dist/lib/chart/use-arrival.js +41 -0
  516. package/dist/{chunk-SRQWQUTW.js → lib/cn.js} +5 -5
  517. package/dist/lib/component-guidance.js +215 -0
  518. package/dist/lib/density.js +54 -0
  519. package/dist/lib/field-shell.js +11 -0
  520. package/dist/lib/glyphs.js +9 -0
  521. package/dist/lib/heading-row.js +36 -0
  522. package/dist/lib/highlight-glide.js +84 -0
  523. package/dist/lib/hit-target.js +5 -0
  524. package/dist/lib/meta.js +0 -0
  525. package/dist/lib/portal-theme-scope.js +74 -0
  526. package/dist/{chunk-FKZXAFIY.js → lib/presentation.js} +8 -3
  527. package/dist/lib/refs.js +14 -0
  528. package/dist/lib/scrub-gesture.js +50 -0
  529. package/dist/lib/segmented.js +13 -0
  530. package/dist/lib/selection-card.js +41 -0
  531. package/dist/lib/status-tone.js +13 -0
  532. package/dist/lib/taxonomy.js +214 -0
  533. package/dist/lib/trim-label.js +20 -0
  534. package/dist/lib/use-copy.js +27 -0
  535. package/dist/lib/use-edge-fade.js +32 -0
  536. package/dist/lib/use-isomorphic-layout-effect.js +8 -0
  537. package/dist/lib/use-resize-observer.js +18 -0
  538. package/dist/lib/use-scroll-activity.js +52 -0
  539. package/dist/lib/use-wheel-to-horizontal.js +65 -0
  540. package/dist/motion/MotionProvider.js +75 -0
  541. package/dist/motion/Presence.js +112 -0
  542. package/dist/motion/Reveal.js +42 -0
  543. package/dist/motion/TextReveal.js +61 -0
  544. package/dist/motion/index.js +24 -0
  545. package/dist/motion/useInView.js +36 -0
  546. package/dist/motion/useReducedMotion.js +25 -0
  547. package/dist/motion/viewTransition.js +47 -0
  548. package/dist/patterns/auth-page.js +90 -0
  549. package/dist/patterns/auth-page.meta.js +27 -0
  550. package/dist/patterns/brand-landing-page.js +341 -0
  551. package/dist/patterns/brand-landing-page.meta.js +28 -0
  552. package/dist/patterns/chat-panel.js +185 -0
  553. package/dist/patterns/chat-panel.meta.js +31 -0
  554. package/dist/patterns/dashboard-page.js +332 -0
  555. package/dist/patterns/dashboard-page.meta.js +29 -0
  556. package/dist/patterns/feed-page.js +205 -0
  557. package/dist/patterns/feed-page.meta.js +25 -0
  558. package/dist/patterns/index.js +34 -2140
  559. package/dist/patterns/landing-page.js +356 -0
  560. package/dist/patterns/landing-page.meta.js +26 -0
  561. package/dist/patterns/list-detail-page.js +373 -0
  562. package/dist/patterns/list-detail-page.meta.js +24 -0
  563. package/dist/patterns/settings-page.js +268 -0
  564. package/dist/patterns/settings-page.meta.js +27 -0
  565. package/dist/presets/index.d.ts +7 -1
  566. package/dist/presets/index.js +53 -185
  567. package/dist/styles.css +118 -3
  568. package/dist/{theme-provider-kQszHYji.d.ts → theme-provider-S0NVJs2Y.d.ts} +6 -1
  569. package/dist/{chunk-NETUFI34.js → themes/_contract.js} +14 -2
  570. package/dist/themes/atelier/index.js +17 -1
  571. package/dist/themes/contrast.js +288 -0
  572. package/dist/themes/ember/ember.css +6 -6
  573. package/dist/themes/ember/index.js +16 -1
  574. package/dist/themes/index.d.ts +1 -1
  575. package/dist/themes/index.js +3 -8
  576. package/dist/themes/meridian/index.js +16 -1
  577. package/dist/themes/poppy/index.js +17 -1
  578. package/dist/themes/prism/index.js +17 -1
  579. package/dist/themes/prism/prism.css +2 -2
  580. package/dist/themes/registry.js +59 -0
  581. package/dist/themes/sable/index.js +16 -1
  582. package/dist/{chunk-EFLC4NA3.js → themes/theme-provider.js} +38 -54
  583. package/dist/themes/verdant/index.js +17 -1
  584. package/llms/blocks/media-player-block.md +20 -7
  585. package/llms/blocks/product-card-block.md +6 -4
  586. package/llms/blocks/task-list-block.md +1 -1
  587. package/llms/blocks/traffic-sources-block.md +1 -1
  588. package/llms/blocks/traffic-trend-block.md +5 -3
  589. package/llms/blocks/verify-code-block.md +1 -1
  590. package/llms/components/app-shell.md +5 -2
  591. package/llms/components/avatar.md +25 -3
  592. package/llms/components/button.md +20 -1
  593. package/llms/components/card.md +1 -1
  594. package/llms/components/disclosure-picker.md +168 -0
  595. package/llms/components/font-picker.md +104 -0
  596. package/llms/components/media-image.md +98 -0
  597. package/llms/components/media-tile.md +143 -0
  598. package/llms/components/page-header.md +6 -5
  599. package/llms/components/pie-chart.md +2 -2
  600. package/llms/components/radial-chart.md +1 -1
  601. package/llms/components/section-header.md +144 -0
  602. package/llms/components/sheet.md +10 -0
  603. package/llms/components/sidebar.md +4 -3
  604. package/llms/components/slider.md +14 -2
  605. package/llms/components/theme-selector.md +44 -4
  606. package/llms/components/tree.md +3 -3
  607. package/llms/layout/grid.md +24 -3
  608. package/llms/layout/region.md +16 -3
  609. package/llms/patterns/dashboard-page.md +3 -6
  610. package/llms/patterns/feed-page.md +4 -4
  611. package/llms/patterns/list-detail-page.md +1 -0
  612. package/llms/patterns/settings-page.md +2 -5
  613. package/llms/reference/utilities.md +84 -4
  614. package/llms/tasks/building-a-page.md +34 -3
  615. package/llms/tasks/converting-an-app.md +14 -4
  616. package/llms/tasks/creating-a-preset.md +4 -1
  617. package/llms/tasks/reviewing-a-design.md +19 -0
  618. package/llms/tasks/using-presets.md +19 -0
  619. package/llms-full.txt +1066 -78
  620. package/llms.txt +26 -9
  621. package/package.json +1 -1
  622. package/registry.json +2342 -164
  623. package/scripts/agent-setup.mjs +90 -0
  624. package/scripts/design-presets.mjs +59 -6
  625. package/scripts/grade.mjs +195 -29
  626. package/scripts/lint-rules.mjs +486 -10
  627. package/scripts/review-evidence.mjs +28 -1
  628. package/skills/sable/SKILL.md +67 -24
  629. package/targets/ios.json +45 -0
  630. package/dist/chunk-3IWZYBGH.js +0 -398
  631. package/dist/chunk-7MG4SHYX.js +0 -15
  632. package/dist/chunk-7QSDYBXO.js +0 -7421
  633. package/dist/chunk-ADLLDWLW.js +0 -15
  634. package/dist/chunk-C4Z3CF54.js +0 -15
  635. package/dist/chunk-EDXLM7YL.js +0 -5078
  636. package/dist/chunk-EHH4MHO6.js +0 -14
  637. package/dist/chunk-K7RUHUGC.js +0 -14
  638. package/dist/chunk-KO3NAEBF.js +0 -15
  639. package/dist/chunk-L4IEIQQA.js +0 -14
  640. package/dist/chunk-QJ6FWTUG.js +0 -40
  641. package/dist/chunk-VVW4UCDI.js +0 -4908
  642. package/dist/chunk-XAZGMY73.js +0 -187
@@ -20,6 +20,96 @@ function entryContent(source, projectPath) {
20
20
  .replace(RESOLVE, `node -p "require.resolve('@sableui/react/registry.json', { paths: ['${projectPath}'] })"`)
21
21
  }
22
22
 
23
+ /**
24
+ * How the project runs its own installed `sable`, read from its lockfile. Never a
25
+ * bare `npx sable`, which would fetch an unrelated public package when this one
26
+ * is missing.
27
+ */
28
+ export function packageManager(root, project) {
29
+ for (let dir = resolve(project); ; dir = dirname(dir)) {
30
+ if (existsSync(join(dir, "pnpm-lock.yaml"))) return "pnpm"
31
+ if (existsSync(join(dir, "yarn.lock"))) return "yarn"
32
+ if (existsSync(join(dir, "package-lock.json"))) return "npm"
33
+ if (dir === resolve(root) || dirname(dir) === dir) break
34
+ }
35
+ return "npm"
36
+ }
37
+
38
+ const STOP_HOOK_MARKER = "sable check --stop-hook"
39
+ // Words both Git Bash and PowerShell 5.1 pass through literally once double-quoted.
40
+ const PORTABLE = /^[\w@./ -]+$/
41
+ const quoted = (s) => (/^[\w./-]+$/.test(s) ? s : `"${s}"`)
42
+
43
+ /**
44
+ * The Claude Code Stop hook that runs `sable check` before a task can end:
45
+ * settings.json → hooks.Stop[] → { hooks: [{ type: "command", command }] }.
46
+ * Stop takes no matcher. The command exits 2 with the failures on stderr, which
47
+ * keeps Claude working and shows it what to fix (code.claude.com/docs/en/hooks).
48
+ *
49
+ * The command is one plain invocation with no shell syntax — no `cd`, no `&&`,
50
+ * no "${CLAUDE_PROJECT_DIR}" — because Claude Code runs hooks under Git Bash or,
51
+ * without it on Windows, PowerShell 5.1, which supports neither. `sable check
52
+ * --stop-hook` reads CLAUDE_PROJECT_DIR from its environment instead, and
53
+ * `--root` names the Sable package when the entries live at a repository root.
54
+ * A workspace package is reached through the package manager's own workspace
55
+ * flag, since its `sable` bin is not on the root's path.
56
+ */
57
+ export function claudeStopHook({ root, project = root }) {
58
+ const projectPath = relative(resolve(root), resolve(project)).replaceAll("\\", "/")
59
+ if (projectPath.startsWith("..") || isAbsolute(projectPath)) throw new Error("--root must be the project directory or one of its ancestors, such as the repository root.")
60
+ if (projectPath && !PORTABLE.test(projectPath)) throw new Error(`Unsupported characters for a portable hook in the project path: ${projectPath}. Add the hook by hand.`)
61
+ const pm = packageManager(root, project)
62
+ let runner = { pnpm: "pnpm exec sable", yarn: "yarn sable", npm: "npm exec --no -- sable" }[pm]
63
+ if (projectPath) {
64
+ let name
65
+ try { name = JSON.parse(readFileSync(join(project, "package.json"), "utf8")).name } catch { /* unnamed */ }
66
+ if (typeof name === "string" && PORTABLE.test(name)) {
67
+ runner = {
68
+ pnpm: `pnpm --filter ${quoted(name)} exec sable`,
69
+ yarn: `yarn workspace ${quoted(name)} sable`,
70
+ npm: `npm exec --workspace ${quoted(name)} --no -- sable`,
71
+ }[pm]
72
+ }
73
+ }
74
+ const command = `${runner} check --stop-hook${projectPath ? ` --root ${quoted(projectPath)}` : ""}`
75
+ const entry = { hooks: [{ type: "command", command, timeout: 300 }] }
76
+ return { settingsFile: ".claude/settings.json", command, entry, settings: { hooks: { Stop: [entry] } }, installed: false }
77
+ }
78
+
79
+ /**
80
+ * Write the Stop hook into `.claude/settings.json`, only when asked (`--hook`).
81
+ * Merges into existing settings, keeps every other key and hook, refuses
82
+ * settings it cannot parse rather than overwrite them, and adds nothing when a
83
+ * Sable check hook is already there.
84
+ */
85
+ export function installClaudeStopHook({ root, project = root, dryRun = false }) {
86
+ const hook = claudeStopHook({ root, project })
87
+ const path = join(root, hook.settingsFile)
88
+ let current = root
89
+ for (const part of hook.settingsFile.split("/")) {
90
+ current = join(current, part)
91
+ try { if (lstatSync(current).isSymbolicLink()) throw new Error(`Refusing a linked destination: ${hook.settingsFile}`) }
92
+ catch (error) { if (error.code !== "ENOENT") throw error }
93
+ }
94
+ let settings = {}
95
+ if (existsSync(path)) {
96
+ try { settings = JSON.parse(readFileSync(path, "utf8")) }
97
+ catch { throw new Error(`${hook.settingsFile} is not valid JSON; fix it or add the hook by hand.`) }
98
+ if (!settings || typeof settings !== "object" || Array.isArray(settings)) throw new Error(`${hook.settingsFile} is not a JSON object.`)
99
+ }
100
+ const hooks = settings.hooks ?? {}
101
+ if (typeof hooks !== "object" || Array.isArray(hooks)) throw new Error(`${hook.settingsFile} has a "hooks" value that is not an object.`)
102
+ const stop = hooks.Stop ?? []
103
+ if (!Array.isArray(stop)) throw new Error(`${hook.settingsFile} has a "hooks.Stop" value that is not an array.`)
104
+ const alreadyPresent = stop.some((group) => (group?.hooks ?? []).some((h) => String(h?.command ?? "").includes(STOP_HOOK_MARKER)))
105
+ if (!alreadyPresent && !dryRun) {
106
+ const next = { ...settings, hooks: { ...hooks, Stop: [...stop, hook.entry] } }
107
+ mkdirSync(dirname(path), { recursive: true })
108
+ writeFileSync(path, `${JSON.stringify(next, null, 2)}\n`)
109
+ }
110
+ return { ...hook, installed: true, alreadyPresent }
111
+ }
112
+
23
113
  /**
24
114
  * Install discoverable loaders; the installed package owns the actual workflow.
25
115
  *
@@ -1,13 +1,47 @@
1
1
  import { readFileSync } from "node:fs"
2
- import { resolve } from "node:path"
2
+ import { parse, resolve } from "node:path"
3
3
  import { fileURLToPath } from "node:url"
4
4
  import { LABEL_CONTRAST_RULE, parsePreset, presetCss, presetLabelContrast } from "../dist/presets/index.js"
5
5
  import { parseFlags } from "./design-preferences.mjs"
6
+ import { packageManager } from "./agent-setup.mjs"
6
7
 
7
8
  const packageRoot = fileURLToPath(new URL("../", import.meta.url))
8
9
  const args = process.argv.slice(2)
9
10
  const json = args.includes("--json")
10
11
  function emit(type, data, output) { process.stdout.write(json ? JSON.stringify({ type, data }) + "\n" : output + "\n") }
12
+
13
+ /** -1, 0 or 1 for two semver strings; a prerelease sorts before its release. */
14
+ function compareVersions(a, b) {
15
+ const parse = (v) => {
16
+ const [core, pre] = String(v).split("-", 2)
17
+ return { nums: core.split(".").map((n) => Number(n) || 0), pre: pre ?? null }
18
+ }
19
+ const x = parse(a), y = parse(b)
20
+ for (let i = 0; i < 3; i++) if ((x.nums[i] ?? 0) !== (y.nums[i] ?? 0)) return (x.nums[i] ?? 0) < (y.nums[i] ?? 0) ? -1 : 1
21
+ if (x.pre === y.pre) return 0
22
+ if (x.pre === null) return 1
23
+ if (y.pre === null) return -1
24
+ return x.pre < y.pre ? -1 : 1
25
+ }
26
+
27
+ /**
28
+ * A preset records the Sable it was authored with. Authored with a NEWER one
29
+ * than is installed, it can name tokens and defaults this package does not have
30
+ * yet; it still validates against the installed contract, so this warns rather
31
+ * than fails — and says how to upgrade, with the project's own package manager.
32
+ */
33
+ function versionWarnings(preset, installed) {
34
+ if (compareVersions(preset.sableVersion, installed) <= 0) return []
35
+ const pm = packageManager(parse(process.cwd()).root, process.cwd())
36
+ const upgrade = { pnpm: "pnpm add @sableui/react@latest", yarn: "yarn add @sableui/react@latest", npm: "npm install @sableui/react@latest" }[pm]
37
+ return [{
38
+ code: "WARN_PRESET_NEWER_SABLE",
39
+ message: `Preset ${preset.id} was authored with @sableui/react ${preset.sableVersion}, newer than the installed ${installed}. It validated against ${installed}; tokens or behaviour added after ${installed} will not apply until you upgrade.`,
40
+ authoredWith: preset.sableVersion,
41
+ installed,
42
+ suggestion: upgrade,
43
+ }]
44
+ }
11
45
  try {
12
46
  const flags = parseFlags(args, ["--check", "--css"], ["--template", "--json"])
13
47
  const modes = ["--template", "--check", "--css"].filter(arg => flags.has(arg))
@@ -21,7 +55,7 @@ try {
21
55
  const target = match[1].includes(".dark") ? groups.dark : groups.light
22
56
  for (const declaration of match[2].matchAll(/--([\w-]+)\s*:\s*([^;]+);/g)) target[declaration[1]] = declaration[2].trim()
23
57
  }
24
- const { PRESET_TOKEN_NAMES } = await import("../dist/presets/index.js")
58
+ const { PAGE_WIDTH_TOKENS, PRESET_TOKEN_NAMES } = await import("../dist/presets/index.js")
25
59
  function tokens(mode) {
26
60
  const values = { ...groups.light, ...(mode === "dark" ? groups.dark : {}) }
27
61
  function resolveValue(value, chain = []) {
@@ -31,7 +65,9 @@ try {
31
65
  return resolveValue(values[name], [...chain, name])
32
66
  })
33
67
  }
34
- return Object.fromEntries(PRESET_TOKEN_NAMES.filter(name => values[name]).map(name => [name, resolveValue(values[name])]))
68
+ // Page widths are opt-in: copying the defaults would override any widths
69
+ // an app already sets in its own CSS, since preset CSS loads after it.
70
+ return Object.fromEntries(PRESET_TOKEN_NAMES.filter(name => values[name] && !PAGE_WIDTH_TOKENS.includes(name)).map(name => [name, resolveValue(values[name])]))
35
71
  }
36
72
  const preset = parsePreset({ schemaVersion: 1, id: "my-preset", label: "My preset", revision: 1, sableVersion: manifest.version, defaultMode: "dark", fonts: { sans: groups.light["font-sans"], mono: groups.light["font-mono"] }, direction: "Content-first composition with a clear primary action. Replace this guidance with the direction for your product.", tokens: { shared: {}, light: tokens("light"), dark: tokens("dark") } })
37
73
  emit("preset.template", preset, JSON.stringify(preset, null, 2))
@@ -39,6 +75,9 @@ try {
39
75
  const file = flags.get(action)
40
76
  if (!file || file.startsWith("--")) throw new Error(`${action} requires a preset JSON path.`)
41
77
  const preset = parsePreset(JSON.parse(readFileSync(resolve(file), "utf8")))
78
+ const installed = JSON.parse(readFileSync(resolve(packageRoot, "package.json"), "utf8")).version
79
+ const warnings = versionWarnings(preset, installed)
80
+ const warningText = warnings.map((w) => `\nWarning: ${w.message}\n → ${w.suggestion}`).join("")
42
81
  if (action === "--check") {
43
82
  // Labels on brand and status fills: Sable's opinion, enforced unless the preset
44
83
  // records that it declines it (`exceptions`), which is then reported.
@@ -58,12 +97,26 @@ try {
58
97
  }
59
98
  const accepted = labels.accepted.map(f => `\n accepted: ${pair(f)} — ${f.reason}`).join("")
60
99
  const unmeasured = labels.unmeasured.length ? `, ${labels.unmeasured.length} unmeasured (${labels.unmeasured.join(", ")})` : ""
100
+ // A passing check is the start of applying a preset, not the end: say what
101
+ // makes the app use it, so a check does not read as "done".
102
+ const fonts = [...new Set(Object.values(preset.fonts).filter(Boolean).map(stack => stack.split(",")[0].trim().replace(/^["']|["']$/g, "")))]
103
+ const next = [
104
+ `sable preset --css ${file} > <stylesheet>, imported after Sable's`,
105
+ `load fonts: ${fonts.join(", ")}`,
106
+ `SableThemeProvider defaultThemeId="${preset.id}" defaultPreference="${preset.defaultMode}" with the preset in customThemes`,
107
+ "build the app, run sable check, review both modes (sable docs using-presets)",
108
+ ]
61
109
  emit(
62
110
  "preset.check",
63
- { valid: true, id: preset.id, revision: preset.revision, authoredWith: preset.sableVersion, labelContrast: { measured: labels.measured, accepted: labels.accepted, unmeasured: labels.unmeasured } },
64
- `Valid preset ${preset.id}, revision ${preset.revision}. Labels on filled roles: ${labels.measured} pairs measured${unmeasured}.${accepted}\nReview rendered contrast and brief fit separately.`,
111
+ { valid: true, id: preset.id, revision: preset.revision, authoredWith: preset.sableVersion, installed, warnings, labelContrast: { measured: labels.measured, accepted: labels.accepted, unmeasured: labels.unmeasured }, fonts, next },
112
+ `Valid preset ${preset.id}, revision ${preset.revision}. Labels on filled roles: ${labels.measured} pairs measured${unmeasured}.${accepted}${warningText}\nReview rendered contrast and brief fit separately.\nTo apply it to an app:\n${next.map(step => ` - ${step}`).join("\n")}`,
65
113
  )
66
- } else { const css = presetCss(preset); emit("preset.css", { id: preset.id, revision: preset.revision, css }, css) }
114
+ } else {
115
+ const css = presetCss(preset)
116
+ // The CSS is the output, so in text mode the warning goes to stderr.
117
+ if (!json && warningText) process.stderr.write(`${warningText.trim()}\n`)
118
+ emit("preset.css", { id: preset.id, revision: preset.revision, css, warnings }, css)
119
+ }
67
120
  }
68
121
  } catch (error) {
69
122
  process.stderr.write(JSON.stringify({ type: "error", code: error.code ?? "ERR_PRESET_INVALID", message: error.message, ...(error.data ? { data: error.data } : {}), suggestions: error.suggestions ?? ["sable docs using-presets", "sable preset --template"] }) + "\n")
package/scripts/grade.mjs CHANGED
@@ -33,7 +33,7 @@ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs"
33
33
  import { createHash } from "node:crypto"
34
34
  import { dirname, extname, join, relative, resolve } from "node:path"
35
35
  import { fileURLToPath } from "node:url"
36
- import { ignoreGlob, lintText, makeRules } from "./lint-rules.mjs"
36
+ import { ignoreGlob, lintText, makeRules, surfaceInventory } from "./lint-rules.mjs"
37
37
  import { labelContrastScanner, statusTextDrift } from "./label-contrast.mjs"
38
38
  import { skeletonize } from "./skeleton.mjs"
39
39
  import { assessReview, reviewTemplate } from "./review-evidence.mjs"
@@ -150,23 +150,150 @@ function exportSurface() {
150
150
  for (const n of REGISTRY.motionLayer?.policy ?? []) names.add(n)
151
151
  for (const n of REGISTRY.motionLayer?.primitives ?? []) names.add(n)
152
152
 
153
- for (const rel of ["dist/index.d.ts", "src/index.ts"]) {
154
- const p = join(PKG_ROOT, rel)
155
- if (!existsSync(p)) continue
156
- const src = readFileSync(p, "utf8")
157
- for (const m of src.matchAll(/export\s+(?:type\s+)?\{([^}]+)\}/g)) {
153
+ const root = SUBPATHS.get("")
154
+ if (root) for (const n of root.names) names.add(n)
155
+ return names
156
+ }
157
+
158
+ /**
159
+ * The export surface of every published subpath, read from the package's own
160
+ * `exports` map (src in the workspace, dist .d.ts once published) by following
161
+ * each entry's `export … from` / `export *` chain.
162
+ *
163
+ * It has to be per subpath. Judging every `@sableui/react/<anything>` import
164
+ * against the root barrel reported `SignInBlock` from `/blocks`,
165
+ * `presetManifest` from `/presets` and `reiconIconSet` from `/icons/reicon` as
166
+ * hallucinations — 25 of them in Sable's own docs app — and a check that cries
167
+ * wolf on correct code is a check people learn to skip.
168
+ *
169
+ * A surface is `complete: false` when a chain leaves the package (`export * from
170
+ * "some-dep"`); names imported from it are then not judged rather than guessed.
171
+ */
172
+ function subpathSurfaces() {
173
+ const pkg = JSON.parse(readFileSync(join(PKG_ROOT, "package.json"), "utf8"))
174
+ const exportsMap = pkg.exports ?? {}
175
+ const out = new Map()
176
+ const typesFile = (entry) => {
177
+ const v = typeof entry === "string" ? entry : (entry?.types ?? entry?.import?.types ?? entry?.default ?? entry?.import)
178
+ if (typeof v !== "string" || !/\.(?:d\.)?[cm]?[jt]sx?$/.test(v)) return null
179
+ return join(PKG_ROOT, v)
180
+ }
181
+ const candidates = (base) => {
182
+ const stem = base.replace(/\.(?:d\.)?[cm]?[jt]sx?$/, "")
183
+ return [base, `${stem}.d.ts`, `${stem}.ts`, `${stem}.tsx`, `${stem}.d.mts`, join(base, "index.d.ts"), join(base, "index.ts"), join(base, "index.tsx")]
184
+ }
185
+ const collect = (file, names, seen, state) => {
186
+ if (seen.has(file)) return
187
+ seen.add(file)
188
+ const src = readFileSync(file, "utf8").replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1")
189
+ for (const m of src.matchAll(/export\s+(?:type\s+)?\{([^}]*)\}/g)) {
158
190
  for (const raw of m[1].split(",")) {
159
- const name = raw.trim().split(/\s+as\s+/).pop()?.trim()
160
- if (name && /^[A-Za-z_$][\w$]*$/.test(name)) names.add(name)
191
+ const name = raw.trim().replace(/^type\s+/, "").split(/\s+as\s+/).pop()?.trim()
192
+ if (name && name !== "default" && /^[A-Za-z_$][\w$]*$/.test(name)) names.add(name)
161
193
  }
162
194
  }
163
- for (const m of src.matchAll(/export\s+(?:declare\s+)?(?:const|function|class)\s+([\w$]+)/g)) names.add(m[1])
164
- break
195
+ for (const m of src.matchAll(/export\s+(?:declare\s+)?(?:default\s+)?(?:abstract\s+)?(?:const|let|var|function\*?|class|type|interface|enum|namespace)\s+([\w$]+)/g)) names.add(m[1])
196
+ for (const m of src.matchAll(/export\s+\*\s+as\s+([\w$]+)\s+from/g)) names.add(m[1])
197
+ for (const m of src.matchAll(/export\s+\*\s+from\s+["']([^"']+)["']/g)) {
198
+ if (!m[1].startsWith(".")) { state.complete = false; continue }
199
+ const next = candidates(resolve(dirname(file), m[1])).find((c) => existsSync(c) && statSync(c).isFile())
200
+ if (next) collect(next, names, seen, state)
201
+ else state.complete = false
202
+ }
165
203
  }
166
- return names
204
+ for (const [key, entry] of Object.entries(exportsMap)) {
205
+ const sub = key === "." ? "" : key.replace(/^\.\//, "")
206
+ const file = typesFile(entry)
207
+ if (!file) {
208
+ out.set(sub, { names: new Set(), complete: true, asset: true })
209
+ continue
210
+ }
211
+ const names = new Set()
212
+ const state = { complete: true }
213
+ const start = candidates(file).find((c) => existsSync(c) && statSync(c).isFile())
214
+ if (start) collect(start, names, new Set(), state)
215
+ else state.complete = false
216
+ out.set(sub, { names, complete: state.complete })
217
+ }
218
+ return out
167
219
  }
220
+ const SUBPATHS = subpathSurfaces()
168
221
  const EXPORTS = exportSurface()
169
222
 
223
+ /**
224
+ * What one file imports from Sable: local name → { name, from }. Aliases
225
+ * (`Button as SableButton`) map back to the Sable name, and a component the app
226
+ * defines itself under a Sable name (its own `PageHeader`) is simply absent, so
227
+ * the prop and icon checks below never judge it against Sable's contract.
228
+ */
229
+ /**
230
+ * The text with every template literal's literal part blanked (newlines kept,
231
+ * so line numbers hold). A docs page or a code-preview builder carries JSX as
232
+ * DATA — `<RadioCard value="post" …>` inside a backtick string, `<Icon
233
+ * name="${glyph}" />` assembled for display — and judging that as rendered JSX
234
+ * reported attributes like `n` (from a `\n` escape) and an icon named
235
+ * `${glyph}`. `${…}` expressions are kept, since they are code.
236
+ */
237
+ function maskTemplates(text) {
238
+ let out = ""
239
+ const stack = [] // "t" = inside a template literal, number = brace depth of a ${ } expression
240
+ let quote = null
241
+ for (let i = 0; i < text.length; i++) {
242
+ const c = text[i]
243
+ const top = stack[stack.length - 1]
244
+ if (top === "t") {
245
+ if (c === "\\") { out += text[i + 1] === "\n" ? " \n" : " "; i++; continue }
246
+ if (c === "`") { stack.pop(); out += c; continue }
247
+ if (c === "$" && text[i + 1] === "{") { stack.push(0); out += "${"; i++; continue }
248
+ out += c === "\n" ? "\n" : " "
249
+ continue
250
+ }
251
+ if (quote) {
252
+ if (c === "\\") { out += c + (text[i + 1] ?? ""); i++; continue }
253
+ if (c === quote || c === "\n") quote = null
254
+ out += c
255
+ continue
256
+ }
257
+ if (c === "/" && text[i + 1] === "/") {
258
+ const nl = text.indexOf("\n", i)
259
+ const end = nl === -1 ? text.length : nl
260
+ out += text.slice(i, end)
261
+ i = end - 1
262
+ continue
263
+ }
264
+ if (c === "/" && text[i + 1] === "*") {
265
+ const end = text.indexOf("*/", i + 2)
266
+ const stop = end === -1 ? text.length : end + 2
267
+ out += text.slice(i, stop)
268
+ i = stop - 1
269
+ continue
270
+ }
271
+ if (c === "'" || c === '"') { quote = c; out += c; continue }
272
+ if (c === "`") { stack.push("t"); out += c; continue }
273
+ if (typeof top === "number") {
274
+ if (c === "{") stack[stack.length - 1]++
275
+ else if (c === "}") {
276
+ if (top === 0) { stack.pop(); out += c; continue }
277
+ stack[stack.length - 1]--
278
+ }
279
+ }
280
+ out += c
281
+ }
282
+ return out
283
+ }
284
+
285
+ const SABLE_IMPORT =/import\s+(?:type\s+)?(?:[\w$]+\s*,\s*)?\{([^}]*)\}\s*from\s*["'](@sableui\/react(?:\/[^"']*)?)["']/g
286
+ function sableImports(text) {
287
+ const out = new Map()
288
+ for (const m of text.matchAll(SABLE_IMPORT)) {
289
+ for (const raw of m[1].split(",")) {
290
+ const [name, local] = raw.trim().replace(/^type\s+/, "").split(/\s+as\s+/).map((s) => s?.trim())
291
+ if (name) out.set(local || name, { name, from: m[2], index: m.index })
292
+ }
293
+ }
294
+ return out
295
+ }
296
+
170
297
  /** Props any element legitimately takes, so they never read as hallucinated. */
171
298
  const UNIVERSAL_PROPS = new Set([
172
299
  "key", "ref", "className", "style", "id", "role", "tabIndex", "title", "hidden", "slot", "children",
@@ -175,6 +302,8 @@ const UNIVERSAL_PROPS = new Set([
175
302
  "disabled", "required", "readOnly", "placeholder", "type", "href", "target", "rel", "src", "alt",
176
303
  "width", "height", "colSpan", "rowSpan", "htmlFor", "autoFocus", "autoComplete", "maxLength",
177
304
  "minLength", "min", "max", "step", "rows", "cols", "open", "selected", "multiple", "loading",
305
+ // Media attributes, for components that spread onto <video>/<audio>/<img> (Video, Image).
306
+ "autoPlay", "muted", "loop", "playsInline", "preload", "crossOrigin", "decoding", "fetchPriority", "srcSet", "sizes",
178
307
  ])
179
308
  const isEventProp = (p) => /^on[A-Z]/.test(p)
180
309
  const isPassthrough = (p) => p.startsWith("data-") || p.startsWith("aria-") || p.startsWith("--")
@@ -237,6 +366,12 @@ const sourceHash = fingerprint.digest("hex")
237
366
  const findings = { contract: [], hallucinatedComponent: [], hallucinatedProp: [], radixIdiom: [], hardcodedControl: [], offContractIcon: [] }
238
367
  let loc = 0
239
368
  const routeSkeletons = []
369
+ /* App-owned surfaces: intrinsic elements wearing a surface's or a shape's
370
+ classes. Never scored — a review list for the reuse inventory, so the agent
371
+ accounts for each one (compose the named component, or say why not) instead
372
+ of listing from memory. The lookalike-* lint rules fail the unambiguous
373
+ cases; this lists the rest. */
374
+ const inventory = []
240
375
 
241
376
  /**
242
377
  * Attribute names on one JSX open tag, stopping at the tag's own `>`.
@@ -298,31 +433,39 @@ for (const file of files) {
298
433
 
299
434
  if (!/\.(tsx|jsx|ts|js)$/.test(extname(file) ? file : "")) continue
300
435
 
301
- /* 2. Imports that name something the package does not export. */
302
- for (const m of text.matchAll(/import\s*(?:type\s*)?\{([^}]+)\}\s*from\s*["']@sableui\/react[^"']*["']/g)) {
303
- for (const raw of m[1].split(",")) {
304
- const name = raw.trim().split(/\s+as\s+/)[0]?.trim()
305
- if (!name || !/^[A-Z]/.test(name)) continue
306
- if (!EXPORTS.has(name)) {
307
- findings.hallucinatedComponent.push({ file: rel, name, line: lineOf(text, m.index) })
308
- }
436
+ /* 2. Imports that name something the subpath they come from does not export.
437
+ Each import is judged against ITS subpath's surface; an unpublished
438
+ subpath is itself the finding. */
439
+ const imported = sableImports(text)
440
+ for (const { name, from, index } of imported.values()) {
441
+ const sub = from === "@sableui/react" ? "" : from.slice("@sableui/react/".length)
442
+ const surface = SUBPATHS.get(sub)
443
+ if (!surface) {
444
+ findings.hallucinatedComponent.push({ file: rel, name, from, reason: "not a published subpath", line: lineOf(text, index) })
445
+ continue
309
446
  }
447
+ const known = surface.names.has(name) || (sub === "" && EXPORTS.has(name))
448
+ if (!known && surface.complete) findings.hallucinatedComponent.push({ file: rel, name, from, line: lineOf(text, index) })
310
449
  }
311
450
 
312
451
  /* 3. Props set on a Sable component that its contract does not declare.
313
452
  The contract is the component's real type (registry → propSurface),
314
453
  which includes what it forwards to Base UI — Toggle's `pressed`,
315
- Combobox's `filter` — plus its documented table. */
316
- for (const m of text.matchAll(/<([A-Z][\w]*)[\s/>]/g)) {
317
- const comp = REGISTRY.components?.find((c) => c.name === m[1])
454
+ Combobox's `filter` — plus its documented table. Only tags the file
455
+ imports from Sable are judged: an app's own `PageHeader` is not ours. */
456
+ const code = maskTemplates(text)
457
+ for (const m of code.matchAll(/<([A-Z][\w]*)[\s/>]/g)) {
458
+ const sableName = imported.get(m[1])?.name
459
+ if (!sableName) continue
460
+ const comp = REGISTRY.components?.find((c) => c.name === sableName)
318
461
  if (!comp?.props?.length) continue
319
- const declared = new Set([...comp.props.map((p) => p.name), ...(PROP_SURFACE[m[1]] ?? [])])
462
+ const declared = new Set([...comp.props.map((p) => p.name), ...(PROP_SURFACE[sableName] ?? [])])
320
463
  for (const sub of comp.subcomponents ?? []) {
321
464
  if (typeof sub !== "string") for (const p of sub.props ?? []) declared.add(p.name)
322
465
  }
323
- for (const attr of attrsOf(text, m.index + m[0].length - 1)) {
466
+ for (const attr of attrsOf(code, m.index + m[0].length - 1)) {
324
467
  if (declared.has(attr) || UNIVERSAL_PROPS.has(attr) || isEventProp(attr) || isPassthrough(attr)) continue
325
- findings.hallucinatedProp.push({ file: rel, component: m[1], prop: attr, line: lineOf(text, m.index) })
468
+ findings.hallucinatedProp.push({ file: rel, component: sableName, prop: attr, line: lineOf(text, m.index) })
326
469
  }
327
470
  }
328
471
 
@@ -345,12 +488,21 @@ for (const file of files) {
345
488
  }
346
489
 
347
490
  /* 6. Icon names outside the contract render nothing at all — unless the app
348
- supplies them itself and has declared them (APP_ICONS). */
349
- for (const m of text.matchAll(/<Icon\s+[^>]*name=["']([^"']+)["']/g)) {
350
- if (!ICONS.has(m[1]) && !APP_ICONS?.names.has(m[1])) findings.offContractIcon.push({ file: rel, name: m[1], line: lineOf(text, m.index) })
491
+ supplies them itself and has declared them (APP_ICONS). Only Sable's
492
+ <Icon> (under whatever local name it was imported as) is judged. */
493
+ for (const [local, { name }] of imported) {
494
+ if (name !== "Icon") continue
495
+ for (const m of code.matchAll(new RegExp(`<${local}\\s+[^>]*name=["']([^"']+)["']`, "g"))) {
496
+ if (!ICONS.has(m[1]) && !APP_ICONS?.names.has(m[1])) findings.offContractIcon.push({ file: rel, name: m[1], line: lineOf(text, m.index) })
497
+ }
498
+ }
499
+
500
+ /* 7. Surfaces the app built itself (informational). */
501
+ if (/\.(tsx|jsx)$/.test(file)) {
502
+ for (const { index, ...s } of surfaceInventory(text)) inventory.push({ file: rel, line: lineOf(text, index), ...s })
351
503
  }
352
504
 
353
- /* 7. Composition. Anything exporting a component that returns a big tree is
505
+ /* 8. Composition. Anything exporting a component that returns a big tree is
354
506
  treated as a route worth comparing against the canonical shells. */
355
507
  if (/\.(tsx|jsx)$/.test(file) && /export\s+(default\s+)?function\s+[A-Z]/.test(text)) {
356
508
  const { tree } = skeletonize(text)
@@ -485,6 +637,13 @@ const result = {
485
637
  counts,
486
638
  per100loc: Object.fromEntries(Object.entries(counts).map(([k, v]) => [k, per100(v)])),
487
639
  conformance: { informationalOnly: true, mean: meanConformance, routes: conformance },
640
+ inventory: {
641
+ informationalOnly: true,
642
+ meaning: "App-owned surfaces to account for in the reuse inventory: compose the named Sable component, or record why the app owns it. Not scored.",
643
+ total: inventory.length,
644
+ byKind: Object.fromEntries(["surface", "media frame", "round"].map((k) => [k, inventory.filter((s) => s.kind === k).length])),
645
+ surfaces: inventory,
646
+ },
488
647
  cliUsage,
489
648
  appIcons: APP_ICONS ? { file: APP_ICONS.file, names: APP_ICONS.names.size } : null,
490
649
  findings,
@@ -519,6 +678,13 @@ if (conformance.length) {
519
678
  } else {
520
679
  L(" composition no route-like files found")
521
680
  }
681
+ if (inventory.length) {
682
+ const kinds = ["surface", "media frame", "round"].map((k) => [k, inventory.filter((s) => s.kind === k).length]).filter(([, n]) => n)
683
+ L()
684
+ L(` app-owned surfaces (review, not scored) — ${inventory.length}: ${kinds.map(([k, n]) => `${n} ${k}`).join(" · ")}`)
685
+ for (const s of inventory.slice(0, 12)) L(` ${s.file}:${s.line} <${s.tag}> ${s.kind} (${s.classes}) → ${s.consider}`)
686
+ if (inventory.length > 12) L(` … ${inventory.length - 12} more (--json lists every one)`)
687
+ }
522
688
  if (cliUsage) {
523
689
  L()
524
690
  L(` sable CLI ${cliUsage.invocations} invocation(s)`)