@bonnard/cli 0.3.0 → 0.3.2
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/bin/{api-DqgY-30K.mjs → api-CirD2MoX.mjs} +4 -1
- package/dist/bin/api-DZ1Xj0_d.mjs +3 -0
- package/dist/bin/bon.mjs +153 -9
- package/dist/bin/{push-BOkUmRL8.mjs → push-BRq4Yy0V.mjs} +1 -1
- package/dist/docs/topics/dashboards.md +2 -0
- package/dist/docs/topics/dashboards.theming.md +152 -0
- package/dist/docs/topics/sdk.browser.md +1 -1
- package/dist/templates/claude/skills/bonnard-build-dashboard/SKILL.md +10 -0
- package/dist/templates/cursor/rules/bonnard-build-dashboard.mdc +10 -0
- package/dist/templates/shared/bonnard.md +3 -0
- package/dist/viewer.html +176 -176
- package/package.json +3 -2
- package/dist/bin/api-B7cdKn9j.mjs +0 -3
|
@@ -67,9 +67,12 @@ function get(path) {
|
|
|
67
67
|
function post(path, body) {
|
|
68
68
|
return request("POST", path, body);
|
|
69
69
|
}
|
|
70
|
+
function put(path, body) {
|
|
71
|
+
return request("PUT", path, body);
|
|
72
|
+
}
|
|
70
73
|
function del(path) {
|
|
71
74
|
return request("DELETE", path);
|
|
72
75
|
}
|
|
73
76
|
|
|
74
77
|
//#endregion
|
|
75
|
-
export {
|
|
78
|
+
export { clearCredentials as a, put as i, get as n, loadCredentials as o, post as r, saveCredentials as s, del as t };
|
package/dist/bin/bon.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { n as getProjectPaths, t as BONNARD_DIR } from "./project-Dj085D_B.mjs";
|
|
3
|
-
import { a as
|
|
3
|
+
import { a as clearCredentials, i as put, n as get, o as loadCredentials, r as post, s as saveCredentials, t as del } from "./api-CirD2MoX.mjs";
|
|
4
4
|
import { i as ensureBonDir, n as addLocalDatasource, o as loadLocalDatasources, r as datasourceExists, s as removeLocalDatasource, t as isDatasourcesTrackedByGit } from "./local-ByvuW3eV.mjs";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import { program } from "commander";
|
|
@@ -9,7 +9,7 @@ import path from "node:path";
|
|
|
9
9
|
import os from "node:os";
|
|
10
10
|
import pc from "picocolors";
|
|
11
11
|
import { fileURLToPath } from "node:url";
|
|
12
|
-
import YAML from "yaml";
|
|
12
|
+
import YAML, { parse, stringify } from "yaml";
|
|
13
13
|
import http from "node:http";
|
|
14
14
|
import crypto from "node:crypto";
|
|
15
15
|
import { encode } from "@toon-format/toon";
|
|
@@ -1601,7 +1601,7 @@ async function listRemoteDatasources() {
|
|
|
1601
1601
|
return;
|
|
1602
1602
|
}
|
|
1603
1603
|
try {
|
|
1604
|
-
const { get } = await import("./api-
|
|
1604
|
+
const { get } = await import("./api-DZ1Xj0_d.mjs");
|
|
1605
1605
|
const result = await get("/api/datasources");
|
|
1606
1606
|
if (result.dataSources.length === 0) {
|
|
1607
1607
|
console.log(pc.dim("No remote data sources found."));
|
|
@@ -1662,7 +1662,7 @@ async function removeRemote(name) {
|
|
|
1662
1662
|
process.exit(1);
|
|
1663
1663
|
}
|
|
1664
1664
|
try {
|
|
1665
|
-
const { del } = await import("./api-
|
|
1665
|
+
const { del } = await import("./api-DZ1Xj0_d.mjs");
|
|
1666
1666
|
await del(`/api/datasources/${encodeURIComponent(name)}`);
|
|
1667
1667
|
console.log(pc.green(`✓ Removed "${name}" from remote server`));
|
|
1668
1668
|
} catch (err) {
|
|
@@ -1825,7 +1825,7 @@ async function deployCommand(options = {}) {
|
|
|
1825
1825
|
async function testAndSyncDatasources(cwd, options = {}) {
|
|
1826
1826
|
const { extractDatasourcesFromCubes } = await import("./cubes-BvtwNBUG.mjs");
|
|
1827
1827
|
const { loadLocalDatasources } = await import("./local-BkK5XL7T.mjs");
|
|
1828
|
-
const { pushDatasource } = await import("./push-
|
|
1828
|
+
const { pushDatasource } = await import("./push-BRq4Yy0V.mjs");
|
|
1829
1829
|
const references = extractDatasourcesFromCubes(cwd);
|
|
1830
1830
|
if (references.length === 0) return false;
|
|
1831
1831
|
console.log();
|
|
@@ -4093,7 +4093,7 @@ async function dashboardListCommand() {
|
|
|
4093
4093
|
|
|
4094
4094
|
//#endregion
|
|
4095
4095
|
//#region src/commands/dashboard/remove.ts
|
|
4096
|
-
async function confirm(message) {
|
|
4096
|
+
async function confirm$1(message) {
|
|
4097
4097
|
const rl = createInterface({
|
|
4098
4098
|
input: process.stdin,
|
|
4099
4099
|
output: process.stdout
|
|
@@ -4107,7 +4107,7 @@ async function confirm(message) {
|
|
|
4107
4107
|
}
|
|
4108
4108
|
async function dashboardRemoveCommand(slug, options) {
|
|
4109
4109
|
if (!options.force) {
|
|
4110
|
-
if (!await confirm(`Remove dashboard "${slug}"? This cannot be undone.`)) {
|
|
4110
|
+
if (!await confirm$1(`Remove dashboard "${slug}"? This cannot be undone.`)) {
|
|
4111
4111
|
console.log(pc.dim("Cancelled."));
|
|
4112
4112
|
return;
|
|
4113
4113
|
}
|
|
@@ -4148,6 +4148,26 @@ function loadViewer() {
|
|
|
4148
4148
|
}
|
|
4149
4149
|
return fs.readFileSync(viewerPath, "utf-8");
|
|
4150
4150
|
}
|
|
4151
|
+
function loadThemeFile(filePath) {
|
|
4152
|
+
try {
|
|
4153
|
+
const parsed = parse(fs.readFileSync(filePath, "utf-8"));
|
|
4154
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
4155
|
+
console.error(pc.yellow(`Warning: Theme file is not a valid object, ignoring.`));
|
|
4156
|
+
return null;
|
|
4157
|
+
} catch (err) {
|
|
4158
|
+
console.error(pc.yellow(`Warning: Could not load theme file: ${err instanceof Error ? err.message : err}`));
|
|
4159
|
+
return null;
|
|
4160
|
+
}
|
|
4161
|
+
}
|
|
4162
|
+
async function fetchOrgTheme() {
|
|
4163
|
+
try {
|
|
4164
|
+
const result = await get("/api/org/theme");
|
|
4165
|
+
if (result.theme && typeof result.theme === "object") return result.theme;
|
|
4166
|
+
return null;
|
|
4167
|
+
} catch {
|
|
4168
|
+
return null;
|
|
4169
|
+
}
|
|
4170
|
+
}
|
|
4151
4171
|
async function dashboardDevCommand(file, options) {
|
|
4152
4172
|
const filePath = path.resolve(file);
|
|
4153
4173
|
if (!fs.existsSync(filePath)) {
|
|
@@ -4161,6 +4181,24 @@ async function dashboardDevCommand(file, options) {
|
|
|
4161
4181
|
}
|
|
4162
4182
|
const viewerHtml = loadViewer();
|
|
4163
4183
|
const sseClients = /* @__PURE__ */ new Set();
|
|
4184
|
+
let orgTheme = null;
|
|
4185
|
+
if (options.theme) {
|
|
4186
|
+
const themePath = path.resolve(options.theme);
|
|
4187
|
+
if (!fs.existsSync(themePath)) {
|
|
4188
|
+
console.error(pc.red(`Theme file not found: ${options.theme}`));
|
|
4189
|
+
process.exit(1);
|
|
4190
|
+
}
|
|
4191
|
+
orgTheme = loadThemeFile(themePath);
|
|
4192
|
+
if (orgTheme) console.log(pc.dim(`Using local theme from ${path.basename(themePath)}`));
|
|
4193
|
+
fs.watch(themePath, () => {
|
|
4194
|
+
orgTheme = loadThemeFile(themePath);
|
|
4195
|
+
for (const client of sseClients) client.write("data: reload\n\n");
|
|
4196
|
+
});
|
|
4197
|
+
} else {
|
|
4198
|
+
console.log(pc.dim("Fetching org theme..."));
|
|
4199
|
+
orgTheme = await fetchOrgTheme();
|
|
4200
|
+
if (orgTheme) console.log(pc.dim("Using org theme from Bonnard."));
|
|
4201
|
+
}
|
|
4164
4202
|
let debounce = null;
|
|
4165
4203
|
fs.watch(filePath, () => {
|
|
4166
4204
|
if (debounce) clearTimeout(debounce);
|
|
@@ -4190,7 +4228,8 @@ async function dashboardDevCommand(file, options) {
|
|
|
4190
4228
|
});
|
|
4191
4229
|
res.end(JSON.stringify({
|
|
4192
4230
|
token: creds.token,
|
|
4193
|
-
baseUrl: APP_URL
|
|
4231
|
+
baseUrl: APP_URL,
|
|
4232
|
+
orgTheme
|
|
4194
4233
|
}));
|
|
4195
4234
|
return;
|
|
4196
4235
|
}
|
|
@@ -4216,6 +4255,107 @@ async function dashboardDevCommand(file, options) {
|
|
|
4216
4255
|
});
|
|
4217
4256
|
}
|
|
4218
4257
|
|
|
4258
|
+
//#endregion
|
|
4259
|
+
//#region src/commands/theme/get.ts
|
|
4260
|
+
async function themeGetCommand() {
|
|
4261
|
+
try {
|
|
4262
|
+
const result = await get("/api/org/theme");
|
|
4263
|
+
if (!result.theme) {
|
|
4264
|
+
console.log(pc.dim("No org theme set. Using defaults."));
|
|
4265
|
+
return;
|
|
4266
|
+
}
|
|
4267
|
+
console.log(pc.bold("Organization theme:\n"));
|
|
4268
|
+
console.log(stringify(result.theme).trimEnd());
|
|
4269
|
+
} catch (err) {
|
|
4270
|
+
console.error(pc.red(`Error: ${err instanceof Error ? err.message : err}`));
|
|
4271
|
+
process.exit(1);
|
|
4272
|
+
}
|
|
4273
|
+
}
|
|
4274
|
+
|
|
4275
|
+
//#endregion
|
|
4276
|
+
//#region src/commands/theme/set.ts
|
|
4277
|
+
const VALID_TOP_LEVEL_KEYS = new Set([
|
|
4278
|
+
"palette",
|
|
4279
|
+
"chartHeight",
|
|
4280
|
+
"colors"
|
|
4281
|
+
]);
|
|
4282
|
+
async function themeSetCommand(file, options) {
|
|
4283
|
+
const filePath = path.resolve(file);
|
|
4284
|
+
if (!fs.existsSync(filePath)) {
|
|
4285
|
+
console.error(pc.red(`File not found: ${file}`));
|
|
4286
|
+
process.exit(1);
|
|
4287
|
+
}
|
|
4288
|
+
let theme;
|
|
4289
|
+
try {
|
|
4290
|
+
theme = parse(fs.readFileSync(filePath, "utf-8"));
|
|
4291
|
+
} catch (err) {
|
|
4292
|
+
console.error(pc.red(`Failed to parse ${file}: ${err instanceof Error ? err.message : err}`));
|
|
4293
|
+
process.exit(1);
|
|
4294
|
+
}
|
|
4295
|
+
if (!theme || typeof theme !== "object" || Array.isArray(theme)) {
|
|
4296
|
+
console.error(pc.red("Theme file must contain a YAML/JSON object."));
|
|
4297
|
+
process.exit(1);
|
|
4298
|
+
}
|
|
4299
|
+
const unknownKeys = Object.keys(theme).filter((k) => !VALID_TOP_LEVEL_KEYS.has(k));
|
|
4300
|
+
if (unknownKeys.length > 0) {
|
|
4301
|
+
console.error(pc.red(`Unknown top-level keys: ${unknownKeys.join(", ")}\nAllowed: ${[...VALID_TOP_LEVEL_KEYS].join(", ")}`));
|
|
4302
|
+
process.exit(1);
|
|
4303
|
+
}
|
|
4304
|
+
if (options.dryRun) {
|
|
4305
|
+
console.log(pc.bold("Theme to be set (dry run):\n"));
|
|
4306
|
+
console.log(stringify(theme).trimEnd());
|
|
4307
|
+
return;
|
|
4308
|
+
}
|
|
4309
|
+
try {
|
|
4310
|
+
await put("/api/org/theme", { theme });
|
|
4311
|
+
console.log(pc.green("Organization theme updated.\n"));
|
|
4312
|
+
const summary = [];
|
|
4313
|
+
if (theme.palette) {
|
|
4314
|
+
const p = theme.palette;
|
|
4315
|
+
summary.push(` palette: ${typeof p === "string" ? p : `[${p.length} colors]`}`);
|
|
4316
|
+
}
|
|
4317
|
+
if (theme.chartHeight) summary.push(` chartHeight: ${theme.chartHeight}`);
|
|
4318
|
+
if (theme.colors) {
|
|
4319
|
+
const count = Object.keys(theme.colors).length;
|
|
4320
|
+
summary.push(` colors: ${count} override${count !== 1 ? "s" : ""}`);
|
|
4321
|
+
}
|
|
4322
|
+
if (summary.length > 0) console.log(summary.join("\n"));
|
|
4323
|
+
} catch (err) {
|
|
4324
|
+
console.error(pc.red(`Failed to set theme: ${err instanceof Error ? err.message : err}`));
|
|
4325
|
+
process.exit(1);
|
|
4326
|
+
}
|
|
4327
|
+
}
|
|
4328
|
+
|
|
4329
|
+
//#endregion
|
|
4330
|
+
//#region src/commands/theme/reset.ts
|
|
4331
|
+
async function confirm(message) {
|
|
4332
|
+
const rl = createInterface({
|
|
4333
|
+
input: process.stdin,
|
|
4334
|
+
output: process.stdout
|
|
4335
|
+
});
|
|
4336
|
+
return new Promise((resolve) => {
|
|
4337
|
+
rl.question(`${message} (y/N) `, (answer) => {
|
|
4338
|
+
rl.close();
|
|
4339
|
+
resolve(answer.toLowerCase() === "y");
|
|
4340
|
+
});
|
|
4341
|
+
});
|
|
4342
|
+
}
|
|
4343
|
+
async function themeResetCommand(options) {
|
|
4344
|
+
if (!options.force) {
|
|
4345
|
+
if (!await confirm("Reset org theme to defaults?")) {
|
|
4346
|
+
console.log(pc.dim("Cancelled."));
|
|
4347
|
+
return;
|
|
4348
|
+
}
|
|
4349
|
+
}
|
|
4350
|
+
try {
|
|
4351
|
+
await put("/api/org/theme", { theme: null });
|
|
4352
|
+
console.log(pc.green("Org theme reset to defaults."));
|
|
4353
|
+
} catch (err) {
|
|
4354
|
+
console.error(pc.red(`Failed to reset theme: ${err instanceof Error ? err.message : err}`));
|
|
4355
|
+
process.exit(1);
|
|
4356
|
+
}
|
|
4357
|
+
}
|
|
4358
|
+
|
|
4219
4359
|
//#endregion
|
|
4220
4360
|
//#region src/bin/bon.ts
|
|
4221
4361
|
const { version } = createRequire(import.meta.url)("../../package.json");
|
|
@@ -4242,11 +4382,15 @@ keys.command("list").description("List all API keys for your organization").acti
|
|
|
4242
4382
|
keys.command("create").description("Create a new API key").requiredOption("--name <name>", "Key name (e.g. 'Production SDK')").requiredOption("--type <type>", "Key type: publishable or secret").action(keysCreateCommand);
|
|
4243
4383
|
keys.command("revoke").description("Revoke an API key by name or prefix").argument("<name-or-prefix>", "Key name or key prefix to revoke").action(keysRevokeCommand);
|
|
4244
4384
|
const dashboard = program.command("dashboard").description("Manage hosted dashboards");
|
|
4245
|
-
dashboard.command("dev").description("Preview a markdown dashboard locally with live reload").argument("<file>", "Path to dashboard .md file").option("--port <port>", "Server port (default: random available port)").action(dashboardDevCommand);
|
|
4385
|
+
dashboard.command("dev").description("Preview a markdown dashboard locally with live reload").argument("<file>", "Path to dashboard .md file").option("--port <port>", "Server port (default: random available port)").option("--theme <file>", "Path to a local theme YAML/JSON file").action(dashboardDevCommand);
|
|
4246
4386
|
dashboard.command("deploy").description("Deploy an HTML or markdown file as a hosted dashboard").argument("<file>", "Path to HTML or markdown file").option("--slug <slug>", "Dashboard slug (defaults to filename)").option("--title <title>", "Dashboard title (defaults to <title> tag or slug)").action(dashboardDeployCommand);
|
|
4247
4387
|
dashboard.command("list").description("List deployed dashboards").action(dashboardListCommand);
|
|
4248
4388
|
dashboard.command("remove").description("Remove a deployed dashboard").argument("<slug>", "Dashboard slug to remove").option("--force", "Skip confirmation prompt").action(dashboardRemoveCommand);
|
|
4249
4389
|
dashboard.command("open").description("Open a deployed dashboard in the browser").argument("<slug>", "Dashboard slug to open").action(dashboardOpenCommand);
|
|
4390
|
+
const theme = program.command("theme").description("Manage organization dashboard theme");
|
|
4391
|
+
theme.command("get").description("Show the current organization theme").action(themeGetCommand);
|
|
4392
|
+
theme.command("set").description("Set the organization theme from a YAML or JSON file").argument("<file>", "Path to theme YAML or JSON file").option("--dry-run", "Validate and preview without uploading").action(themeSetCommand);
|
|
4393
|
+
theme.command("reset").description("Reset the organization theme to defaults").option("--force", "Skip confirmation prompt").action(themeResetCommand);
|
|
4250
4394
|
const metabase = program.command("metabase").description("Connect to and explore Metabase content");
|
|
4251
4395
|
metabase.command("connect").description("Configure Metabase API connection").option("--url <url>", "Metabase instance URL").option("--api-key <key>", "Metabase API key").option("--force", "Overwrite existing configuration").action(metabaseConnectCommand);
|
|
4252
4396
|
metabase.command("explore").description("Browse Metabase databases, collections, cards, and dashboards").argument("[resource]", "databases, collections, cards, dashboards, card, dashboard, database, table, collection").argument("[id]", "Resource ID (e.g. card <id>, dashboard <id>, database <id>, table <id>, collection <id>)").action(metabaseExploreCommand);
|
|
@@ -55,6 +55,7 @@ description: Monthly trends # Optional
|
|
|
55
55
|
|-------|----------|-------------|
|
|
56
56
|
| `title` | Yes | Dashboard title displayed in the viewer and listings |
|
|
57
57
|
| `description` | No | Short description shown in dashboard listings |
|
|
58
|
+
| `theme` | No | Theme overrides for this dashboard (palette, colors, chartHeight) |
|
|
58
59
|
|
|
59
60
|
## Local Preview
|
|
60
61
|
|
|
@@ -109,4 +110,5 @@ Dashboard queries respect the same governance policies as all other queries. Whe
|
|
|
109
110
|
- [Queries](dashboards.queries) — query syntax and properties
|
|
110
111
|
- [Components](dashboards.components) — chart and display components
|
|
111
112
|
- [Inputs](dashboards.inputs) — interactive filters
|
|
113
|
+
- [Theming](dashboards.theming) — customize colors, palettes, and styles
|
|
112
114
|
- [Examples](dashboards.examples) — complete dashboard examples
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Theming
|
|
2
|
+
|
|
3
|
+
> Customize dashboard colors, chart palettes, and visual styles at the org or dashboard level.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Dashboards support two levels of theming:
|
|
8
|
+
|
|
9
|
+
- **Organization theme** — applies to all dashboards in the org
|
|
10
|
+
- **Dashboard theme** — per-file override via frontmatter
|
|
11
|
+
|
|
12
|
+
The cascade order is: defaults → org theme → dashboard frontmatter theme. Each level deep-merges into the previous, so you only need to specify the properties you want to change.
|
|
13
|
+
|
|
14
|
+
## Theme Properties
|
|
15
|
+
|
|
16
|
+
A theme object can include any combination of these properties:
|
|
17
|
+
|
|
18
|
+
### `palette`
|
|
19
|
+
|
|
20
|
+
The color palette used for chart series. Can be a named palette or an array of hex colors.
|
|
21
|
+
|
|
22
|
+
**Named palettes:**
|
|
23
|
+
|
|
24
|
+
| Name | Colors | Best for |
|
|
25
|
+
|------|--------|----------|
|
|
26
|
+
| `tableau` | 10 balanced colors | General use (default) |
|
|
27
|
+
| `default` | 8 saturated Tailwind colors | Bold dashboards |
|
|
28
|
+
| `observable` | 10 modern vibrant colors | Data-heavy visualizations |
|
|
29
|
+
| `metabase` | 8 soft muted colors | Friendly reports |
|
|
30
|
+
|
|
31
|
+
**Custom palette:**
|
|
32
|
+
|
|
33
|
+
```yaml
|
|
34
|
+
palette: ["#2563eb", "#dc2626", "#16a34a", "#ca8a04"]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### `chartHeight`
|
|
38
|
+
|
|
39
|
+
Height of chart components in pixels. Default: `320`.
|
|
40
|
+
|
|
41
|
+
```yaml
|
|
42
|
+
chartHeight: 400
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### `colors`
|
|
46
|
+
|
|
47
|
+
Override individual color tokens that control the dashboard UI. Only specify the tokens you want to change — unspecified tokens keep their defaults.
|
|
48
|
+
|
|
49
|
+
| Token | Description |
|
|
50
|
+
|-------|-------------|
|
|
51
|
+
| `bg` | Page background |
|
|
52
|
+
| `bgMuted` | Muted/secondary background |
|
|
53
|
+
| `bgCard` | Card background |
|
|
54
|
+
| `border` | Default border color |
|
|
55
|
+
| `text` | Primary text |
|
|
56
|
+
| `textMuted` | Secondary/muted text |
|
|
57
|
+
| `textTitle` | Heading text |
|
|
58
|
+
| `textLabel` | Label text (axes, legends) |
|
|
59
|
+
| `shadow` | Card shadow |
|
|
60
|
+
| `radius` | Border radius |
|
|
61
|
+
| `gridLine` | Chart grid lines |
|
|
62
|
+
| `legendText` | Chart legend text |
|
|
63
|
+
| `tooltip.bg` | Tooltip background |
|
|
64
|
+
| `tooltip.border` | Tooltip border |
|
|
65
|
+
| `tooltip.text` | Tooltip text |
|
|
66
|
+
| `tooltip.shadow` | Tooltip shadow |
|
|
67
|
+
| `table.headerBg` | Data table header background |
|
|
68
|
+
| `table.hoverBg` | Data table row hover |
|
|
69
|
+
|
|
70
|
+
## Organization Theme
|
|
71
|
+
|
|
72
|
+
Set a theme that applies to all dashboards in your organization.
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# View current theme
|
|
76
|
+
bon theme get
|
|
77
|
+
|
|
78
|
+
# Set theme from a file
|
|
79
|
+
bon theme set theme.yml
|
|
80
|
+
|
|
81
|
+
# Validate without uploading
|
|
82
|
+
bon theme set theme.yml --dry-run
|
|
83
|
+
|
|
84
|
+
# Reset to defaults
|
|
85
|
+
bon theme reset
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Example `theme.yml`
|
|
89
|
+
|
|
90
|
+
```yaml
|
|
91
|
+
palette: observable
|
|
92
|
+
chartHeight: 360
|
|
93
|
+
colors:
|
|
94
|
+
bgCard: "#1e293b"
|
|
95
|
+
border: "#334155"
|
|
96
|
+
gridLine: "#334155"
|
|
97
|
+
tooltip:
|
|
98
|
+
bg: "#1e293b"
|
|
99
|
+
border: "#475569"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Example with custom palette
|
|
103
|
+
|
|
104
|
+
```yaml
|
|
105
|
+
palette:
|
|
106
|
+
- "#2563eb"
|
|
107
|
+
- "#dc2626"
|
|
108
|
+
- "#16a34a"
|
|
109
|
+
- "#ca8a04"
|
|
110
|
+
- "#9333ea"
|
|
111
|
+
chartHeight: 300
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Dashboard Theme (Frontmatter Override)
|
|
115
|
+
|
|
116
|
+
Override the org theme for a specific dashboard by adding `theme` to the frontmatter:
|
|
117
|
+
|
|
118
|
+
```yaml
|
|
119
|
+
---
|
|
120
|
+
title: Revenue Dashboard
|
|
121
|
+
theme:
|
|
122
|
+
palette: metabase
|
|
123
|
+
colors:
|
|
124
|
+
bgCard: "#f0f9ff"
|
|
125
|
+
---
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
This overrides the org theme for this dashboard only. Properties not specified in the dashboard theme inherit from the org theme (or defaults if no org theme is set).
|
|
129
|
+
|
|
130
|
+
### Combining org and dashboard themes
|
|
131
|
+
|
|
132
|
+
If your org theme uses the Observable palette and a dashboard specifies `palette: metabase`, that dashboard uses Metabase colors while keeping all other org theme settings (chartHeight, colors, etc.).
|
|
133
|
+
|
|
134
|
+
## Local Preview
|
|
135
|
+
|
|
136
|
+
Preview dashboards with theming applied locally:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
# Uses deployed org theme (fetched from API)
|
|
140
|
+
bon dashboard dev revenue.md
|
|
141
|
+
|
|
142
|
+
# Uses a local theme file instead
|
|
143
|
+
bon dashboard dev revenue.md --theme theme.yml
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The `--theme` flag watches the theme file for changes — edit and save to see updates live.
|
|
147
|
+
|
|
148
|
+
## See Also
|
|
149
|
+
|
|
150
|
+
- [Dashboards](dashboards) — format and deployment
|
|
151
|
+
- [Components](dashboards.components) — chart reference
|
|
152
|
+
- [Examples](dashboards.examples) — complete examples
|
|
@@ -17,7 +17,7 @@ Alternative CDN:
|
|
|
17
17
|
|
|
18
18
|
Pin a specific version:
|
|
19
19
|
```html
|
|
20
|
-
<script src="https://cdn.jsdelivr.net/npm/@bonnard/sdk@
|
|
20
|
+
<script src="https://cdn.jsdelivr.net/npm/@bonnard/sdk@latest/dist/bonnard.iife.js"></script>
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
## First query
|
|
@@ -119,6 +119,16 @@ Options:
|
|
|
119
119
|
- `--slug <slug>` — custom URL slug (default: derived from filename)
|
|
120
120
|
- `--title <title>` — override frontmatter title
|
|
121
121
|
|
|
122
|
+
## Theming (Optional)
|
|
123
|
+
|
|
124
|
+
Customize colors and palettes:
|
|
125
|
+
|
|
126
|
+
- **Per-dashboard**: Add `theme:` to frontmatter (e.g. `theme: { palette: observable }`)
|
|
127
|
+
- **Org-wide**: Create a `theme.yml` and run `bon theme set theme.yml`
|
|
128
|
+
- **Preview locally**: `bon dashboard dev dashboard.md --theme theme.yml`
|
|
129
|
+
|
|
130
|
+
See `bon docs dashboards.theming` for palette names, color tokens, and examples.
|
|
131
|
+
|
|
122
132
|
## Phase 6: View Live
|
|
123
133
|
|
|
124
134
|
Open the deployed dashboard in the browser:
|
|
@@ -118,6 +118,16 @@ Options:
|
|
|
118
118
|
- `--slug <slug>` — custom URL slug (default: derived from filename)
|
|
119
119
|
- `--title <title>` — override frontmatter title
|
|
120
120
|
|
|
121
|
+
## Theming (Optional)
|
|
122
|
+
|
|
123
|
+
Customize colors and palettes:
|
|
124
|
+
|
|
125
|
+
- **Per-dashboard**: Add `theme:` to frontmatter (e.g. `theme: { palette: observable }`)
|
|
126
|
+
- **Org-wide**: Create a `theme.yml` and run `bon theme set theme.yml`
|
|
127
|
+
- **Preview locally**: `bon dashboard dev dashboard.md --theme theme.yml`
|
|
128
|
+
|
|
129
|
+
See `bon docs dashboards.theming` for palette names, color tokens, and examples.
|
|
130
|
+
|
|
121
131
|
## Phase 6: View Live
|
|
122
132
|
|
|
123
133
|
Open the deployed dashboard in the browser:
|
|
@@ -78,6 +78,9 @@ All tables are in the `contoso` schema. The datasource is named `contoso_demo`.
|
|
|
78
78
|
| `bon dashboard list` | List deployed dashboards with URLs |
|
|
79
79
|
| `bon dashboard remove <slug>` | Remove a deployed dashboard |
|
|
80
80
|
| `bon dashboard open <slug>` | Open a deployed dashboard in the browser |
|
|
81
|
+
| `bon theme get` | Show current org dashboard theme |
|
|
82
|
+
| `bon theme set <file>` | Set org theme from YAML/JSON file |
|
|
83
|
+
| `bon theme reset` | Reset org theme to defaults |
|
|
81
84
|
| `bon metabase connect` | Connect to a Metabase instance (API key) |
|
|
82
85
|
| `bon metabase analyze` | Generate analysis report for semantic layer planning |
|
|
83
86
|
| `bon metabase explore` | Browse Metabase databases, collections, cards, dashboards |
|