blume 0.5.3 → 0.5.4

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 (113) hide show
  1. package/dist/cli/index.js +2985 -6883
  2. package/dist/cli/index.js.map +27 -58
  3. package/dist/types/core/config.d.ts +0 -8
  4. package/dist/types/core/data.d.ts +2 -2
  5. package/dist/types/core/schema.d.ts +58 -453
  6. package/dist/types/core/types.d.ts +1 -7
  7. package/docs/advanced/meta.ts +1 -8
  8. package/docs/advanced/skills.mdx +28 -0
  9. package/docs/configuration/ai.mdx +2 -0
  10. package/docs/configuration/index.mdx +13 -17
  11. package/docs/configuration/theming.mdx +2 -10
  12. package/docs/content/components.mdx +2 -53
  13. package/docs/content/i18n.mdx +0 -4
  14. package/docs/content/syntax.mdx +1 -1
  15. package/docs/index.mdx +0 -1
  16. package/docs/reference/cli.mdx +12 -13
  17. package/package.json +4 -6
  18. package/skills/blume/SKILL.md +71 -0
  19. package/skills/blume-update-docs/SKILL.md +52 -0
  20. package/skills/blume-update-docs/references/audit-checklist.md +46 -0
  21. package/src/astro/generate.ts +15 -12
  22. package/src/astro/integration.ts +0 -21
  23. package/src/astro/templates.ts +9 -16
  24. package/src/cli/commands/dev.ts +0 -6
  25. package/src/cli/commands/validate.ts +0 -2
  26. package/src/cli/index.ts +0 -2
  27. package/src/components/BlumePage.astro +0 -6
  28. package/src/components/Icon.astro +1 -12
  29. package/src/components/content/AccordionItem.astro +3 -6
  30. package/src/components/content/Badge.astro +1 -3
  31. package/src/components/content/Callout.astro +3 -9
  32. package/src/components/content/Card.astro +2 -3
  33. package/src/components/content/ColorItem.astro +2 -2
  34. package/src/components/content/Column.astro +1 -1
  35. package/src/components/content/Prompt.astro +1 -1
  36. package/src/components/content/Step.astro +3 -4
  37. package/src/components/content/Tab.astro +2 -3
  38. package/src/components/content/TypeTable.astro +5 -5
  39. package/src/components/content/Update.astro +1 -1
  40. package/src/components/layout/Header.astro +26 -3
  41. package/src/components/layout/Logo.astro +5 -1
  42. package/src/components/layout/NavSelector.astro +1 -1
  43. package/src/components/layout/NavTree.astro +14 -14
  44. package/src/components/layout/PageLayout.astro +42 -0
  45. package/src/components/layout/ReferenceLayout.astro +1 -0
  46. package/src/components/layout/RootLayout.astro +31 -2
  47. package/src/components/layout/Search.astro +5 -5
  48. package/src/components/layout/nav-utils.ts +9 -4
  49. package/src/components/openapi/ApiOverview.astro +4 -50
  50. package/src/components/openapi/ApiTagOperations.astro +42 -0
  51. package/src/core/builtin-tags.ts +1 -3
  52. package/src/core/config.ts +5 -28
  53. package/src/core/data.ts +2 -2
  54. package/src/core/graph.ts +1 -4
  55. package/src/core/links.ts +5 -19
  56. package/src/core/meta.ts +1 -1
  57. package/src/core/navigation.ts +0 -5
  58. package/src/core/project-graph.ts +0 -5
  59. package/src/core/schema.ts +13 -90
  60. package/src/core/sources/resolve.ts +0 -13
  61. package/src/core/sources/watch.ts +3 -2
  62. package/src/core/types.ts +1 -8
  63. package/src/openapi/parse.ts +197 -14
  64. package/src/openapi/render-mdx.ts +44 -10
  65. package/src/openapi/source.ts +19 -2
  66. package/src/theme/entry.ts +45 -17
  67. package/src/theme/icons.ts +18 -109
  68. package/src/theme/palette.ts +4 -37
  69. package/src/theme/twoslash.ts +6 -1
  70. package/dist/types/core/bridge.d.ts +0 -24
  71. package/dist/types/core/package-json.d.ts +0 -12
  72. package/dist/types/migrate/mintlify/assets.d.ts +0 -8
  73. package/dist/types/migrate/mintlify/config.d.ts +0 -16
  74. package/dist/types/migrate/mintlify/i18n.d.ts +0 -7
  75. package/dist/types/migrate/shared.d.ts +0 -153
  76. package/docs/advanced/bridge.mdx +0 -76
  77. package/docs/advanced/migrate.mdx +0 -124
  78. package/src/astro/static-assets.ts +0 -124
  79. package/src/cli/commands/migrate.ts +0 -39
  80. package/src/components/content/ApiField.astro +0 -75
  81. package/src/components/content/ParamField.astro +0 -39
  82. package/src/components/content/RequestField.astro +0 -23
  83. package/src/components/content/ResponseField.astro +0 -23
  84. package/src/components/content/Warning.astro +0 -9
  85. package/src/core/assets.ts +0 -31
  86. package/src/core/bridge.ts +0 -102
  87. package/src/core/sources/mintlify.ts +0 -190
  88. package/src/migrate/fumadocs/config.ts +0 -155
  89. package/src/migrate/fumadocs/content.ts +0 -376
  90. package/src/migrate/fumadocs/frontmatter.ts +0 -18
  91. package/src/migrate/fumadocs/groups.ts +0 -237
  92. package/src/migrate/fumadocs/index.ts +0 -355
  93. package/src/migrate/fumadocs/meta.ts +0 -244
  94. package/src/migrate/migrate.ts +0 -53
  95. package/src/migrate/mintlify/assets.ts +0 -46
  96. package/src/migrate/mintlify/config.ts +0 -954
  97. package/src/migrate/mintlify/content.ts +0 -120
  98. package/src/migrate/mintlify/frontmatter.ts +0 -126
  99. package/src/migrate/mintlify/i18n.ts +0 -51
  100. package/src/migrate/mintlify/icons.ts +0 -128
  101. package/src/migrate/mintlify/index.ts +0 -459
  102. package/src/migrate/mintlify/snippets.ts +0 -315
  103. package/src/migrate/mintlify/transform.ts +0 -82
  104. package/src/migrate/nextra/content.ts +0 -46
  105. package/src/migrate/nextra/frontmatter.ts +0 -40
  106. package/src/migrate/nextra/index.ts +0 -389
  107. package/src/migrate/nextra/meta.ts +0 -266
  108. package/src/migrate/shared.ts +0 -801
  109. package/src/migrate/starlight/config.ts +0 -455
  110. package/src/migrate/starlight/content.ts +0 -75
  111. package/src/migrate/starlight/frontmatter.ts +0 -111
  112. package/src/migrate/starlight/i18n.ts +0 -54
  113. package/src/migrate/starlight/index.ts +0 -131
