@sableui/react 0.6.1 → 0.7.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 (633) hide show
  1. package/AGENTS.md +2 -2
  2. package/CHANGELOG.md +285 -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 +200 -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 +33 -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 +611 -33
  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 +25 -6
  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 +19 -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 +210 -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.js +43 -185
  566. package/dist/styles.css +118 -3
  567. package/dist/{theme-provider-kQszHYji.d.ts → theme-provider-S0NVJs2Y.d.ts} +6 -1
  568. package/dist/{chunk-NETUFI34.js → themes/_contract.js} +14 -2
  569. package/dist/themes/atelier/index.js +17 -1
  570. package/dist/themes/contrast.js +288 -0
  571. package/dist/themes/ember/ember.css +6 -6
  572. package/dist/themes/ember/index.js +16 -1
  573. package/dist/themes/index.d.ts +1 -1
  574. package/dist/themes/index.js +3 -8
  575. package/dist/themes/meridian/index.js +16 -1
  576. package/dist/themes/poppy/index.js +17 -1
  577. package/dist/themes/prism/index.js +17 -1
  578. package/dist/themes/prism/prism.css +2 -2
  579. package/dist/themes/registry.js +59 -0
  580. package/dist/themes/sable/index.js +16 -1
  581. package/dist/{chunk-EFLC4NA3.js → themes/theme-provider.js} +38 -54
  582. package/dist/themes/verdant/index.js +17 -1
  583. package/llms/blocks/media-player-block.md +20 -7
  584. package/llms/blocks/product-card-block.md +6 -4
  585. package/llms/blocks/task-list-block.md +1 -1
  586. package/llms/blocks/verify-code-block.md +1 -1
  587. package/llms/components/app-shell.md +5 -2
  588. package/llms/components/avatar.md +25 -3
  589. package/llms/components/button.md +20 -1
  590. package/llms/components/card.md +1 -1
  591. package/llms/components/disclosure-picker.md +168 -0
  592. package/llms/components/font-picker.md +104 -0
  593. package/llms/components/media-image.md +98 -0
  594. package/llms/components/media-tile.md +143 -0
  595. package/llms/components/page-header.md +6 -5
  596. package/llms/components/section-header.md +144 -0
  597. package/llms/components/sheet.md +10 -0
  598. package/llms/components/sidebar.md +4 -3
  599. package/llms/components/slider.md +14 -2
  600. package/llms/components/theme-selector.md +44 -4
  601. package/llms/components/tree.md +3 -3
  602. package/llms/layout/grid.md +24 -3
  603. package/llms/patterns/dashboard-page.md +3 -6
  604. package/llms/patterns/feed-page.md +4 -4
  605. package/llms/patterns/list-detail-page.md +1 -0
  606. package/llms/patterns/settings-page.md +2 -5
  607. package/llms/reference/utilities.md +84 -4
  608. package/llms/tasks/converting-an-app.md +14 -4
  609. package/llms/tasks/reviewing-a-design.md +15 -0
  610. package/llms-full.txt +981 -65
  611. package/llms.txt +27 -10
  612. package/package.json +1 -1
  613. package/registry.json +2324 -171
  614. package/scripts/agent-setup.mjs +90 -0
  615. package/scripts/design-presets.mjs +46 -4
  616. package/scripts/grade.mjs +195 -29
  617. package/scripts/lint-rules.mjs +486 -10
  618. package/scripts/review-evidence.mjs +28 -1
  619. package/skills/sable/SKILL.md +56 -22
  620. package/targets/ios.json +45 -0
  621. package/dist/chunk-3IWZYBGH.js +0 -398
  622. package/dist/chunk-7MG4SHYX.js +0 -15
  623. package/dist/chunk-7QSDYBXO.js +0 -7421
  624. package/dist/chunk-ADLLDWLW.js +0 -15
  625. package/dist/chunk-C4Z3CF54.js +0 -15
  626. package/dist/chunk-EDXLM7YL.js +0 -5078
  627. package/dist/chunk-EHH4MHO6.js +0 -14
  628. package/dist/chunk-K7RUHUGC.js +0 -14
  629. package/dist/chunk-KO3NAEBF.js +0 -15
  630. package/dist/chunk-L4IEIQQA.js +0 -14
  631. package/dist/chunk-QJ6FWTUG.js +0 -40
  632. package/dist/chunk-VVW4UCDI.js +0 -4908
  633. package/dist/chunk-XAZGMY73.js +0 -187
package/AGENTS.md CHANGED
@@ -32,7 +32,7 @@ library gap to report, not a reason for a native control.
32
32
 
33
33
  These are short on purpose. Each links to a foundations doc with the full vocabulary, the reasoning, and before/after examples; `sable-lint` catches most violations.
34
34
 
35
- 0. **Compose from Sable components; never rebuild one.** Before writing any interactive or recognisable element — a button, a field, a toggle, tabs, a menu, a badge, a card, a search box, a dialog — run `npx sable search "<what you need>"` and use what it returns. A raw `<button>`, `<input>` or `<div>` dressed in a component's classes is a reinvention, and it is a bug even when every class is a semantic token: it misses the component's states, density, focus and keyboard contract, and drifts the moment the component changes. A link that looks like a button is `<Button render={<a href=… />}>`. If nothing fits, compose from the nearest component's parts; if it genuinely does not exist, say so in your summary — that is a library gap to report, not a license to invent. The rules below are for styling what you compose *around* components, not for re-creating them. `sable-lint` flags the common cases (`hand-rolled-control`, `lookalike-control`).
35
+ 0. **Compose from Sable components; never rebuild one.** Before writing any interactive or recognisable element — a button, a field, a toggle, tabs, a menu, a badge, a card, a search box, a dialog — run `npx sable search "<what you need>"` and use what it returns. A raw `<button>`, `<input>` or `<div>` dressed in a component's classes is a reinvention, and it is a bug even when every class is a semantic token: it misses the component's states, density, focus and keyboard contract, and drifts the moment the component changes. A link that looks like a button is `<Button render={<a href=… />}>`. If nothing fits, compose from the nearest component's parts; if it genuinely does not exist, say so in your summary — that is a library gap to report, not a license to invent. The rules below are for styling what you compose *around* components, not for re-creating them. `sable-lint` flags the common cases (`hand-rolled-control`, `lookalike-control`), and the bigger assemblies built from utilities and tokens: a raw `grid` of mapped items is `<Grid cols minItemWidth>` (`lookalike-grid`), a link or button wearing a card's radius, surface and padding is `<Card render={<Link … />}>` (`lookalike-card`), a round fixed-size box holding a picture, GradientArt or initials is `<Avatar>` (`lookalike-avatar`), and an `aspect-*` + `overflow-hidden` frame around media is `<AspectBox ratio>` (`lookalike-media-frame`). `sable check` also lists every app-owned surface (a card fill, a bordered padded box, a media frame, a fixed-size circle) for your reuse inventory: each one composes the component named beside it, or the inventory says why the app owns it.
36
36
 
