@wrikka/create-docs 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (380) hide show
  1. package/README.md +137 -0
  2. package/bin/cli.js +408 -0
  3. package/dist/index.cjs +20 -0
  4. package/dist/index.d.cts +475 -0
  5. package/dist/index.d.ts +475 -0
  6. package/dist/index.js +20 -0
  7. package/docs/_dir.yml +2 -0
  8. package/docs/config.md +75 -0
  9. package/docs/data-sources.md +88 -0
  10. package/docs/deploy.md +38 -0
  11. package/docs/features.md +51 -0
  12. package/docs/getting-started.md +42 -0
  13. package/docs/index.md +34 -0
  14. package/docs/markdown.md +154 -0
  15. package/docs/theming.md +53 -0
  16. package/package.json +142 -0
  17. package/scripts/generate-docs-index.ts +111 -0
  18. package/scripts/github-pull.ts +124 -0
  19. package/src/adapters/config/index.ts +1 -0
  20. package/src/adapters/config/node-config-loader.ts +36 -0
  21. package/src/adapters/fs/fs-docs-loader.ts +31 -0
  22. package/src/adapters/fs/index.ts +12 -0
  23. package/src/adapters/fs/memory-fs.ts +60 -0
  24. package/src/adapters/fs/node-fs.ts +116 -0
  25. package/src/adapters/git/index.ts +164 -0
  26. package/src/adapters/github/github-pull.ts +340 -0
  27. package/src/adapters/index.ts +6 -0
  28. package/src/adapters/velite.ts +112 -0
  29. package/src/adapters/vite/docs-watcher.ts +36 -0
  30. package/src/adapters/vite/index.ts +2 -0
  31. package/src/adapters/vite/polling-watcher.ts +89 -0
  32. package/src/code-block-enhancements/index.ts +3 -0
  33. package/src/config/index.ts +15 -0
  34. package/src/content/index.ts +11 -0
  35. package/src/content-validation/index.ts +5 -0
  36. package/src/index.ts +174 -0
  37. package/src/live-editor/index.ts +7 -0
  38. package/src/modules/analytics/application/plausible-adapter.ts +103 -0
  39. package/src/modules/analytics/application/umami-adapter.ts +109 -0
  40. package/src/modules/analytics/domain/events/index.ts +37 -0
  41. package/src/modules/analytics/domain/models/index.ts +35 -0
  42. package/src/modules/analytics/domain/operations/analytics-operations.ts +67 -0
  43. package/src/modules/analytics/domain/validators/index.ts +50 -0
  44. package/src/modules/analytics/index.ts +9 -0
  45. package/src/modules/analytics/ports/analytics-port.ts +17 -0
  46. package/src/modules/analytics/types/analytics.ts +16 -0
  47. package/src/modules/api-docs/application/type-docs-adapter.ts +34 -0
  48. package/src/modules/api-docs/domain/events/index.ts +42 -0
  49. package/src/modules/api-docs/domain/models/index.ts +40 -0
  50. package/src/modules/api-docs/domain/operations/api-docs-operations.ts +59 -0
  51. package/src/modules/api-docs/domain/validators/index.ts +56 -0
  52. package/src/modules/api-docs/index.ts +8 -0
  53. package/src/modules/api-docs/ports/api-docs-port.ts +21 -0
  54. package/src/modules/api-docs/types/api-docs.ts +15 -0
  55. package/src/modules/api-reference/openapi.ts +166 -0
  56. package/src/modules/api-reference/orpc.ts +94 -0
  57. package/src/modules/code-block-enhancements/application/usecases/index.ts +39 -0
  58. package/src/modules/code-block-enhancements/domain/code-operations.ts +72 -0
  59. package/src/modules/code-block-enhancements/domain/events/index.ts +58 -0
  60. package/src/modules/code-block-enhancements/domain/index.ts +11 -0
  61. package/src/modules/code-block-enhancements/domain/models/index.ts +36 -0
  62. package/src/modules/code-block-enhancements/domain/validators/index.ts +50 -0
  63. package/src/modules/code-block-enhancements/index.ts +10 -0
  64. package/src/modules/code-block-enhancements/ports/index.ts +18 -0
  65. package/src/modules/code-block-enhancements/types/index.ts +37 -0
  66. package/src/modules/code-execution/application/sandbox-adapter.ts +44 -0
  67. package/src/modules/code-execution/domain/events/index.ts +58 -0
  68. package/src/modules/code-execution/domain/models/index.ts +33 -0
  69. package/src/modules/code-execution/domain/operations/execution-operations.ts +60 -0
  70. package/src/modules/code-execution/domain/validators/index.ts +50 -0
  71. package/src/modules/code-execution/index.ts +8 -0
  72. package/src/modules/code-execution/ports/execution-port.ts +15 -0
  73. package/src/modules/code-execution/types/execution.ts +15 -0
  74. package/src/modules/comments/application/giscus-adapter.ts +55 -0
  75. package/src/modules/comments/domain/events/index.ts +55 -0
  76. package/src/modules/comments/domain/models/index.ts +30 -0
  77. package/src/modules/comments/domain/operations/comment-operations.ts +46 -0
  78. package/src/modules/comments/domain/validators/index.ts +50 -0
  79. package/src/modules/comments/index.ts +8 -0
  80. package/src/modules/comments/ports/comment-port.ts +18 -0
  81. package/src/modules/comments/types/comments.ts +19 -0
  82. package/src/modules/config/application/usecases/index.ts +1 -0
  83. package/src/modules/config/application/usecases/loadConfig.ts +38 -0
  84. package/src/modules/config/application/workflows/index.ts +1 -0
  85. package/src/modules/config/application/workflows/validateConfig.ts +14 -0
  86. package/src/modules/config/domain/events/index.ts +50 -0
  87. package/src/modules/config/domain/models/index.ts +9 -0
  88. package/src/modules/config/domain/operations/defaults.ts +74 -0
  89. package/src/modules/config/domain/operations/index.ts +2 -0
  90. package/src/modules/config/domain/operations/resolve.ts +40 -0
  91. package/src/modules/config/domain/validators/index.ts +152 -0
  92. package/src/modules/config/index.ts +23 -0
  93. package/src/modules/config/ports/index.ts +15 -0
  94. package/src/modules/config/types/index.ts +113 -0
  95. package/src/modules/config/types/public.ts +19 -0
  96. package/src/modules/content/application/cache/index.ts +6 -0
  97. package/src/modules/content/application/cache/parse-cache.ts +6 -0
  98. package/src/modules/content/application/cache/parseCache.ts +56 -0
  99. package/src/modules/content/application/usecases/index.ts +2 -0
  100. package/src/modules/content/application/usecases/load-doc-page.ts +1 -0
  101. package/src/modules/content/application/usecases/loadDocPage.ts +111 -0
  102. package/src/modules/content/application/usecases/scanDocsDir.ts +105 -0
  103. package/src/modules/content/application/workflows/index.ts +1 -0
  104. package/src/modules/content/application/workflows/processDocs.ts +51 -0
  105. package/src/modules/content/domain/events/index.ts +61 -0
  106. package/src/modules/content/domain/models/index.ts +13 -0
  107. package/src/modules/content/domain/operations/build-page.ts +146 -0
  108. package/src/modules/content/domain/operations/buildPage.ts +93 -0
  109. package/src/modules/content/domain/operations/index.ts +17 -0
  110. package/src/modules/content/domain/operations/parse.ts +241 -0
  111. package/src/modules/content/domain/validators/frontmatter.ts +82 -0
  112. package/src/modules/content/domain/validators/index.ts +6 -0
  113. package/src/modules/content/index.ts +27 -0
  114. package/src/modules/content/ports/index.ts +45 -0
  115. package/src/modules/content/ports/velite.ts +134 -0
  116. package/src/modules/content/types/index.ts +76 -0
  117. package/src/modules/content/types/public.ts +1 -0
  118. package/src/modules/content-linting/application/builtin-linting-adapter.ts +82 -0
  119. package/src/modules/content-linting/domain/events/index.ts +58 -0
  120. package/src/modules/content-linting/domain/models/index.ts +46 -0
  121. package/src/modules/content-linting/domain/operations/linting-operations.ts +82 -0
  122. package/src/modules/content-linting/domain/validators/index.ts +50 -0
  123. package/src/modules/content-linting/index.ts +8 -0
  124. package/src/modules/content-linting/ports/linting-port.ts +21 -0
  125. package/src/modules/content-linting/types/linting.ts +18 -0
  126. package/src/modules/content-validation/application/usecases/index.ts +18 -0
  127. package/src/modules/content-validation/domain/events/index.ts +7 -0
  128. package/src/modules/content-validation/domain/index.ts +16 -0
  129. package/src/modules/content-validation/domain/models/index.ts +33 -0
  130. package/src/modules/content-validation/domain/operations/index.ts +157 -0
  131. package/src/modules/content-validation/domain/types/index.ts +17 -0
  132. package/src/modules/content-validation/domain/validators/index.ts +8 -0
  133. package/src/modules/content-validation/index.ts +9 -0
  134. package/src/modules/content-validation/ports/index.ts +15 -0
  135. package/src/modules/content-validation/types/index.ts +5 -0
  136. package/src/modules/export/application/puppeteer-export-adapter.ts +63 -0
  137. package/src/modules/export/domain/events/index.ts +55 -0
  138. package/src/modules/export/domain/models/index.ts +38 -0
  139. package/src/modules/export/domain/operations/export-operations.ts +56 -0
  140. package/src/modules/export/domain/validators/index.ts +52 -0
  141. package/src/modules/export/index.ts +8 -0
  142. package/src/modules/export/ports/export-port.ts +15 -0
  143. package/src/modules/export/types/export.ts +15 -0
  144. package/src/modules/external-search/application/algolia-adapter.ts +182 -0
  145. package/src/modules/external-search/application/meilisearch-adapter.ts +161 -0
  146. package/src/modules/external-search/domain/events/index.ts +42 -0
  147. package/src/modules/external-search/domain/models/index.ts +42 -0
  148. package/src/modules/external-search/domain/operations/search-operations.ts +53 -0
  149. package/src/modules/external-search/domain/validators/index.ts +55 -0
  150. package/src/modules/external-search/index.ts +9 -0
  151. package/src/modules/external-search/ports/external-search-port.ts +23 -0
  152. package/src/modules/external-search/types/search.ts +19 -0
  153. package/src/modules/git-history/application/simple-git-adapter.ts +46 -0
  154. package/src/modules/git-history/domain/events/index.ts +34 -0
  155. package/src/modules/git-history/domain/models/index.ts +25 -0
  156. package/src/modules/git-history/domain/operations/git-history-operations.ts +56 -0
  157. package/src/modules/git-history/domain/validators/index.ts +52 -0
  158. package/src/modules/git-history/index.ts +8 -0
  159. package/src/modules/git-history/ports/git-history-port.ts +21 -0
  160. package/src/modules/git-history/types/git-history.ts +15 -0
  161. package/src/modules/i18n/application/fs-locale-loader.ts +39 -0
  162. package/src/modules/i18n/domain/events/index.ts +34 -0
  163. package/src/modules/i18n/domain/models/index.ts +34 -0
  164. package/src/modules/i18n/domain/operations/locale-operations.ts +74 -0
  165. package/src/modules/i18n/domain/validators/index.ts +52 -0
  166. package/src/modules/i18n/index.ts +8 -0
  167. package/src/modules/i18n/ports/locale-loader-port.ts +18 -0
  168. package/src/modules/i18n/types/locale.ts +17 -0
  169. package/src/modules/image-optimization/application/sharp-adapter.ts +58 -0
  170. package/src/modules/image-optimization/domain/events/index.ts +39 -0
  171. package/src/modules/image-optimization/domain/models/index.ts +27 -0
  172. package/src/modules/image-optimization/domain/operations/image-operations.ts +65 -0
  173. package/src/modules/image-optimization/domain/validators/index.ts +52 -0
  174. package/src/modules/image-optimization/index.ts +8 -0
  175. package/src/modules/image-optimization/ports/image-optimizer-port.ts +15 -0
  176. package/src/modules/image-optimization/types/image.ts +17 -0
  177. package/src/modules/live-editor/application/usecases/index.ts +60 -0
  178. package/src/modules/live-editor/domain/events/index.ts +37 -0
  179. package/src/modules/live-editor/domain/gitOperations.ts +110 -0
  180. package/src/modules/live-editor/domain/index.ts +15 -0
  181. package/src/modules/live-editor/domain/models/index.ts +21 -0
  182. package/src/modules/live-editor/domain/validators/index.ts +55 -0
  183. package/src/modules/live-editor/index.ts +14 -0
  184. package/src/modules/live-editor/ports/index.ts +49 -0
  185. package/src/modules/live-editor/types/index.ts +80 -0
  186. package/src/modules/live-preview/application/vite-preview-adapter.ts +20 -0
  187. package/src/modules/live-preview/domain/operations/preview-operations.ts +56 -0
  188. package/src/modules/live-preview/index.ts +8 -0
  189. package/src/modules/live-preview/ports/preview-port.ts +20 -0
  190. package/src/modules/live-preview/types/preview.ts +16 -0
  191. package/src/modules/mdx/application/mdx-js-adapter.ts +38 -0
  192. package/src/modules/mdx/domain/operations/parse-mdx.ts +43 -0
  193. package/src/modules/mdx/index.ts +8 -0
  194. package/src/modules/mdx/ports/mdx-parser-port.ts +15 -0
  195. package/src/modules/mdx/types/mdx.ts +14 -0
  196. package/src/modules/navigation/application/usecases/buildNav.ts +15 -0
  197. package/src/modules/navigation/application/usecases/buildSidebar.ts +45 -0
  198. package/src/modules/navigation/application/usecases/index.ts +2 -0
  199. package/src/modules/navigation/domain/events/index.ts +30 -0
  200. package/src/modules/navigation/domain/models/index.ts +7 -0
  201. package/src/modules/navigation/domain/operations/group.ts +79 -0
  202. package/src/modules/navigation/domain/operations/index.ts +10 -0
  203. package/src/modules/navigation/domain/operations/sort.ts +27 -0
  204. package/src/modules/navigation/domain/shared/utils/array.ts +39 -0
  205. package/src/modules/navigation/domain/validators/index.ts +88 -0
  206. package/src/modules/navigation/index.ts +15 -0
  207. package/src/modules/navigation/ports/index.ts +9 -0
  208. package/src/modules/navigation/types/index.ts +38 -0
  209. package/src/modules/navigation/types/public.ts +9 -0
  210. package/src/modules/openapi/application/swagger-parser-adapter.ts +178 -0
  211. package/src/modules/openapi/domain/operations/parse-openapi.ts +46 -0
  212. package/src/modules/openapi/index.ts +8 -0
  213. package/src/modules/openapi/ports/openapi-parser-port.ts +15 -0
  214. package/src/modules/openapi/types/openapi.ts +37 -0
  215. package/src/modules/plugin/application/usecases/build-virtual-modules.ts +53 -0
  216. package/src/modules/plugin/application/usecases/index.ts +1 -0
  217. package/src/modules/plugin/application/workflows/build-docs.ts +79 -0
  218. package/src/modules/plugin/application/workflows/index.ts +1 -0
  219. package/src/modules/plugin/domain/events/index.ts +62 -0
  220. package/src/modules/plugin/domain/models/index.ts +1 -0
  221. package/src/modules/plugin/domain/operations/build-virtual.ts +76 -0
  222. package/src/modules/plugin/domain/operations/index.ts +11 -0
  223. package/src/modules/plugin/domain/validators/index.ts +30 -0
  224. package/src/modules/plugin/index.ts +14 -0
  225. package/src/modules/plugin/ports/index.ts +21 -0
  226. package/src/modules/plugin/types/index.ts +27 -0
  227. package/src/modules/plugin/types/public.ts +1 -0
  228. package/src/modules/search/application/usecases/index.ts +1 -0
  229. package/src/modules/search/application/usecases/indexPages.ts +15 -0
  230. package/src/modules/search/domain/events/index.ts +32 -0
  231. package/src/modules/search/domain/models/index.ts +1 -0
  232. package/src/modules/search/domain/operations/build-index.ts +6 -0
  233. package/src/modules/search/domain/operations/buildIndex.ts +70 -0
  234. package/src/modules/search/domain/operations/index.ts +7 -0
  235. package/src/modules/search/domain/operations/query.ts +186 -0
  236. package/src/modules/search/domain/validators/index.ts +32 -0
  237. package/src/modules/search/index.ts +9 -0
  238. package/src/modules/search/types/index.ts +22 -0
  239. package/src/modules/search/types/public.ts +1 -0
  240. package/src/modules/search-analytics/application/local-storage-adapter.ts +68 -0
  241. package/src/modules/search-analytics/domain/operations/search-analytics-operations.ts +74 -0
  242. package/src/modules/search-analytics/index.ts +8 -0
  243. package/src/modules/search-analytics/ports/search-analytics-port.ts +20 -0
  244. package/src/modules/search-analytics/types/search-analytics.ts +16 -0
  245. package/src/modules/search-highlighting/application/dom-highlighting-adapter.ts +36 -0
  246. package/src/modules/search-highlighting/domain/operations/highlighting-operations.ts +72 -0
  247. package/src/modules/search-highlighting/index.ts +8 -0
  248. package/src/modules/search-highlighting/ports/highlighting-port.ts +14 -0
  249. package/src/modules/search-highlighting/types/highlighting.ts +15 -0
  250. package/src/modules/seo-optimization/application/usecases/index.ts +18 -0
  251. package/src/modules/seo-optimization/domain/index.ts +12 -0
  252. package/src/modules/seo-optimization/domain/seoOperations.ts +92 -0
  253. package/src/modules/seo-optimization/index.ts +8 -0
  254. package/src/modules/seo-optimization/ports/index.ts +18 -0
  255. package/src/modules/seo-optimization/types/index.ts +41 -0
  256. package/src/modules/theme/application/css-theme-adapter.ts +50 -0
  257. package/src/modules/theme/domain/operations/theme-operations.ts +68 -0
  258. package/src/modules/theme/index.ts +8 -0
  259. package/src/modules/theme/ports/theme-port.ts +8 -0
  260. package/src/modules/theme/types/theme.ts +12 -0
  261. package/src/modules/toc/application/markdown-toc-adapter.ts +106 -0
  262. package/src/modules/toc/domain/operations/toc-operations.ts +92 -0
  263. package/src/modules/toc/index.ts +8 -0
  264. package/src/modules/toc/ports/toc-port.ts +21 -0
  265. package/src/modules/toc/types/toc.ts +17 -0
  266. package/src/modules/toc-generator/application/usecases/index.ts +30 -0
  267. package/src/modules/toc-generator/domain/index.ts +12 -0
  268. package/src/modules/toc-generator/domain/tocOperations.ts +104 -0
  269. package/src/modules/toc-generator/index.ts +9 -0
  270. package/src/modules/toc-generator/ports/index.ts +20 -0
  271. package/src/modules/toc-generator/types/index.ts +50 -0
  272. package/src/modules/version-control/domain/events/index.ts +39 -0
  273. package/src/modules/version-control/domain/models/index.ts +25 -0
  274. package/src/modules/version-control/domain/validators/index.ts +50 -0
  275. package/src/modules/versioning/application/fs-version-loader.ts +44 -0
  276. package/src/modules/versioning/domain/operations/versioning-operations.ts +80 -0
  277. package/src/modules/versioning/index.ts +8 -0
  278. package/src/modules/versioning/ports/version-loader-port.ts +18 -0
  279. package/src/modules/versioning/types/versioning.ts +17 -0
  280. package/src/modules/workflow/domain/events/index.ts +55 -0
  281. package/src/modules/workflow/domain/models/index.ts +40 -0
  282. package/src/modules/workflow/domain/validators/index.ts +50 -0
  283. package/src/modules/yaml-parser/application/js-yaml-adapter.ts +34 -0
  284. package/src/modules/yaml-parser/domain/operations/parse-yaml.ts +58 -0
  285. package/src/modules/yaml-parser/index.ts +7 -0
  286. package/src/modules/yaml-parser/ports/yaml-parser-port.ts +18 -0
  287. package/src/navigation/index.ts +9 -0
  288. package/src/plugin/index.ts +4 -0
  289. package/src/presentation/index.ts +1 -0
  290. package/src/presentation/live-editor/index.ts +22 -0
  291. package/src/presentation/live-editor/live-editor-component.ts +219 -0
  292. package/src/presentation/live-editor/virtual-module.ts +16 -0
  293. package/src/presentation/solid/CodeBlock.tsx +120 -0
  294. package/src/presentation/solid/Toc.tsx +58 -0
  295. package/src/presentation/solid/index.ts +11 -0
  296. package/src/presentation/vite-plugin/docs-plugin.ts +212 -0
  297. package/src/presentation/vite-plugin/index.ts +9 -0
  298. package/src/presentation/vite-plugin/transform-markdown.ts +26 -0
  299. package/src/runtime/analytics.ts +59 -0
  300. package/src/runtime/api-diff.ts +115 -0
  301. package/src/runtime/app.tsx +32 -0
  302. package/src/runtime/components/AccentPicker.tsx +114 -0
  303. package/src/runtime/components/ApiPlayground.tsx +332 -0
  304. package/src/runtime/components/AskAiDialog.tsx +127 -0
  305. package/src/runtime/components/BackToTop.tsx +38 -0
  306. package/src/runtime/components/Banner.tsx +75 -0
  307. package/src/runtime/components/Breadcrumbs.tsx +42 -0
  308. package/src/runtime/components/CollectionDropdown.tsx +85 -0
  309. package/src/runtime/components/CustomizeDrawer.tsx +195 -0
  310. package/src/runtime/components/DocMarkdown.tsx +579 -0
  311. package/src/runtime/components/DocPrevNext.tsx +54 -0
  312. package/src/runtime/components/DocToc.tsx +147 -0
  313. package/src/runtime/components/DocsDropdown.tsx +184 -0
  314. package/src/runtime/components/Footer.tsx +52 -0
  315. package/src/runtime/components/GitHubAuth.tsx +82 -0
  316. package/src/runtime/components/GitHubStats.tsx +73 -0
  317. package/src/runtime/components/Head.tsx +225 -0
  318. package/src/runtime/components/LocaleDropdown.tsx +75 -0
  319. package/src/runtime/components/MobileBottomNav.tsx +111 -0
  320. package/src/runtime/components/NotFound.tsx +139 -0
  321. package/src/runtime/components/PageActions.tsx +144 -0
  322. package/src/runtime/components/RelatedDocs.tsx +50 -0
  323. package/src/runtime/components/ScrollProgress.tsx +32 -0
  324. package/src/runtime/components/SearchPalette.tsx +333 -0
  325. package/src/runtime/components/ShowcaseCard.tsx +87 -0
  326. package/src/runtime/components/SidebarNav.tsx +237 -0
  327. package/src/runtime/components/Skeleton.tsx +39 -0
  328. package/src/runtime/components/ThemeToggle.tsx +26 -0
  329. package/src/runtime/components/TopNav.tsx +270 -0
  330. package/src/runtime/components/VersionDropdown.tsx +189 -0
  331. package/src/runtime/components/markdown/components.ts +280 -0
  332. package/src/runtime/config.ts +225 -0
  333. package/src/runtime/content.ts +451 -0
  334. package/src/runtime/context.tsx +30 -0
  335. package/src/runtime/data.ts +93 -0
  336. package/src/runtime/github.ts +226 -0
  337. package/src/runtime/icons.ts +32 -0
  338. package/src/runtime/index.ts +153 -0
  339. package/src/runtime/layouts/DocsLayout.tsx +144 -0
  340. package/src/runtime/markdown-content.css +996 -0
  341. package/src/runtime/pages/AbTestPage.tsx +88 -0
  342. package/src/runtime/pages/AnalyticsPage.tsx +90 -0
  343. package/src/runtime/pages/ApiDiffPage.tsx +86 -0
  344. package/src/runtime/pages/ApiEndpointPage.tsx +248 -0
  345. package/src/runtime/pages/BuilderPage.tsx +150 -0
  346. package/src/runtime/pages/ChangelogPage.tsx +210 -0
  347. package/src/runtime/pages/CollectionPage.tsx +140 -0
  348. package/src/runtime/pages/CommunityPage.tsx +384 -0
  349. package/src/runtime/pages/CreatePage.tsx +448 -0
  350. package/src/runtime/pages/DocPage.tsx +193 -0
  351. package/src/runtime/pages/EditPage.tsx +201 -0
  352. package/src/runtime/pages/HomePage.tsx +187 -0
  353. package/src/runtime/pages/IssuesPage.tsx +140 -0
  354. package/src/runtime/pages/OAuthCallbackPage.tsx +80 -0
  355. package/src/runtime/pages/PluginsPage.tsx +249 -0
  356. package/src/runtime/pages/ShowcasePage.tsx +202 -0
  357. package/src/runtime/pwa.ts +39 -0
  358. package/src/runtime/router.tsx +192 -0
  359. package/src/runtime/seo.ts +192 -0
  360. package/src/runtime/theme.css +175 -0
  361. package/src/runtime/theme.ts +224 -0
  362. package/src/runtime/types.ts +217 -0
  363. package/src/search/index.ts +6 -0
  364. package/src/seo-optimization/index.ts +4 -0
  365. package/src/shared/constants/index.ts +26 -0
  366. package/src/shared/errors/index.ts +195 -0
  367. package/src/shared/index.ts +27 -0
  368. package/src/shared/types/brand.ts +14 -0
  369. package/src/shared/types/client.d.ts +45 -0
  370. package/src/shared/types/either.ts +38 -0
  371. package/src/shared/types/index.ts +42 -0
  372. package/src/shared/types/option.ts +52 -0
  373. package/src/shared/types/result.ts +81 -0
  374. package/src/shared/utils/array.ts +97 -0
  375. package/src/shared/utils/index.ts +46 -0
  376. package/src/shared/utils/object.ts +47 -0
  377. package/src/shared/utils/string.ts +74 -0
  378. package/src/toc-generator/index.ts +1 -0
  379. package/src/types/js-yaml.d.ts +42 -0
  380. package/src/worker.ts +462 -0