@@ -4,7 +4,6 @@ import { dirname, isAbsolute, join, relative } from "pathe";
4
4
 
5
5
  import { askBackendRuntimeDep } from "../ai/ask.ts";
6
6
  import type { AskBackend } from "../ai/ask.ts";
7
- import { resolveAssetMounts } from "../core/assets.ts";
8
7
  import type { ResolvedConfig } from "../core/schema.ts";
9
8
  import type { ProjectContext } from "../core/types.ts";
10
9
  import { hasScalarReferences } from "../openapi/references.ts";
@@ -313,12 +312,10 @@ export const astroConfigTemplate = (options: {
313
312
  if (needsSvelte) {
314
313
  integrations.push("svelte()");
315
314
  }
316
- // Always mounted: injects user pages (a no-op when there are none), serves
317
- // `content.assets` mounts, and wires up dev-server `Accept: text/markdown`
318
- // negotiation over the content routes.
319
- const assets = resolveAssetMounts(context.root, config.content.assets);
315
+ // Always mounted: injects user pages (a no-op when there are none) and wires
316
+ // up dev-server `Accept: text/markdown` negotiation over the content routes.
320
317
  integrations.push(
321
- `blumeIntegration(${JSON.stringify({ assets, base: deployment.base, contentRoutes, pages })})`
318
+ `blumeIntegration(${JSON.stringify({ base: deployment.base, contentRoutes, pages })})`
322
319
  );
323
320
 
324
321
  return `// Generated by Blume. Do not edit; this file is recreated on each run.
@@ -403,8 +400,8 @@ export const contentConfigTemplate = (options: {
403
400
  stagedBase?: string;
404
401
  /**
405
402
  * Whether any filesystem (non-staged) source feeds the `docs` collection.
406
- * When false (e.g. Mintlify bridge mode, where every page is staged), the
407
- * collection globs nothing — see below.
403
+ * When false (e.g. an all-staged project where every page is materialized by
404
+ * a non-filesystem source), the collection globs nothing — see below.
408
405
  */
409
406
  filesystem?: boolean;
410
407
  }): string => {
@@ -423,8 +420,8 @@ export const contentConfigTemplate = (options: {
423
420
 
424
421
  // With no filesystem source, no route renders through `docs`, so glob nothing.
425
422
  // Beyond skipping wasted work, this is the only thing that keeps Astro's
426
- // content-layer *watcher* out of `.blume/`: bridge mode roots the collection
427
- // at the project dir (which contains `.blume/.astro/fonts`, rewritten on every
423
+ // content-layer *watcher* out of `.blume/`: an all-staged project roots the
424
+ // collection at the project dir (which contains `.blume/.astro/fonts`, rewritten on every
428
425
  // request), and the watcher's match test is `picomatch.isMatch(path, pattern)`
429
426
  // — with array-OR semantics, any `!ignored/**` negation *matches* unrelated
430
427
  // files, so negative patterns can't exclude a subtree there. An empty pattern
@@ -980,10 +977,7 @@ import FileTree from "blume/components/content/FileTree.astro";
980
977
  import Frame from "blume/components/content/Frame.astro";
981
978
  import GithubInfo from "blume/components/content/GithubInfo.astro";
982
979
  import Panel from "blume/components/content/Panel.astro";
983
- import ParamField from "blume/components/content/ParamField.astro";
984
980
  import Prompt from "blume/components/content/Prompt.astro";
985
- import RequestField from "blume/components/content/RequestField.astro";
986
- import ResponseField from "blume/components/content/ResponseField.astro";
987
981
  import Step from "blume/components/content/Step.astro";
988
982
  import Steps from "blume/components/content/Steps.astro";
989
983
  import Tab from "blume/components/content/Tab.astro";
@@ -998,6 +992,7 @@ import Visibility from "blume/components/content/Visibility.astro";
998
992
  import YouTube from "blume/components/content/YouTube.astro";
999
993
  import Icon from "blume/components/Icon.astro";
1000
994
  import ApiOverview from "blume/components/openapi/ApiOverview.astro";
995
+ import ApiTagOperations from "blume/components/openapi/ApiTagOperations.astro";
1001
996
  import Operation from "blume/components/openapi/Operation.astro";
1002
997
  ${mathImport}import { mdxComponents as userMdx, layoutOverrides } from "../generated/components.ts";
1003
998
  import { islandComponents } from "../generated/islands.ts";
@@ -1016,6 +1011,7 @@ const components = {
1016
1011
  Accordion,
1017
1012
  AccordionItem,
1018
1013
  ApiOverview,
1014
+ ApiTagOperations,
1019
1015
  AutoTypeTable,
1020
1016
  Badge,
1021
1017
  Callout,
@@ -1035,10 +1031,7 @@ const components = {
1035
1031
  Icon,
1036
1032
  Operation,
1037
1033
  Panel,
1038
- ParamField,
1039
1034
  Prompt,
1040
- RequestField,
1041
- ResponseField,
1042
1035
  Step,
1043
1036
  Steps,
1044
1037
  Tab,
@@ -56,12 +56,6 @@ export const devCommand = defineCommand({
56
56
  strict: args.strict,
57
57
  });
58
58
 
59
- if (project.bridge) {
60
- logger.info(
61
- 'Detected docs.json — running in Mintlify bridge mode (no migration). Run "blume migrate mintlify" to convert permanently.'
62
- );
63
- }
64
-
65
59
  // Claim the shared `.blume` dir so a concurrent build/eject/sync refuses
66
60
  // rather than regenerating or deleting it out from under this server. A
67
61
  // second dev server would fight over the same generated tree the same
@@ -3,7 +3,6 @@ import { existsSync } from "node:fs";
3
3
  import { defineCommand } from "citty";
4
4
  import { join } from "pathe";
5
5
 
6
- import { resolveAssetMounts } from "../../core/assets.ts";
7
6
  import { BlumeError } from "../../core/diagnostics.ts";
8
7
  import { validateLinks } from "../../core/links.ts";
9
8
  import { scanProject } from "../../core/project-graph.ts";
@@ -48,7 +47,6 @@ export const validateCommand = defineCommand({
48
47
  const publicDir = join(root, "public");
49
48
  diagnostics.push(
50
49
  ...(await validateLinks(project.graph, {
51
- assetMounts: resolveAssetMounts(root, project.config.content.assets),
52
50
  checkExternal: Boolean(args.external),
53
51
  publicDir: existsSync(publicDir) ? publicDir : null,
54
52
  redirects: project.config.redirects,
package/src/cli/index.ts CHANGED
@@ -8,7 +8,6 @@ import { devCommand } from "./commands/dev.ts";
8
8
  import { doctorCommand } from "./commands/doctor.ts";
9
9
  import { ejectCommand } from "./commands/eject.ts";
10
10
  import { initCommand } from "./commands/init.ts";
11
- import { migrateCommand } from "./commands/migrate.ts";
12
11
  import { previewCommand } from "./commands/preview.ts";
13
12
  import { syncCommand } from "./commands/sync.ts";
14
13
  import { validateCommand } from "./commands/validate.ts";
@@ -29,7 +28,6 @@ const main = defineCommand({
29
28
  doctor: doctorCommand,
30
29
  eject: ejectCommand,
31
30
  init: initCommand,
32
- migrate: migrateCommand,
33
31
  preview: previewCommand,
34
32
  sync: syncCommand,
35
33
  validate: validateCommand,
@@ -38,10 +38,7 @@ import FileTree from "./content/FileTree.astro";
38
38
  import Frame from "./content/Frame.astro";
39
39
  import GithubInfo from "./content/GithubInfo.astro";
40
40
  import Panel from "./content/Panel.astro";
41
- import ParamField from "./content/ParamField.astro";
42
41
  import Prompt from "./content/Prompt.astro";
43
- import RequestField from "./content/RequestField.astro";
44
- import ResponseField from "./content/ResponseField.astro";
45
42
  import Step from "./content/Step.astro";
46
43
  import Steps from "./content/Steps.astro";
47
44
  import Tab from "./content/Tab.astro";
@@ -90,10 +87,7 @@ const components = {
90
87
  GithubInfo,
91
88
  Icon,
92
89
  Panel,
93
- ParamField,
94
90
  Prompt,
95
- RequestField,
96
- ResponseField,
97
91
  Step,
98
92
  Steps,
99
93
  Tab,
@@ -1,13 +1,9 @@
1
1
  ---
2
- import data from "blume:data";
3
2
  import { resolveIcon } from "../theme/icons.ts";
4
3
 
5
4
  interface Props {
6
5
  name?: unknown;
7
6
  icon?: unknown;
8
- iconType?: string;
9
- /** Icon library for a bare name; defaults to the project's `icons.library`. */
10
- library?: string;
11
7
  size?: number;
12
8
  class?: string;
13
9
  className?: string;
@@ -18,8 +14,6 @@ interface Props {
18
14
  const {
19
15
  color,
20
16
  icon,
21
- iconType,
22
- library,
23
17
  name,
24
18
  size = 16,
25
19
  class: astroClass,
@@ -41,12 +35,7 @@ const isImageIcon = (value: string): boolean =>
41
35
  /\.(?:avif|gif|jpe?g|png|svg|webp)$/iu.test(value);
42
36
  const imageSrc = iconName && !rawSvg && isImageIcon(iconName) ? iconName : null;
43
37
  const resolvedIcon =
44
- iconName && !(imageSrc || rawSvg)
45
- ? resolveIcon(iconName, {
46
- iconType,
47
- library: library ?? data.config.icons.library,
48
- })
49
- : null;
38
+ iconName && !(imageSrc || rawSvg) ? resolveIcon(iconName) : null;
50
39
  const resolvedClass = astroClass ?? className;
51
40
  const customStyle = `display:inline-flex;width:${size}px;height:${size}px;${
52
41
  color ? `color:${color}` : ""
@@ -7,11 +7,9 @@ interface Props {
7
7
  defaultOpen?: boolean;
8
8
  id?: string;
9
9
  icon?: string;
10
- iconType?: string;
11
10
  }
12
11
 
13
- const { defaultOpen = false, description, icon, iconType, id, title } =
14
- Astro.props;
12
+ const { defaultOpen = false, description, icon, id, title } = Astro.props;
15
13
  const slugify = (value: string): string =>
16
14
  value
17
15
  .toLowerCase()
@@ -32,18 +30,17 @@ const accordionId = id ?? slugify(title);
32
30
  <summary
33
31
  class="flex cursor-pointer list-none items-center justify-between p-4 font-medium [&::-webkit-details-marker]:hidden"
34
32
  >
35
- <span class="flex min-w-0 items-start gap-2">
33
+ <span class="flex items-start gap-2">
36
34
  {
37
35
  icon && (
38
36
  <Icon
39
37
  class="mt-0.5 shrink-0 text-muted-foreground"
40
- iconType={iconType}
41
38
  name={icon}
42
39
  size={16}
43
40
  />
44
41
  )
45
42
  }
46
- <span class="min-w-0">
43
+ <span>
47
44
  <span class="block">{title}</span>
48
45
  {
49
46
  description && (
@@ -10,7 +10,6 @@ interface Props {
10
10
  color?: string;
11
11
  disabled?: boolean | string;
12
12
  icon?: unknown;
13
- iconType?: string;
14
13
  shape?: BadgeShape;
15
14
  size?: BadgeSize;
16
15
  stroke?: boolean | string;
@@ -23,7 +22,6 @@ const {
23
22
  color,
24
23
  disabled = false,
25
24
  icon,
26
- iconType,
27
25
  shape = "rounded",
28
26
  size = "md",
29
27
  stroke = false,
@@ -119,6 +117,6 @@ const customStyle = customColor
119
117
  style={customStyle}
120
118
  title={tooltip}
121
119
  >
122
- {icon && <Icon icon={icon} iconType={iconType} size={iconSize[size]} />}
120
+ {icon && <Icon icon={icon} size={iconSize[size]} />}
123
121
  <slot />
124
122
  </span>
@@ -14,11 +14,10 @@ interface Props {
14
14
  type?: CalloutType;
15
15
  title?: string;
16
16
  icon?: unknown;
17
- iconType?: string;
18
17
  color?: string;
19
18
  }
20
19
 
21
- const { color, icon, iconType, type = "info", title } = Astro.props;
20
+ const { color, icon, type = "info", title } = Astro.props;
22
21
 
23
22
  const iconByType: Record<CalloutType, string> = {
24
23
  check: "circle-check",
@@ -59,14 +58,9 @@ const iconClass: Record<CalloutType, string> = {
59
58
  style={color ? `border:1px solid ${color};color:${color}` : undefined}
60
59
  >
61
60
  <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
- />
61
+ <Icon color={color} icon={icon ?? iconByType[type]} size={16} />
68
62
  </span>
69
- <div class="min-w-0 flex-1 [&>:first-child]:mt-0! [&>:last-child]:mb-0!">
63
+ <div class="flex-1 [&>:first-child]:mt-0! [&>:last-child]:mb-0!">
70
64
  {title && <p class="mb-1 font-semibold text-foreground">{title}</p>}
71
65
  <slot />
72
66
  </div>
@@ -5,7 +5,6 @@ interface Props {
5
5
  title?: string;
6
6
  href?: string;
7
7
  icon?: string;
8
- iconType?: string;
9
8
  img?: string;
10
9
  horizontal?: boolean | string;
11
10
  cta?: string;
@@ -14,7 +13,7 @@ interface Props {
14
13
  color?: string;
15
14
  }
16
15
 
17
- const { arrow, color, cta, horizontal, href, icon, iconType, img, title, type } =
16
+ const { arrow, color, cta, horizontal, href, icon, img, title, type } =
18
17
  Astro.props;
19
18
  const Tag = href ? "a" : "div";
20
19
  const external = href?.startsWith("http");
@@ -75,7 +74,7 @@ const variantClass = {
75
74
  {
76
75
  iconName && (
77
76
  <div class:list={["mb-2.5", color ? "" : type ? iconClass[type] : "text-accent"]}>
78
- <Icon color={color} iconType={iconType} name={iconName} size={20} />
77
+ <Icon color={color} name={iconName} size={20} />
79
78
  </div>
80
79
  )
81
80
  }
@@ -21,7 +21,7 @@ const displayValue =
21
21
 
22
22
  <button
23
23
  aria-label={`Copy ${name} color ${displayValue}`}
24
- class="group flex w-full min-w-0 cursor-pointer flex-col gap-2.5 text-center focus:outline-none"
24
+ class="group flex w-full cursor-pointer flex-col gap-2.5 text-center focus:outline-none"
25
25
  data-blume-color-copy={displayValue}
26
26
  style={`--blume-color-light:${lightValue};--blume-color-dark:${darkValue};`}
27
27
  type="button"
@@ -32,7 +32,7 @@ const displayValue =
32
32
  style="background:var(--blume-color-light)"
33
33
  >
34
34
  </span>
35
- <span class="block min-w-0 leading-tight">
35
+ <span class="block leading-tight">
36
36
  <span class="block truncate font-semibold text-foreground text-sm">
37
37
  {name}
38
38
  </span>
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  ---
3
3
 
4
- <div class="min-w-0">
4
+ <div>
5
5
  <slot />
6
6
  </div>
@@ -45,7 +45,7 @@ const secondaryButton =
45
45
  >
46
46
  <div class="flex items-center justify-between gap-3 px-4 py-3">
47
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"
48
+ class="m-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
49
  data-blume-prompt-description
50
50
  set:html={descriptionHtml}
51
51
  />
@@ -4,11 +4,10 @@ import { hasIcon } from "../../theme/icons.ts";
4
4
 
5
5
  interface Props {
6
6
  icon?: string;
7
- iconType?: string;
8
7
  title?: string;
9
8
  }
10
9
 
11
- const { icon, iconType, title } = Astro.props;
10
+ const { icon, title } = Astro.props;
12
11
  const markerClass =
13
12
  "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
13
  const isImageIcon = (value: string): boolean =>
@@ -18,14 +17,14 @@ const canRenderIcon =
18
17
  icon !== undefined &&
19
18
  (/^\s*<svg[\s\S]*<\/svg>\s*$/u.test(icon) ||
20
19
  isImageIcon(icon) ||
21
- hasIcon(icon, { iconType }));
20
+ hasIcon(icon));
22
21
  ---
23
22
 
24
23
  <div
25
24
  class="relative [counter-increment:blume-step]"
26
25
  >
27
26
  <span class:list={[markerClass, !canRenderIcon && "before:[content:counter(blume-step)]"]}>
28
- {canRenderIcon && <Icon iconType={iconType} name={icon} size={14} />}
27
+ {canRenderIcon && <Icon name={icon} size={14} />}
29
28
  </span>
30
29
  {title && <p class="mb-1.5 font-semibold">{title}</p>}
31
30
  <div class="[&>:first-child]:mt-0! [&>:last-child]:mb-0!">
@@ -4,13 +4,12 @@ import Icon from "../Icon.astro";
4
4
  interface Props {
5
5
  class?: string;
6
6
  icon?: string;
7
- iconType?: string;
8
7
  id?: string;
9
8
  style?: string;
10
9
  title: string;
11
10
  }
12
11
 
13
- const { class: className, icon, iconType, id, style, title } = Astro.props;
12
+ const { class: className, icon, id, style, title } = Astro.props;
14
13
  const slugify = (value: string): string =>
15
14
  value
16
15
  .toLowerCase()
@@ -39,7 +38,7 @@ const tabId = id ?? slugify(title);
39
38
  {
40
39
  icon && (
41
40
  <template data-blume-tab-icon>
42
- <Icon class="shrink-0" iconType={iconType} name={icon} size={14} />
41
+ <Icon class="shrink-0" name={icon} size={14} />
43
42
  </template>
44
43
  )
45
44
  }
@@ -37,19 +37,19 @@ const columns = "grid grid-cols-[1fr_2fr_auto] items-center gap-4";
37
37
  {entries.map(([name, info]) => (
38
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
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">
40
+ <code class="break-words font-mono text-accent text-xs">
41
41
  {name}
42
42
  {info.required ? "" : "?"}
43
43
  </code>
44
44
  {info.typeDescriptionLink ? (
45
45
  <a
46
- class="min-w-0 break-words font-mono text-foreground text-xs underline-offset-2 hover:underline"
46
+ class="break-words font-mono text-foreground text-xs underline-offset-2 hover:underline"
47
47
  href={info.typeDescriptionLink}
48
48
  >
49
49
  {info.type}
50
50
  </a>
51
51
  ) : (
52
- <code class="min-w-0 break-words font-mono text-foreground text-xs">
52
+ <code class="break-words font-mono text-foreground text-xs">
53
53
  {info.type}
54
54
  </code>
55
55
  )}
@@ -81,7 +81,7 @@ const columns = "grid grid-cols-[1fr_2fr_auto] items-center gap-4";
81
81
  <div class="space-y-1.5">
82
82
  <div class={columns}>
83
83
  <span class="text-muted-foreground">Type</span>
84
- <code class="min-w-0 break-words font-mono text-foreground">
84
+ <code class="break-words font-mono text-foreground">
85
85
  {info.type}
86
86
  </code>
87
87
  <span aria-hidden="true" class="size-3.5" />
@@ -89,7 +89,7 @@ const columns = "grid grid-cols-[1fr_2fr_auto] items-center gap-4";
89
89
  {info.default && (
90
90
  <div class={columns}>
91
91
  <span class="text-muted-foreground">Default</span>
92
- <code class="min-w-0 break-words font-mono text-foreground">
92
+ <code class="break-words font-mono text-foreground">
93
93
  {info.default}
94
94
  </code>
95
95
  <span aria-hidden="true" class="size-3.5" />
@@ -60,7 +60,7 @@ const tagList = Array.isArray(tags) ? tags : tags ? [tags] : [];
60
60
  )
61
61
  }
62
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!">
63
+ <div class="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
64
  <slot />
65
65
  </div>
66
66
  </article>
@@ -18,6 +18,7 @@ interface Props {
18
18
  dark?: string;
19
19
  alt: string;
20
20
  href: string;
21
+ text?: string;
21
22
  } | null;
22
23
  navigation: Navigation;
23
24
  route: string;
@@ -57,6 +58,19 @@ const {
57
58
  const LogoSlot = resolveSlot(layout.Logo, Logo);
58
59
  const SearchSlot = resolveSlot(layout.Search, Search);
59
60
 
61
+ // The mobile nav-toggle opens a drawer of navigation. On docs pages that drawer
62
+ // is the sidebar tree (`hasSidebar`); on chrome-only pages (a landing page via
63
+ // PageLayout) it's a tabs-only drawer the layout renders — so the button is also
64
+ // needed whenever there are tabs to reveal.
65
+ const showNavToggle = hasSidebar || navigation.tabs.length > 0;
66
+ // Where the header's inline tab bar appears. With a sidebar it shares the `md`
67
+ // breakpoint with the docs drawer; without one, the tabs-only drawer is the sole
68
+ // mobile nav below `lg`, so the inline tabs wait until `lg` to avoid duplicating
69
+ // the drawer's links between `md` and `lg`.
70
+ const tabsNavClass = hasSidebar
71
+ ? "hidden gap-1 md:flex"
72
+ : "hidden gap-1 lg:flex";
73
+
60
74
  const iconButton =
61
75
  "inline-flex size-9 cursor-pointer items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground";
62
76
 
@@ -68,14 +82,23 @@ const iconButton =
68
82
  // styles fade while the non-animated ones snap, so we disable all transitions
69
83
  // for the swap (next-themes' disableTransitionOnChange trick), force a
70
84
  // synchronous style flush, then restore them on the next tick.
71
- const clickScript = `document.addEventListener("click",(e)=>{const t=e.target.closest("[data-blume-theme-toggle]");if(t){const d=document.documentElement;const next=d.dataset.theme==="dark"?"light":"dark";const s=document.createElement("style");s.appendChild(document.createTextNode("*,*::before,*::after{transition:none!important}"));document.head.appendChild(s);d.dataset.theme=next;localStorage.setItem("blume-theme",next);window.getComputedStyle(d).opacity;setTimeout(()=>document.head.removeChild(s),1);}const n=e.target.closest("[data-blume-nav-toggle]");if(n){document.documentElement.toggleAttribute("data-blume-nav-open");}const b=e.target.closest("[data-blume-banner-dismiss]");if(b){const bar=b.closest("[data-blume-banner]");const k=bar&&bar.getAttribute("data-banner-key");if(k){localStorage.setItem("blume-banner:"+k,"1");}document.documentElement.setAttribute("data-blume-banner-hidden","");}});`;
85
+ //
86
+ // The mobile drawer sits below the sticky header, but the banner above the
87
+ // header has no fixed height (its text wraps), so the drawer's top offset can't
88
+ // be static: on open we measure the header's on-screen bottom edge into
89
+ // `--blume-drawer-top` (read by the drawer's `top`/`height`), lock page scroll
90
+ // so the offset can't drift while open, and re-measure when a banner dismiss or
91
+ // a resize/rotation moves the header. Crossing into the desktop breakpoint
92
+ // (64rem, Tailwind `lg`) closes the drawer so the scroll lock can't outlive it.
93
+ const clickScript = `(()=>{const dr=()=>{const h=document.querySelector("[data-blume-header]");if(h){document.documentElement.style.setProperty("--blume-drawer-top",h.getBoundingClientRect().bottom+"px");}};document.addEventListener("click",(e)=>{const t=e.target.closest("[data-blume-theme-toggle]");if(t){const d=document.documentElement;const next=d.dataset.theme==="dark"?"light":"dark";const s=document.createElement("style");s.appendChild(document.createTextNode("*,*::before,*::after{transition:none!important}"));document.head.appendChild(s);d.dataset.theme=next;localStorage.setItem("blume-theme",next);window.getComputedStyle(d).opacity;setTimeout(()=>document.head.removeChild(s),1);}const n=e.target.closest("[data-blume-nav-toggle]");if(n){const d=document.documentElement;const open=d.toggleAttribute("data-blume-nav-open");if(open){dr();}d.style.overflow=open?"hidden":"";}const b=e.target.closest("[data-blume-banner-dismiss]");if(b){const bar=b.closest("[data-blume-banner]");const k=bar&&bar.getAttribute("data-banner-key");if(k){localStorage.setItem("blume-banner:"+k,"1");}document.documentElement.setAttribute("data-blume-banner-hidden","");requestAnimationFrame(dr);}});addEventListener("resize",()=>{const d=document.documentElement;if(!d.hasAttribute("data-blume-nav-open")){return;}if(matchMedia("(min-width:64rem)").matches){d.removeAttribute("data-blume-nav-open");d.style.overflow="";}else{dr();}});})();`;
72
94
  ---
73
95
 
74
96
  <header
75
97
  class="sticky top-0 z-40 flex h-16 items-center gap-3 bg-background/90 px-4 backdrop-blur md:px-6"
98
+ data-blume-header
76
99
  >
77
100
  {
78
- hasSidebar && (
101
+ showNavToggle && (
79
102
  <button
80
103
  aria-label="Toggle navigation"
81
104
  class={`${iconButton} lg:hidden`}
@@ -98,7 +121,7 @@ const clickScript = `document.addEventListener("click",(e)=>{const t=e.target.cl
98
121
  }
99
122
  {
100
123
  navigation.tabs.length > 0 && (
101
- <nav aria-label="Sections" class="hidden gap-1 md:flex">
124
+ <nav aria-label="Sections" class={tabsNavClass}>
102
125
  {navigation.tabs.map((tab) => (
103
126
  <a
104
127
  aria-current={
@@ -19,6 +19,10 @@ const logoLight = logo?.light;
19
19
  const logoDark = logo?.dark ?? logo?.light;
20
20
  const logoAlt = logo?.alt ?? "";
21
21
  const brandHref = logo?.href ?? "/";
22
+ // Wordmark beside the mark: the configured `logo.text`, or the site title when
23
+ // it's omitted. An explicit `""` hides it (an image-only logo whose mark already
24
+ // carries the wordmark).
25
+ const brandText = logo?.text ?? site.title;
22
26
  ---
23
27
 
24
28
  <a
@@ -46,5 +50,5 @@ const brandHref = logo?.href ?? "/";
46
50
  </>
47
51
  ))
48
52
  }
49
- {site.title}
53
+ {brandText && <span>{brandText}</span>}
50
54
  </a>
@@ -1,5 +1,5 @@
1
1
  ---
2
- // A top-level navigation selector (Mintlify-style): a dropdown that switches
2
+ // A top-level navigation selector: a dropdown that switches
3
3
  // between partitions of the site — a product, a version, or any grouped set of
4
4
  // destinations (`navigation.selectors` in the config). Zero-JS, built on
5
5
  // <details>/<summary> like the language switcher.