37
37
  **The same goes for everything below a component. Resolve in this order, and stop at the first that fits:** (1) a **component** → (2) a Sable **utility or recipe** — relative surfaces (`sable-surface*`, `sable-well`, `sable-surface-interactive`), state variants (`sable-selected:`, `sable-highlighted:`…), geometry (`rounded-concentric-*`, `popup-inset`, `rounded-popup-item`, `popup-bleed`), scroll policies, `motion-*` recipes → (3) a **semantic token** → (4) hand-written CSS, last, and reported as a gap. `npx sable search "<what you need>"` searches all of these; `registry.json → utilities` lists the utilities with intent and what each replaces. Mixing ink into a surface by hand (`color-mix(… var(--foreground) …)`) is the tell that you skipped step 2 — that is what the relative-surface utilities compute, in every theme and mode.
38
38
 
@@ -61,7 +61,7 @@ The machine-readable version of this section is the `consume` block in `registry
61
61
 
62
62
  `styles.css` carries **Sable only** — the default theme, and the layout floor every other theme inherits (`--page-inset`, `--region-*`, `--rhythm-unit` on a bare `:root`). The bundled **alternates are opt-in**, because they are `[data-sable-theme]`-scoped and an app that never sets the attribute ships CSS that can never match: 5.5 KB gzip of permanently-dead weight, which is exactly how a real app's build audit found this. Using one? `@import "@sableui/react/theme/prism.css";`. Offering the whole set through a `<ThemeSelector>`? `@import "@sableui/react/themes.css";`. **Symptom of forgetting:** `data-sable-theme` is set, nothing matches, and the page silently renders as the Sable theme — no error, just the wrong theme.
63
63
  4. **Fonts:** load Figtree (sans) + DM Mono (mono) — the theme declares the stacks, the host provides the files. Apply `antialiased` (`-webkit-font-smoothing: antialiased` / `-moz-osx-font-smoothing: grayscale`) **once on the app root**: Sable's type scale was drawn against smoothed text, and without it macOS renders every weight heavier than designed. The package deliberately doesn't set it — a library has no business styling the host's whole page — so it has to happen here.
64
- 5. **Providers (all optional):** components render with **Reicon** by default, so `<IconProvider set={…}>` is only needed to replace it with another icon package or extend it using `composeIconSets(appExtras, reiconIconSet)` (list the added names in `.sable/icons.json` as `{ "names": [...] }` so `sable grade` accepts them); `<MotionProvider>` is optional too. Use `<SableThemeProvider>` + `useSableTheme()` when the app should expose user-selectable themes; pair `themeId` / `setThemeId` with `<ThemeSelector>` and `preference` / `setPreference` with `<ThemeSwitcher>`. The provider stores `light` / `dark` / `system`, toggles the `dark` class on `<html>`, stores the active design theme, and writes `data-sable-theme` for non-default themes. Apps with their own state layer can call `applySableColorMode()` / `applySableTheme()` instead. You only need a Tailwind `@custom-variant dark` if **your** app code uses `dark:` utilities (Sable ships none).
64
+ 5. **Providers:** `<IconProvider>` is **required** — wrap the app once in `<IconProvider set={reiconIconSet}>` with `import { reiconIconSet } from "@sableui/react/icons/reicon"` (without it every `<Icon>` renders nothing; development builds warn once). Pass another icon package's set instead to replace Reicon, or extend it using `composeIconSets(appExtras, reiconIconSet)` (list the added names in `.sable/icons.json` as `{ "names": [...] }` so `sable grade` accepts them); `<MotionProvider>` is optional too. Use `<SableThemeProvider>` + `useSableTheme()` when the app should expose user-selectable themes; pair `themeId` / `setThemeId` with `<ThemeSelector>` and `preference` / `setPreference` with `<ThemeSwitcher>`. The provider stores `light` / `dark` / `system`, toggles the `dark` class on `<html>`, stores the active design theme, and writes `data-sable-theme` for non-default themes. Apps with their own state layer can call `applySableColorMode()` / `applySableTheme()` instead. You only need a Tailwind `@custom-variant dark` if **your** app code uses `dark:` utilities (Sable ships none).
65
65
  6. Import from the root barrel: `import { Button } from "@sableui/react"`. Page shells live on the `patterns` subpath (`import { SettingsPage } from "@sableui/react/patterns"`) — preview one, then copy its source from `registry.json → patterns` when the route needs to diverge. Blocks — section-sized compositions such as a sign-in card or a pricing card — live on the `blocks` subpath (`import { SignInBlock } from "@sableui/react/blocks"`) and ship the same way (`registry.json → blocks`); copy one and edit its content constants. A block's props are only `className`, `framed` and its primary action's callback, never content.
66
66
 
67
67
  7. **Check the wiring:** `npx sable doctor`. Every check it runs corresponds to a way Sable fails *silently* — a second React (blank page, "invalid hook call"), a missing `@source` (Tailwind emits none of the package's classes, everything renders unstyled), or `data-sable-theme` set with no theme CSS imported (every theme renders as the Sable theme and the switcher looks broken). Exits 1 on any failure; `--json` for the structured report.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,290 @@
1
1
  # What's new
2
2
 
3
+ ## 0.7.0
4
+
5
+ A dogfooding release. An agent built a music streaming app on Sable with
6
+ Sable's own skill, and this release closes the gaps it hit:
7
+ - media tiles, an image fallback and a persistent player dock;
8
+ - section headers, portrait avatars and in-place pickers;
9
+ - labelled sliders and focusable text links.
10
+ The package is also much smaller per import, `sable check` runs the whole
11
+ finish check in one command, and lint now catches hand-assembled lookalikes of
12
+ Sable components.
13
+
14
+ ### Upgrading from 0.6
15
+
16
+ - **Icons are opt-in.** Wrap the app once in `<IconProvider set={reiconIconSet}>`,
17
+ with `reiconIconSet` from `@sableui/react/icons/reicon`. Without it, every
18
+ `<Icon>` (including those inside Sable components) renders nothing, and
19
+ `sable doctor` reports it.
20
+ - **`SableThemeProvider` defaults to `"system"`.** Pass
21
+ `defaultPreference="dark"` to keep the old first-visit mode.
22
+ - **`Grid minItemWidth` keeps its empty columns (`auto-fill`).** Pass
23
+ `track="fit"` where a fixed set should stretch across the row.
24
+ - **Button's side padding is a variable.** A `px-*` className on a Button no
25
+ longer matches its optical alignment (`bleed`). Set `--button-px` instead.
26
+
27
+ ### Everything in this release
28
+
29
+ - **`SableThemeProvider` follows the visitor's OS on a first visit.**
30
+ `defaultPreference` now defaults to `"system"` instead of `"dark"`. A stored
31
+ choice still wins. **Behaviour change:** an app that relied on the implicit
32
+ dark default should pass `defaultPreference="dark"` (or its preset's
33
+ `defaultMode`).
34
+ - **`Slider` names the slider, not its wrapper.** `aria-label` used to land on
35
+ the Root's `role="group"`, so screen readers announced an unnamed slider. It
36
+ now goes to each thumb's input. New `getAriaLabel` (per thumb, for ranges)
37
+ and `getAriaValueText` (e.g. a seek bar's "1:23 of 3:45").
38
+ - **AppShell `dock` slot** for persistent chrome such as a now-playing or call
39
+ bar. It is full width and sticky to the bottom, above `bottomNav` on mobile,
40
+ and the app Sidebar shortens to clear it.
41
+ - **`link-underline` carries a focus-visible outline** in `--ring`, so plain
42
+ text links meet the focus floor without app CSS.
43
+ - **Media icons:** `shuffle`, `repeat`, `repeat-one`, `volume`, `volume-low`,
44
+ `volume-off`, `queue`, `music`.
45
+ - **`--theme-art-brand` hook.** Ember's and Prism's art (and the default art
46
+ ground) key off it, falling back to `--primary`. Set it wherever the primary
47
+ action stops being the brand, such as a neutral black/white primary, so the
48
+ art keeps the brand colour.
49
+ - **`sable lint`'s `static-interactive` rule reads variants, not substrings.**
50
+ A capability query like `[@media(hover:none)]:opacity-100` is no longer
51
+ mistaken for a hover style.
52
+ - **Breaking: icons are opt-in.** `<Icon>` no longer falls back to Reicon, so
53
+ an app that brings its own set ships none of Reicon's artwork (about 190 KB
54
+ minified). Without an `IconProvider`, icons render nothing and a development
55
+ build warns once. `reiconIconSet`, `reiconIconNames` and the deprecated
56
+ `fallbackIconSet` moved off the root (and `/icons`) barrel to
57
+ `@sableui/react/icons/reicon`. **Migrate:** wrap the app once in
58
+ `<IconProvider set={reiconIconSet}>` with
59
+ `import { reiconIconSet } from "@sableui/react/icons/reicon"`.
60
+ - **Smaller bundles: the package ships one module per source file.** `dist`
61
+ keeps the `src` layout instead of a few large bundles, so a bundler drops
62
+ every component an app does not import. Importing only `Button` used to pull
63
+ in about 70% of the library under esbuild; the import paths and exports are
64
+ unchanged. Each module also keeps its `"use client"` directive.
65
+ - **GradientArt loads its WebGL renderer on first paint.** The shader code
66
+ (about 43 KB minified) is a separate chunk, so a plain `Avatar` no longer
67
+ ships it, and art still shows its CSS stand-in until the shader fades in.
68
+ - **`OtpField` resolves Base UI's OTP parts when it first renders**, so apps
69
+ that never use it no longer carry Base UI's OTP Field.
70
+ - **`sable check`: one command to run before calling a build done.** It runs
71
+ `sable doctor`, `sable lint` and `sable grade` on the given paths (default
72
+ `src`), prints one pass/fail summary, exits 1 if any fails, and then lists the
73
+ review steps no tool can verify (rendered design, journey, motion, reduced
74
+ motion, reuse inventory, lessons). `--json` returns `{ type: "check", data }`.
75
+ The skill's finish checklist now opens the skill and starts with it.
76
+ - **`sable setup --agent` offers a Claude Code Stop hook** that runs
77
+ `sable check --stop-hook` before Claude can end a task: on failure it exits 2
78
+ with the findings on stderr, so Claude keeps working (once; a second
79
+ consecutive block lets it stop). Setup prints the exact `.claude/settings.json`
80
+ entry and writes it only with `--hook`, merging into existing settings. The
81
+ command is one plain invocation with no `cd`, `&&` or `${…}`, so it runs under
82
+ Git Bash and PowerShell 5.1 alike; `sable check --stop-hook` locates the
83
+ project from `CLAUDE_PROJECT_DIR` (plus `--root <dir>` for a workspace
84
+ package, reached with the package manager's workspace flag).
85
+ - **`sable grade` judges each import against the subpath it came from.** It
86
+ checked every `@sableui/react/*` import against the root barrel, so correct
87
+ imports from `/blocks`, `/presets` or `/icons/reicon` read as hallucinations
88
+ (25 in Sable's own docs app). Each subpath's surface is now read from the
89
+ package `exports`, an unpublished subpath is reported as such, and
90
+ `reiconIconSet` imported from the root is caught. Props and icon names are
91
+ judged only on components imported from Sable (aliases followed), not on an
92
+ app's own `PageHeader` or `Icon`, and not on JSX inside template-literal code
93
+ samples. Media attributes such as `autoPlay` are accepted.
94
+ - **`sable doctor` checks icons.** An app that imports Sable with no
95
+ `<IconProvider>` fails with the fix, since every icon would render nothing.
96
+ - **Global flags work on every command.** `sable learn --search "…" --dense`
97
+ failed with "Unknown flag: --dense"; `--dense` and `--detail` are now taken out
98
+ before a command hands off to its script, and `--json --dense` prints that
99
+ script's JSON on one line. A switch no longer swallows the next word
100
+ (`sable component --dense Button`), and a bad `--detail` value is an error on
101
+ every command.
102
+ - **Every utility in styles.css is retrievable.** `sable utility
103
+ motion-icon-swap` answered ERR_UNKNOWN_UTILITY: the 24 `motion-*`/`animate-*`
104
+ recipes were only names in `tokens.motion.recipes`. They now have full entries
105
+ (family `motion`), and `sable utility text-heading-1` resolves the paired type
106
+ tiers. A test asks the CLI for every `@utility` and `@custom-variant`, with an
107
+ explicit `INTERNAL_UTILITIES` list for deliberate exceptions.
108
+ - **Providers are in the catalog.** New `sable provider [Name]` and
109
+ `registry.json → providers` cover SableThemeProvider, IconProvider,
110
+ MotionProvider, ToastProvider, TooltipProvider, CodeHighlightProvider,
111
+ DensityProvider and PresentationProvider: whether each is required, where it
112
+ goes, props and examples. `sable component SableThemeProvider` answers with the
113
+ provider, and search finds them. The build fails on an exported `*Provider`
114
+ documented nowhere.
115
+ - **`sable docs AGENTS` serves the package's AGENTS.md** (also README,
116
+ CONTRIBUTING, DESIGN, CHANGELOG and llms), the file llms.txt points agents to
117
+ first. An unknown doc now lists the package and task guide names.
118
+ - **`sable lint --json`** returns `{ type: "lint", data: { violations, … } }`.
119
+ The skill already told agents to pass `--json`; it printed text.
120
+ - **`sable preset --check` warns when a preset is newer than the installed
121
+ Sable.** A preset authored with a later `sableVersion` used to validate
122
+ silently. It still validates, but now prints both versions and the upgrade
123
+ command for the project's package manager; `--json` carries it as
124
+ `warnings: [{ code: "WARN_PRESET_NEWER_SABLE", authoredWith, installed,
125
+ suggestion }]`. `--css` reports the same warning.
126
+ - **`sable grade --template` shows what an artifact looks like.** It now starts
127
+ with one example screenshot and one example report artifact (they fail
128
+ validation until replaced with real captures), and `sable docs
129
+ reviewing-a-design` lists every artifact field with its meaning.
130
+ - **`SheetList`**: edge-to-edge rows in a Sheet (a queue, notifications, a
131
+ picker), the Sheet equivalent of `CardList`. Entity rows line up with
132
+ `SheetHeader` instead of sitting 12px in, with no `--entity-px` override. The
133
+ Sheet's padding is now the `--sheet-p` variable.
134
+ - **`SidebarBrand`**: the brand row on the same grid as `SidebarItem`, with the
135
+ mark in the icon slot and the name on the label column. It collapses to the
136
+ mark. The dashboard and settings patterns use it.
137
+ - **GradientArt gives each seed its own variant of the palette.** The palette
138
+ turns by up to ±20° of hue and ±0.04 lightness, so presets whose art is one
139
+ colour family (Ember, brand-derived palettes) still tell artists and covers
140
+ apart. The same seed still paints the same art.
141
+ - **Selected table rows paint their fill on the cells**, each overlapping the
142
+ next by 1px, to rule out hairline seams at fractional column widths.
143
+ - **`MediaPlayerBlock`'s seek bar runs in seconds.** The readouts follow the
144
+ drag, and the slider announces "1:24 of 3:40".
145
+ - **`static-interactive` explains display-only hovers.** When a hover only
146
+ flips `display`, visibility or an underline, the hint points at an opacity
147
+ swap or `motion-icon-swap` instead of `motion-interactive`.
148
+ - **ThemeSelector draws each theme as a miniature of itself.** Each tile is
149
+ the theme's background, "Aa" in its display face, a card, and a primary
150
+ button whose corners scale from the theme's real radii, so it shows shape as
151
+ well as colour. Four of six themes used to come out as the same
152
+ white-and-black strip. Names sit in the app's face, so every option is the
153
+ same height. The selected tile takes an outline in the app's own ink, and
154
+ list rows carry a small tile. A light preview is now pinned with `.light`, so
155
+ a dark page no longer paints it dark. The docs' Customize panel uses this
156
+ component instead of its own preset tiles.
157
+ - **Segmented controls follow the theme's control shape.** ThemeSwitcher and
158
+ ToggleGroup `appearance="segmented"` take `--button-radius`, the same corners
159
+ as Button: square in Meridian, 12px in Poppy, a capsule in Sable.
160
+ - **`sable lint` catches assemblies, not just controls.** Four new
161
+ consumer rules (new in 0.7.0, so `sable-lint --since 0.6.2` lists them) find
162
+ pieces built from Sable's utilities and tokens, which every token rule passes:
163
+ `lookalike-grid` (a raw `grid` over mapped or repeated items, or an
164
+ auto-fill `minmax()` track → `Grid`, with the `cols` map spelled out),
165
+ `lookalike-card` (an `<a>`, `<button>` or router `<Link>` with a card's
166
+ radius, surface and padding → `Card render={…}`), `lookalike-avatar` (a
167
+ round fixed-size box holding an image, GradientArt or initials → `Avatar`)
168
+ and `lookalike-media-frame` (`aspect-*` + `overflow-hidden` around media →
169
+ `AspectBox`). Each needs a signal beyond the classes, so a form grid, a text
170
+ link, a status dot or an empty ratio box is left alone. The docs site's own
171
+ item grids now compose `Grid`.
172
+ - **`sable grade` and `sable check` list app-owned surfaces.** Elements with a
173
+ card fill, a bordered padded box, a media frame or a fixed-size circle are
174
+ listed with the component to consider (`inventory` in `--json`). The list is
175
+ never scored and never fails a check; it is the starting point for the reuse
176
+ inventory.
177
+ - **`MediaTile`: a cover-first tile** for albums, videos, podcasts, galleries
178
+ and shelves. Square, `video` or `poster` cover, a title and subtitle that
179
+ can each be links (`href` / `render={<Link />}`, `subtitleHref` /
180
+ `subtitleRender`), an `action` over the cover's corner and a `badge` on it.
181
+ The whole tile follows the title's link without wrapping anything in an
182
+ `<a>`, so the artist link and the play button stay separate controls; Tab
183
+ goes title → subtitle → action.
184
+ - **`MediaImage`: an image with a fallback.** It fills its box, rests on a
185
+ muted surface while loading, fades the image in, and paints GradientArt
186
+ seeded by the item when there is no `src` or it fails to load. It uses
187
+ Base UI's image-load tracking, the same Avatar uses, so a failure before
188
+ hydration is still caught. `MediaPlayerBlock` and `ProductCardBlock` use it
189
+ for their artwork; add the image URL to their content constant.
190
+ - **`reveal-on-hover` utility** for row and tile actions that wait for hover.
191
+ It hides the action only on devices that can hover, shows it on hover or
192
+ focus within the nearest `group` (or `data-reveal-scope`) and while its menu
193
+ is open, and fades on the motion tokens. The action stays in the tab order
194
+ and the accessibility tree, and touch devices always show it. `sable lint`'s
195
+ `static-interactive` rule accepts it as a recipe and now suggests it for a
196
+ hand-written `group-hover:opacity-*` reveal.
197
+ - **`stretched-link` utility.** One link covers its whole item while the
198
+ item's other controls stay clickable, and its focus ring goes around the
199
+ item (`--stretched-link-radius`).
200
+ - **`SectionHeader`** heads each section below the page title: an h2 by
201
+ default (`titleAs` for h3/h4), an optional description, and actions on the
202
+ title row, such as a "View all" link or two small buttons. The actions drop
203
+ beneath the title only when the row runs out of room. `size` sets the look
204
+ apart from the level (`md` text-heading-3, `sm` text-heading-4), and the
205
+ title always carries an id (`titleId`, or a generated one) for
206
+ `<section aria-labelledby>`. It shares PageHeader's title row and has no outer
207
+ spacing, so the section's gap sets the rhythm. PageHeader's docs now point a
208
+ second heading to it.
209
+ - **Avatar `xl` (96px) and `2xl` (160px)** for portraits: profile headers,
210
+ artist heroes and roster tiles. The initials move to `text-heading-2` /
211
+ `text-display-lg`, about a third of the circle as at row sizes. A `status`
212
+ indicator and its cut-out ring scale with the portrait, and the photo fades
213
+ in over the resting surface. AvatarGroup stays at `sm`–`lg`.
214
+ - **Breaking: `Grid minItemWidth` keeps empty columns (`auto-fill`).** With one
215
+ or two items, `auto-fit` collapsed the empty tracks and stretched each item
216
+ across the row, so a single search result rendered as a full-width cover.
217
+ New `track` prop: `"fill"` (default) keeps items at their size whatever their
218
+ count; `"fit"` restores the old stretch for a fixed set designed to span the
219
+ row. **Migrate:** add `track="fit"` to a grid of a known, fixed number of
220
+ cards that should share the row (a KPI row, plan tiers).
221
+ - **New `DisclosurePicker`:** a property row that names the current value and
222
+ opens its list in place, beneath the row, instead of in a popup, for a tool
223
+ panel where the choice is made while looking at what it changes. Parts:
224
+ `DisclosurePickerTrigger` (optional `label` caption and `media`, the value,
225
+ a chevron that flips), `DisclosurePickerPanel`, `DisclosurePickerList` (a
226
+ listbox: arrows move, Enter/Space/click pick and close),
227
+ `DisclosurePickerOption` (optional `media` and `note`) and
228
+ `DisclosurePickerEmpty`, plus `useDisclosurePicker()` for a list it does not
229
+ own, such as a Tree.
230
+ Search is opt-in (`search` on the panel), for lists past about a dozen
231
+ options. Keyboard opens move focus to the search field or the chosen option;
232
+ Escape and picks return it to the row. `size="xs"` sits on the
233
+ property-grid rung.
234
+ - **New `FontPicker`,** built on DisclosurePicker: the row names the family in
235
+ its own face, and the list draws each family in itself. Fonts and loading
236
+ come from the app (`fonts`, `loadFont(font, "preview" | "use")`); Sable
237
+ bundles no catalog. Search is on by default; pass `searchable={false}` for a
238
+ short list.
239
+ - **Button `bleed` — optical alignment for borderless buttons.** A ghost or
240
+ link button carries padding you can't see, so at the start or end of a row
241
+ its label sat ~12px inside the edge the row shares. `bleed="start" | "end" |
242
+ "both"` pulls the box out by exactly its own padding (the size's new
243
+ `--button-px`, which the padding now reads; the icon side's optical padding
244
+ when an icon sits there; an icon-only size's inset to its glyph), so the
245
+ label lands on the edge while the hit area and hover fill keep their full
246
+ size. Logical sides, so RTL mirrors. A no-op on variants with a visible edge
247
+ (primary, secondary, outline, destructive, floating, field, on-color,
248
+ on-color-outline). SectionHeader and PageHeader actions, PageHeader's back
249
+ control and CardAction now align their outermost ghost/link Button
250
+ automatically (it renders `data-bleed="auto"`; `bleed="none"` opts out);
251
+ when a header's actions wrap beneath the title, the first one aligns to the
252
+ start edge. Applied in the dashboard, feed and list-detail patterns and the
253
+ task-list and verify-code blocks (the feed's hand-set `-ms-3` is gone).
254
+ **Migrate:** replace `-ms-3` / `-me-3` nudges on ghost buttons with `bleed`;
255
+ to retune a button's padding set `[--button-px:…]` rather than `px-*`.
256
+ - **`DisclosurePicker` commits on Enter, not on every arrow key.** Its list
257
+ was a radio group, whose checked state follows focus, so each arrow press
258
+ called `onValueChange`: in the docs Customize panel the first ArrowDown on
259
+ the preset row switched presets (or opened "discard changes?"), and on a font
260
+ row loaded every family passed. The list is now a `role="listbox"` of
261
+ `role="option"` rows: ArrowUp/ArrowDown, Home/End and type-ahead move focus
262
+ (roving tabindex) and apply nothing; Enter, Space or a click picks, applies
263
+ and closes; Escape closes with the value untouched. `aria-selected` marks the
264
+ committed value. `onValueChange` fires once per pick, and not when the pick
265
+ is the current value. ArrowUp from the first option returns to the search
266
+ field. New on `DisclosurePickerOption`: `media` (a leading tile),
267
+ `disabled`, `textValue` (what type-ahead matches). Changed from the
268
+ unreleased radio version: the list takes div props instead of RadioGroup
269
+ props, options are no longer
270
+ `role="radio"`, and `DisclosurePickerEmpty` belongs in place of the list,
271
+ not inside it. **FontPicker** inherits it: arrows walk the families, Enter
272
+ picks and loads one. ThemeSelector's `layout="list"` stays a radio group
273
+ (instant switching is standard there); its guidance now says to use a
274
+ DisclosurePicker list with `ThemePreview` media when a switch has a cost.
275
+
276
+ ## 0.6.2
277
+
278
+ - **`llms.txt` names the default theme correctly.** Its summary still called
279
+ the default theme Base, which was renamed Sable in 0.4.0; the first line an
280
+ agent reads now says Sable.
281
+ - **sableui.com is readable without JavaScript.** Every docs page now carries
282
+ its content as plain HTML in the page itself, so an agent fetching a URL such
283
+ as /getting-started gets the install steps rather than only the title (the
284
+ app replaces that copy when it boots). Every page is also available as
285
+ markdown at its own URL plus `.md`, for example /getting-started.md, and
286
+ each page opens with links to that, /llms.txt and /llms-full.txt.
287
+
3
288
  ## 0.6.1
4
289
 
5
290
  - **`sable lint` catches a status fill retuned without its text.** Since 0.6,
package/CONTRIBUTING.md CHANGED
@@ -11,7 +11,7 @@ The checks are the specification. Every rule below names the gate that enforces
11
11
  1. **Components reference semantic tokens only.** Use `bg-card`, `text-muted-foreground`, `bg-tone-edition`, `text-primary`, etc. — never raw palette steps (`--purple-heart-700`) and never inline `oklch()`/hex. For text/icons on a tone fill use the theme-adaptive `text-tone-*-foreground` ink (no `dark:` variant); for scrims, media letterbox, and pills on imagery use `bg-overlay`/`text-overlay-foreground`. Palette names are theme-internal; a component that names a palette breaks themeability — and `design-lint`'s `palette-ref` rule now has **zero** sanctioned exceptions.
12
12
  2. **Paired typography utilities, never decomposed.** Use `text-heading-1`, `text-body-md`, `text-label-lg`, … Each bundles size + weight + line-height + tracking. Do **not** combine `text-{size}` + `font-{weight}` + `tracking-{x}` by hand. (Icons are the exception — size them with `text-{size}` scales, never type utilities.)
13
13
  3. **Base UI is behavior; `components/` is the public API.** Each component wraps `@base-ui/react` directly inside its own folder (use the **`render` prop** for composition; read state from `data-[state=…]` attributes) and owns 100% of the styling. There is no separate primitives tier. Consumers import only from the package root barrel.
14
- 4. **Semantic icons in components.** Use `<Icon name="…">`; Reicon is the default. Concrete icon-package imports belong only in `src/icon-sets/`. Apps may override or extend the set through `<IconProvider>`.
14
+ 4. **Semantic icons in components.** Use `<Icon name="…">`. Concrete icon-package imports belong only in `src/icon-sets/`, which nothing in the core graph imports. Apps supply the set through `<IconProvider>` (Reicon from `@sableui/react/icons/reicon`, or their own).
15
15
  5. **Every scroll container declares a scrollbar policy.** An `overflow-*-auto|scroll` utility must be paired with `scrollbar-quiet` (a 4px thumb, invisible at rest, fading in while the region scrolls) or `scrollbar-hidden` (no bar — only when something else carries the affordance, and you should be able to name it). Left bare, the platform draws its own: on Windows that means stepper arrows hard against the padding edge, poking out of any rounded corner. Vertical scrolling takes a bar; **horizontal takes none** — an edge fade says "there is more this way" better than a rail does, which is why `Scroller` splits by axis. New scroll container? Reach for `useScrollActivity` so the bar appears on touch drags and programmatic scrolls, which hover cannot see. Enforced by `design-lint`'s `unmanaged-overflow` **and** by `build-registry` over every registry example. Full rationale: `DESIGN.md → Scrolling`.
16
16
  6. **Tailwind v4 packaging discipline.** `src/styles.css` must **not** `@import "tailwindcss"` (the consumer owns that). It carries only token blocks, `@theme inline` mappings, `@utility` typography, layout vars, and `@source`. `@source` points at `src` (never the gitignored `dist`).
17
17
 
@@ -139,19 +139,25 @@ The manifest's `fonts` field is `{ sans: string; mono: string; display?: string
139
139
  ## Icon contract
140
140
 
141
141
  Components reference semantic names through `<Icon name="…" />`. Reicon (MIT)
142
- is the default, installed as a runtime dependency and kept external to the build.
143
- Concrete icon imports belong only in `src/icon-sets/`; components never import
144
- an icon package or duplicate its SVG artwork.
145
-
146
- - **Use the default:** render components without an icon provider.
142
+ is the bundled set, installed as a runtime dependency and kept external to the
143
+ build. It is opt-in: `<Icon>` has no fallback, so an app that brings its own set
144
+ ships none of Reicon's artwork. Concrete icon imports belong only in
145
+ `src/icon-sets/`; components never import an icon package or duplicate its SVG
146
+ artwork, and nothing in the core graph imports `src/icon-sets/`.
147
+
148
+ - **Use Reicon:** `import { reiconIconSet } from "@sableui/react/icons/reicon"` and
149
+ mount `<IconProvider set={reiconIconSet}>` once at the app root. Without a
150
+ provider, icons render nothing and a development build warns once.
147
151
  - **Replace the package:** pass your own `IconSet` to `<IconProvider set={mySet}>`.
148
152
  Map semantic names to that package's components; preserve className, variant,
149
153
  spin and accessible labels. Return `null` for unknown names.
150
- - **Extend Reicon:** import `composeIconSets` and `reiconIconSet` from Sable and
151
- use `<IconProvider set={composeIconSets(appExtras, reiconIconSet)}>`.
154
+ - **Extend Reicon:** import `composeIconSets` from Sable and `reiconIconSet` from
155
+ `@sableui/react/icons/reicon`, then use
156
+ `<IconProvider set={composeIconSets(appExtras, reiconIconSet)}>`.
152
157
  The first set returning a non-null node wins.
153
- - `@sableui/react/icons/reicon` remains an alternative import for the same adapter.
154
- `fallbackIconSet` is a deprecated alias of Reicon, with no separate artwork.
158
+ - `@sableui/react/icons/reicon` is the only import path for the adapter; the root
159
+ barrel does not re-export it. `fallbackIconSet` is a deprecated alias of Reicon
160
+ on the same subpath, with no separate artwork.
155
161
  - Brand marks are a separate additive set at `@sableui/react/icons/brands`.
156
162
  The 23 marks are generated from licensed `reicon-brands` npm artwork by
157
163
  `pnpm icons:brands`, normalized to the 24-unit grid, and inherit currentColor.
package/DESIGN.md CHANGED
@@ -329,7 +329,7 @@ The same 4px scale governs **sizes**, so controls, icons, and avatars all land o
329
329
 
330
330
  - **Controls** — `sm` h-8 (32, compact / chips) · **`md` h-10 (40) — the default** (Input, Button, Select, the field row) · `lg` h-12 (48, prominent / touch-first) · `xl` h-14 (56, hero / mobile CTA).
331
331
  - **Icons** — `xs` 14 · `sm` 16 · **`md` 20 — default UI glyph** · `lg` 24 · `xl` 32. Icons are em-sized with `text-{size}` (never type utilities) and inherit text color.
332
- - **Avatars / media** — `sm` 24 · `md` 32 · `lg` 40 · `xl` 48, then up the scale (`size-16`, `size-24`).
332
+ - **Avatars** — `sm` 32 · **`md` 40 — default** · `lg` 48 (rows with a title and subtitle) · `xl` 96 (a profile header beside a name) · `2xl` 160 (an artist or profile hero, a roster portrait). `xl` doubles `lg` and `2xl` is ×1.67 again — steps far enough apart that two neighbours never read as the same size. Media beyond that is an `AspectBox`, not an avatar.
333
333
  - **Touch targets** — interactive elements clear **44×44px** (`size-11`, WCAG 2.5.5). A visually smaller control (a 32px icon button) keeps the 44px *hit* area via padding or hit-slop, not a bigger glyph. The `hit-target` utility is the sanctioned implementation: a centered 44px `::after`, so the hit area grows without the layout moving. Never on an `<input>` — replaced elements don't render pseudo-elements reliably; wrap it in a label instead.
334
334
 
335
335
  **Borderless controls need roughly twice the clearance of bordered ones.** A bordered control declares where it ends; a borderless one has nothing but the gap to say so, and two ghost buttons 12px apart read as one wide target. Budget around 24px between adjacent borderless controls where 12px would be right for bordered ones.
@@ -369,7 +369,7 @@ Sable's layout foundation is deliberately fluid: **edge-to-edge, with a fluid pa
369
369
 
370
370
  (`--region-ultrawide` 1800 is legacy; prefer `full`.)
371
371
 
372
- **Primitives** (in `src/layout/`): `Region` (fluid page region: `inset`, opt-in `max` as a named region *or any CSS length*, per-edge `safeArea`) · `Page` (route shell — `Region` + a vertically-rhythmic `Stack`) · `Grid` (CSS Grid; explicit `cols` or `auto-fit minmax` so cells breathe) · `Columns`/`Col` (track + spans) · `Bleed` (escapes a capped/inset ancestor to the **content-area** edge, via container-query units, so it stops at a fixed sidebar) · `SplitPane` (responsive two-pane, side-by-side above a breakpoint, stacked below) · `Stack` (vertical rhythm) · `Row` (horizontal flex) · `Spacer` · `Center` (optional prose cap) · `AspectBox` (reserves space, no layout shift) · `SafeArea` · `GridOverlay` (structure visualizer for docs/dev).
372
+ **Primitives** (in `src/layout/`): `Region` (fluid page region: `inset`, opt-in `max` as a named region *or any CSS length*, per-edge `safeArea`) · `Page` (route shell — `Region` + a vertically-rhythmic `Stack`) · `Grid` (CSS Grid; explicit `cols` or `minItemWidth` cells that breathe — `auto-fill` by default, so a sparse row keeps its empty columns and one result stays one cell wide; `track="fit"` stretches a fixed set across the row) · `Columns`/`Col` (track + spans) · `Bleed` (escapes a capped/inset ancestor to the **content-area** edge, via container-query units, so it stops at a fixed sidebar) · `SplitPane` (responsive two-pane, side-by-side above a breakpoint, stacked below) · `Stack` (vertical rhythm) · `Row` (horizontal flex) · `Spacer` · `Center` (optional prose cap) · `AspectBox` (reserves space, no layout shift) · `SafeArea` · `GridOverlay` (structure visualizer for docs/dev).
373
373
 
374
374
  `Columns count`, `Col span`/`start`, and `Grid cols` accept either a number **or a responsive breakpoint map** (`{ base, sm, md, lg, xl, "2xl" }`) — resolved mobile-first by the stylesheet, so a grid can thin to fewer columns on small screens without the consumer's Tailwind scanning a class.
375
375
 
@@ -528,7 +528,7 @@ Not every removal is a regression; check for an **equivalent replacement** befor
528
528
  - Pure `#000`/`#fff` (use `zinc-950`/`zinc-50`, or `overlay`/`overlay-foreground` for scrims). *(CI-enforced: `raw-black-white`.)*
529
529
  - In the Sable theme, purple/brand for primary CTAs (brand = editions + selection only). A different theme chooses its action palette through the semantic pair.
530
530
  - Gradient text, side-stripe borders, nested cards, mixed radii.
531
- - Concrete icon-package imports scattered through components. Reicon supplies the default through the shared adapter; IconProvider allows replacement.
531
+ - Concrete icon-package imports scattered through components. Reicon is an opt-in set behind the shared adapter; the app's IconProvider supplies it or a replacement.
532
532
  - A global page max-width (use opt-in region caps).
533
533
  - Off-scale spacing or sizing (`p-[15px]`, `h-[37px]`) — snap to the 4px scale. In a component, a fixed `rem`/`px` padding or gap also escapes the density control. *(CI-enforced in the package: `fixed-spacing`.)*
534
534
  - Restyling a library component with override classes to get a look it lacks (`<Button className="rounded-full bg-…">`, `<MenuItem className="text-destructive">`, `pl-6` on a table cell). Use or add a real variant. *(CI-enforced: `component-restyle`, `destructive-item-ink`, `table-edge-padding`.)*
package/README.md CHANGED
@@ -21,7 +21,7 @@ in Sable.
21
21
 
22
22
  - **Behavior:** [Base UI](https://base-ui.com) (`@base-ui/react`) — accessible primitives, isolated behind our own component APIs.
23
23
  - **Styling:** Tailwind CSS v4 + semantic CSS-variable tokens; variants via `class-variance-authority`.
24
- - **Icons:** Reicon by default, with a pluggable adapter for any icon package.
24
+ - **Icons:** opt-in through `IconProvider` — Reicon's set ships ready to mount, or plug in any icon package. Apps pay only for the set they choose.
25
25
  - **Motion:** a token-driven, zero-dependency motion layer (recipes, policy, browser-native primitives).
26
26
  - **Themeable:** components reference semantic tokens only; a theme is a CSS variable set.
27
27
 
@@ -98,7 +98,7 @@ function ThemeControls() {
98
98
  )
99
99
  }
100
100
 
101
- <IconProvider set={reiconIconSet}> {/* optional — omit for Reicon, or pass another icon set */}
101
+ <IconProvider set={reiconIconSet}> {/* required — Reicon, or pass another icon set */}
102
102
  <MotionProvider> {/* optional — enables the motion on/off toggle */}
