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,60 @@
1
+ import { existsSync } from "node:fs";
2
+
3
+ import { defineCommand } from "citty";
4
+ import { join } from "pathe";
5
+
6
+ import { BlumeError } from "../../core/diagnostics.ts";
7
+ import { validateLinks } from "../../core/links.ts";
8
+ import { scanProject } from "../../core/project-graph.ts";
9
+ import type { Diagnostic } from "../../core/types.ts";
10
+ import { logger, reportDiagnostics } from "../log.ts";
11
+
12
+ export const validateCommand = defineCommand({
13
+ args: {
14
+ external: {
15
+ description: "Check external (HTTP) links over the network.",
16
+ type: "boolean",
17
+ },
18
+ strict: {
19
+ description: "Treat warnings as errors.",
20
+ type: "boolean",
21
+ },
22
+ },
23
+ meta: {
24
+ description: "Validate internal, anchor, asset, and external links.",
25
+ name: "validate",
26
+ },
27
+ async run({ args }) {
28
+ const root = process.cwd();
29
+ const diagnostics: Diagnostic[] = [];
30
+
31
+ try {
32
+ const project = await scanProject(root, { mode: "build" });
33
+ // Surface content/graph problems too: a page that fails to parse is a
34
+ // link-validation blind spot, so silently passing would be misleading.
35
+ diagnostics.push(...project.diagnostics);
36
+
37
+ const publicDir = join(root, "public");
38
+ diagnostics.push(
39
+ ...(await validateLinks(project.graph, {
40
+ checkExternal: Boolean(args.external),
41
+ publicDir: existsSync(publicDir) ? publicDir : null,
42
+ }))
43
+ );
44
+ } catch (error) {
45
+ if (error instanceof BlumeError) {
46
+ diagnostics.push(error.diagnostic);
47
+ } else {
48
+ throw error;
49
+ }
50
+ }
51
+
52
+ const hadErrors = reportDiagnostics(diagnostics, root);
53
+ if (diagnostics.length === 0) {
54
+ logger.success("No broken links found.");
55
+ }
56
+ if (hadErrors || (Boolean(args.strict) && diagnostics.length > 0)) {
57
+ process.exit(1);
58
+ }
59
+ },
60
+ });
@@ -0,0 +1,35 @@
1
+ import { defineCommand, runMain } from "citty";
2
+
3
+ import { getBlumeVersion } from "../core/version.ts";
4
+ import { addCommand } from "./commands/add.ts";
5
+ import { buildCommand } from "./commands/build.ts";
6
+ import { devCommand } from "./commands/dev.ts";
7
+ import { doctorCommand } from "./commands/doctor.ts";
8
+ import { ejectCommand } from "./commands/eject.ts";
9
+ import { initCommand } from "./commands/init.ts";
10
+ import { migrateCommand } from "./commands/migrate.ts";
11
+ import { previewCommand } from "./commands/preview.ts";
12
+ import { syncCommand } from "./commands/sync.ts";
13
+ import { validateCommand } from "./commands/validate.ts";
14
+
15
+ const main = defineCommand({
16
+ meta: {
17
+ description: "Markdown-first documentation powered by Astro and Vite.",
18
+ name: "blume",
19
+ version: getBlumeVersion(),
20
+ },
21
+ subCommands: {
22
+ add: addCommand,
23
+ build: buildCommand,
24
+ dev: devCommand,
25
+ doctor: doctorCommand,
26
+ eject: ejectCommand,
27
+ init: initCommand,
28
+ migrate: migrateCommand,
29
+ preview: previewCommand,
30
+ sync: syncCommand,
31
+ validate: validateCommand,
32
+ },
33
+ });
34
+
35
+ runMain(main);
package/src/cli/log.ts ADDED
@@ -0,0 +1,37 @@
1
+ import { consola } from "consola";
2
+
3
+ import {
4
+ countBySeverity,
5
+ formatDiagnostic,
6
+ hasErrors,
7
+ } from "../core/diagnostics.ts";
8
+ import type { Diagnostic } from "../core/types.ts";
9
+
10
+ export const logger = consola.withTag("blume");
11
+
12
+ /** Print a batch of diagnostics and return whether any were errors. */
13
+ export const reportDiagnostics = (
14
+ diagnostics: Diagnostic[],
15
+ root?: string
16
+ ): boolean => {
17
+ if (diagnostics.length === 0) {
18
+ return false;
19
+ }
20
+
21
+ for (const diagnostic of diagnostics) {
22
+ process.stderr.write(`${formatDiagnostic(diagnostic, root)}\n`);
23
+ }
24
+
25
+ const counts = countBySeverity(diagnostics);
26
+ const summary = [
27
+ counts.error ? `${counts.error} error(s)` : null,
28
+ counts.warning ? `${counts.warning} warning(s)` : null,
29
+ ]
30
+ .filter(Boolean)
31
+ .join(", ");
32
+ if (summary) {
33
+ process.stderr.write(`\n${summary}\n`);
34
+ }
35
+
36
+ return hasErrors(diagnostics);
37
+ };
@@ -0,0 +1,80 @@
1
+ import { generateRuntime } from "../astro/generate.ts";
2
+ import { BlumeError, hasErrors } from "../core/diagnostics.ts";
3
+ import { scanProject } from "../core/project-graph.ts";
4
+ import type { BlumeProject, BuildMode } from "../core/project-graph.ts";
5
+ import { serverFeatures } from "../core/server-features.ts";
6
+ import { logger, reportDiagnostics } from "./log.ts";
7
+
8
+ export interface PrepareOptions {
9
+ root: string;
10
+ mode?: BuildMode;
11
+ strict?: boolean;
12
+ /** Local dev server URL, used as the `deployment.site` fallback (dev only). */
13
+ devServerUrl?: string;
14
+ /** Render drafts and fetch unpublished CMS content. */
15
+ preview?: boolean;
16
+ /** Force remote sources to re-fetch instead of serving the cached snapshot. */
17
+ refresh?: boolean;
18
+ }
19
+
20
+ /**
21
+ * Scan the project, surface diagnostics, and (re)generate the `.blume` runtime.
22
+ * In strict mode, any error aborts. Returns the resolved project.
23
+ */
24
+ export const prepareProject = async (
25
+ options: PrepareOptions
26
+ ): Promise<BlumeProject> => {
27
+ let project: BlumeProject;
28
+ try {
29
+ project = await scanProject(options.root, {
30
+ devServerUrl: options.devServerUrl,
31
+ mode: options.mode,
32
+ preview: options.preview,
33
+ refresh: options.refresh,
34
+ });
35
+ } catch (error) {
36
+ if (error instanceof BlumeError) {
37
+ reportDiagnostics([error.diagnostic], options.root);
38
+ process.exit(1);
39
+ }
40
+ throw error;
41
+ }
42
+
43
+ // Hard gate: server-only features cannot ship in a static build.
44
+ if (
45
+ options.mode === "build" &&
46
+ project.config.deployment.output === "static"
47
+ ) {
48
+ const features = serverFeatures(project.config);
49
+ if (features.length > 0) {
50
+ reportDiagnostics(
51
+ [
52
+ {
53
+ code: "BLUME_SERVER_FEATURE_REQUIRED",
54
+ message: `${features.join(", ")} require server output, but deployment.output is "static".`,
55
+ severity: "error",
56
+ suggestion:
57
+ 'Set deployment: { output: "server", adapter: "vercel" } in blume.config.ts.',
58
+ },
59
+ ],
60
+ options.root
61
+ );
62
+ process.exit(1);
63
+ }
64
+ }
65
+
66
+ const hadErrors = reportDiagnostics(project.diagnostics, options.root);
67
+ if (hadErrors && options.strict) {
68
+ logger.error("Aborting due to errors (strict mode).");
69
+ process.exit(1);
70
+ }
71
+ if (hasErrors(project.diagnostics) && !options.strict) {
72
+ logger.warn("Continuing despite errors. Use --strict to fail the build.");
73
+ }
74
+
75
+ const { warnings } = await generateRuntime(project);
76
+ for (const warning of warnings) {
77
+ logger.warn(warning);
78
+ }
79
+ return project;
80
+ };
@@ -0,0 +1,99 @@
1
+ ---
2
+ import { resolveIcon } from "../theme/icons.ts";
3
+
4
+ interface Props {
5
+ name?: unknown;
6
+ icon?: unknown;
7
+ iconType?: string;
8
+ size?: number;
9
+ class?: string;
10
+ className?: string;
11
+ color?: string;
12
+ label?: string;
13
+ }
14
+
15
+ const {
16
+ color,
17
+ icon,
18
+ iconType,
19
+ name,
20
+ size = 16,
21
+ class: astroClass,
22
+ className,
23
+ label,
24
+ } = Astro.props;
25
+ const rawIcon = name ?? icon;
26
+ const customIcon =
27
+ rawIcon !== undefined && rawIcon !== null && typeof rawIcon !== "string"
28
+ ? rawIcon
29
+ : null;
30
+ const iconName = typeof rawIcon === "string" ? rawIcon : null;
31
+ const rawSvg =
32
+ iconName && /^\s*<svg[\s\S]*<\/svg>\s*$/u.test(iconName)
33
+ ? iconName.trim()
34
+ : null;
35
+ const isImageIcon = (value: string): boolean =>
36
+ /^(?:https?:\/\/|data:image\/|\/|\.{1,2}\/)/u.test(value) ||
37
+ /\.(?:avif|gif|jpe?g|png|svg|webp)$/iu.test(value);
38
+ const imageSrc = iconName && !rawSvg && isImageIcon(iconName) ? iconName : null;
39
+ const resolvedIcon =
40
+ iconName && !(imageSrc || rawSvg) ? resolveIcon(iconName, iconType) : null;
41
+ const markup = resolvedIcon?.markup;
42
+ const resolvedClass = astroClass ?? className;
43
+ const customStyle = `display:inline-flex;width:${size}px;height:${size}px;${
44
+ color ? `color:${color}` : ""
45
+ }`;
46
+ const style = color ? `color:${color}` : undefined;
47
+ ---
48
+
49
+ {
50
+ customIcon ? (
51
+ <span
52
+ aria-hidden={label ? undefined : "true"}
53
+ aria-label={label}
54
+ class={resolvedClass}
55
+ role={label ? "img" : undefined}
56
+ style={customStyle}
57
+ >
58
+ {customIcon}
59
+ </span>
60
+ ) : rawSvg ? (
61
+ <span
62
+ aria-hidden={label ? undefined : "true"}
63
+ aria-label={label}
64
+ class={resolvedClass}
65
+ role={label ? "img" : undefined}
66
+ set:html={rawSvg}
67
+ style={customStyle}
68
+ />
69
+ ) : imageSrc ? (
70
+ <img
71
+ alt={label ?? ""}
72
+ aria-hidden={label ? undefined : "true"}
73
+ class={resolvedClass}
74
+ height={size}
75
+ src={imageSrc}
76
+ width={size}
77
+ />
78
+ ) : (
79
+ markup && (
80
+ <svg
81
+ aria-hidden={label ? undefined : "true"}
82
+ aria-label={label}
83
+ class={resolvedClass}
84
+ fill="none"
85
+ height={size}
86
+ role={label ? "img" : undefined}
87
+ set:html={markup}
88
+ stroke="currentColor"
89
+ stroke-linecap="round"
90
+ stroke-linejoin="round"
91
+ stroke-width="2"
92
+ style={style}
93
+ viewBox="0 0 24 24"
94
+ width={size}
95
+ xmlns="http://www.w3.org/2000/svg"
96
+ />
97
+ )
98
+ )
99
+ }
@@ -0,0 +1,8 @@
1
+ ---
2
+ ---
3
+
4
+ <div
5
+ class="not-prose my-6 overflow-hidden rounded-blume border border-border [&>details:not(:first-of-type)]:border-border [&>details:not(:first-of-type)]:border-t"
6
+ >
7
+ <slot />
8
+ </div>
@@ -0,0 +1,121 @@
1
+ ---
2
+ import Icon from "../Icon.astro";
3
+
4
+ interface Props {
5
+ title: string;
6
+ description?: string;
7
+ defaultOpen?: boolean;
8
+ id?: string;
9
+ icon?: string;
10
+ iconType?: string;
11
+ }
12
+
13
+ const { defaultOpen = false, description, icon, iconType, id, title } =
14
+ Astro.props;
15
+ const slugify = (value: string): string =>
16
+ value
17
+ .toLowerCase()
18
+ .trim()
19
+ .replaceAll(/[^\w\s-]/gu, "")
20
+ .replaceAll(/[\s_]+/gu, "-")
21
+ .replaceAll(/-+/gu, "-")
22
+ .replaceAll(/^-|-$/gu, "");
23
+ const accordionId = id ?? slugify(title);
24
+ ---
25
+
26
+ <details
27
+ class="not-prose group"
28
+ data-blume-accordion
29
+ id={accordionId}
30
+ open={defaultOpen}
31
+ >
32
+ <summary
33
+ class="flex cursor-pointer list-none items-center justify-between p-4 font-medium [&::-webkit-details-marker]:hidden"
34
+ >
35
+ <span class="flex min-w-0 items-start gap-2">
36
+ {
37
+ icon && (
38
+ <Icon
39
+ class="mt-0.5 shrink-0 text-muted-foreground"
40
+ iconType={iconType}
41
+ name={icon}
42
+ size={16}
43
+ />
44
+ )
45
+ }
46
+ <span class="min-w-0">
47
+ <span class="block">{title}</span>
48
+ {
49
+ description && (
50
+ <span class="block font-normal text-muted-foreground text-sm">
51
+ {description}
52
+ </span>
53
+ )
54
+ }
55
+ </span>
56
+ </span>
57
+ <Icon
58
+ class="text-muted-foreground transition-transform group-open:rotate-180"
59
+ name="chevron-down"
60
+ size={16}
61
+ />
62
+ </summary>
63
+ <div class="px-4 pb-4 [&>:first-child]:mt-0! [&>:last-child]:mb-0!">
64
+ <slot />
65
+ </div>
66
+ </details>
67
+
68
+ <script>
69
+ const state = window as Window & { __blumeAccordionHash?: boolean };
70
+ if (!state.__blumeAccordionHash) {
71
+ state.__blumeAccordionHash = true;
72
+
73
+ const accordionFromHash = () => {
74
+ const id = decodeURIComponent(window.location.hash.slice(1));
75
+ if (!id) {
76
+ return null;
77
+ }
78
+ return document.getElementById(id)?.matches("[data-blume-accordion]")
79
+ ? (document.getElementById(id) as HTMLDetailsElement)
80
+ : null;
81
+ };
82
+
83
+ const openHashAccordion = () => {
84
+ const accordion = accordionFromHash();
85
+ if (!accordion) {
86
+ return;
87
+ }
88
+ accordion.open = true;
89
+ requestAnimationFrame(() => {
90
+ accordion.scrollIntoView({ block: "start" });
91
+ });
92
+ };
93
+
94
+ document.addEventListener(
95
+ "toggle",
96
+ (event) => {
97
+ const accordion = event.target;
98
+ if (
99
+ !(accordion instanceof HTMLDetailsElement) ||
100
+ !accordion.matches("[data-blume-accordion]") ||
101
+ !accordion.open ||
102
+ !accordion.id
103
+ ) {
104
+ return;
105
+ }
106
+
107
+ history.replaceState(null, "", `#${encodeURIComponent(accordion.id)}`);
108
+ },
109
+ true
110
+ );
111
+
112
+ window.addEventListener("hashchange", openHashAccordion);
113
+ if (document.readyState === "loading") {
114
+ document.addEventListener("DOMContentLoaded", openHashAccordion, {
115
+ once: true,
116
+ });
117
+ } else {
118
+ openHashAccordion();
119
+ }
120
+ }
121
+ </script>
@@ -0,0 +1,51 @@
1
+ ---
2
+ // Generate a <TypeTable> from a TypeScript interface or type alias. Point it at
3
+ // a file (`path`, relative to the project root) and a type `name`, or pass inline
4
+ // source via `type`. Extraction runs at build time through the TypeScript
5
+ // compiler API; failures degrade to an inline notice rather than breaking the
6
+ // build.
7
+ import { extractTypeTable } from "./auto-type-table.ts";
8
+ import TypeTable from "./TypeTable.astro";
9
+
10
+ interface Props {
11
+ name: string;
12
+ path?: string;
13
+ type?: string;
14
+ }
15
+
16
+ const { name, path, type: source } = Astro.props;
17
+
18
+ let typeMap: Record<
19
+ string,
20
+ { default?: string; description?: string; required: boolean; type: string }
21
+ > = {};
22
+ let error: string | undefined;
23
+
24
+ try {
25
+ const rows = await extractTypeTable({ name, path, source });
26
+ typeMap = Object.fromEntries(
27
+ rows.map((row) => [
28
+ row.name,
29
+ {
30
+ default: row.default,
31
+ description: row.description,
32
+ required: row.required,
33
+ type: row.type,
34
+ },
35
+ ])
36
+ );
37
+ } catch (cause) {
38
+ error = cause instanceof Error ? cause.message : String(cause);
39
+ }
40
+ ---
41
+
42
+ {
43
+ error ? (
44
+ <div class="not-prose my-6 rounded-blume border border-border px-3 py-2 text-muted-foreground text-sm">
45
+ Could not generate a type table for <code class="font-mono">{name}</code>:{" "}
46
+ {error}
47
+ </div>
48
+ ) : (
49
+ <TypeTable type={typeMap} />
50
+ )
51
+ }
@@ -0,0 +1,124 @@
1
+ ---
2
+ import Icon from "../Icon.astro";
3
+
4
+ type BadgeVariant = "default" | "accent" | "success" | "warning" | "danger";
5
+ type BadgeSize = "xs" | "sm" | "md" | "lg";
6
+ type BadgeShape = "rounded" | "pill";
7
+
8
+ interface Props {
9
+ variant?: BadgeVariant;
10
+ color?: string;
11
+ disabled?: boolean | string;
12
+ icon?: unknown;
13
+ iconType?: string;
14
+ shape?: BadgeShape;
15
+ size?: BadgeSize;
16
+ stroke?: boolean | string;
17
+ tooltip?: string;
18
+ class?: string;
19
+ className?: string;
20
+ }
21
+
22
+ const {
23
+ color,
24
+ disabled = false,
25
+ icon,
26
+ iconType,
27
+ shape = "rounded",
28
+ size = "md",
29
+ stroke = false,
30
+ tooltip,
31
+ variant = "default",
32
+ class: astroClass,
33
+ className,
34
+ } = Astro.props;
35
+
36
+ const activeStroke = stroke === true || stroke === "true";
37
+ const isDisabled = disabled === true || disabled === "true";
38
+ const colorName =
39
+ color ??
40
+ {
41
+ accent: "blue",
42
+ danger: "red",
43
+ default: "gray",
44
+ success: "green",
45
+ warning: "orange",
46
+ }[variant];
47
+ const customColor = colorName.startsWith("#") ? colorName : null;
48
+
49
+ const filledColorClass: Record<string, string> = {
50
+ blue: "bg-blue-500/15 text-blue-700 dark:text-blue-300",
51
+ gray: "bg-muted text-muted-foreground",
52
+ green: "bg-green-500/15 text-green-700 dark:text-green-300",
53
+ orange: "bg-orange-500/15 text-orange-700 dark:text-orange-300",
54
+ purple: "bg-purple-500/15 text-purple-700 dark:text-purple-300",
55
+ red: "bg-red-500/15 text-red-700 dark:text-red-300",
56
+ surface: "bg-background text-muted-foreground ring-1 ring-border",
57
+ "surface-destructive":
58
+ "bg-background text-red-700 ring-1 ring-red-500/30 dark:text-red-300",
59
+ white: "bg-white text-muted-foreground ring-1 ring-border dark:bg-white/10",
60
+ "white-destructive":
61
+ "bg-white text-red-700 ring-1 ring-red-500/30 dark:bg-white/10 dark:text-red-300",
62
+ yellow: "bg-yellow-500/20 text-yellow-800 dark:text-yellow-300",
63
+ };
64
+
65
+ const strokeColorClass: Record<string, string> = {
66
+ blue: "border-blue-500/40 text-blue-700 dark:text-blue-300",
67
+ gray: "border-border text-muted-foreground",
68
+ green: "border-green-500/40 text-green-700 dark:text-green-300",
69
+ orange: "border-orange-500/40 text-orange-700 dark:text-orange-300",
70
+ purple: "border-purple-500/40 text-purple-700 dark:text-purple-300",
71
+ red: "border-red-500/40 text-red-700 dark:text-red-300",
72
+ surface: "border-border text-muted-foreground",
73
+ "surface-destructive": "border-red-500/40 text-red-700 dark:text-red-300",
74
+ white: "border-border text-muted-foreground",
75
+ "white-destructive": "border-red-500/40 text-red-700 dark:text-red-300",
76
+ yellow: "border-yellow-500/40 text-yellow-800 dark:text-yellow-300",
77
+ };
78
+
79
+ const sizeClass: Record<BadgeSize, string> = {
80
+ lg: "gap-1.5 px-2.5 py-1 text-sm",
81
+ md: "gap-1 px-2 py-0.5 text-xs",
82
+ sm: "gap-1 px-1.5 py-0.5 text-[0.6875rem]",
83
+ xs: "gap-0.5 px-1.5 py-px text-[0.625rem]",
84
+ };
85
+
86
+ const iconSize: Record<BadgeSize, number> = {
87
+ lg: 14,
88
+ md: 12,
89
+ sm: 11,
90
+ xs: 10,
91
+ };
92
+
93
+ const shapeClass: Record<BadgeShape, string> = {
94
+ pill: "rounded-full",
95
+ rounded: "rounded-md",
96
+ };
97
+
98
+ const resolvedClass = astroClass ?? className;
99
+ const colorClass = activeStroke
100
+ ? strokeColorClass[colorName] ?? strokeColorClass.gray
101
+ : filledColorClass[colorName] ?? filledColorClass.gray;
102
+ const customStyle = customColor
103
+ ? activeStroke
104
+ ? `border-color:${customColor};color:${customColor}`
105
+ : `background:${customColor}20;color:${customColor}`
106
+ : undefined;
107
+ ---
108
+
109
+ <span
110
+ class:list={[
111
+ "not-prose inline-flex w-fit items-center border font-medium leading-5",
112
+ activeStroke ? "bg-transparent" : "border-transparent",
113
+ colorClass,
114
+ sizeClass[size],
115
+ shapeClass[shape],
116
+ isDisabled ? "opacity-50" : "",
117
+ resolvedClass,
118
+ ]}
119
+ style={customStyle}
120
+ title={tooltip}
121
+ >
122
+ {icon && <Icon icon={icon} iconType={iconType} size={iconSize[size]} />}
123
+ <slot />
124
+ </span>
@@ -0,0 +1,73 @@
1
+ ---
2
+ import Icon from "../Icon.astro";
3
+
4
+ type CalloutType =
5
+ | "info"
6
+ | "note"
7
+ | "tip"
8
+ | "success"
9
+ | "check"
10
+ | "warning"
11
+ | "danger";
12
+
13
+ interface Props {
14
+ type?: CalloutType;
15
+ title?: string;
16
+ icon?: unknown;
17
+ iconType?: string;
18
+ color?: string;
19
+ }
20
+
21
+ const { color, icon, iconType, type = "info", title } = Astro.props;
22
+
23
+ const iconByType: Record<CalloutType, string> = {
24
+ check: "circle-check",
25
+ danger: "circle-x",
26
+ info: "info",
27
+ note: "info",
28
+ success: "circle-check",
29
+ tip: "lightbulb",
30
+ warning: "triangle-alert",
31
+ };
32
+
33
+ const variantClass: Record<CalloutType, string> = {
34
+ check: "border-green-500/25 bg-green-500/10",
35
+ danger: "border-red-500/25 bg-red-500/10",
36
+ info: "border-blue-500/25 bg-blue-500/10",
37
+ note: "border-border bg-muted",
38
+ success: "border-green-500/25 bg-green-500/10",
39
+ tip: "border-accent/25 bg-accent/10",
40
+ warning: "border-amber-500/25 bg-amber-500/10",
41
+ };
42
+
43
+ const iconClass: Record<CalloutType, string> = {
44
+ check: "text-green-600 dark:text-green-400",
45
+ danger: "text-red-600 dark:text-red-400",
46
+ info: "text-blue-600 dark:text-blue-400",
47
+ note: "text-muted-foreground/70",
48
+ success: "text-green-600 dark:text-green-400",
49
+ tip: "text-accent",
50
+ warning: "text-amber-600 dark:text-amber-400",
51
+ };
52
+ ---
53
+
54
+ <aside
55
+ class:list={[
56
+ "not-prose my-5 flex flex-row items-start gap-2 rounded-blume border px-4 py-3 font-sans text-muted-foreground text-sm leading-6 [&_a]:underline [&_a]:decoration-current/50 [&_code]:font-mono",
57
+ color ? "bg-background" : variantClass[type],
58
+ ]}
59
+ style={color ? `border:1px solid ${color};color:${color}` : undefined}
60
+ >
61
+ <span class:list={["mt-0.5 shrink-0", color ? "" : iconClass[type]]}>
62
+ <Icon
63
+ color={color}
64
+ icon={icon ?? iconByType[type]}
65
+ iconType={iconType}
66
+ size={16}
67
+ />
68
+ </span>
69
+ <div class="min-w-0 flex-1 [&>:first-child]:mt-0! [&>:last-child]:mb-0!">
70
+ {title && <p class="mb-1 font-semibold text-foreground">{title}</p>}
71
+ <slot />
72
+ </div>
73
+ </aside>