blume 0.6.7 → 0.7.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.
- package/dist/cli/index.js +1179 -738
- package/dist/cli/index.js.map +52 -51
- package/dist/types/core/base-path.d.ts +38 -0
- package/dist/types/core/config-input.d.ts +74 -10
- package/dist/types/core/config.d.ts +3 -2
- package/dist/types/core/data.d.ts +2 -0
- package/dist/types/core/i18n-ui.d.ts +1 -3
- package/dist/types/core/schema.d.ts +95 -52
- package/dist/types/core/sources/types.d.ts +2 -0
- package/dist/types/core/types.d.ts +6 -1
- package/docs/02-deployment.mdx +16 -1
- package/docs/03-faq.mdx +8 -8
- package/docs/configuration/index.mdx +6 -0
- package/docs/content/components.mdx +29 -2
- package/docs/content/islands.mdx +8 -0
- package/docs/content/syntax.mdx +13 -0
- package/package.json +2 -1
- package/src/ai/agent-readability.ts +7 -2
- package/src/ai/ask.ts +12 -7
- package/src/ai/llms.ts +15 -4
- package/src/ai/mcp/data.ts +8 -4
- package/src/ai/mcp/server.ts +3 -0
- package/src/astro/component-slots.ts +5 -3
- package/src/astro/examples.ts +12 -7
- package/src/astro/generate.ts +317 -144
- package/src/astro/index.ts +5 -1
- package/src/astro/integration.ts +8 -4
- package/src/astro/islands.ts +11 -5
- package/src/astro/markdown-negotiation.ts +1 -1
- package/src/astro/pages.ts +8 -3
- package/src/astro/templates.ts +166 -19
- package/src/cli/commands/build.ts +32 -19
- package/src/cli/commands/dev.ts +48 -15
- package/src/cli/commands/doctor.ts +2 -2
- package/src/cli/commands/validate.ts +1 -0
- package/src/cli/dev-lock.ts +26 -15
- package/src/cli/required-secrets.ts +2 -1
- package/src/components/content/CodeBlock.astro +3 -0
- package/src/components/content/Component.astro +30 -16
- package/src/components/content/Diff.astro +3 -1
- package/src/components/content/auto-type-table.ts +18 -8
- package/src/components/content/diff.ts +12 -6
- package/src/components/content/mermaid-element.ts +3 -0
- package/src/components/index.ts +23 -1
- package/src/components/islands/ask-ai.tsx +12 -6
- package/src/components/islands/base-path.ts +28 -0
- package/src/components/islands/hooks.ts +16 -1
- package/src/components/layout/Banner.astro +2 -1
- package/src/components/layout/Breadcrumbs.astro +2 -1
- package/src/components/layout/Favicon.astro +3 -2
- package/src/components/layout/Header.astro +2 -1
- package/src/components/layout/LanguageSwitcher.astro +2 -1
- package/src/components/layout/Logo.astro +2 -1
- package/src/components/layout/NavSelector.astro +2 -1
- package/src/components/layout/NavTree.astro +5 -4
- package/src/components/layout/PageFeedback.astro +4 -1
- package/src/components/layout/PageLayout.astro +9 -4
- package/src/components/layout/Pagination.astro +3 -2
- package/src/components/layout/RootLayout.astro +7 -4
- package/src/components/layout/Search.astro +13 -5
- package/src/components/layout/nav-utils.ts +18 -10
- package/src/components/layout/search/pagefind.ts +3 -0
- package/src/components/layout/toc-element.ts +7 -1
- package/src/components/openapi/RequestPanel.astro +7 -1
- package/src/components/openapi/snippets.ts +25 -11
- package/src/core/base-path.ts +70 -0
- package/src/core/component-overrides.ts +103 -74
- package/src/core/config-input.ts +81 -15
- package/src/core/config.ts +5 -3
- package/src/core/content.ts +2 -0
- package/src/core/data.ts +2 -0
- package/src/core/diagnostics.ts +54 -34
- package/src/core/gitignore.ts +4 -1
- package/src/core/graph.ts +156 -88
- package/src/core/i18n-ui.ts +18 -3
- package/src/core/last-modified.ts +2 -0
- package/src/core/links.ts +38 -18
- package/src/core/manifest.ts +62 -45
- package/src/core/nav-diagnostics.ts +1 -1
- package/src/core/navigation.ts +116 -55
- package/src/core/project-graph.ts +10 -9
- package/src/core/schema.ts +572 -621
- package/src/core/sources/github-releases.ts +2 -1
- package/src/core/sources/mdx-remote.ts +58 -54
- package/src/core/sources/normalize.ts +116 -73
- package/src/core/sources/notion.ts +19 -10
- package/src/core/sources/types.ts +2 -0
- package/src/core/tsconfig-aliases.ts +59 -30
- package/src/core/types.ts +6 -1
- package/src/deploy/redirects.ts +18 -0
- package/src/deploy/robots.ts +6 -1
- package/src/deploy/rss.ts +10 -3
- package/src/deploy/sitemap.ts +14 -10
- package/src/markdown/base-links.ts +58 -0
- package/src/markdown/code-title.ts +11 -14
- package/src/markdown/index.ts +34 -9
- package/src/markdown/inline-code.ts +7 -2
- package/src/markdown/themes.ts +24 -0
- package/src/openapi/model.ts +3 -1
- package/src/openapi/references.ts +41 -17
- package/src/openapi/render-mdx.ts +11 -6
- package/src/openapi/scalar.ts +32 -16
- package/src/registry/eject.ts +64 -8
- package/src/search/build.ts +3 -0
- package/src/search/documents.ts +2 -2
- package/src/search/sync/typesense.ts +6 -4
- package/src/seo/jsonld.ts +16 -6
- package/src/theme/entry.ts +85 -20
package/src/registry/eject.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
envTemplate,
|
|
25
25
|
exampleMapTemplate,
|
|
26
26
|
exampleWrapperTemplate,
|
|
27
|
+
examplesPageTemplate,
|
|
27
28
|
exampleSlug,
|
|
28
29
|
islandMapTemplate,
|
|
29
30
|
islandWrapperTemplate,
|
|
@@ -45,11 +46,14 @@ import { buildReferenceFiles } from "../openapi/scalar.ts";
|
|
|
45
46
|
import { isOpenApiSource } from "../openapi/source.ts";
|
|
46
47
|
import { buildSearchDocuments } from "../search/documents.ts";
|
|
47
48
|
import { servesStaticIndex } from "../search/providers.ts";
|
|
48
|
-
import {
|
|
49
|
+
import {
|
|
50
|
+
examplesEntryTemplate,
|
|
51
|
+
tailwindEntryTemplate,
|
|
52
|
+
} from "../theme/entry.ts";
|
|
49
53
|
import { buildThemeCss } from "../theme/palette.ts";
|
|
50
54
|
import { twoslashCss } from "../theme/twoslash.ts";
|
|
51
55
|
|
|
52
|
-
const
|
|
56
|
+
const toPosix = (path: string): string => path.split("\\").join("/");
|
|
53
57
|
|
|
54
58
|
/** The `blume:openapi` payload for the ejected app (`{}` when none). */
|
|
55
59
|
const ejectOpenApiData = (project: BlumeProject): unknown => {
|
|
@@ -86,6 +90,40 @@ const askFiles = async (
|
|
|
86
90
|
return files;
|
|
87
91
|
};
|
|
88
92
|
|
|
93
|
+
/** Contents of the configured `examples.css`, or `""` when unset/absent. */
|
|
94
|
+
const readExamplesCss = (
|
|
95
|
+
root: string,
|
|
96
|
+
css: string | undefined
|
|
97
|
+
): Promise<string> =>
|
|
98
|
+
css && existsSync(join(root, css))
|
|
99
|
+
? readFile(join(root, css), "utf-8")
|
|
100
|
+
: Promise.resolve("");
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The per-example preview route `<Component />` iframes embed, nested under
|
|
104
|
+
* `basePath` so it stays reachable behind a proxy that only forwards the
|
|
105
|
+
* base. Empty when the project has no examples.
|
|
106
|
+
*/
|
|
107
|
+
const examplesPreviewFiles = (
|
|
108
|
+
srcDir: string,
|
|
109
|
+
basePath: string,
|
|
110
|
+
hasExamples: boolean
|
|
111
|
+
): { content: string; path: string }[] =>
|
|
112
|
+
hasExamples
|
|
113
|
+
? [
|
|
114
|
+
{
|
|
115
|
+
content: examplesPageTemplate(),
|
|
116
|
+
path: join(
|
|
117
|
+
srcDir,
|
|
118
|
+
"pages",
|
|
119
|
+
...basePath.split("/").filter(Boolean),
|
|
120
|
+
"blume-examples",
|
|
121
|
+
"[...path].astro"
|
|
122
|
+
),
|
|
123
|
+
},
|
|
124
|
+
]
|
|
125
|
+
: [];
|
|
126
|
+
|
|
89
127
|
/**
|
|
90
128
|
* Promote the generated runtime into the project as an owned Astro app. After
|
|
91
129
|
* eject the project has a normal `astro.config.mjs` and `src/`, the `blume` CLI
|
|
@@ -108,6 +146,7 @@ export const eject = async (root: string): Promise<string[]> => {
|
|
|
108
146
|
needsReactRaw,
|
|
109
147
|
usesMath,
|
|
110
148
|
userTheme,
|
|
149
|
+
userExamplesCss,
|
|
111
150
|
rawMarkdown,
|
|
112
151
|
islands,
|
|
113
152
|
examples,
|
|
@@ -118,9 +157,10 @@ export const eject = async (root: string): Promise<string[]> => {
|
|
|
118
157
|
context.themeFile
|
|
119
158
|
? readFile(context.themeFile, "utf-8")
|
|
120
159
|
: Promise.resolve(""),
|
|
160
|
+
readExamplesCss(root, config.examples.css),
|
|
121
161
|
buildRawMarkdown(project),
|
|
122
162
|
discoverIslands(root),
|
|
123
|
-
discoverExamples(root, config.examples),
|
|
163
|
+
discoverExamples(root, config.examples.source),
|
|
124
164
|
]);
|
|
125
165
|
// Island/example frameworks drive which Astro renderers the ejected config
|
|
126
166
|
// wires in; React also switches on for project `.tsx`/`.jsx` and Ask AI.
|
|
@@ -135,16 +175,16 @@ export const eject = async (root: string): Promise<string[]> => {
|
|
|
135
175
|
// A project-relative context so generated files use portable paths.
|
|
136
176
|
const relContext: ProjectContext = {
|
|
137
177
|
...context,
|
|
138
|
-
contentRoot:
|
|
178
|
+
contentRoot: toPosix(relative(root, context.contentRoot)),
|
|
139
179
|
outDir: ".",
|
|
140
180
|
root: ".",
|
|
141
181
|
};
|
|
142
182
|
|
|
143
183
|
const componentsImport = context.componentsFile
|
|
144
|
-
? `../../${
|
|
184
|
+
? `../../${toPosix(relative(root, context.componentsFile))}`
|
|
145
185
|
: null;
|
|
146
186
|
const relPages = pages.map((page) => ({
|
|
147
|
-
entrypoint:
|
|
187
|
+
entrypoint: toPosix(relative(root, page.entrypoint)),
|
|
148
188
|
pattern: page.pattern,
|
|
149
189
|
}));
|
|
150
190
|
|
|
@@ -168,6 +208,7 @@ export const eject = async (root: string): Promise<string[]> => {
|
|
|
168
208
|
context: relContext,
|
|
169
209
|
dataPath: "./src/generated/data.json",
|
|
170
210
|
examplesPath: "./src/generated/examples.ts",
|
|
211
|
+
examplesThemePath: "./src/generated/examples.css",
|
|
171
212
|
needsReact,
|
|
172
213
|
needsSvelte,
|
|
173
214
|
needsVue,
|
|
@@ -218,9 +259,19 @@ export const eject = async (root: string): Promise<string[]> => {
|
|
|
218
259
|
path: join(genDir, "islands.ts"),
|
|
219
260
|
},
|
|
220
261
|
{
|
|
221
|
-
content: exampleMapTemplate(examples.examples),
|
|
262
|
+
content: exampleMapTemplate(examples.examples, config.basePath),
|
|
222
263
|
path: join(genDir, "examples.ts"),
|
|
223
264
|
},
|
|
265
|
+
{
|
|
266
|
+
// The isolated Tailwind entry for `<Component />` preview frames.
|
|
267
|
+
// Relative sources keep the ejected app portable.
|
|
268
|
+
content: examplesEntryTemplate({
|
|
269
|
+
configTokens: buildThemeCss(config.theme),
|
|
270
|
+
sources: ["../../**/*.{astro,jsx,svelte,ts,tsx,vue}"],
|
|
271
|
+
userCss: userExamplesCss,
|
|
272
|
+
}),
|
|
273
|
+
path: join(genDir, "examples.css"),
|
|
274
|
+
},
|
|
224
275
|
{
|
|
225
276
|
content: tailwindEntryTemplate({
|
|
226
277
|
configTokens: buildThemeCss(config.theme),
|
|
@@ -345,7 +396,12 @@ export const eject = async (root: string): Promise<string[]> => {
|
|
|
345
396
|
...examples.examples.map((example) => ({
|
|
346
397
|
content: exampleWrapperTemplate(example),
|
|
347
398
|
path: join(genDir, "examples", `${exampleSlug(example.path)}.astro`),
|
|
348
|
-
}))
|
|
399
|
+
})),
|
|
400
|
+
...examplesPreviewFiles(
|
|
401
|
+
srcDir,
|
|
402
|
+
config.basePath,
|
|
403
|
+
examples.examples.length > 0
|
|
404
|
+
)
|
|
349
405
|
);
|
|
350
406
|
|
|
351
407
|
// Materialize staged source bodies under `<root>/blume-staged/<source>/<ref>`,
|
package/src/search/build.ts
CHANGED
|
@@ -15,6 +15,9 @@ export const buildSearchIndex = async (outDir: string): Promise<number> => {
|
|
|
15
15
|
throw new Error("Failed to create Pagefind index.");
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
// These awaits are strictly ordered, not independent: the directory must be
|
|
19
|
+
// indexed before its files are written, and the index closed only after.
|
|
20
|
+
// oxlint-disable-next-line react-doctor/async-parallel
|
|
18
21
|
const result = await index.addDirectory({ path: outDir });
|
|
19
22
|
await index.writeFiles({ outputPath: join(outDir, "pagefind") });
|
|
20
23
|
await pagefind.close();
|
package/src/search/documents.ts
CHANGED
|
@@ -65,9 +65,9 @@ const toPlainText = (markdown: string): string => {
|
|
|
65
65
|
for (const match of withoutBlocks.matchAll(INLINE_CODE)) {
|
|
66
66
|
const start = match.index ?? 0;
|
|
67
67
|
pieces.push(
|
|
68
|
-
withoutBlocks.slice(cursor, start).replaceAll(HTML_OR_JSX, " ")
|
|
68
|
+
withoutBlocks.slice(cursor, start).replaceAll(HTML_OR_JSX, " "),
|
|
69
|
+
match.groups?.code ?? ""
|
|
69
70
|
);
|
|
70
|
-
pieces.push(match.groups?.code ?? "");
|
|
71
71
|
cursor = start + match[0].length;
|
|
72
72
|
}
|
|
73
73
|
pieces.push(withoutBlocks.slice(cursor).replaceAll(HTML_OR_JSX, " "));
|
|
@@ -40,10 +40,12 @@ export const syncTypesense = async (
|
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
const collection = client.collections(config.collection);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
.
|
|
46
|
-
|
|
43
|
+
let exists = true;
|
|
44
|
+
try {
|
|
45
|
+
await collection.retrieve();
|
|
46
|
+
} catch {
|
|
47
|
+
exists = false;
|
|
48
|
+
}
|
|
47
49
|
if (exists) {
|
|
48
50
|
await collection.delete();
|
|
49
51
|
}
|
package/src/seo/jsonld.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { Crumb } from "../components/layout/nav-utils.ts";
|
|
2
|
+
import { normalizeBasePath, withBasePath } from "../core/base-path.ts";
|
|
3
|
+
|
|
4
|
+
/** A date-ish value carried through frontmatter (string, YAML Date, or unset). */
|
|
5
|
+
type DateInput = string | Date | null;
|
|
2
6
|
|
|
3
7
|
/** Inputs for a page's JSON-LD, all known at render time in RootLayout. */
|
|
4
8
|
export interface StructuredDataInput {
|
|
@@ -9,12 +13,14 @@ export interface StructuredDataInput {
|
|
|
9
13
|
description?: string;
|
|
10
14
|
/** Page route, e.g. `/blog/post`. */
|
|
11
15
|
route: string;
|
|
16
|
+
/** Deployment base (`import.meta.env.BASE_URL`); prefixed onto absolute URLs. */
|
|
17
|
+
base?: string;
|
|
12
18
|
/** Content type — `blog` and `changelog` map to richer article types. */
|
|
13
19
|
pageType?: string;
|
|
14
20
|
/** Publish date (string or YAML Date); emitted as ISO `datePublished`. */
|
|
15
|
-
published?:
|
|
21
|
+
published?: DateInput;
|
|
16
22
|
/** Last-modified date; emitted as ISO `dateModified`. */
|
|
17
|
-
modified?:
|
|
23
|
+
modified?: DateInput;
|
|
18
24
|
/** BCP-47 language tag for `inLanguage`; defaults to `en`. */
|
|
19
25
|
locale?: string;
|
|
20
26
|
breadcrumbs: Crumb[];
|
|
@@ -31,7 +37,7 @@ const trimSlash = (value: string): string => value.replace(/\/$/u, "");
|
|
|
31
37
|
const absolute = (base: string | null, path: string): string =>
|
|
32
38
|
base ? `${base}${path}` : path;
|
|
33
39
|
|
|
34
|
-
const toIso = (value:
|
|
40
|
+
const toIso = (value: DateInput | undefined): string | undefined => {
|
|
35
41
|
if (!value) {
|
|
36
42
|
return;
|
|
37
43
|
}
|
|
@@ -49,7 +55,11 @@ export const buildStructuredData = (
|
|
|
49
55
|
input: StructuredDataInput
|
|
50
56
|
): Record<string, unknown> | null => {
|
|
51
57
|
const base = input.siteUrl ? trimSlash(input.siteUrl) : null;
|
|
52
|
-
|
|
58
|
+
// Routes carry `basePath`; a `deployment.base` subdirectory is layered on top
|
|
59
|
+
// so JSON-LD URLs match the served location.
|
|
60
|
+
const deployBase = normalizeBasePath(input.base);
|
|
61
|
+
const pageUrl = absolute(base, withBasePath(deployBase, input.route));
|
|
62
|
+
const rootUrl = absolute(base, deployBase);
|
|
53
63
|
const graph: Record<string, unknown>[] = [];
|
|
54
64
|
|
|
55
65
|
if (base) {
|
|
@@ -57,7 +67,7 @@ export const buildStructuredData = (
|
|
|
57
67
|
"@id": `${base}#website`,
|
|
58
68
|
"@type": "WebSite",
|
|
59
69
|
name: input.siteName,
|
|
60
|
-
url:
|
|
70
|
+
url: rootUrl,
|
|
61
71
|
});
|
|
62
72
|
}
|
|
63
73
|
|
|
@@ -98,7 +108,7 @@ export const buildStructuredData = (
|
|
|
98
108
|
position: index + 1,
|
|
99
109
|
};
|
|
100
110
|
if (crumb.route) {
|
|
101
|
-
item.item = absolute(base, crumb.route);
|
|
111
|
+
item.item = absolute(base, withBasePath(deployBase, crumb.route));
|
|
102
112
|
}
|
|
103
113
|
return item;
|
|
104
114
|
}),
|
package/src/theme/entry.ts
CHANGED
|
@@ -12,25 +12,16 @@ interface TailwindEntryOptions {
|
|
|
12
12
|
twoslashCss?: string;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/** Dark mode is driven by `data-theme` on the root element (both sheets). */
|
|
16
|
+
const DARK_VARIANT = `/* Dark mode is driven by data-theme on the <html> element. */
|
|
17
|
+
@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));`;
|
|
18
|
+
|
|
15
19
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* generated and design tokens cascade deterministically:
|
|
20
|
-
* base defaults -> config tokens -> user theme.css.
|
|
20
|
+
* The default `--blume-*` design tokens (light + dark), shared by the app
|
|
21
|
+
* sheet and the isolated example-preview sheet so previews inherit the site's
|
|
22
|
+
* palette by default.
|
|
21
23
|
*/
|
|
22
|
-
|
|
23
|
-
`/* Generated by Blume. Do not edit. */
|
|
24
|
-
@import "tailwindcss";
|
|
25
|
-
@plugin "@tailwindcss/typography";
|
|
26
|
-
|
|
27
|
-
/* Scan Blume's components and the user's project for utility classes. */
|
|
28
|
-
${options.sources.map((source) => `@source "${source}";`).join("\n")}
|
|
29
|
-
|
|
30
|
-
/* Dark mode is driven by data-theme on the <html> element. */
|
|
31
|
-
@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
|
|
32
|
-
|
|
33
|
-
:root {
|
|
24
|
+
const TOKEN_DEFAULTS = `:root {
|
|
34
25
|
--blume-background: oklch(1 0 0);
|
|
35
26
|
--blume-background-image: none;
|
|
36
27
|
--blume-background-image-repeat: no-repeat;
|
|
@@ -108,9 +99,15 @@ ${options.sources.map((source) => `@source "${source}";`).join("\n")}
|
|
|
108
99
|
--blume-code-remove-border: oklch(0.7 0.2 22 / 0.7);
|
|
109
100
|
--blume-code-word: oklch(0.7 0.14 255 / 0.22);
|
|
110
101
|
--blume-code-word-border: oklch(0.7 0.14 255 / 0.55);
|
|
111
|
-
}
|
|
102
|
+
}`;
|
|
112
103
|
|
|
113
|
-
|
|
104
|
+
/**
|
|
105
|
+
* Tailwind theme mapping from `--blume-*` tokens to utility-facing names
|
|
106
|
+
* (`bg-background`, `border-border`, `rounded-blume`, the font stacks).
|
|
107
|
+
* Shared by both sheets so example code written against Blume's utility
|
|
108
|
+
* vocabulary renders identically inside the isolated preview frame.
|
|
109
|
+
*/
|
|
110
|
+
const THEME_MAPPING = `@theme inline {
|
|
114
111
|
--color-background: var(--blume-background);
|
|
115
112
|
--color-foreground: var(--blume-foreground);
|
|
116
113
|
--color-muted: var(--blume-muted);
|
|
@@ -125,7 +122,28 @@ ${options.sources.map((source) => `@source "${source}";`).join("\n")}
|
|
|
125
122
|
--font-sans: var(--blume-font-body);
|
|
126
123
|
--font-mono: var(--blume-font-mono);
|
|
127
124
|
--font-display: var(--blume-font-display);
|
|
128
|
-
}
|
|
125
|
+
}`;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Build the single Tailwind v4 entry stylesheet for the generated runtime.
|
|
129
|
+
*
|
|
130
|
+
* Everything flows through one Tailwind-processed file so utilities are
|
|
131
|
+
* generated and design tokens cascade deterministically:
|
|
132
|
+
* base defaults -> config tokens -> user theme.css.
|
|
133
|
+
*/
|
|
134
|
+
export const tailwindEntryTemplate = (options: TailwindEntryOptions): string =>
|
|
135
|
+
`/* Generated by Blume. Do not edit. */
|
|
136
|
+
@import "tailwindcss";
|
|
137
|
+
@plugin "@tailwindcss/typography";
|
|
138
|
+
|
|
139
|
+
/* Scan Blume's components and the user's project for utility classes. */
|
|
140
|
+
${options.sources.map((source) => `@source "${source}";`).join("\n")}
|
|
141
|
+
|
|
142
|
+
${DARK_VARIANT}
|
|
143
|
+
|
|
144
|
+
${TOKEN_DEFAULTS}
|
|
145
|
+
|
|
146
|
+
${THEME_MAPPING}
|
|
129
147
|
|
|
130
148
|
@layer base {
|
|
131
149
|
/* Nothing refuses to shrink below its intrinsic content width. This global
|
|
@@ -670,3 +688,50 @@ ${options.twoslashCss ?? ""}
|
|
|
670
688
|
${options.configTokens}
|
|
671
689
|
${options.userTheme}
|
|
672
690
|
`;
|
|
691
|
+
|
|
692
|
+
interface ExamplesEntryOptions {
|
|
693
|
+
/** Config-derived token overrides (`:root { --blume-accent: ... }`). */
|
|
694
|
+
configTokens: string;
|
|
695
|
+
/** Globs to scan for utility classes (example files and their imports). */
|
|
696
|
+
sources: string[];
|
|
697
|
+
/** Raw contents of the configured `examples.css`, if any. */
|
|
698
|
+
userCss: string;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* Build the Tailwind entry for `<Component />` preview frames. Each example
|
|
703
|
+
* renders in its own iframe so none of the app sheet above — prose typography,
|
|
704
|
+
* component chrome, base overrides — can reach it. This sheet provides only
|
|
705
|
+
* what an example needs to look like it does in an app: Tailwind (preflight +
|
|
706
|
+
* utilities scanned from the example sources), the `--blume-*` tokens and
|
|
707
|
+
* their utility mapping (so `bg-background`-style classes keep working and
|
|
708
|
+
* previews follow the site palette by default), and the user's example CSS
|
|
709
|
+
* (shadcn variables, `@theme` mappings, custom styles) last so it wins.
|
|
710
|
+
*/
|
|
711
|
+
export const examplesEntryTemplate = (options: ExamplesEntryOptions): string =>
|
|
712
|
+
`/* Generated by Blume. Do not edit. */
|
|
713
|
+
@import "tailwindcss";
|
|
714
|
+
|
|
715
|
+
/* Scan the example files and the project sources they import. */
|
|
716
|
+
${options.sources.map((source) => `@source "${source}";`).join("\n")}
|
|
717
|
+
|
|
718
|
+
${DARK_VARIANT}
|
|
719
|
+
|
|
720
|
+
${TOKEN_DEFAULTS}
|
|
721
|
+
|
|
722
|
+
${THEME_MAPPING}
|
|
723
|
+
|
|
724
|
+
/* Frame defaults: readable text in both modes, transparent so the docs pane's
|
|
725
|
+
surface shows through. Base layer, so any user/example CSS wins. */
|
|
726
|
+
@layer base {
|
|
727
|
+
body {
|
|
728
|
+
background: transparent;
|
|
729
|
+
color: var(--blume-foreground);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/* Token overrides: config first, then the configured examples css (highest
|
|
734
|
+
priority) — the place for shadcn variables and other component tokens. */
|
|
735
|
+
${options.configTokens}
|
|
736
|
+
${options.userCss}
|
|
737
|
+
`;
|