103
103
  <SableThemeProvider> {/* optional, recommended when users can switch themes */}
104
104
  <App />
@@ -107,7 +107,7 @@ function ThemeControls() {
107
107
  </IconProvider>
108
108
  ```
109
109
 
110
- Components render Reicon icons out of the box. To replace Reicon, mount `<IconProvider set={yourIconSet}>`; to add domain icons, use `composeIconSets(appExtras, reiconIconSet)`. Both helpers and the `IconSet` type are exported from Sable. A set returns `null` for names it does not handle. List the names your extras add in `.sable/icons.json` (`{ "names": [...] }`) so `sable grade` counts them as icons the app supplies, not off-contract ones. `fallbackIconSet` remains a deprecated alias of Reicon for compatibility. `SableThemeProvider` is the preferred route for user-facing theme switching: it stores the user's color preference (`light` / `dark` / `system`), toggles the **`.dark` class**, stores the active design theme, and writes **`data-sable-theme`** for non-default themes. If your app already owns this state, use `applySableColorMode()` / `applySableTheme()` or the exported `SABLE_THEME_ATTRIBUTE` constant directly. You do **not** need a Tailwind `@custom-variant dark` for Sable itself — add one (`@custom-variant dark (&:where(.dark, .dark *));`) only if **your own** app code uses `dark:` utilities (Sable ships none).
110
+ Icons are opt-in: mount `<IconProvider set={reiconIconSet}>` once at the app root, with `reiconIconSet` from `@sableui/react/icons/reicon` (its only import path). Without a provider every `<Icon>` renders nothing, and a development build warns once. An app that passes its own set ships none of Reicon's artwork. To add domain icons, use `composeIconSets(appExtras, reiconIconSet)`; `composeIconSets` and the `IconSet` type are exported from the root. A set returns `null` for names it does not handle. List the names your extras add in `.sable/icons.json` (`{ "names": [...] }`) so `sable grade` counts them as icons the app supplies, not off-contract ones. `fallbackIconSet` remains a deprecated alias of Reicon, exported from `@sableui/react/icons/reicon`. `SableThemeProvider` is the preferred route for user-facing theme switching: it stores the user's color preference (`light` / `dark` / `system`), toggles the **`.dark` class**, stores the active design theme, and writes **`data-sable-theme`** for non-default themes. If your app already owns this state, use `applySableColorMode()` / `applySableTheme()` or the exported `SABLE_THEME_ATTRIBUTE` constant directly. You do **not** need a Tailwind `@custom-variant dark` for Sable itself — add one (`@custom-variant dark (&:where(.dark, .dark *));`) only if **your own** app code uses `dark:` utilities (Sable ships none).
111
111
 
112
112
  **6. Use it:**
113
113
 
@@ -58,6 +58,10 @@ const rules = makeRules({ semanticTokens, motionTokens, labelContrast: await lab
58
58
 
59
59
  /* ── Args. ─────────────────────────────────────────────────────────────────── */
60
60
  const args = process.argv.slice(2)
61
+ /* --json: agents and `sable check` read the result instead of scraping the
62
+ report. The skill told agents to run `sable lint <paths> --json` while this
63
+ printed the text report regardless. */
64
+ const JSON_OUT = args.includes("--json")
61
65
  if (args.includes("--help") || args.includes("-h")) {
62
66
  console.log(
63
67
  [
@@ -67,6 +71,7 @@ if (args.includes("--help") || args.includes("-h")) {
67
71
  " sable-lint --ignore <glob> skip matching paths (repeatable)",
68
72
  " sable-lint --rules list the rules",
69
73
  " sable-lint --since <ver> what the contract added since that version",
74
+ ' sable-lint --json print { type: "lint", data } instead of the report',
70
75
  "",
71
76
  `Contract: @sableui/react ${REGISTRY.version} (${semanticTokens.size} semantic tokens)`,
72
77
  "",
@@ -131,6 +136,13 @@ const targets = args.filter((a, i) => !a.startsWith("-") && args[i - 1] !== "--i
131
136
  const dirs = (targets.length ? targets : ["src"]).map((d) => resolve(CWD, d))
132
137
  const missing = dirs.filter((d) => !existsSync(d))
133
138
  if (missing.length) {
139
+ if (JSON_OUT) {
140
+ const message = `Not found: ${missing.map((d) => relative(CWD, d)).join(", ")}`
141
+ process.stdout.write(
142
+ `${JSON.stringify({ type: "error", code: "ERR_LINT_INPUT", message, suggestions: ["Pass the directories to lint, e.g. sable lint app components"] })}\n`,
143
+ )
144
+ process.exit(1)
145
+ }
134
146
  console.error(
135
147
  `[sable-lint] Not found: ${missing.map((d) => relative(CWD, d)).join(", ")}\n` +
136
148
  "Pass the directories to lint, e.g. `sable-lint app components`.",
@@ -191,6 +203,12 @@ for (const dir of dirs) {
191
203
  }
192
204
  }
193
205
 
206
+ if (JSON_OUT) {
207
+ const data = { version: REGISTRY.version, filesScanned, failed: violations.length > 0, violations }
208
+ process.stdout.write(`${JSON.stringify({ type: "lint", data }, null, 1)}\n`)
209
+ process.exit(violations.length ? 1 : 0)
210
+ }
211
+
194
212
  process.exit(
195
213
  report(
196
214
  "sable-lint",