elcrm 1.1.28 → 1.1.35
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/index.js +282 -6
- package/dist/vite/discover-lib.d.ts +6 -0
- package/dist/vite/discover-lib.js +59 -11
- package/dist/vite/minify-css-vars.js +8 -8
- package/dist/vite/plugin-css-scoped.d.ts +18 -4
- package/dist/vite/plugin-css-scoped.js +80 -41
- package/package.json +5 -19
- package/templates/elcrm-docs/CLI.elCRM.md +3 -2
- package/templates/elcrm-docs/COMPONENTS.elCRM.md +59 -20
- package/templates/elcrm-docs/FORM.elCRM.md +1 -1
- package/templates/orbit/web/src/style/theme-dark.css +11 -0
- package/templates/orbit/web/src/style/theme-light.css +11 -0
- package/templates/orbit/web/src/style/theme.css +19 -0
- package/templates/panel/web/src/style/theme-dark.css +11 -0
- package/templates/panel/web/src/style/theme-light.css +11 -0
- package/templates/panel/web/src/style/theme.css +19 -0
package/dist/index.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
4
|
// src/index.ts
|
|
5
|
-
import { readFileSync as
|
|
6
|
-
import { dirname as dirname4, join as
|
|
5
|
+
import { readFileSync as readFileSync29 } from "fs";
|
|
6
|
+
import { dirname as dirname4, join as join26 } from "path";
|
|
7
7
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8
8
|
|
|
9
9
|
// src/parseArgs.ts
|
|
@@ -37,7 +37,10 @@ function parseArgs(argv = process.argv.slice(2)) {
|
|
|
37
37
|
"folder",
|
|
38
38
|
"out",
|
|
39
39
|
"api",
|
|
40
|
-
"web"
|
|
40
|
+
"web",
|
|
41
|
+
"access",
|
|
42
|
+
"tag",
|
|
43
|
+
"otp"
|
|
41
44
|
]);
|
|
42
45
|
if (valueOpts.has(body)) {
|
|
43
46
|
options[body] = next;
|
|
@@ -913,6 +916,70 @@ function rewriteImports(source) {
|
|
|
913
916
|
hits++;
|
|
914
917
|
return `import { Stack } from "@elcrm/components/Stack"`;
|
|
915
918
|
});
|
|
919
|
+
next = next.replace(/\bPricingGroupItem\b/g, () => {
|
|
920
|
+
hits++;
|
|
921
|
+
return "PricingPlan";
|
|
922
|
+
});
|
|
923
|
+
next = next.replace(/import\s*\{([^}]+)\}\s*from\s*["']@elcrm\/components(?:\/Toolbar)?["']/g, (full, list) => {
|
|
924
|
+
if (!/\bapplyToolbarSize\b/.test(list))
|
|
925
|
+
return full;
|
|
926
|
+
hits++;
|
|
927
|
+
const parts = list.split(",").map((p) => p.trim()).filter((p) => p && !/\bapplyToolbarSize\b/.test(p));
|
|
928
|
+
if (parts.length === 0)
|
|
929
|
+
return "";
|
|
930
|
+
const from = /from\s*(["'][^"']+["'])/.exec(full)?.[1] ?? `"@elcrm/components"`;
|
|
931
|
+
return `import { ${parts.join(", ")} } from ${from}`;
|
|
932
|
+
});
|
|
933
|
+
next = next.replace(/\bapplyToolbarSize\s*\(\s*([^,]+)\s*,\s*[^)]+\)/g, (_m, node) => {
|
|
934
|
+
hits++;
|
|
935
|
+
return node.trim();
|
|
936
|
+
});
|
|
937
|
+
let needChatFrame = false;
|
|
938
|
+
let needChatFrameProps = false;
|
|
939
|
+
next = next.replace(/import\s*(type\s+)?\{([^}]+)\}\s*from\s*["']@elcrm\/components["']/g, (full, typeKw, list) => {
|
|
940
|
+
if (!/\bChatFrame(?:Props)?\b/.test(list))
|
|
941
|
+
return full;
|
|
942
|
+
hits++;
|
|
943
|
+
const parts = list.split(",").map((p) => p.trim()).filter(Boolean);
|
|
944
|
+
const kept = [];
|
|
945
|
+
for (const p of parts) {
|
|
946
|
+
const isType = Boolean(typeKw) || /^type\s+/.test(p);
|
|
947
|
+
const bare = p.replace(/^type\s+/, "").replace(/\s+as\s+\w+$/, "").trim();
|
|
948
|
+
if (bare === "ChatFrame") {
|
|
949
|
+
needChatFrame = true;
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
952
|
+
if (bare === "ChatFrameProps") {
|
|
953
|
+
needChatFrameProps = true;
|
|
954
|
+
continue;
|
|
955
|
+
}
|
|
956
|
+
kept.push(p);
|
|
957
|
+
}
|
|
958
|
+
if (kept.length === 0)
|
|
959
|
+
return "/* elcrm: ChatFrame \u2192 deep */";
|
|
960
|
+
const head = typeKw ? `import type { ` : `import { `;
|
|
961
|
+
return `${head}${kept.join(", ")} } from "@elcrm/components"`;
|
|
962
|
+
});
|
|
963
|
+
if (needChatFrame || needChatFrameProps) {
|
|
964
|
+
const inject = [];
|
|
965
|
+
if (needChatFrame) {
|
|
966
|
+
inject.push(`import { ChatFrame } from "@elcrm/components/ChatFrame";`);
|
|
967
|
+
}
|
|
968
|
+
if (needChatFrameProps) {
|
|
969
|
+
inject.push(`import type { ChatFrameProps } from "@elcrm/components/ChatFrame";`);
|
|
970
|
+
}
|
|
971
|
+
const block = inject.join(`
|
|
972
|
+
`);
|
|
973
|
+
if (/from\s*["']@elcrm\/components/.test(next)) {
|
|
974
|
+
next = next.replace(/(from\s*["']@elcrm\/components(?:\/[^"']*)?["'];?)/, `$1
|
|
975
|
+
${block}`);
|
|
976
|
+
} else {
|
|
977
|
+
next = `${block}
|
|
978
|
+
${next}`;
|
|
979
|
+
}
|
|
980
|
+
next = next.replace(/\n?\/\* elcrm: ChatFrame \u2192 deep \*\/\n?/g, `
|
|
981
|
+
`);
|
|
982
|
+
}
|
|
916
983
|
return { next, hits };
|
|
917
984
|
}
|
|
918
985
|
function rewriteJsx(source) {
|
|
@@ -967,6 +1034,34 @@ function rewriteSectionOnChange(source) {
|
|
|
967
1034
|
});
|
|
968
1035
|
return { next, hits };
|
|
969
1036
|
}
|
|
1037
|
+
function rewriteGroupCallbacks(source) {
|
|
1038
|
+
if (!/@elcrm\/components/.test(source) && !/\b(Menu|Sidebar|Dropdown|Breadcrumb|AvatarGroup|ActionGroup)\b/.test(source)) {
|
|
1039
|
+
return { next: source, hits: 0 };
|
|
1040
|
+
}
|
|
1041
|
+
let hits = 0;
|
|
1042
|
+
let next = source;
|
|
1043
|
+
next = next.replace(/\bonFooterSelect=/g, () => {
|
|
1044
|
+
hits++;
|
|
1045
|
+
return "onFooterValueChange=";
|
|
1046
|
+
});
|
|
1047
|
+
next = next.replace(/\bonItemClick=/g, () => {
|
|
1048
|
+
hits++;
|
|
1049
|
+
return "onValueChange=";
|
|
1050
|
+
});
|
|
1051
|
+
next = next.replace(/\bonSelect=/g, () => {
|
|
1052
|
+
hits++;
|
|
1053
|
+
return "onValueChange=";
|
|
1054
|
+
});
|
|
1055
|
+
next = next.replace(/(<ActionGroup\b[^>]*?)\sitems=/g, (_m, pre) => {
|
|
1056
|
+
hits++;
|
|
1057
|
+
return `${pre} actions=`;
|
|
1058
|
+
});
|
|
1059
|
+
next = next.replace(/(<Sidebar\b[^>]*?)\sitems=/g, (_m, pre) => {
|
|
1060
|
+
hits++;
|
|
1061
|
+
return `${pre} groups=`;
|
|
1062
|
+
});
|
|
1063
|
+
return { next, hits };
|
|
1064
|
+
}
|
|
970
1065
|
function rewriteItemKeys(source) {
|
|
971
1066
|
if (!/\b(Menu|NavSections|TabSections|MenuItem|NavSectionsItem|TabSectionsItem)\b/.test(source)) {
|
|
972
1067
|
return { next: source, hits: 0 };
|
|
@@ -1001,6 +1096,9 @@ function rewriteComponentsTsx(source) {
|
|
|
1001
1096
|
const oc = rewriteSectionOnChange(next);
|
|
1002
1097
|
next = oc.next;
|
|
1003
1098
|
hits += oc.hits;
|
|
1099
|
+
const cb = rewriteGroupCallbacks(next);
|
|
1100
|
+
next = cb.next;
|
|
1101
|
+
hits += cb.hits;
|
|
1004
1102
|
const keys = rewriteItemKeys(next);
|
|
1005
1103
|
next = keys.next;
|
|
1006
1104
|
hits += keys.hits;
|
|
@@ -5072,15 +5170,186 @@ async function cmdKit(parsed) {
|
|
|
5072
5170
|
process.exit(1);
|
|
5073
5171
|
}
|
|
5074
5172
|
|
|
5173
|
+
// src/commands/publish.ts
|
|
5174
|
+
import { copyFileSync, existsSync as existsSync20, readFileSync as readFileSync28, unlinkSync, writeFileSync as writeFileSync12 } from "fs";
|
|
5175
|
+
import { join as join25, resolve as resolve13 } from "path";
|
|
5176
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
5177
|
+
var PUBLIC_NAME = "package.public.json";
|
|
5178
|
+
var PKG_NAME = "package.json";
|
|
5179
|
+
var BACKUP_NAME = ".package.json.elcrm-publish.bak";
|
|
5180
|
+
async function cmdPublish(parsed) {
|
|
5181
|
+
const cwd = resolve13(process.cwd(), parsed.options.dir || ".");
|
|
5182
|
+
const dry = hasFlag(parsed, "dry");
|
|
5183
|
+
const noCheck = hasFlag(parsed, "no-check", "noCheck");
|
|
5184
|
+
const pkgPath = join25(cwd, PKG_NAME);
|
|
5185
|
+
const publicPath = join25(cwd, PUBLIC_NAME);
|
|
5186
|
+
const backupPath = join25(cwd, BACKUP_NAME);
|
|
5187
|
+
if (!existsSync20(pkgPath)) {
|
|
5188
|
+
throw new Error(`\u041D\u0435\u0442 ${PKG_NAME} \u0432 ${cwd}`);
|
|
5189
|
+
}
|
|
5190
|
+
const local = readPackageJson(cwd);
|
|
5191
|
+
if (!local?.name || !local.version) {
|
|
5192
|
+
throw new Error(`${PKG_NAME}: \u043D\u0443\u0436\u043D\u044B \u043F\u043E\u043B\u044F name \u0438 version`);
|
|
5193
|
+
}
|
|
5194
|
+
if (!existsSync20(publicPath)) {
|
|
5195
|
+
await publishPlain(cwd, local, parsed, dry, noCheck);
|
|
5196
|
+
return;
|
|
5197
|
+
}
|
|
5198
|
+
let pub;
|
|
5199
|
+
try {
|
|
5200
|
+
pub = JSON.parse(readFileSync28(publicPath, "utf8"));
|
|
5201
|
+
} catch {
|
|
5202
|
+
throw new Error(`${PUBLIC_NAME}: \u043D\u0435\u0432\u0430\u043B\u0438\u0434\u043D\u044B\u0439 JSON`);
|
|
5203
|
+
}
|
|
5204
|
+
pub.version = local.version;
|
|
5205
|
+
if (typeof pub.name !== "string")
|
|
5206
|
+
pub.name = local.name;
|
|
5207
|
+
const access = parsed.options.access || (typeof pub.publishConfig?.access === "string" ? pub.publishConfig.access : "public");
|
|
5208
|
+
console.log(`[publish] ${local.name}@${local.version}`);
|
|
5209
|
+
console.log(`[publish] \u043C\u0430\u043D\u0438\u0444\u0435\u0441\u0442: ${PUBLIC_NAME} \u2192 ${PKG_NAME}`);
|
|
5210
|
+
if (!noCheck && !dry) {
|
|
5211
|
+
await runPreChecks(cwd, local.scripts);
|
|
5212
|
+
} else if (noCheck) {
|
|
5213
|
+
console.log("[publish] \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u043F\u0440\u043E\u043F\u0443\u0449\u0435\u043D\u044B (--no-check)");
|
|
5214
|
+
}
|
|
5215
|
+
const npmArgs = buildNpmArgs(parsed, access);
|
|
5216
|
+
console.log(`[publish] npm ${npmArgs.join(" ")}`);
|
|
5217
|
+
if (dry) {
|
|
5218
|
+
console.log("[publish] --dry: \u043F\u043E\u0434\u043C\u0435\u043D\u0430 \u0438 publish \u043D\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u044F\u043B\u0438\u0441\u044C");
|
|
5219
|
+
return;
|
|
5220
|
+
}
|
|
5221
|
+
const originalBytes = readFileSync28(pkgPath);
|
|
5222
|
+
copyFileSync(pkgPath, backupPath);
|
|
5223
|
+
const indent = detectIndent2(readFileSync28(publicPath, "utf8"));
|
|
5224
|
+
writeFileSync12(pkgPath, JSON.stringify(pub, null, indent) + `
|
|
5225
|
+
`, "utf8");
|
|
5226
|
+
let publishStatus = 1;
|
|
5227
|
+
try {
|
|
5228
|
+
const r = spawnSync3("npm", npmArgs, {
|
|
5229
|
+
cwd,
|
|
5230
|
+
stdio: "inherit",
|
|
5231
|
+
env: process.env
|
|
5232
|
+
});
|
|
5233
|
+
publishStatus = r.status ?? 1;
|
|
5234
|
+
if (r.error) {
|
|
5235
|
+
console.error(r.error.message);
|
|
5236
|
+
}
|
|
5237
|
+
} finally {
|
|
5238
|
+
writeFileSync12(pkgPath, originalBytes);
|
|
5239
|
+
if (existsSync20(backupPath)) {
|
|
5240
|
+
try {
|
|
5241
|
+
unlinkSync(backupPath);
|
|
5242
|
+
} catch {}
|
|
5243
|
+
}
|
|
5244
|
+
console.log(`[publish] \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D ${PKG_NAME}`);
|
|
5245
|
+
}
|
|
5246
|
+
if (publishStatus !== 0) {
|
|
5247
|
+
process.exit(publishStatus);
|
|
5248
|
+
}
|
|
5249
|
+
console.log(`[publish] \u0433\u043E\u0442\u043E\u0432\u043E: ${local.name}@${local.version}`);
|
|
5250
|
+
}
|
|
5251
|
+
async function publishPlain(cwd, local, parsed, dry, noCheck) {
|
|
5252
|
+
const access = parsed.options.access || (typeof local.publishConfig?.access === "string" ? local.publishConfig.access : "public");
|
|
5253
|
+
console.log(`[publish] ${local.name}@${local.version}`);
|
|
5254
|
+
console.log(`[publish] \u0431\u0435\u0437 ${PUBLIC_NAME} \u2014 npm publish \u0442\u0435\u043A\u0443\u0449\u0435\u0433\u043E ${PKG_NAME}`);
|
|
5255
|
+
if (!noCheck && !dry) {
|
|
5256
|
+
await runPreChecks(cwd, local.scripts);
|
|
5257
|
+
} else if (noCheck) {
|
|
5258
|
+
console.log("[publish] \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u043F\u0440\u043E\u043F\u0443\u0449\u0435\u043D\u044B (--no-check)");
|
|
5259
|
+
}
|
|
5260
|
+
const npmArgs = buildNpmArgs(parsed, access);
|
|
5261
|
+
console.log(`[publish] npm ${npmArgs.join(" ")}`);
|
|
5262
|
+
if (dry) {
|
|
5263
|
+
console.log("[publish] --dry: publish \u043D\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u044F\u043B\u0441\u044F");
|
|
5264
|
+
return;
|
|
5265
|
+
}
|
|
5266
|
+
const r = spawnSync3("npm", npmArgs, {
|
|
5267
|
+
cwd,
|
|
5268
|
+
stdio: "inherit",
|
|
5269
|
+
env: process.env
|
|
5270
|
+
});
|
|
5271
|
+
if (r.error) {
|
|
5272
|
+
console.error(r.error.message);
|
|
5273
|
+
}
|
|
5274
|
+
if ((r.status ?? 1) !== 0) {
|
|
5275
|
+
process.exit(r.status ?? 1);
|
|
5276
|
+
}
|
|
5277
|
+
console.log(`[publish] \u0433\u043E\u0442\u043E\u0432\u043E: ${local.name}@${local.version}`);
|
|
5278
|
+
}
|
|
5279
|
+
async function runPreChecks(cwd, scripts) {
|
|
5280
|
+
const list = scripts ?? {};
|
|
5281
|
+
const chain = [];
|
|
5282
|
+
if (list.prepublishOnly)
|
|
5283
|
+
chain.push("prepublishOnly");
|
|
5284
|
+
else {
|
|
5285
|
+
if (list["check:css"])
|
|
5286
|
+
chain.push("check:css");
|
|
5287
|
+
if (list.test)
|
|
5288
|
+
chain.push("test");
|
|
5289
|
+
if (list.build)
|
|
5290
|
+
chain.push("build");
|
|
5291
|
+
}
|
|
5292
|
+
if (chain.length === 0) {
|
|
5293
|
+
console.log("[publish] scripts \u0434\u043B\u044F \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u043D\u0435\u0442 \u2014 \u0441\u0440\u0430\u0437\u0443 publish");
|
|
5294
|
+
return;
|
|
5295
|
+
}
|
|
5296
|
+
for (const script of chain) {
|
|
5297
|
+
console.log(`[publish] bun run ${script}`);
|
|
5298
|
+
const r = spawnSync3("bun", ["run", script], {
|
|
5299
|
+
cwd,
|
|
5300
|
+
stdio: "inherit",
|
|
5301
|
+
env: process.env
|
|
5302
|
+
});
|
|
5303
|
+
if ((r.status ?? 1) !== 0) {
|
|
5304
|
+
throw new Error(`\u0421\u043A\u0440\u0438\u043F\u0442 "${script}" \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F \u0441 \u043A\u043E\u0434\u043E\u043C ${r.status}`);
|
|
5305
|
+
}
|
|
5306
|
+
}
|
|
5307
|
+
}
|
|
5308
|
+
function buildNpmArgs(parsed, access) {
|
|
5309
|
+
const args = ["publish", `--access=${access}`, "--ignore-scripts"];
|
|
5310
|
+
if (parsed.options.tag)
|
|
5311
|
+
args.push(`--tag=${parsed.options.tag}`);
|
|
5312
|
+
if (parsed.options.otp)
|
|
5313
|
+
args.push(`--otp=${parsed.options.otp}`);
|
|
5314
|
+
const reserved = new Set([
|
|
5315
|
+
"dir",
|
|
5316
|
+
"access",
|
|
5317
|
+
"tag",
|
|
5318
|
+
"otp",
|
|
5319
|
+
"dry",
|
|
5320
|
+
"no-check",
|
|
5321
|
+
"noCheck"
|
|
5322
|
+
]);
|
|
5323
|
+
for (const [key, value] of Object.entries(parsed.options)) {
|
|
5324
|
+
if (reserved.has(key))
|
|
5325
|
+
continue;
|
|
5326
|
+
args.push(`--${key}=${value}`);
|
|
5327
|
+
}
|
|
5328
|
+
for (const flag of parsed.flags) {
|
|
5329
|
+
if (reserved.has(flag) || flag === "dry")
|
|
5330
|
+
continue;
|
|
5331
|
+
if (flag === "no-check" || flag === "noCheck")
|
|
5332
|
+
continue;
|
|
5333
|
+
args.push(`--${flag}`);
|
|
5334
|
+
}
|
|
5335
|
+
return args;
|
|
5336
|
+
}
|
|
5337
|
+
function detectIndent2(jsonText) {
|
|
5338
|
+
const m = jsonText.match(/\n([ \t]+)"/);
|
|
5339
|
+
if (!m?.[1])
|
|
5340
|
+
return 4;
|
|
5341
|
+
return m[1].includes("\t") ? 4 : m[1].length;
|
|
5342
|
+
}
|
|
5343
|
+
|
|
5075
5344
|
// src/index.ts
|
|
5076
5345
|
function cliVersion() {
|
|
5077
5346
|
const here = dirname4(fileURLToPath2(import.meta.url));
|
|
5078
5347
|
for (const p of [
|
|
5079
|
-
|
|
5080
|
-
|
|
5348
|
+
join26(here, "..", "package.json"),
|
|
5349
|
+
join26(here, "package.json")
|
|
5081
5350
|
]) {
|
|
5082
5351
|
try {
|
|
5083
|
-
const v = JSON.parse(
|
|
5352
|
+
const v = JSON.parse(readFileSync29(p, "utf8")).version;
|
|
5084
5353
|
if (typeof v === "string" && v)
|
|
5085
5354
|
return v;
|
|
5086
5355
|
} catch {}
|
|
@@ -5144,6 +5413,8 @@ function showHelp() {
|
|
|
5144
5413
|
init [--template app|lib|panel|orbit] [--force] [--no-install]
|
|
5145
5414
|
build [--client] [--url=\u2026] [--name=\u2026] [--dir=\u2026] [--folder=\u2026]
|
|
5146
5415
|
postbuild [--dir=./dist]
|
|
5416
|
+
publish [--dry] [--no-check] [--dir=\u2026] [--access=\u2026] [--tag=\u2026] [--otp=\u2026]
|
|
5417
|
+
npm publish; \u0435\u0441\u043B\u0438 \u0435\u0441\u0442\u044C package.public.json \u2014 \u043F\u043E\u0434\u043C\u0435\u043D\u0430 \u043C\u0430\u043D\u0438\u0444\u0435\u0441\u0442\u0430 \u0438 \u043E\u0442\u043A\u0430\u0442
|
|
5147
5418
|
uuid
|
|
5148
5419
|
|
|
5149
5420
|
\u0424\u043B\u0430\u0433\u0438: -v/--version, -h/--help
|
|
@@ -5151,6 +5422,8 @@ function showHelp() {
|
|
|
5151
5422
|
\u041F\u0440\u0438\u043C\u0435\u0440\u044B:
|
|
5152
5423
|
elcrm doctor
|
|
5153
5424
|
elcrm doctor --docs --test
|
|
5425
|
+
elcrm publish
|
|
5426
|
+
elcrm publish --dry
|
|
5154
5427
|
elcrm update --fix --test && elcrm css && elcrm docs && elcrm cursor
|
|
5155
5428
|
elcrm css # ENOENT @elcrm/\u2026/light.css \u2192 \u0441\u043D\u0438\u043C\u0435\u0442 \u0431\u0438\u0442\u044B\u0439 import
|
|
5156
5429
|
elcrm audit
|
|
@@ -5210,6 +5483,9 @@ async function main() {
|
|
|
5210
5483
|
case "kit":
|
|
5211
5484
|
await cmdKit(parsed);
|
|
5212
5485
|
break;
|
|
5486
|
+
case "publish":
|
|
5487
|
+
await cmdPublish(parsed);
|
|
5488
|
+
break;
|
|
5213
5489
|
case "uuid":
|
|
5214
5490
|
await cmdUuid();
|
|
5215
5491
|
break;
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
import type { PrefixGroups } from "./plugin-css-scoped";
|
|
2
|
+
|
|
1
3
|
export type DiscoverLibOptions = {
|
|
2
4
|
libDir: string;
|
|
3
5
|
letterOverrides?: Record<string, string>;
|
|
6
|
+
/** Дефолтная семья; @default "c" */
|
|
7
|
+
prefix?: string;
|
|
8
|
+
prefixGroups?: PrefixGroups;
|
|
9
|
+
prefixOverrides?: Record<string, string>;
|
|
4
10
|
};
|
|
5
11
|
|
|
6
12
|
export type DiscoverLibResult = {
|
|
@@ -1,42 +1,90 @@
|
|
|
1
1
|
// src/vite/discover-lib.ts
|
|
2
2
|
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
// src/vite/css-prefix.ts
|
|
6
|
+
function assertPrefixLetter(value, label) {
|
|
7
|
+
if (!/^[a-z]$/.test(value)) {
|
|
8
|
+
throw new Error(`[css-scoped] ${label} должен быть одной буквой a-z, получено: "${value}"`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function buildPrefixMap(options) {
|
|
12
|
+
const map = {};
|
|
13
|
+
const groups = options.prefixGroups ?? {};
|
|
14
|
+
for (const [pfx, names] of Object.entries(groups)) {
|
|
15
|
+
assertPrefixLetter(pfx, `prefixGroups["${pfx}"]`);
|
|
16
|
+
if (!Array.isArray(names)) {
|
|
17
|
+
throw new Error(`[css-scoped] prefixGroups["${pfx}"] должен быть массивом имён`);
|
|
18
|
+
}
|
|
19
|
+
for (const name of names) {
|
|
20
|
+
if (map[name] != null && map[name] !== pfx) {
|
|
21
|
+
throw new Error(`[css-scoped] "${name}" в двух семьях: ${map[name]} и ${pfx}`);
|
|
22
|
+
}
|
|
23
|
+
map[name] = pfx;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
for (const [name, pfx] of Object.entries(options.prefixOverrides ?? {})) {
|
|
27
|
+
assertPrefixLetter(pfx, `prefixOverrides["${name}"]`);
|
|
28
|
+
map[name] = pfx;
|
|
29
|
+
}
|
|
30
|
+
return map;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/vite/discover-lib.ts
|
|
34
|
+
var LETTER_ALPHABET = "abcdefghijklmnopqrstuvwxyz";
|
|
4
35
|
function listComponentDirs(libDir) {
|
|
5
36
|
return readdirSync(libDir).filter((name) => {
|
|
6
37
|
const dir = join(libDir, name);
|
|
7
38
|
return statSync(dir).isDirectory() && existsSync(join(dir, "index.ts"));
|
|
8
39
|
}).sort();
|
|
9
40
|
}
|
|
10
|
-
function
|
|
11
|
-
|
|
41
|
+
function slotFor(used, prefix) {
|
|
42
|
+
let set = used.get(prefix);
|
|
43
|
+
if (!set) {
|
|
44
|
+
set = new Set;
|
|
45
|
+
used.set(prefix, set);
|
|
46
|
+
}
|
|
47
|
+
return set;
|
|
48
|
+
}
|
|
49
|
+
function assignLetters(names, overrides, prefixOf) {
|
|
50
|
+
const used = new Map;
|
|
12
51
|
const letters = {};
|
|
13
52
|
for (const [name, letter] of Object.entries(overrides)) {
|
|
14
|
-
if (!/^[a-
|
|
15
|
-
throw new Error(`[discover-lib] override "${name}" → "${letter}" (нужна a-
|
|
53
|
+
if (!/^[a-z]$/.test(letter)) {
|
|
54
|
+
throw new Error(`[discover-lib] override "${name}" → "${letter}" (нужна одна буква a-z)`);
|
|
16
55
|
}
|
|
17
|
-
|
|
18
|
-
|
|
56
|
+
const pfx = prefixOf(name);
|
|
57
|
+
const slot = slotFor(used, pfx);
|
|
58
|
+
if (slot.has(letter)) {
|
|
59
|
+
throw new Error(`[discover-lib] дубль буквы "${letter}" в семье "${pfx}" (overrides)`);
|
|
19
60
|
}
|
|
20
|
-
|
|
61
|
+
slot.add(letter);
|
|
21
62
|
if (names.includes(name))
|
|
22
63
|
letters[name] = letter;
|
|
23
64
|
}
|
|
24
65
|
for (const name of names) {
|
|
25
66
|
if (letters[name])
|
|
26
67
|
continue;
|
|
68
|
+
const slot = slotFor(used, prefixOf(name));
|
|
27
69
|
const base = name.replace(/[^a-zA-Z]/g, "").toLowerCase();
|
|
28
|
-
|
|
70
|
+
const letter = [...base].find((c) => /[a-z]/.test(c) && !slot.has(c)) ?? [...LETTER_ALPHABET].find((c) => !slot.has(c));
|
|
29
71
|
if (!letter) {
|
|
30
|
-
throw new Error(`[discover-lib] нет свободной
|
|
72
|
+
throw new Error(`[discover-lib] нет свободной буквы a-z для "${name}" в семье "${prefixOf(name)}" — заведи новую семью в prefixGroups`);
|
|
31
73
|
}
|
|
32
|
-
|
|
74
|
+
slot.add(letter);
|
|
33
75
|
letters[name] = letter;
|
|
34
76
|
}
|
|
35
77
|
return letters;
|
|
36
78
|
}
|
|
37
79
|
function discoverLibComponents(options) {
|
|
38
80
|
const names = listComponentDirs(options.libDir);
|
|
39
|
-
const
|
|
81
|
+
const defaultPrefix = options.prefix ?? "c";
|
|
82
|
+
const prefixMap = buildPrefixMap({
|
|
83
|
+
prefixGroups: options.prefixGroups,
|
|
84
|
+
prefixOverrides: options.prefixOverrides
|
|
85
|
+
});
|
|
86
|
+
const prefixOf = (name) => prefixMap[name] ?? defaultPrefix;
|
|
87
|
+
const letters = assignLetters(names, options.letterOverrides ?? {}, prefixOf);
|
|
40
88
|
const entries = {
|
|
41
89
|
index: join(options.libDir, "index.ts")
|
|
42
90
|
};
|
|
@@ -269,13 +269,13 @@ function minifyCssVars(options) {
|
|
|
269
269
|
};
|
|
270
270
|
}
|
|
271
271
|
export {
|
|
272
|
-
|
|
273
|
-
replaceCssVarToken,
|
|
274
|
-
namespaceSize,
|
|
275
|
-
minifyCssVars,
|
|
276
|
-
isVendorJsChunk,
|
|
277
|
-
encodeName,
|
|
278
|
-
collectCssVarNames,
|
|
272
|
+
allocateUnique,
|
|
279
273
|
applyVarDictionary,
|
|
280
|
-
|
|
274
|
+
collectCssVarNames,
|
|
275
|
+
encodeName,
|
|
276
|
+
isVendorJsChunk,
|
|
277
|
+
minifyCssVars,
|
|
278
|
+
namespaceSize,
|
|
279
|
+
replaceCssVarToken,
|
|
280
|
+
toIndex
|
|
281
281
|
};
|
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Vite helper: короткие unique className для CSS Modules elCRM.
|
|
3
|
-
* Формат: 3
|
|
3
|
+
* Формат: 3 буквы `{prefix}{буква}{первая буква локального}`.
|
|
4
|
+
* В исходнике `.root` / `.icon`; в DOM `slr` / `llr`.
|
|
5
|
+
* Семьи: `prefixGroups` (буква уникальна внутри prefix).
|
|
4
6
|
* @see createCssScoped
|
|
5
7
|
*/
|
|
8
|
+
export type PrefixGroups = Record<string, string[]>;
|
|
9
|
+
|
|
10
|
+
export declare function buildPrefixMap(options: {
|
|
11
|
+
prefixGroups?: PrefixGroups;
|
|
12
|
+
prefixOverrides?: Record<string, string>;
|
|
13
|
+
}): Record<string, string>;
|
|
14
|
+
|
|
15
|
+
/** 3-й знак scoped: первая буква локального имени (`.root` → `r`). */
|
|
16
|
+
export declare function localChar(name: string): string;
|
|
17
|
+
|
|
6
18
|
export type CssScopedOptions = {
|
|
7
|
-
/**
|
|
19
|
+
/** Дефолтная семья (c = chrome/components, b = button, …) */
|
|
8
20
|
prefix: string;
|
|
9
|
-
/**
|
|
21
|
+
/** Семьи: prefix → папки компонентов */
|
|
22
|
+
prefixGroups?: PrefixGroups;
|
|
23
|
+
/** Свой prefix у компонента (перебивает группу) */
|
|
10
24
|
prefixOverrides?: Record<string, string>;
|
|
11
|
-
/** Папка компонента → одна буква */
|
|
25
|
+
/** Папка компонента → одна буква a–z */
|
|
12
26
|
components: Record<string, string>;
|
|
13
27
|
/** Каталог с папками компонентов (абсолютный путь) */
|
|
14
28
|
libDir: string;
|
|
@@ -1,6 +1,44 @@
|
|
|
1
1
|
// src/vite/plugin-css-scoped.ts
|
|
2
2
|
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
// src/vite/css-prefix.ts
|
|
6
|
+
function assertPrefixLetter(value, label) {
|
|
7
|
+
if (!/^[a-z]$/.test(value)) {
|
|
8
|
+
throw new Error(`[css-scoped] ${label} должен быть одной буквой a-z, получено: "${value}"`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function buildPrefixMap(options) {
|
|
12
|
+
const map = {};
|
|
13
|
+
const groups = options.prefixGroups ?? {};
|
|
14
|
+
for (const [pfx, names] of Object.entries(groups)) {
|
|
15
|
+
assertPrefixLetter(pfx, `prefixGroups["${pfx}"]`);
|
|
16
|
+
if (!Array.isArray(names)) {
|
|
17
|
+
throw new Error(`[css-scoped] prefixGroups["${pfx}"] должен быть массивом имён`);
|
|
18
|
+
}
|
|
19
|
+
for (const name of names) {
|
|
20
|
+
if (map[name] != null && map[name] !== pfx) {
|
|
21
|
+
throw new Error(`[css-scoped] "${name}" в двух семьях: ${map[name]} и ${pfx}`);
|
|
22
|
+
}
|
|
23
|
+
map[name] = pfx;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
for (const [name, pfx] of Object.entries(options.prefixOverrides ?? {})) {
|
|
27
|
+
assertPrefixLetter(pfx, `prefixOverrides["${name}"]`);
|
|
28
|
+
map[name] = pfx;
|
|
29
|
+
}
|
|
30
|
+
return map;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/vite/css-local.ts
|
|
34
|
+
function localChar(name) {
|
|
35
|
+
if (!/^[a-z][a-z0-9]*$/.test(name)) {
|
|
36
|
+
throw new Error(`[css-scoped] локальный класс — [a-z][a-z0-9]*: ".${name}"`);
|
|
37
|
+
}
|
|
38
|
+
return name[0];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/vite/plugin-css-scoped.ts
|
|
4
42
|
var DEFAULT_MODULE_RE = /\/(?:src\/)?lib\/([^/]+)\/[^/]+\.module\.css$/;
|
|
5
43
|
function listModuleCss(dir) {
|
|
6
44
|
const out = [];
|
|
@@ -14,24 +52,20 @@ function listModuleCss(dir) {
|
|
|
14
52
|
return out;
|
|
15
53
|
}
|
|
16
54
|
function localClasses(css) {
|
|
55
|
+
const stripped = css.replace(/\/\*[\s\S]*?\*\//g, " ");
|
|
17
56
|
const set = new Set;
|
|
18
|
-
for (const m of
|
|
57
|
+
for (const m of stripped.matchAll(/(?:^|[,{\s>+~])\.([a-z][a-z0-9]*)\b/gi)) {
|
|
19
58
|
set.add(m[1].toLowerCase());
|
|
20
59
|
}
|
|
21
60
|
return [...set];
|
|
22
61
|
}
|
|
23
|
-
function assertPrefix(value, label) {
|
|
24
|
-
if (!/^[a-z]$/.test(value)) {
|
|
25
|
-
throw new Error(`[css-scoped] ${label} должен быть одной буквой a-z, получено: "${value}"`);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
62
|
function createCssScoped(options) {
|
|
29
63
|
const prefix = options.prefix;
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
64
|
+
assertPrefixLetter(prefix, "prefix");
|
|
65
|
+
const prefixMap = buildPrefixMap({
|
|
66
|
+
prefixGroups: options.prefixGroups,
|
|
67
|
+
prefixOverrides: options.prefixOverrides
|
|
68
|
+
});
|
|
35
69
|
const components = options.components;
|
|
36
70
|
const libDir = options.libDir;
|
|
37
71
|
const modulePathRe = options.modulePathRe ?? DEFAULT_MODULE_RE;
|
|
@@ -45,23 +79,21 @@ function createCssScoped(options) {
|
|
|
45
79
|
return m[1];
|
|
46
80
|
}
|
|
47
81
|
function prefixFor(component) {
|
|
48
|
-
return
|
|
82
|
+
return prefixMap[component] ?? prefix;
|
|
49
83
|
}
|
|
50
84
|
function generateScopedName(name, filename) {
|
|
51
|
-
|
|
52
|
-
throw new Error(`[css-scoped] локальный класс — 1 знак [a-z0-9] (scoped = 3 символа): ".${name}" (${filename})`);
|
|
53
|
-
}
|
|
85
|
+
const key = localChar(name);
|
|
54
86
|
const component = componentFromFilename(filename);
|
|
55
87
|
const letter = components[component];
|
|
56
88
|
if (!letter) {
|
|
57
89
|
throw new Error(`[css-scoped] нет буквы для компонента "${component}". Добавьте в components.`);
|
|
58
90
|
}
|
|
59
|
-
if (!/^[a-
|
|
60
|
-
throw new Error(`[css-scoped] ключ компонента "${component}" должен быть a-
|
|
91
|
+
if (!/^[a-z]$/.test(letter)) {
|
|
92
|
+
throw new Error(`[css-scoped] ключ компонента "${component}" должен быть a-z: "${letter}"`);
|
|
61
93
|
}
|
|
62
|
-
const scoped = `${prefixFor(component)}${letter}${
|
|
63
|
-
if (scoped
|
|
64
|
-
throw new Error(`[css-scoped] "${scoped}" ≠ 3
|
|
94
|
+
const scoped = `${prefixFor(component)}${letter}${key}`;
|
|
95
|
+
if (!/^[a-z]{3}$/.test(scoped)) {
|
|
96
|
+
throw new Error(`[css-scoped] "${scoped}" ≠ 3 буквы a-z (${component}.${name})`);
|
|
65
97
|
}
|
|
66
98
|
const owner = `${component}.${name}`;
|
|
67
99
|
const prev = registry.get(scoped);
|
|
@@ -77,6 +109,11 @@ function createCssScoped(options) {
|
|
|
77
109
|
function snapshot() {
|
|
78
110
|
return new Map(registry);
|
|
79
111
|
}
|
|
112
|
+
function groupedNames() {
|
|
113
|
+
const fromGroups = Object.values(options.prefixGroups ?? {}).flat();
|
|
114
|
+
const fromOverrides = Object.keys(options.prefixOverrides ?? {});
|
|
115
|
+
return [...new Set([...fromGroups, ...fromOverrides])];
|
|
116
|
+
}
|
|
80
117
|
function check() {
|
|
81
118
|
reset();
|
|
82
119
|
const folders = readdirSync(libDir).filter((n) => statSync(join(libDir, n)).isDirectory());
|
|
@@ -85,16 +122,9 @@ function createCssScoped(options) {
|
|
|
85
122
|
if (missingMap.length) {
|
|
86
123
|
throw new Error(`[css-scoped] нет буквы в components: ${missingMap.join(", ")}`);
|
|
87
124
|
}
|
|
88
|
-
const
|
|
89
|
-
if (
|
|
90
|
-
throw new Error(`[css-scoped] prefixOverrides без компонента: ${
|
|
91
|
-
}
|
|
92
|
-
const letters = Object.values(components);
|
|
93
|
-
const dupLetter = [
|
|
94
|
-
...new Set(letters.filter((l, i) => letters.indexOf(l) !== i))
|
|
95
|
-
];
|
|
96
|
-
if (dupLetter.length) {
|
|
97
|
-
throw new Error(`[css-scoped] дубли букв компонентов: ${dupLetter.join(", ")}`);
|
|
125
|
+
const unknownFamily = groupedNames().filter((n) => !mapped.has(n));
|
|
126
|
+
if (unknownFamily.length) {
|
|
127
|
+
throw new Error(`[css-scoped] prefixGroups/prefixOverrides без компонента: ${unknownFamily.join(", ")}`);
|
|
98
128
|
}
|
|
99
129
|
const namespaces = Object.entries(components).map(([name, letter]) => `${prefixFor(name)}${letter}`);
|
|
100
130
|
const dupNs = [
|
|
@@ -106,20 +136,27 @@ function createCssScoped(options) {
|
|
|
106
136
|
const files = listModuleCss(libDir);
|
|
107
137
|
for (const file of files) {
|
|
108
138
|
const css = readFileSync(file, "utf8");
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
139
|
+
const locals = localClasses(css);
|
|
140
|
+
const seen = new Map;
|
|
141
|
+
for (const local of locals) {
|
|
142
|
+
const ch = localChar(local);
|
|
143
|
+
const prev = seen.get(ch);
|
|
144
|
+
if (prev && prev !== local) {
|
|
145
|
+
const component = componentFromFilename(file);
|
|
146
|
+
throw new Error(`[css-scoped] ${component}: ".${prev}" и ".${local}" дают один 3-й знак "${ch}"`);
|
|
147
|
+
}
|
|
148
|
+
seen.set(ch, local);
|
|
117
149
|
generateScopedName(local, file);
|
|
118
150
|
}
|
|
119
151
|
}
|
|
120
152
|
const snap = snapshot();
|
|
121
|
-
const
|
|
122
|
-
|
|
153
|
+
const familyCounts = new Map;
|
|
154
|
+
for (const name of Object.keys(components)) {
|
|
155
|
+
const p = prefixFor(name);
|
|
156
|
+
familyCounts.set(p, (familyCounts.get(p) ?? 0) + 1);
|
|
157
|
+
}
|
|
158
|
+
const families = [...familyCounts.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([p, n]) => `${p}:${n}`).join(", ");
|
|
159
|
+
console.log(`[css-scoped] OK: ${files.length} module.css, ${snap.size} классов (${families})`);
|
|
123
160
|
return snap;
|
|
124
161
|
}
|
|
125
162
|
function plugin() {
|
|
@@ -148,5 +185,7 @@ function createCssScoped(options) {
|
|
|
148
185
|
};
|
|
149
186
|
}
|
|
150
187
|
export {
|
|
151
|
-
|
|
188
|
+
buildPrefixMap,
|
|
189
|
+
createCssScoped,
|
|
190
|
+
localChar
|
|
152
191
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "elcrm",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "CLI @elcrm/*: doctor --fix (порядок проекта), update, css, docs, cursor, kit, migrate",
|
|
3
|
+
"version": "1.1.35",
|
|
4
|
+
"description": "CLI @elcrm/*: doctor --fix (порядок проекта), update, css, docs, cursor, kit, migrate, publish",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "MaSkal <dev@elcrm.online>",
|
|
@@ -48,18 +48,6 @@
|
|
|
48
48
|
"engines": {
|
|
49
49
|
"node": ">=18"
|
|
50
50
|
},
|
|
51
|
-
"scripts": {
|
|
52
|
-
"build": "bun build ./src/index.ts --outdir ./dist --target bun --format esm && bun build ./src/vite/plugin-css-scoped.ts --outfile ./dist/vite/plugin-css-scoped.js --target node --format esm --packages external && bun build ./src/vite/discover-lib.ts --outfile ./dist/vite/discover-lib.js --target node --format esm --packages external && bun build ./src/vite/concat-css.ts --outfile ./dist/vite/concat-css.js --target node --format esm --packages external && bun build ./src/vite/check-tokens.ts --outfile ./dist/vite/check-tokens.js --target node --format esm --packages external && bun build ./src/vite/postbuild.ts --outfile ./dist/vite/postbuild.js --target node --format esm --packages external && bun build ./src/vite/minify-css-vars.ts --outfile ./dist/vite/minify-css-vars.js --target node --format esm --packages external && node ./scripts/chmod-bin.mjs && node ./scripts/copy-vite-dts.mjs",
|
|
53
|
-
"dev": "bun run ./src/index.ts",
|
|
54
|
-
"elcrm": "bun run ./src/index.ts",
|
|
55
|
-
"test": "bun test src/lib/migrate src/lib/tidy.test.ts src/lib/kit src/vite",
|
|
56
|
-
"demo": "bun run scripts/dev-template.ts",
|
|
57
|
-
"demo:panel": "bun run scripts/dev-template.ts panel",
|
|
58
|
-
"demo:orbit": "bun run scripts/dev-template.ts orbit",
|
|
59
|
-
"link:local": "bun run build && bun link",
|
|
60
|
-
"prepublishOnly": "bun run test && bun run build",
|
|
61
|
-
"publish:npm": "npm publish"
|
|
62
|
-
},
|
|
63
51
|
"keywords": [
|
|
64
52
|
"elcrm",
|
|
65
53
|
"cli",
|
|
@@ -74,6 +62,9 @@
|
|
|
74
62
|
"bugs": {
|
|
75
63
|
"url": "https://elgit.ru/lib/elCRM.cli/issues"
|
|
76
64
|
},
|
|
65
|
+
"publishConfig": {
|
|
66
|
+
"access": "public"
|
|
67
|
+
},
|
|
77
68
|
"peerDependencies": {
|
|
78
69
|
"vite": ">=5.0.0"
|
|
79
70
|
},
|
|
@@ -81,10 +72,5 @@
|
|
|
81
72
|
"vite": {
|
|
82
73
|
"optional": true
|
|
83
74
|
}
|
|
84
|
-
},
|
|
85
|
-
"devDependencies": {
|
|
86
|
-
"@types/bun": "^1.2.18",
|
|
87
|
-
"typescript": "^5.8.2",
|
|
88
|
-
"vite": "^5.4.2"
|
|
89
75
|
}
|
|
90
76
|
}
|
|
@@ -70,7 +70,7 @@ elcrm migrate front # пакет (то же, что doctor --fix без
|
|
|
70
70
|
elcrm migrate form-native # снять prop native у @elcrm/form (не трогает pages/Auth.tsx)
|
|
71
71
|
elcrm migrate native-html # <input|textarea> → StringField/CheckField/…
|
|
72
72
|
elcrm migrate css-sanitize # осиротевшие in srgb / transparent);
|
|
73
|
-
elcrm migrate components # Block/Row →
|
|
73
|
+
elcrm migrate components # Block/Row→Stack; onSelect→onValueChange; ActionGroup actions; --page-*/--item-*→--shell-*
|
|
74
74
|
elcrm migrate form-tokens
|
|
75
75
|
elcrm migrate form-aliases
|
|
76
76
|
elcrm migrate size-sml # size sm|md → s|m|l
|
|
@@ -111,7 +111,7 @@ minifyCssVars({ var: 2 })
|
|
|
111
111
|
|
|
112
112
|
Короткие имена **уникальны и подряд**: `--aa`, `--ab`, `--ac`… Занятые слоты пропускаются; `--field` не затирает `--field-border`. Чанки JSZip не минифицируются (декремент `--n`). Если слотов нет — сборка падает.
|
|
113
113
|
|
|
114
|
-
Также: `elcrm/vite/plugin-css-scoped` (`prefix` + `
|
|
114
|
+
Также: `elcrm/vite/plugin-css-scoped` (`prefix` + `prefixGroups`, scoped = 3 буквы a–z: `.root` → 3-й знак `r`; буква компонента уникальна внутри семьи), `discover-lib`, `concat-css`, `check-tokens`, `postbuild`.
|
|
115
115
|
|
|
116
116
|
### Прочее
|
|
117
117
|
|
|
@@ -127,6 +127,7 @@ elcrm init --template panel
|
|
|
127
127
|
elcrm init --template orbit --force --no-install
|
|
128
128
|
elcrm build --client [--url=…] [--name=…] [--dir=…] [--folder=…]
|
|
129
129
|
elcrm postbuild [--dir=./dist] # lib: склейка CSS после vite build
|
|
130
|
+
elcrm publish [--dry] [--no-check] # npm publish; lib: package.public.json → откат
|
|
130
131
|
elcrm uuid
|
|
131
132
|
elcrm -v / --help
|
|
132
133
|
```
|
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
# `@elcrm/components`
|
|
4
4
|
|
|
5
5
|
Shell UI: оболочка, шапка, секции, списки, Dropdown / RadioGroup / Pricing* / Avatar* / Breadcrumb / Stack.
|
|
6
|
-
**Цвета и размеры задаёт приложение** (`theme.css` + `theme-light` / `theme-dark`). Fallback в пакете нет.
|
|
6
|
+
**Цвета и размеры задаёт приложение** (`theme.css` + `theme-light` / `theme-dark`). Fallback в пакете нет.
|
|
7
|
+
**Локаль:** дефолтные aria/подписи — русский; переопределяй `navLabel` / `label` / `copyLabel` / `aria-label` (без i18n-runtime в пакете).
|
|
7
8
|
|
|
8
9
|
Токены как у form: сначала общие **`--shell-*`** (+ `--control-*`, `--popup-shadow`), потом узкие (`--header-height`, `--section-padding`, `--avatar-size-*`, …).
|
|
9
10
|
Дефолты подтягивает `elcrm css` из `@elcrm/components/tokens.css` + `light.css` / `dark.css`.
|
|
10
11
|
|
|
11
|
-
Группы — через `items` / `groups`, идентификатор пункта — **`value
|
|
12
|
+
Группы — через `items` / `groups`, идентификатор пункта — **`value`**, выбор — **`onValueChange`** (+ `defaultValue` где нужно).
|
|
13
|
+
`ActionGroup` — слоты `actions` / `children`, не декларативные `items`. Sidebar — только `groups` (не alias `items`).
|
|
12
14
|
|
|
13
15
|
```ts
|
|
14
16
|
import {
|
|
@@ -19,52 +21,83 @@ import {
|
|
|
19
21
|
Loading, EmptyState, RadioGroup, PricingGroup, PricingTable,
|
|
20
22
|
Table, CodeBlock, SplitPane, Banner, IconButton, Text,
|
|
21
23
|
createLazyResolver,
|
|
24
|
+
acquireOverlayZIndex,
|
|
22
25
|
} from "@elcrm/components";
|
|
23
|
-
import type { PricingPlan, HealthmapData } from "@elcrm/components";
|
|
26
|
+
import type { PricingPlan, HealthmapData, NavTreeItem } from "@elcrm/components";
|
|
24
27
|
```
|
|
25
28
|
|
|
29
|
+
Версия пакета: **≥ 0.5.x** (`onValueChange`, без Block/Row/Column).
|
|
30
|
+
## Роли (кто когда)
|
|
31
|
+
|
|
32
|
+
Не сливаем компоненты — выбирай по слою:
|
|
33
|
+
|
|
34
|
+
| Слой | Компоненты | Не путать с |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| Оболочка app | `Layout` (+ Header / Footer / Sidebar) | PageShell |
|
|
37
|
+
| Каркас модуля | `PageShell` | Section |
|
|
38
|
+
| Шапка страницы | `PageHead` | Section title |
|
|
39
|
+
| Блок контента | `Section` (`direction` / `gap` / `columns` как у Stack) | PageHead |
|
|
40
|
+
| Нав app | `Sidebar` | NavSections (страница + lazy-панель) |
|
|
41
|
+
| Вкладки | `Menu variant="line"` = только табы; `TabSections` = табы + lazy-панель | |
|
|
42
|
+
| Две колонки | `SplitPane` = resize; `ChatSplit` = чат без resize | |
|
|
43
|
+
| Чат | `ChatList` / `ChatMessages` / `ChatSplit`; `ChatFrame` — каркас колонки | |
|
|
44
|
+
| Планы | `PricingGroup` / `PricingTable`; `RadioGroup variant="split"` — radio-UI | |
|
|
45
|
+
| Toolbar | `Toolbar` = фильтры; `ActionGroup` = иконки в карточке | |
|
|
46
|
+
| Текст | `Text` / `TextGroup` / `AvatarName` (+аватар) | |
|
|
47
|
+
| Статус | `Badge` метка; `Banner` полоса; `EmptyState` пустой экран | |
|
|
48
|
+
| Таблица | `Table` = `<table>`; `PricingTable` = матрица тарифов | |
|
|
49
|
+
| Раскладка | `Stack` (или `Section` с `direction`) | Block / Row / Column |
|
|
50
|
+
|
|
26
51
|
## Роли layout
|
|
27
52
|
|
|
28
53
|
- **PageShell** — каркас модуля (`scroll` в body + footer). `scroll={false}` — без внутреннего скролла.
|
|
29
54
|
- **ChatList** — колонка диалогов: слоты `header` / `footer`, `children` = `List` + `Item`. Токены `--chat-list-width`, `--chat-*` (алиасы `--shell-*`).
|
|
30
55
|
- **ChatMessages** — колонка переписки. `bodyRef` / `onBodyScroll`. Паддинги `--chat-messages-*-padding`.
|
|
31
|
-
- **ChatSplit** — ряд `list` + `messages` (`--chat-split-gap`).
|
|
56
|
+
- **ChatSplit** — ряд `list` + `messages` (`--chat-split-gap`). Не `SplitPane` (нет resize).
|
|
32
57
|
- **PageHead** — title + actions. `sup` — текст или Badge сразу после title (надстрочно; `0` скрыт). `sticky` — шапка прилипает к верху скролла (фон `--shell-bg`). `children` — фильтры/поиск под заголовком, отступ `--shell-gap`.
|
|
33
|
-
- **Section** — блок контента / auth.
|
|
58
|
+
- **Section** — блок контента / auth / подраздел. Корень по умолчанию `<section>` (не `<main>` — его даёт Layout). Тело: `direction` / `gap` / `columns` как у Stack. Ряд кнопок в `actions` — `<Stack direction="row">`.
|
|
34
59
|
- **Layout / Header** — оболочка приложения. `Layout sidebar={<Sidebar />}` — ряд: меню слева + контент. `fixed={["header", "footer", "sidebar"]}` (по умолчанию) — эти слоты не скроллятся, крутится только `children`. `fixed={[]}` — поток страницы. Высота каркаса: `--layout-height` (`100dvh`).
|
|
35
|
-
- **Sidebar** — левое
|
|
36
|
-
- **NavSections** — сайдбар
|
|
37
|
-
- **
|
|
60
|
+
- **Sidebar** — левое меню приложения. `variant="cards"` (карточки) или `"plain"` (плоский backoffice). Только `groups` + `value` / `defaultValue` + `onValueChange`. Подменю: у пункта `items` + `defaultOpen`. Контроль раскрытия: `open` / `onOpenChange`. Клик по родителю только раскрывает; выбор — у листьев. Слоты: `brand`, `extra`, `footer`. Меню аккаунта: `footer` + `footerItems` + `onFooterValueChange` — chip + панель в цветах темы (`Dropdown tone="account"`, токены `--sidebar-account-*`). `version` под chip. Отступ вложенных: `--sidebar-indent`.
|
|
61
|
+
- **NavSections** — сайдбар **страницы** + панель (не замена Sidebar в Layout). У группы обязателен `value`. Подменю как у Sidebar. Панель: `resolveSection` (lazy) **или** готовые `children`. Клик по родителю только раскрывает.
|
|
62
|
+
- **TabSections** — горизонтальные вкладки + панель (`resolveSection` или `children`). Только табы без панели — `Menu variant="line"`.
|
|
63
|
+
- **Stack** — ряд/колонка/сетка (`direction="row"|"column"|"grid"`). `Block` / `Row` / `Column` **удалены** из пакета → `elcrm migrate components`. Сетка: `auto-fit` + `--stack-grid-min`, либо `columns={4}` / `columns={["1fr", "100px", "auto"]}`. С `columns` ряды `stretch`; в одну линию — `style={{ alignItems: "center" }}`.
|
|
38
64
|
- **Card** — курсор: `--card-pointer` (`auto` / `pointer`). Сетка Stack: `--stack-grid-min`.
|
|
39
65
|
- **Stat** — метрика: `label` + `value`, `tone` как у Badge, `labelMuted`. Не собирать из `span` в приложении.
|
|
40
|
-
- **Healthmap** — теплокарта
|
|
41
|
-
- **PanelInfo** — выезд
|
|
42
|
-
- **ActionGroup** — ряд иконок в карточке / строке: `label` + `
|
|
43
|
-
- **Toolbar** — фильтры над списком. `size`
|
|
66
|
+
- **Healthmap** — теплокарта (`data.days`). Слоты `brandName` / `brandSub` / `unit` / `hint`; locale: `months` / `weekdays` / `locale` / `loadingLabel` / `lastYearLabel`. Токены `--healthmap-cell`, `--healthmap-gap`, `--healthmap-l0`…`l4`.
|
|
67
|
+
- **PanelInfo** — выезд справа. Desktop — `aside`; узкий (`max-width: 56rem`) — `dialog` + `aria-modal`, focus trap, Escape не перебивает вложенный Popover. `open` / `defaultOpen` + `onOpenChange` (`onClose` — alias). Слоты: `children`, `footer`, `headerExtra`, `close`. Токены `--panel-info-*`.
|
|
68
|
+
- **ActionGroup** — ряд иконок в карточке / строке: `label` + `actions` и/или `children` (не group-`items`). Кнопки — `@elcrm/button`.
|
|
69
|
+
- **Toolbar** — фильтры над списком. `size` — токены `data-size` + `useToolbar()` (не cloneElement). Поля `@elcrm/form` ≥ 0.1.20 и `Dropdown` читают контекст. `align` / `width`. Токены `--toolbar-gap`, `--select-height` на `data-size`. Не `ActionGroup`.
|
|
70
|
+
- **Popover** — portal у якоря. Канон: `trigger={(api) => <button {...api.anchorProps}>…</button>}` (без cloneElement). Узел-trigger — клик/ARIA на обёртке.
|
|
44
71
|
- **TextGroup** — две строки: `title` + `description` (без аватара; с аватаром — AvatarName).
|
|
45
72
|
- **Text** — короткий текст. `tone="muted"` → `--shell-color-muted`. `as` — тег (`p`/`span`/…). Не `className="muted"` в приложении.
|
|
46
73
|
- **Loading** — индикатор ожидания. Мин. высота: `--loading-min-height` (`min(50vh, 24rem)`).
|
|
47
74
|
- **EmptyState** — заглушка / ошибка страницы. `--empty-max-width`, `--empty-padding`.
|
|
48
|
-
- **Item** — строка списка. `as`
|
|
75
|
+
- **Item** — строка списка. По умолчанию `<li>`; кликабельный — `as="button"` / `as="a"`. `active`. `variant="card"` — рамка и muted-фон. Не дублировать `.desk-row` в приложении.
|
|
76
|
+
- **Brand** — логотип в шапке. По умолчанию `<strong>`; кликабельный — `as="button"` / `as="a"`.
|
|
49
77
|
- **ItemColumn** — внутри Item: `icon` слева, столбик `title` / `description` / `footer` или `meta`, `extra` — бейджи справа сверху, `aside` — колонка справа. `unread` — жирный title. Title: `--item-column-title-size` / `--item-column-title-line` (в console = `--button-size-s` / `--button-line-height`).
|
|
50
|
-
- **Table** — `<table>` в обёртке со скроллом. `layout="fixed"
|
|
78
|
+
- **Table** — `<table>` в обёртке со скроллом. `stickyHeader` — липкий thead. `layout="fixed"`. Подпись — `<caption>`. Токены `--table-*`.
|
|
79
|
+
- **ChatFrame** — внутренний каркас ChatList/Messages; **не** в корневом экспорте → `@elcrm/components/ChatFrame` (`elcrm migrate components`).
|
|
51
80
|
- **CodeBlock** — bar + `<pre>`, `onCopy`, `children` для подсветки. Токены `--code-block-*`.
|
|
52
|
-
- **SplitPane** — две панели + drag handle. `direction`, `ratio`, `onRatioChange`, `minFirst` / `minSecond`. Не `ChatSplit` (только чат). Токены `--split-handle-*`.
|
|
81
|
+
- **SplitPane** — две панели + drag handle. `direction`, `ratio` / `defaultRatio`, `onRatioChange`, `minFirst` / `minSecond`. Не `ChatSplit` (только чат). Токены `--split-handle-*`.
|
|
53
82
|
- **Banner** — статусная полоса (`tone`: info / warning / danger / success). Не toast — не `Notice`.
|
|
54
83
|
- **IconButton** — `name` (`Icons.Line`) + обязательный `label` + `Tooltip` по умолчанию. `tooltip={false}` без подсказки. Низкий уровень — `@elcrm/button/IconButton` (`icon` + `label`).
|
|
55
84
|
|
|
56
85
|
## Меню
|
|
57
86
|
|
|
58
|
-
- **Menu** — inline-навигация в шапке. `items
|
|
59
|
-
- **Dropdown** — выпадающее меню по `items`/`groups` (portal)
|
|
60
|
-
- **Popover** — свой попап в portal (якорь + `children`).
|
|
87
|
+
- **Menu** — inline-навигация в шапке. `items` + `value` / `defaultValue` + `onValueChange`. `badge` (`0` скрыт). `variant="line"` — вкладки с чертой снизу.
|
|
88
|
+
- **Dropdown** — выпадающее меню по `items`/`groups` (portal) + `onValueChange`. `placement="auto"|"top"|"bottom"`. `size` — высота триггера; в `Toolbar` берётся сам. `item.submenu` — только шеврон (намёк); вложенное меню — снаружи.
|
|
89
|
+
- **Popover** — свой попап в portal (якорь + `children`). По умолчанию **без** `role="menu"`; меню пунктов — `Dropdown` (`role="menu"`). ARIA на триггере. z-index: `acquireOverlayZIndex`. Канон `trigger={(api) => …}`.
|
|
90
|
+
- **Breadcrumb** — `items` + `separator` / `collapseAfter`. `item.dropdown` — только ↕-намёк; меню — обернуть в `Dropdown`.
|
|
91
|
+
- Native `<button>` внутри chrome пакета — **внутренний primitive**; в приложении — `@elcrm/button`.
|
|
61
92
|
|
|
62
93
|
## Avatar
|
|
63
94
|
|
|
64
95
|
- **Avatar** — фото / инициалы (`name` только для инициалов). Скругление: `radius={10}` или `--avatar-radius` (дефолт `999px` — круг).
|
|
65
96
|
- **Badge** — метка: `tone`, `size` (`s`/`m`/`l`), `dot` (точка слева). Не Button.
|
|
66
97
|
- **AvatarName** — `title` + `description` (+ `name` для инициалов). Тот же `radius`. Цвета: `--avatar-name-title-color`, `--avatar-name-description-color`.
|
|
67
|
-
- **AvatarGroup** — `items` с `value`
|
|
98
|
+
- **AvatarGroup** — `items` с обязательным `value` + `title` / `name` / `src`; клик — `onValueChange(value)`.
|
|
99
|
+
- **Breadcrumb** — `items` + `onValueChange(value)`; `separator`, `collapseAfter`.
|
|
100
|
+
- **createLazyResolver** — кэш lazy-чанков; при ошибке загрузки слот сбрасывается; `.clear(segment?)` для retry.
|
|
68
101
|
|
|
69
102
|
## Pricing
|
|
70
103
|
|
|
@@ -82,12 +115,18 @@ import type { PricingPlan, HealthmapData } from "@elcrm/components";
|
|
|
82
115
|
- `--popup-shadow` общий с `@elcrm/form`.
|
|
83
116
|
- `elcrm css` — недостающие `--shell-*`.
|
|
84
117
|
- Импорт `@elcrm/components/style.css` — опционально.
|
|
118
|
+
- CSS Modules: в DOM всегда **3 буквы** `{семья}{компонент}{первая буква локального}` (короткий код, все a–z): List `.root` → `llr`, Layout `.root` → `slr`. В исходнике — слова. Семьи: `l` lists, `n` nav, `s` shell, `k` kit, `t` type, `a` avatar, `o` overlay, `g` pricing, `h` healthmap, `d` data. Не опирайся на эти className в приложении.
|
|
85
119
|
|
|
86
120
|
## Нельзя
|
|
87
121
|
|
|
88
122
|
- `import "@elcrm/…/themes.css"`.
|
|
89
|
-
- `Block` / `Row` / `Column` — `Stack` (`elcrm migrate components
|
|
123
|
+
- `Block` / `Row` / `Column` — удалены; только `Stack` (или `Section` с `direction`). `elcrm migrate components`.
|
|
124
|
+
- `applyToolbarSize` — удалён; размер — `useToolbar` / `data-size`.
|
|
125
|
+
- `PricingGroupItem` — удалён; тип `PricingPlan`.
|
|
90
126
|
- `key` / `activeKey` / `defaultActiveKey` в группах — `value` / `defaultValue` (`elcrm migrate components`).
|
|
127
|
+
- `onSelect` / `onItemClick` / `onFooterSelect` у Menu / Sidebar / Dropdown / Breadcrumb / AvatarGroup — `onValueChange` / `onFooterValueChange` (`elcrm migrate components`).
|
|
128
|
+
- `ActionGroup items=` — `actions=`; `Sidebar items=` (группы) — `groups=`.
|
|
129
|
+
- `ChatFrame` из `@elcrm/components` — deep `@elcrm/components/ChatFrame` (`elcrm migrate components`).
|
|
91
130
|
- Нативные `<button>` / инпуты вместо `@elcrm/button` / `@elcrm/form` в приложении.
|
|
92
131
|
- `size="sm"|"md"` → `"s"|"m"`.
|
|
93
132
|
- Хардкод цветов в модулях приложения, если есть токен.
|
|
@@ -32,7 +32,7 @@ const form = useForm({ login: "", password: "" });
|
|
|
32
32
|
Канонические поля: `StringField`, `PasswordField`, `TextareaField`, `NumberField`, `PercentField`, `MoneyField`, `MaskField`, `PhoneField`, `EmailField`, `UrlField`, `DateField`, `TimeField`, `SelectField`, `OptionsField`, `ModalField`, `RangeField`, `CheckField`, `RadioField`, `TagsField`, `RatingField`, `CodeField`, `FileField`, `DragDropField`, `RichTextField`, `ColorField`, `HiddenField`, `DisplayField`, `SearchField`, `TabsField`.
|
|
33
33
|
|
|
34
34
|
`disabled`, `size` (`"s"` | `"m"` | `"l"`) — у всех видимых `*Field` (кроме `HiddenField`).
|
|
35
|
-
В ряду фильтров — `<Toolbar size="s">` из `@elcrm/components
|
|
35
|
+
В ряду фильтров — `<Toolbar size="s">` из `@elcrm/components` (дефолт тулбара уже `"s"`): без своего `size` поля берут размер через `useToolbar` (`@elcrm/form` ≥ 0.1.20). Явный `size` у поля важнее. `--select-height` = `var(--field-height)` — селект той же высоты, что капсула.
|
|
36
36
|
|
|
37
37
|
`PasswordField`: `generate` — кнопка случайного пароля. `true` → `Az09#` (A–Z, a–z, 0–9, символы). `generate="A"` только заглавные, `"59"` цифры 5–9, `"#"` только символы. Длина — `maxLength` или 12.
|
|
38
38
|
|
|
@@ -139,6 +139,17 @@ body[data-theme="dark"] {
|
|
|
139
139
|
--sidebar-item-active: var(--shell-color);
|
|
140
140
|
--sidebar-item-active-bg: var(--shell-background-selected);
|
|
141
141
|
--sidebar-icon-active: var(--shell-color-accent);
|
|
142
|
+
--sidebar-account-menu-bg: var(--shell-background);
|
|
143
|
+
--sidebar-account-menu-color: var(--shell-color);
|
|
144
|
+
--sidebar-account-menu-border: var(--shell-border);
|
|
145
|
+
--sidebar-account-menu-shadow: var(--popup-shadow, var(--shell-shadow));
|
|
146
|
+
--sidebar-account-item-hover: var(--shell-background-hover);
|
|
147
|
+
--sidebar-account-item-icon: var(--shell-color-muted);
|
|
148
|
+
--sidebar-account-danger-hover: color-mix(
|
|
149
|
+
in srgb,
|
|
150
|
+
var(--shell-color-danger) 14%,
|
|
151
|
+
transparent
|
|
152
|
+
);
|
|
142
153
|
--shell-background-hover: color-mix(in srgb, var(--text) 8%, transparent);
|
|
143
154
|
--shell-background-selected: color-mix(in srgb, var(--accent) 14%, transparent);
|
|
144
155
|
--shell-shadow: var(--shadow);
|
|
@@ -139,6 +139,17 @@ body[data-theme="light"] {
|
|
|
139
139
|
--sidebar-item-active: var(--shell-color);
|
|
140
140
|
--sidebar-item-active-bg: var(--shell-background-selected);
|
|
141
141
|
--sidebar-icon-active: var(--shell-color-accent);
|
|
142
|
+
--sidebar-account-menu-bg: var(--shell-background);
|
|
143
|
+
--sidebar-account-menu-color: var(--shell-color);
|
|
144
|
+
--sidebar-account-menu-border: var(--shell-border);
|
|
145
|
+
--sidebar-account-menu-shadow: var(--popup-shadow, var(--shell-shadow));
|
|
146
|
+
--sidebar-account-item-hover: var(--shell-background-hover);
|
|
147
|
+
--sidebar-account-item-icon: var(--shell-color-muted);
|
|
148
|
+
--sidebar-account-danger-hover: color-mix(
|
|
149
|
+
in srgb,
|
|
150
|
+
var(--shell-color-danger) 12%,
|
|
151
|
+
transparent
|
|
152
|
+
);
|
|
142
153
|
--shell-background-hover: color-mix(in srgb, var(--text) 6%, transparent);
|
|
143
154
|
--shell-background-selected: color-mix(in srgb, var(--accent) 10%, transparent);
|
|
144
155
|
--shell-shadow: var(--shadow);
|
|
@@ -64,6 +64,11 @@
|
|
|
64
64
|
/* Layout: внутренний отступ и внешний margin каркаса */
|
|
65
65
|
--layout-padding: 0;
|
|
66
66
|
--layout-margin: 0;
|
|
67
|
+
--layout-gap: 0;
|
|
68
|
+
--layout-main-radius: 0;
|
|
69
|
+
--layout-main-background: transparent;
|
|
70
|
+
--layout-main-shadow: none;
|
|
71
|
+
--layout-main-border: none;
|
|
67
72
|
/* none — на всю ширину; иначе например 1440px */
|
|
68
73
|
--layout-max-width: none;
|
|
69
74
|
--elcrm-z-popover: 1000;
|
|
@@ -114,6 +119,7 @@
|
|
|
114
119
|
--item-column-icon-radius: var(--avatar-radius);
|
|
115
120
|
--item-column-title-size: var(--shell-font-size);
|
|
116
121
|
--item-column-title-line: 1.3;
|
|
122
|
+
--item-column-meta-size: var(--shell-font-size-sm);
|
|
117
123
|
--stat-value-size: 1.75rem;
|
|
118
124
|
--action-group-gap: var(--shell-gap-sm);
|
|
119
125
|
--toolbar-gap: var(--shell-gap-sm);
|
|
@@ -126,6 +132,14 @@
|
|
|
126
132
|
--sidebar-icon-size: 16px;
|
|
127
133
|
--sidebar-group-font-size: 11px;
|
|
128
134
|
--sidebar-indent: 14px;
|
|
135
|
+
--sidebar-account-chip-radius: 14px;
|
|
136
|
+
--sidebar-account-chip-padding: 8px;
|
|
137
|
+
--sidebar-account-chip-gap: 10px;
|
|
138
|
+
--sidebar-account-menu-radius: 18px;
|
|
139
|
+
--sidebar-account-menu-padding: 6px;
|
|
140
|
+
--sidebar-account-item-radius: 12px;
|
|
141
|
+
--sidebar-account-item-padding: 10px 12px;
|
|
142
|
+
--sidebar-account-item-gap: 12px;
|
|
129
143
|
--pricing-card-min-width: 16rem;
|
|
130
144
|
--pricing-table-label-width: 10rem;
|
|
131
145
|
--chat-list-width: 340px;
|
|
@@ -152,6 +166,8 @@
|
|
|
152
166
|
--table-cell-padding: 0.55rem 0.7rem;
|
|
153
167
|
--table-head-font-size: 0.75rem;
|
|
154
168
|
--table-code-font-size: 0.8rem;
|
|
169
|
+
--table-head-background: var(--shell-background);
|
|
170
|
+
--table-max-height: none;
|
|
155
171
|
--code-block-bg: var(--shell-background);
|
|
156
172
|
--code-block-bar-padding: 0.4rem 0.75rem;
|
|
157
173
|
--code-block-padding: 0.85rem 1rem;
|
|
@@ -168,6 +184,9 @@
|
|
|
168
184
|
--banner-padding: 0.55rem 0.85rem;
|
|
169
185
|
--banner-bg: color-mix(in srgb, var(--shell-color-accent) 8%, var(--shell-bg));
|
|
170
186
|
--banner-border: color-mix(in srgb, var(--shell-color-accent) 35%, var(--shell-border));
|
|
187
|
+
--loading-min-height: min(50vh, 24rem);
|
|
188
|
+
--empty-max-width: 36rem;
|
|
189
|
+
--empty-padding: 2rem;
|
|
171
190
|
}
|
|
172
191
|
|
|
173
192
|
/* —— база приложения (без захардкоженных цветов) —— */
|
|
@@ -144,6 +144,17 @@ body[data-theme="dark"] {
|
|
|
144
144
|
--sidebar-item-active: var(--shell-color);
|
|
145
145
|
--sidebar-item-active-bg: var(--shell-background-selected);
|
|
146
146
|
--sidebar-icon-active: var(--shell-color-accent);
|
|
147
|
+
--sidebar-account-menu-bg: var(--shell-background);
|
|
148
|
+
--sidebar-account-menu-color: var(--shell-color);
|
|
149
|
+
--sidebar-account-menu-border: var(--shell-border);
|
|
150
|
+
--sidebar-account-menu-shadow: var(--popup-shadow, var(--shell-shadow));
|
|
151
|
+
--sidebar-account-item-hover: var(--shell-background-hover);
|
|
152
|
+
--sidebar-account-item-icon: var(--shell-color-muted);
|
|
153
|
+
--sidebar-account-danger-hover: color-mix(
|
|
154
|
+
in srgb,
|
|
155
|
+
var(--shell-color-danger) 14%,
|
|
156
|
+
transparent
|
|
157
|
+
);
|
|
147
158
|
--shell-background-hover: color-mix(in srgb, var(--text) 8%, transparent);
|
|
148
159
|
--shell-background-selected: color-mix(in srgb, var(--accent) 14%, transparent);
|
|
149
160
|
--shell-shadow: var(--shadow);
|
|
@@ -144,6 +144,17 @@ body[data-theme="light"] {
|
|
|
144
144
|
--sidebar-item-active: var(--shell-color);
|
|
145
145
|
--sidebar-item-active-bg: var(--shell-background-selected);
|
|
146
146
|
--sidebar-icon-active: var(--shell-color-accent);
|
|
147
|
+
--sidebar-account-menu-bg: var(--shell-background);
|
|
148
|
+
--sidebar-account-menu-color: var(--shell-color);
|
|
149
|
+
--sidebar-account-menu-border: var(--shell-border);
|
|
150
|
+
--sidebar-account-menu-shadow: var(--popup-shadow, var(--shell-shadow));
|
|
151
|
+
--sidebar-account-item-hover: var(--shell-background-hover);
|
|
152
|
+
--sidebar-account-item-icon: var(--shell-color-muted);
|
|
153
|
+
--sidebar-account-danger-hover: color-mix(
|
|
154
|
+
in srgb,
|
|
155
|
+
var(--shell-color-danger) 12%,
|
|
156
|
+
transparent
|
|
157
|
+
);
|
|
147
158
|
--shell-background-hover: color-mix(in srgb, var(--text) 6%, transparent);
|
|
148
159
|
--shell-background-selected: color-mix(in srgb, var(--accent) 10%, transparent);
|
|
149
160
|
--shell-shadow: var(--shadow);
|
|
@@ -64,6 +64,11 @@
|
|
|
64
64
|
/* Layout: внутренний отступ и внешний margin каркаса */
|
|
65
65
|
--layout-padding: 0;
|
|
66
66
|
--layout-margin: 0;
|
|
67
|
+
--layout-gap: 0;
|
|
68
|
+
--layout-main-radius: 0;
|
|
69
|
+
--layout-main-background: transparent;
|
|
70
|
+
--layout-main-shadow: none;
|
|
71
|
+
--layout-main-border: none;
|
|
67
72
|
/* none — на всю ширину; иначе например 1440px */
|
|
68
73
|
--layout-max-width: none;
|
|
69
74
|
--elcrm-z-popover: 1000;
|
|
@@ -125,6 +130,7 @@
|
|
|
125
130
|
--item-column-icon-radius: var(--avatar-radius);
|
|
126
131
|
--item-column-title-size: var(--shell-font-size);
|
|
127
132
|
--item-column-title-line: 1.3;
|
|
133
|
+
--item-column-meta-size: var(--shell-font-size-sm);
|
|
128
134
|
--stat-value-size: 1.75rem;
|
|
129
135
|
--action-group-gap: var(--shell-gap-sm);
|
|
130
136
|
--toolbar-gap: var(--shell-gap-sm);
|
|
@@ -137,6 +143,14 @@
|
|
|
137
143
|
--sidebar-icon-size: 16px;
|
|
138
144
|
--sidebar-group-font-size: 11px;
|
|
139
145
|
--sidebar-indent: 14px;
|
|
146
|
+
--sidebar-account-chip-radius: 14px;
|
|
147
|
+
--sidebar-account-chip-padding: 8px;
|
|
148
|
+
--sidebar-account-chip-gap: 10px;
|
|
149
|
+
--sidebar-account-menu-radius: 18px;
|
|
150
|
+
--sidebar-account-menu-padding: 6px;
|
|
151
|
+
--sidebar-account-item-radius: 12px;
|
|
152
|
+
--sidebar-account-item-padding: 10px 12px;
|
|
153
|
+
--sidebar-account-item-gap: 12px;
|
|
140
154
|
--pricing-card-min-width: 16rem;
|
|
141
155
|
--pricing-table-label-width: 10rem;
|
|
142
156
|
--chat-list-width: 340px;
|
|
@@ -163,6 +177,8 @@
|
|
|
163
177
|
--table-cell-padding: 0.55rem 0.7rem;
|
|
164
178
|
--table-head-font-size: 0.75rem;
|
|
165
179
|
--table-code-font-size: 0.8rem;
|
|
180
|
+
--table-head-background: var(--shell-background);
|
|
181
|
+
--table-max-height: none;
|
|
166
182
|
--code-block-bg: var(--shell-background);
|
|
167
183
|
--code-block-bar-padding: 0.4rem 0.75rem;
|
|
168
184
|
--code-block-padding: 0.85rem 1rem;
|
|
@@ -179,6 +195,9 @@
|
|
|
179
195
|
--banner-padding: 0.55rem 0.85rem;
|
|
180
196
|
--banner-bg: color-mix(in srgb, var(--shell-color-accent) 8%, var(--shell-bg));
|
|
181
197
|
--banner-border: color-mix(in srgb, var(--shell-color-accent) 35%, var(--shell-border));
|
|
198
|
+
--loading-min-height: min(50vh, 24rem);
|
|
199
|
+
--empty-max-width: 36rem;
|
|
200
|
+
--empty-padding: 2rem;
|
|
182
201
|
}
|
|
183
202
|
|
|
184
203
|
/* —— база приложения (без захардкоженных цветов) —— */
|