@webjsdev/ui 0.3.17 → 0.4.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 (358) hide show
  1. package/README.md +66 -7
  2. package/THIRD_PARTY_NOTICES.md +349 -0
  3. package/package.json +4 -2
  4. package/packages/registry/blocks/_shared/placeholder.svg +1 -0
  5. package/packages/registry/blocks/charts/chart-area-axes.ts +72 -0
  6. package/packages/registry/blocks/charts/chart-area-default.ts +64 -0
  7. package/packages/registry/blocks/charts/chart-area-gradient.ts +82 -0
  8. package/packages/registry/blocks/charts/chart-area-icons.ts +77 -0
  9. package/packages/registry/blocks/charts/chart-area-interactive.ts +204 -0
  10. package/packages/registry/blocks/charts/chart-area-legend.ts +72 -0
  11. package/packages/registry/blocks/charts/chart-area-linear.ts +64 -0
  12. package/packages/registry/blocks/charts/chart-area-stacked-expand.ts +77 -0
  13. package/packages/registry/blocks/charts/chart-area-stacked.ts +71 -0
  14. package/packages/registry/blocks/charts/chart-area-step.ts +68 -0
  15. package/packages/registry/blocks/charts/chart-bar-active.ts +93 -0
  16. package/packages/registry/blocks/charts/chart-bar-default.ts +60 -0
  17. package/packages/registry/blocks/charts/chart-bar-horizontal.ts +62 -0
  18. package/packages/registry/blocks/charts/chart-bar-interactive.ts +185 -0
  19. package/packages/registry/blocks/charts/chart-bar-label-custom.ts +78 -0
  20. package/packages/registry/blocks/charts/chart-bar-label.ts +66 -0
  21. package/packages/registry/blocks/charts/chart-bar-mixed.ts +87 -0
  22. package/packages/registry/blocks/charts/chart-bar-multiple.ts +67 -0
  23. package/packages/registry/blocks/charts/chart-bar-negative.ts +61 -0
  24. package/packages/registry/blocks/charts/chart-bar-stacked.ts +68 -0
  25. package/packages/registry/blocks/charts/chart-line-default.ts +61 -0
  26. package/packages/registry/blocks/charts/chart-line-dots-colors.ts +86 -0
  27. package/packages/registry/blocks/charts/chart-line-dots-custom.ts +75 -0
  28. package/packages/registry/blocks/charts/chart-line-dots.ts +65 -0
  29. package/packages/registry/blocks/charts/chart-line-interactive.ts +185 -0
  30. package/packages/registry/blocks/charts/chart-line-label-custom.ts +94 -0
  31. package/packages/registry/blocks/charts/chart-line-label.ts +73 -0
  32. package/packages/registry/blocks/charts/chart-line-linear.ts +61 -0
  33. package/packages/registry/blocks/charts/chart-line-multiple.ts +71 -0
  34. package/packages/registry/blocks/charts/chart-line-step.ts +61 -0
  35. package/packages/registry/blocks/charts/chart-pie-donut-active.ts +86 -0
  36. package/packages/registry/blocks/charts/chart-pie-donut-text.ts +89 -0
  37. package/packages/registry/blocks/charts/chart-pie-donut.ts +77 -0
  38. package/packages/registry/blocks/charts/chart-pie-interactive.ts +120 -0
  39. package/packages/registry/blocks/charts/chart-pie-label-custom.ts +82 -0
  40. package/packages/registry/blocks/charts/chart-pie-label-list.ts +86 -0
  41. package/packages/registry/blocks/charts/chart-pie-label.ts +77 -0
  42. package/packages/registry/blocks/charts/chart-pie-legend.ts +79 -0
  43. package/packages/registry/blocks/charts/chart-pie-separator-none.ts +77 -0
  44. package/packages/registry/blocks/charts/chart-pie-simple.ts +77 -0
  45. package/packages/registry/blocks/charts/chart-pie-stacked.ts +99 -0
  46. package/packages/registry/blocks/charts/chart-radar-default.ts +61 -0
  47. package/packages/registry/blocks/charts/chart-radar-dots.ts +61 -0
  48. package/packages/registry/blocks/charts/chart-radar-grid-circle-fill.ts +61 -0
  49. package/packages/registry/blocks/charts/chart-radar-grid-circle-no-lines.ts +61 -0
  50. package/packages/registry/blocks/charts/chart-radar-grid-circle.ts +61 -0
  51. package/packages/registry/blocks/charts/chart-radar-grid-custom.ts +61 -0
  52. package/packages/registry/blocks/charts/chart-radar-grid-fill.ts +61 -0
  53. package/packages/registry/blocks/charts/chart-radar-grid-none.ts +60 -0
  54. package/packages/registry/blocks/charts/chart-radar-icons.ts +76 -0
  55. package/packages/registry/blocks/charts/chart-radar-label-custom.ts +77 -0
  56. package/packages/registry/blocks/charts/chart-radar-legend.ts +70 -0
  57. package/packages/registry/blocks/charts/chart-radar-lines-only.ts +68 -0
  58. package/packages/registry/blocks/charts/chart-radar-multiple.ts +68 -0
  59. package/packages/registry/blocks/charts/chart-radar-radius.ts +68 -0
  60. package/packages/registry/blocks/charts/chart-radial-grid.ts +80 -0
  61. package/packages/registry/blocks/charts/chart-radial-label.ts +85 -0
  62. package/packages/registry/blocks/charts/chart-radial-shape.ts +81 -0
  63. package/packages/registry/blocks/charts/chart-radial-simple.ts +79 -0
  64. package/packages/registry/blocks/charts/chart-radial-stacked.ts +69 -0
  65. package/packages/registry/blocks/charts/chart-radial-text.ts +82 -0
  66. package/packages/registry/blocks/charts/chart-tooltip-advanced.ts +84 -0
  67. package/packages/registry/blocks/charts/chart-tooltip-default.ts +62 -0
  68. package/packages/registry/blocks/charts/chart-tooltip-formatter.ts +75 -0
  69. package/packages/registry/blocks/charts/chart-tooltip-icons.ts +68 -0
  70. package/packages/registry/blocks/charts/chart-tooltip-indicator-line.ts +62 -0
  71. package/packages/registry/blocks/charts/chart-tooltip-indicator-none.ts +62 -0
  72. package/packages/registry/blocks/charts/chart-tooltip-label-custom.ts +65 -0
  73. package/packages/registry/blocks/charts/chart-tooltip-label-formatter.ts +66 -0
  74. package/packages/registry/blocks/charts/chart-tooltip-label-none.ts +62 -0
  75. package/packages/registry/blocks/dashboard-01/components/app-sidebar.ts +108 -0
  76. package/packages/registry/blocks/dashboard-01/components/chart-area-interactive.ts +276 -0
  77. package/packages/registry/blocks/dashboard-01/components/dashboard-data.ts +89 -0
  78. package/packages/registry/blocks/dashboard-01/components/data-table.ts +637 -0
  79. package/packages/registry/blocks/dashboard-01/components/nav-documents.ts +65 -0
  80. package/packages/registry/blocks/dashboard-01/components/nav-main.ts +61 -0
  81. package/packages/registry/blocks/dashboard-01/components/nav-secondary.ts +40 -0
  82. package/packages/registry/blocks/dashboard-01/components/nav-user.ts +66 -0
  83. package/packages/registry/blocks/dashboard-01/components/section-cards.ts +59 -0
  84. package/packages/registry/blocks/dashboard-01/components/site-header.ts +52 -0
  85. package/packages/registry/blocks/dashboard-01/page.ts +39 -0
  86. package/packages/registry/blocks/login-01/components/login-form.ts +70 -0
  87. package/packages/registry/blocks/login-01/page.ts +19 -0
  88. package/packages/registry/blocks/login-02/components/login-form.ts +68 -0
  89. package/packages/registry/blocks/login-02/page.ts +36 -0
  90. package/packages/registry/blocks/login-03/components/login-form.ts +89 -0
  91. package/packages/registry/blocks/login-03/page.ts +28 -0
  92. package/packages/registry/blocks/login-04/components/login-form.ts +91 -0
  93. package/packages/registry/blocks/login-04/page.ts +20 -0
  94. package/packages/registry/blocks/login-05/components/login-form.ts +75 -0
  95. package/packages/registry/blocks/login-05/page.ts +19 -0
  96. package/packages/registry/blocks/preview/cards/activate-agent-dialog.ts +77 -0
  97. package/packages/registry/blocks/preview/cards/analytics-card.ts +64 -0
  98. package/packages/registry/blocks/preview/cards/anomaly-alert.ts +35 -0
  99. package/packages/registry/blocks/preview/cards/bar-chart-card.ts +91 -0
  100. package/packages/registry/blocks/preview/cards/book-appointment.ts +51 -0
  101. package/packages/registry/blocks/preview/cards/codespaces-card.ts +172 -0
  102. package/packages/registry/blocks/preview/cards/contributions-activity.ts +63 -0
  103. package/packages/registry/blocks/preview/cards/contributors.ts +51 -0
  104. package/packages/registry/blocks/preview/cards/environment-variables.ts +47 -0
  105. package/packages/registry/blocks/preview/cards/feedback-form.ts +61 -0
  106. package/packages/registry/blocks/preview/cards/file-upload.ts +51 -0
  107. package/packages/registry/blocks/preview/cards/github-profile.ts +66 -0
  108. package/packages/registry/blocks/preview/cards/icon-preview-grid.ts +47 -0
  109. package/packages/registry/blocks/preview/cards/invite-team.ts +78 -0
  110. package/packages/registry/blocks/preview/cards/invoice.ts +91 -0
  111. package/packages/registry/blocks/preview/cards/live-waveform.ts +330 -0
  112. package/packages/registry/blocks/preview/cards/no-team-members.ts +61 -0
  113. package/packages/registry/blocks/preview/cards/not-found.ts +46 -0
  114. package/packages/registry/blocks/preview/cards/observability-card.ts +44 -0
  115. package/packages/registry/blocks/preview/cards/pie-chart-card.ts +84 -0
  116. package/packages/registry/blocks/preview/cards/report-bug.ts +71 -0
  117. package/packages/registry/blocks/preview/cards/shipping-address.ts +79 -0
  118. package/packages/registry/blocks/preview/cards/shortcuts.ts +57 -0
  119. package/packages/registry/blocks/preview/cards/skeleton-loading.ts +37 -0
  120. package/packages/registry/blocks/preview/cards/sleep-report.ts +85 -0
  121. package/packages/registry/blocks/preview/cards/style-overview.ts +64 -0
  122. package/packages/registry/blocks/preview/cards/typography-specimen.ts +107 -0
  123. package/packages/registry/blocks/preview/cards/ui-elements.ts +164 -0
  124. package/packages/registry/blocks/preview/cards/usage-card.ts +63 -0
  125. package/packages/registry/blocks/preview/cards/visitors.ts +71 -0
  126. package/packages/registry/blocks/preview/cards/weekly-fitness-summary.ts +54 -0
  127. package/packages/registry/blocks/preview/page.ts +103 -0
  128. package/packages/registry/blocks/preview-02/cards/account-access.ts +70 -0
  129. package/packages/registry/blocks/preview-02/cards/card-overview.ts +75 -0
  130. package/packages/registry/blocks/preview-02/cards/claimable-balance.ts +54 -0
  131. package/packages/registry/blocks/preview-02/cards/contribution-history.ts +80 -0
  132. package/packages/registry/blocks/preview-02/cards/cover-art.ts +41 -0
  133. package/packages/registry/blocks/preview-02/cards/dividend-income.ts +76 -0
  134. package/packages/registry/blocks/preview-02/cards/empty-connect-bank.ts +38 -0
  135. package/packages/registry/blocks/preview-02/cards/empty-distribute-track.ts +38 -0
  136. package/packages/registry/blocks/preview-02/cards/empty-explore-catalog.ts +39 -0
  137. package/packages/registry/blocks/preview-02/cards/faq.ts +79 -0
  138. package/packages/registry/blocks/preview-02/cards/front-door.ts +40 -0
  139. package/packages/registry/blocks/preview-02/cards/index-investing.ts +31 -0
  140. package/packages/registry/blocks/preview-02/cards/kitchen-island.ts +90 -0
  141. package/packages/registry/blocks/preview-02/cards/loading-card.ts +34 -0
  142. package/packages/registry/blocks/preview-02/cards/new-milestone.ts +53 -0
  143. package/packages/registry/blocks/preview-02/cards/notification-settings.ts +126 -0
  144. package/packages/registry/blocks/preview-02/cards/payments.ts +88 -0
  145. package/packages/registry/blocks/preview-02/cards/payout-threshold.ts +92 -0
  146. package/packages/registry/blocks/preview-02/cards/power-usage.ts +84 -0
  147. package/packages/registry/blocks/preview-02/cards/preferences.ts +89 -0
  148. package/packages/registry/blocks/preview-02/cards/qr-connect.ts +54 -0
  149. package/packages/registry/blocks/preview-02/cards/receiving-method.ts +89 -0
  150. package/packages/registry/blocks/preview-02/cards/recent-transactions.ts +101 -0
  151. package/packages/registry/blocks/preview-02/cards/release-catalog.ts +73 -0
  152. package/packages/registry/blocks/preview-02/cards/roller-shades.ts +110 -0
  153. package/packages/registry/blocks/preview-02/cards/savings-progress.ts +68 -0
  154. package/packages/registry/blocks/preview-02/cards/savings-targets.ts +132 -0
  155. package/packages/registry/blocks/preview-02/cards/sidebar-nav.ts +88 -0
  156. package/packages/registry/blocks/preview-02/cards/social-links.ts +57 -0
  157. package/packages/registry/blocks/preview-02/cards/stock-performance.ts +106 -0
  158. package/packages/registry/blocks/preview-02/cards/syncing-state.ts +37 -0
  159. package/packages/registry/blocks/preview-02/cards/transfer-funds.ts +111 -0
  160. package/packages/registry/blocks/preview-02/cards/upcoming-payments.ts +69 -0
  161. package/packages/registry/blocks/preview-02/page.ts +111 -0
  162. package/packages/registry/blocks/preview-03/cards/files-chat.ts +75 -0
  163. package/packages/registry/blocks/preview-03/cards/group-chat.ts +114 -0
  164. package/packages/registry/blocks/preview-03/cards/reasoning-chat.ts +88 -0
  165. package/packages/registry/blocks/preview-03/cards/simple-chat.ts +53 -0
  166. package/packages/registry/blocks/preview-03/components/chat-card.ts +126 -0
  167. package/packages/registry/blocks/preview-03/components/chat-player.ts +174 -0
  168. package/packages/registry/blocks/preview-03/components/part-file.ts +77 -0
  169. package/packages/registry/blocks/preview-03/components/part-reasoning.ts +25 -0
  170. package/packages/registry/blocks/preview-03/components/part-source.ts +48 -0
  171. package/packages/registry/blocks/preview-03/components/part-text.ts +69 -0
  172. package/packages/registry/blocks/preview-03/components/part-tool.ts +71 -0
  173. package/packages/registry/blocks/preview-03/components/prompt-input.ts +86 -0
  174. package/packages/registry/blocks/preview-03/page.ts +39 -0
  175. package/packages/registry/blocks/sidebar-01/components/app-sidebar.ts +113 -0
  176. package/packages/registry/blocks/sidebar-01/components/search-form.ts +33 -0
  177. package/packages/registry/blocks/sidebar-01/components/version-switcher.ts +50 -0
  178. package/packages/registry/blocks/sidebar-01/page.ts +58 -0
  179. package/packages/registry/blocks/sidebar-02/components/app-sidebar.ts +131 -0
  180. package/packages/registry/blocks/sidebar-02/components/search-form.ts +33 -0
  181. package/packages/registry/blocks/sidebar-02/components/version-switcher.ts +50 -0
  182. package/packages/registry/blocks/sidebar-02/page.ts +53 -0
  183. package/packages/registry/blocks/sidebar-03/components/app-sidebar.ts +134 -0
  184. package/packages/registry/blocks/sidebar-03/page.ts +60 -0
  185. package/packages/registry/blocks/sidebar-04/components/app-sidebar.ts +136 -0
  186. package/packages/registry/blocks/sidebar-04/page.ts +58 -0
  187. package/packages/registry/blocks/sidebar-05/components/app-sidebar.ts +147 -0
  188. package/packages/registry/blocks/sidebar-05/components/search-form.ts +33 -0
  189. package/packages/registry/blocks/sidebar-05/page.ts +58 -0
  190. package/packages/registry/blocks/sidebar-06/components/app-sidebar.ts +105 -0
  191. package/packages/registry/blocks/sidebar-06/components/nav-main.ts +56 -0
  192. package/packages/registry/blocks/sidebar-06/components/sidebar-opt-in-form.ts +44 -0
  193. package/packages/registry/blocks/sidebar-06/page.ts +57 -0
  194. package/packages/registry/blocks/sidebar-07/components/app-sidebar.ts +108 -0
  195. package/packages/registry/blocks/sidebar-07/components/nav-main.ts +61 -0
  196. package/packages/registry/blocks/sidebar-07/components/nav-projects.ts +65 -0
  197. package/packages/registry/blocks/sidebar-07/components/nav-user.ts +70 -0
  198. package/packages/registry/blocks/sidebar-07/components/team-switcher.ts +66 -0
  199. package/packages/registry/blocks/sidebar-07/page.ts +59 -0
  200. package/packages/registry/blocks/sidebar-08/components/app-sidebar.ts +122 -0
  201. package/packages/registry/blocks/sidebar-08/components/nav-main.ts +68 -0
  202. package/packages/registry/blocks/sidebar-08/components/nav-projects.ts +65 -0
  203. package/packages/registry/blocks/sidebar-08/components/nav-secondary.ts +40 -0
  204. package/packages/registry/blocks/sidebar-08/components/nav-user.ts +70 -0
  205. package/packages/registry/blocks/sidebar-08/page.ts +59 -0
  206. package/packages/registry/blocks/sidebar-09/components/app-sidebar.ts +213 -0
  207. package/packages/registry/blocks/sidebar-09/components/nav-user.ts +70 -0
  208. package/packages/registry/blocks/sidebar-09/page.ts +52 -0
  209. package/packages/registry/blocks/sidebar-10/components/app-sidebar.ts +125 -0
  210. package/packages/registry/blocks/sidebar-10/components/nav-actions.ts +84 -0
  211. package/packages/registry/blocks/sidebar-10/components/nav-favorites.ts +66 -0
  212. package/packages/registry/blocks/sidebar-10/components/nav-main.ts +37 -0
  213. package/packages/registry/blocks/sidebar-10/components/nav-secondary.ts +43 -0
  214. package/packages/registry/blocks/sidebar-10/components/nav-workspaces.ts +78 -0
  215. package/packages/registry/blocks/sidebar-10/components/team-switcher.ts +63 -0
  216. package/packages/registry/blocks/sidebar-10/page.ts +46 -0
  217. package/packages/registry/blocks/sidebar-11/components/app-sidebar.ts +114 -0
  218. package/packages/registry/blocks/sidebar-11/page.ts +63 -0
  219. package/packages/registry/blocks/sidebar-12/components/app-sidebar.ts +63 -0
  220. package/packages/registry/blocks/sidebar-12/components/calendars.ts +53 -0
  221. package/packages/registry/blocks/sidebar-12/components/date-picker.ts +26 -0
  222. package/packages/registry/blocks/sidebar-12/components/nav-user.ts +70 -0
  223. package/packages/registry/blocks/sidebar-12/page.ts +43 -0
  224. package/packages/registry/blocks/sidebar-13/components/settings-dialog.ts +114 -0
  225. package/packages/registry/blocks/sidebar-13/page.ts +21 -0
  226. package/packages/registry/blocks/sidebar-14/components/app-sidebar.ts +120 -0
  227. package/packages/registry/blocks/sidebar-14/page.ts +55 -0
  228. package/packages/registry/blocks/sidebar-15/components/calendars.ts +53 -0
  229. package/packages/registry/blocks/sidebar-15/components/date-picker.ts +26 -0
  230. package/packages/registry/blocks/sidebar-15/components/nav-favorites.ts +66 -0
  231. package/packages/registry/blocks/sidebar-15/components/nav-main.ts +35 -0
  232. package/packages/registry/blocks/sidebar-15/components/nav-secondary.ts +43 -0
  233. package/packages/registry/blocks/sidebar-15/components/nav-user.ts +70 -0
  234. package/packages/registry/blocks/sidebar-15/components/nav-workspaces.ts +75 -0
  235. package/packages/registry/blocks/sidebar-15/components/sidebar-left.ts +126 -0
  236. package/packages/registry/blocks/sidebar-15/components/sidebar-right.ts +62 -0
  237. package/packages/registry/blocks/sidebar-15/components/team-switcher.ts +63 -0
  238. package/packages/registry/blocks/sidebar-15/page.ts +47 -0
  239. package/packages/registry/blocks/sidebar-16/components/app-sidebar.ts +124 -0
  240. package/packages/registry/blocks/sidebar-16/components/nav-main.ts +67 -0
  241. package/packages/registry/blocks/sidebar-16/components/nav-projects.ts +65 -0
  242. package/packages/registry/blocks/sidebar-16/components/nav-secondary.ts +40 -0
  243. package/packages/registry/blocks/sidebar-16/components/nav-user.ts +70 -0
  244. package/packages/registry/blocks/sidebar-16/components/search-form.ts +27 -0
  245. package/packages/registry/blocks/sidebar-16/components/site-header.ts +42 -0
  246. package/packages/registry/blocks/sidebar-16/page.ts +38 -0
  247. package/packages/registry/blocks/signup-01/components/signup-form.ts +78 -0
  248. package/packages/registry/blocks/signup-01/page.ts +19 -0
  249. package/packages/registry/blocks/signup-02/components/signup-form.ts +78 -0
  250. package/packages/registry/blocks/signup-02/page.ts +36 -0
  251. package/packages/registry/blocks/signup-03/components/signup-form.ts +79 -0
  252. package/packages/registry/blocks/signup-03/page.ts +28 -0
  253. package/packages/registry/blocks/signup-04/components/signup-form.ts +100 -0
  254. package/packages/registry/blocks/signup-04/page.ts +20 -0
  255. package/packages/registry/blocks/signup-05/components/signup-form.ts +75 -0
  256. package/packages/registry/blocks/signup-05/page.ts +20 -0
  257. package/packages/registry/components/accordion.ts +20 -7
  258. package/packages/registry/components/alert-dialog.ts +58 -13
  259. package/packages/registry/components/alert.ts +11 -6
  260. package/packages/registry/components/attachment.ts +119 -0
  261. package/packages/registry/components/avatar.ts +17 -8
  262. package/packages/registry/components/badge.ts +16 -9
  263. package/packages/registry/components/breadcrumb.ts +6 -6
  264. package/packages/registry/components/bubble.ts +95 -0
  265. package/packages/registry/components/button-group.ts +73 -0
  266. package/packages/registry/components/button.ts +35 -25
  267. package/packages/registry/components/calendar.ts +566 -0
  268. package/packages/registry/components/card.ts +8 -8
  269. package/packages/registry/components/carousel.ts +352 -0
  270. package/packages/registry/components/chart-element.ts +547 -0
  271. package/packages/registry/components/chart-geometry.ts +457 -0
  272. package/packages/registry/components/chart.ts +932 -0
  273. package/packages/registry/components/checkbox.ts +22 -84
  274. package/packages/registry/components/code-block.ts +191 -0
  275. package/packages/registry/components/collapsible.ts +5 -2
  276. package/packages/registry/components/combobox.ts +802 -0
  277. package/packages/registry/components/command.ts +618 -0
  278. package/packages/registry/components/context-menu.ts +302 -0
  279. package/packages/registry/components/dialog.ts +156 -45
  280. package/packages/registry/components/direction.ts +43 -0
  281. package/packages/registry/components/drawer.ts +226 -0
  282. package/packages/registry/components/dropdown-menu.ts +262 -83
  283. package/packages/registry/components/empty.ts +8 -8
  284. package/packages/registry/components/field.ts +20 -18
  285. package/packages/registry/components/floating.ts +124 -0
  286. package/packages/registry/components/hover-card.ts +19 -8
  287. package/packages/registry/components/input-group.ts +118 -0
  288. package/packages/registry/components/input-otp.ts +365 -0
  289. package/packages/registry/components/input.ts +3 -3
  290. package/packages/registry/components/item.ts +134 -0
  291. package/packages/registry/components/kbd.ts +2 -2
  292. package/packages/registry/components/label.ts +1 -1
  293. package/packages/registry/components/marker.ts +63 -0
  294. package/packages/registry/components/menubar.ts +388 -0
  295. package/packages/registry/components/message-scroller.ts +470 -0
  296. package/packages/registry/components/message.ts +69 -0
  297. package/packages/registry/components/native-select.ts +5 -5
  298. package/packages/registry/components/navigation-menu.ts +564 -0
  299. package/packages/registry/components/pagination.ts +5 -5
  300. package/packages/registry/components/popover.ts +30 -84
  301. package/packages/registry/components/progress.ts +7 -3
  302. package/packages/registry/components/questionnaire.ts +840 -0
  303. package/packages/registry/components/radio-group.ts +18 -73
  304. package/packages/registry/components/resizable.ts +330 -0
  305. package/packages/registry/components/scroll-area.ts +46 -0
  306. package/packages/registry/components/select.ts +650 -0
  307. package/packages/registry/components/separator.ts +1 -1
  308. package/packages/registry/components/sheet.ts +128 -0
  309. package/packages/registry/components/sidebar.ts +457 -0
  310. package/packages/registry/components/skeleton.ts +1 -1
  311. package/packages/registry/components/slider.ts +333 -0
  312. package/packages/registry/components/sonner.ts +74 -7
  313. package/packages/registry/components/spinner.ts +7 -4
  314. package/packages/registry/components/switch.ts +117 -9
  315. package/packages/registry/components/table.ts +11 -9
  316. package/packages/registry/components/tabs.ts +76 -25
  317. package/packages/registry/components/textarea.ts +1 -1
  318. package/packages/registry/components/toggle-group.ts +30 -11
  319. package/packages/registry/components/toggle.ts +20 -13
  320. package/packages/registry/components/tooltip.ts +68 -7
  321. package/packages/registry/components/trace.ts +75 -0
  322. package/packages/registry/lib/icons-hugeicons.ts +955 -0
  323. package/packages/registry/lib/icons-phosphor.ts +955 -0
  324. package/packages/registry/lib/icons-remixicon.ts +955 -0
  325. package/packages/registry/lib/icons-tabler.ts +955 -0
  326. package/packages/registry/lib/icons.ts +768 -0
  327. package/packages/registry/lib/utils.ts +49 -4
  328. package/packages/registry/registry.json +4056 -72
  329. package/packages/registry/styles/shadcn-tailwind.css +654 -0
  330. package/packages/registry/styles/style-luma.css +1987 -0
  331. package/packages/registry/styles/style-lyra.css +1965 -0
  332. package/packages/registry/styles/style-maia.css +1987 -0
  333. package/packages/registry/styles/style-mira.css +1992 -0
  334. package/packages/registry/styles/style-new-york.css +1597 -0
  335. package/packages/registry/styles/style-nova.css +1990 -0
  336. package/packages/registry/styles/style-rhea.css +1987 -0
  337. package/packages/registry/styles/style-sera.css +1977 -0
  338. package/packages/registry/styles/style-vega.css +1990 -0
  339. package/packages/registry/styles/supplement.json +54 -0
  340. package/packages/registry/styles/tw-animate.css +6 -0
  341. package/packages/registry/themes/base-colors.js +8 -2
  342. package/packages/registry/themes/fonts.js +120 -0
  343. package/packages/registry/themes/index.css +2 -0
  344. package/packages/registry/themes/items.js +61 -0
  345. package/packages/registry/themes/preset.js +302 -0
  346. package/packages/registry/themes/themes.js +1193 -0
  347. package/src/commands/add.js +236 -12
  348. package/src/commands/diff.js +34 -20
  349. package/src/commands/info.js +7 -0
  350. package/src/commands/init.js +125 -8
  351. package/src/commands/list.js +43 -11
  352. package/src/commands/view.js +33 -5
  353. package/src/registry/extract.js +192 -2
  354. package/src/registry/icons.js +31 -0
  355. package/src/registry/local.js +15 -1
  356. package/src/registry/schema.js +13 -0
  357. package/src/registry/style-bake.js +506 -0
  358. package/src/utils/theme.js +110 -21
