@ciderpress/ui 1.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (428) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +23 -0
  3. package/dist/0~142.js +2 -0
  4. package/dist/0~164.js +2 -0
  5. package/dist/0~2.js +2 -0
  6. package/dist/0~225.js +2 -0
  7. package/dist/0~288.js +2 -0
  8. package/dist/0~299.js +2 -0
  9. package/dist/0~3.js +2 -0
  10. package/dist/0~301.js +2 -0
  11. package/dist/0~311.js +2 -0
  12. package/dist/0~326.js +2 -0
  13. package/dist/0~40.js +2 -0
  14. package/dist/0~43.js +2 -0
  15. package/dist/0~433.js +2 -0
  16. package/dist/0~453.js +2 -0
  17. package/dist/0~457.js +2 -0
  18. package/dist/0~497.js +2 -0
  19. package/dist/0~511.js +2 -0
  20. package/dist/0~603.js +2 -0
  21. package/dist/0~616.js +2 -0
  22. package/dist/0~659.js +2 -0
  23. package/dist/0~670.js +2 -0
  24. package/dist/0~687.js +2 -0
  25. package/dist/0~706.js +2 -0
  26. package/dist/0~729.js +2 -0
  27. package/dist/0~768.js +2 -0
  28. package/dist/0~819.js +2 -0
  29. package/dist/0~830.js +2 -0
  30. package/dist/0~854.js +2 -0
  31. package/dist/0~863.js +2 -0
  32. package/dist/0~879.js +2 -0
  33. package/dist/0~96.js +2 -0
  34. package/dist/0~97.js +2 -0
  35. package/dist/components/FileTree/FileTree.css +90 -0
  36. package/dist/components/FileTree/FileTree.d.ts +7 -0
  37. package/dist/components/FileTree/FileTree.js +531 -0
  38. package/dist/components/FileTree/FileTreeItem.d.ts +8 -0
  39. package/dist/components/FileTree/RemoteSvgIcon.d.ts +11 -0
  40. package/dist/components/FileTree/index.d.ts +4 -0
  41. package/dist/components/folder-icons.d.ts +32 -0
  42. package/dist/components/languages.d.ts +7 -0
  43. package/dist/components/tree-parser/tree-parser.d.ts +12 -0
  44. package/dist/components/tree-parser/types.d.ts +11 -0
  45. package/dist/head/css/loader-apple.css +1 -0
  46. package/dist/head/css/loader-backdrop.css +1 -0
  47. package/dist/head/css/loader-dots.css +1 -0
  48. package/dist/head/css/themes/amber.css +1 -0
  49. package/dist/head/css/themes/arcade.css +1 -0
  50. package/dist/head/css/themes/grannysmith.css +1 -0
  51. package/dist/head/css/themes/honeycrisp.css +1 -0
  52. package/dist/head/css/themes/midnight.css +1 -0
  53. package/dist/head/css/themes/mulled.css +1 -0
  54. package/dist/head/js/loader-dots.js +1 -0
  55. package/dist/head/js/vscode-nav.js +1 -0
  56. package/dist/index.d.ts +43 -0
  57. package/dist/index.mjs +40 -0
  58. package/dist/node.d.ts +81 -0
  59. package/dist/node.mjs +17762 -0
  60. package/dist/plugins/mermaid/MermaidRenderer.tsx +480 -0
  61. package/dist/plugins/mermaid/mermaid.css +199 -0
  62. package/dist/theme/components/announcement/announcement-bar.css +103 -0
  63. package/dist/theme/components/announcement/announcement-bar.tsx +173 -0
  64. package/dist/theme/components/ask-ai/ask-ai-button.css +52 -0
  65. package/dist/theme/components/ask-ai/ask-ai-button.tsx +56 -0
  66. package/dist/theme/components/content-footer/content-footer-portal.tsx +70 -0
  67. package/dist/theme/components/content-footer/feedback.css +47 -0
  68. package/dist/theme/components/content-footer/feedback.tsx +96 -0
  69. package/dist/theme/components/content-footer/meta-actions.css +25 -0
  70. package/dist/theme/components/content-footer/meta-actions.tsx +57 -0
  71. package/dist/theme/components/content-footer/page-pager.css +61 -0
  72. package/dist/theme/components/content-footer/page-pager.tsx +110 -0
  73. package/dist/theme/components/edit-source-button.tsx +97 -0
  74. package/dist/theme/components/footer/site-footer.css +179 -0
  75. package/dist/theme/components/footer/site-footer.tsx +115 -0
  76. package/dist/theme/components/home/cta.css +67 -0
  77. package/dist/theme/components/home/cta.tsx +77 -0
  78. package/dist/theme/components/home/feature-card.css +52 -0
  79. package/dist/theme/components/home/feature-card.tsx +131 -0
  80. package/dist/theme/components/home/feature.tsx +79 -0
  81. package/dist/theme/components/home/hero-demo.css +66 -0
  82. package/dist/theme/components/home/hero-demo.tsx +46 -0
  83. package/dist/theme/components/home/hero.css +96 -0
  84. package/dist/theme/components/home/hero.tsx +111 -0
  85. package/dist/theme/components/home/layout.tsx +183 -0
  86. package/dist/theme/components/home/page-rail.css +21 -0
  87. package/dist/theme/components/home/page-rail.tsx +19 -0
  88. package/dist/theme/components/home/split.css +148 -0
  89. package/dist/theme/components/home/split.tsx +120 -0
  90. package/dist/theme/components/home/trust-strip.css +33 -0
  91. package/dist/theme/components/home/trust-strip.tsx +43 -0
  92. package/dist/theme/components/home/workspaces.tsx +71 -0
  93. package/dist/theme/components/nav/branch-tag.css +54 -0
  94. package/dist/theme/components/nav/branch-tag.tsx +101 -0
  95. package/dist/theme/components/nav/ciderpress-doc-layout.tsx +247 -0
  96. package/dist/theme/components/nav/ciderpress-docs-bar.css +505 -0
  97. package/dist/theme/components/nav/ciderpress-docs-bar.tsx +426 -0
  98. package/dist/theme/components/nav/ciderpress-header.css +172 -0
  99. package/dist/theme/components/nav/ciderpress-header.tsx +113 -0
  100. package/dist/theme/components/nav/ciderpress-layout-context.tsx +49 -0
  101. package/dist/theme/components/nav/ciderpress-nav-hamburger.css +193 -0
  102. package/dist/theme/components/nav/ciderpress-nav-hamburger.tsx +112 -0
  103. package/dist/theme/components/nav/ciderpress-nav-menu.css +130 -0
  104. package/dist/theme/components/nav/ciderpress-nav-menu.tsx +372 -0
  105. package/dist/theme/components/nav/ciderpress-nav-search.css +73 -0
  106. package/dist/theme/components/nav/ciderpress-nav-search.tsx +55 -0
  107. package/dist/theme/components/nav/ciderpress-nav-social-links.css +35 -0
  108. package/dist/theme/components/nav/ciderpress-nav-social-links.tsx +87 -0
  109. package/dist/theme/components/nav/floating-branch-indicator.css +106 -0
  110. package/dist/theme/components/nav/floating-branch-indicator.tsx +109 -0
  111. package/dist/theme/components/nav/layout.tsx +268 -0
  112. package/dist/theme/components/nav/mobile-nav-cta.tsx +79 -0
  113. package/dist/theme/components/nav/nav-divider.css +15 -0
  114. package/dist/theme/components/nav/nav-divider.tsx +19 -0
  115. package/dist/theme/components/nav/nav-logo.css +42 -0
  116. package/dist/theme/components/nav/nav-logo.tsx +262 -0
  117. package/dist/theme/components/nav/theme-switcher.css +92 -0
  118. package/dist/theme/components/nav/theme-switcher.tsx +298 -0
  119. package/dist/theme/components/nav/topbar-cta.css +45 -0
  120. package/dist/theme/components/nav/topbar-cta.tsx +39 -0
  121. package/dist/theme/components/nav/variant-toggle.css +70 -0
  122. package/dist/theme/components/nav/variant-toggle.tsx +83 -0
  123. package/dist/theme/components/nav/version-chip.css +31 -0
  124. package/dist/theme/components/nav/version-chip.tsx +45 -0
  125. package/dist/theme/components/nav/vscode-tag.css +32 -0
  126. package/dist/theme/components/nav/vscode-tag.tsx +20 -0
  127. package/dist/theme/components/openapi/code-sample.tsx +341 -0
  128. package/dist/theme/components/openapi/copy-markdown-button.tsx +41 -0
  129. package/dist/theme/components/openapi/icons.tsx +45 -0
  130. package/dist/theme/components/openapi/index.ts +6 -0
  131. package/dist/theme/components/openapi/method-badge.tsx +24 -0
  132. package/dist/theme/components/openapi/openapi.css +665 -0
  133. package/dist/theme/components/openapi/operation.tsx +200 -0
  134. package/dist/theme/components/openapi/overview.tsx +207 -0
  135. package/dist/theme/components/openapi/parameters-table.tsx +149 -0
  136. package/dist/theme/components/openapi/request-body.tsx +90 -0
  137. package/dist/theme/components/openapi/response-list.tsx +112 -0
  138. package/dist/theme/components/openapi/schema-viewer.tsx +277 -0
  139. package/dist/theme/components/openapi/security-badges.tsx +63 -0
  140. package/dist/theme/components/openapi/spec-utils.ts +122 -0
  141. package/dist/theme/components/shared/accordion.css +118 -0
  142. package/dist/theme/components/shared/accordion.stories.tsx +96 -0
  143. package/dist/theme/components/shared/accordion.tsx +191 -0
  144. package/dist/theme/components/shared/brand-logo.stories.tsx +49 -0
  145. package/dist/theme/components/shared/brand-mark.stories.tsx +53 -0
  146. package/dist/theme/components/shared/browser-window.css +1 -0
  147. package/dist/theme/components/shared/browser-window.stories.tsx +100 -0
  148. package/dist/theme/components/shared/browser-window.tsx +2 -0
  149. package/dist/theme/components/shared/card.stories.tsx +50 -0
  150. package/dist/theme/components/shared/card.tsx +35 -0
  151. package/dist/theme/components/shared/ciderpress-logo.tsx +93 -0
  152. package/dist/theme/components/shared/ciderpress-mark.tsx +77 -0
  153. package/dist/theme/components/shared/color.css +56 -0
  154. package/dist/theme/components/shared/color.stories.tsx +47 -0
  155. package/dist/theme/components/shared/color.tsx +69 -0
  156. package/dist/theme/components/shared/columns.css +37 -0
  157. package/dist/theme/components/shared/columns.stories.tsx +91 -0
  158. package/dist/theme/components/shared/columns.tsx +50 -0
  159. package/dist/theme/components/shared/desktop-window.css +339 -0
  160. package/dist/theme/components/shared/desktop-window.tsx +535 -0
  161. package/dist/theme/components/shared/field.css +158 -0
  162. package/dist/theme/components/shared/field.stories.tsx +100 -0
  163. package/dist/theme/components/shared/field.tsx +186 -0
  164. package/dist/theme/components/shared/frame.css +39 -0
  165. package/dist/theme/components/shared/frame.stories.tsx +49 -0
  166. package/dist/theme/components/shared/frame.tsx +76 -0
  167. package/dist/theme/components/shared/icon.stories.tsx +90 -0
  168. package/dist/theme/components/shared/icon.tsx +41 -0
  169. package/dist/theme/components/shared/prompt.css +178 -0
  170. package/dist/theme/components/shared/prompt.stories.tsx +57 -0
  171. package/dist/theme/components/shared/prompt.tsx +369 -0
  172. package/dist/theme/components/shared/resolve-card-icon.ts +28 -0
  173. package/dist/theme/components/shared/section-card.stories.tsx +51 -0
  174. package/dist/theme/components/shared/section-card.tsx +44 -0
  175. package/dist/theme/components/shared/section-grid.stories.tsx +46 -0
  176. package/dist/theme/components/shared/section-grid.tsx +15 -0
  177. package/dist/theme/components/shared/status-badge.css +37 -0
  178. package/dist/theme/components/shared/status-badge.stories.tsx +61 -0
  179. package/dist/theme/components/shared/status-badge.tsx +74 -0
  180. package/dist/theme/components/shared/steps.css +88 -0
  181. package/dist/theme/components/shared/steps.stories.tsx +68 -0
  182. package/dist/theme/components/shared/steps.tsx +100 -0
  183. package/dist/theme/components/shared/tech-icon-table.stories.tsx +29 -0
  184. package/dist/theme/components/shared/tech-icon-table.tsx +71 -0
  185. package/dist/theme/components/shared/tech-tag.stories.tsx +40 -0
  186. package/dist/theme/components/shared/tech-tag.tsx +31 -0
  187. package/dist/theme/components/shared/theme-color-palette.css +80 -0
  188. package/dist/theme/components/shared/theme-color-palette.stories.tsx +52 -0
  189. package/dist/theme/components/shared/theme-color-palette.tsx +275 -0
  190. package/dist/theme/components/shared/tooltip.css +69 -0
  191. package/dist/theme/components/shared/tooltip.stories.tsx +72 -0
  192. package/dist/theme/components/shared/tooltip.tsx +77 -0
  193. package/dist/theme/components/sidebar/framework-picker.css +77 -0
  194. package/dist/theme/components/sidebar/framework-picker.tsx +78 -0
  195. package/dist/theme/components/sidebar/sidebar-filter.test.ts +140 -0
  196. package/dist/theme/components/sidebar/sidebar-filter.ts +79 -0
  197. package/dist/theme/components/sidebar/sidebar-links.css +102 -0
  198. package/dist/theme/components/sidebar/sidebar-links.tsx +122 -0
  199. package/dist/theme/components/sidebar/sidebar-promo.css +42 -0
  200. package/dist/theme/components/sidebar/sidebar-promo.tsx +52 -0
  201. package/dist/theme/components/sidebar/sidebar-scope.tsx +151 -0
  202. package/dist/theme/components/sidebar/sidebar-toggle.css +181 -0
  203. package/dist/theme/components/sidebar/sidebar-toggle.tsx +127 -0
  204. package/dist/theme/components/theme-provider.tsx +637 -0
  205. package/dist/theme/components/workspaces/card.css +151 -0
  206. package/dist/theme/components/workspaces/card.tsx +166 -0
  207. package/dist/theme/components/workspaces/grid.tsx +62 -0
  208. package/dist/theme/css.d.ts +1 -0
  209. package/dist/theme/env.d.ts +12 -0
  210. package/dist/theme/fonts/GeistMono-Variable.woff2 +0 -0
  211. package/dist/theme/fonts/GeistPixel-Square.woff2 +0 -0
  212. package/dist/theme/fonts/GeistSans-Variable.woff2 +0 -0
  213. package/dist/theme/hooks/use-ciderpress.ts +61 -0
  214. package/dist/theme/hooks/use-nav-items.ts +76 -0
  215. package/dist/theme/icons/index.ts +2 -0
  216. package/dist/theme/icons/tech-map.ts +228 -0
  217. package/dist/theme/index.tsx +180 -0
  218. package/dist/theme/lib/read-social-links.ts +27 -0
  219. package/dist/theme/lib/route-link.tsx +126 -0
  220. package/dist/theme/lib/safe-url.test.ts +0 -0
  221. package/dist/theme/lib/safe-url.ts +68 -0
  222. package/dist/theme/lib/theme-favicon.ts +125 -0
  223. package/dist/theme/lib/user-config-stub.ts +12 -0
  224. package/dist/theme/styles/layers.css +32 -0
  225. package/dist/theme/styles/overrides/badge.css +26 -0
  226. package/dist/theme/styles/overrides/content-footer.css +80 -0
  227. package/dist/theme/styles/overrides/details.css +61 -0
  228. package/dist/theme/styles/overrides/fonts.css +32 -0
  229. package/dist/theme/styles/overrides/home-card.css +133 -0
  230. package/dist/theme/styles/overrides/home.css +243 -0
  231. package/dist/theme/styles/overrides/rail.css +337 -0
  232. package/dist/theme/styles/overrides/rspress.css +62 -0
  233. package/dist/theme/styles/overrides/scrollbar.css +25 -0
  234. package/dist/theme/styles/overrides/section-card.css +114 -0
  235. package/dist/theme/styles/overrides/sidebar.css +144 -0
  236. package/dist/theme/styles/overrides/tokens.css +288 -0
  237. package/dist/theme/styles/overrides/vscode.css +88 -0
  238. package/dist/theme/styles/themes/amber.css +545 -0
  239. package/dist/theme/styles/themes/arcade-fx.css +366 -0
  240. package/dist/theme/styles/themes/arcade.css +273 -0
  241. package/dist/theme/styles/themes/grannysmith.css +545 -0
  242. package/dist/theme/styles/themes/honeycrisp.css +545 -0
  243. package/dist/theme/styles/themes/midnight.css +273 -0
  244. package/dist/theme/styles/themes/mulled.css +817 -0
  245. package/package.json +103 -0
  246. package/src/theme/components/announcement/announcement-bar.css +103 -0
  247. package/src/theme/components/announcement/announcement-bar.tsx +173 -0
  248. package/src/theme/components/ask-ai/ask-ai-button.css +52 -0
  249. package/src/theme/components/ask-ai/ask-ai-button.tsx +56 -0
  250. package/src/theme/components/content-footer/content-footer-portal.tsx +70 -0
  251. package/src/theme/components/content-footer/feedback.css +47 -0
  252. package/src/theme/components/content-footer/feedback.tsx +96 -0
  253. package/src/theme/components/content-footer/meta-actions.css +25 -0
  254. package/src/theme/components/content-footer/meta-actions.tsx +57 -0
  255. package/src/theme/components/content-footer/page-pager.css +61 -0
  256. package/src/theme/components/content-footer/page-pager.tsx +110 -0
  257. package/src/theme/components/edit-source-button.tsx +97 -0
  258. package/src/theme/components/footer/site-footer.css +179 -0
  259. package/src/theme/components/footer/site-footer.tsx +115 -0
  260. package/src/theme/components/home/cta.css +67 -0
  261. package/src/theme/components/home/cta.tsx +77 -0
  262. package/src/theme/components/home/feature-card.css +52 -0
  263. package/src/theme/components/home/feature-card.tsx +131 -0
  264. package/src/theme/components/home/feature.tsx +79 -0
  265. package/src/theme/components/home/hero-demo.css +66 -0
  266. package/src/theme/components/home/hero-demo.tsx +46 -0
  267. package/src/theme/components/home/hero.css +96 -0
  268. package/src/theme/components/home/hero.tsx +111 -0
  269. package/src/theme/components/home/layout.tsx +183 -0
  270. package/src/theme/components/home/page-rail.css +21 -0
  271. package/src/theme/components/home/page-rail.tsx +19 -0
  272. package/src/theme/components/home/split.css +148 -0
  273. package/src/theme/components/home/split.tsx +120 -0
  274. package/src/theme/components/home/trust-strip.css +33 -0
  275. package/src/theme/components/home/trust-strip.tsx +43 -0
  276. package/src/theme/components/home/workspaces.tsx +71 -0
  277. package/src/theme/components/nav/branch-tag.css +54 -0
  278. package/src/theme/components/nav/branch-tag.tsx +101 -0
  279. package/src/theme/components/nav/ciderpress-doc-layout.tsx +247 -0
  280. package/src/theme/components/nav/ciderpress-docs-bar.css +505 -0
  281. package/src/theme/components/nav/ciderpress-docs-bar.tsx +426 -0
  282. package/src/theme/components/nav/ciderpress-header.css +172 -0
  283. package/src/theme/components/nav/ciderpress-header.tsx +113 -0
  284. package/src/theme/components/nav/ciderpress-layout-context.tsx +49 -0
  285. package/src/theme/components/nav/ciderpress-nav-hamburger.css +193 -0
  286. package/src/theme/components/nav/ciderpress-nav-hamburger.tsx +112 -0
  287. package/src/theme/components/nav/ciderpress-nav-menu.css +130 -0
  288. package/src/theme/components/nav/ciderpress-nav-menu.tsx +372 -0
  289. package/src/theme/components/nav/ciderpress-nav-search.css +73 -0
  290. package/src/theme/components/nav/ciderpress-nav-search.tsx +55 -0
  291. package/src/theme/components/nav/ciderpress-nav-social-links.css +35 -0
  292. package/src/theme/components/nav/ciderpress-nav-social-links.tsx +87 -0
  293. package/src/theme/components/nav/floating-branch-indicator.css +106 -0
  294. package/src/theme/components/nav/floating-branch-indicator.tsx +109 -0
  295. package/src/theme/components/nav/layout.tsx +268 -0
  296. package/src/theme/components/nav/mobile-nav-cta.tsx +79 -0
  297. package/src/theme/components/nav/nav-divider.css +15 -0
  298. package/src/theme/components/nav/nav-divider.tsx +19 -0
  299. package/src/theme/components/nav/nav-logo.css +42 -0
  300. package/src/theme/components/nav/nav-logo.tsx +262 -0
  301. package/src/theme/components/nav/theme-switcher.css +92 -0
  302. package/src/theme/components/nav/theme-switcher.tsx +298 -0
  303. package/src/theme/components/nav/topbar-cta.css +45 -0
  304. package/src/theme/components/nav/topbar-cta.tsx +39 -0
  305. package/src/theme/components/nav/variant-toggle.css +70 -0
  306. package/src/theme/components/nav/variant-toggle.tsx +83 -0
  307. package/src/theme/components/nav/version-chip.css +31 -0
  308. package/src/theme/components/nav/version-chip.tsx +45 -0
  309. package/src/theme/components/nav/vscode-tag.css +32 -0
  310. package/src/theme/components/nav/vscode-tag.tsx +20 -0
  311. package/src/theme/components/openapi/code-sample.tsx +341 -0
  312. package/src/theme/components/openapi/copy-markdown-button.tsx +41 -0
  313. package/src/theme/components/openapi/icons.tsx +45 -0
  314. package/src/theme/components/openapi/index.ts +6 -0
  315. package/src/theme/components/openapi/method-badge.tsx +24 -0
  316. package/src/theme/components/openapi/openapi.css +665 -0
  317. package/src/theme/components/openapi/operation.tsx +200 -0
  318. package/src/theme/components/openapi/overview.tsx +207 -0
  319. package/src/theme/components/openapi/parameters-table.tsx +149 -0
  320. package/src/theme/components/openapi/request-body.tsx +90 -0
  321. package/src/theme/components/openapi/response-list.tsx +112 -0
  322. package/src/theme/components/openapi/schema-viewer.tsx +277 -0
  323. package/src/theme/components/openapi/security-badges.tsx +63 -0
  324. package/src/theme/components/openapi/spec-utils.ts +122 -0
  325. package/src/theme/components/shared/accordion.css +118 -0
  326. package/src/theme/components/shared/accordion.stories.tsx +96 -0
  327. package/src/theme/components/shared/accordion.tsx +191 -0
  328. package/src/theme/components/shared/brand-logo.stories.tsx +49 -0
  329. package/src/theme/components/shared/brand-mark.stories.tsx +53 -0
  330. package/src/theme/components/shared/browser-window.css +1 -0
  331. package/src/theme/components/shared/browser-window.stories.tsx +100 -0
  332. package/src/theme/components/shared/browser-window.tsx +2 -0
  333. package/src/theme/components/shared/card.stories.tsx +50 -0
  334. package/src/theme/components/shared/card.tsx +35 -0
  335. package/src/theme/components/shared/ciderpress-logo.tsx +93 -0
  336. package/src/theme/components/shared/ciderpress-mark.tsx +77 -0
  337. package/src/theme/components/shared/color.css +56 -0
  338. package/src/theme/components/shared/color.stories.tsx +47 -0
  339. package/src/theme/components/shared/color.tsx +69 -0
  340. package/src/theme/components/shared/columns.css +37 -0
  341. package/src/theme/components/shared/columns.stories.tsx +91 -0
  342. package/src/theme/components/shared/columns.tsx +50 -0
  343. package/src/theme/components/shared/desktop-window.css +339 -0
  344. package/src/theme/components/shared/desktop-window.tsx +535 -0
  345. package/src/theme/components/shared/field.css +158 -0
  346. package/src/theme/components/shared/field.stories.tsx +100 -0
  347. package/src/theme/components/shared/field.tsx +186 -0
  348. package/src/theme/components/shared/frame.css +39 -0
  349. package/src/theme/components/shared/frame.stories.tsx +49 -0
  350. package/src/theme/components/shared/frame.tsx +76 -0
  351. package/src/theme/components/shared/icon.stories.tsx +90 -0
  352. package/src/theme/components/shared/icon.tsx +41 -0
  353. package/src/theme/components/shared/prompt.css +178 -0
  354. package/src/theme/components/shared/prompt.stories.tsx +57 -0
  355. package/src/theme/components/shared/prompt.tsx +369 -0
  356. package/src/theme/components/shared/resolve-card-icon.ts +28 -0
  357. package/src/theme/components/shared/section-card.stories.tsx +51 -0
  358. package/src/theme/components/shared/section-card.tsx +44 -0
  359. package/src/theme/components/shared/section-grid.stories.tsx +46 -0
  360. package/src/theme/components/shared/section-grid.tsx +15 -0
  361. package/src/theme/components/shared/status-badge.css +37 -0
  362. package/src/theme/components/shared/status-badge.stories.tsx +61 -0
  363. package/src/theme/components/shared/status-badge.tsx +74 -0
  364. package/src/theme/components/shared/steps.css +88 -0
  365. package/src/theme/components/shared/steps.stories.tsx +68 -0
  366. package/src/theme/components/shared/steps.tsx +100 -0
  367. package/src/theme/components/shared/tech-icon-table.stories.tsx +29 -0
  368. package/src/theme/components/shared/tech-icon-table.tsx +71 -0
  369. package/src/theme/components/shared/tech-tag.stories.tsx +40 -0
  370. package/src/theme/components/shared/tech-tag.tsx +31 -0
  371. package/src/theme/components/shared/theme-color-palette.css +80 -0
  372. package/src/theme/components/shared/theme-color-palette.stories.tsx +52 -0
  373. package/src/theme/components/shared/theme-color-palette.tsx +275 -0
  374. package/src/theme/components/shared/tooltip.css +69 -0
  375. package/src/theme/components/shared/tooltip.stories.tsx +72 -0
  376. package/src/theme/components/shared/tooltip.tsx +77 -0
  377. package/src/theme/components/sidebar/framework-picker.css +77 -0
  378. package/src/theme/components/sidebar/framework-picker.tsx +78 -0
  379. package/src/theme/components/sidebar/sidebar-filter.test.ts +140 -0
  380. package/src/theme/components/sidebar/sidebar-filter.ts +79 -0
  381. package/src/theme/components/sidebar/sidebar-links.css +102 -0
  382. package/src/theme/components/sidebar/sidebar-links.tsx +122 -0
  383. package/src/theme/components/sidebar/sidebar-promo.css +42 -0
  384. package/src/theme/components/sidebar/sidebar-promo.tsx +52 -0
  385. package/src/theme/components/sidebar/sidebar-scope.tsx +151 -0
  386. package/src/theme/components/sidebar/sidebar-toggle.css +181 -0
  387. package/src/theme/components/sidebar/sidebar-toggle.tsx +127 -0
  388. package/src/theme/components/theme-provider.tsx +637 -0
  389. package/src/theme/components/workspaces/card.css +151 -0
  390. package/src/theme/components/workspaces/card.tsx +166 -0
  391. package/src/theme/components/workspaces/grid.tsx +62 -0
  392. package/src/theme/css.d.ts +1 -0
  393. package/src/theme/env.d.ts +12 -0
  394. package/src/theme/fonts/GeistMono-Variable.woff2 +0 -0
  395. package/src/theme/fonts/GeistPixel-Square.woff2 +0 -0
  396. package/src/theme/fonts/GeistSans-Variable.woff2 +0 -0
  397. package/src/theme/hooks/use-ciderpress.ts +61 -0
  398. package/src/theme/hooks/use-nav-items.ts +76 -0
  399. package/src/theme/icons/index.ts +2 -0
  400. package/src/theme/icons/tech-map.ts +228 -0
  401. package/src/theme/index.tsx +180 -0
  402. package/src/theme/lib/read-social-links.ts +27 -0
  403. package/src/theme/lib/route-link.tsx +126 -0
  404. package/src/theme/lib/safe-url.test.ts +0 -0
  405. package/src/theme/lib/safe-url.ts +68 -0
  406. package/src/theme/lib/theme-favicon.ts +125 -0
  407. package/src/theme/lib/user-config-stub.ts +12 -0
  408. package/src/theme/styles/layers.css +32 -0
  409. package/src/theme/styles/overrides/badge.css +26 -0
  410. package/src/theme/styles/overrides/content-footer.css +80 -0
  411. package/src/theme/styles/overrides/details.css +61 -0
  412. package/src/theme/styles/overrides/fonts.css +32 -0
  413. package/src/theme/styles/overrides/home-card.css +133 -0
  414. package/src/theme/styles/overrides/home.css +243 -0
  415. package/src/theme/styles/overrides/rail.css +337 -0
  416. package/src/theme/styles/overrides/rspress.css +62 -0
  417. package/src/theme/styles/overrides/scrollbar.css +25 -0
  418. package/src/theme/styles/overrides/section-card.css +114 -0
  419. package/src/theme/styles/overrides/sidebar.css +144 -0
  420. package/src/theme/styles/overrides/tokens.css +288 -0
  421. package/src/theme/styles/overrides/vscode.css +88 -0
  422. package/src/theme/styles/themes/amber.css +545 -0
  423. package/src/theme/styles/themes/arcade-fx.css +366 -0
  424. package/src/theme/styles/themes/arcade.css +273 -0
  425. package/src/theme/styles/themes/grannysmith.css +545 -0
  426. package/src/theme/styles/themes/honeycrisp.css +545 -0
  427. package/src/theme/styles/themes/midnight.css +273 -0
  428. package/src/theme/styles/themes/mulled.css +817 -0