@@ -0,0 +1,332 @@
1
+ import { createSignal, For, Show } from "solid-js";
2
+ import type { ApiEndpoint } from "../types";
3
+
4
+ const METHOD_COLOR: Record<string, string> = {
5
+ GET: "text-accent",
6
+ POST: "text-success",
7
+ PUT: "text-warning",
8
+ PATCH: "text-warning",
9
+ DELETE: "text-destructive",
10
+ };
11
+
12
+ function buildCurl(ep: ApiEndpoint, url: string, body: string): string {
13
+ const lines = [`curl -X ${ep.method} '${url}'`];
14
+ if (body.trim()) {
15
+ lines.push(` -H 'Content-Type: application/json'`, ` -d '${body}'`);
16
+ }
17
+ return lines.join(" \\\n");
18
+ }
19
+
20
+ function buildCommand(
21
+ ep: ApiEndpoint,
22
+ argValues: Record<string, string>,
23
+ ): string {
24
+ if (!ep.cli) return `${ep.method} ${ep.path}`;
25
+ const parts = [ep.cli.command];
26
+ if (ep.cli.subcommand) parts.push(ep.cli.subcommand);
27
+ for (const a of ep.cli.args ?? []) {
28
+ const v = argValues[a.name] ?? "";
29
+ if (v) parts.push(`--${a.name}`, v);
30
+ }
31
+ return parts.join(" ");
32
+ }
33
+
34
+ export function ApiPlayground(props: {
35
+ endpoint: ApiEndpoint;
36
+ style?: string;
37
+ }) {
38
+ const ep = () => props.endpoint;
39
+ const style = () => props.style ?? "rest";
40
+
41
+ const [paramValues, setParamValues] = createSignal<Record<string, string>>(
42
+ {},
43
+ );
44
+ const [argValues, setArgValues] = createSignal<Record<string, string>>({});
45
+ const [body, setBody] = createSignal(
46
+ ep().requestBody?.example != null
47
+ ? JSON.stringify(ep().requestBody?.example, null, 2)
48
+ : "",
49
+ );
50
+ const [query, setQuery] = createSignal(ep().graphql?.query ?? "");
51
+ const [variables, setVariables] = createSignal(
52
+ ep().graphql?.variables != null
53
+ ? JSON.stringify(ep().graphql?.variables, null, 2)
54
+ : "",
55
+ );
56
+ const [sending, setSending] = createSignal(false);
57
+ const [result, setResult] = createSignal<{
58
+ status: number;
59
+ ms: number;
60
+ body: string;
61
+ } | null>(null);
62
+ const [error, setError] = createSignal("");
63
+ const [copied, setCopied] = createSignal(false);
64
+
65
+ const setParam = (name: string, value: string) =>
66
+ setParamValues((p) => ({ ...p, [name]: value }));
67
+ const setArg = (name: string, value: string) =>
68
+ setArgValues((p) => ({ ...p, [name]: value }));
69
+
70
+ const resolvedUrl = () => {
71
+ let path = ep().path;
72
+ const queryParams = new URLSearchParams();
73
+ for (const p of ep().parameters) {
74
+ const v = paramValues()[p.name] ?? "";
75
+ if (p.in === "path")
76
+ path = path.replace(`{${p.name}}`, v || `{${p.name}}`);
77
+ else if (p.in === "query" && v) queryParams.set(p.name, v);
78
+ }
79
+ const base = ep().server ?? "";
80
+ const qs = queryParams.toString();
81
+ return `${base}${path}${qs ? `?${qs}` : ""}`;
82
+ };
83
+
84
+ const send = async () => {
85
+ setSending(true);
86
+ setError("");
87
+ setResult(null);
88
+ const t0 = performance.now();
89
+ try {
90
+ let requestBody: string | undefined;
91
+ let headers: Record<string, string> | undefined;
92
+
93
+ if (style() === "graphql") {
94
+ const vars = variables().trim()
95
+ ? (JSON.parse(variables()) as Record<string, unknown>)
96
+ : {};
97
+ requestBody = JSON.stringify({ query: query(), variables: vars });
98
+ headers = { "Content-Type": "application/json" };
99
+ } else if (body().trim()) {
100
+ requestBody = body();
101
+ headers = { "Content-Type": "application/json" };
102
+ }
103
+
104
+ const res = await fetch(resolvedUrl(), {
105
+ method: ep().method,
106
+ headers,
107
+ body: ep().method === "GET" || !requestBody ? undefined : requestBody,
108
+ });
109
+ const text = await res.text();
110
+ setResult({
111
+ status: res.status,
112
+ ms: Math.round(performance.now() - t0),
113
+ body: text,
114
+ });
115
+ } catch (e) {
116
+ setError(e instanceof Error ? e.message : String(e));
117
+ } finally {
118
+ setSending(false);
119
+ }
120
+ };
121
+
122
+ const prettyResult = () => {
123
+ const r = result();
124
+ if (!r) return "";
125
+ try {
126
+ return JSON.stringify(JSON.parse(r.body), null, 2);
127
+ } catch {
128
+ return r.body;
129
+ }
130
+ };
131
+
132
+ const copyCommand = () => {
133
+ const text = buildCommand(ep(), argValues());
134
+ navigator.clipboard.writeText(text).catch(() => {});
135
+ setCopied(true);
136
+ setTimeout(() => setCopied(false), 1500);
137
+ };
138
+
139
+ return (
140
+ <div class="rounded-lg border border-border bg-surface overflow-hidden">
141
+ <Show when={style() !== "cli"}>
142
+ <div class="flex items-center gap-2 px-3 py-2 border-b border-border">
143
+ <span
144
+ class={`text-xs font-bold ${METHOD_COLOR[ep().method] ?? "text-foreground"}`}
145
+ >
146
+ {ep().method}
147
+ </span>
148
+ <code class="text-xs text-muted font-mono truncate flex-1">
149
+ {resolvedUrl() || ep().path}
150
+ </code>
151
+ </div>
152
+ </Show>
153
+
154
+ <div class="p-3 flex flex-col gap-3">
155
+ <Show when={style() === "graphql"}>
156
+ <div>
157
+ <p class="text-[11px] font-semibold uppercase tracking-wide text-muted m-0 mb-1.5">
158
+ Query
159
+ </p>
160
+ <textarea
161
+ value={query()}
162
+ onInput={(e) => setQuery(e.currentTarget.value)}
163
+ rows={8}
164
+ spellcheck={false}
165
+ class="w-full px-2 py-1.5 rounded border border-border bg-background text-xs font-mono text-foreground outline-none focus:border-focus resize-y"
166
+ />
167
+ </div>
168
+ <div>
169
+ <p class="text-[11px] font-semibold uppercase tracking-wide text-muted m-0 mb-1.5">
170
+ Variables
171
+ </p>
172
+ <textarea
173
+ value={variables()}
174
+ onInput={(e) => setVariables(e.currentTarget.value)}
175
+ rows={4}
176
+ spellcheck={false}
177
+ class="w-full px-2 py-1.5 rounded border border-border bg-background text-xs font-mono text-foreground outline-none focus:border-focus resize-y"
178
+ />
179
+ </div>
180
+ </Show>
181
+
182
+ <Show when={style() === "cli"}>
183
+ <div>
184
+ <p class="text-[11px] font-semibold uppercase tracking-wide text-muted m-0 mb-1.5">
185
+ Command
186
+ </p>
187
+ <pre class="m-0 p-2 rounded border border-border bg-background text-xs font-mono text-foreground overflow-auto whitespace-pre-wrap">
188
+ {buildCommand(ep(), argValues())}
189
+ </pre>
190
+ </div>
191
+ <Show when={ep().cli?.args?.length}>
192
+ <div>
193
+ <p class="text-[11px] font-semibold uppercase tracking-wide text-muted m-0 mb-1.5">
194
+ Arguments
195
+ </p>
196
+ <For each={ep().cli?.args ?? []}>
197
+ {(a) => (
198
+ <label class="flex items-center gap-2 mb-1.5">
199
+ <span class="text-xs font-mono text-foreground w-28 truncate shrink-0">
200
+ {a.name}
201
+ <Show when={a.required}>
202
+ <span class="text-destructive">*</span>
203
+ </Show>
204
+ </span>
205
+ <input
206
+ type="text"
207
+ value={argValues()[a.name] ?? ""}
208
+ onInput={(e) => setArg(a.name, e.currentTarget.value)}
209
+ placeholder={a.example ?? ""}
210
+ class="flex-1 min-w-0 h-7 px-2 rounded border border-border bg-background text-xs font-mono text-foreground outline-none focus:border-focus"
211
+ />
212
+ </label>
213
+ )}
214
+ </For>
215
+ </div>
216
+ </Show>
217
+ <button
218
+ type="button"
219
+ onClick={copyCommand}
220
+ class="inline-flex items-center justify-center gap-1.5 h-9 rounded-md bg-primary text-primary-foreground text-sm font-medium hover:bg-primary-hover transition-colors cursor-pointer border-none"
221
+ >
222
+ <span
223
+ class={copied() ? "i-mdi:check" : "i-mdi:content-copy"}
224
+ aria-hidden="true"
225
+ />
226
+ {copied() ? "Copied!" : "Copy command"}
227
+ </button>
228
+ </Show>
229
+
230
+ <Show
231
+ when={
232
+ ep().parameters.length > 0 &&
233
+ style() !== "graphql" &&
234
+ style() !== "cli"
235
+ }
236
+ >
237
+ <div>
238
+ <p class="text-[11px] font-semibold uppercase tracking-wide text-muted m-0 mb-1.5">
239
+ Parameters
240
+ </p>
241
+ <For each={ep().parameters}>
242
+ {(p) => (
243
+ <label class="flex items-center gap-2 mb-1.5">
244
+ <span class="text-xs font-mono text-foreground w-28 truncate shrink-0">
245
+ {p.name}
246
+ <Show when={p.required}>
247
+ <span class="text-destructive">*</span>
248
+ </Show>
249
+ </span>
250
+ <input
251
+ type="text"
252
+ value={paramValues()[p.name] ?? ""}
253
+ onInput={(e) => setParam(p.name, e.currentTarget.value)}
254
+ placeholder={p.in}
255
+ class="flex-1 min-w-0 h-7 px-2 rounded border border-border bg-background text-xs font-mono text-foreground outline-none focus:border-focus"
256
+ />
257
+ </label>
258
+ )}
259
+ </For>
260
+ </div>
261
+ </Show>
262
+
263
+ <Show
264
+ when={ep().requestBody && style() !== "graphql" && style() !== "cli"}
265
+ >
266
+ <div>
267
+ <p class="text-[11px] font-semibold uppercase tracking-wide text-muted m-0 mb-1.5">
268
+ Body
269
+ </p>
270
+ <textarea
271
+ value={body()}
272
+ onInput={(e) => setBody(e.currentTarget.value)}
273
+ rows={6}
274
+ spellcheck={false}
275
+ class="w-full px-2 py-1.5 rounded border border-border bg-background text-xs font-mono text-foreground outline-none focus:border-focus resize-y"
276
+ />
277
+ </div>
278
+ </Show>
279
+
280
+ <Show when={style() !== "cli"}>
281
+ <button
282
+ type="button"
283
+ onClick={send}
284
+ disabled={sending()}
285
+ class="inline-flex items-center justify-center gap-1.5 h-9 rounded-md bg-primary text-primary-foreground text-sm font-medium hover:bg-primary-hover transition-colors cursor-pointer border-none disabled:opacity-50"
286
+ >
287
+ <span
288
+ class={sending() ? "i-mdi:loading animate-spin" : "i-mdi:send"}
289
+ aria-hidden="true"
290
+ />
291
+ {sending()
292
+ ? "Sending…"
293
+ : style() === "graphql"
294
+ ? "Run query"
295
+ : "Send request"}
296
+ </button>
297
+ </Show>
298
+
299
+ <Show when={error()}>
300
+ <p class="text-xs text-destructive m-0">{error()}</p>
301
+ </Show>
302
+
303
+ <Show when={result()}>
304
+ {(r) => (
305
+ <div>
306
+ <div class="flex items-center gap-2 mb-1.5">
307
+ <span
308
+ class={`text-xs font-bold ${r().status < 400 ? "text-success" : "text-destructive"}`}
309
+ >
310
+ {r().status}
311
+ </span>
312
+ <span class="text-[11px] text-muted">{r().ms}ms</span>
313
+ </div>
314
+ <pre class="m-0 p-2 rounded border border-border bg-background text-xs font-mono text-foreground overflow-auto max-h-64 whitespace-pre-wrap">
315
+ {prettyResult()}
316
+ </pre>
317
+ </div>
318
+ )}
319
+ </Show>
320
+
321
+ <Show when={style() !== "cli" && style() !== "graphql"}>
322
+ <details class="text-xs text-muted">
323
+ <summary class="cursor-pointer select-none">cURL</summary>
324
+ <pre class="m-0 mt-1.5 p-2 rounded border border-border bg-background font-mono text-foreground overflow-auto whitespace-pre-wrap">
325
+ {buildCurl(ep(), resolvedUrl(), body())}
326
+ </pre>
327
+ </details>
328
+ </Show>
329
+ </div>
330
+ </div>
331
+ );
332
+ }
@@ -0,0 +1,127 @@
1
+ import { createSignal, Show } from "solid-js";
2
+ import { useDocs } from "../context";
3
+ import { DocMarkdown } from "./DocMarkdown";
4
+
5
+ async function suggest(prompt: string): Promise<string> {
6
+ const res = await fetch("/api/ai/suggest", {
7
+ method: "POST",
8
+ headers: { "Content-Type": "application/json" },
9
+ body: JSON.stringify({ prompt }),
10
+ });
11
+ const data = (await res.json()) as { suggestion?: string; error?: string };
12
+ if (!res.ok) throw new Error(data.error ?? "AI request failed");
13
+ return data.suggestion ?? "";
14
+ }
15
+
16
+ export function AskAiDialog(props: {
17
+ open: boolean;
18
+ onClose: () => void;
19
+ collection: string;
20
+ docId: string;
21
+ docTitle: string;
22
+ }) {
23
+ const [question, setQuestion] = createSignal("");
24
+ const [answer, setAnswer] = createSignal("");
25
+ const [loading, setLoading] = createSignal(false);
26
+ const [error, setError] = createSignal("");
27
+ const dataSource = useDocs().dataSource;
28
+
29
+ const ask = async () => {
30
+ const q = question().trim();
31
+ if (!q || loading()) return;
32
+ setLoading(true);
33
+ setError("");
34
+ setAnswer("");
35
+ try {
36
+ let answer = "";
37
+ if (dataSource.ask) {
38
+ const res = await dataSource.ask({
39
+ collection: props.collection,
40
+ id: props.docId,
41
+ question: q,
42
+ });
43
+ answer = res.answer;
44
+ } else {
45
+ answer = await suggest(`Document: ${props.docTitle}\nQuestion: ${q}`);
46
+ }
47
+ setAnswer(answer);
48
+ } catch (e) {
49
+ setError(e instanceof Error ? e.message : "Failed to get answer");
50
+ } finally {
51
+ setLoading(false);
52
+ }
53
+ };
54
+
55
+ return (
56
+ <Show when={props.open}>
57
+ <div
58
+ class="fixed inset-0 z-50 bg-overlay flex items-start justify-center pt-20 px-4"
59
+ onClick={(e) => {
60
+ if (e.target === e.currentTarget) props.onClose();
61
+ }}
62
+ role="presentation"
63
+ >
64
+ <div class="w-full max-w-2xl rounded-lg border border-border bg-surface shadow-2xl overflow-hidden">
65
+ <div class="flex items-center gap-2 px-4 py-3 border-b border-border">
66
+ <span
67
+ class="i-mdi:robot-happy-outline text-primary"
68
+ aria-hidden="true"
69
+ />
70
+ <span class="text-sm font-semibold text-foreground">
71
+ Ask AI about this page
72
+ </span>
73
+ <span class="text-xs text-muted truncate">— {props.docTitle}</span>
74
+ <button
75
+ type="button"
76
+ onClick={props.onClose}
77
+ aria-label="Close"
78
+ class="ml-auto w-8 h-8 inline-flex items-center justify-center rounded-md text-muted hover:text-foreground hover:bg-background transition-colors cursor-pointer border-none bg-transparent"
79
+ >
80
+ <span class="i-mdi:close" aria-hidden="true" />
81
+ </button>
82
+ </div>
83
+ <div class="p-4">
84
+ <div class="flex gap-2">
85
+ <textarea
86
+ value={question()}
87
+ onInput={(e) => setQuestion(e.currentTarget.value)}
88
+ onKeyDown={(e) => {
89
+ if (e.key === "Enter" && !e.shiftKey) {
90
+ e.preventDefault();
91
+ ask();
92
+ }
93
+ }}
94
+ placeholder="Ask a question about this document..."
95
+ aria-label="Question"
96
+ rows={2}
97
+ class="flex-1 px-3 py-2 rounded-md border border-border bg-background text-sm text-foreground placeholder:text-muted resize-none outline-none focus:border-focus"
98
+ />
99
+ <button
100
+ type="button"
101
+ onClick={ask}
102
+ disabled={loading() || !question().trim()}
103
+ class="self-end inline-flex items-center gap-1.5 px-4 h-9 rounded-md bg-primary text-primary-foreground text-sm font-medium hover:bg-primary-hover transition-colors cursor-pointer border-none disabled:opacity-50 disabled:cursor-not-allowed"
104
+ >
105
+ <span
106
+ class={
107
+ loading() ? "i-mdi:loading animate-spin" : "i-mdi:send"
108
+ }
109
+ aria-hidden="true"
110
+ />
111
+ {loading() ? "Asking…" : "Ask"}
112
+ </button>
113
+ </div>
114
+ <Show when={error()}>
115
+ <p class="mt-3 text-sm text-destructive">{error()}</p>
116
+ </Show>
117
+ <Show when={answer()}>
118
+ <div class="mt-4 pt-3 border-t border-border">
119
+ <DocMarkdown source={answer()} />
120
+ </div>
121
+ </Show>
122
+ </div>
123
+ </div>
124
+ </div>
125
+ </Show>
126
+ );
127
+ }
@@ -0,0 +1,38 @@
1
+ import { createEffect, createSignal, onCleanup, Show } from "solid-js";
2
+
3
+ export function BackToTop() {
4
+ const [visible, setVisible] = createSignal(false);
5
+
6
+ createEffect(() => {
7
+ const onScroll = () => {
8
+ setVisible(window.scrollY > 400);
9
+ };
10
+
11
+ onScroll();
12
+ window.addEventListener("scroll", onScroll, { passive: true });
13
+ onCleanup(() => window.removeEventListener("scroll", onScroll));
14
+ });
15
+
16
+ const scrollToTop = () =>
17
+ window.scrollTo({
18
+ top: 0,
19
+ behavior: prefersReducedMotion() ? "auto" : "smooth",
20
+ });
21
+
22
+ return (
23
+ <Show when={visible()}>
24
+ <button
25
+ type="button"
26
+ onClick={scrollToTop}
27
+ class="fixed bottom-6 right-6 z-50 flex items-center justify-center w-10 h-10 rounded-full border border-border bg-surface text-foreground shadow-md hover:bg-primary hover:text-primary-foreground transition-colors"
28
+ aria-label="Back to top"
29
+ >
30
+ <span class="i-mdi:chevron-up" aria-hidden="true" />
31
+ </button>
32
+ </Show>
33
+ );
34
+ }
35
+
36
+ function prefersReducedMotion() {
37
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
38
+ }
@@ -0,0 +1,75 @@
1
+ import { createSignal, onMount, Show } from "solid-js";
2
+ import { useDocs } from "../context";
3
+
4
+ const BANNER_HEIGHT_VAR = "--docs-banner-height";
5
+
6
+ function setBannerHeight(height: number) {
7
+ document.documentElement.style.setProperty(BANNER_HEIGHT_VAR, `${height}px`);
8
+ }
9
+
10
+ export function Banner() {
11
+ const config = useDocs();
12
+ const banner = () => config.announcement;
13
+ const storageKey = "docs-banner-dismissed";
14
+ const [dismissed, setDismissed] = createSignal(false);
15
+
16
+ const isVisible = () => {
17
+ if (!banner()?.text) return false;
18
+ if (dismissed()) return false;
19
+ if (
20
+ banner()?.closable !== false &&
21
+ localStorage.getItem(storageKey) === "1"
22
+ ) {
23
+ return false;
24
+ }
25
+ return true;
26
+ };
27
+
28
+ const dismiss = () => {
29
+ setDismissed(true);
30
+ setBannerHeight(0);
31
+ if (banner()?.closable !== false) {
32
+ localStorage.setItem(storageKey, "1");
33
+ }
34
+ };
35
+
36
+ onMount(() => {
37
+ if (!isVisible()) setBannerHeight(0);
38
+ });
39
+
40
+ return (
41
+ <Show when={isVisible()}>
42
+ <div
43
+ class="sticky top-0 z-50 w-full bg-primary text-primary-foreground text-sm"
44
+ ref={(el) => {
45
+ queueMicrotask(() => {
46
+ if (isVisible()) setBannerHeight(el.getBoundingClientRect().height);
47
+ });
48
+ }}
49
+ >
50
+ <div class="max-w-6xl mx-auto px-4 py-2 flex items-center justify-center gap-2">
51
+ <Show when={banner()?.to} fallback={<span>{banner()?.text}</span>}>
52
+ {(to) => (
53
+ <a
54
+ href={to()}
55
+ class="underline hover:no-underline text-primary-foreground"
56
+ >
57
+ {banner()?.text}
58
+ </a>
59
+ )}
60
+ </Show>
61
+ <Show when={banner()?.closable !== false}>
62
+ <button
63
+ type="button"
64
+ onClick={dismiss}
65
+ class="ml-2 inline-flex items-center justify-center w-6 h-6 rounded-full hover:bg-primary-foreground/20"
66
+ aria-label="Dismiss announcement"
67
+ >
68
+ <span class="i-mdi:close" aria-hidden="true" />
69
+ </button>
70
+ </Show>
71
+ </div>
72
+ </div>
73
+ </Show>
74
+ );
75
+ }
@@ -0,0 +1,42 @@
1
+ import { Link } from "@tanstack/solid-router";
2
+ import { useDocs } from "../context";
3
+ import { useCollections } from "../data";
4
+
5
+ export function Breadcrumbs(props: {
6
+ collection: string;
7
+ category: string;
8
+ label: string;
9
+ }) {
10
+ const collections = useCollections();
11
+ const config = useDocs();
12
+ const collectionMeta = () =>
13
+ collections()?.find((c) => c.id === props.collection);
14
+
15
+ return (
16
+ <nav
17
+ aria-label="Breadcrumbs"
18
+ class="flex items-center gap-1.5 text-xs text-muted pb-3"
19
+ >
20
+ <Link to="/" class="hover:text-foreground transition-colors">
21
+ {config.site.title}
22
+ </Link>
23
+ <span class="i-mdi:chevron-right" aria-hidden="true" />
24
+ <Link
25
+ to={`/${props.collection}`}
26
+ class="hover:text-foreground transition-colors inline-flex items-center gap-1"
27
+ >
28
+ <span
29
+ class={collectionMeta()?.icon ?? "i-mdi:folder-outline"}
30
+ aria-hidden="true"
31
+ />
32
+ <span>{collectionMeta()?.label ?? props.collection}</span>
33
+ </Link>
34
+ <span class="i-mdi:chevron-right" aria-hidden="true" />
35
+ <span>{props.category}</span>
36
+ <span class="i-mdi:chevron-right" aria-hidden="true" />
37
+ <span class="text-foreground font-medium truncate max-w-[16rem]">
38
+ {props.label}
39
+ </span>
40
+ </nav>
41
+ );
42
+ }
@@ -0,0 +1,85 @@
1
+ import { Link } from "@tanstack/solid-router";
2
+ import { createSignal, For, onCleanup, onMount, Show } from "solid-js";
3
+ import { useCollections } from "../data";
4
+
5
+ export function CollectionDropdown(props: { current: string | undefined }) {
6
+ const [open, setOpen] = createSignal(false);
7
+ const collections = useCollections();
8
+ let rootEl: HTMLDivElement | undefined;
9
+
10
+ const currentMeta = () => collections()?.find((c) => c.id === props.current);
11
+
12
+ const onDocClick = (e: MouseEvent) => {
13
+ if (rootEl && !rootEl.contains(e.target as Node)) setOpen(false);
14
+ };
15
+ const onKey = (e: KeyboardEvent) => {
16
+ if (e.key === "Escape") setOpen(false);
17
+ };
18
+
19
+ onMount(() => {
20
+ document.addEventListener("click", onDocClick);
21
+ document.addEventListener("keydown", onKey);
22
+ });
23
+ onCleanup(() => {
24
+ document.removeEventListener("click", onDocClick);
25
+ document.removeEventListener("keydown", onKey);
26
+ });
27
+
28
+ return (
29
+ <div ref={rootEl} class="relative">
30
+ <button
31
+ type="button"
32
+ onClick={() => setOpen(!open())}
33
+ aria-expanded={open()}
34
+ aria-haspopup="listbox"
35
+ class="inline-flex items-center gap-2 px-3 h-9 rounded-md border border-border bg-surface text-sm text-foreground hover:border-focus transition-colors cursor-pointer"
36
+ >
37
+ <Show when={currentMeta()}>
38
+ <span class={currentMeta()!.icon} aria-hidden="true" />
39
+ </Show>
40
+ <span class="font-medium">
41
+ {currentMeta()?.label ?? "Select collection"}
42
+ </span>
43
+ <span
44
+ class={`i-mdi:chevron-down text-muted transition-transform ${open() ? "rotate-180" : ""}`}
45
+ aria-hidden="true"
46
+ />
47
+ </button>
48
+ <Show when={open()}>
49
+ <ul
50
+ role="listbox"
51
+ aria-label="Documentation collections"
52
+ class="absolute left-0 top-full mt-2 min-w-56 rounded-lg border border-border bg-surface shadow-lg py-1 z-50 list-none m-0"
53
+ >
54
+ <For each={collections()}>
55
+ {(c) => (
56
+ <li role="option" aria-selected={c.id === props.current}>
57
+ <Link
58
+ to="/$collection"
59
+ params={{ collection: c.id }}
60
+ onClick={() => setOpen(false)}
61
+ class="flex items-start gap-3 px-3 py-2 text-sm no-underline transition-colors hover:bg-background"
62
+ >
63
+ <span
64
+ class={`${c.icon} mt-0.5 text-primary`}
65
+ aria-hidden="true"
66
+ />
67
+ <span class="min-w-0">
68
+ <span
69
+ class={`block font-medium ${c.id === props.current ? "text-primary" : "text-foreground"}`}
70
+ >
71
+ {c.label}
72
+ </span>
73
+ <span class="block text-xs text-muted leading-snug">
74
+ {c.description}
75
+ </span>
76
+ </span>
77
+ </Link>
78
+ </li>
79
+ )}
80
+ </For>
81
+ </ul>
82
+ </Show>
83
+ </div>
84
+ );
85
+ }