blume 0.0.0 → 0.1.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 (330) hide show
  1. package/bin/blume.mjs +18 -0
  2. package/dist/cli/index.js +12696 -0
  3. package/dist/cli/index.js.map +145 -0
  4. package/dist/types/core/bridge.d.ts +24 -0
  5. package/dist/types/core/config.d.ts +35 -0
  6. package/dist/types/core/data.d.ts +129 -0
  7. package/dist/types/core/define-components.d.ts +27 -0
  8. package/dist/types/core/define-meta.d.ts +16 -0
  9. package/dist/types/core/deployment-env.d.ts +9 -0
  10. package/dist/types/core/diagnostics.d.ts +17 -0
  11. package/dist/types/core/i18n-ui.d.ts +500 -0
  12. package/dist/types/core/load-module.d.ts +7 -0
  13. package/dist/types/core/package-root.d.ts +17 -0
  14. package/dist/types/core/project.d.ts +9 -0
  15. package/dist/types/core/schema.d.ts +3451 -0
  16. package/dist/types/core/sources/types.d.ts +107 -0
  17. package/dist/types/core/types.d.ts +245 -0
  18. package/dist/types/core/ui-packs/ar.d.ts +3 -0
  19. package/dist/types/core/ui-packs/bg.d.ts +3 -0
  20. package/dist/types/core/ui-packs/bn.d.ts +3 -0
  21. package/dist/types/core/ui-packs/ca.d.ts +3 -0
  22. package/dist/types/core/ui-packs/cs.d.ts +3 -0
  23. package/dist/types/core/ui-packs/da.d.ts +3 -0
  24. package/dist/types/core/ui-packs/de.d.ts +3 -0
  25. package/dist/types/core/ui-packs/el.d.ts +3 -0
  26. package/dist/types/core/ui-packs/es.d.ts +3 -0
  27. package/dist/types/core/ui-packs/fa.d.ts +3 -0
  28. package/dist/types/core/ui-packs/fi.d.ts +3 -0
  29. package/dist/types/core/ui-packs/fr.d.ts +3 -0
  30. package/dist/types/core/ui-packs/he.d.ts +3 -0
  31. package/dist/types/core/ui-packs/hi.d.ts +3 -0
  32. package/dist/types/core/ui-packs/hr.d.ts +3 -0
  33. package/dist/types/core/ui-packs/hu.d.ts +3 -0
  34. package/dist/types/core/ui-packs/id.d.ts +3 -0
  35. package/dist/types/core/ui-packs/index.d.ts +13 -0
  36. package/dist/types/core/ui-packs/it.d.ts +3 -0
  37. package/dist/types/core/ui-packs/ja.d.ts +3 -0
  38. package/dist/types/core/ui-packs/ko.d.ts +3 -0
  39. package/dist/types/core/ui-packs/nl.d.ts +3 -0
  40. package/dist/types/core/ui-packs/no.d.ts +3 -0
  41. package/dist/types/core/ui-packs/pl.d.ts +3 -0
  42. package/dist/types/core/ui-packs/pt-br.d.ts +3 -0
  43. package/dist/types/core/ui-packs/pt.d.ts +3 -0
  44. package/dist/types/core/ui-packs/ro.d.ts +3 -0
  45. package/dist/types/core/ui-packs/ru.d.ts +3 -0
  46. package/dist/types/core/ui-packs/sk.d.ts +3 -0
  47. package/dist/types/core/ui-packs/sr.d.ts +3 -0
  48. package/dist/types/core/ui-packs/sv.d.ts +3 -0
  49. package/dist/types/core/ui-packs/th.d.ts +3 -0
  50. package/dist/types/core/ui-packs/tr.d.ts +3 -0
  51. package/dist/types/core/ui-packs/uk.d.ts +3 -0
  52. package/dist/types/core/ui-packs/vi.d.ts +3 -0
  53. package/dist/types/core/ui-packs/zh-tw.d.ts +3 -0
  54. package/dist/types/core/ui-packs/zh.d.ts +3 -0
  55. package/dist/types/core/version.d.ts +8 -0
  56. package/dist/types/index.d.ts +10 -0
  57. package/dist/types/migrate/mintlify/config.d.ts +2 -0
  58. package/dist/types/migrate/mintlify/i18n.d.ts +7 -0
  59. package/dist/types/theme/fonts.d.ts +163 -0
  60. package/docs/01-quickstart.mdx +99 -0
  61. package/docs/02-deployment.mdx +129 -0
  62. package/docs/advanced/api-reference.mdx +114 -0
  63. package/docs/advanced/blog.mdx +121 -0
  64. package/docs/advanced/changelog.mdx +113 -0
  65. package/docs/advanced/custom-pages.mdx +268 -0
  66. package/docs/advanced/meta.ts +7 -0
  67. package/docs/changelog/v0-1-0.mdx +12 -0
  68. package/docs/changelog/v0-2-0.mdx +16 -0
  69. package/docs/configuration/ai.mdx +228 -0
  70. package/docs/configuration/analytics.mdx +98 -0
  71. package/docs/configuration/customization.mdx +91 -0
  72. package/docs/configuration/export.mdx +70 -0
  73. package/docs/configuration/index.mdx +290 -0
  74. package/docs/configuration/meta.ts +15 -0
  75. package/docs/configuration/search.mdx +172 -0
  76. package/docs/configuration/seo.mdx +196 -0
  77. package/docs/configuration/theming.mdx +178 -0
  78. package/docs/content/components.mdx +651 -0
  79. package/docs/content/i18n.mdx +205 -0
  80. package/docs/content/index.mdx +161 -0
  81. package/docs/content/islands.mdx +94 -0
  82. package/docs/content/meta.mdx +119 -0
  83. package/docs/content/meta.ts +15 -0
  84. package/docs/content/navigation.mdx +168 -0
  85. package/docs/content/sources.mdx +216 -0
  86. package/docs/content/syntax.mdx +445 -0
  87. package/docs/index.mdx +112 -0
  88. package/docs/reference/cli.mdx +43 -0
  89. package/docs/reference/frontmatter.mdx +74 -0
  90. package/docs/reference/meta.ts +7 -0
  91. package/package.json +150 -6
  92. package/src/ai/ask.ts +93 -0
  93. package/src/ai/llms.ts +64 -0
  94. package/src/ai/markdown.ts +31 -0
  95. package/src/ai/mcp/data.ts +74 -0
  96. package/src/ai/mcp/discovery.ts +49 -0
  97. package/src/ai/mcp/server.ts +225 -0
  98. package/src/ai/mcp/tools.ts +47 -0
  99. package/src/assets/icon.png +0 -0
  100. package/src/astro/examples.ts +104 -0
  101. package/src/astro/generate.ts +1006 -0
  102. package/src/astro/index.ts +4 -0
  103. package/src/astro/integration.ts +74 -0
  104. package/src/astro/islands.ts +131 -0
  105. package/src/astro/markdown-negotiation.ts +68 -0
  106. package/src/astro/pages.ts +87 -0
  107. package/src/astro/templates.ts +1326 -0
  108. package/src/cli/commands/add.ts +81 -0
  109. package/src/cli/commands/build.ts +103 -0
  110. package/src/cli/commands/dev.ts +108 -0
  111. package/src/cli/commands/doctor.ts +74 -0
  112. package/src/cli/commands/eject.ts +57 -0
  113. package/src/cli/commands/init.ts +98 -0
  114. package/src/cli/commands/migrate.ts +39 -0
  115. package/src/cli/commands/preview.ts +39 -0
  116. package/src/cli/commands/sync.ts +52 -0
  117. package/src/cli/commands/validate.ts +61 -0
  118. package/src/cli/index.ts +35 -0
  119. package/src/cli/log.ts +37 -0
  120. package/src/cli/prepare.ts +80 -0
  121. package/src/components/Icon.astro +99 -0
  122. package/src/components/content/Accordion.astro +8 -0
  123. package/src/components/content/AccordionItem.astro +121 -0
  124. package/src/components/content/AutoTypeTable.astro +51 -0
  125. package/src/components/content/Badge.astro +124 -0
  126. package/src/components/content/Callout.astro +73 -0
  127. package/src/components/content/Card.astro +104 -0
  128. package/src/components/content/CardGroup.astro +14 -0
  129. package/src/components/content/CodeBlock.astro +28 -0
  130. package/src/components/content/CodeGroup.astro +13 -0
  131. package/src/components/content/Color.astro +15 -0
  132. package/src/components/content/ColorItem.astro +87 -0
  133. package/src/components/content/ColorRow.astro +10 -0
  134. package/src/components/content/Column.astro +6 -0
  135. package/src/components/content/Columns.astro +9 -0
  136. package/src/components/content/Component.astro +65 -0
  137. package/src/components/content/Diff.astro +44 -0
  138. package/src/components/content/Expandable.astro +11 -0
  139. package/src/components/content/FileTree.astro +8 -0
  140. package/src/components/content/Frame.astro +70 -0
  141. package/src/components/content/GithubInfo.astro +110 -0
  142. package/src/components/content/Math.astro +24 -0
  143. package/src/components/content/Panel.astro +20 -0
  144. package/src/components/content/Prompt.astro +129 -0
  145. package/src/components/content/Step.astro +34 -0
  146. package/src/components/content/Steps.astro +20 -0
  147. package/src/components/content/Tab.astro +46 -0
  148. package/src/components/content/Tabs.astro +273 -0
  149. package/src/components/content/Tile.astro +42 -0
  150. package/src/components/content/Tooltip.astro +68 -0
  151. package/src/components/content/Tree.astro +300 -0
  152. package/src/components/content/TreeFile.astro +15 -0
  153. package/src/components/content/TreeFolder.astro +62 -0
  154. package/src/components/content/TypeTable.astro +106 -0
  155. package/src/components/content/Update.astro +66 -0
  156. package/src/components/content/Visibility.astro +12 -0
  157. package/src/components/content/Warning.astro +9 -0
  158. package/src/components/content/auto-type-table.ts +141 -0
  159. package/src/components/content/diff.ts +95 -0
  160. package/src/components/content/github-info.ts +79 -0
  161. package/src/components/content/mermaid-element.ts +68 -0
  162. package/src/components/github-mark.ts +9 -0
  163. package/src/components/index.ts +14 -0
  164. package/src/components/islands/AskAI.astro +12 -0
  165. package/src/components/islands/ask-ai.tsx +156 -0
  166. package/src/components/layout/Analytics.astro +63 -0
  167. package/src/components/layout/Banner.astro +50 -0
  168. package/src/components/layout/Breadcrumbs.astro +31 -0
  169. package/src/components/layout/Favicon.astro +22 -0
  170. package/src/components/layout/Fonts.astro +14 -0
  171. package/src/components/layout/Header.astro +188 -0
  172. package/src/components/layout/LanguageSwitcher.astro +56 -0
  173. package/src/components/layout/NavTree.astro +462 -0
  174. package/src/components/layout/PageActions.astro +438 -0
  175. package/src/components/layout/PageFeedback.astro +58 -0
  176. package/src/components/layout/PageLayout.astro +173 -0
  177. package/src/components/layout/Pagination.astro +56 -0
  178. package/src/components/layout/ReferenceLayout.astro +107 -0
  179. package/src/components/layout/RootLayout.astro +537 -0
  180. package/src/components/layout/Search.astro +608 -0
  181. package/src/components/layout/TableOfContents.astro +68 -0
  182. package/src/components/layout/analytics-client.ts +38 -0
  183. package/src/components/layout/head-scripts.ts +19 -0
  184. package/src/components/layout/nav-utils.ts +87 -0
  185. package/src/components/layout/overrides.ts +32 -0
  186. package/src/components/layout/search/algolia.ts +43 -0
  187. package/src/components/layout/search/endpoint.ts +22 -0
  188. package/src/components/layout/search/flexsearch.ts +52 -0
  189. package/src/components/layout/search/orama-cloud.ts +41 -0
  190. package/src/components/layout/search/orama.ts +26 -0
  191. package/src/components/layout/search/pagefind.ts +43 -0
  192. package/src/components/layout/search/types.ts +163 -0
  193. package/src/components/layout/search/typesense.ts +60 -0
  194. package/src/components/layout/toc-element.ts +108 -0
  195. package/src/core/bridge.ts +92 -0
  196. package/src/core/config.ts +112 -0
  197. package/src/core/content.ts +50 -0
  198. package/src/core/data.ts +128 -0
  199. package/src/core/define-components.ts +34 -0
  200. package/src/core/define-meta.ts +20 -0
  201. package/src/core/deployment-env.ts +73 -0
  202. package/src/core/diagnostics.ts +104 -0
  203. package/src/core/frontmatter.ts +43 -0
  204. package/src/core/graph.ts +128 -0
  205. package/src/core/i18n-ui.ts +171 -0
  206. package/src/core/i18n.ts +169 -0
  207. package/src/core/last-modified.ts +88 -0
  208. package/src/core/links.ts +352 -0
  209. package/src/core/load-module.ts +15 -0
  210. package/src/core/manifest.ts +126 -0
  211. package/src/core/meta.ts +97 -0
  212. package/src/core/navigation.ts +392 -0
  213. package/src/core/package-root.ts +37 -0
  214. package/src/core/project-graph.ts +153 -0
  215. package/src/core/project.ts +56 -0
  216. package/src/core/schema.ts +1057 -0
  217. package/src/core/server-features.ts +23 -0
  218. package/src/core/sources/assets.ts +77 -0
  219. package/src/core/sources/cache.ts +122 -0
  220. package/src/core/sources/filesystem.ts +99 -0
  221. package/src/core/sources/mdx-remote.ts +215 -0
  222. package/src/core/sources/mintlify.ts +161 -0
  223. package/src/core/sources/normalize.ts +243 -0
  224. package/src/core/sources/notion.ts +440 -0
  225. package/src/core/sources/portable-text.ts +143 -0
  226. package/src/core/sources/read.ts +36 -0
  227. package/src/core/sources/resolve.ts +158 -0
  228. package/src/core/sources/sanity.ts +218 -0
  229. package/src/core/sources/types.ts +105 -0
  230. package/src/core/tsconfig-aliases.ts +201 -0
  231. package/src/core/types.ts +261 -0
  232. package/src/core/ui-packs/ar.ts +47 -0
  233. package/src/core/ui-packs/bg.ts +47 -0
  234. package/src/core/ui-packs/bn.ts +47 -0
  235. package/src/core/ui-packs/ca.ts +47 -0
  236. package/src/core/ui-packs/cs.ts +47 -0
  237. package/src/core/ui-packs/da.ts +47 -0
  238. package/src/core/ui-packs/de.ts +47 -0
  239. package/src/core/ui-packs/el.ts +47 -0
  240. package/src/core/ui-packs/es.ts +47 -0
  241. package/src/core/ui-packs/fa.ts +47 -0
  242. package/src/core/ui-packs/fi.ts +47 -0
  243. package/src/core/ui-packs/fr.ts +47 -0
  244. package/src/core/ui-packs/he.ts +47 -0
  245. package/src/core/ui-packs/hi.ts +47 -0
  246. package/src/core/ui-packs/hr.ts +47 -0
  247. package/src/core/ui-packs/hu.ts +47 -0
  248. package/src/core/ui-packs/id.ts +47 -0
  249. package/src/core/ui-packs/index.ts +87 -0
  250. package/src/core/ui-packs/it.ts +47 -0
  251. package/src/core/ui-packs/ja.ts +47 -0
  252. package/src/core/ui-packs/ko.ts +47 -0
  253. package/src/core/ui-packs/nl.ts +47 -0
  254. package/src/core/ui-packs/no.ts +47 -0
  255. package/src/core/ui-packs/pl.ts +47 -0
  256. package/src/core/ui-packs/pt-br.ts +47 -0
  257. package/src/core/ui-packs/pt.ts +47 -0
  258. package/src/core/ui-packs/ro.ts +47 -0
  259. package/src/core/ui-packs/ru.ts +47 -0
  260. package/src/core/ui-packs/sk.ts +47 -0
  261. package/src/core/ui-packs/sr.ts +47 -0
  262. package/src/core/ui-packs/sv.ts +47 -0
  263. package/src/core/ui-packs/th.ts +47 -0
  264. package/src/core/ui-packs/tr.ts +47 -0
  265. package/src/core/ui-packs/uk.ts +47 -0
  266. package/src/core/ui-packs/vi.ts +47 -0
  267. package/src/core/ui-packs/zh-tw.ts +47 -0
  268. package/src/core/ui-packs/zh.ts +47 -0
  269. package/src/core/version.ts +23 -0
  270. package/src/deploy/robots.ts +20 -0
  271. package/src/deploy/rss.ts +128 -0
  272. package/src/deploy/sitemap.ts +28 -0
  273. package/src/index.ts +39 -0
  274. package/src/markdown/code-title.ts +71 -0
  275. package/src/markdown/directives.ts +83 -0
  276. package/src/markdown/heading-anchors.ts +137 -0
  277. package/src/markdown/index.ts +228 -0
  278. package/src/markdown/inline-code.ts +108 -0
  279. package/src/markdown/language-icon.ts +172 -0
  280. package/src/markdown/math.ts +32 -0
  281. package/src/markdown/mdast.ts +48 -0
  282. package/src/markdown/mermaid.ts +37 -0
  283. package/src/markdown/package-commands.ts +159 -0
  284. package/src/markdown/package-install.ts +40 -0
  285. package/src/migrate/fumadocs/config.ts +155 -0
  286. package/src/migrate/fumadocs/content.ts +365 -0
  287. package/src/migrate/fumadocs/frontmatter.ts +18 -0
  288. package/src/migrate/fumadocs/groups.ts +230 -0
  289. package/src/migrate/fumadocs/index.ts +337 -0
  290. package/src/migrate/fumadocs/meta.ts +244 -0
  291. package/src/migrate/migrate.ts +53 -0
  292. package/src/migrate/mintlify/config.ts +1040 -0
  293. package/src/migrate/mintlify/content.ts +98 -0
  294. package/src/migrate/mintlify/frontmatter.ts +126 -0
  295. package/src/migrate/mintlify/i18n.ts +51 -0
  296. package/src/migrate/mintlify/icons.ts +128 -0
  297. package/src/migrate/mintlify/index.ts +266 -0
  298. package/src/migrate/mintlify/snippets.ts +306 -0
  299. package/src/migrate/mintlify/transform.ts +80 -0
  300. package/src/migrate/nextra/content.ts +46 -0
  301. package/src/migrate/nextra/frontmatter.ts +40 -0
  302. package/src/migrate/nextra/index.ts +374 -0
  303. package/src/migrate/nextra/meta.ts +266 -0
  304. package/src/migrate/shared.ts +720 -0
  305. package/src/migrate/starlight/config.ts +459 -0
  306. package/src/migrate/starlight/content.ts +78 -0
  307. package/src/migrate/starlight/frontmatter.ts +111 -0
  308. package/src/migrate/starlight/i18n.ts +54 -0
  309. package/src/migrate/starlight/index.ts +131 -0
  310. package/src/og/card.ts +92 -0
  311. package/src/og/index.ts +2 -0
  312. package/src/openapi/scalar.ts +246 -0
  313. package/src/registry/eject.ts +310 -0
  314. package/src/registry/registry.ts +100 -0
  315. package/src/registry/rewrite-imports.ts +39 -0
  316. package/src/runtime/index.ts +14 -0
  317. package/src/search/build.ts +23 -0
  318. package/src/search/documents.ts +164 -0
  319. package/src/search/orama-index.ts +66 -0
  320. package/src/search/providers.ts +91 -0
  321. package/src/search/sync/algolia.ts +30 -0
  322. package/src/search/sync/index.ts +50 -0
  323. package/src/search/sync/orama-cloud.ts +40 -0
  324. package/src/search/sync/typesense.ts +65 -0
  325. package/src/seo/jsonld.ts +113 -0
  326. package/src/theme/entry.ts +637 -0
  327. package/src/theme/fonts.ts +198 -0
  328. package/src/theme/icons.ts +184 -0
  329. package/src/theme/palette.ts +143 -0
  330. package/src/theme/twoslash.ts +81 -0
