blume 0.0.0 → 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 (263) hide show
  1. package/bin/blume.mjs +18 -0
  2. package/dist/cli/index.js +11989 -0
  3. package/dist/cli/index.js.map +141 -0
  4. package/docs/01-quickstart.mdx +99 -0
  5. package/docs/02-deployment.mdx +129 -0
  6. package/docs/advanced/api-reference.mdx +114 -0
  7. package/docs/advanced/blog.mdx +121 -0
  8. package/docs/advanced/changelog.mdx +113 -0
  9. package/docs/advanced/custom-pages.mdx +187 -0
  10. package/docs/advanced/meta.ts +7 -0
  11. package/docs/changelog/v0-1-0.mdx +12 -0
  12. package/docs/changelog/v0-2-0.mdx +16 -0
  13. package/docs/configuration/ai.mdx +228 -0
  14. package/docs/configuration/analytics.mdx +98 -0
  15. package/docs/configuration/customization.mdx +91 -0
  16. package/docs/configuration/export.mdx +70 -0
  17. package/docs/configuration/index.mdx +271 -0
  18. package/docs/configuration/meta.ts +15 -0
  19. package/docs/configuration/search.mdx +172 -0
  20. package/docs/configuration/seo.mdx +196 -0
  21. package/docs/configuration/theming.mdx +178 -0
  22. package/docs/content/components.mdx +565 -0
  23. package/docs/content/i18n.mdx +205 -0
  24. package/docs/content/index.mdx +161 -0
  25. package/docs/content/islands.mdx +94 -0
  26. package/docs/content/meta.mdx +119 -0
  27. package/docs/content/meta.ts +15 -0
  28. package/docs/content/navigation.mdx +168 -0
  29. package/docs/content/sources.mdx +216 -0
  30. package/docs/content/syntax.mdx +445 -0
  31. package/docs/index.mdx +112 -0
  32. package/docs/reference/cli.mdx +43 -0
  33. package/docs/reference/frontmatter.mdx +74 -0
  34. package/docs/reference/meta.ts +7 -0
  35. package/package.json +140 -6
  36. package/src/ai/ask.ts +93 -0
  37. package/src/ai/llms.ts +65 -0
  38. package/src/ai/markdown.ts +31 -0
  39. package/src/ai/mcp/data.ts +74 -0
  40. package/src/ai/mcp/discovery.ts +49 -0
  41. package/src/ai/mcp/server.ts +225 -0
  42. package/src/ai/mcp/tools.ts +47 -0
  43. package/src/assets/icon.png +0 -0
  44. package/src/astro/generate.ts +878 -0
  45. package/src/astro/index.ts +4 -0
  46. package/src/astro/integration.ts +74 -0
  47. package/src/astro/islands.ts +131 -0
  48. package/src/astro/markdown-negotiation.ts +68 -0
  49. package/src/astro/pages.ts +28 -0
  50. package/src/astro/templates.ts +1199 -0
  51. package/src/cli/commands/add.ts +81 -0
  52. package/src/cli/commands/build.ts +103 -0
  53. package/src/cli/commands/dev.ts +108 -0
  54. package/src/cli/commands/doctor.ts +74 -0
  55. package/src/cli/commands/eject.ts +57 -0
  56. package/src/cli/commands/init.ts +98 -0
  57. package/src/cli/commands/migrate.ts +39 -0
  58. package/src/cli/commands/preview.ts +39 -0
  59. package/src/cli/commands/sync.ts +52 -0
  60. package/src/cli/commands/validate.ts +60 -0
  61. package/src/cli/index.ts +35 -0
  62. package/src/cli/log.ts +37 -0
  63. package/src/cli/prepare.ts +80 -0
  64. package/src/components/Icon.astro +99 -0
  65. package/src/components/content/Accordion.astro +8 -0
  66. package/src/components/content/AccordionItem.astro +121 -0
  67. package/src/components/content/AutoTypeTable.astro +51 -0
  68. package/src/components/content/Badge.astro +124 -0
  69. package/src/components/content/Callout.astro +73 -0
  70. package/src/components/content/Card.astro +104 -0
  71. package/src/components/content/CardGroup.astro +14 -0
  72. package/src/components/content/CodeGroup.astro +13 -0
  73. package/src/components/content/Color.astro +15 -0
  74. package/src/components/content/ColorItem.astro +87 -0
  75. package/src/components/content/ColorRow.astro +10 -0
  76. package/src/components/content/Column.astro +6 -0
  77. package/src/components/content/Columns.astro +9 -0
  78. package/src/components/content/Expandable.astro +11 -0
  79. package/src/components/content/FileTree.astro +8 -0
  80. package/src/components/content/Frame.astro +70 -0
  81. package/src/components/content/GithubInfo.astro +110 -0
  82. package/src/components/content/Math.astro +24 -0
  83. package/src/components/content/Panel.astro +20 -0
  84. package/src/components/content/Prompt.astro +129 -0
  85. package/src/components/content/Step.astro +34 -0
  86. package/src/components/content/Steps.astro +20 -0
  87. package/src/components/content/Tab.astro +40 -0
  88. package/src/components/content/Tabs.astro +273 -0
  89. package/src/components/content/Tile.astro +42 -0
  90. package/src/components/content/Tooltip.astro +68 -0
  91. package/src/components/content/Tree.astro +300 -0
  92. package/src/components/content/TreeFile.astro +15 -0
  93. package/src/components/content/TreeFolder.astro +62 -0
  94. package/src/components/content/TypeTable.astro +106 -0
  95. package/src/components/content/Update.astro +66 -0
  96. package/src/components/content/Visibility.astro +12 -0
  97. package/src/components/content/Warning.astro +9 -0
  98. package/src/components/content/auto-type-table.ts +141 -0
  99. package/src/components/content/github-info.ts +79 -0
  100. package/src/components/content/mermaid-element.ts +68 -0
  101. package/src/components/github-mark.ts +9 -0
  102. package/src/components/index.ts +14 -0
  103. package/src/components/islands/AskAI.astro +12 -0
  104. package/src/components/islands/ask-ai.tsx +156 -0
  105. package/src/components/layout/Analytics.astro +63 -0
  106. package/src/components/layout/Banner.astro +50 -0
  107. package/src/components/layout/Breadcrumbs.astro +31 -0
  108. package/src/components/layout/Favicon.astro +15 -0
  109. package/src/components/layout/Fonts.astro +14 -0
  110. package/src/components/layout/Header.astro +188 -0
  111. package/src/components/layout/LanguageSwitcher.astro +56 -0
  112. package/src/components/layout/NavTree.astro +462 -0
  113. package/src/components/layout/PageActions.astro +438 -0
  114. package/src/components/layout/PageFeedback.astro +58 -0
  115. package/src/components/layout/Pagination.astro +56 -0
  116. package/src/components/layout/ReferenceLayout.astro +102 -0
  117. package/src/components/layout/RootLayout.astro +533 -0
  118. package/src/components/layout/Search.astro +608 -0
  119. package/src/components/layout/TableOfContents.astro +68 -0
  120. package/src/components/layout/analytics-client.ts +38 -0
  121. package/src/components/layout/nav-utils.ts +87 -0
  122. package/src/components/layout/overrides.ts +32 -0
  123. package/src/components/layout/search/algolia.ts +43 -0
  124. package/src/components/layout/search/endpoint.ts +22 -0
  125. package/src/components/layout/search/flexsearch.ts +52 -0
  126. package/src/components/layout/search/orama-cloud.ts +41 -0
  127. package/src/components/layout/search/orama.ts +26 -0
  128. package/src/components/layout/search/pagefind.ts +43 -0
  129. package/src/components/layout/search/types.ts +163 -0
  130. package/src/components/layout/search/typesense.ts +60 -0
  131. package/src/components/layout/toc-element.ts +108 -0
  132. package/src/core/bridge.ts +92 -0
  133. package/src/core/config.ts +112 -0
  134. package/src/core/content.ts +50 -0
  135. package/src/core/define-components.ts +34 -0
  136. package/src/core/define-meta.ts +20 -0
  137. package/src/core/deployment-env.ts +73 -0
  138. package/src/core/diagnostics.ts +104 -0
  139. package/src/core/graph.ts +128 -0
  140. package/src/core/i18n-ui.ts +171 -0
  141. package/src/core/i18n.ts +169 -0
  142. package/src/core/last-modified.ts +88 -0
  143. package/src/core/links.ts +336 -0
  144. package/src/core/load-module.ts +15 -0
  145. package/src/core/manifest.ts +126 -0
  146. package/src/core/meta.ts +97 -0
  147. package/src/core/navigation.ts +392 -0
  148. package/src/core/package-root.ts +37 -0
  149. package/src/core/project-graph.ts +153 -0
  150. package/src/core/project.ts +56 -0
  151. package/src/core/schema.ts +1057 -0
  152. package/src/core/server-features.ts +23 -0
  153. package/src/core/sources/assets.ts +77 -0
  154. package/src/core/sources/cache.ts +122 -0
  155. package/src/core/sources/filesystem.ts +99 -0
  156. package/src/core/sources/mdx-remote.ts +216 -0
  157. package/src/core/sources/mintlify.ts +161 -0
  158. package/src/core/sources/normalize.ts +227 -0
  159. package/src/core/sources/notion.ts +440 -0
  160. package/src/core/sources/portable-text.ts +143 -0
  161. package/src/core/sources/read.ts +36 -0
  162. package/src/core/sources/resolve.ts +158 -0
  163. package/src/core/sources/sanity.ts +218 -0
  164. package/src/core/sources/types.ts +105 -0
  165. package/src/core/types.ts +261 -0
  166. package/src/core/ui-packs/ar.ts +47 -0
  167. package/src/core/ui-packs/bg.ts +47 -0
  168. package/src/core/ui-packs/bn.ts +47 -0
  169. package/src/core/ui-packs/ca.ts +47 -0
  170. package/src/core/ui-packs/cs.ts +47 -0
  171. package/src/core/ui-packs/da.ts +47 -0
  172. package/src/core/ui-packs/de.ts +47 -0
  173. package/src/core/ui-packs/el.ts +47 -0
  174. package/src/core/ui-packs/es.ts +47 -0
  175. package/src/core/ui-packs/fa.ts +47 -0
  176. package/src/core/ui-packs/fi.ts +47 -0
  177. package/src/core/ui-packs/fr.ts +47 -0
  178. package/src/core/ui-packs/he.ts +47 -0
  179. package/src/core/ui-packs/hi.ts +47 -0
  180. package/src/core/ui-packs/hr.ts +47 -0
  181. package/src/core/ui-packs/hu.ts +47 -0
  182. package/src/core/ui-packs/id.ts +47 -0
  183. package/src/core/ui-packs/index.ts +87 -0
  184. package/src/core/ui-packs/it.ts +47 -0
  185. package/src/core/ui-packs/ja.ts +47 -0
  186. package/src/core/ui-packs/ko.ts +47 -0
  187. package/src/core/ui-packs/nl.ts +47 -0
  188. package/src/core/ui-packs/no.ts +47 -0
  189. package/src/core/ui-packs/pl.ts +47 -0
  190. package/src/core/ui-packs/pt-br.ts +47 -0
  191. package/src/core/ui-packs/pt.ts +47 -0
  192. package/src/core/ui-packs/ro.ts +47 -0
  193. package/src/core/ui-packs/ru.ts +47 -0
  194. package/src/core/ui-packs/sk.ts +47 -0
  195. package/src/core/ui-packs/sr.ts +47 -0
  196. package/src/core/ui-packs/sv.ts +47 -0
  197. package/src/core/ui-packs/th.ts +47 -0
  198. package/src/core/ui-packs/tr.ts +47 -0
  199. package/src/core/ui-packs/uk.ts +47 -0
  200. package/src/core/ui-packs/vi.ts +47 -0
  201. package/src/core/ui-packs/zh-tw.ts +47 -0
  202. package/src/core/ui-packs/zh.ts +47 -0
  203. package/src/core/version.ts +23 -0
  204. package/src/deploy/robots.ts +20 -0
  205. package/src/deploy/rss.ts +128 -0
  206. package/src/deploy/sitemap.ts +28 -0
  207. package/src/index.ts +27 -0
  208. package/src/markdown/code-title.ts +71 -0
  209. package/src/markdown/directives.ts +83 -0
  210. package/src/markdown/heading-anchors.ts +137 -0
  211. package/src/markdown/index.ts +159 -0
  212. package/src/markdown/inline-code.ts +108 -0
  213. package/src/markdown/language-icon.ts +172 -0
  214. package/src/markdown/math.ts +32 -0
  215. package/src/markdown/mdast.ts +48 -0
  216. package/src/markdown/mermaid.ts +37 -0
  217. package/src/markdown/package-commands.ts +159 -0
  218. package/src/markdown/package-install.ts +40 -0
  219. package/src/migrate/fumadocs/config.ts +106 -0
  220. package/src/migrate/fumadocs/content.ts +365 -0
  221. package/src/migrate/fumadocs/frontmatter.ts +18 -0
  222. package/src/migrate/fumadocs/index.ts +252 -0
  223. package/src/migrate/fumadocs/meta.ts +114 -0
  224. package/src/migrate/migrate.ts +53 -0
  225. package/src/migrate/mintlify/config.ts +1040 -0
  226. package/src/migrate/mintlify/content.ts +98 -0
  227. package/src/migrate/mintlify/frontmatter.ts +126 -0
  228. package/src/migrate/mintlify/i18n.ts +51 -0
  229. package/src/migrate/mintlify/icons.ts +128 -0
  230. package/src/migrate/mintlify/index.ts +266 -0
  231. package/src/migrate/mintlify/snippets.ts +305 -0
  232. package/src/migrate/mintlify/transform.ts +81 -0
  233. package/src/migrate/nextra/content.ts +46 -0
  234. package/src/migrate/nextra/frontmatter.ts +40 -0
  235. package/src/migrate/nextra/index.ts +374 -0
  236. package/src/migrate/nextra/meta.ts +266 -0
  237. package/src/migrate/shared.ts +623 -0
  238. package/src/migrate/starlight/config.ts +459 -0
  239. package/src/migrate/starlight/content.ts +78 -0
  240. package/src/migrate/starlight/frontmatter.ts +111 -0
  241. package/src/migrate/starlight/i18n.ts +54 -0
  242. package/src/migrate/starlight/index.ts +131 -0
  243. package/src/og/card.ts +92 -0
  244. package/src/og/index.ts +2 -0
  245. package/src/openapi/scalar.ts +246 -0
  246. package/src/registry/eject.ts +263 -0
  247. package/src/registry/registry.ts +100 -0
  248. package/src/registry/rewrite-imports.ts +39 -0
  249. package/src/runtime/index.ts +14 -0
  250. package/src/search/build.ts +23 -0
  251. package/src/search/documents.ts +165 -0
  252. package/src/search/orama-index.ts +66 -0
  253. package/src/search/providers.ts +91 -0
  254. package/src/search/sync/algolia.ts +30 -0
  255. package/src/search/sync/index.ts +50 -0
  256. package/src/search/sync/orama-cloud.ts +40 -0
  257. package/src/search/sync/typesense.ts +65 -0
  258. package/src/seo/jsonld.ts +113 -0
  259. package/src/theme/entry.ts +608 -0
  260. package/src/theme/fonts.ts +198 -0
  261. package/src/theme/icons.ts +184 -0
  262. package/src/theme/palette.ts +143 -0
  263. package/src/theme/twoslash.ts +81 -0
