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,129 @@
1
+ ---
2
+ import { createSatteriMarkdownProcessor } from "@astrojs/markdown-satteri";
3
+
4
+ interface Props {
5
+ actions?: boolean | string | string[];
6
+ description: string;
7
+ }
8
+
9
+ const { actions = ["copy"], description } = Astro.props;
10
+
11
+ const escapeRawHtml = (value: string): string =>
12
+ value.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
13
+
14
+ const unwrapParagraph = (html: string): string => {
15
+ const trimmed = html.trim();
16
+ const match = trimmed.match(/^<p>(?<content>[\s\S]*)<\/p>$/u);
17
+ return match?.groups?.content ?? trimmed;
18
+ };
19
+
20
+ const normalizeActions = (value: Props["actions"]): string[] => {
21
+ if (Array.isArray(value)) {
22
+ return value;
23
+ }
24
+ if (typeof value === "string") {
25
+ return [value];
26
+ }
27
+ return value === false ? [] : ["copy"];
28
+ };
29
+
30
+ const enabledActions = new Set(normalizeActions(actions));
31
+ const markdownProcessor = await createSatteriMarkdownProcessor();
32
+ const renderedDescription = await markdownProcessor.render(
33
+ escapeRawHtml(description)
34
+ );
35
+ const descriptionHtml = unwrapParagraph(renderedDescription.code);
36
+
37
+ const primaryButton =
38
+ "inline-flex h-8 cursor-pointer items-center rounded-lg bg-accent px-3.5 font-medium text-accent-foreground text-xs transition hover:bg-accent/90";
39
+ const secondaryButton =
40
+ "inline-flex h-8 cursor-pointer items-center rounded-lg border border-border bg-background px-3.5 font-medium text-muted-foreground text-xs transition hover:border-accent hover:text-accent";
41
+ ---
42
+
43
+ <blume-prompt
44
+ class="not-prose my-6 block rounded-blume border border-border bg-muted/30"
45
+ >
46
+ <div class="flex items-center justify-between gap-3 px-4 py-3">
47
+ <p
48
+ class="m-0! min-w-0 text-foreground text-sm [&_a]:font-medium [&_a]:text-foreground [&_a]:underline [&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_strong]:font-semibold [&_strong]:text-foreground"
49
+ data-blume-prompt-description
50
+ set:html={descriptionHtml}
51
+ />
52
+ {
53
+ enabledActions.size > 0 && (
54
+ <div class="flex shrink-0 items-center gap-2">
55
+ {enabledActions.has("copy") && (
56
+ <button
57
+ aria-label="Copy prompt"
58
+ class={primaryButton}
59
+ data-blume-prompt-copy
60
+ type="button"
61
+ >
62
+ <span data-blume-prompt-copy-label>Copy prompt</span>
63
+ </button>
64
+ )}
65
+ {enabledActions.has("cursor") && (
66
+ <a
67
+ aria-label="Open prompt in Cursor"
68
+ class={secondaryButton}
69
+ data-blume-prompt-cursor
70
+ href="cursor://anysphere.cursor-deeplink/prompt"
71
+ >
72
+ Open in Cursor
73
+ </a>
74
+ )}
75
+ </div>
76
+ )
77
+ }
78
+ </div>
79
+ <div data-blume-prompt-content hidden>
80
+ <slot />
81
+ </div>
82
+ </blume-prompt>
83
+
84
+ <script>
85
+ class BlumePrompt extends HTMLElement {
86
+ connectedCallback() {
87
+ const content = this.querySelector<HTMLElement>(
88
+ "[data-blume-prompt-content]"
89
+ );
90
+ const promptText = () => content?.textContent?.trim() ?? "";
91
+
92
+ const cursor = this.querySelector<HTMLAnchorElement>(
93
+ "[data-blume-prompt-cursor]"
94
+ );
95
+ if (cursor) {
96
+ cursor.href = `cursor://anysphere.cursor-deeplink/prompt?text=${encodeURIComponent(promptText())}`;
97
+ }
98
+
99
+ const copy = this.querySelector<HTMLButtonElement>(
100
+ "[data-blume-prompt-copy]"
101
+ );
102
+ copy?.addEventListener("click", async () => {
103
+ const text = promptText();
104
+ if (!text) {
105
+ return;
106
+ }
107
+ try {
108
+ await navigator.clipboard.writeText(text);
109
+ } catch {
110
+ return;
111
+ }
112
+
113
+ const label = copy.querySelector("[data-blume-prompt-copy-label]");
114
+ if (!label) {
115
+ return;
116
+ }
117
+ const previous = label.textContent ?? "Copy prompt";
118
+ label.textContent = "Copied";
119
+ setTimeout(() => {
120
+ label.textContent = previous;
121
+ }, 1500);
122
+ });
123
+ }
124
+ }
125
+
126
+ if (!customElements.get("blume-prompt")) {
127
+ customElements.define("blume-prompt", BlumePrompt);
128
+ }
129
+ </script>
@@ -0,0 +1,34 @@
1
+ ---
2
+ import Icon from "../Icon.astro";
3
+ import { hasIcon } from "../../theme/icons.ts";
4
+
5
+ interface Props {
6
+ icon?: string;
7
+ iconType?: string;
8
+ title?: string;
9
+ }
10
+
11
+ const { icon, iconType, title } = Astro.props;
12
+ const markerClass =
13
+ "absolute top-0 -start-[2.75rem] flex size-6 items-center justify-center rounded-full bg-accent text-center font-semibold text-accent-foreground text-xs";
14
+ const isImageIcon = (value: string): boolean =>
15
+ /^(?:https?:\/\/|data:image\/|\/|\.{1,2}\/)/u.test(value) ||
16
+ /\.(?:avif|gif|jpe?g|png|svg|webp)$/iu.test(value);
17
+ const canRenderIcon =
18
+ icon !== undefined &&
19
+ (/^\s*<svg[\s\S]*<\/svg>\s*$/u.test(icon) ||
20
+ isImageIcon(icon) ||
21
+ hasIcon(icon, iconType));
22
+ ---
23
+
24
+ <div
25
+ class="relative [counter-increment:blume-step]"
26
+ >
27
+ <span class:list={[markerClass, !canRenderIcon && "before:[content:counter(blume-step)]"]}>
28
+ {canRenderIcon && <Icon iconType={iconType} name={icon} size={14} />}
29
+ </span>
30
+ {title && <p class="mb-1.5 font-semibold">{title}</p>}
31
+ <div class="[&>:first-child]:mt-0! [&>:last-child]:mb-0!">
32
+ <slot />
33
+ </div>
34
+ </div>
@@ -0,0 +1,20 @@
1
+ ---
2
+ interface Props {
3
+ titleSize?: "h2" | "h3" | "h4" | "p";
4
+ }
5
+
6
+ const { titleSize = "p" } = Astro.props;
7
+ const titleSizeStyle = {
8
+ h2: "--blume-step-title-size:1.5rem;--blume-step-title-line-height:2rem;",
9
+ h3: "--blume-step-title-size:1.25rem;--blume-step-title-line-height:1.75rem;",
10
+ h4: "--blume-step-title-size:1.125rem;--blume-step-title-line-height:1.75rem;",
11
+ p: "--blume-step-title-size:1rem;--blume-step-title-line-height:1.75rem;",
12
+ }[titleSize];
13
+ ---
14
+
15
+ <div
16
+ class="not-prose my-6 ms-3 space-y-6 border-border border-s ps-8 [counter-reset:blume-step]"
17
+ style={titleSizeStyle}
18
+ >
19
+ <slot />
20
+ </div>
@@ -0,0 +1,46 @@
1
+ ---
2
+ import Icon from "../Icon.astro";
3
+
4
+ interface Props {
5
+ class?: string;
6
+ icon?: string;
7
+ iconType?: string;
8
+ id?: string;
9
+ style?: string;
10
+ title: string;
11
+ }
12
+
13
+ const { class: className, icon, iconType, id, style, title } = Astro.props;
14
+ const slugify = (value: string): string =>
15
+ value
16
+ .toLowerCase()
17
+ .trim()
18
+ .replaceAll(/[^\w\s-]/gu, "")
19
+ .replaceAll(/[\s_]+/gu, "-")
20
+ .replaceAll(/-+/gu, "-")
21
+ .replaceAll(/^-|-$/gu, "");
22
+ const tabId = id ?? slugify(title);
23
+ ---
24
+
25
+ <div
26
+ class:list={[
27
+ "px-4 py-3 [&:has(>pre)]:p-0 [&>:first-child]:mt-0! [&>:last-child]:mb-0!",
28
+ className,
29
+ ]}
30
+ data-blume-tab-panel
31
+ style={style}
32
+ data-icon={icon}
33
+ data-tab-id={tabId}
34
+ data-title={title}
35
+ id={id}
36
+ role="tabpanel"
37
+ >
38
+ <slot />
39
+ {
40
+ icon && (
41
+ <template data-blume-tab-icon>
42
+ <Icon class="shrink-0" iconType={iconType} name={icon} size={14} />
43
+ </template>
44
+ )
45
+ }
46
+ </div>
@@ -0,0 +1,273 @@
1
+ ---
2
+ interface Props {
3
+ borderBottom?: boolean;
4
+ defaultTabIndex?: number;
5
+ dropdown?: boolean;
6
+ hash?: boolean;
7
+ sync?: boolean;
8
+ }
9
+
10
+ const {
11
+ borderBottom = true,
12
+ defaultTabIndex = 0,
13
+ dropdown = false,
14
+ hash = true,
15
+ sync = true,
16
+ } = Astro.props;
17
+ ---
18
+
19
+ <blume-tabs
20
+ class="not-prose my-6 block overflow-hidden rounded-blume border border-border bg-background"
21
+ data-border-bottom={borderBottom ? "true" : "false"}
22
+ data-default-tab-index={defaultTabIndex}
23
+ data-dropdown={dropdown ? "true" : "false"}
24
+ data-hash={hash ? "true" : "false"}
25
+ data-sync={sync ? "true" : "false"}
26
+ >
27
+ <div class:list={[dropdown ? "" : "overflow-x-auto"]}>
28
+ <div
29
+ class:list={[
30
+ dropdown || borderBottom ? "border-border border-b" : "",
31
+ dropdown ? "p-3" : "flex gap-4 px-3",
32
+ ]}
33
+ data-blume-tablist
34
+ role={dropdown ? undefined : "tablist"}
35
+ >
36
+ </div>
37
+ </div>
38
+ <div data-blume-tab-content>
39
+ <slot />
40
+ </div>
41
+ </blume-tabs>
42
+
43
+ <script>
44
+ const TRIGGER_CLASS =
45
+ "-mb-px inline-flex shrink-0 cursor-pointer items-center gap-1.5 border-transparent border-b-2 bg-transparent px-1 py-2.5 font-medium text-muted-foreground text-xs transition-colors hover:text-foreground aria-[selected=true]:border-accent aria-[selected=true]:text-foreground";
46
+ const SELECT_CLASS =
47
+ "w-full rounded-blume border border-border bg-background px-3 py-2 font-medium text-foreground text-sm";
48
+ const SYNC_EVENT = "blume-tabs-change";
49
+
50
+ const panelTitle = (panel: HTMLElement, index: number) =>
51
+ panel.dataset.title ?? `Tab ${index + 1}`;
52
+ const panelId = (panel: HTMLElement) => panel.dataset.tabId ?? panel.id;
53
+ const boundedIndex = (value: number, length: number) =>
54
+ Math.min(Math.max(value, 0), Math.max(length - 1, 0));
55
+ const appendTriggerContent = (
56
+ trigger: HTMLButtonElement,
57
+ panel: HTMLElement,
58
+ index: number
59
+ ) => {
60
+ const icon = panel.querySelector<HTMLTemplateElement>(
61
+ ":scope > template[data-blume-tab-icon]"
62
+ );
63
+ if (icon) {
64
+ trigger.appendChild(icon.content.cloneNode(true));
65
+ }
66
+ const label = document.createElement("span");
67
+ label.textContent = panelTitle(panel, index);
68
+ trigger.appendChild(label);
69
+ };
70
+
71
+ class BlumeTabs extends HTMLElement {
72
+ #panels: HTMLElement[] = [];
73
+ #triggers: HTMLButtonElement[] = [];
74
+ #select: HTMLSelectElement | null = null;
75
+
76
+ connectedCallback() {
77
+ const list = this.querySelector<HTMLElement>("[data-blume-tablist]");
78
+ let panels = Array.from(
79
+ this.querySelectorAll<HTMLElement>("[data-blume-tab-panel]")
80
+ );
81
+ // CodeGroup passes raw code fences instead of <Tab> elements, so adopt
82
+ // the content wrapper's direct code blocks as panels. The tab label comes
83
+ // from each block's title (the text after the language in the fence).
84
+ if (panels.length === 0) {
85
+ const wrapper = this.querySelector<HTMLElement>(
86
+ ":scope > [data-blume-tab-content]"
87
+ );
88
+ const blocks = wrapper
89
+ ? Array.from(wrapper.children).filter(
90
+ (child): child is HTMLElement =>
91
+ child instanceof HTMLElement && child.matches("pre, figure")
92
+ )
93
+ : [];
94
+ for (const block of blocks) {
95
+ block.setAttribute("data-blume-tab-panel", "");
96
+ }
97
+ panels = blocks;
98
+ }
99
+ if (!list || panels.length === 0) {
100
+ return;
101
+ }
102
+ if (this.dataset.blumeTabsReady === "true") {
103
+ return;
104
+ }
105
+ this.dataset.blumeTabsReady = "true";
106
+ this.#panels = panels;
107
+
108
+ if (this.dataset.dropdown === "true") {
109
+ this.#select = document.createElement("select");
110
+ this.#select.className = SELECT_CLASS;
111
+ this.#select.setAttribute("aria-label", "Select code example");
112
+ panels.forEach((panel, index) => {
113
+ const option = document.createElement("option");
114
+ option.value = String(index);
115
+ option.textContent = panelTitle(panel, index);
116
+ this.#select?.appendChild(option);
117
+ });
118
+ this.#select.addEventListener("change", () => {
119
+ this.activate(Number(this.#select?.value ?? "0"), true, true);
120
+ });
121
+ list.appendChild(this.#select);
122
+ } else {
123
+ panels.forEach((panel, index) => {
124
+ const trigger = document.createElement("button");
125
+ trigger.type = "button";
126
+ trigger.role = "tab";
127
+ trigger.className = TRIGGER_CLASS;
128
+ appendTriggerContent(trigger, panel, index);
129
+ trigger.tabIndex = index === 0 ? 0 : -1;
130
+ if (panel.id) {
131
+ trigger.setAttribute("aria-controls", panel.id);
132
+ }
133
+ trigger.setAttribute(
134
+ "aria-selected",
135
+ index === 0 ? "true" : "false"
136
+ );
137
+ trigger.addEventListener("click", () => {
138
+ this.activate(index, true, true);
139
+ });
140
+ trigger.addEventListener("keydown", (event) => {
141
+ this.#onTriggerKeydown(event, index);
142
+ });
143
+ list.appendChild(trigger);
144
+ this.#triggers.push(trigger);
145
+ panel.classList.toggle("hidden", index !== 0);
146
+ });
147
+ }
148
+
149
+ this.activate(this.#initialIndex(), false, false);
150
+ if (this.#syncEnabled()) {
151
+ document.addEventListener(SYNC_EVENT, this.#sync);
152
+ }
153
+ window.addEventListener("hashchange", this.#hashChange);
154
+ }
155
+
156
+ disconnectedCallback() {
157
+ document.removeEventListener(SYNC_EVENT, this.#sync);
158
+ window.removeEventListener("hashchange", this.#hashChange);
159
+ }
160
+
161
+ activate(index: number, sync: boolean, updateHash: boolean) {
162
+ const activePanel = this.#panels[index];
163
+ if (!activePanel) {
164
+ return;
165
+ }
166
+
167
+ this.#panels.forEach((panel, panelIndex) => {
168
+ panel.classList.toggle("hidden", panelIndex !== index);
169
+ });
170
+ this.#triggers.forEach((trigger, triggerIndex) => {
171
+ trigger.setAttribute(
172
+ "aria-selected",
173
+ triggerIndex === index ? "true" : "false"
174
+ );
175
+ trigger.tabIndex = triggerIndex === index ? 0 : -1;
176
+ });
177
+ if (this.#select) {
178
+ this.#select.value = String(index);
179
+ }
180
+ const id = panelId(activePanel);
181
+ if (updateHash && id && this.#hashEnabled()) {
182
+ history.replaceState(null, "", `#${encodeURIComponent(id)}`);
183
+ }
184
+ if (sync && this.#syncEnabled()) {
185
+ document.dispatchEvent(
186
+ new CustomEvent(SYNC_EVENT, {
187
+ detail: { title: panelTitle(activePanel, index) },
188
+ })
189
+ );
190
+ }
191
+ }
192
+
193
+ activateByTitle(title: string, sync: boolean) {
194
+ const index = this.#panels.findIndex((panel, panelIndex) => {
195
+ return panelTitle(panel, panelIndex) === title;
196
+ });
197
+ if (index !== -1) {
198
+ this.activate(index, sync, false);
199
+ }
200
+ }
201
+
202
+ #activateByHash() {
203
+ if (!this.#hashEnabled()) {
204
+ return false;
205
+ }
206
+ const id = decodeURIComponent(window.location.hash.slice(1));
207
+ if (!id) {
208
+ return false;
209
+ }
210
+ const index = this.#panels.findIndex((panel) => panelId(panel) === id);
211
+ if (index === -1) {
212
+ return false;
213
+ }
214
+
215
+ this.activate(index, false, false);
216
+ return true;
217
+ }
218
+
219
+ #initialIndex() {
220
+ if (this.#activateByHash()) {
221
+ return this.#panels.findIndex((panel) => !panel.classList.contains("hidden"));
222
+ }
223
+ return boundedIndex(
224
+ Number(this.dataset.defaultTabIndex ?? "0"),
225
+ this.#panels.length
226
+ );
227
+ }
228
+
229
+ #onTriggerKeydown(event: KeyboardEvent, index: number) {
230
+ const lastIndex = this.#triggers.length - 1;
231
+ let nextIndex: number | undefined;
232
+ if (event.key === "ArrowRight") {
233
+ nextIndex = index === lastIndex ? 0 : index + 1;
234
+ } else if (event.key === "ArrowLeft") {
235
+ nextIndex = index === 0 ? lastIndex : index - 1;
236
+ } else if (event.key === "Home") {
237
+ nextIndex = 0;
238
+ } else if (event.key === "End") {
239
+ nextIndex = lastIndex;
240
+ }
241
+ if (nextIndex === undefined) {
242
+ return;
243
+ }
244
+
245
+ event.preventDefault();
246
+ this.activate(nextIndex, true, true);
247
+ this.#triggers[nextIndex]?.focus();
248
+ }
249
+
250
+ #syncEnabled() {
251
+ return this.dataset.sync !== "false";
252
+ }
253
+
254
+ #hashEnabled() {
255
+ return this.dataset.hash !== "false";
256
+ }
257
+
258
+ #sync = (event: Event) => {
259
+ const title = (event as CustomEvent<{ title?: string }>).detail?.title;
260
+ if (title) {
261
+ this.activateByTitle(title, false);
262
+ }
263
+ };
264
+
265
+ #hashChange = () => {
266
+ this.#activateByHash();
267
+ };
268
+ }
269
+
270
+ if (!customElements.get("blume-tabs")) {
271
+ customElements.define("blume-tabs", BlumeTabs);
272
+ }
273
+ </script>
@@ -0,0 +1,42 @@
1
+ ---
2
+ import Icon from "../Icon.astro";
3
+
4
+ const { description, href, title } = Astro.props;
5
+ const Tag = href ? "a" : "div";
6
+ const external = href?.startsWith("http");
7
+ const previewPattern =
8
+ "background-image:radial-gradient(circle at 1px 1px,color-mix(in oklab,currentColor 18%,transparent) 1px,transparent 0);background-size:18px 18px;";
9
+ ---
10
+
11
+ <Tag
12
+ class="not-prose block overflow-hidden rounded-blume border border-border bg-background text-inherit no-underline! transition-colors hover:no-underline! hover:[&[href]]:border-accent"
13
+ data-blume-tile
14
+ href={href}
15
+ rel={external ? "noreferrer" : undefined}
16
+ target={external ? "_blank" : undefined}
17
+ >
18
+ <div
19
+ class="flex aspect-[16/9] min-h-32 items-center justify-center bg-muted/40 p-4 text-muted-foreground [&_img]:max-h-full [&_img]:max-w-full [&_svg]:max-h-full [&_svg]:max-w-full"
20
+ data-blume-tile-preview
21
+ style={previewPattern}
22
+ >
23
+ <slot />
24
+ </div>
25
+ <div class="p-4">
26
+ {
27
+ title && (
28
+ <p class="m-0! flex items-center gap-1.5 font-semibold">
29
+ {title}
30
+ {href && (
31
+ <Icon class="text-muted-foreground" name="arrow-right" size={14} />
32
+ )}
33
+ </p>
34
+ )
35
+ }
36
+ {
37
+ description && (
38
+ <p class="mt-1! mb-0! text-muted-foreground text-sm">{description}</p>
39
+ )
40
+ }
41
+ </div>
42
+ </Tag>
@@ -0,0 +1,68 @@
1
+ ---
2
+ import { randomUUID } from "node:crypto";
3
+
4
+ interface Props {
5
+ cta?: string;
6
+ headline?: string;
7
+ href?: string;
8
+ tip: string;
9
+ }
10
+
11
+ const { cta, headline, href, tip } = Astro.props;
12
+ const tooltipId = `blume-tooltip-${randomUUID()}`;
13
+ const external = href?.startsWith("http");
14
+
15
+ const unwrapParagraph = (html: string): string => {
16
+ const trimmed = html.trim();
17
+ const match = trimmed.match(/^<p>(?<content>[\s\S]*)<\/p>$/u);
18
+ return match?.groups?.content ?? trimmed;
19
+ };
20
+
21
+ const labelHtml = Astro.slots.has("default")
22
+ ? unwrapParagraph(await Astro.slots.render("default"))
23
+ : "";
24
+ ---
25
+
26
+ <span class="not-prose group relative inline-flex cursor-help items-baseline">
27
+ <span
28
+ aria-describedby={tooltipId}
29
+ class="underline decoration-dotted underline-offset-4"
30
+ data-blume-tooltip-trigger
31
+ tabindex="0"
32
+ set:html={labelHtml}
33
+ />
34
+ <span
35
+ class="absolute bottom-full left-1/2 z-20 mb-2 w-max max-w-xs -translate-x-1/2 rounded-blume border border-border bg-background p-3 text-start font-sans text-foreground text-sm shadow-lg"
36
+ data-blume-tooltip-panel
37
+ id={tooltipId}
38
+ role="tooltip"
39
+ >
40
+ {headline && <span class="mb-1 block font-semibold">{headline}</span>}
41
+ <span class="block text-muted-foreground leading-5">{tip}</span>
42
+ {
43
+ cta && href && (
44
+ <a
45
+ class="mt-2 inline-flex font-medium text-accent text-xs hover:underline"
46
+ href={href}
47
+ rel={external ? "noreferrer" : undefined}
48
+ target={external ? "_blank" : undefined}
49
+ >
50
+ {cta}
51
+ </a>
52
+ )
53
+ }
54
+ </span>
55
+ </span>
56
+
57
+ <style>
58
+ [data-blume-tooltip-panel] {
59
+ pointer-events: none;
60
+ visibility: hidden;
61
+ }
62
+
63
+ [data-blume-tooltip-trigger]:focus + [data-blume-tooltip-panel],
64
+ .group:hover [data-blume-tooltip-panel] {
65
+ pointer-events: auto;
66
+ visibility: visible;
67
+ }
68
+ </style>