@@ -0,0 +1,462 @@
1
+ ---
2
+ import type { NavNode } from "../../core/types.ts";
3
+ import Icon from "../Icon.astro";
4
+ import Self from "./NavTree.astro";
5
+
6
+ interface PagePanel {
7
+ id: string;
8
+ parentId: string;
9
+ label: string;
10
+ route?: string;
11
+ icon?: string;
12
+ children: NavNode[];
13
+ active: boolean;
14
+ }
15
+
16
+ interface Props {
17
+ items: NavNode[];
18
+ currentRoute: string;
19
+ depth?: number;
20
+ /** Id prefix used to address `page`-mode panels (`${prefix}.${index}`). */
21
+ idPrefix?: string;
22
+ /** True only for the top-level call; renders the `<blume-nav>` panel stack. */
23
+ root?: boolean;
24
+ }
25
+
26
+ const {
27
+ items,
28
+ currentRoute,
29
+ depth = 0,
30
+ idPrefix = "n",
31
+ root = depth === 0,
32
+ } = Astro.props;
33
+
34
+ const badgeClass =
35
+ "shrink-0 rounded-full bg-muted px-1.5 py-0.5 font-medium text-[0.65rem] text-muted-foreground leading-none";
36
+ const deprecatedClass =
37
+ "shrink-0 rounded-full bg-amber-500/10 px-1.5 py-0.5 font-medium text-[0.65rem] text-amber-700 leading-none dark:text-amber-300";
38
+
39
+ const hasActivePage = (item: NavNode): boolean =>
40
+ item.kind === "page"
41
+ ? item.route === currentRoute
42
+ : item.route === currentRoute || item.children.some(hasActivePage);
43
+
44
+ // Collect every `page`-mode group in the tree as a panel that is hoisted to the
45
+ // top of the `<blume-nav>` stack. Panel ids match the `${prefix}.${index}`
46
+ // scheme the row renderer uses for `data-nav-to`, so a drill-in trigger and its
47
+ // panel always agree. `flat`/`group` ancestors keep their children in the same
48
+ // visual panel (parentId unchanged); a `page` group opens a new panel.
49
+ const collectPanels = (
50
+ nodes: NavNode[],
51
+ prefix: string,
52
+ parentId: string
53
+ ): PagePanel[] => {
54
+ const out: PagePanel[] = [];
55
+ nodes.forEach((node, index) => {
56
+ if (node.kind !== "group") {
57
+ return;
58
+ }
59
+ const id = `${prefix}.${index}`;
60
+ if ((node.display ?? "flat") === "page") {
61
+ out.push({
62
+ active: hasActivePage(node),
63
+ children: node.children,
64
+ icon: node.icon,
65
+ id,
66
+ label: node.label,
67
+ parentId,
68
+ route: node.route,
69
+ });
70
+ out.push(...collectPanels(node.children, id, id));
71
+ } else {
72
+ out.push(...collectPanels(node.children, id, parentId));
73
+ }
74
+ });
75
+ return out;
76
+ };
77
+
78
+ const panels = root ? collectPanels(items, idPrefix, "root") : [];
79
+ // Open the deepest panel that contains the current page, so a real page load
80
+ // lands on the right sub-panel (and degrades gracefully without JS).
81
+ const activePanels = panels.filter((panel) => panel.active);
82
+ const initialId =
83
+ activePanels.length > 0
84
+ ? activePanels.reduce((a, b) =>
85
+ b.id.split(".").length > a.id.split(".").length ? b : a
86
+ ).id
87
+ : "root";
88
+ ---
89
+
90
+ {
91
+ root && panels.length > 0 ? (
92
+ <blume-nav class="block" data-initial={initialId}>
93
+ <div data-nav-panel="root" hidden={initialId !== "root"}>
94
+ <Self
95
+ currentRoute={currentRoute}
96
+ depth={0}
97
+ idPrefix={idPrefix}
98
+ items={items}
99
+ root={false}
100
+ />
101
+ </div>
102
+ {panels.map((panel) => (
103
+ <div data-nav-panel={panel.id} hidden={panel.id !== initialId}>
104
+ <div class="mb-3 flex items-center gap-1.5">
105
+ <button
106
+ aria-label="Back"
107
+ class="-ml-1 flex shrink-0 items-center justify-center rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
108
+ data-nav-back={panel.parentId}
109
+ type="button"
110
+ >
111
+ <Icon name="arrow-left" size={16} />
112
+ </button>
113
+ {panel.route ? (
114
+ <a
115
+ aria-current={panel.route === currentRoute ? "page" : undefined}
116
+ class="min-w-0 flex-1 truncate font-semibold text-foreground text-sm hover:underline"
117
+ href={panel.route}
118
+ >
119
+ {panel.label}
120
+ </a>
121
+ ) : (
122
+ <span class="min-w-0 flex-1 truncate font-semibold text-foreground text-sm">
123
+ {panel.label}
124
+ </span>
125
+ )}
126
+ </div>
127
+ <Self
128
+ currentRoute={currentRoute}
129
+ depth={1}
130
+ idPrefix={panel.id}
131
+ items={panel.children}
132
+ root={false}
133
+ />
134
+ </div>
135
+ ))}
136
+ </blume-nav>
137
+ ) : (
138
+ <ul class="m-0 list-none p-0">
139
+ {items.map((item, index) => {
140
+ if (item.kind === "page") {
141
+ return (
142
+ <li>
143
+ <a
144
+ aria-current={item.route === currentRoute ? "page" : undefined}
145
+ class="block rounded-[0.65rem] px-2.5 py-1.5 text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground aria-[current=page]:bg-muted aria-[current=page]:font-medium aria-[current=page]:text-foreground"
146
+ href={item.route}
147
+ >
148
+ <span class="flex min-w-0 items-center gap-2">
149
+ {item.icon && (
150
+ <Icon
151
+ class="shrink-0 text-muted-foreground"
152
+ name={item.icon}
153
+ size={14}
154
+ />
155
+ )}
156
+ <span class="min-w-0 flex-1 truncate">{item.label}</span>
157
+ {item.badge && <span class={badgeClass}>{item.badge}</span>}
158
+ {item.deprecated && (
159
+ <span class={deprecatedClass}>deprecated</span>
160
+ )}
161
+ </span>
162
+ </a>
163
+ </li>
164
+ );
165
+ }
166
+
167
+ const display = item.display ?? "flat";
168
+ const id = `${idPrefix}.${index}`;
169
+ // Flat groups are section headers and want breathing room; collapsible
170
+ // `group`/`page` rows are single rows and should stack tightly.
171
+ const spacing =
172
+ index === 0 ? "mt-0" : display === "flat" ? "mt-6" : "mt-0.5";
173
+
174
+ // `page`: a single row that drills into this group's panel.
175
+ if (display === "page") {
176
+ return (
177
+ <li class={spacing}>
178
+ <button
179
+ class="flex w-full min-w-0 items-center gap-2 rounded-[0.65rem] px-2.5 py-1.5 text-left font-medium text-foreground text-sm transition-colors hover:bg-muted"
180
+ data-nav-to={id}
181
+ type="button"
182
+ >
183
+ {item.icon && (
184
+ <Icon
185
+ class="shrink-0 text-muted-foreground"
186
+ name={item.icon}
187
+ size={14}
188
+ />
189
+ )}
190
+ <span class="min-w-0 flex-1 truncate">{item.label}</span>
191
+ {item.badge && <span class={badgeClass}>{item.badge}</span>}
192
+ <Icon
193
+ class="shrink-0 text-muted-foreground"
194
+ name="chevron-right"
195
+ size={14}
196
+ />
197
+ </button>
198
+ </li>
199
+ );
200
+ }
201
+
202
+ // `group`: a collapsible `<details>` disclosure.
203
+ if (display === "group") {
204
+ const active = hasActivePage(item);
205
+ const open = depth === 0 || active || item.collapsed === false;
206
+ return (
207
+ <li class={spacing}>
208
+ <details class="group" open={open}>
209
+ <summary class="mb-1 flex cursor-pointer list-none items-center gap-1.5 rounded-[0.65rem] px-2.5 py-1.5 font-medium text-foreground text-sm transition-colors hover:bg-muted [&::-webkit-details-marker]:hidden">
210
+ {item.route ? (
211
+ <a
212
+ aria-current={
213
+ item.route === currentRoute ? "page" : undefined
214
+ }
215
+ class="-my-1 flex min-w-0 flex-1 items-center gap-1.5 rounded py-1 text-foreground transition-colors aria-[current=page]:font-semibold"
216
+ href={item.route}
217
+ >
218
+ {item.icon && (
219
+ <Icon
220
+ class="shrink-0 text-muted-foreground"
221
+ name={item.icon}
222
+ size={14}
223
+ />
224
+ )}
225
+ <span class="min-w-0 flex-1 truncate">{item.label}</span>
226
+ {item.badge && (
227
+ <span class={badgeClass}>{item.badge}</span>
228
+ )}
229
+ </a>
230
+ ) : (
231
+ <>
232
+ {item.icon && (
233
+ <Icon
234
+ class="shrink-0 text-muted-foreground"
235
+ name={item.icon}
236
+ size={14}
237
+ />
238
+ )}
239
+ <span class="min-w-0 flex-1 truncate">{item.label}</span>
240
+ {item.badge && (
241
+ <span class={badgeClass}>{item.badge}</span>
242
+ )}
243
+ </>
244
+ )}
245
+ <span class="shrink-0 text-muted-foreground transition-transform group-open:rotate-90">
246
+ <Icon name="chevron-right" size={13} />
247
+ </span>
248
+ </summary>
249
+ <div class="space-y-0.5 border-border border-l pl-3">
250
+ <Self
251
+ currentRoute={currentRoute}
252
+ depth={depth + 1}
253
+ idPrefix={id}
254
+ items={item.children}
255
+ root={false}
256
+ />
257
+ </div>
258
+ </details>
259
+ </li>
260
+ );
261
+ }
262
+
263
+ // `flat` (default): a non-collapsible header with its items beneath.
264
+ return (
265
+ <li class={spacing}>
266
+ <p class="mb-2 flex min-w-0 items-center gap-1.5 px-2.5 font-medium text-foreground text-sm">
267
+ {item.route ? (
268
+ <a
269
+ aria-current={item.route === currentRoute ? "page" : undefined}
270
+ class="-ml-1 flex min-w-0 flex-1 items-center gap-1.5 rounded px-1 py-0.5 text-foreground transition-colors hover:bg-muted aria-[current=page]:bg-muted"
271
+ href={item.route}
272
+ >
273
+ {item.icon && (
274
+ <Icon
275
+ class="shrink-0 text-muted-foreground"
276
+ name={item.icon}
277
+ size={14}
278
+ />
279
+ )}
280
+ <span class="min-w-0 truncate">{item.label}</span>
281
+ {item.badge && (
282
+ <span class={`ml-auto ${badgeClass}`}>{item.badge}</span>
283
+ )}
284
+ </a>
285
+ ) : (
286
+ <>
287
+ {item.icon && (
288
+ <Icon
289
+ class="shrink-0 text-muted-foreground"
290
+ name={item.icon}
291
+ size={14}
292
+ />
293
+ )}
294
+ <span class="min-w-0 truncate">{item.label}</span>
295
+ {item.badge && (
296
+ <span class={`ml-auto ${badgeClass}`}>{item.badge}</span>
297
+ )}
298
+ </>
299
+ )}
300
+ </p>
301
+ <div class="space-y-0.5">
302
+ <Self
303
+ currentRoute={currentRoute}
304
+ depth={depth + 1}
305
+ idPrefix={id}
306
+ items={item.children}
307
+ root={false}
308
+ />
309
+ </div>
310
+ </li>
311
+ );
312
+ })}
313
+ </ul>
314
+ )
315
+ }
316
+
317
+ <script>
318
+ const DURATION = 260;
319
+ const EASING = "cubic-bezier(0.33, 1, 0.68, 1)";
320
+
321
+ class BlumeNav extends HTMLElement {
322
+ connectedCallback() {
323
+ if (this.dataset.ready) {
324
+ return;
325
+ }
326
+ this.dataset.ready = "1";
327
+ this.addEventListener("click", (event) => {
328
+ const target = event.target;
329
+ if (!(target instanceof Element)) {
330
+ return;
331
+ }
332
+ const drill = target.closest("[data-nav-to]");
333
+ if (drill && this.contains(drill)) {
334
+ const id = drill.getAttribute("data-nav-to");
335
+ if (id && this.panel(id)) {
336
+ event.preventDefault();
337
+ this.show(id, true);
338
+ }
339
+ return;
340
+ }
341
+ const back = target.closest("[data-nav-back]");
342
+ if (back && this.contains(back)) {
343
+ event.preventDefault();
344
+ this.show(back.getAttribute("data-nav-back") || "root", true);
345
+ }
346
+ });
347
+ // Initial panel is route-driven, so it snaps into place without a slide.
348
+ this.show(this.dataset.initial || "root", false);
349
+ }
350
+
351
+ panel(id) {
352
+ return this.querySelector(`[data-nav-panel="${id}"]`);
353
+ }
354
+
355
+ depth(id) {
356
+ return id === "root" ? 0 : id.split(".").length;
357
+ }
358
+
359
+ reduced() {
360
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
361
+ }
362
+
363
+ show(id, animate) {
364
+ const next = this.panel(id) || this.panel("root");
365
+ if (!next) {
366
+ return;
367
+ }
368
+ const resolved = next.getAttribute("data-nav-panel");
369
+ const prev = this.active;
370
+ if (prev === next) {
371
+ return;
372
+ }
373
+ // Settle any in-flight slide before starting the next one.
374
+ if (this.finishSlide) {
375
+ this.finishSlide();
376
+ }
377
+ this.active = next;
378
+ this.dataset.active = resolved;
379
+
380
+ if (!(animate && prev) || this.reduced()) {
381
+ for (const panel of this.querySelectorAll("[data-nav-panel]")) {
382
+ panel.hidden = panel !== next;
383
+ }
384
+ return;
385
+ }
386
+
387
+ const forward =
388
+ this.depth(resolved) > this.depth(prev.getAttribute("data-nav-panel"));
389
+ this.slide(prev, next, forward);
390
+ }
391
+
392
+ slide(out, into, forward) {
393
+ const rtl = getComputedStyle(this).direction === "rtl" ? -1 : 1;
394
+ const sign = (forward ? -1 : 1) * rtl;
395
+
396
+ const fromHeight = out.offsetHeight;
397
+ this.style.position = "relative";
398
+ this.style.overflow = "hidden";
399
+ this.style.height = `${fromHeight}px`;
400
+ into.hidden = false;
401
+ for (const panel of [out, into]) {
402
+ panel.style.position = "absolute";
403
+ panel.style.top = "0";
404
+ panel.style.insetInlineStart = "0";
405
+ panel.style.width = "100%";
406
+ }
407
+ const toHeight = into.offsetHeight;
408
+ this.style.height = `${toHeight}px`;
409
+
410
+ const options = { duration: DURATION, easing: EASING };
411
+ const animations = [
412
+ out.animate(
413
+ [
414
+ { transform: "translateX(0)" },
415
+ { transform: `translateX(${sign * 100}%)` },
416
+ ],
417
+ options
418
+ ),
419
+ into.animate(
420
+ [
421
+ { transform: `translateX(${-sign * 100}%)` },
422
+ { transform: "translateX(0)" },
423
+ ],
424
+ options
425
+ ),
426
+ this.animate(
427
+ [{ height: `${fromHeight}px` }, { height: `${toHeight}px` }],
428
+ options
429
+ ),
430
+ ];
431
+
432
+ let done = false;
433
+ const finish = () => {
434
+ if (done) {
435
+ return;
436
+ }
437
+ done = true;
438
+ for (const animation of animations) {
439
+ animation.cancel();
440
+ }
441
+ out.hidden = true;
442
+ for (const panel of [out, into]) {
443
+ panel.style.position = "";
444
+ panel.style.top = "";
445
+ panel.style.insetInlineStart = "";
446
+ panel.style.width = "";
447
+ panel.style.transform = "";
448
+ }
449
+ this.style.position = "";
450
+ this.style.overflow = "";
451
+ this.style.height = "";
452
+ this.finishSlide = null;
453
+ };
454
+ this.finishSlide = finish;
455
+ animations[0].finished.then(finish, finish);
456
+ }
457
+ }
458
+
459
+ if (!customElements.get("blume-nav")) {
460
+ customElements.define("blume-nav", BlumeNav);
461
+ }
462
+ </script>