basalt-ui 1.23.0 → 1.24.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/AGENTS.md +31 -17
- package/CHANGELOG.md +23 -0
- package/MIGRATING.md +222 -6
- package/README.md +159 -49
- package/agent/rules/basalt-app.md +8 -2
- package/agent/rules/basalt-charts.md +38 -2
- package/agent/rules/basalt-content.md +3 -3
- package/agent/rules/basalt-data.md +45 -13
- package/agent/rules/basalt-mantine.md +4 -4
- package/agent/rules/basalt-query.md +57 -0
- package/agent/rules/basalt-tokens.md +18 -5
- package/agent/skills/basalt-app/SKILL.md +32 -12
- package/agent/skills/basalt-charts/SKILL.md +4 -2
- package/agent/templates/CLAUDE-block.md.tpl +3 -2
- package/dist/charts/kinds/BandStrip.d.ts +20 -6
- package/dist/charts/kinds/BandStrip.d.ts.map +1 -1
- package/dist/charts/kinds/BandStrip.js +59 -6
- package/dist/charts/kinds/MirroredBars.d.ts +4 -2
- package/dist/charts/kinds/MirroredBars.d.ts.map +1 -1
- package/dist/charts/kinds/MirroredBars.js +13 -0
- package/dist/cli/index.d.ts +4 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +269 -27
- package/dist/dashboard/empty-state.d.ts +6 -2
- package/dist/dashboard/empty-state.d.ts.map +1 -1
- package/dist/dashboard/empty-state.js +1 -1
- package/dist/dashboard/index.d.ts +1 -0
- package/dist/dashboard/index.d.ts.map +1 -1
- package/dist/dashboard/index.js +8 -0
- package/dist/dashboard/query-state.d.ts +96 -0
- package/dist/dashboard/query-state.d.ts.map +1 -0
- package/dist/dashboard/query-state.js +123 -0
- package/dist/dashboard/query-state.test.d.ts +2 -0
- package/dist/dashboard/query-state.test.d.ts.map +1 -0
- package/dist/data/data-table.d.ts +61 -4
- package/dist/data/data-table.d.ts.map +1 -1
- package/dist/data/data-table.js +41 -4
- package/dist/data/data-table.test.d.ts +2 -0
- package/dist/data/data-table.test.d.ts.map +1 -0
- package/dist/data/table.d.ts +1 -1
- package/dist/data/table.d.ts.map +1 -1
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +50 -9
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/query/error-message.d.ts +41 -0
- package/dist/query/error-message.d.ts.map +1 -0
- package/dist/query/error-message.js +30 -0
- package/dist/query/error-message.test.d.ts +2 -0
- package/dist/query/error-message.test.d.ts.map +1 -0
- package/dist/query/index.d.ts +1 -19
- package/dist/query/index.d.ts.map +1 -1
- package/dist/query/index.js +2 -14
- package/dist/surfaces.d.ts +1 -1
- package/dist/surfaces.js +1 -1
- package/llms.txt +3 -3
- package/package.json +1 -1
- package/src/charts/kinds/BandStrip.test.tsx +87 -5
- package/src/charts/kinds/BandStrip.tsx +105 -12
- package/src/charts/kinds/MirroredBars.test.tsx +39 -0
- package/src/charts/kinds/MirroredBars.tsx +38 -2
- package/src/cli/check-theme.test.ts +34 -0
- package/src/cli/help.test.ts +74 -1
- package/src/cli/index.ts +496 -41
- package/src/cli/tokens-css.test.ts +129 -0
- package/src/cli/toolchain-wiring.test.ts +146 -0
- package/src/dashboard/empty-state.tsx +9 -3
- package/src/dashboard/index.ts +11 -0
- package/src/dashboard/query-state.test.tsx +152 -0
- package/src/dashboard/query-state.tsx +272 -0
- package/src/data/data-table.test.tsx +158 -0
- package/src/data/data-table.tsx +123 -5
- package/src/data/table.ts +6 -1
- package/src/guard/check-source.test.ts +214 -0
- package/src/guard/index.ts +169 -15
- package/src/index.ts +9 -0
- package/src/query/error-message.test.ts +67 -0
- package/src/query/error-message.ts +70 -0
- package/src/query/index.ts +2 -34
- package/src/surfaces.ts +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -89,7 +89,7 @@ function readBasaltConfig(cwd) {
|
|
|
89
89
|
return {};
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
|
-
const SCANNABLE_EXT = /\.(?:tsx?|css|html?|webmanifest)$/;
|
|
92
|
+
const SCANNABLE_EXT = /\.(?:tsx?|jsx|astro|vue|css|html?|webmanifest)$/;
|
|
93
93
|
function walkSourceFiles(dir, out = []) {
|
|
94
94
|
let entries;
|
|
95
95
|
try {
|
|
@@ -170,6 +170,18 @@ function descendantProjects(cwd, depth = 2) {
|
|
|
170
170
|
}
|
|
171
171
|
return out;
|
|
172
172
|
}
|
|
173
|
+
function ascendantProject(cwd) {
|
|
174
|
+
const repoRoot = findRepoRoot(cwd);
|
|
175
|
+
if (repoRoot === cwd) return null;
|
|
176
|
+
let current = dirname(cwd);
|
|
177
|
+
for (; ; ) {
|
|
178
|
+
if (hasBasaltProject(current)) return current;
|
|
179
|
+
if (current === repoRoot) return null;
|
|
180
|
+
const parent = dirname(current);
|
|
181
|
+
if (parent === current) return null;
|
|
182
|
+
current = parent;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
173
185
|
function resolveProjectDir(cwd) {
|
|
174
186
|
const override = process.env["BASALT_CWD"];
|
|
175
187
|
if (override !== void 0 && override.length > 0) {
|
|
@@ -183,6 +195,8 @@ function resolveProjectDir(cwd) {
|
|
|
183
195
|
return { dir: candidates[0], relocatedFrom: cwd, ambiguous: null };
|
|
184
196
|
}
|
|
185
197
|
if (candidates.length > 1) return { dir: cwd, relocatedFrom: null, ambiguous: candidates };
|
|
198
|
+
const above = ascendantProject(cwd);
|
|
199
|
+
if (above !== null) return { dir: above, relocatedFrom: cwd, ambiguous: null };
|
|
186
200
|
return { dir: cwd, relocatedFrom: null, ambiguous: null };
|
|
187
201
|
}
|
|
188
202
|
function declaredProfile(cfg, flags) {
|
|
@@ -617,9 +631,13 @@ function readFrameworkVersion(pkgRoot) {
|
|
|
617
631
|
return "0.0.0";
|
|
618
632
|
}
|
|
619
633
|
}
|
|
634
|
+
function basaltBinCommand(install, cwd) {
|
|
635
|
+
if (install.dir === null) return "bunx basalt-ui";
|
|
636
|
+
const binDir = resolve(install.dir, "..", ".bin");
|
|
637
|
+
return `${relativePosix(cwd, binDir).replace(/^\.\//, "")}/basalt-ui`;
|
|
638
|
+
}
|
|
620
639
|
function buildTemplateVars(pkgRoot, cwd, cfg) {
|
|
621
640
|
const install = findBasaltInstall(cwd);
|
|
622
|
-
const binDir = install.dir === null ? null : resolve(install.dir, "..", ".bin");
|
|
623
641
|
return {
|
|
624
642
|
APP_NAME: readPackageName(cwd),
|
|
625
643
|
BASALT_VERSION: readFrameworkVersion(pkgRoot),
|
|
@@ -628,7 +646,7 @@ function buildTemplateVars(pkgRoot, cwd, cfg) {
|
|
|
628
646
|
ROOTS_GLOBS: resolveRoots(cfg).map(toRootGlob).join(" "),
|
|
629
647
|
OXLINT_PRESET_PATH: shippedAssetPath(install, cwd, "configs/oxlint.json"),
|
|
630
648
|
LEFTHOOK_PRESET_PATH: shippedAssetPath(install, cwd, "configs/lefthook.yml"),
|
|
631
|
-
BASALT_BIN:
|
|
649
|
+
BASALT_BIN: basaltBinCommand(install, cwd)
|
|
632
650
|
};
|
|
633
651
|
}
|
|
634
652
|
function fillTemplate(tpl, vars) {
|
|
@@ -1268,7 +1286,7 @@ Theme guard hook: add to .claude/settings.json \u2192 hooks.PreToolUse to catch
|
|
|
1268
1286
|
"PreToolUse": [
|
|
1269
1287
|
{
|
|
1270
1288
|
"matcher": "Write|Edit|MultiEdit",
|
|
1271
|
-
"hooks": [{ "type": "command", "command": "
|
|
1289
|
+
"hooks": [{ "type": "command", "command": "${ctx.vars.BASALT_BIN} guard-hook" }]
|
|
1272
1290
|
}
|
|
1273
1291
|
]
|
|
1274
1292
|
}`
|
|
@@ -1299,6 +1317,9 @@ function readOxlintPresetPlugins(pkgRoot) {
|
|
|
1299
1317
|
return [];
|
|
1300
1318
|
}
|
|
1301
1319
|
}
|
|
1320
|
+
function parentInstallAdvice(cwd, parent, verb) {
|
|
1321
|
+
return `This repo's install is at ${relativePosix(cwd, parent)} (${parent}) \u2014 run ${verb} there, or set BASALT_CWD to it.`;
|
|
1322
|
+
}
|
|
1302
1323
|
function findManifestAbove(dir) {
|
|
1303
1324
|
let current = resolve(dir, "..");
|
|
1304
1325
|
for (; ; ) {
|
|
@@ -1337,7 +1358,7 @@ function sync(opts = {}, invocationCwd = process.cwd()) {
|
|
|
1337
1358
|
const elsewhere = findManifestAbove(cwd);
|
|
1338
1359
|
const wouldWrite = managedFiles(resolvePeerFlags(cwd, {}), resolvePlacement(cwd)).length;
|
|
1339
1360
|
console.error(
|
|
1340
|
-
`\u2716 basalt-ui sync: no ${MANIFEST_PATH} at ${cwd} \u2014 refusing to scaffold. \`sync\` refreshes an EXISTING basalt install; creating one is \`basalt-ui init\`'s decision, and continuing here would write ${wouldWrite} managed file(s) into this directory.` + (elsewhere === null ? " No manifest exists at any ancestor either \u2014 run `basalt-ui init` here if this package really is meant to be a consumer." : `
|
|
1361
|
+
`\u2716 basalt-ui sync: no ${MANIFEST_PATH} at ${cwd} \u2014 refusing to scaffold. \`sync\` refreshes an EXISTING basalt install; creating one is \`basalt-ui init\`'s decision, and continuing here would write ${wouldWrite} managed file(s) into this directory.` + (elsewhere === null ? " No manifest exists at any ancestor either \u2014 run `basalt-ui init` here if this package really is meant to be a consumer." : ` ${parentInstallAdvice(cwd, elsewhere, "sync")}`)
|
|
1341
1362
|
);
|
|
1342
1363
|
return 1;
|
|
1343
1364
|
}
|
|
@@ -1706,6 +1727,7 @@ basalt-ui doctor \u2014 ${cwd}
|
|
|
1706
1727
|
}
|
|
1707
1728
|
const manifestAbs = resolve(cwd, MANIFEST_PATH);
|
|
1708
1729
|
const manifestExists = existsSync(manifestAbs);
|
|
1730
|
+
const parentInstall = manifestExists ? null : findManifestAbove(cwd);
|
|
1709
1731
|
const profile = inferredProfile(cwd, cfg, flags);
|
|
1710
1732
|
if (profile === "tokens-only") {
|
|
1711
1733
|
lines.push(
|
|
@@ -1721,6 +1743,10 @@ basalt-ui doctor \u2014 ${cwd}
|
|
|
1721
1743
|
pass(`${MANIFEST_PATH}: n/a \u2014 a tokens-only consumer has no scaffold to reconcile`);
|
|
1722
1744
|
} else if (manifestExists) {
|
|
1723
1745
|
pass(`${MANIFEST_PATH} exists`);
|
|
1746
|
+
} else if (parentInstall !== null) {
|
|
1747
|
+
fail(
|
|
1748
|
+
`${MANIFEST_PATH} missing here, but this is not an unscaffolded consumer. ${parentInstallAdvice(cwd, parentInstall, "doctor")} \`basalt-ui init\` is NOT the fix: it would scaffold a SECOND consumer beside the real one.`
|
|
1749
|
+
);
|
|
1724
1750
|
} else {
|
|
1725
1751
|
fail(`${MANIFEST_PATH} missing \u2014 run \`basalt-ui init\` to scaffold the consumer repo`);
|
|
1726
1752
|
}
|
|
@@ -1816,7 +1842,7 @@ basalt-ui doctor \u2014 ${cwd}
|
|
|
1816
1842
|
const oxlintrcRaw = readIfExists(resolve(cwd, ".oxlintrc.json"));
|
|
1817
1843
|
if (oxlintrcRaw === null) {
|
|
1818
1844
|
fail(
|
|
1819
|
-
".oxlintrc.json missing \u2014 the shipped oxlint preset (the basalt/* design rules) is not active. Run `basalt-ui init` to seed it."
|
|
1845
|
+
".oxlintrc.json missing \u2014 the shipped oxlint preset (the basalt/* design rules) is not active. " + (parentInstall === null ? "Run `basalt-ui init` to seed it." : `${parentInstallAdvice(cwd, parentInstall, "doctor")} The preset is seeded once, at the install \u2014 not per package.`)
|
|
1820
1846
|
);
|
|
1821
1847
|
} else {
|
|
1822
1848
|
const parsed = parseJsonc(oxlintrcRaw);
|
|
@@ -1864,30 +1890,52 @@ basalt-ui doctor \u2014 ${cwd}
|
|
|
1864
1890
|
);
|
|
1865
1891
|
} else if (lefthookGate.kind === "absent") {
|
|
1866
1892
|
warn(
|
|
1867
|
-
`${lefthookGate.file}: \`lefthook dump\` resolves the merged config and NO pre-commit command runs check-theme \u2014 the theme guard is not gating commits. Either add "extends: [${lefthookCorrect}]", or add your own command running
|
|
1893
|
+
`${lefthookGate.file}: \`lefthook dump\` resolves the merged config and NO pre-commit command runs check-theme \u2014 the theme guard is not gating commits. Either add "extends: [${lefthookCorrect}]", or add your own command running \`${basaltBinCommand(install, cwd)} check-theme\` (with \`root:\` if it must run in a subdirectory).`
|
|
1868
1894
|
);
|
|
1869
1895
|
} else {
|
|
1870
1896
|
warn(
|
|
1871
|
-
`${lefthookGate.file} names no basalt gate and \`lefthook dump\` could not be run here, so this check cannot tell a missing gate from one wired through an \`include:\` or a remote config it never read \u2014 treat it as advisory. If the guard really is unwired, add "extends: [${lefthookCorrect}]" or your own command running
|
|
1897
|
+
`${lefthookGate.file} names no basalt gate and \`lefthook dump\` could not be run here, so this check cannot tell a missing gate from one wired through an \`include:\` or a remote config it never read \u2014 treat it as advisory. If the guard really is unwired, add "extends: [${lefthookCorrect}]" or your own command running \`${basaltBinCommand(install, cwd)} check-theme\`.`
|
|
1872
1898
|
);
|
|
1873
1899
|
}
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
const
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
"
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1900
|
+
if (profile === "framework") {
|
|
1901
|
+
const appDir = findAppPluginDir(cwd, cfg);
|
|
1902
|
+
const iconsIn = appDir ?? cwd;
|
|
1903
|
+
const icons = readAppIconsOption(iconsIn);
|
|
1904
|
+
const here = appDir === null || appDir === cwd ? "" : `${relativePosix(cwd, appDir)}/`;
|
|
1905
|
+
const publicDir = resolve(iconsIn, "public");
|
|
1906
|
+
if (appDir === null && !existsSync(publicDir)) {
|
|
1907
|
+
pass("app icons: no `basaltAppPlugin(` under this project and no public/ \u2014 nothing to check");
|
|
1908
|
+
} else if (icons.kind === "none") {
|
|
1909
|
+
pass("basaltAppPlugin is configured with no icons \u2014 nothing to check");
|
|
1910
|
+
} else if (!existsSync(publicDir)) {
|
|
1911
|
+
skip(
|
|
1912
|
+
`app icons \u2014 ${here === "" ? "this package" : here.replace(/\/$/, "")} configures basaltAppPlugin, but there is no \`public/\` beside its vite config, so the files it references cannot be located. Use Vite's public-dir convention, or set \`icons: false\` if this app ships none.`
|
|
1913
|
+
);
|
|
1914
|
+
} else if (icons.kind === "explicit") {
|
|
1915
|
+
const missing = icons.sources.filter(
|
|
1916
|
+
(src) => !existsSync(resolve(publicDir, src.replace(/^\//, "")))
|
|
1888
1917
|
);
|
|
1918
|
+
if (missing.length > 0) {
|
|
1919
|
+
warn(
|
|
1920
|
+
`basaltAppPlugin declares icon(s) that are not in ${here}public/: ${missing.join(", ")} \u2014 the manifest and the head links will point at a 404. Place them, or drop them from the \`icons\` array.`
|
|
1921
|
+
);
|
|
1922
|
+
} else {
|
|
1923
|
+
pass(
|
|
1924
|
+
`${here}public/ has all ${icons.sources.length} icon file(s) basaltAppPlugin's \`icons\` option names`
|
|
1925
|
+
);
|
|
1926
|
+
}
|
|
1889
1927
|
} else {
|
|
1890
|
-
|
|
1928
|
+
const dir = icons.kind === "default" ? icons.dir : null;
|
|
1929
|
+
const iconRoot = dir === null ? publicDir : resolve(publicDir, dir.replace(/^\//, ""));
|
|
1930
|
+
const shown = dir === null ? `${here}public/` : `${here}public/${dir.replace(/^\//, "")}`;
|
|
1931
|
+
const missingIcons = DEFAULT_APP_ICON_FILES.filter((f) => !existsSync(resolve(iconRoot, f)));
|
|
1932
|
+
if (missingIcons.length > 0) {
|
|
1933
|
+
warn(
|
|
1934
|
+
`${shown} is missing basaltAppPlugin icon file(s): ${missingIcons.join(", ")} \u2014 generate them (e.g. via realfavicongenerator.net) and place them at the ${shown} root, or name the icons this app really has via the plugin's \`icons\` array.`
|
|
1935
|
+
);
|
|
1936
|
+
} else {
|
|
1937
|
+
pass(`${shown} has all of basaltAppPlugin's default icon files`);
|
|
1938
|
+
}
|
|
1891
1939
|
}
|
|
1892
1940
|
}
|
|
1893
1941
|
const workspaceWalk = collectWorkspacePackages(cwd);
|
|
@@ -2008,6 +2056,93 @@ basalt-ui v${pkgVersion} \u2014 published surface
|
|
|
2008
2056
|
console.log("");
|
|
2009
2057
|
return 0;
|
|
2010
2058
|
}
|
|
2059
|
+
const DEFAULT_APP_ICON_FILES = [
|
|
2060
|
+
"favicon.ico",
|
|
2061
|
+
"favicon.svg",
|
|
2062
|
+
"favicon-96x96.png",
|
|
2063
|
+
"apple-touch-icon.png",
|
|
2064
|
+
"web-app-manifest-192x192.png",
|
|
2065
|
+
"web-app-manifest-512x512.png"
|
|
2066
|
+
];
|
|
2067
|
+
const VITE_CONFIG_FILES = [
|
|
2068
|
+
"vite.config.ts",
|
|
2069
|
+
"vite.config.mts",
|
|
2070
|
+
"vite.config.cts",
|
|
2071
|
+
"vite.config.js",
|
|
2072
|
+
"vite.config.mjs",
|
|
2073
|
+
"vite.config.cjs"
|
|
2074
|
+
];
|
|
2075
|
+
function findAppPluginDir(cwd, cfg) {
|
|
2076
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2077
|
+
const candidates = [];
|
|
2078
|
+
const add = (dir) => {
|
|
2079
|
+
if (seen.has(dir)) return;
|
|
2080
|
+
seen.add(dir);
|
|
2081
|
+
candidates.push(dir);
|
|
2082
|
+
};
|
|
2083
|
+
add(cwd);
|
|
2084
|
+
for (const root of resolveRoots(cfg)) {
|
|
2085
|
+
let current = resolve(cwd, root);
|
|
2086
|
+
while (current !== cwd && !relative(cwd, current).startsWith("..")) {
|
|
2087
|
+
add(current);
|
|
2088
|
+
const parent = dirname(current);
|
|
2089
|
+
if (parent === current) break;
|
|
2090
|
+
current = parent;
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
for (const dir of candidates) {
|
|
2094
|
+
const configPath = VITE_CONFIG_FILES.map((f) => resolve(dir, f)).find((f) => existsSync(f));
|
|
2095
|
+
if (configPath === void 0) continue;
|
|
2096
|
+
if (readIfExists(configPath)?.includes("basaltAppPlugin(") === true) return dir;
|
|
2097
|
+
}
|
|
2098
|
+
return null;
|
|
2099
|
+
}
|
|
2100
|
+
function balancedSpan(text, open) {
|
|
2101
|
+
const pairs = { "(": ")", "{": "}", "[": "]" };
|
|
2102
|
+
const closer = pairs[text[open]];
|
|
2103
|
+
if (closer === void 0) return null;
|
|
2104
|
+
let depth = 0;
|
|
2105
|
+
for (let i = open; i < text.length; i++) {
|
|
2106
|
+
const ch = text[i];
|
|
2107
|
+
if (ch === text[open]) depth++;
|
|
2108
|
+
else if (ch === closer) {
|
|
2109
|
+
depth--;
|
|
2110
|
+
if (depth === 0) return text.slice(open, i + 1);
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
return null;
|
|
2114
|
+
}
|
|
2115
|
+
function readAppIconsOption(cwd) {
|
|
2116
|
+
const configPath = VITE_CONFIG_FILES.map((f) => resolve(cwd, f)).find((f) => existsSync(f));
|
|
2117
|
+
if (configPath === void 0) return { kind: "unknown" };
|
|
2118
|
+
const text = readIfExists(configPath);
|
|
2119
|
+
if (text === null) return { kind: "unknown" };
|
|
2120
|
+
const call = text.indexOf("basaltAppPlugin(");
|
|
2121
|
+
if (call === -1) return { kind: "unknown" };
|
|
2122
|
+
const args = balancedSpan(text, call + "basaltAppPlugin".length);
|
|
2123
|
+
if (args === null) return { kind: "unknown" };
|
|
2124
|
+
const key = args.search(/\bicons\s*:/);
|
|
2125
|
+
if (key === -1) return { kind: "default", dir: null };
|
|
2126
|
+
const valueStart = args.indexOf(":", key) + 1;
|
|
2127
|
+
const rest = args.slice(valueStart).trimStart();
|
|
2128
|
+
if (rest.startsWith("false")) return { kind: "none" };
|
|
2129
|
+
const offset = args.length - rest.length;
|
|
2130
|
+
if (rest.startsWith("[")) {
|
|
2131
|
+
const list = balancedSpan(args, offset);
|
|
2132
|
+
if (list === null) return { kind: "unknown" };
|
|
2133
|
+
const sources = [...list.matchAll(/\bsrc\s*:\s*['"`]([^'"`]+)['"`]/g)].map(
|
|
2134
|
+
(m) => m[1]
|
|
2135
|
+
);
|
|
2136
|
+
return sources.length === 0 ? { kind: "none" } : { kind: "explicit", sources };
|
|
2137
|
+
}
|
|
2138
|
+
if (rest.startsWith("{")) {
|
|
2139
|
+
const obj = balancedSpan(args, offset);
|
|
2140
|
+
if (obj === null) return { kind: "unknown" };
|
|
2141
|
+
const dir = /\bdir\s*:\s*['"`]([^'"`]+)['"`]/.exec(obj);
|
|
2142
|
+
return { kind: "default", dir: dir === null ? null : dir[1] };
|
|
2143
|
+
}
|
|
2144
|
+
return { kind: "unknown" };
|
|
2145
|
+
}
|
|
2011
2146
|
function flagValue(flags, name) {
|
|
2012
2147
|
const i = flags.indexOf(name);
|
|
2013
2148
|
if (i === -1) return void 0;
|
|
@@ -2021,6 +2156,36 @@ function generatedHeader(version, command, flags) {
|
|
|
2021
2156
|
/* basalt-ui ${version} \u2014 \`basalt-ui ${invocation}\` */
|
|
2022
2157
|
`;
|
|
2023
2158
|
}
|
|
2159
|
+
const PROVENANCE_VERSION = /^(\/\* basalt-ui )(\d+\.\d+\.\d+\S*)( — `basalt-ui (?:tokens:css|fonts:css)[^`]*` \*\/)$/;
|
|
2160
|
+
function provenanceVersion(css) {
|
|
2161
|
+
const match = PROVENANCE_VERSION.exec(css.split("\n")[1] ?? "");
|
|
2162
|
+
return match === null ? null : match[2];
|
|
2163
|
+
}
|
|
2164
|
+
function withoutProvenanceVersion(css) {
|
|
2165
|
+
const lines = css.split("\n");
|
|
2166
|
+
const line = lines[1];
|
|
2167
|
+
if (line === void 0) return css;
|
|
2168
|
+
const match = PROVENANCE_VERSION.exec(line);
|
|
2169
|
+
if (match === null) return css;
|
|
2170
|
+
lines[1] = `${match[1]}<version>${match[3]}`;
|
|
2171
|
+
return lines.join("\n");
|
|
2172
|
+
}
|
|
2173
|
+
function versionTriple(version) {
|
|
2174
|
+
const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version);
|
|
2175
|
+
return match === null ? null : [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
2176
|
+
}
|
|
2177
|
+
function describeVersionSkew(onDisk, current) {
|
|
2178
|
+
const a = versionTriple(onDisk);
|
|
2179
|
+
const b = versionTriple(current);
|
|
2180
|
+
if (a === null || b === null) return "a different";
|
|
2181
|
+
for (let i = 0; i < 3; i++) {
|
|
2182
|
+
const left = a[i];
|
|
2183
|
+
const right = b[i];
|
|
2184
|
+
if (left < right) return "an older";
|
|
2185
|
+
if (left > right) return "a newer";
|
|
2186
|
+
}
|
|
2187
|
+
return "a different";
|
|
2188
|
+
}
|
|
2024
2189
|
function normalizeColorFunctions(css) {
|
|
2025
2190
|
return css.replace(/\b(rgba?|hsla?)\(([^()]*)\)/g, (_match, fn, args) => {
|
|
2026
2191
|
const parts = args.split(",").map((part) => normalizeCssNumber(part.trim()));
|
|
@@ -2055,12 +2220,27 @@ function emitGeneratedCss(content, flags, cwd, command) {
|
|
|
2055
2220
|
);
|
|
2056
2221
|
return 1;
|
|
2057
2222
|
}
|
|
2058
|
-
if (onDisk === content) {
|
|
2059
|
-
|
|
2223
|
+
if (withoutProvenanceVersion(onDisk) === withoutProvenanceVersion(content)) {
|
|
2224
|
+
const diskVersion = provenanceVersion(onDisk);
|
|
2225
|
+
const currentVersion = provenanceVersion(content);
|
|
2226
|
+
const skew = diskVersion !== null && currentVersion !== null && diskVersion !== currentVersion ? ` (Its provenance line names basalt-ui ${diskVersion}, ${describeVersionSkew(diskVersion, currentVersion)} release than the ${currentVersion} that just ran \u2014 only that version token is excluded from the comparison. Re-run without --check whenever you want it refreshed.)` : "";
|
|
2227
|
+
console.log(`\u2713 ${command} --check: ${shown} is up to date.${skew}`);
|
|
2060
2228
|
return 0;
|
|
2061
2229
|
}
|
|
2230
|
+
const diskLines = onDisk.split("\n");
|
|
2231
|
+
const wantLines = content.split("\n");
|
|
2232
|
+
const provenanceOnly = diskLines.length === wantLines.length && diskLines.every((line, i) => i === 1 || line === wantLines[i]) && diskLines[1] !== wantLines[1];
|
|
2233
|
+
if (provenanceOnly) {
|
|
2234
|
+
console.error(
|
|
2235
|
+
`\u2716 ${command} --check: ${shown}'s \`@generated\` provenance line does not match the command that produced this content \u2014 the token values agree, line 2 does not.
|
|
2236
|
+
on disk: ${diskLines[1]}
|
|
2237
|
+
emitted: ${wantLines[1]}
|
|
2238
|
+
Line 1 tells the reader to regenerate with line 2, so line 2 is gated: run the emitted invocation without --check and commit the result, or change the flags this check runs with to the ones the file was really built from. Only the version token is exempt.`
|
|
2239
|
+
);
|
|
2240
|
+
return 1;
|
|
2241
|
+
}
|
|
2062
2242
|
console.error(
|
|
2063
|
-
`\u2716 ${command} --check: ${shown} differs from what \`basalt-ui ${command}\` emits today (on disk ${
|
|
2243
|
+
`\u2716 ${command} --check: ${shown} differs from what \`basalt-ui ${command}\` emits today (on disk ${diskLines.length} lines, emitted ${wantLines.length}) \u2014 re-run the same command without --check and commit the result. Only the \`@generated\` provenance line's VERSION token is excluded from this comparison, so a release alone never trips it \u2014 but its flags do.`
|
|
2064
2244
|
);
|
|
2065
2245
|
return 1;
|
|
2066
2246
|
}
|
|
@@ -2241,13 +2421,71 @@ async function guardHook(cwd = process.cwd()) {
|
|
|
2241
2421
|
}
|
|
2242
2422
|
return 0;
|
|
2243
2423
|
}
|
|
2244
|
-
const
|
|
2424
|
+
const COMMAND_FLAGS = {
|
|
2425
|
+
init: ["--with-router", "--with-query", "--merge-lint"],
|
|
2426
|
+
sync: ["--force", "--check", "--tokens-only", "--framework"],
|
|
2427
|
+
"check-theme": ["--audit-allows", "--tokens-only", "--framework"],
|
|
2428
|
+
"check-coverage": [],
|
|
2429
|
+
info: ["--json"],
|
|
2430
|
+
doctor: ["--tokens-only", "--framework"],
|
|
2431
|
+
"guard-hook": [],
|
|
2432
|
+
"tokens:css": [
|
|
2433
|
+
"--out",
|
|
2434
|
+
"--check",
|
|
2435
|
+
"--selector-attribute",
|
|
2436
|
+
"--selector-class",
|
|
2437
|
+
"--light-class",
|
|
2438
|
+
"--dark-value",
|
|
2439
|
+
"--light-value",
|
|
2440
|
+
"--default-scheme",
|
|
2441
|
+
"--media-fallback",
|
|
2442
|
+
"--only",
|
|
2443
|
+
"--no-legacy-aliases"
|
|
2444
|
+
],
|
|
2445
|
+
"fonts:css": ["--out", "--check"]
|
|
2446
|
+
};
|
|
2447
|
+
const VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
2448
|
+
"--out",
|
|
2449
|
+
"--selector-attribute",
|
|
2450
|
+
"--selector-class",
|
|
2451
|
+
"--light-class",
|
|
2452
|
+
"--dark-value",
|
|
2453
|
+
"--light-value",
|
|
2454
|
+
"--default-scheme",
|
|
2455
|
+
"--only"
|
|
2456
|
+
]);
|
|
2457
|
+
function unknownFlag(cmd, flags) {
|
|
2458
|
+
const allowed = COMMAND_FLAGS[cmd];
|
|
2459
|
+
if (allowed === void 0) return null;
|
|
2460
|
+
for (let i = 0; i < flags.length; i++) {
|
|
2461
|
+
const flag = flags[i];
|
|
2462
|
+
if (!flag.startsWith("-")) continue;
|
|
2463
|
+
if (!allowed.includes(flag)) return flag;
|
|
2464
|
+
if (VALUE_FLAGS.has(flag)) i++;
|
|
2465
|
+
}
|
|
2466
|
+
return null;
|
|
2467
|
+
}
|
|
2468
|
+
const USAGE = "Usage: basalt-ui <--version | init [--with-router] [--with-query] [--merge-lint] |\n sync [--force] [--check] [--tokens-only|--framework] |\n check-theme [--audit-allows] | check-coverage | info [--json] |\n doctor [--tokens-only|--framework] | guard-hook | tokens:css | fonts:css | help>\n\ncheck-theme [--tokens-only|--framework] [--audit-allows]\n --audit-allows reports instead of scanning: every `theme-allow` annotation and every\n `basalt.exemptRules` entry, with what each one still suppresses \u2014 proved by re-running the\n guard with that one waiver neutralized, not by reading its text. Exits 1 on a waiver that\n suppresses nothing (dead), which is what makes it usable as a CI gate.\n\ntokens:css [--out <path>] [--check] [--selector-attribute <attr> | --selector-class <class>]\n [--light-class <class>] [--dark-value <v>] [--light-value <v>]\n [--default-scheme <dark|light|none>] [--media-fallback] [--only <core|all>]\n [--no-legacy-aliases]\n Emit the --vx-* stylesheet (stdout unless --out). Defaults reproduce basalt-ui/tokens.css.\n --selector-class emits `:root.dark` instead of an attribute selector (the Tailwind convention).\n --check writes nothing and exits 1 when --out differs from what would be emitted (a CI gate).\n --no-legacy-aliases drops the deprecated camelCase spellings (--vx-accentFill and friends),\n which are emitted by default as aliases of the canonical kebab-case names.\n\nfonts:css [--out <path>] [--check]\n Emit the shipped --basalt-font-* stacks as plain CSS \u2014 the typeface half of the token layer,\n otherwise reachable only by importing styles.css.\n\ndoctor [--tokens-only|--framework]\n Auto-detects a tokens-only consumer (no manifest + no @mantine/core) and checks only what\n applies. A check that cannot RUN is reported as SKIPPED and exits non-zero.\n\nsync [--force] [--check] [--tokens-only|--framework]\n Refreshes an EXISTING install; it never creates one (that is init). A tokens-only consumer\n has no scaffold to reconcile, so sync reports n/a and exits 0 there \u2014 as doctor does.\n\ncheck-theme / doctor / sync all resolve their project the same way: they honour BASALT_CWD, and\nrelocate to the single package below the cwd carrying a basalt config when invoked from a repo\nroot that has none \u2014 several carrying one is ambiguous and exits 1 in ALL THREE.\n--tokens-only and --framework are alternatives; passing both is an error.\n\nEvery subcommand accepts --help / -h to print this message and exit without running.\n--version / -v prints the resolved package version and exits 0 \u2014 the one command that proves\nWHICH basalt-ui ran, which a `bunx` cache can otherwise make a lie. An unknown command or an\nunrecognized flag exits 1 and names what it did not understand; neither is ever ignored.";
|
|
2245
2469
|
function run(argv, cwd = process.cwd()) {
|
|
2246
2470
|
const [cmd, ...flags] = argv;
|
|
2247
2471
|
if (cmd === "help" || cmd === "--help" || cmd === "-h" || flags.includes("--help") || flags.includes("-h")) {
|
|
2248
2472
|
console.log(USAGE);
|
|
2249
2473
|
return 0;
|
|
2250
2474
|
}
|
|
2475
|
+
if (cmd === "--version" || cmd === "-v" || cmd === "version") {
|
|
2476
|
+
console.log(readFrameworkVersion(packageRoot()));
|
|
2477
|
+
return 0;
|
|
2478
|
+
}
|
|
2479
|
+
if (cmd !== void 0) {
|
|
2480
|
+
const bad = unknownFlag(cmd, flags);
|
|
2481
|
+
if (bad !== null) {
|
|
2482
|
+
console.error(
|
|
2483
|
+
`basalt-ui ${cmd}: unrecognized flag '${bad}'. Accepted: ${(COMMAND_FLAGS[cmd] ?? []).join(" ") || "(none)"} --help
|
|
2484
|
+
`
|
|
2485
|
+
);
|
|
2486
|
+
return 1;
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2251
2489
|
switch (cmd) {
|
|
2252
2490
|
case "init":
|
|
2253
2491
|
return init(cwd, {
|
|
@@ -2275,6 +2513,10 @@ function run(argv, cwd = process.cwd()) {
|
|
|
2275
2513
|
case "fonts:css":
|
|
2276
2514
|
return fontsCss(flags, cwd);
|
|
2277
2515
|
default:
|
|
2516
|
+
console.error(
|
|
2517
|
+
cmd === void 0 ? "basalt-ui: no command given.\n" : `basalt-ui: unknown command '${cmd}'.
|
|
2518
|
+
`
|
|
2519
|
+
);
|
|
2278
2520
|
console.error(USAGE);
|
|
2279
2521
|
return 1;
|
|
2280
2522
|
}
|
|
@@ -4,8 +4,12 @@ export type EmptyStateProps = {
|
|
|
4
4
|
icon?: ReactNode;
|
|
5
5
|
/** Head-font title (18px, weight 550, ink). */
|
|
6
6
|
title: string;
|
|
7
|
-
/**
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Muted 15px description, capped at ~360px so it reads as a short explanation. Optional: a
|
|
9
|
+
* compact panel ("No data yet") should not have to invent a second sentence, and five argo
|
|
10
|
+
* features wrapped this component solely to avoid doing so.
|
|
11
|
+
*/
|
|
12
|
+
description?: string;
|
|
9
13
|
/** Optional call-to-action rendered below the description. */
|
|
10
14
|
action?: ReactNode;
|
|
11
15
|
/** `'page'` (default) = generous padding for a full-page state; `'section'` = compact. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"empty-state.d.ts","sourceRoot":"","sources":["../../src/dashboard/empty-state.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,MAAM,MAAM,eAAe,GAAG;IAC5B,uFAAuF;IACvF,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAA;IACb
|
|
1
|
+
{"version":3,"file":"empty-state.d.ts","sourceRoot":"","sources":["../../src/dashboard/empty-state.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,MAAM,MAAM,eAAe,GAAG;IAC5B,uFAAuF;IACvF,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAA;IACb;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,8DAA8D;IAC9D,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,0FAA0F;IAC1F,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC7B,CAAA;AAED,wBAAgB,UAAU,CAAC,EACzB,IAAI,EACJ,KAAK,EACL,WAAW,EACX,MAAM,EACN,OAAgB,GACjB,EAAE,eAAe,+BA4BjB"}
|
|
@@ -32,7 +32,7 @@ function EmptyState({
|
|
|
32
32
|
children: title
|
|
33
33
|
}
|
|
34
34
|
),
|
|
35
|
-
/* @__PURE__ */ jsx("span", { style: { fontSize: VX.text.md, color: VX.muted, maxWidth: 360 }, children: description }),
|
|
35
|
+
description !== void 0 && /* @__PURE__ */ jsx("span", { style: { fontSize: VX.text.md, color: VX.muted, maxWidth: 360 }, children: description }),
|
|
36
36
|
action
|
|
37
37
|
]
|
|
38
38
|
}
|
|
@@ -2,4 +2,5 @@ export { DeltaBadge, type DeltaBadgeProps } from './delta-badge.js';
|
|
|
2
2
|
export { StatCard, type StatCardProps, type StatCardTone } from './stat-card.js';
|
|
3
3
|
export { EmptyState, type EmptyStateProps } from './empty-state.js';
|
|
4
4
|
export { SettingsSection, type SettingsSectionProps, SettingsRow, type SettingsRowProps, DangerZone, type DangerZoneProps, } from './settings-section.js';
|
|
5
|
+
export { QueryState, type QueryStateProps, type QueryStateLike, type QueryStateVariant, type QueryEmptyCopy, LoadingState, type LoadingStateProps, ErrorState, type ErrorStateProps, } from './query-state.js';
|
|
5
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dashboard/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAA;AAChE,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA;AAC7E,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAA;AAChE,OAAO,EACL,eAAe,EACf,KAAK,oBAAoB,EACzB,WAAW,EACX,KAAK,gBAAgB,EACrB,UAAU,EACV,KAAK,eAAe,GACrB,MAAM,oBAAoB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dashboard/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAA;AAChE,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA;AAC7E,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAA;AAChE,OAAO,EACL,eAAe,EACf,KAAK,oBAAoB,EACzB,WAAW,EACX,KAAK,gBAAgB,EACrB,UAAU,EACV,KAAK,eAAe,GACrB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,UAAU,EACV,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,YAAY,EACZ,KAAK,iBAAiB,EACtB,UAAU,EACV,KAAK,eAAe,GACrB,MAAM,eAAe,CAAA"}
|
package/dist/dashboard/index.js
CHANGED
|
@@ -6,10 +6,18 @@ import {
|
|
|
6
6
|
SettingsRow,
|
|
7
7
|
DangerZone
|
|
8
8
|
} from "./settings-section.js";
|
|
9
|
+
import {
|
|
10
|
+
QueryState,
|
|
11
|
+
LoadingState,
|
|
12
|
+
ErrorState
|
|
13
|
+
} from "./query-state.js";
|
|
9
14
|
export {
|
|
10
15
|
DangerZone,
|
|
11
16
|
DeltaBadge,
|
|
12
17
|
EmptyState,
|
|
18
|
+
ErrorState,
|
|
19
|
+
LoadingState,
|
|
20
|
+
QueryState,
|
|
13
21
|
SettingsRow,
|
|
14
22
|
SettingsSection,
|
|
15
23
|
StatCard
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
/** `'page'` = a full route body (generous padding). `'section'` = a card/panel region (compact). */
|
|
3
|
+
export type QueryStateVariant = 'page' | 'section';
|
|
4
|
+
/**
|
|
5
|
+
* The structural subset of a TanStack `UseQueryResult` these components read.
|
|
6
|
+
*
|
|
7
|
+
* Typed as a SUBSET rather than as `UseQueryResult<T>` on purpose: a composed, derived or
|
|
8
|
+
* hand-rolled result must be passable without a cast (3 of image-share's 10 call sites are), and
|
|
9
|
+
* these components stay independent of which `@tanstack/react-query` major a consumer pins. This
|
|
10
|
+
* is NOT a claim that the root barrel is free of that package — `./connectivity` imports
|
|
11
|
+
* `onlineManager` as a value and is re-exported from `src/index.ts`. The cost of the subset is
|
|
12
|
+
* that the compiler stops policing the shape — which is why {@link QueryState} asserts it at
|
|
13
|
+
* runtime.
|
|
14
|
+
*/
|
|
15
|
+
export type QueryStateLike<TData> = {
|
|
16
|
+
data: TData | undefined;
|
|
17
|
+
isError: boolean;
|
|
18
|
+
error: unknown;
|
|
19
|
+
fetchStatus: 'fetching' | 'paused' | 'idle';
|
|
20
|
+
refetch: () => unknown;
|
|
21
|
+
};
|
|
22
|
+
/** Copy for the empty branch. Rendered through `EmptyState`, so it looks like every other one. */
|
|
23
|
+
export type QueryEmptyCopy = {
|
|
24
|
+
title: string;
|
|
25
|
+
/** Optional — a one-word empty panel should not have to invent a sentence. */
|
|
26
|
+
description?: string;
|
|
27
|
+
/** Optional glyph slot, rendered faint. */
|
|
28
|
+
icon?: ReactNode;
|
|
29
|
+
/** Call-to-action below the copy (e.g. "Reset filters"). */
|
|
30
|
+
action?: ReactNode;
|
|
31
|
+
};
|
|
32
|
+
export type LoadingStateProps = {
|
|
33
|
+
variant?: QueryStateVariant;
|
|
34
|
+
/** Accessible name for the spinner. Say what is loading. */
|
|
35
|
+
label?: string;
|
|
36
|
+
};
|
|
37
|
+
/** A spinner on its own — `variant="page"` centres it in a generous block, `'section'` is bare. */
|
|
38
|
+
export declare function LoadingState({ variant, label, }: LoadingStateProps): ReactNode;
|
|
39
|
+
export type ErrorStateProps = {
|
|
40
|
+
/** Whatever was thrown — the raw Eden/fetch envelope is fine, it gets decoded. */
|
|
41
|
+
error: unknown;
|
|
42
|
+
/** Alert heading. Say what failed, e.g. `Could not load images`. */
|
|
43
|
+
title?: string;
|
|
44
|
+
/** Shown only when the server body carries no readable message. */
|
|
45
|
+
fallback?: string;
|
|
46
|
+
onRetry?: () => void;
|
|
47
|
+
/** Disables/spins the retry button while a refetch is in flight. */
|
|
48
|
+
retrying?: boolean;
|
|
49
|
+
variant?: QueryStateVariant;
|
|
50
|
+
/** Extra controls beside Retry (e.g. a "Back to shares" link). */
|
|
51
|
+
action?: ReactNode;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* The error branch: the REAL server message (via `toErrorMessage`) plus a retry. Usable on its own,
|
|
55
|
+
* outside a query — an auth gate or a boundary fallback renders it directly.
|
|
56
|
+
*/
|
|
57
|
+
export declare function ErrorState({ error, title, fallback, onRetry, retrying, variant, action, }: ErrorStateProps): ReactNode;
|
|
58
|
+
export type QueryStateProps<TData> = {
|
|
59
|
+
query: QueryStateLike<TData>;
|
|
60
|
+
/** A render function gets `data` narrowed to non-undefined; a plain node also works. */
|
|
61
|
+
children: ReactNode | ((data: TData) => ReactNode);
|
|
62
|
+
/** Omit to render nothing when the result is empty. */
|
|
63
|
+
empty?: QueryEmptyCopy;
|
|
64
|
+
/** Override the default `[]` / `{ data: [] }` emptiness test — `() => false` disables it. */
|
|
65
|
+
isEmpty?: (data: TData) => boolean;
|
|
66
|
+
/** Alert heading on the error branch — say what failed. */
|
|
67
|
+
errorTitle?: string;
|
|
68
|
+
/** Shown only when the server body carries no readable message. */
|
|
69
|
+
errorFallback?: string;
|
|
70
|
+
/** Extra controls beside Retry on the error branch. */
|
|
71
|
+
errorAction?: ReactNode;
|
|
72
|
+
variant?: QueryStateVariant;
|
|
73
|
+
/** Replace the default spinner (e.g. with a skeleton grid). */
|
|
74
|
+
loading?: ReactNode;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Renders loading / error-with-retry / empty / children for a query result, in that order.
|
|
78
|
+
* Branch precedence is the whole point, and it is deliberate:
|
|
79
|
+
*
|
|
80
|
+
* - error AND no cached data → the full error state (the page cannot render)
|
|
81
|
+
* - error WITH cached data → children, plus a compact "showing cached data" banner on top
|
|
82
|
+
* (a background refetch failing must not blank a page that already works)
|
|
83
|
+
* - no data, fetch idle → empty (this is an `enabled: false` query, not a pending one)
|
|
84
|
+
* - no data, fetching → `loading` (or the default spinner)
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* <QueryState
|
|
88
|
+
* query={q}
|
|
89
|
+
* errorTitle="Could not load images"
|
|
90
|
+
* empty={{ title: 'No images match', description: 'Widen the capture dates.' }}
|
|
91
|
+
* >
|
|
92
|
+
* {(data) => <ImageGrid images={data.rows} />}
|
|
93
|
+
* </QueryState>
|
|
94
|
+
*/
|
|
95
|
+
export declare function QueryState<TData>({ query, children, empty, isEmpty, errorTitle, errorFallback, errorAction, variant, loading, }: QueryStateProps<TData>): ReactNode;
|
|
96
|
+
//# sourceMappingURL=query-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-state.d.ts","sourceRoot":"","sources":["../../src/dashboard/query-state.tsx"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAItC,oGAAoG;AACpG,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,SAAS,CAAA;AAElD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,cAAc,CAAC,KAAK,IAAI;IAClC,IAAI,EAAE,KAAK,GAAG,SAAS,CAAA;IACvB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,OAAO,CAAA;IACd,WAAW,EAAE,UAAU,GAAG,QAAQ,GAAG,MAAM,CAAA;IAC3C,OAAO,EAAE,MAAM,OAAO,CAAA;CACvB,CAAA;AAED,kGAAkG;AAClG,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,2CAA2C;IAC3C,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,SAAS,CAAA;CACnB,CAAA;AAID,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,CAAC,EAAE,iBAAiB,CAAA;IAC3B,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAED,mGAAmG;AACnG,wBAAgB,YAAY,CAAC,EAC3B,OAAgB,EAChB,KAAiB,GAClB,EAAE,iBAAiB,GAAG,SAAS,CAI/B;AAID,MAAM,MAAM,eAAe,GAAG;IAC5B,kFAAkF;IAClF,KAAK,EAAE,OAAO,CAAA;IACd,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,OAAO,CAAC,EAAE,iBAAiB,CAAA;IAC3B,kEAAkE;IAClE,MAAM,CAAC,EAAE,SAAS,CAAA;CACnB,CAAA;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,KAA8B,EAC9B,QAAgC,EAChC,OAAO,EACP,QAAgB,EAChB,OAAgB,EAChB,MAAM,GACP,EAAE,eAAe,GAAG,SAAS,CAoB7B;AAyCD,MAAM,MAAM,eAAe,CAAC,KAAK,IAAI;IACnC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,CAAA;IAC5B,wFAAwF;IACxF,QAAQ,EAAE,SAAS,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,KAAK,SAAS,CAAC,CAAA;IAClD,uDAAuD;IACvD,KAAK,CAAC,EAAE,cAAc,CAAA;IACtB,6FAA6F;IAC7F,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,OAAO,CAAA;IAClC,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,mEAAmE;IACnE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,uDAAuD;IACvD,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,OAAO,CAAC,EAAE,iBAAiB,CAAA;IAC3B,+DAA+D;IAC/D,OAAO,CAAC,EAAE,SAAS,CAAA;CACpB,CAAA;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,EAChC,KAAK,EACL,QAAQ,EACR,KAAK,EACL,OAAO,EACP,UAA6B,EAC7B,aAAqC,EACrC,WAAW,EACX,OAAgB,EAChB,OAAO,GACR,EAAE,eAAe,CAAC,KAAK,CAAC,GAAG,SAAS,CAiDpC"}
|