@@ -0,0 +1,506 @@
1
+ /**
2
+ * The style bake (#1666, D8): turn a registry component written in BASE FORM
3
+ * into plain Tailwind for one chosen style.
4
+ *
5
+ * A base-form component carries shadcn's semantic class names alongside the
6
+ * layout classes it keeps inline, the way shadcn's own
7
+ * `registry/bases/radix/ui/*.tsx` does:
8
+ *
9
+ * const BASE = 'cn-button group/button inline-flex shrink-0 items-center ...';
10
+ *
11
+ * A STYLE is a file of nested rules that map each of those names to Tailwind
12
+ * through `@apply`, all under one `.style-<name>` wrapper:
13
+ *
14
+ * .style-nova { .cn-button { @apply rounded-lg border text-sm ...; } }
15
+ *
16
+ * The bake replaces each `cn-*` name with the classes its style maps it to,
17
+ * ahead of the classes already in the string, and drops the name. It runs
18
+ * wherever an app or an agent READS code: `add` and `diff` (through
19
+ * `transformForProject`), `webjs ui view`, and the MCP `ui` tool. So an
20
+ * installed file, and anything an agent copies, is plain readable Tailwind
21
+ * with no `cn-*` name in it, and `cn()` merging keeps working on it.
22
+ *
23
+ * This is a port of shadcn's `createStyleMap` and `transformStyle`
24
+ * (`packages/shadcn/src/styles/` in the shadcn repo), written without postcss
25
+ * or ts-morph so the CLI gains no dependency. Two deliberate differences:
26
+ *
27
+ * - shadcn inlines a name only at its FIRST occurrence in a file. Here every
28
+ * occurrence is inlined, because a Tier-2 template can render the same part
29
+ * in two branches and both must be styled.
30
+ * - shadcn merges with tailwind-merge. Here the mapped classes are placed
31
+ * BEFORE the string's own classes with exact duplicates dropped, and the
32
+ * component's runtime `cn()` (or the class attribute as written) resolves
33
+ * the rest. Placing them first is what lets an inline layout class win a
34
+ * conflict, the same precedence `twMerge(mapped, original)` gives.
35
+ *
36
+ * A `cn-*` name must sit in static text: a string literal, or the static part
37
+ * of a `class="..."` attribute in an `html` template. A name produced inside a
38
+ * `${...}` hole is never seen and never baked.
39
+ */
40
+ import { readFileSync, existsSync } from 'node:fs';
41
+ import { join } from 'node:path';
42
+ import { REGISTRY_ROOT } from './local.js';
43
+
44
+ const CN_PREFIX = 'cn-';
45
+ const CN_TOKEN = /^cn-[\w-]+$/;
46
+
47
+ /**
48
+ * Names that survive the bake, because they are hooks rather than styles: a
49
+ * selector elsewhere targets them, or the CLI rewrites them at install time.
50
+ * shadcn's own list (`ALLOWLIST` in transform-style-map.ts).
51
+ */
52
+ export const CN_ALLOWLIST = new Set([
53
+ 'cn-menu-target',
54
+ 'cn-menu-translucent',
55
+ 'cn-logical-sides',
56
+ 'cn-rtl-flip',
57
+ 'cn-font-heading',
58
+ ]);
59
+
60
+ /** The styles the kit ships, in the order a picker lists them. `new-york` is the default. */
61
+ export const STYLE_NAMES = ['new-york', 'vega', 'nova', 'maia', 'lyra', 'mira', 'luma', 'sera', 'rhea'];
62
+ export const DEFAULT_STYLE = 'new-york';
63
+
64
+ /**
65
+ * Normalise a `components.json` `style` value to a style the kit ships.
66
+ * `'default'` (the schema's default) and an absent value mean `new-york`.
67
+ * @param {string | undefined | null} style
68
+ */
69
+ export function resolveStyleName(style) {
70
+ if (!style || style === 'default') return DEFAULT_STYLE;
71
+ return STYLE_NAMES.includes(style) ? style : DEFAULT_STYLE;
72
+ }
73
+
74
+ // ---------------------------------------------------------------------------
75
+ // Style map: CSS with @apply rules -> { 'cn-x': 'tailwind classes' }
76
+ // ---------------------------------------------------------------------------
77
+
78
+ /**
79
+ * Build the style map from a style file's CSS. Mirrors shadcn's
80
+ * `createStyleMap`: every rule whose body holds `@apply` contributes its
81
+ * classes to the LAST `cn-*` class of each selector in its selector list,
82
+ * with `&` removed; a later rule for the same name is PREPENDED to what the
83
+ * earlier ones gave it. A rule with no `@apply` contributes nothing.
84
+ *
85
+ * @param {string} css
86
+ * @returns {Record<string, string>}
87
+ */
88
+ export function createStyleMap(css) {
89
+ /** @type {Record<string, string>} */
90
+ const result = {};
91
+ const src = stripCssComments(css);
92
+ /** @type {string[][]} */
93
+ const stack = []; // selector lists of the enclosing rules (unused for the subject, kept for clarity)
94
+ let i = 0;
95
+ let buf = '';
96
+ /** @type {{ selectors: string[], applies: string[] }[]} */
97
+ const frames = [];
98
+ while (i < src.length) {
99
+ const ch = src[i];
100
+ if (ch === '{') {
101
+ const prelude = buf.trim();
102
+ buf = '';
103
+ const selectors = prelude.startsWith('@') ? [] : splitSelectors(prelude);
104
+ frames.push({ selectors, applies: [] });
105
+ stack.push(selectors);
106
+ i++;
107
+ continue;
108
+ }
109
+ if (ch === '}') {
110
+ const frame = frames.pop();
111
+ stack.pop();
112
+ buf = '';
113
+ if (frame && frame.applies.length) {
114
+ const classes = frame.applies.join(' ');
115
+ for (const sel of frame.selectors) {
116
+ const subject = subjectClass(sel.replace(/\s*&\s*/g, '').trim());
117
+ if (!subject) continue;
118
+ result[subject] = result[subject] ? `${classes} ${result[subject]}` : classes;
119
+ }
120
+ }
121
+ i++;
122
+ continue;
123
+ }
124
+ if (ch === ';') {
125
+ const decl = buf.trim();
126
+ buf = '';
127
+ const m = /^@apply\s+([\s\S]+)$/.exec(decl);
128
+ if (m && frames.length) {
129
+ const value = m[1].trim().replace(/\s+/g, ' ');
130
+ if (value) frames[frames.length - 1].applies.push(value);
131
+ }
132
+ i++;
133
+ continue;
134
+ }
135
+ buf += ch;
136
+ i++;
137
+ }
138
+ return result;
139
+ }
140
+
141
+ /** @param {string} css */
142
+ function stripCssComments(css) {
143
+ return css.replace(/\/\*[\s\S]*?\*\//g, ' ');
144
+ }
145
+
146
+ /** Split a selector list on top-level commas (not inside () or []). */
147
+ function splitSelectors(prelude) {
148
+ const out = [];
149
+ let depth = 0;
150
+ let cur = '';
151
+ for (const ch of prelude) {
152
+ if (ch === '(' || ch === '[') depth++;
153
+ else if (ch === ')' || ch === ']') depth--;
154
+ if (ch === ',' && depth === 0) { out.push(cur.trim()); cur = ''; continue; }
155
+ cur += ch;
156
+ }
157
+ if (cur.trim()) out.push(cur.trim());
158
+ return out;
159
+ }
160
+
161
+ /** The last `.cn-*` class in a selector, ignoring anything inside [] or (). */
162
+ function subjectClass(selector) {
163
+ let depth = 0;
164
+ let last = null;
165
+ for (let i = 0; i < selector.length; i++) {
166
+ const ch = selector[i];
167
+ if (ch === '(' || ch === '[') { depth++; continue; }
168
+ if (ch === ')' || ch === ']') { depth--; continue; }
169
+ if (depth === 0 && ch === '.' && selector.startsWith(CN_PREFIX, i + 1)) {
170
+ const m = /^[\w-]+/.exec(selector.slice(i + 1));
171
+ if (m) last = m[0];
172
+ }
173
+ }
174
+ return last;
175
+ }
176
+
177
+ /** @type {Map<string, Record<string, string>>} */
178
+ const MAP_CACHE = new Map();
179
+
180
+ /** Absolute path of a shipped style file. */
181
+ export function styleFilePath(name) {
182
+ return join(REGISTRY_ROOT, 'styles', `style-${name}.css`);
183
+ }
184
+
185
+ /**
186
+ * The style map for one shipped style, read from the packaged registry and
187
+ * cached. An unknown or missing style yields an empty map, which bakes every
188
+ * `cn-*` name away to nothing rather than leaking it.
189
+ * @param {string | undefined | null} style
190
+ */
191
+ export function loadStyleMap(style) {
192
+ const name = resolveStyleName(style);
193
+ const cached = MAP_CACHE.get(name);
194
+ if (cached) return cached;
195
+ const file = styleFilePath(name);
196
+ const map = existsSync(file) ? createStyleMap(readFileSync(file, 'utf8')) : {};
197
+ MAP_CACHE.set(name, map);
198
+ return map;
199
+ }
200
+
201
+ // ---------------------------------------------------------------------------
202
+ // Class segments: where in a JS/TS source a class list can sit
203
+ // ---------------------------------------------------------------------------
204
+
205
+ /**
206
+ * @typedef {{ start: number, end: number, quote: "'" | '"' | '`' | 'attr' }} Segment
207
+ * `start` / `end` delimit the RAW source text of the segment (between the
208
+ * quotes, or the static run of a class attribute). `quote` is the delimiter
209
+ * the text must stay legal inside.
210
+ */
211
+
212
+ const REGEX_PRECEDERS = new Set(['(', ',', '=', ':', '[', '!', '&', '|', '?', '{', '}', ';', '+', '-', '*', '%', '<', '>', '~', '^']);
213
+ const REGEX_KEYWORDS = new Set(['return', 'typeof', 'case', 'do', 'else', 'in', 'of', 'new', 'delete', 'void', 'throw', 'yield', 'await']);
214
+
215
+ /**
216
+ * Find every string literal in a JS/TS source, plus every static run of a
217
+ * `class="..."` attribute inside an `html` tagged template. This one function
218
+ * is what the bake rewrites, what the fixture records and what the snapshot
219
+ * test compares, so the three cannot disagree about which strings carry
220
+ * classes. Comments and regex literals are skipped. A template literal with
221
+ * no holes is a plain string; a template with holes contributes nothing of
222
+ * its own unless it is an `html` template, where only class attribute text
223
+ * counts.
224
+ *
225
+ * @param {string} src
226
+ * @returns {Segment[]}
227
+ */
228
+ export function classSegments(src) {
229
+ /** @type {Segment[]} */
230
+ const segs = [];
231
+ let i = 0;
232
+ let prevSig = ''; // last significant code char, for regex detection
233
+ let prevWord = '';
234
+ /** @type {{ html: boolean, inClass: boolean, attrQuote: string, runStart: number }[]} */
235
+ const tpl = []; // open template literals (innermost last)
236
+ /** @type {number[]} */
237
+ const holeDepth = []; // brace depth inside each open ${ } hole
238
+
239
+ const n = src.length;
240
+ const inCode = () => tpl.length === 0 || holeDepth.length === tpl.length;
241
+
242
+ while (i < n) {
243
+ if (!inCode()) {
244
+ // Inside the static text of the innermost template.
245
+ const t = tpl[tpl.length - 1];
246
+ const ch = src[i];
247
+ if (ch === '\\') { i += 2; continue; }
248
+ if (ch === '`') {
249
+ if (t.html && t.inClass && i > t.runStart) segs.push({ start: t.runStart, end: i, quote: 'attr' });
250
+ tpl.pop();
251
+ i++;
252
+ prevSig = ')';
253
+ prevWord = '';
254
+ continue;
255
+ }
256
+ if (ch === '$' && src[i + 1] === '{') {
257
+ if (t.html && t.inClass && i > t.runStart) segs.push({ start: t.runStart, end: i, quote: 'attr' });
258
+ holeDepth.push(0);
259
+ i += 2;
260
+ prevSig = '{';
261
+ prevWord = '';
262
+ continue;
263
+ }
264
+ if (t.html) {
265
+ if (t.inClass) {
266
+ if (ch === t.attrQuote) {
267
+ if (i > t.runStart) segs.push({ start: t.runStart, end: i, quote: 'attr' });
268
+ t.inClass = false;
269
+ }
270
+ } else {
271
+ const m = /^class=(["'])/.exec(src.slice(i, i + 7));
272
+ if (m && /[\s<]/.test(src[i - 1] || ' ')) {
273
+ t.inClass = true;
274
+ t.attrQuote = m[1];
275
+ i += 7;
276
+ t.runStart = i;
277
+ continue;
278
+ }
279
+ }
280
+ }
281
+ i++;
282
+ continue;
283
+ }
284
+
285
+ // Code.
286
+ const ch = src[i];
287
+ if (ch === '/' && src[i + 1] === '/') { const e = src.indexOf('\n', i); i = e < 0 ? n : e; continue; }
288
+ if (ch === '/' && src[i + 1] === '*') { const e = src.indexOf('*/', i + 2); i = e < 0 ? n : e + 2; continue; }
289
+ if (ch === "'" || ch === '"') {
290
+ const start = i + 1;
291
+ let j = start;
292
+ while (j < n && src[j] !== ch) { if (src[j] === '\\') j++; if (src[j] === '\n') break; j++; }
293
+ segs.push({ start, end: j, quote: ch });
294
+ i = j + 1;
295
+ prevSig = ')';
296
+ prevWord = '';
297
+ continue;
298
+ }
299
+ if (ch === '`') {
300
+ // A template with no holes is a plain string. Decide by scanning ahead.
301
+ const isHtml = /\bhtml\s*$/.test(src.slice(Math.max(0, i - 12), i));
302
+ let j = i + 1;
303
+ let hasHole = false;
304
+ while (j < n && src[j] !== '`') {
305
+ if (src[j] === '\\') { j += 2; continue; }
306
+ if (src[j] === '$' && src[j + 1] === '{') { hasHole = true; break; }
307
+ j++;
308
+ }
309
+ if (!hasHole && !isHtml) {
310
+ segs.push({ start: i + 1, end: j, quote: '`' });
311
+ i = j + 1;
312
+ prevSig = ')';
313
+ prevWord = '';
314
+ continue;
315
+ }
316
+ tpl.push({ html: isHtml, inClass: false, attrQuote: '"', runStart: 0 });
317
+ i++;
318
+ continue;
319
+ }
320
+ if (ch === '/' ) {
321
+ const regexAllowed = !prevSig || REGEX_PRECEDERS.has(prevSig) || REGEX_KEYWORDS.has(prevWord);
322
+ if (regexAllowed) {
323
+ let j = i + 1;
324
+ let inClassSet = false;
325
+ while (j < n) {
326
+ const c = src[j];
327
+ if (c === '\\') { j += 2; continue; }
328
+ if (c === '\n') break;
329
+ if (inClassSet) { if (c === ']') inClassSet = false; }
330
+ else if (c === '[') inClassSet = true;
331
+ else if (c === '/') break;
332
+ j++;
333
+ }
334
+ j++;
335
+ while (j < n && /[a-z]/i.test(src[j])) j++;
336
+ i = j;
337
+ prevSig = ')';
338
+ prevWord = '';
339
+ continue;
340
+ }
341
+ }
342
+ if (holeDepth.length && holeDepth.length === tpl.length) {
343
+ if (ch === '{') holeDepth[holeDepth.length - 1]++;
344
+ else if (ch === '}') {
345
+ if (holeDepth[holeDepth.length - 1] === 0) {
346
+ holeDepth.pop();
347
+ const t = tpl[tpl.length - 1];
348
+ i++;
349
+ t.runStart = i; // a class attribute continues after the hole
350
+ continue;
351
+ }
352
+ holeDepth[holeDepth.length - 1]--;
353
+ }
354
+ }
355
+ if (/[A-Za-z_$]/.test(ch)) {
356
+ let j = i;
357
+ while (j < n && /[\w$]/.test(src[j])) j++;
358
+ prevWord = src.slice(i, j);
359
+ prevSig = 'a';
360
+ i = j;
361
+ continue;
362
+ }
363
+ if (!/\s/.test(ch)) { prevSig = ch; prevWord = ''; }
364
+ i++;
365
+ }
366
+ return segs;
367
+ }
368
+
369
+ /**
370
+ * The cooked value of a segment: what the string MEANS at runtime, with its
371
+ * escapes resolved. Class attribute text is taken as written.
372
+ * @param {string} src
373
+ * @param {Segment} seg
374
+ */
375
+ export function segmentValue(src, seg) {
376
+ const raw = src.slice(seg.start, seg.end);
377
+ if (seg.quote === 'attr') return raw;
378
+ return raw.replace(/\\(u\{[0-9a-fA-F]+\}|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{2}|[\s\S])/g, (_, e) => {
379
+ if (e[0] === 'u' && e[1] === '{') return String.fromCodePoint(parseInt(e.slice(2, -1), 16));
380
+ if (e[0] === 'u' && e.length === 5) return String.fromCharCode(parseInt(e.slice(1), 16));
381
+ if (e[0] === 'x' && e.length === 3) return String.fromCharCode(parseInt(e.slice(1), 16));
382
+ return { n: '\n', t: '\t', r: '\r', b: '\b', f: '\f', v: '\v', 0: '\0' }[e] ?? e;
383
+ });
384
+ }
385
+
386
+ /**
387
+ * Write a cooked value back inside the delimiter it sits in.
388
+ *
389
+ * The result must read right TWICE: as JavaScript (or an HTML attribute), and
390
+ * as the raw text Tailwind's scanner reads, which never unescapes anything. So
391
+ * a quote is never escaped with a backslash or an entity if it can be helped:
392
+ * `[class*=\'size-\']` is valid JavaScript, but Tailwind compiles the
393
+ * backslashes into the selector and the rule never matches. bakeSource()
394
+ * avoids it for a JS string by changing the string's delimiter (see there).
395
+ * In an HTML attribute a double quote becomes a single one, which means the
396
+ * same in a CSS string or attribute value. Escaping is the last resort, for a
397
+ * value carrying both quotes, which no style emits.
398
+ */
399
+ function escapeFor(value, quote) {
400
+ if (quote === 'attr') {
401
+ if (!value.includes('"')) return value;
402
+ return value.includes("'") ? value.replace(/"/g, '&quot;') : value.replace(/"/g, "'");
403
+ }
404
+ let out = value.replace(/\\/g, '\\\\');
405
+ if (quote === '`') return out.replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
406
+ return out.replace(new RegExp(quote, 'g'), `\\${quote}`);
407
+ }
408
+
409
+ // ---------------------------------------------------------------------------
410
+ // The bake
411
+ // ---------------------------------------------------------------------------
412
+
413
+ /**
414
+ * Bake one class list. Every `cn-*` name is replaced by the classes the map
415
+ * gives it (none, when the map has no entry), the mapped classes go FIRST,
416
+ * the allowlisted hooks stay, and exact duplicates are dropped.
417
+ *
418
+ * @param {string} value a cooked class list
419
+ * @param {Record<string, string>} styleMap
420
+ */
421
+ export function bakeClassList(value, styleMap) {
422
+ const tokens = value.split(/\s+/).filter(Boolean);
423
+ if (!tokens.some((t) => CN_TOKEN.test(t))) return value;
424
+ const mapped = [];
425
+ const rest = [];
426
+ for (const t of tokens) {
427
+ if (CN_TOKEN.test(t)) {
428
+ if (CN_ALLOWLIST.has(t)) rest.push(t);
429
+ else if (styleMap[t]) mapped.push(...styleMap[t].split(/\s+/).filter(Boolean));
430
+ } else {
431
+ rest.push(t);
432
+ }
433
+ }
434
+ const seen = new Set();
435
+ const out = [];
436
+ for (const t of [...mapped, ...rest]) {
437
+ if (seen.has(t)) continue;
438
+ seen.add(t);
439
+ out.push(t);
440
+ }
441
+ // Keep the original's leading / trailing whitespace, so a class list that
442
+ // was concatenated into a longer string still has its separators.
443
+ const lead = /^\s*/.exec(value)[0];
444
+ const trail = /\s*$/.exec(value)[0];
445
+ return out.length ? lead + out.join(' ') + trail : (lead || trail ? ' ' : '');
446
+ }
447
+
448
+ /**
449
+ * Bake a whole source file for one style map. A file with no `cn-*` name
450
+ * comes back unchanged, byte for byte.
451
+ *
452
+ * @param {string} src
453
+ * @param {Record<string, string>} styleMap
454
+ */
455
+ export function bakeSource(src, styleMap) {
456
+ if (!src.includes(CN_PREFIX)) return src;
457
+ const segs = classSegments(src);
458
+ let out = '';
459
+ let last = 0;
460
+ for (const seg of segs) {
461
+ const value = segmentValue(src, seg);
462
+ if (!/(^|\s)cn-[\w-]/.test(value)) continue;
463
+ const baked = bakeClassList(value, styleMap);
464
+ // A class that carries the string's own quote (the styles' `[class*='size-']`
465
+ // inside a single-quoted string) moves the string to the other quote, the
466
+ // way a person writes it, rather than escaping: Tailwind reads the raw
467
+ // text and would keep the backslashes.
468
+ const q = seg.quote;
469
+ const other = q === "'" ? '"' : q === '"' ? "'" : '';
470
+ if (other && baked.includes(q) && !baked.includes(other)) {
471
+ out += src.slice(last, seg.start - 1) + other + escapeFor(baked, other) + other;
472
+ last = seg.end + 1;
473
+ continue;
474
+ }
475
+ out += src.slice(last, seg.start) + escapeFor(baked, q);
476
+ last = seg.end;
477
+ }
478
+ return out + src.slice(last);
479
+ }
480
+
481
+ /**
482
+ * Bake for a project: the style named in its `components.json`, `new-york`
483
+ * when there is none. This is the entry `transformForProject` and the kit
484
+ * projector call.
485
+ *
486
+ * @param {string} src
487
+ * @param {string | undefined | null} style
488
+ */
489
+ export function bakeForStyle(src, style) {
490
+ return bakeSource(src, loadStyleMap(style));
491
+ }
492
+
493
+ /**
494
+ * Every `cn-*` name left in a baked source outside the allowlist. A test uses
495
+ * it to prove nothing an app or agent reads ever carries one.
496
+ * @param {string} src
497
+ */
498
+ export function leftoverCnNames(src) {
499
+ const names = new Set();
500
+ for (const seg of classSegments(src)) {
501
+ for (const t of segmentValue(src, seg).split(/\s+/)) {
502
+ if (CN_TOKEN.test(t) && !CN_ALLOWLIST.has(t)) names.add(t);
503
+ }
504
+ }
505
+ return [...names];
506
+ }
@@ -5,64 +5,153 @@
5
5
  * `--foreground`, `--destructive`, ...) that the app must define. `init` plants
6
6
  * them; `add` self-heals if they are missing (an app that copied a component
7
7
  * without the tokens paints unstyled). Both go through {@link ensureTheme} so
8
- * the contract is one place: a token block is written exactly once, keyed by
9
- * the {@link THEME_MARKER}, and a genuine failure to write is reported (never
8
+ * the contract is one place: a token block is written once, keyed by the
9
+ * {@link THEME_MARKER}, and a genuine failure to write is reported (never
10
10
  * swallowed) so `init` can exit non-zero instead of leaving a clean exit code
11
11
  * on an unstyled install.
12
12
  *
13
+ * The block ends at {@link THEME_END_MARKER} (#1666), which is what lets
14
+ * `init --preset` (or `--overwrite`) REPLACE it in place later: everything
15
+ * from the start marker through the end marker is the kit's, and everything
16
+ * around it is the app's. A block written before the end marker existed has
17
+ * no such boundary, so it is never replaced, only reported, since guessing
18
+ * where it stops would cut into the app's own CSS.
19
+ *
13
20
  * @module utils/theme
14
21
  */
15
22
 
16
23
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
17
24
  import { dirname, join } from 'node:path';
18
25
  import { getRegistryItem } from '../registry/fetcher.js';
26
+ import { buildThemeVars } from '../../packages/registry/themes/themes.js';
27
+ import { presetFontVars } from '../../packages/registry/themes/preset.js';
19
28
 
20
- /** Idempotency marker at the top of the theme block. */
29
+ /** The line that opens the theme block. */
21
30
  export const THEME_MARKER = '/* @webjsdev/ui theme */';
22
31
 
32
+ /** The line that closes it (#1666). */
33
+ export const THEME_END_MARKER = '/* @webjsdev/ui theme end */';
34
+
23
35
  function ensureDir(d) {
24
36
  if (!existsSync(d)) mkdirSync(d, { recursive: true });
25
37
  }
26
38
 
39
+ function escapeRe(s) {
40
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
41
+ }
42
+
43
+ /**
44
+ * Set CSS variables inside the first `<selector> {` block of `css`: a variable
45
+ * the block declares has its value replaced where it stands, and one it does
46
+ * not is appended at the end of the block. Comments and every other line are
47
+ * left as they are.
48
+ *
49
+ * @param {string} css
50
+ * @param {string} selector e.g. `:root`, `.dark`, `@theme inline`
51
+ * @param {Record<string, string>} vars bare names (`primary`, not `--primary`)
52
+ */
53
+ export function setBlockVars(css, selector, vars) {
54
+ const entries = Object.entries(vars || {});
55
+ if (!entries.length) return css;
56
+ const re = new RegExp(`(${escapeRe(selector)}\\s*\\{)([\\s\\S]*?)(\\n\\})`);
57
+ if (!re.test(css)) return css;
58
+ return css.replace(re, (_m, open, body, close) => {
59
+ let next = body;
60
+ const missing = [];
61
+ for (const [key, value] of entries) {
62
+ const varRe = new RegExp(`(\\n\\s*--${escapeRe(key)}\\s*:\\s*)[^;\\n]+`);
63
+ if (varRe.test(next)) next = next.replace(varRe, (_v, lead) => lead + value);
64
+ else missing.push(` --${key}: ${value};`);
65
+ }
66
+ return open + next + (missing.length ? '\n' + missing.join('\n') : '') + close;
67
+ });
68
+ }
69
+
27
70
  /**
28
- * Ensure the theme tokens exist in the project's Tailwind CSS file. Idempotent:
29
- * if the marker is already present it is a no-op. Returns a status so the caller
71
+ * The theme block for a design-system preset: the kit's theme file (the
72
+ * neutral `themes/index.css`, so the `@theme inline` map, the variants and the
73
+ * base rules stay the kit's) with every token set to the preset's values
74
+ * (shadcn's palettes, see `themes/themes.js`) and the font tokens added to
75
+ * `@theme inline`.
76
+ *
77
+ * @param {string} baseCss the `theme-neutral` file content
78
+ * @param {import('../../packages/registry/themes/preset.js').PresetConfig} config a resolved preset
79
+ */
80
+ export function presetThemeBlock(baseCss, config) {
81
+ const { light, dark } = buildThemeVars(config);
82
+ let css = setBlockVars(baseCss, ':root', light);
83
+ css = setBlockVars(css, '.dark', dark);
84
+ css = setBlockVars(css, '@theme inline', presetFontVars(config));
85
+ return withEndMarker(css);
86
+ }
87
+
88
+ /** A block that does not already close with the end marker gets one. */
89
+ function withEndMarker(block) {
90
+ if (block.includes(THEME_END_MARKER)) return block;
91
+ return block.replace(/\s*$/, '\n') + '\n' + THEME_END_MARKER + '\n';
92
+ }
93
+
94
+ /**
95
+ * Ensure the theme tokens exist in the project's Tailwind CSS file.
96
+ *
97
+ * With no block present it appends one. With a block present it is a no-op,
98
+ * unless `opts.replace` is set, in which case a block carrying the end marker
99
+ * is replaced in place and a block without one is left alone and reported as
100
+ * `'legacy'`. The block written is `opts.block` when given (a preset's), else
101
+ * the `theme-<baseColor>` registry item's file. Returns a status so the caller
30
102
  * decides how to react (`init` fails hard on `'failed'`; `add` warns).
31
103
  *
32
104
  * @param {string} cwd
33
105
  * @param {string} baseColor one of the base-colour names (`neutral`, ...)
34
106
  * @param {string} cssPath project-relative path to the Tailwind CSS file
35
107
  * @param {string} [registryUrl]
36
- * @returns {Promise<{ status: 'written'|'present'|'failed', cssPath: string, error?: string }>}
108
+ * @param {{ replace?: boolean, block?: string }} [opts]
109
+ * @returns {Promise<{ status: 'written'|'replaced'|'present'|'legacy'|'failed', cssPath: string, error?: string }>}
37
110
  */
38
- export async function ensureTheme(cwd, baseColor, cssPath, registryUrl) {
111
+ export async function ensureTheme(cwd, baseColor, cssPath, registryUrl, opts = {}) {
39
112
  if (!cssPath || !baseColor) {
40
113
  return { status: 'failed', cssPath: cssPath || '', error: 'missing tailwind.css / tailwind.baseColor in components.json' };
41
114
  }
42
115
  const target = join(cwd, cssPath);
43
116
  const existing = existsSync(target) ? readFileSync(target, 'utf8') : '';
44
- if (existing.includes(THEME_MARKER)) {
117
+ const start = existing.indexOf(THEME_MARKER);
118
+ if (start >= 0 && !opts.replace) {
45
119
  return { status: 'present', cssPath };
46
120
  }
47
- let item;
48
- try {
49
- item = await getRegistryItem(`theme-${baseColor}`, registryUrl);
50
- } catch (e) {
51
- return { status: 'failed', cssPath, error: e && e.message ? e.message : String(e) };
121
+ const end = start >= 0 ? existing.indexOf(THEME_END_MARKER, start) : -1;
122
+ if (start >= 0 && end < 0) {
123
+ return { status: 'legacy', cssPath };
52
124
  }
53
- const themeBlock = item && item.files && item.files[0] ? item.files[0].content || '' : '';
125
+
126
+ let themeBlock = opts.block || '';
54
127
  if (!themeBlock) {
55
- return { status: 'failed', cssPath, error: `theme-${baseColor} has no content` };
128
+ let item;
129
+ try {
130
+ item = await getRegistryItem(`theme-${baseColor}`, registryUrl);
131
+ } catch (e) {
132
+ return { status: 'failed', cssPath, error: e && e.message ? e.message : String(e) };
133
+ }
134
+ themeBlock = item && item.files && item.files[0] ? item.files[0].content || '' : '';
135
+ if (!themeBlock) {
136
+ return { status: 'failed', cssPath, error: `theme-${baseColor} has no content` };
137
+ }
138
+ }
139
+ themeBlock = withEndMarker(themeBlock);
140
+
141
+ let next;
142
+ if (start >= 0) {
143
+ // Replace from the start marker through the end of the end-marker line.
144
+ const lineEnd = existing.indexOf('\n', end);
145
+ const after = lineEnd < 0 ? '' : existing.slice(lineEnd + 1);
146
+ next = existing.slice(0, start) + themeBlock + after;
147
+ } else {
148
+ next = existing + (existing && !existing.endsWith('\n') ? '\n' : '') + themeBlock;
56
149
  }
57
150
  try {
58
151
  ensureDir(dirname(target));
59
- writeFileSync(
60
- target,
61
- existing + (existing && !existing.endsWith('\n') ? '\n' : '') + themeBlock,
62
- 'utf8',
63
- );
152
+ writeFileSync(target, next, 'utf8');
64
153
  } catch (e) {
65
154
  return { status: 'failed', cssPath, error: e && e.message ? e.message : String(e) };
66
155
  }
67
- return { status: 'written', cssPath };
156
+ return { status: start >= 0 ? 'replaced' : 'written', cssPath };
68
157
  }