@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,103 @@
1
+ .cp-announce {
2
+ position: relative;
3
+ height: var(--cp-announce-h);
4
+ display: flex;
5
+ align-items: center;
6
+ justify-content: center;
7
+ gap: 12px;
8
+ padding: 0 48px;
9
+ background: linear-gradient(
10
+ 90deg,
11
+ var(--cp-c-brand-soft) 0%,
12
+ transparent 50%,
13
+ var(--cp-c-brand-soft) 100%
14
+ );
15
+ border-bottom: 1px solid var(--cp-c-border);
16
+ font-size: 12.5px;
17
+ color: var(--cp-c-text-2);
18
+ }
19
+
20
+ .cp-announce__pulse {
21
+ width: 6px;
22
+ height: 6px;
23
+ border-radius: 50%;
24
+ background: var(--cp-c-brand-1);
25
+ animation: cp-announce-pulse 2.4s cubic-bezier(0.4, 0, 0.6, 1) infinite;
26
+ }
27
+
28
+ @keyframes cp-announce-pulse {
29
+ 0%,
30
+ 100% {
31
+ box-shadow: 0 0 0 0 var(--cp-c-brand-soft);
32
+ }
33
+ 50% {
34
+ box-shadow: 0 0 0 6px transparent;
35
+ }
36
+ }
37
+
38
+ .cp-announce__msg {
39
+ display: inline-flex;
40
+ flex-wrap: wrap;
41
+ gap: 4px;
42
+ align-items: center;
43
+ }
44
+
45
+ .cp-announce__lead {
46
+ color: var(--cp-c-text-1);
47
+ font-style: normal;
48
+ font-weight: 500;
49
+ }
50
+
51
+ .cp-announce__cta {
52
+ color: var(--cp-c-brand-1);
53
+ font-weight: 500;
54
+ margin-left: 4px;
55
+ }
56
+
57
+ .cp-announce__cta:hover {
58
+ color: var(--cp-c-brand-2);
59
+ }
60
+
61
+ .cp-announce__close {
62
+ position: absolute;
63
+ right: 12px;
64
+ top: 50%;
65
+ transform: translateY(-50%);
66
+ width: 22px;
67
+ height: 22px;
68
+ display: grid;
69
+ place-items: center;
70
+ background: transparent;
71
+ border: 0;
72
+ border-radius: 4px;
73
+ color: var(--cp-c-text-3);
74
+ cursor: pointer;
75
+ }
76
+
77
+ .cp-announce__close:hover {
78
+ background: var(--cp-c-bg-soft);
79
+ color: var(--cp-c-text-1);
80
+ }
81
+
82
+ /* Mobile: shrink padding so the message doesn't wrap weirdly inside a
83
+ fixed 36px row. Let the bar grow vertically when the message + CTA
84
+ would otherwise collide with the close button. */
85
+ @media (max-width: 720px) {
86
+ html .cp-announce {
87
+ height: auto;
88
+ min-height: var(--cp-announce-h);
89
+ padding: 6px 36px 6px 12px;
90
+ font-size: 12px;
91
+ line-height: 1.35;
92
+ text-align: left;
93
+ justify-content: flex-start;
94
+ }
95
+ html .cp-announce__msg {
96
+ flex: 1;
97
+ flex-wrap: wrap;
98
+ row-gap: 2px;
99
+ }
100
+ html .cp-announce__close {
101
+ right: 6px;
102
+ }
103
+ }
@@ -0,0 +1,173 @@
1
+ import { match } from 'massaman/match'
2
+ import React, { useCallback, useEffect, useRef, useState } from 'react'
3
+
4
+ import { RouteLink } from '../../lib/route-link.tsx'
5
+ import { safeUrl } from '../../lib/safe-url.ts'
6
+
7
+ import './announcement-bar.css'
8
+
9
+ const STORAGE_PREFIX = 'ciderpress-announcement-dismissed:'
10
+ /**
11
+ * Initial height assumption before the bar mounts and measures itself.
12
+ * Matches the value Rspress's own `<Banner />` uses (`useState(36)`) so
13
+ * downstream sticky offsets are roughly correct on the first paint.
14
+ */
15
+ const INITIAL_HEIGHT = 36
16
+
17
+ export interface AnnouncementBarProps {
18
+ /**
19
+ * Stable ID — when present, dismissal is remembered in localStorage.
20
+ */
21
+ readonly id?: string
22
+ /**
23
+ * Lead text. Use for the highlighted phrase ("ciderpress 1.0", "NEW:", etc).
24
+ */
25
+ readonly lead?: React.ReactNode
26
+ /**
27
+ * Body of the announcement.
28
+ */
29
+ readonly children: React.ReactNode
30
+ /**
31
+ * Optional CTA link rendered after the message.
32
+ */
33
+ readonly cta?: { readonly href: string; readonly label: string }
34
+ /**
35
+ * Hide the dismiss button.
36
+ */
37
+ readonly persistent?: boolean
38
+ }
39
+
40
+ /**
41
+ * AnnouncementBar — full-width banner rendered above the topbar via the
42
+ * Layout `top` slot. Ships with a pulsing accent dot and an optional
43
+ * dismiss button that persists in localStorage when an `id` is provided.
44
+ *
45
+ * @param props - AnnouncementBar configuration.
46
+ * @returns React element, or null when the bar has been dismissed.
47
+ */
48
+ export function AnnouncementBar(props: AnnouncementBarProps): React.ReactElement | null {
49
+ const { id, lead, children, cta, persistent } = props
50
+ const [dismissed, setDismissed] = useState(() => readDismissed(id))
51
+ // Track the rendered height so downstream sticky offsets
52
+ // (`--rp-nav-height` calc + the docs bar's sticky `top`) clear the
53
+ // announcement bar without anybody hardcoding a number. Mirrors the
54
+ // pattern Rspress's own `<Banner />` uses: a state value + a
55
+ // callback ref + a sibling `<style>` tag exposing the value as a
56
+ // CSS variable.
57
+ const [height, setHeight] = useState(INITIAL_HEIGHT)
58
+ const measureRef = useRef<HTMLDivElement | null>(null)
59
+
60
+ useEffect(() => {
61
+ setDismissed(readDismissed(id))
62
+ }, [id])
63
+
64
+ const handleDismiss = useCallback(() => {
65
+ writeDismissed(id)
66
+ setDismissed(true)
67
+ }, [id])
68
+
69
+ const setMeasureRef = useCallback((element: HTMLDivElement | null) => {
70
+ measureRef.current = element
71
+ if (element !== null && element.offsetHeight > 0) {
72
+ setHeight(element.offsetHeight)
73
+ }
74
+ }, [])
75
+
76
+ return match(dismissed)
77
+ .with(true, () => null)
78
+ .otherwise(() => (
79
+ <>
80
+ <div ref={setMeasureRef} className="cp-announce" role="region" aria-label="Announcement">
81
+ <span className="cp-announce__pulse" aria-hidden="true" />
82
+ <span className="cp-announce__msg">
83
+ {match(lead)
84
+ .with(undefined, () => null)
85
+ .otherwise((l) => (
86
+ <em className="cp-announce__lead">{l}</em>
87
+ ))}{' '}
88
+ {children}
89
+ {match(cta)
90
+ .with(undefined, () => null)
91
+ .otherwise((c) => {
92
+ const href = safeUrl(c.href)
93
+ if (href === null) {
94
+ return null
95
+ }
96
+ return (
97
+ <RouteLink className="cp-announce__cta" href={href}>
98
+ {c.label} →
99
+ </RouteLink>
100
+ )
101
+ })}
102
+ </span>
103
+ {match(persistent === true)
104
+ .with(true, () => null)
105
+ .otherwise(() => (
106
+ <button
107
+ className="cp-announce__close"
108
+ type="button"
109
+ onClick={handleDismiss}
110
+ aria-label="Dismiss announcement"
111
+ >
112
+ <svg
113
+ width="12"
114
+ height="12"
115
+ viewBox="0 0 24 24"
116
+ fill="none"
117
+ stroke="currentColor"
118
+ strokeWidth="2"
119
+ >
120
+ <line x1="18" y1="6" x2="6" y2="18" />
121
+ <line x1="6" y1="6" x2="18" y2="18" />
122
+ </svg>
123
+ </button>
124
+ ))}
125
+ </div>
126
+ {/* Expose the measured height as a CSS variable so the docs
127
+ bar's sticky `top:` and the `--rp-nav-height` calc clear
128
+ the announcement without anybody hardcoding 36px. Pattern
129
+ cribbed directly from Rspress's `<Banner />`. */}
130
+ <style>{`:root { --cp-announcement-height: ${height}px; }`}</style>
131
+ </>
132
+ ))
133
+ }
134
+
135
+ /**
136
+ * Read the dismissed flag from localStorage.
137
+ *
138
+ * @private
139
+ * @param id - Announcement ID, or undefined.
140
+ * @returns true when the announcement has been dismissed.
141
+ */
142
+ function readDismissed(id: string | undefined): boolean {
143
+ return match(id)
144
+ .with(undefined, () => false)
145
+ .otherwise((key) => {
146
+ if (globalThis.window === undefined) {
147
+ return false
148
+ }
149
+ try {
150
+ return globalThis.localStorage.getItem(STORAGE_PREFIX + key) === '1'
151
+ } catch {
152
+ return false
153
+ }
154
+ })
155
+ }
156
+
157
+ /**
158
+ * Persist the dismissed flag to localStorage.
159
+ *
160
+ * @private
161
+ * @param id - Announcement ID, or undefined.
162
+ */
163
+ function writeDismissed(id: string | undefined): void {
164
+ match(id)
165
+ .with(undefined, () => {})
166
+ .otherwise((key) => {
167
+ try {
168
+ globalThis.localStorage.setItem(STORAGE_PREFIX + key, '1')
169
+ } catch {
170
+ // ignore — storage may be unavailable
171
+ }
172
+ })
173
+ }
@@ -0,0 +1,52 @@
1
+ .cp-ask-ai {
2
+ position: fixed;
3
+ bottom: var(--cp-s-20);
4
+ right: var(--cp-s-20);
5
+ z-index: var(--cp-z-floating);
6
+ display: inline-flex;
7
+ align-items: center;
8
+ gap: var(--cp-s-8);
9
+ padding: var(--cp-s-10) var(--cp-s-14);
10
+ background: var(--cp-c-bg-soft);
11
+ border: 1px solid var(--cp-c-border);
12
+ border-radius: var(--cp-radius-pill);
13
+ color: var(--cp-c-text-1);
14
+ font-family: var(--cp-font-family-base);
15
+ font-size: var(--cp-fs-ask-ai);
16
+ font-weight: 500;
17
+ cursor: pointer;
18
+ box-shadow: var(--cp-shadow-ask-ai);
19
+ backdrop-filter: blur(var(--cp-blur-base));
20
+ transition: border-color var(--cp-duration-fast) var(--cp-ease-base);
21
+ }
22
+
23
+ .cp-ask-ai:hover {
24
+ border-color: var(--cp-c-brand-soft);
25
+ }
26
+
27
+ .cp-ask-ai__icon {
28
+ width: var(--cp-size-ask-ai-icon);
29
+ height: var(--cp-size-ask-ai-icon);
30
+ border-radius: 50%;
31
+ background: var(--cp-gradient-brand);
32
+ display: grid;
33
+ place-items: center;
34
+ color: var(--cp-c-brand-fg);
35
+ font-family: var(--cp-font-family-mono);
36
+ font-size: var(--cp-fs-ask-ai-mark);
37
+ font-weight: 700;
38
+ }
39
+
40
+ .cp-ask-ai__label {
41
+ white-space: nowrap;
42
+ }
43
+
44
+ .cp-ask-ai__shortcut {
45
+ font-family: var(--cp-font-family-mono);
46
+ font-size: var(--cp-fs-ask-ai-shortcut);
47
+ color: var(--cp-c-text-3);
48
+ padding: var(--cp-s-1) var(--cp-s-5);
49
+ border: 1px solid var(--cp-c-border);
50
+ border-radius: var(--cp-radius-xs-sm);
51
+ margin-left: var(--cp-s-4);
52
+ }
@@ -0,0 +1,56 @@
1
+ import { match } from 'massaman/match'
2
+ import React, { useCallback } from 'react'
3
+
4
+ import './ask-ai-button.css'
5
+
6
+ export interface AskAIButtonProps {
7
+ /**
8
+ * Click handler. When omitted, the button does nothing.
9
+ */
10
+ readonly onClick?: () => void
11
+ /**
12
+ * Visible label. Defaults to "Ask AI".
13
+ */
14
+ readonly label?: string
15
+ /**
16
+ * Optional shortcut hint (e.g. "⌘J").
17
+ */
18
+ readonly shortcut?: string
19
+ /**
20
+ * Pre-positioned glyph for the badge (default: ★).
21
+ */
22
+ readonly badge?: React.ReactNode
23
+ }
24
+
25
+ /**
26
+ * AskAIButton — floating CTA pill anchored bottom-right of the viewport.
27
+ * Visual sibling of the page chrome; pairs with AI-search integrations.
28
+ *
29
+ * @param props - AskAIButton configuration.
30
+ * @returns React element.
31
+ */
32
+ export function AskAIButton(props: AskAIButtonProps): React.ReactElement {
33
+ const { onClick, label = 'Ask AI', shortcut, badge = '★' } = props
34
+
35
+ const handleClick = useCallback(() => {
36
+ match(onClick)
37
+ .with(undefined, () => {})
38
+ .otherwise((fn) => {
39
+ fn()
40
+ })
41
+ }, [onClick])
42
+
43
+ return (
44
+ <button className="cp-ask-ai" type="button" onClick={handleClick} aria-label={label}>
45
+ <span className="cp-ask-ai__icon" aria-hidden="true">
46
+ {badge}
47
+ </span>
48
+ <span className="cp-ask-ai__label">{label}</span>
49
+ {match(shortcut)
50
+ .with(undefined, () => null)
51
+ .otherwise((s) => (
52
+ <span className="cp-ask-ai__shortcut">{s}</span>
53
+ ))}
54
+ </button>
55
+ )
56
+ }
@@ -0,0 +1,70 @@
1
+ import React, { useEffect, useState } from 'react'
2
+ import { createPortal } from 'react-dom'
3
+
4
+ export interface ContentFooterPortalProps {
5
+ /**
6
+ * Children to portal into the in-rail doc footer slot.
7
+ */
8
+ readonly children: React.ReactNode
9
+ }
10
+
11
+ const HOST_ATTR = 'data-cp-content-footer-host'
12
+
13
+ /**
14
+ * ContentFooterPortal — portals its children into Rspress's `.rp-doc-footer`
15
+ * so the content lives INSIDE the doc rail (at the reading width) rather
16
+ * than at body level (where the `afterDoc` slot otherwise lands).
17
+ *
18
+ * Inserts a sentinel div as the FIRST child of `.rp-doc-footer` so the
19
+ * portalled content appears above Rspress's built-in `.rp-prev-next-page`
20
+ * pager. Watches DOM mutations so the portal re-attaches on client-side
21
+ * route changes (Rspress re-renders the doc-footer per page).
22
+ *
23
+ * @param props - Portal configuration.
24
+ * @returns React element.
25
+ */
26
+ export function ContentFooterPortal(props: ContentFooterPortalProps): React.ReactElement | null {
27
+ const [host, setHost] = useState<HTMLElement | null>(null)
28
+
29
+ useEffect(() => {
30
+ function ensureHost(): HTMLElement | null {
31
+ const docFooter = globalThis.document.querySelector('.rp-doc-footer')
32
+ if (docFooter === null) {
33
+ return null
34
+ }
35
+ const existing = docFooter.querySelector(`[${HOST_ATTR}]`) as HTMLElement | null
36
+ if (existing !== null) {
37
+ return existing
38
+ }
39
+ const node = globalThis.document.createElement('div')
40
+ node.setAttribute(HOST_ATTR, '')
41
+ const first = docFooter.firstChild
42
+ if (first === null) {
43
+ docFooter.append(node)
44
+ } else {
45
+ first.before(node)
46
+ }
47
+ return node
48
+ }
49
+
50
+ setHost(ensureHost())
51
+
52
+ const observer = new globalThis.MutationObserver(() => {
53
+ const next = ensureHost()
54
+ setHost((prev) => {
55
+ if (prev !== null && prev.isConnected) {
56
+ return prev
57
+ }
58
+ return next
59
+ })
60
+ })
61
+ observer.observe(globalThis.document.body, { childList: true, subtree: true })
62
+
63
+ return () => observer.disconnect()
64
+ }, [])
65
+
66
+ if (host === null) {
67
+ return null
68
+ }
69
+ return createPortal(<div className="cp-content-footer">{props.children}</div>, host)
70
+ }
@@ -0,0 +1,47 @@
1
+ .cp-feedback {
2
+ display: flex;
3
+ align-items: center;
4
+ justify-content: space-between;
5
+ padding: 14px 16px;
6
+ border: 1px solid var(--cp-c-border);
7
+ border-radius: var(--cp-radius-card);
8
+ margin-bottom: 24px;
9
+ background: var(--cp-c-bg-soft);
10
+ }
11
+
12
+ .cp-feedback__q {
13
+ font-size: 13px;
14
+ color: var(--cp-c-text-1);
15
+ }
16
+
17
+ .cp-feedback__actions {
18
+ display: flex;
19
+ gap: 6px;
20
+ }
21
+
22
+ .cp-feedback__btn {
23
+ display: inline-flex;
24
+ align-items: center;
25
+ gap: 6px;
26
+ padding: 6px 12px;
27
+ font-size: 12px;
28
+ font-weight: 500;
29
+ font-family: inherit;
30
+ border-radius: var(--cp-radius-pill);
31
+ border: 1px solid var(--cp-c-border);
32
+ background: transparent;
33
+ color: var(--cp-c-text-1);
34
+ cursor: pointer;
35
+ transition: all 0.15s ease;
36
+ white-space: nowrap;
37
+ }
38
+
39
+ .cp-feedback__btn:hover {
40
+ background: var(--cp-c-bg-elv);
41
+ border-color: var(--cp-c-border);
42
+ }
43
+
44
+ .cp-feedback__thanks {
45
+ font-size: 13px;
46
+ color: var(--cp-c-text-2);
47
+ }
@@ -0,0 +1,96 @@
1
+ import { match } from 'massaman/match'
2
+ import React, { useCallback, useState } from 'react'
3
+
4
+ import './feedback.css'
5
+
6
+ export interface FeedbackProps {
7
+ /**
8
+ * Question shown to the reader. Defaults to "Was this page helpful?".
9
+ */
10
+ readonly question?: string
11
+ /**
12
+ * Optional callback invoked with the user's answer.
13
+ */
14
+ readonly onSubmit?: (answer: 'yes' | 'no') => void
15
+ }
16
+
17
+ type FeedbackState = 'idle' | 'yes' | 'no'
18
+
19
+ /**
20
+ * Feedback — yes/no widget rendered at the bottom of doc pages.
21
+ * Submitting hides the buttons and shows a thank-you message.
22
+ *
23
+ * @param props - Feedback configuration.
24
+ * @returns React element.
25
+ */
26
+ export function Feedback(props: FeedbackProps): React.ReactElement {
27
+ const { question = 'Was this page helpful?', onSubmit } = props
28
+ const [state, setState] = useState<FeedbackState>('idle')
29
+
30
+ const handleClick = useCallback(
31
+ (answer: 'yes' | 'no') => () => {
32
+ setState(answer)
33
+ match(onSubmit)
34
+ .with(undefined, () => {})
35
+ .otherwise((fn) => {
36
+ fn(answer)
37
+ })
38
+ },
39
+ [onSubmit]
40
+ )
41
+
42
+ return (
43
+ <div className="cp-feedback">
44
+ {match(state)
45
+ .with('idle', () => (
46
+ <>
47
+ <span className="cp-feedback__q">{question}</span>
48
+ <div className="cp-feedback__actions">
49
+ <button className="cp-feedback__btn" type="button" onClick={handleClick('yes')}>
50
+ <ThumbIcon />
51
+ Yes
52
+ </button>
53
+ <button className="cp-feedback__btn" type="button" onClick={handleClick('no')}>
54
+ <ThumbIcon flipped />
55
+ No
56
+ </button>
57
+ </div>
58
+ </>
59
+ ))
60
+ .otherwise(() => (
61
+ <span className="cp-feedback__thanks">Thanks for the feedback.</span>
62
+ ))}
63
+ </div>
64
+ )
65
+ }
66
+
67
+ interface ThumbIconProps {
68
+ readonly flipped?: boolean
69
+ }
70
+
71
+ /**
72
+ * Thumb icon used in feedback buttons.
73
+ *
74
+ * @private
75
+ * @param props - Whether to flip the icon (for thumbs-down).
76
+ * @returns SVG element.
77
+ */
78
+ function ThumbIcon(props: ThumbIconProps): React.ReactElement {
79
+ const transform = match(props.flipped === true)
80
+ .with(true, () => 'rotate(180deg)')
81
+ .otherwise(() => 'none')
82
+
83
+ return (
84
+ <svg
85
+ width="11"
86
+ height="11"
87
+ viewBox="0 0 24 24"
88
+ fill="none"
89
+ stroke="currentColor"
90
+ strokeWidth="2"
91
+ style={{ transform }}
92
+ >
93
+ <path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3" />
94
+ </svg>
95
+ )
96
+ }
@@ -0,0 +1,25 @@
1
+ .cp-meta-actions {
2
+ display: flex;
3
+ flex-wrap: wrap;
4
+ gap: 16px;
5
+ font-family: var(--cp-font-family-mono);
6
+ font-size: 12px;
7
+ color: var(--cp-c-text-3);
8
+ margin-bottom: 24px;
9
+ }
10
+
11
+ .cp-meta-actions__item {
12
+ display: inline-flex;
13
+ align-items: center;
14
+ gap: 6px;
15
+ color: var(--cp-c-text-2);
16
+ transition: color 0.15s ease;
17
+ }
18
+
19
+ .cp-meta-actions__item:hover {
20
+ color: var(--cp-c-text-1);
21
+ }
22
+
23
+ .cp-meta-actions__icon {
24
+ display: inline-flex;
25
+ }
@@ -0,0 +1,57 @@
1
+ import { match } from 'massaman/match'
2
+ import React from 'react'
3
+
4
+ import { safeUrl } from '../../lib/safe-url.ts'
5
+
6
+ import './meta-actions.css'
7
+
8
+ export interface MetaAction {
9
+ readonly href: string
10
+ readonly label: string
11
+ readonly icon?: React.ReactNode
12
+ }
13
+
14
+ export interface MetaActionsProps {
15
+ /**
16
+ * List of actions to render — typically Edit on GitHub, Report issue, Discuss.
17
+ */
18
+ readonly actions: readonly MetaAction[]
19
+ }
20
+
21
+ /**
22
+ * MetaActions — horizontal row of mono-styled links rendered below the
23
+ * feedback widget on doc pages. Each action is an external/internal link
24
+ * with an optional leading icon.
25
+ *
26
+ * @param props - Actions to render.
27
+ * @returns React element, or null when no actions are provided.
28
+ */
29
+ export function MetaActions(props: MetaActionsProps): React.ReactElement | null {
30
+ const safeActions = props.actions.flatMap((action) => {
31
+ const href = safeUrl(action.href)
32
+ if (href === null) {
33
+ return []
34
+ }
35
+ return [{ ...action, href }]
36
+ })
37
+ return match(safeActions.length === 0)
38
+ .with(true, () => null)
39
+ .otherwise(() => (
40
+ <div className="cp-meta-actions">
41
+ {safeActions.map((action) => (
42
+ <a
43
+ key={`${action.label}:${action.href}`}
44
+ className="cp-meta-actions__item"
45
+ href={action.href}
46
+ >
47
+ {match(action.icon)
48
+ .with(undefined, () => null)
49
+ .otherwise((icon) => (
50
+ <span className="cp-meta-actions__icon">{icon}</span>
51
+ ))}
52
+ <span>{action.label}</span>
53
+ </a>
54
+ ))}
55
+ </div>
56
+ ))
57
+ }