@@ -0,0 +1,77 @@
1
+ .cp-fwpicker {
2
+ padding: 14px 14px 12px;
3
+ border-bottom: 1px solid var(--cp-c-border);
4
+ }
5
+
6
+ .cp-fwpicker__head {
7
+ display: flex;
8
+ align-items: center;
9
+ justify-content: space-between;
10
+ margin-bottom: 6px;
11
+ gap: 8px;
12
+ }
13
+
14
+ .cp-fwpicker__head-right {
15
+ display: flex;
16
+ align-items: center;
17
+ gap: 6px;
18
+ }
19
+
20
+ .cp-fwpicker__label {
21
+ font-family: var(--cp-font-family-mono);
22
+ font-size: 10px;
23
+ text-transform: uppercase;
24
+ letter-spacing: 0.08em;
25
+ color: var(--cp-c-text-3);
26
+ }
27
+
28
+ .cp-fwpicker__version {
29
+ font-family: var(--cp-font-family-mono);
30
+ font-size: 10px;
31
+ color: var(--cp-c-text-3);
32
+ padding: 1px 6px;
33
+ border: 1px solid var(--cp-c-border);
34
+ border-radius: 4px;
35
+ white-space: nowrap;
36
+ }
37
+
38
+ .cp-fwpicker__control {
39
+ display: flex;
40
+ align-items: center;
41
+ gap: 8px;
42
+ padding: 7px 10px;
43
+ background: var(--cp-c-bg-alt);
44
+ border: 1px solid var(--cp-c-border);
45
+ border-radius: var(--cp-radius-card);
46
+ font-size: 13px;
47
+ color: var(--cp-c-text-1);
48
+ cursor: pointer;
49
+ transition: border-color 0.15s ease;
50
+ }
51
+
52
+ .cp-fwpicker__control:hover {
53
+ border-color: var(--cp-c-border);
54
+ }
55
+
56
+ .cp-fwpicker__mark {
57
+ width: 14px;
58
+ height: 14px;
59
+ border-radius: 3px;
60
+ background: linear-gradient(135deg, var(--cp-c-brand-1), var(--cp-c-brand-2));
61
+ display: grid;
62
+ place-items: center;
63
+ color: var(--cp-c-brand-fg);
64
+ font-size: 9px;
65
+ font-weight: 700;
66
+ font-family: var(--cp-font-family-mono);
67
+ flex-shrink: 0;
68
+ }
69
+
70
+ .cp-fwpicker__name {
71
+ flex: 1;
72
+ }
73
+
74
+ .cp-fwpicker__chev {
75
+ color: var(--cp-c-text-3);
76
+ flex-shrink: 0;
77
+ }
@@ -0,0 +1,78 @@
1
+ import { match } from 'massaman/match'
2
+ import type React from 'react'
3
+
4
+ import { SidebarToggle } from './sidebar-toggle'
5
+
6
+ import './framework-picker.css'
7
+
8
+ export interface FrameworkPickerProps {
9
+ /**
10
+ * Eyebrow label above the picker. Defaults to "Framework".
11
+ */
12
+ readonly label?: string
13
+ /**
14
+ * Currently active framework name shown in the picker.
15
+ */
16
+ readonly current: string
17
+ /**
18
+ * Optional small mark / icon shown to the left of the framework name.
19
+ */
20
+ readonly mark?: React.ReactNode
21
+ /**
22
+ * Optional version chip shown next to the eyebrow label.
23
+ */
24
+ readonly version?: string
25
+ /**
26
+ * When true, render a sidebar collapse toggle in the picker header.
27
+ */
28
+ readonly showCollapseToggle?: boolean
29
+ }
30
+
31
+ /**
32
+ * FrameworkPicker — top-of-sidebar selector showing the current framework
33
+ * + an optional version chip + an optional sidebar collapse toggle.
34
+ * Mirrors the mockup's framework switcher pattern.
35
+ *
36
+ * @param props - Picker configuration.
37
+ * @returns React element.
38
+ */
39
+ export function FrameworkPicker(props: FrameworkPickerProps): React.ReactElement {
40
+ const { label = 'Framework', current, mark, version, showCollapseToggle } = props
41
+
42
+ return (
43
+ <div className="cp-fwpicker">
44
+ <div className="cp-fwpicker__head">
45
+ <span className="cp-fwpicker__label">{label}</span>
46
+ <div className="cp-fwpicker__head-right">
47
+ {match(version)
48
+ .with(undefined, () => null)
49
+ .otherwise((v) => (
50
+ <span className="cp-fwpicker__version">{v}</span>
51
+ ))}
52
+ {match(showCollapseToggle)
53
+ .with(true, () => <SidebarToggle />)
54
+ .otherwise(() => null)}
55
+ </div>
56
+ </div>
57
+ <div className="cp-fwpicker__control">
58
+ {match(mark)
59
+ .with(undefined, () => null)
60
+ .otherwise((m) => (
61
+ <span className="cp-fwpicker__mark">{m}</span>
62
+ ))}
63
+ <span className="cp-fwpicker__name">{current}</span>
64
+ <svg
65
+ className="cp-fwpicker__chev"
66
+ width="14"
67
+ height="14"
68
+ viewBox="0 0 24 24"
69
+ fill="none"
70
+ stroke="currentColor"
71
+ strokeWidth="2"
72
+ >
73
+ <polyline points="6 9 12 15 18 9" />
74
+ </svg>
75
+ </div>
76
+ </div>
77
+ )
78
+ }
@@ -0,0 +1,140 @@
1
+ import type { SidebarData } from '@rspress/core'
2
+ import { describe, expect, it } from 'vitest'
3
+
4
+ import { belongsToScope, resolveScopedSidebar } from './sidebar-filter'
5
+
6
+ const gettingStarted: SidebarData[number] = {
7
+ text: 'Getting Started',
8
+ link: '/getting-started',
9
+ collapsible: true,
10
+ collapsed: true,
11
+ items: [
12
+ { text: 'Introduction', link: '/getting-started/introduction' },
13
+ { text: 'Quick Start', link: '/getting-started/quick-start' },
14
+ ],
15
+ }
16
+
17
+ const packages: SidebarData[number] = {
18
+ text: 'Packages',
19
+ link: '/packages',
20
+ collapsible: true,
21
+ collapsed: true,
22
+ items: [
23
+ { text: 'CLI', link: '/packages/cli' },
24
+ { text: 'Core', link: '/packages/core' },
25
+ ],
26
+ }
27
+
28
+ const contributing: SidebarData[number] = {
29
+ text: 'Contributing',
30
+ link: '/contributing',
31
+ collapsible: true,
32
+ collapsed: true,
33
+ items: [
34
+ { text: 'Guides', link: '/contributing/guides' },
35
+ { text: 'Standards', link: '/contributing/standards' },
36
+ ],
37
+ }
38
+
39
+ const groupWithoutLink: SidebarData[number] = {
40
+ text: 'Nested Group',
41
+ collapsible: true,
42
+ collapsed: true,
43
+ items: [{ text: 'Deep Page', link: '/packages/deep/page' }],
44
+ }
45
+
46
+ const fullSidebar: SidebarData = [gettingStarted, packages, contributing]
47
+ const scopes: readonly string[] = ['/packages', '/contributing']
48
+
49
+ describe('resolveScopedSidebar()', () => {
50
+ it('should return all items when no scopes are defined', () => {
51
+ const result = resolveScopedSidebar(fullSidebar, '/getting-started', [])
52
+
53
+ expect(result).toHaveLength(3)
54
+ })
55
+
56
+ it('should exclude standalone sections on a main page', () => {
57
+ const result = resolveScopedSidebar(fullSidebar, '/getting-started/introduction', scopes)
58
+
59
+ expect(result).toHaveLength(1)
60
+ expect(result[0]).toMatchObject({ text: 'Getting Started' })
61
+ })
62
+
63
+ it('should show only the matching standalone section on an exact scope path', () => {
64
+ const result = resolveScopedSidebar(fullSidebar, '/packages', scopes)
65
+
66
+ expect(result).toHaveLength(1)
67
+ expect(result[0]).toMatchObject({ text: 'Packages' })
68
+ })
69
+
70
+ it('should show only the matching standalone section on a nested scope path', () => {
71
+ const result = resolveScopedSidebar(fullSidebar, '/packages/cli', scopes)
72
+
73
+ expect(result).toHaveLength(1)
74
+ expect(result[0]).toMatchObject({ text: 'Packages' })
75
+ })
76
+
77
+ it('should show only contributing items on the contributing scope', () => {
78
+ const result = resolveScopedSidebar(fullSidebar, '/contributing/guides', scopes)
79
+
80
+ expect(result).toHaveLength(1)
81
+ expect(result[0]).toMatchObject({ text: 'Contributing' })
82
+ })
83
+
84
+ it('should not mutate the original sidebar data', () => {
85
+ const original = [...fullSidebar]
86
+
87
+ resolveScopedSidebar(fullSidebar, '/packages', scopes)
88
+
89
+ expect(fullSidebar).toStrictEqual(original)
90
+ })
91
+
92
+ it('should not match a scope that is only a prefix of the pathname segment', () => {
93
+ const result = resolveScopedSidebar(fullSidebar, '/packages-extra/foo', scopes)
94
+
95
+ expect(result).toHaveLength(1)
96
+ expect(result[0]).toMatchObject({ text: 'Getting Started' })
97
+ })
98
+ })
99
+
100
+ describe('belongsToScope()', () => {
101
+ it('should match an item whose link equals the scope', () => {
102
+ expect(belongsToScope(packages, '/packages')).toBe(true)
103
+ })
104
+
105
+ it('should match an item whose link starts with scope/', () => {
106
+ const item: SidebarData[number] = { text: 'CLI', link: '/packages/cli' }
107
+
108
+ expect(belongsToScope(item, '/packages')).toBe(true)
109
+ })
110
+
111
+ it('should not match an item from a different scope', () => {
112
+ expect(belongsToScope(gettingStarted, '/packages')).toBe(false)
113
+ })
114
+
115
+ it('should not match a partial path segment overlap', () => {
116
+ const item: SidebarData[number] = { text: 'Extra', link: '/packages-extra' }
117
+
118
+ expect(belongsToScope(item, '/packages')).toBe(false)
119
+ })
120
+
121
+ it('should match a group without a link by checking children', () => {
122
+ expect(belongsToScope(groupWithoutLink, '/packages')).toBe(true)
123
+ })
124
+
125
+ it('should not match a group without a link when children belong to a different scope', () => {
126
+ expect(belongsToScope(groupWithoutLink, '/contributing')).toBe(false)
127
+ })
128
+
129
+ it('should not match a divider (no link, no items)', () => {
130
+ const divider: SidebarData[number] = { dividerType: 'solid' }
131
+
132
+ expect(belongsToScope(divider, '/packages')).toBe(false)
133
+ })
134
+
135
+ it('should not match a section header (no link, no items)', () => {
136
+ const header: SidebarData[number] = { sectionHeaderText: 'Header' }
137
+
138
+ expect(belongsToScope(header, '/packages')).toBe(false)
139
+ })
140
+ })
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Pure sidebar filtering logic for multi-scope support.
3
+ *
4
+ * Extracts the runtime filtering from the Sidebar React component so the
5
+ * logic is independently testable without React rendering.
6
+ */
7
+
8
+ import type { SidebarData } from '@rspress/core'
9
+
10
+ /**
11
+ * Union element of `SidebarData` — sidebar items may be groups, leaf items,
12
+ * dividers, or section headers.
13
+ *
14
+ * @private
15
+ */
16
+ type SidebarDataItem = SidebarData[number]
17
+
18
+ /**
19
+ * Resolve the sidebar items visible for the current scope.
20
+ *
21
+ * When the current pathname matches a standalone scope (e.g. `/packages`),
22
+ * only that section's sidebar items are returned. For all other paths,
23
+ * standalone sections are excluded and the remaining sections form a
24
+ * unified sidebar.
25
+ *
26
+ * @param items - Full unified sidebar items
27
+ * @param pathname - Current decoded URL pathname
28
+ * @param scopes - Standalone scope paths (e.g. `["/packages", "/contributing"]`)
29
+ * @returns Filtered sidebar items for the active scope
30
+ */
31
+ export function resolveScopedSidebar(
32
+ items: SidebarData,
33
+ pathname: string,
34
+ scopes: readonly string[]
35
+ ): SidebarData {
36
+ if (scopes.length === 0) {
37
+ return [...items]
38
+ }
39
+
40
+ const standaloneMatch = scopes.find(
41
+ (scope) => pathname === scope || pathname.startsWith(`${scope}/`)
42
+ )
43
+
44
+ const all = [...items]
45
+
46
+ if (standaloneMatch) {
47
+ return all.filter((item) => belongsToScope(item, standaloneMatch))
48
+ }
49
+
50
+ return all.filter((item) => !scopes.some((scope) => belongsToScope(item, scope)))
51
+ }
52
+
53
+ /**
54
+ * Check whether a sidebar item belongs to a given scope path.
55
+ *
56
+ * Matches when the item's link equals the scope or starts with scope + "/".
57
+ * For groups without a direct link, recursively checks child items.
58
+ * Dividers and section headers (no link, no items) are never matched.
59
+ *
60
+ * @private
61
+ * @param item - Sidebar item to check
62
+ * @param scope - Scope path (e.g. "/packages")
63
+ * @returns True when the item belongs to the scope
64
+ */
65
+ export function belongsToScope(item: SidebarDataItem, scope: string): boolean {
66
+ if (Object.hasOwn(item, 'link')) {
67
+ const { link } = item as { readonly link?: string }
68
+ if (link && (link === scope || link.startsWith(`${scope}/`))) {
69
+ return true
70
+ }
71
+ }
72
+ // Check children — auto-discovered groups from _meta.json may have no
73
+ // link on the group itself, only on their child items.
74
+ if (Object.hasOwn(item, 'items')) {
75
+ const { items } = item as { readonly items: readonly SidebarDataItem[] }
76
+ return items.some((child) => belongsToScope(child, scope))
77
+ }
78
+ return false
79
+ }
@@ -0,0 +1,102 @@
1
+ .cp-sidebar-links {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: 6px;
5
+ padding: 8px 0;
6
+ }
7
+
8
+ /* Position modifiers (`--above` / `--below`) used to own their own
9
+ divider hairlines + spacing, but the `.cp-sidebar-top` and
10
+ `.cp-sidebar-bottom` wrappers (added in v1) now provide the dividers
11
+ and edge-to-edge backgrounds. Keep the modifier classes as semantic
12
+ hooks for downstream consumers, but don't paint extra borders here. */
13
+
14
+ /* ── Base ───────────────────────────────────────────────── */
15
+
16
+ .cp-sidebar-link {
17
+ display: flex;
18
+ align-items: center;
19
+ gap: 8px;
20
+ padding: 6px 12px;
21
+ border-radius: 8px;
22
+ color: var(--rp-c-text-2);
23
+ font-size: 14px;
24
+ font-weight: 500;
25
+ line-height: 24px;
26
+ text-decoration: none;
27
+ transition:
28
+ color 0.15s,
29
+ background-color 0.15s,
30
+ border-color 0.15s,
31
+ opacity 0.15s;
32
+ }
33
+
34
+ /* ── Ghost (default) — no background, subtle hover ───── */
35
+
36
+ .cp-sidebar-link--ghost {
37
+ background-color: transparent;
38
+ }
39
+
40
+ .cp-sidebar-link--ghost:hover {
41
+ color: var(--rp-c-text-1);
42
+ background-color: var(--rp-c-bg-mute);
43
+ }
44
+
45
+ /* ── Brand — solid brand background ──────────────────── */
46
+
47
+ .cp-sidebar-link--brand {
48
+ color: var(--cp-c-on-brand);
49
+ background: var(--rp-c-brand-darker);
50
+ }
51
+
52
+ .cp-sidebar-link--brand:hover {
53
+ background: var(--rp-c-brand-dark);
54
+ }
55
+
56
+ /* ── Alt — outlined with border ──────────────────────── */
57
+
58
+ .cp-sidebar-link--alt {
59
+ border: 1px solid var(--rp-c-gray-light-3);
60
+ color: var(--rp-c-text-1);
61
+ background-color: var(--rp-c-bg);
62
+ }
63
+
64
+ html.dark .cp-sidebar-link--alt {
65
+ background-color: var(--rp-c-bg-mute);
66
+ border-color: var(--cp-c-sidebar-alt-border-dark);
67
+ }
68
+
69
+ .cp-sidebar-link--alt:hover {
70
+ opacity: 0.8;
71
+ background-color: var(--rp-c-bg-mute);
72
+ }
73
+
74
+ /* ── Shapes ──────────────────────────────────────────── */
75
+
76
+ .cp-sidebar-link--square {
77
+ border-radius: 8px;
78
+ }
79
+
80
+ .cp-sidebar-link--rounded {
81
+ border-radius: 9999px;
82
+ }
83
+
84
+ .cp-sidebar-link--circle {
85
+ border-radius: 50%;
86
+ justify-content: center;
87
+ width: 36px;
88
+ height: 36px;
89
+ padding: 0;
90
+ }
91
+
92
+ .cp-sidebar-link-icon {
93
+ flex-shrink: 0;
94
+ width: 16px;
95
+ height: 16px;
96
+ }
97
+
98
+ .cp-sidebar-link-text {
99
+ overflow: hidden;
100
+ text-overflow: ellipsis;
101
+ white-space: nowrap;
102
+ }
@@ -0,0 +1,122 @@
1
+ import { Link } from '@rspress/core/runtime'
2
+ import { match, P } from 'massaman/match'
3
+ import type React from 'react'
4
+
5
+ import { safeUrl } from '../../lib/safe-url.ts'
6
+ import { Icon } from '../shared/icon'
7
+
8
+ import './sidebar-links.css'
9
+
10
+ interface SidebarLinkItem {
11
+ readonly text: string
12
+ readonly link: string
13
+ readonly icon?: string | { readonly id: string; readonly color: string }
14
+ readonly style?: 'brand' | 'alt' | 'ghost'
15
+ readonly shape?: 'square' | 'rounded' | 'circle'
16
+ }
17
+
18
+ interface SidebarLinksProps {
19
+ readonly items: readonly SidebarLinkItem[]
20
+ readonly position: 'above' | 'below'
21
+ }
22
+
23
+ /**
24
+ * Render a group of sidebar navigation links above or below the main sidebar.
25
+ *
26
+ * @param props - Sidebar links props with items and position
27
+ * @returns React element with sidebar links or null
28
+ */
29
+ export function SidebarLinks(props: SidebarLinksProps): React.ReactElement | null {
30
+ return match(props.items.length > 0)
31
+ .with(true, () => (
32
+ <nav
33
+ className={`cp-sidebar-links cp-sidebar-links--${props.position}`}
34
+ aria-label={`Sidebar links ${props.position}`}
35
+ >
36
+ {props.items.map((item) => (
37
+ <SidebarLinkEntry key={item.link} item={item} />
38
+ ))}
39
+ </nav>
40
+ ))
41
+ .otherwise(() => null)
42
+ }
43
+
44
+ /**
45
+ * Render an icon from a sidebar link item's icon configuration.
46
+ *
47
+ * @private
48
+ * @param icon - Icon string or object with id/color
49
+ * @returns Icon element or null
50
+ */
51
+ function renderIcon(icon: SidebarLinkItem['icon']): React.ReactElement | null {
52
+ return match(icon)
53
+ .with(P.string, (id) => <Icon icon={id} className="cp-sidebar-link-icon" />)
54
+ .with({ id: P.string, color: P.string }, (i) => (
55
+ <Icon icon={i.id} className="cp-sidebar-link-icon" style={{ color: i.color }} />
56
+ ))
57
+ .otherwise(() => null)
58
+ }
59
+
60
+ /**
61
+ * Check whether a URL points to an external origin.
62
+ *
63
+ * @private
64
+ * @param link - Link URL to check
65
+ * @returns True when the link is absolute http(s)
66
+ */
67
+ function isExternal(link: string): boolean {
68
+ return link.startsWith('http://') || link.startsWith('https://')
69
+ }
70
+
71
+ /**
72
+ * Render a single sidebar link entry with optional icon.
73
+ * Uses client-side `Link` for internal routes, `<a>` for external URLs.
74
+ *
75
+ * @private
76
+ * @param props - Props with sidebar link item
77
+ * @returns Sidebar link element
78
+ */
79
+ function SidebarLinkEntry({ item }: { readonly item: SidebarLinkItem }): React.ReactElement | null {
80
+ const isCircle = item.shape === 'circle'
81
+ const content = (
82
+ <>
83
+ {renderIcon(item.icon)}
84
+ {match(isCircle)
85
+ .with(true, () => null)
86
+ .otherwise(() => (
87
+ <span className="cp-sidebar-link-text">{item.text}</span>
88
+ ))}
89
+ </>
90
+ )
91
+
92
+ const variant = item.style ?? 'ghost'
93
+ const shape = item.shape ?? 'square'
94
+ const cls = `cp-sidebar-link cp-sidebar-link--${variant} cp-sidebar-link--${shape}`
95
+ /* oxlint-disable unicorn/no-useless-undefined -- React aria-label expects undefined, not null */
96
+ const ariaLabel = match(isCircle)
97
+ .with(true, () => item.text)
98
+ .otherwise(() => undefined)
99
+ /* oxlint-enable unicorn/no-useless-undefined */
100
+
101
+ const safeLink = safeUrl(item.link)
102
+ if (safeLink === null) {
103
+ return null
104
+ }
105
+ return match(isExternal(safeLink))
106
+ .with(true, () => (
107
+ <a
108
+ href={safeLink}
109
+ className={cls}
110
+ target="_blank"
111
+ rel="noopener noreferrer"
112
+ aria-label={ariaLabel}
113
+ >
114
+ {content}
115
+ </a>
116
+ ))
117
+ .otherwise(() => (
118
+ <Link to={safeLink} className={cls} aria-label={ariaLabel}>
119
+ {content}
120
+ </Link>
121
+ ))
122
+ }
@@ -0,0 +1,42 @@
1
+ .cp-sidebar-promo {
2
+ margin: 12px 8px;
3
+ border: 1px solid var(--cp-c-border);
4
+ border-radius: var(--cp-radius-card);
5
+ padding: 14px;
6
+ background: var(--cp-c-bg-soft);
7
+ }
8
+
9
+ .cp-sidebar-promo__title {
10
+ font-size: 13px;
11
+ font-weight: 500;
12
+ margin: 0 0 4px;
13
+ color: var(--cp-c-text-1);
14
+ }
15
+
16
+ .cp-sidebar-promo__body {
17
+ font-size: 12px;
18
+ color: var(--cp-c-text-2);
19
+ line-height: 1.5;
20
+ margin: 0 0 10px;
21
+ }
22
+
23
+ .cp-sidebar-promo__cta {
24
+ display: inline-flex;
25
+ align-items: center;
26
+ justify-content: center;
27
+ width: 100%;
28
+ padding: 6px 12px;
29
+ font-size: 12px;
30
+ font-weight: 600;
31
+ border-radius: var(--cp-radius-pill);
32
+ background: var(--cp-c-brand-1);
33
+ color: var(--cp-c-brand-fg);
34
+ text-decoration: none;
35
+ transition: background 0.15s ease;
36
+ }
37
+
38
+ .cp-sidebar-promo__cta:hover {
39
+ background: var(--cp-c-brand-2);
40
+ color: var(--cp-c-brand-fg);
41
+ text-decoration: none;
42
+ }
@@ -0,0 +1,52 @@
1
+ import type React from 'react'
2
+
3
+ import { RouteLink } from '../../lib/route-link.tsx'
4
+ import { safeUrl } from '../../lib/safe-url.ts'
5
+
6
+ import './sidebar-promo.css'
7
+
8
+ export interface SidebarPromoProps {
9
+ /**
10
+ * Headline of the promo card.
11
+ */
12
+ readonly title: string
13
+ /**
14
+ * Body copy beneath the headline.
15
+ */
16
+ readonly body: string
17
+ /**
18
+ * CTA button label.
19
+ */
20
+ readonly ctaText: string
21
+ /**
22
+ * CTA destination URL.
23
+ */
24
+ readonly ctaHref: string
25
+ }
26
+
27
+ /**
28
+ * SidebarPromo — bordered promo card pinned at the bottom of the docs
29
+ * sidebar. Mirrors the mockup's "Ship docs that stay in sync" pattern.
30
+ *
31
+ * Renders `null` when `ctaHref` fails `safeUrl()` validation — the entire
32
+ * promo card disappears rather than rendering a card whose CTA goes
33
+ * nowhere safe.
34
+ *
35
+ * @param props - Promo card configuration.
36
+ * @returns React element, or `null` when the CTA href fails URL validation.
37
+ */
38
+ export function SidebarPromo(props: SidebarPromoProps): React.ReactElement | null {
39
+ const ctaHref = safeUrl(props.ctaHref)
40
+ if (ctaHref === null) {
41
+ return null
42
+ }
43
+ return (
44
+ <div className="cp-sidebar-promo">
45
+ <p className="cp-sidebar-promo__title">{props.title}</p>
46
+ <p className="cp-sidebar-promo__body">{props.body}</p>
47
+ <RouteLink className="cp-sidebar-promo__cta" href={ctaHref}>
48
+ {props.ctaText}
49
+ </RouteLink>
50
+ </div>
51
+ )
52
+ }