@@ -0,0 +1,300 @@
1
+ ---
2
+ ---
3
+
4
+ <blume-tree
5
+ class="not-prose my-6 block rounded-blume border border-border bg-background p-3 text-sm"
6
+ data-blume-tree
7
+ role="tree"
8
+ >
9
+ <slot />
10
+ </blume-tree>
11
+
12
+ <script>
13
+ const ITEM_SELECTOR = "[data-blume-tree-item]";
14
+ const FOLDER_SELECTOR = "[data-blume-tree-folder]";
15
+ const OPENABLE_SELECTOR =
16
+ '[data-blume-tree-folder][data-blume-tree-openable="true"]';
17
+ const CHILDREN_SELECTOR = "[data-blume-tree-children]";
18
+ const TYPEAHEAD_RESET_MS = 700;
19
+
20
+ const directItem = (folder: Element) =>
21
+ folder.querySelector<HTMLElement>(`:scope > ${ITEM_SELECTOR}`);
22
+
23
+ const folderForItem = (item: HTMLElement) => {
24
+ const folder = item.closest<HTMLElement>(FOLDER_SELECTOR);
25
+ return directItem(folder ?? document.documentElement) === item
26
+ ? folder
27
+ : null;
28
+ };
29
+
30
+ const isOpenableDetails = (
31
+ folder: HTMLElement | null
32
+ ): folder is HTMLDetailsElement =>
33
+ folder instanceof HTMLDetailsElement &&
34
+ folder.matches(OPENABLE_SELECTOR);
35
+
36
+ class BlumeTree extends HTMLElement {
37
+ #activeItem: HTMLElement | null = null;
38
+ #typeahead = "";
39
+ #typeaheadTimer = 0;
40
+
41
+ connectedCallback() {
42
+ if (this.dataset.blumeTreeReady === "true") {
43
+ return;
44
+ }
45
+ this.dataset.blumeTreeReady = "true";
46
+
47
+ this.addEventListener("click", this.#onClick);
48
+ this.addEventListener("focusin", this.#onFocusIn);
49
+ this.addEventListener("keydown", this.#onKeydown);
50
+ this.#folders().forEach((folder) => {
51
+ if (folder instanceof HTMLDetailsElement) {
52
+ folder.addEventListener("toggle", this.#syncFolderState);
53
+ }
54
+ });
55
+ this.#syncTree();
56
+ this.#setActive(this.#visibleItems()[0] ?? null, false);
57
+ }
58
+
59
+ disconnectedCallback() {
60
+ this.removeEventListener("click", this.#onClick);
61
+ this.removeEventListener("focusin", this.#onFocusIn);
62
+ this.removeEventListener("keydown", this.#onKeydown);
63
+ this.#folders().forEach((folder) => {
64
+ if (folder instanceof HTMLDetailsElement) {
65
+ folder.removeEventListener("toggle", this.#syncFolderState);
66
+ }
67
+ });
68
+ window.clearTimeout(this.#typeaheadTimer);
69
+ }
70
+
71
+ #items() {
72
+ return Array.from(
73
+ this.querySelectorAll<HTMLElement>(ITEM_SELECTOR)
74
+ );
75
+ }
76
+
77
+ #folders() {
78
+ return Array.from(
79
+ this.querySelectorAll<HTMLElement>(FOLDER_SELECTOR)
80
+ );
81
+ }
82
+
83
+ #visibleItems() {
84
+ return this.#items().filter((item) => this.#isVisible(item));
85
+ }
86
+
87
+ #isVisible(item: HTMLElement) {
88
+ for (const folder of this.#folders()) {
89
+ if (
90
+ folder instanceof HTMLDetailsElement &&
91
+ folder.contains(item) &&
92
+ !folder.open &&
93
+ directItem(folder) !== item
94
+ ) {
95
+ return false;
96
+ }
97
+ }
98
+ return true;
99
+ }
100
+
101
+ #syncTree() {
102
+ this.#folders().forEach((folder) => {
103
+ const item = directItem(folder);
104
+ if (!item) {
105
+ return;
106
+ }
107
+ const openable = folder.dataset.blumeTreeOpenable === "true";
108
+ if (openable || folder.querySelector(CHILDREN_SELECTOR)) {
109
+ const expanded =
110
+ folder instanceof HTMLDetailsElement ? folder.open : true;
111
+ item.setAttribute("aria-expanded", expanded ? "true" : "false");
112
+ }
113
+ });
114
+
115
+ this.#items().forEach((item) => {
116
+ item.setAttribute("aria-level", String(this.#level(item)));
117
+ item.tabIndex = item === this.#activeItem ? 0 : -1;
118
+ });
119
+ }
120
+
121
+ #level(item: HTMLElement) {
122
+ let level = 1;
123
+ let group = item.parentElement?.closest<HTMLElement>(CHILDREN_SELECTOR);
124
+ while (group && this.contains(group)) {
125
+ level += 1;
126
+ const parentFolder =
127
+ group.parentElement?.closest<HTMLElement>(FOLDER_SELECTOR);
128
+ group =
129
+ parentFolder?.parentElement?.closest<HTMLElement>(
130
+ CHILDREN_SELECTOR
131
+ ) ?? null;
132
+ }
133
+ return level;
134
+ }
135
+
136
+ #setActive(item: HTMLElement | null, focus: boolean) {
137
+ if (!item) {
138
+ return;
139
+ }
140
+ this.#activeItem = item;
141
+ this.#syncTree();
142
+ item.tabIndex = 0;
143
+ if (focus) {
144
+ item.focus();
145
+ }
146
+ }
147
+
148
+ #move(delta: number) {
149
+ const items = this.#visibleItems();
150
+ const active = this.#activeItem ?? (document.activeElement as HTMLElement);
151
+ const index = items.indexOf(active);
152
+ const nextIndex = Math.min(Math.max(index + delta, 0), items.length - 1);
153
+ this.#setActive(items[nextIndex] ?? items[0] ?? null, true);
154
+ }
155
+
156
+ #moveToEdge(edge: "first" | "last") {
157
+ const items = this.#visibleItems();
158
+ this.#setActive(
159
+ edge === "first" ? items[0] : items[items.length - 1],
160
+ true
161
+ );
162
+ }
163
+
164
+ #firstChild(folder: HTMLElement) {
165
+ return folder.querySelector<HTMLElement>(
166
+ `:scope > ${CHILDREN_SELECTOR} > ${ITEM_SELECTOR}, :scope > ${CHILDREN_SELECTOR} > ${FOLDER_SELECTOR} > ${ITEM_SELECTOR}`
167
+ );
168
+ }
169
+
170
+ #parentItem(item: HTMLElement) {
171
+ const group = item.parentElement?.closest(CHILDREN_SELECTOR);
172
+ const parentFolder = group?.parentElement?.closest(FOLDER_SELECTOR);
173
+ return parentFolder ? directItem(parentFolder) : null;
174
+ }
175
+
176
+ #expandSiblings(item: HTMLElement) {
177
+ const group = item.parentElement?.closest(CHILDREN_SELECTOR);
178
+ const scope = group ?? this;
179
+ scope
180
+ .querySelectorAll<HTMLDetailsElement>(
181
+ `:scope > ${OPENABLE_SELECTOR}`
182
+ )
183
+ .forEach((folder) => {
184
+ folder.open = true;
185
+ });
186
+ this.#syncTree();
187
+ }
188
+
189
+ #toggle(folder: HTMLDetailsElement) {
190
+ folder.open = !folder.open;
191
+ this.#syncTree();
192
+ }
193
+
194
+ #search(character: string) {
195
+ window.clearTimeout(this.#typeaheadTimer);
196
+ this.#typeahead += character.toLowerCase();
197
+ this.#typeaheadTimer = window.setTimeout(() => {
198
+ this.#typeahead = "";
199
+ }, TYPEAHEAD_RESET_MS);
200
+
201
+ const items = this.#visibleItems();
202
+ const active = this.#activeItem ?? (document.activeElement as HTMLElement);
203
+ const activeIndex = items.indexOf(active);
204
+ const ordered = items
205
+ .slice(Math.max(activeIndex + 1, 0))
206
+ .concat(items.slice(0, Math.max(activeIndex + 1, 0)));
207
+ const match = ordered.find((item) =>
208
+ (item.dataset.blumeTreeName ?? "")
209
+ .toLowerCase()
210
+ .startsWith(this.#typeahead)
211
+ );
212
+ this.#setActive(match ?? null, true);
213
+ }
214
+
215
+ #onClick = (event: MouseEvent) => {
216
+ const item = (event.target as Element | null)?.closest<HTMLElement>(
217
+ ITEM_SELECTOR
218
+ );
219
+ if (item && this.contains(item)) {
220
+ this.#setActive(item, false);
221
+ }
222
+ };
223
+
224
+ #onFocusIn = (event: FocusEvent) => {
225
+ const item = (event.target as Element | null)?.closest<HTMLElement>(
226
+ ITEM_SELECTOR
227
+ );
228
+ if (item && this.contains(item)) {
229
+ this.#setActive(item, false);
230
+ }
231
+ };
232
+
233
+ #onKeydown = (event: KeyboardEvent) => {
234
+ const item = (event.target as Element | null)?.closest<HTMLElement>(
235
+ ITEM_SELECTOR
236
+ );
237
+ if (!item || !this.contains(item)) {
238
+ return;
239
+ }
240
+
241
+ const folder = folderForItem(item);
242
+ if (event.key === "ArrowDown") {
243
+ event.preventDefault();
244
+ this.#move(1);
245
+ } else if (event.key === "ArrowUp") {
246
+ event.preventDefault();
247
+ this.#move(-1);
248
+ } else if (event.key === "Home") {
249
+ event.preventDefault();
250
+ this.#moveToEdge("first");
251
+ } else if (event.key === "End") {
252
+ event.preventDefault();
253
+ this.#moveToEdge("last");
254
+ } else if (event.key === "ArrowRight" && folder) {
255
+ event.preventDefault();
256
+ if (isOpenableDetails(folder) && !folder.open) {
257
+ folder.open = true;
258
+ this.#syncTree();
259
+ } else {
260
+ this.#setActive(this.#firstChild(folder), true);
261
+ }
262
+ } else if (event.key === "ArrowLeft") {
263
+ const parent = this.#parentItem(item);
264
+ if (isOpenableDetails(folder) && folder.open) {
265
+ event.preventDefault();
266
+ folder.open = false;
267
+ this.#syncTree();
268
+ } else if (parent) {
269
+ event.preventDefault();
270
+ this.#setActive(parent, true);
271
+ }
272
+ } else if (
273
+ (event.key === "Enter" || event.key === " ") &&
274
+ isOpenableDetails(folder)
275
+ ) {
276
+ event.preventDefault();
277
+ this.#toggle(folder);
278
+ } else if (event.key === "*") {
279
+ event.preventDefault();
280
+ this.#expandSiblings(item);
281
+ } else if (
282
+ event.key.length === 1 &&
283
+ !event.altKey &&
284
+ !event.ctrlKey &&
285
+ !event.metaKey
286
+ ) {
287
+ event.preventDefault();
288
+ this.#search(event.key);
289
+ }
290
+ };
291
+
292
+ #syncFolderState = () => {
293
+ this.#syncTree();
294
+ };
295
+ }
296
+
297
+ if (!customElements.get("blume-tree")) {
298
+ customElements.define("blume-tree", BlumeTree);
299
+ }
300
+ </script>
@@ -0,0 +1,15 @@
1
+ ---
2
+ import Icon from "../Icon.astro";
3
+
4
+ const { name } = Astro.props;
5
+ ---
6
+
7
+ <div
8
+ class="flex items-center gap-2 rounded px-2 py-1 text-muted-foreground outline-none transition-colors hover:bg-muted/60 focus-visible:ring-2 focus-visible:ring-accent/40"
9
+ data-blume-tree-item
10
+ data-blume-tree-name={name}
11
+ role="treeitem"
12
+ >
13
+ <Icon class="shrink-0" name="file" size={15} />
14
+ <span class="font-mono text-xs">{name}</span>
15
+ </div>
@@ -0,0 +1,62 @@
1
+ ---
2
+ import Icon from "../Icon.astro";
3
+
4
+ const { defaultOpen = false, name, openable = true } = Astro.props;
5
+ ---
6
+
7
+ {
8
+ openable ? (
9
+ <details
10
+ class="group"
11
+ data-blume-tree-folder
12
+ data-blume-tree-openable="true"
13
+ open={defaultOpen}
14
+ role="none"
15
+ >
16
+ <summary
17
+ class="flex cursor-pointer list-none items-center gap-2 rounded px-2 py-1 font-medium outline-none transition-colors hover:bg-muted/60 focus-visible:ring-2 focus-visible:ring-accent/40 [&::-webkit-details-marker]:hidden"
18
+ data-blume-tree-item
19
+ data-blume-tree-name={name}
20
+ role="treeitem"
21
+ >
22
+ <Icon
23
+ class="shrink-0 transition-transform group-open:rotate-90"
24
+ name="chevron-right"
25
+ size={15}
26
+ />
27
+ <Icon class="shrink-0 text-muted-foreground" name="folder" size={15} />
28
+ <span class="font-mono text-xs">{name}</span>
29
+ </summary>
30
+ <div
31
+ class="ms-4 border-border border-s ps-2"
32
+ data-blume-tree-children
33
+ role="group"
34
+ >
35
+ <slot />
36
+ </div>
37
+ </details>
38
+ ) : (
39
+ <div
40
+ data-blume-tree-folder
41
+ data-blume-tree-openable="false"
42
+ role="none"
43
+ >
44
+ <div
45
+ class="flex items-center gap-2 rounded px-2 py-1 font-medium outline-none transition-colors hover:bg-muted/60 focus-visible:ring-2 focus-visible:ring-accent/40"
46
+ data-blume-tree-item
47
+ data-blume-tree-name={name}
48
+ role="treeitem"
49
+ >
50
+ <Icon class="shrink-0 text-muted-foreground" name="folder" size={15} />
51
+ <span class="font-mono text-xs">{name}</span>
52
+ </div>
53
+ <div
54
+ class="ms-4 border-border border-s ps-2"
55
+ data-blume-tree-children
56
+ role="group"
57
+ >
58
+ <slot />
59
+ </div>
60
+ </div>
61
+ )
62
+ }
@@ -0,0 +1,106 @@
1
+ ---
2
+ // A table of object properties laid out like Fumadocs' <TypeTable>: a Prop /
3
+ // Type grid where every row is an expandable <details>. Collapsed, a row shows
4
+ // the accent-colored prop name (with a `?` suffix when optional) and its type;
5
+ // expanded, the row lifts into a card showing the description and a Type/Default
6
+ // detail grid. Uses native <details>/<summary> so the core stays React-free.
7
+ interface TypeEntry {
8
+ default?: string;
9
+ description?: string;
10
+ required?: boolean;
11
+ type: string;
12
+ typeDescription?: string;
13
+ typeDescriptionLink?: string;
14
+ }
15
+
16
+ interface Props {
17
+ type?: Record<string, TypeEntry>;
18
+ }
19
+
20
+ const { type = {} } = Astro.props;
21
+ const entries = Object.entries(type);
22
+
23
+ // Shared column track so the header, every summary, and the detail grid align.
24
+ const columns = "grid grid-cols-[1fr_2fr_auto] items-center gap-4";
25
+ ---
26
+
27
+ {
28
+ entries.length > 0 && (
29
+ <div class="not-prose my-6 rounded-blume border border-border bg-muted/30 p-1.5">
30
+ <div class={`${columns} px-3 py-2 font-medium text-muted-foreground text-xs`}>
31
+ <span>Prop</span>
32
+ <span>Type</span>
33
+ <span aria-hidden="true" class="size-3.5" />
34
+ </div>
35
+
36
+ <div class="mt-1 flex flex-col gap-1">
37
+ {entries.map(([name, info]) => (
38
+ <details class="group/tt border-border border-t [[open]+&]:border-t-transparent open:rounded-blume open:border-transparent open:bg-background open:shadow-sm open:ring-1 open:ring-border">
39
+ <summary class={`${columns} cursor-pointer list-none px-3 py-2.5 [&::-webkit-details-marker]:hidden`}>
40
+ <code class="min-w-0 break-words font-mono text-accent text-xs">
41
+ {name}
42
+ {info.required ? "" : "?"}
43
+ </code>
44
+ {info.typeDescriptionLink ? (
45
+ <a
46
+ class="min-w-0 break-words font-mono text-foreground text-xs underline-offset-2 hover:underline"
47
+ href={info.typeDescriptionLink}
48
+ >
49
+ {info.type}
50
+ </a>
51
+ ) : (
52
+ <code class="min-w-0 break-words font-mono text-foreground text-xs">
53
+ {info.type}
54
+ </code>
55
+ )}
56
+ <svg
57
+ aria-hidden="true"
58
+ class="size-3.5 shrink-0 text-muted-foreground transition-transform group-open/tt:rotate-180"
59
+ fill="none"
60
+ height="16"
61
+ stroke="currentColor"
62
+ stroke-linecap="round"
63
+ stroke-linejoin="round"
64
+ stroke-width="2"
65
+ viewBox="0 0 24 24"
66
+ width="16"
67
+ >
68
+ <path d="m6 9 6 6 6-6" />
69
+ </svg>
70
+ </summary>
71
+
72
+ <div class="border-border border-t px-3 py-3 text-xs">
73
+ {info.description && (
74
+ <p class="mt-0! mb-2! text-foreground">{info.description}</p>
75
+ )}
76
+ {info.typeDescription && (
77
+ <p class="mt-0! mb-2! text-muted-foreground">
78
+ {info.typeDescription}
79
+ </p>
80
+ )}
81
+ <div class="space-y-1.5">
82
+ <div class={columns}>
83
+ <span class="text-muted-foreground">Type</span>
84
+ <code class="min-w-0 break-words font-mono text-foreground">
85
+ {info.type}
86
+ </code>
87
+ <span aria-hidden="true" class="size-3.5" />
88
+ </div>
89
+ {info.default && (
90
+ <div class={columns}>
91
+ <span class="text-muted-foreground">Default</span>
92
+ <code class="min-w-0 break-words font-mono text-foreground">
93
+ {info.default}
94
+ </code>
95
+ <span aria-hidden="true" class="size-3.5" />
96
+ </div>
97
+ )}
98
+ </div>
99
+ </div>
100
+ </details>
101
+ ))}
102
+ </div>
103
+ </div>
104
+ )
105
+ }
106
+ <slot />
@@ -0,0 +1,66 @@
1
+ ---
2
+ interface RssMetadata {
3
+ description?: string;
4
+ title?: string;
5
+ }
6
+
7
+ interface Props {
8
+ description?: string;
9
+ id?: string;
10
+ label?: string;
11
+ rss?: RssMetadata;
12
+ tags?: string[] | string;
13
+ title?: string;
14
+ }
15
+
16
+ const { description, id: providedId, label, rss, tags, title } = Astro.props;
17
+ const slugify = (text: string): string =>
18
+ text
19
+ .toLowerCase()
20
+ .trim()
21
+ .replaceAll(/[^\w\s-]/gu, "")
22
+ .replaceAll(/[\s_]+/gu, "-")
23
+ .replaceAll(/-+/gu, "-")
24
+ .replaceAll(/^-|-$/gu, "");
25
+ const updateLabel = label ?? title ?? "Update";
26
+ const id = providedId ?? (slugify(updateLabel) || "update");
27
+ const tagList = Array.isArray(tags) ? tags : tags ? [tags] : [];
28
+ ---
29
+
30
+ <article
31
+ class="not-prose group/update my-8 grid gap-4 border-border border-s ps-4 md:grid-cols-[10rem_minmax(0,1fr)] md:border-s-0 md:ps-0"
32
+ data-blume-update
33
+ data-blume-update-rss-description={rss?.description}
34
+ data-blume-update-rss-title={rss?.title}
35
+ id={id}
36
+ >
37
+ <header class="md:border-border md:border-e md:pe-4">
38
+ <a
39
+ class="font-semibold text-foreground text-sm no-underline hover:text-accent"
40
+ href={`#${id}`}
41
+ >
42
+ {updateLabel}
43
+ </a>
44
+ {
45
+ description && (
46
+ <p class="mt-1! mb-0! text-muted-foreground text-xs leading-5">
47
+ {description}
48
+ </p>
49
+ )
50
+ }
51
+ {
52
+ tagList.length > 0 && (
53
+ <div class="mt-3 flex flex-wrap gap-1.5">
54
+ {tagList.map((tag) => (
55
+ <span class="rounded-full bg-muted px-2 py-0.5 font-medium text-muted-foreground text-[0.65rem]">
56
+ {tag}
57
+ </span>
58
+ ))}
59
+ </div>
60
+ )
61
+ }
62
+ </header>
63
+ <div class="min-w-0 text-muted-foreground text-sm leading-6 [&_a]:font-medium [&_a]:text-foreground [&_a]:underline [&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_h2]:mt-0! [&_h2]:border-t-0! [&_h2]:pt-0! [&_h2]:text-base [&_h2]:font-semibold [&_h2]:text-foreground [&_h3]:font-semibold [&_h3]:text-foreground [&_img]:rounded-blume [&_li]:my-1 [&_p:first-child]:mt-0! [&_p:last-child]:mb-0!">
64
+ <slot />
65
+ </div>
66
+ </article>
@@ -0,0 +1,12 @@
1
+ ---
2
+ const { for: audience = "humans" } = Astro.props;
3
+ const visibleOnWeb = audience !== "agents";
4
+ ---
5
+
6
+ {
7
+ visibleOnWeb && (
8
+ <div data-blume-visibility={audience}>
9
+ <slot />
10
+ </div>
11
+ )
12
+ }
@@ -0,0 +1,9 @@
1
+ ---
2
+ import Callout from "./Callout.astro";
3
+
4
+ const { title } = Astro.props;
5
+ ---
6
+
7
+ <Callout title={title} type="warning">
8
+ <slot />
9
+ </Callout>