@veltra/styles 1.2.22 → 1.2.23
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/_functions.scss +11 -0
- package/dist/theme/helper.d.ts +7 -1
- package/dist/theme/helper.js +16 -1
- package/dist/theme/helper.js.map +1 -1
- package/dist/theme/index.d.ts +2 -2
- package/dist/theme/index.js +2 -2
- package/dist/theme/ui-theme.d.ts +4 -0
- package/dist/theme/ui-theme.js +75 -2
- package/dist/theme/ui-theme.js.map +1 -1
- package/package.json +3 -3
- package/src/_functions.scss +11 -0
- package/src/theme/__test__/ui-theme.test.ts +14 -0
- package/src/theme/helper.ts +21 -0
- package/src/theme/ui-theme.ts +84 -4
package/dist/_functions.scss
CHANGED
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
|
|
4
4
|
$namespace: 'u' !default;
|
|
5
5
|
|
|
6
|
+
// 引用主题注入的 alpha token,如 color-a(color, 8, primary) → var(--u-color-primary-a-8)
|
|
7
|
+
@function color-a($basename, $alpha, $nodes...) {
|
|
8
|
+
$suffix: '';
|
|
9
|
+
|
|
10
|
+
@each $node in $nodes {
|
|
11
|
+
$suffix: $suffix + '-' + $node;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@return var(--#{$namespace}-#{$basename}#{$suffix}-a-#{$alpha});
|
|
15
|
+
}
|
|
16
|
+
|
|
6
17
|
// 使用单个变量(全局主题 token,`--u-*`)
|
|
7
18
|
@function use-var($basename, $nodes...) {
|
|
8
19
|
$suffix: '';
|
package/dist/theme/helper.d.ts
CHANGED
|
@@ -11,6 +11,12 @@ declare function HEXToRGB(color: string): RGBColor;
|
|
|
11
11
|
* @returns 混合结果的十六进制表示。
|
|
12
12
|
*/
|
|
13
13
|
declare function mixColor(color1: `#${string}`, color2: `#${string}`, ratio: number): string;
|
|
14
|
+
/** 将 `#RRGGBB` 转为指定不透明度百分比的 `rgba()`(等价于 color-mix N% + transparent) */
|
|
15
|
+
declare function hexWithAlpha(hex: `#${string}`, alphaPercent: number): string;
|
|
16
|
+
/** 别名:`ratio` 为 0–1 时不透明度(0.08 → 8%) */
|
|
17
|
+
declare function mixColorWithAlpha(color: `#${string}`, ratio: number): string;
|
|
18
|
+
/** 剥离 `#RRGGBBAA` 的 alpha 通道,返回 `#RRGGBB` */
|
|
19
|
+
declare function hexRgbOnly(hex: string): `#${string}`;
|
|
14
20
|
declare function defineBySize(variable: Record<'small' | 'default' | 'large', number>): Record<'small' | 'default' | 'large', number>;
|
|
15
21
|
/**
|
|
16
22
|
* 引用全局主题 CSS 变量(`--u-*` 命名空间)
|
|
@@ -18,5 +24,5 @@ declare function defineBySize(variable: Record<'small' | 'default' | 'large', nu
|
|
|
18
24
|
*/
|
|
19
25
|
declare function cssVar(prop: string): string;
|
|
20
26
|
//#endregion
|
|
21
|
-
export { HEXToRGB, cssVar, defineBySize, mixColor };
|
|
27
|
+
export { HEXToRGB, cssVar, defineBySize, hexRgbOnly, hexWithAlpha, mixColor, mixColorWithAlpha };
|
|
22
28
|
//# sourceMappingURL=helper.d.ts.map
|
package/dist/theme/helper.js
CHANGED
|
@@ -41,6 +41,21 @@ function mixColor(color1, color2, ratio) {
|
|
|
41
41
|
return hex.length === 1 ? "0" + hex : hex;
|
|
42
42
|
}).join("");
|
|
43
43
|
}
|
|
44
|
+
/** 将 `#RRGGBB` 转为指定不透明度百分比的 `rgba()`(等价于 color-mix N% + transparent) */
|
|
45
|
+
function hexWithAlpha(hex, alphaPercent) {
|
|
46
|
+
const [r, g, b] = HEXToRGB(hex);
|
|
47
|
+
return `rgba(${r}, ${g}, ${b}, ${Math.round(alphaPercent / 100 * 1e3) / 1e3})`;
|
|
48
|
+
}
|
|
49
|
+
/** 别名:`ratio` 为 0–1 时不透明度(0.08 → 8%) */
|
|
50
|
+
function mixColorWithAlpha(color, ratio) {
|
|
51
|
+
return hexWithAlpha(color, ratio * 100);
|
|
52
|
+
}
|
|
53
|
+
/** 剥离 `#RRGGBBAA` 的 alpha 通道,返回 `#RRGGBB` */
|
|
54
|
+
function hexRgbOnly(hex) {
|
|
55
|
+
const normalized = hex.replace(/^#/, "");
|
|
56
|
+
if (normalized.length === 8) return `#${normalized.slice(0, 6)}`;
|
|
57
|
+
return hex;
|
|
58
|
+
}
|
|
44
59
|
function defineBySize(variable) {
|
|
45
60
|
return variable;
|
|
46
61
|
}
|
|
@@ -52,6 +67,6 @@ function cssVar(prop) {
|
|
|
52
67
|
return `var(--u-${prop})`;
|
|
53
68
|
}
|
|
54
69
|
//#endregion
|
|
55
|
-
export { HEXToRGB, cssVar, defineBySize, mixColor };
|
|
70
|
+
export { HEXToRGB, cssVar, defineBySize, hexRgbOnly, hexWithAlpha, mixColor, mixColorWithAlpha };
|
|
56
71
|
|
|
57
72
|
//# sourceMappingURL=helper.js.map
|
package/dist/theme/helper.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helper.js","names":[],"sources":["../../src/theme/helper.ts"],"sourcesContent":["import type { RGBColor } from './type'\n\n/** 实现十六进制颜色转RGB颜色,包括透明度 */\nexport function HEXToRGB(color: string): RGBColor {\n // 移除可能存在的 '#' 前缀\n let hex = color.replace(/^#/, '')\n let [r, g, b] = [0, 0, 0]\n\n if (hex.length === 3) {\n const [r10, g10, b10] = [hex[0]!, hex[1]!, hex[2]!]\n hex = `${r10}${r10}${g10}${g10}${b10}${b10}`\n }\n\n r = parseInt(hex.slice(0, 2), 16)\n g = parseInt(hex.slice(2, 4), 16)\n b = parseInt(hex.slice(4, 6), 16)\n\n return [r, g, b]\n}\n\n/**\n * 混合两个颜色,并返回混合结果的十六进制表示。\n * @param color1 - 第一个颜色,格式为`#RRGGBB`。\n * @param color2 - 第二个颜色,格式为`#RRGGBB`。\n * @param ratio - 颜色混合的比例,取值范围为0到1。\n * @returns 混合结果的十六进制表示。\n */\nexport function mixColor(color1: `#${string}`, color2: `#${string}`, ratio: number): string {\n if (ratio > 1) throw new Error('ratio的值在0-1之间')\n const color1RGB = HEXToRGB(color1)\n const color2RGB = HEXToRGB(color2)\n\n const color1Ratio = 1 - ratio\n\n return (\n '#' +\n color1RGB\n .map((n, i) => {\n const hex = Math.floor(color1Ratio * n + color2RGB[i]! * ratio).toString(16)\n return hex.length === 1 ? '0' + hex : hex\n })\n .join('')\n )\n}\n\nexport function defineBySize(\n variable: Record<'small' | 'default' | 'large', number>\n): Record<'small' | 'default' | 'large', number> {\n return variable\n}\n\n/**\n * 引用全局主题 CSS 变量(`--u-*` 命名空间)\n * @param prop - 与 Theme 结构对应的连字符路径,如 `text-color-title`、`bg-color-hover`\n */\nexport function cssVar(prop: string): string {\n return `var(--u-${prop})`\n}\n"],"mappings":";;AAGA,SAAgB,SAAS,OAAyB;CAEhD,IAAI,MAAM,MAAM,QAAQ,MAAM,EAAE;CAChC,IAAI,CAAC,GAAG,GAAG,KAAK;EAAC;EAAG;EAAG;CAAC;CAExB,IAAI,IAAI,WAAW,GAAG;EACpB,MAAM,CAAC,KAAK,KAAK,OAAO;GAAC,IAAI;GAAK,IAAI;GAAK,IAAI;EAAG;EAClD,MAAM,GAAG,MAAM,MAAM,MAAM,MAAM,MAAM;CACzC;CAEA,IAAI,SAAS,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE;CAChC,IAAI,SAAS,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE;CAChC,IAAI,SAAS,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE;CAEhC,OAAO;EAAC;EAAG;EAAG;CAAC;AACjB;;;;;;;;AASA,SAAgB,SAAS,QAAsB,QAAsB,OAAuB;CAC1F,IAAI,QAAQ,GAAG,MAAM,IAAI,MAAM,eAAe;CAC9C,MAAM,YAAY,SAAS,MAAM;CACjC,MAAM,YAAY,SAAS,MAAM;CAEjC,MAAM,cAAc,IAAI;CAExB,OACE,MACA,UACG,KAAK,GAAG,MAAM;EACb,MAAM,MAAM,KAAK,MAAM,cAAc,IAAI,UAAU,KAAM,KAAK,CAAC,CAAC,SAAS,EAAE;EAC3E,OAAO,IAAI,WAAW,IAAI,MAAM,MAAM;CACxC,CAAC,CAAC,CACD,KAAK,EAAE;AAEd;AAEA,SAAgB,aACd,UAC+C;CAC/C,OAAO;AACT;;;;;AAMA,SAAgB,OAAO,MAAsB;CAC3C,OAAO,WAAW,KAAK;AACzB"}
|
|
1
|
+
{"version":3,"file":"helper.js","names":[],"sources":["../../src/theme/helper.ts"],"sourcesContent":["import type { RGBColor } from './type'\n\n/** 实现十六进制颜色转RGB颜色,包括透明度 */\nexport function HEXToRGB(color: string): RGBColor {\n // 移除可能存在的 '#' 前缀\n let hex = color.replace(/^#/, '')\n let [r, g, b] = [0, 0, 0]\n\n if (hex.length === 3) {\n const [r10, g10, b10] = [hex[0]!, hex[1]!, hex[2]!]\n hex = `${r10}${r10}${g10}${g10}${b10}${b10}`\n }\n\n r = parseInt(hex.slice(0, 2), 16)\n g = parseInt(hex.slice(2, 4), 16)\n b = parseInt(hex.slice(4, 6), 16)\n\n return [r, g, b]\n}\n\n/**\n * 混合两个颜色,并返回混合结果的十六进制表示。\n * @param color1 - 第一个颜色,格式为`#RRGGBB`。\n * @param color2 - 第二个颜色,格式为`#RRGGBB`。\n * @param ratio - 颜色混合的比例,取值范围为0到1。\n * @returns 混合结果的十六进制表示。\n */\nexport function mixColor(color1: `#${string}`, color2: `#${string}`, ratio: number): string {\n if (ratio > 1) throw new Error('ratio的值在0-1之间')\n const color1RGB = HEXToRGB(color1)\n const color2RGB = HEXToRGB(color2)\n\n const color1Ratio = 1 - ratio\n\n return (\n '#' +\n color1RGB\n .map((n, i) => {\n const hex = Math.floor(color1Ratio * n + color2RGB[i]! * ratio).toString(16)\n return hex.length === 1 ? '0' + hex : hex\n })\n .join('')\n )\n}\n\n/** 将 `#RRGGBB` 转为指定不透明度百分比的 `rgba()`(等价于 color-mix N% + transparent) */\nexport function hexWithAlpha(hex: `#${string}`, alphaPercent: number): string {\n const [r, g, b] = HEXToRGB(hex)\n const alpha = Math.round((alphaPercent / 100) * 1000) / 1000\n return `rgba(${r}, ${g}, ${b}, ${alpha})`\n}\n\n/** 别名:`ratio` 为 0–1 时不透明度(0.08 → 8%) */\nexport function mixColorWithAlpha(color: `#${string}`, ratio: number): string {\n return hexWithAlpha(color, ratio * 100)\n}\n\n/** 剥离 `#RRGGBBAA` 的 alpha 通道,返回 `#RRGGBB` */\nexport function hexRgbOnly(hex: string): `#${string}` {\n const normalized = hex.replace(/^#/, '')\n if (normalized.length === 8) {\n return `#${normalized.slice(0, 6)}` as `#${string}`\n }\n return hex as `#${string}`\n}\n\nexport function defineBySize(\n variable: Record<'small' | 'default' | 'large', number>\n): Record<'small' | 'default' | 'large', number> {\n return variable\n}\n\n/**\n * 引用全局主题 CSS 变量(`--u-*` 命名空间)\n * @param prop - 与 Theme 结构对应的连字符路径,如 `text-color-title`、`bg-color-hover`\n */\nexport function cssVar(prop: string): string {\n return `var(--u-${prop})`\n}\n"],"mappings":";;AAGA,SAAgB,SAAS,OAAyB;CAEhD,IAAI,MAAM,MAAM,QAAQ,MAAM,EAAE;CAChC,IAAI,CAAC,GAAG,GAAG,KAAK;EAAC;EAAG;EAAG;CAAC;CAExB,IAAI,IAAI,WAAW,GAAG;EACpB,MAAM,CAAC,KAAK,KAAK,OAAO;GAAC,IAAI;GAAK,IAAI;GAAK,IAAI;EAAG;EAClD,MAAM,GAAG,MAAM,MAAM,MAAM,MAAM,MAAM;CACzC;CAEA,IAAI,SAAS,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE;CAChC,IAAI,SAAS,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE;CAChC,IAAI,SAAS,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE;CAEhC,OAAO;EAAC;EAAG;EAAG;CAAC;AACjB;;;;;;;;AASA,SAAgB,SAAS,QAAsB,QAAsB,OAAuB;CAC1F,IAAI,QAAQ,GAAG,MAAM,IAAI,MAAM,eAAe;CAC9C,MAAM,YAAY,SAAS,MAAM;CACjC,MAAM,YAAY,SAAS,MAAM;CAEjC,MAAM,cAAc,IAAI;CAExB,OACE,MACA,UACG,KAAK,GAAG,MAAM;EACb,MAAM,MAAM,KAAK,MAAM,cAAc,IAAI,UAAU,KAAM,KAAK,CAAC,CAAC,SAAS,EAAE;EAC3E,OAAO,IAAI,WAAW,IAAI,MAAM,MAAM;CACxC,CAAC,CAAC,CACD,KAAK,EAAE;AAEd;;AAGA,SAAgB,aAAa,KAAmB,cAA8B;CAC5E,MAAM,CAAC,GAAG,GAAG,KAAK,SAAS,GAAG;CAE9B,OAAO,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,IADf,KAAK,MAAO,eAAe,MAAO,GAAI,IAAI,IACjB;AACzC;;AAGA,SAAgB,kBAAkB,OAAqB,OAAuB;CAC5E,OAAO,aAAa,OAAO,QAAQ,GAAG;AACxC;;AAGA,SAAgB,WAAW,KAA2B;CACpD,MAAM,aAAa,IAAI,QAAQ,MAAM,EAAE;CACvC,IAAI,WAAW,WAAW,GACxB,OAAO,IAAI,WAAW,MAAM,GAAG,CAAC;CAElC,OAAO;AACT;AAEA,SAAgB,aACd,UAC+C;CAC/C,OAAO;AACT;;;;;AAMA,SAAgB,OAAO,MAAsB;CAC3C,OAAO,WAAW,KAAK;AACzB"}
|
package/dist/theme/index.d.ts
CHANGED
|
@@ -6,5 +6,5 @@ import { heroDarkTheme, heroLightTheme } from "./presets/hero.js";
|
|
|
6
6
|
import { shadcnDarkTheme, shadcnLightTheme } from "./presets/shadcn.js";
|
|
7
7
|
import { glassDarkTheme, glassLightTheme } from "./presets/glass.js";
|
|
8
8
|
import { currentTheme, loadTheme, setTheme } from "./load-theme.js";
|
|
9
|
-
import { HEXToRGB, cssVar, defineBySize, mixColor } from "./helper.js";
|
|
10
|
-
export { HEXToRGB, type RGBColor, type Theme, UITheme, cssVar, currentTheme, darkTheme, defineBySize, glassDarkTheme, glassLightTheme, heroDarkTheme, heroLightTheme, lightTheme, loadTheme, mixColor, setTheme, shadcnDarkTheme, shadcnLightTheme };
|
|
9
|
+
import { HEXToRGB, cssVar, defineBySize, hexRgbOnly, hexWithAlpha, mixColor, mixColorWithAlpha } from "./helper.js";
|
|
10
|
+
export { HEXToRGB, type RGBColor, type Theme, UITheme, cssVar, currentTheme, darkTheme, defineBySize, glassDarkTheme, glassLightTheme, heroDarkTheme, heroLightTheme, hexRgbOnly, hexWithAlpha, lightTheme, loadTheme, mixColor, mixColorWithAlpha, setTheme, shadcnDarkTheme, shadcnLightTheme };
|
package/dist/theme/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HEXToRGB, cssVar, defineBySize, mixColor } from "./helper.js";
|
|
1
|
+
import { HEXToRGB, cssVar, defineBySize, hexRgbOnly, hexWithAlpha, mixColor, mixColorWithAlpha } from "./helper.js";
|
|
2
2
|
import { UITheme } from "./ui-theme.js";
|
|
3
3
|
import { lightTheme } from "./presets/light.js";
|
|
4
4
|
import { darkTheme } from "./presets/dark.js";
|
|
@@ -6,4 +6,4 @@ import { heroDarkTheme, heroLightTheme } from "./presets/hero.js";
|
|
|
6
6
|
import { shadcnDarkTheme, shadcnLightTheme } from "./presets/shadcn.js";
|
|
7
7
|
import { glassDarkTheme, glassLightTheme } from "./presets/glass.js";
|
|
8
8
|
import { currentTheme, loadTheme, setTheme } from "./load-theme.js";
|
|
9
|
-
export { HEXToRGB, UITheme, cssVar, currentTheme, darkTheme, defineBySize, glassDarkTheme, glassLightTheme, heroDarkTheme, heroLightTheme, lightTheme, loadTheme, mixColor, setTheme, shadcnDarkTheme, shadcnLightTheme };
|
|
9
|
+
export { HEXToRGB, UITheme, cssVar, currentTheme, darkTheme, defineBySize, glassDarkTheme, glassLightTheme, heroDarkTheme, heroLightTheme, hexRgbOnly, hexWithAlpha, lightTheme, loadTheme, mixColor, mixColorWithAlpha, setTheme, shadcnDarkTheme, shadcnLightTheme };
|
package/dist/theme/ui-theme.d.ts
CHANGED
|
@@ -16,6 +16,10 @@ declare class UITheme {
|
|
|
16
16
|
private renderTypeColor;
|
|
17
17
|
private renderShadow;
|
|
18
18
|
private renderBGColorAlpha;
|
|
19
|
+
/** 语义色 / 边框 / 阴影 / 文本色的 alpha token(替代 color-mix + transparent) */
|
|
20
|
+
private renderAlphaTokens;
|
|
21
|
+
/** 组件级两色混合 token(collapse / batch-edit / kbd) */
|
|
22
|
+
private renderComponentMixTokens;
|
|
19
23
|
private renderBGFilter;
|
|
20
24
|
/** 主题变量声明列表,均为 `--u-` 前缀的 CSS 自定义属性 */
|
|
21
25
|
themeToDeclarationList(theme: Theme): string[];
|
package/dist/theme/ui-theme.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { componentCssVarsDarkDecls, componentCssVarsLightDecls } from "./component-css-vars.js";
|
|
2
|
-
import { mixColor } from "./helper.js";
|
|
2
|
+
import { hexRgbOnly, hexWithAlpha, mixColor } from "./helper.js";
|
|
3
3
|
import { isObj, str } from "@cat-kit/core";
|
|
4
4
|
import { withUnit } from "@veltra/utils";
|
|
5
5
|
import { reactive, toRaw, watch } from "vue";
|
|
@@ -63,7 +63,78 @@ var UITheme = class UITheme {
|
|
|
63
63
|
}
|
|
64
64
|
renderBGColorAlpha(theme) {
|
|
65
65
|
const { color } = theme.bg;
|
|
66
|
-
|
|
66
|
+
const alphas = [70];
|
|
67
|
+
const lines = Object.keys(color).map((type) => `--u-bg-color-${type}-alpha: ${color[type]}aa`);
|
|
68
|
+
for (const type of Object.keys(color)) {
|
|
69
|
+
const hex = color[type];
|
|
70
|
+
for (const a of alphas) lines.push(`--u-bg-color-${type}-a-${a}: ${hexWithAlpha(hex, a)}`);
|
|
71
|
+
}
|
|
72
|
+
return lines.join(";");
|
|
73
|
+
}
|
|
74
|
+
/** 语义色 / 边框 / 阴影 / 文本色的 alpha token(替代 color-mix + transparent) */
|
|
75
|
+
renderAlphaTokens(theme) {
|
|
76
|
+
const alphas = [
|
|
77
|
+
4,
|
|
78
|
+
5,
|
|
79
|
+
8,
|
|
80
|
+
10,
|
|
81
|
+
11,
|
|
82
|
+
16,
|
|
83
|
+
22,
|
|
84
|
+
28,
|
|
85
|
+
35,
|
|
86
|
+
40,
|
|
87
|
+
50,
|
|
88
|
+
52,
|
|
89
|
+
60,
|
|
90
|
+
70,
|
|
91
|
+
86
|
|
92
|
+
];
|
|
93
|
+
const lines = [];
|
|
94
|
+
for (const type of Object.keys(theme.color)) {
|
|
95
|
+
const hex = theme.color[type];
|
|
96
|
+
for (const a of alphas) lines.push(`--u-color-${type}-a-${a}: ${hexWithAlpha(hex, a)}`);
|
|
97
|
+
}
|
|
98
|
+
const borderHex = theme.border.color;
|
|
99
|
+
for (const a of alphas) lines.push(`--u-border-color-a-${a}: ${hexWithAlpha(borderHex, a)}`);
|
|
100
|
+
const shadowHex = hexRgbOnly(theme.shadow.color);
|
|
101
|
+
for (const a of alphas) lines.push(`--u-shadow-color-a-${a}: ${hexWithAlpha(shadowHex, a)}`);
|
|
102
|
+
const textColor = theme["text-color"];
|
|
103
|
+
for (const type of Object.keys(textColor)) {
|
|
104
|
+
const value = textColor[type];
|
|
105
|
+
if (typeof value === "string" && /^#[0-9a-fA-F]{6}$/.test(value)) {
|
|
106
|
+
const hex = value;
|
|
107
|
+
for (const a of alphas) lines.push(`--u-text-color-${type}-a-${a}: ${hexWithAlpha(hex, a)}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return lines.join(";");
|
|
111
|
+
}
|
|
112
|
+
/** 组件级两色混合 token(collapse / batch-edit / kbd) */
|
|
113
|
+
renderComponentMixTokens(theme) {
|
|
114
|
+
const hover = theme.bg.color.hover;
|
|
115
|
+
const top = theme.bg.color.top;
|
|
116
|
+
const black = theme.bg.color.black;
|
|
117
|
+
const primary = theme.color.primary;
|
|
118
|
+
const borderColor = theme.border.color;
|
|
119
|
+
const shadowColor = hexRgbOnly(theme.shadow.color);
|
|
120
|
+
const itemBg = mixColor(hover, top, .28);
|
|
121
|
+
const itemHoverBg = mixColor(primary, itemBg, .96);
|
|
122
|
+
const itemActiveBg = mixColor(primary, itemBg, .95);
|
|
123
|
+
const headerActiveBg = mixColor(primary, itemBg, .92);
|
|
124
|
+
const contentBg = mixColor(itemActiveBg, top, .58);
|
|
125
|
+
const formHeaderBg = mixColor(top, hover, .04);
|
|
126
|
+
const kbdInset = mixColor(top, black, .25);
|
|
127
|
+
return [
|
|
128
|
+
`--u-collapse-item-bg: ${itemBg}`,
|
|
129
|
+
`--u-collapse-item-hover-bg: ${itemHoverBg}`,
|
|
130
|
+
`--u-collapse-item-active-bg: ${itemActiveBg}`,
|
|
131
|
+
`--u-collapse-header-active-bg: ${headerActiveBg}`,
|
|
132
|
+
`--u-collapse-content-bg: ${contentBg}`,
|
|
133
|
+
`--u-batch-edit-form-header-bg: ${formHeaderBg}`,
|
|
134
|
+
`--u-kbd-inset-shadow: ${kbdInset}`,
|
|
135
|
+
`--u-kbd-border-shadow: ${hexWithAlpha(borderColor, 50)}`,
|
|
136
|
+
`--u-kbd-drop-shadow: ${hexWithAlpha(shadowColor, 60)}`
|
|
137
|
+
].join(";");
|
|
67
138
|
}
|
|
68
139
|
renderBGFilter(theme) {
|
|
69
140
|
const { filter } = theme.bg;
|
|
@@ -77,6 +148,8 @@ var UITheme = class UITheme {
|
|
|
77
148
|
this.renderTypeColor(theme),
|
|
78
149
|
this.renderShadow(theme),
|
|
79
150
|
this.renderBGColorAlpha(theme),
|
|
151
|
+
this.renderAlphaTokens(theme),
|
|
152
|
+
this.renderComponentMixTokens(theme),
|
|
80
153
|
this.renderBGFilter(theme),
|
|
81
154
|
this.renderBorder(theme)
|
|
82
155
|
];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui-theme.js","names":[],"sources":["../../src/theme/ui-theme.ts"],"sourcesContent":["import { isObj, str } from '@cat-kit/core'\nimport { withUnit } from '@veltra/utils'\nimport { reactive, toRaw, watch } from 'vue'\n\nimport { componentCssVarsDarkDecls, componentCssVarsLightDecls } from './component-css-vars'\nimport { mixColor } from './helper'\nimport type { Theme } from './type'\n\ntype RecursivePartial<T> = {\n [P in keyof T]?: T[P] extends object ? RecursivePartial<T[P]> : T[P]\n}\n\nexport class UITheme {\n static themeID = 'ultra-ui-theme'\n\n private static adoptedSheet: CSSStyleSheet | null = null\n\n readonly theme: Theme\n\n private readonly reactiveEnabled: boolean\n\n constructor(theme: Theme, options?: { reactive?: boolean }) {\n this.reactiveEnabled = options?.reactive !== false\n this.theme = reactive(theme) as Theme\n if (this.reactiveEnabled) {\n watch(this.theme, () => this.render(), { deep: true })\n }\n }\n\n static setTheme(mode: 'light' | 'dark' | 'auto'): void {\n if (typeof document === 'undefined') return\n const el = document.documentElement\n if (mode === 'auto') {\n delete el.dataset.theme\n } else {\n el.dataset.theme = mode\n }\n }\n\n private renderBase(\n theme: Record<string, unknown>,\n themeRules: string[] = [],\n parentKey = '--u'\n ): string[] {\n Object.keys(theme).forEach((key) => {\n const value = theme[key]\n const varKey = `${parentKey.startsWith('-') ? parentKey : str(parentKey).kebabCase()}-${str(key).kebabCase()}`\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n this.renderBase(value as Record<string, unknown>, themeRules, varKey)\n } else {\n if (value || value === 0) {\n const v = withUnit(value as number | string, 'px')\n if (v !== undefined) {\n themeRules.push(`${varKey}: ${v}`)\n }\n }\n }\n })\n\n return themeRules\n }\n\n private renderBorder(theme: Theme): string {\n const shorthandKeys = (Object.keys(theme.border) as Array<keyof typeof theme.border>).filter(\n (key) => key !== 'mutedColor'\n )\n const border = shorthandKeys.map((key) => `var(--u-border-${key})`).join(' ')\n\n return [\n `--u-border: ${border}`,\n `--u-border-muted: var(--u-border-muted-color) var(--u-border-width) var(--u-border-style)`\n ].join(';')\n }\n\n private renderTypeColor(theme: Theme): string {\n const { color } = theme\n\n const types = Object.keys(color)\n const rates = [1, 3, 5, 7, 9]\n\n return types\n .map((type) => {\n const colorValue = color[type as keyof typeof color]! as `#${string}`\n\n return rates\n .map((rate) => {\n const light = `--u-color-${type}-light-${rate}: ${mixColor(\n colorValue,\n '#fff',\n rate / 10\n )}`\n const dark = `--u-color-${type}-dark-${rate}: ${mixColor(\n colorValue,\n '#000',\n rate / 10\n )}`\n return `${light};${dark}`\n })\n .join(';')\n })\n .join(';')\n }\n\n private renderShadow(_theme: Theme): string {\n const shadow = ['x', 'y', 'blur', 'spread', 'color']\n .map((k) => `var(--u-shadow-${k})`)\n .join(' ')\n\n return `--u-shadow: ${shadow}`\n }\n\n private renderBGColorAlpha(theme: Theme): string {\n const { color } = theme.bg\n return Object.keys(color)\n .map((type) => `--u-bg-color-${type}-alpha: ${color[type as keyof typeof color]}aa`)\n .join(';')\n }\n\n private renderBGFilter(theme: Theme): string {\n const { filter } = theme.bg\n const value = filter.blur === 'none' ? 'none' : `${filter.blur} ${filter.saturate}`\n return `--u-bg-filter: ${value}`\n }\n\n /** 主题变量声明列表,均为 `--u-` 前缀的 CSS 自定义属性 */\n themeToDeclarationList(theme: Theme): string[] {\n const raw = theme as unknown as Record<string, unknown>\n const lines: string[] = [...this.renderBase(raw)]\n const chunks = [\n this.renderTypeColor(theme),\n this.renderShadow(theme),\n this.renderBGColorAlpha(theme),\n this.renderBGFilter(theme),\n this.renderBorder(theme)\n ]\n for (const c of chunks) {\n lines.push(\n ...c\n .split(';')\n .map((s) => s.trim())\n .filter(Boolean)\n )\n }\n return lines\n }\n\n private static declarationBlock(decls: string[]): string {\n return decls.join(';')\n }\n\n private static removeExistingStyleTag(doc: Document): void {\n const el = doc.getElementById(UITheme.themeID)\n if (el?.parentNode) {\n el.parentNode.removeChild(el)\n }\n }\n\n private static applyGlobalCSS(css: string): void {\n const doc = typeof document !== 'undefined' ? document : undefined\n if (!doc) return\n\n const canAdopt =\n typeof CSSStyleSheet !== 'undefined' && 'adoptedStyleSheets' in Document.prototype\n\n if (canAdopt) {\n UITheme.removeExistingStyleTag(doc)\n if (!UITheme.adoptedSheet) {\n UITheme.adoptedSheet = new CSSStyleSheet()\n doc.adoptedStyleSheets = [...doc.adoptedStyleSheets, UITheme.adoptedSheet]\n }\n UITheme.adoptedSheet.replaceSync(css)\n } else {\n if (UITheme.adoptedSheet) {\n UITheme.adoptedSheet = null\n }\n let style = doc.getElementById(UITheme.themeID)\n if (!style) {\n style = doc.createElement('style')\n style.id = UITheme.themeID\n doc.head.appendChild(style)\n }\n style.textContent = css\n }\n }\n\n /** 内置 light/dark:支持 data-theme 与 prefers-color-scheme */\n static injectBuiltInThemes(light: UITheme, dark: UITheme): void {\n const lightDecls = [\n ...light.themeToDeclarationList(toRaw(light.theme)),\n ...componentCssVarsLightDecls\n ]\n const darkDecls = [\n ...dark.themeToDeclarationList(toRaw(dark.theme)),\n ...componentCssVarsDarkDecls\n ]\n const lightBlock = UITheme.declarationBlock(lightDecls)\n const darkBlock = UITheme.declarationBlock(darkDecls)\n\n const css = [\n `html { ${lightBlock} }`,\n `@media (prefers-color-scheme: dark) {`,\n ` html:not([data-theme=\"light\"]) { ${darkBlock} }`,\n `}`,\n `html[data-theme=\"light\"] { ${lightBlock} }`,\n `html[data-theme=\"dark\"] { ${darkBlock} }`\n ].join('\\n')\n\n UITheme.applyGlobalCSS(css)\n }\n\n render(): void {\n const isDark =\n typeof document !== 'undefined' && document.documentElement.dataset.theme === 'dark'\n const componentDecls = isDark ? componentCssVarsDarkDecls : componentCssVarsLightDecls\n const decls = [...this.themeToDeclarationList(toRaw(this.theme)), ...componentDecls]\n const block = UITheme.declarationBlock(decls)\n const css = `html { ${block} }`\n UITheme.applyGlobalCSS(css)\n }\n\n new(customTheme: RecursivePartial<Theme> = {}): UITheme {\n function delEmpty(obj: Record<string, unknown>) {\n Object.keys(obj).forEach((key) => {\n const value = obj[key]\n if (isObj(value)) {\n return delEmpty(value as Record<string, unknown>)\n }\n if (!value && value !== 0) {\n delete obj[key]\n }\n })\n }\n\n delEmpty(customTheme as Record<string, unknown>)\n\n const base = JSON.parse(JSON.stringify(toRaw(this.theme))) as Record<string, any>\n\n function deepMerge(target: any, source: any) {\n if (typeof source !== 'object' || source === null) return\n Object.keys(source).forEach((key) => {\n const sourceVal = source[key]\n if (typeof sourceVal === 'object' && sourceVal !== null && !Array.isArray(sourceVal)) {\n if (typeof target[key] !== 'object' || target[key] === null) {\n target[key] = {}\n }\n deepMerge(target[key], sourceVal)\n } else {\n target[key] = sourceVal\n }\n })\n }\n\n deepMerge(base, customTheme)\n return new UITheme(base as Theme, { reactive: this.reactiveEnabled })\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ui-theme.js","names":[],"sources":["../../src/theme/ui-theme.ts"],"sourcesContent":["import { isObj, str } from '@cat-kit/core'\nimport { withUnit } from '@veltra/utils'\nimport { reactive, toRaw, watch } from 'vue'\n\nimport { componentCssVarsDarkDecls, componentCssVarsLightDecls } from './component-css-vars'\nimport { hexRgbOnly, hexWithAlpha, mixColor } from './helper'\nimport type { Theme } from './type'\n\ntype RecursivePartial<T> = {\n [P in keyof T]?: T[P] extends object ? RecursivePartial<T[P]> : T[P]\n}\n\nexport class UITheme {\n static themeID = 'ultra-ui-theme'\n\n private static adoptedSheet: CSSStyleSheet | null = null\n\n readonly theme: Theme\n\n private readonly reactiveEnabled: boolean\n\n constructor(theme: Theme, options?: { reactive?: boolean }) {\n this.reactiveEnabled = options?.reactive !== false\n this.theme = reactive(theme) as Theme\n if (this.reactiveEnabled) {\n watch(this.theme, () => this.render(), { deep: true })\n }\n }\n\n static setTheme(mode: 'light' | 'dark' | 'auto'): void {\n if (typeof document === 'undefined') return\n const el = document.documentElement\n if (mode === 'auto') {\n delete el.dataset.theme\n } else {\n el.dataset.theme = mode\n }\n }\n\n private renderBase(\n theme: Record<string, unknown>,\n themeRules: string[] = [],\n parentKey = '--u'\n ): string[] {\n Object.keys(theme).forEach((key) => {\n const value = theme[key]\n const varKey = `${parentKey.startsWith('-') ? parentKey : str(parentKey).kebabCase()}-${str(key).kebabCase()}`\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n this.renderBase(value as Record<string, unknown>, themeRules, varKey)\n } else {\n if (value || value === 0) {\n const v = withUnit(value as number | string, 'px')\n if (v !== undefined) {\n themeRules.push(`${varKey}: ${v}`)\n }\n }\n }\n })\n\n return themeRules\n }\n\n private renderBorder(theme: Theme): string {\n const shorthandKeys = (Object.keys(theme.border) as Array<keyof typeof theme.border>).filter(\n (key) => key !== 'mutedColor'\n )\n const border = shorthandKeys.map((key) => `var(--u-border-${key})`).join(' ')\n\n return [\n `--u-border: ${border}`,\n `--u-border-muted: var(--u-border-muted-color) var(--u-border-width) var(--u-border-style)`\n ].join(';')\n }\n\n private renderTypeColor(theme: Theme): string {\n const { color } = theme\n\n const types = Object.keys(color)\n const rates = [1, 3, 5, 7, 9]\n\n return types\n .map((type) => {\n const colorValue = color[type as keyof typeof color]! as `#${string}`\n\n return rates\n .map((rate) => {\n const light = `--u-color-${type}-light-${rate}: ${mixColor(\n colorValue,\n '#fff',\n rate / 10\n )}`\n const dark = `--u-color-${type}-dark-${rate}: ${mixColor(\n colorValue,\n '#000',\n rate / 10\n )}`\n return `${light};${dark}`\n })\n .join(';')\n })\n .join(';')\n }\n\n private renderShadow(_theme: Theme): string {\n const shadow = ['x', 'y', 'blur', 'spread', 'color']\n .map((k) => `var(--u-shadow-${k})`)\n .join(' ')\n\n return `--u-shadow: ${shadow}`\n }\n\n private renderBGColorAlpha(theme: Theme): string {\n const { color } = theme.bg\n const alphas = [70]\n const lines: string[] = Object.keys(color).map(\n (type) => `--u-bg-color-${type}-alpha: ${color[type as keyof typeof color]}aa`\n )\n for (const type of Object.keys(color)) {\n const hex = color[type as keyof typeof color] as `#${string}`\n for (const a of alphas) {\n lines.push(`--u-bg-color-${type}-a-${a}: ${hexWithAlpha(hex, a)}`)\n }\n }\n return lines.join(';')\n }\n\n /** 语义色 / 边框 / 阴影 / 文本色的 alpha token(替代 color-mix + transparent) */\n private renderAlphaTokens(theme: Theme): string {\n const alphas = [4, 5, 8, 10, 11, 16, 22, 28, 35, 40, 50, 52, 60, 70, 86]\n const lines: string[] = []\n\n for (const type of Object.keys(theme.color)) {\n const hex = theme.color[type as keyof typeof theme.color] as `#${string}`\n for (const a of alphas) {\n lines.push(`--u-color-${type}-a-${a}: ${hexWithAlpha(hex, a)}`)\n }\n }\n\n const borderHex = theme.border.color as `#${string}`\n for (const a of alphas) {\n lines.push(`--u-border-color-a-${a}: ${hexWithAlpha(borderHex, a)}`)\n }\n\n const shadowHex = hexRgbOnly(theme.shadow.color)\n for (const a of alphas) {\n lines.push(`--u-shadow-color-a-${a}: ${hexWithAlpha(shadowHex, a)}`)\n }\n\n const textColor = theme['text-color']\n for (const type of Object.keys(textColor)) {\n const value = textColor[type as keyof typeof textColor]\n if (typeof value === 'string' && /^#[0-9a-fA-F]{6}$/.test(value)) {\n const hex = value as `#${string}`\n for (const a of alphas) {\n lines.push(`--u-text-color-${type}-a-${a}: ${hexWithAlpha(hex, a)}`)\n }\n }\n }\n\n return lines.join(';')\n }\n\n /** 组件级两色混合 token(collapse / batch-edit / kbd) */\n private renderComponentMixTokens(theme: Theme): string {\n const hover = theme.bg.color.hover as `#${string}`\n const top = theme.bg.color.top as `#${string}`\n const black = theme.bg.color.black as `#${string}`\n const primary = theme.color.primary as `#${string}`\n const borderColor = theme.border.color as `#${string}`\n const shadowColor = hexRgbOnly(theme.shadow.color)\n\n const itemBg = mixColor(hover, top, 0.28)\n const itemHoverBg = mixColor(primary, itemBg as `#${string}`, 0.96)\n const itemActiveBg = mixColor(primary, itemBg as `#${string}`, 0.95)\n const headerActiveBg = mixColor(primary, itemBg as `#${string}`, 0.92)\n const contentBg = mixColor(itemActiveBg as `#${string}`, top, 0.58)\n\n const formHeaderBg = mixColor(top, hover, 0.04)\n\n const kbdInset = mixColor(top, black, 0.25)\n\n const lines = [\n `--u-collapse-item-bg: ${itemBg}`,\n `--u-collapse-item-hover-bg: ${itemHoverBg}`,\n `--u-collapse-item-active-bg: ${itemActiveBg}`,\n `--u-collapse-header-active-bg: ${headerActiveBg}`,\n `--u-collapse-content-bg: ${contentBg}`,\n `--u-batch-edit-form-header-bg: ${formHeaderBg}`,\n `--u-kbd-inset-shadow: ${kbdInset}`,\n `--u-kbd-border-shadow: ${hexWithAlpha(borderColor, 50)}`,\n `--u-kbd-drop-shadow: ${hexWithAlpha(shadowColor, 60)}`\n ]\n\n return lines.join(';')\n }\n\n private renderBGFilter(theme: Theme): string {\n const { filter } = theme.bg\n const value = filter.blur === 'none' ? 'none' : `${filter.blur} ${filter.saturate}`\n return `--u-bg-filter: ${value}`\n }\n\n /** 主题变量声明列表,均为 `--u-` 前缀的 CSS 自定义属性 */\n themeToDeclarationList(theme: Theme): string[] {\n const raw = theme as unknown as Record<string, unknown>\n const lines: string[] = [...this.renderBase(raw)]\n const chunks = [\n this.renderTypeColor(theme),\n this.renderShadow(theme),\n this.renderBGColorAlpha(theme),\n this.renderAlphaTokens(theme),\n this.renderComponentMixTokens(theme),\n this.renderBGFilter(theme),\n this.renderBorder(theme)\n ]\n for (const c of chunks) {\n lines.push(\n ...c\n .split(';')\n .map((s) => s.trim())\n .filter(Boolean)\n )\n }\n return lines\n }\n\n private static declarationBlock(decls: string[]): string {\n return decls.join(';')\n }\n\n private static removeExistingStyleTag(doc: Document): void {\n const el = doc.getElementById(UITheme.themeID)\n if (el?.parentNode) {\n el.parentNode.removeChild(el)\n }\n }\n\n private static applyGlobalCSS(css: string): void {\n const doc = typeof document !== 'undefined' ? document : undefined\n if (!doc) return\n\n const canAdopt =\n typeof CSSStyleSheet !== 'undefined' && 'adoptedStyleSheets' in Document.prototype\n\n if (canAdopt) {\n UITheme.removeExistingStyleTag(doc)\n if (!UITheme.adoptedSheet) {\n UITheme.adoptedSheet = new CSSStyleSheet()\n doc.adoptedStyleSheets = [...doc.adoptedStyleSheets, UITheme.adoptedSheet]\n }\n UITheme.adoptedSheet.replaceSync(css)\n } else {\n if (UITheme.adoptedSheet) {\n UITheme.adoptedSheet = null\n }\n let style = doc.getElementById(UITheme.themeID)\n if (!style) {\n style = doc.createElement('style')\n style.id = UITheme.themeID\n doc.head.appendChild(style)\n }\n style.textContent = css\n }\n }\n\n /** 内置 light/dark:支持 data-theme 与 prefers-color-scheme */\n static injectBuiltInThemes(light: UITheme, dark: UITheme): void {\n const lightDecls = [\n ...light.themeToDeclarationList(toRaw(light.theme)),\n ...componentCssVarsLightDecls\n ]\n const darkDecls = [\n ...dark.themeToDeclarationList(toRaw(dark.theme)),\n ...componentCssVarsDarkDecls\n ]\n const lightBlock = UITheme.declarationBlock(lightDecls)\n const darkBlock = UITheme.declarationBlock(darkDecls)\n\n const css = [\n `html { ${lightBlock} }`,\n `@media (prefers-color-scheme: dark) {`,\n ` html:not([data-theme=\"light\"]) { ${darkBlock} }`,\n `}`,\n `html[data-theme=\"light\"] { ${lightBlock} }`,\n `html[data-theme=\"dark\"] { ${darkBlock} }`\n ].join('\\n')\n\n UITheme.applyGlobalCSS(css)\n }\n\n render(): void {\n const isDark =\n typeof document !== 'undefined' && document.documentElement.dataset.theme === 'dark'\n const componentDecls = isDark ? componentCssVarsDarkDecls : componentCssVarsLightDecls\n const decls = [...this.themeToDeclarationList(toRaw(this.theme)), ...componentDecls]\n const block = UITheme.declarationBlock(decls)\n const css = `html { ${block} }`\n UITheme.applyGlobalCSS(css)\n }\n\n new(customTheme: RecursivePartial<Theme> = {}): UITheme {\n function delEmpty(obj: Record<string, unknown>) {\n Object.keys(obj).forEach((key) => {\n const value = obj[key]\n if (isObj(value)) {\n return delEmpty(value as Record<string, unknown>)\n }\n if (!value && value !== 0) {\n delete obj[key]\n }\n })\n }\n\n delEmpty(customTheme as Record<string, unknown>)\n\n const base = JSON.parse(JSON.stringify(toRaw(this.theme))) as Record<string, any>\n\n function deepMerge(target: any, source: any) {\n if (typeof source !== 'object' || source === null) return\n Object.keys(source).forEach((key) => {\n const sourceVal = source[key]\n if (typeof sourceVal === 'object' && sourceVal !== null && !Array.isArray(sourceVal)) {\n if (typeof target[key] !== 'object' || target[key] === null) {\n target[key] = {}\n }\n deepMerge(target[key], sourceVal)\n } else {\n target[key] = sourceVal\n }\n })\n }\n\n deepMerge(base, customTheme)\n return new UITheme(base as Theme, { reactive: this.reactiveEnabled })\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veltra/styles",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.23",
|
|
4
4
|
"files": [
|
|
5
5
|
"dist",
|
|
6
6
|
"src"
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"@cat-kit/core": ">=1.1.8",
|
|
39
|
-
"@veltra/compositions": "1.2.
|
|
40
|
-
"@veltra/utils": "1.2.
|
|
39
|
+
"@veltra/compositions": "1.2.23",
|
|
40
|
+
"@veltra/utils": "1.2.23",
|
|
41
41
|
"vue": ">=3.5.39"
|
|
42
42
|
}
|
|
43
43
|
}
|
package/src/_functions.scss
CHANGED
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
|
|
4
4
|
$namespace: 'u' !default;
|
|
5
5
|
|
|
6
|
+
// 引用主题注入的 alpha token,如 color-a(color, 8, primary) → var(--u-color-primary-a-8)
|
|
7
|
+
@function color-a($basename, $alpha, $nodes...) {
|
|
8
|
+
$suffix: '';
|
|
9
|
+
|
|
10
|
+
@each $node in $nodes {
|
|
11
|
+
$suffix: $suffix + '-' + $node;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@return var(--#{$namespace}-#{$basename}#{$suffix}-a-#{$alpha});
|
|
15
|
+
}
|
|
16
|
+
|
|
6
17
|
// 使用单个变量(全局主题 token,`--u-*`)
|
|
7
18
|
@function use-var($basename, $nodes...) {
|
|
8
19
|
$suffix: '';
|
|
@@ -30,4 +30,18 @@ describe('UITheme', () => {
|
|
|
30
30
|
expect(componentCssVarsLight['--u-collapse-title-color']).toBe('var(--u-text-color-main)')
|
|
31
31
|
expect(componentCssVarsDark['--u-collapse-title-color']).toBe('var(--u-text-color-main)')
|
|
32
32
|
})
|
|
33
|
+
|
|
34
|
+
it('renderAlphaTokens emits semantic color alpha vars', () => {
|
|
35
|
+
const decls = lightTheme.themeToDeclarationList(lightTheme.theme)
|
|
36
|
+
expect(decls.some((d) => d.startsWith('--u-color-primary-a-8:'))).toBe(true)
|
|
37
|
+
expect(decls.some((d) => d.startsWith('--u-border-color-a-52:'))).toBe(true)
|
|
38
|
+
expect(decls.some((d) => d.startsWith('--u-text-color-second-a-28:'))).toBe(true)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('renderComponentMixTokens emits collapse and kbd vars', () => {
|
|
42
|
+
const decls = lightTheme.themeToDeclarationList(lightTheme.theme)
|
|
43
|
+
expect(decls.some((d) => d.startsWith('--u-collapse-item-bg:'))).toBe(true)
|
|
44
|
+
expect(decls.some((d) => d.startsWith('--u-kbd-inset-shadow:'))).toBe(true)
|
|
45
|
+
expect(decls.some((d) => d.startsWith('--u-batch-edit-form-header-bg:'))).toBe(true)
|
|
46
|
+
})
|
|
33
47
|
})
|
package/src/theme/helper.ts
CHANGED
|
@@ -43,6 +43,27 @@ export function mixColor(color1: `#${string}`, color2: `#${string}`, ratio: numb
|
|
|
43
43
|
)
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/** 将 `#RRGGBB` 转为指定不透明度百分比的 `rgba()`(等价于 color-mix N% + transparent) */
|
|
47
|
+
export function hexWithAlpha(hex: `#${string}`, alphaPercent: number): string {
|
|
48
|
+
const [r, g, b] = HEXToRGB(hex)
|
|
49
|
+
const alpha = Math.round((alphaPercent / 100) * 1000) / 1000
|
|
50
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 别名:`ratio` 为 0–1 时不透明度(0.08 → 8%) */
|
|
54
|
+
export function mixColorWithAlpha(color: `#${string}`, ratio: number): string {
|
|
55
|
+
return hexWithAlpha(color, ratio * 100)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** 剥离 `#RRGGBBAA` 的 alpha 通道,返回 `#RRGGBB` */
|
|
59
|
+
export function hexRgbOnly(hex: string): `#${string}` {
|
|
60
|
+
const normalized = hex.replace(/^#/, '')
|
|
61
|
+
if (normalized.length === 8) {
|
|
62
|
+
return `#${normalized.slice(0, 6)}` as `#${string}`
|
|
63
|
+
}
|
|
64
|
+
return hex as `#${string}`
|
|
65
|
+
}
|
|
66
|
+
|
|
46
67
|
export function defineBySize(
|
|
47
68
|
variable: Record<'small' | 'default' | 'large', number>
|
|
48
69
|
): Record<'small' | 'default' | 'large', number> {
|
package/src/theme/ui-theme.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { withUnit } from '@veltra/utils'
|
|
|
3
3
|
import { reactive, toRaw, watch } from 'vue'
|
|
4
4
|
|
|
5
5
|
import { componentCssVarsDarkDecls, componentCssVarsLightDecls } from './component-css-vars'
|
|
6
|
-
import { mixColor } from './helper'
|
|
6
|
+
import { hexRgbOnly, hexWithAlpha, mixColor } from './helper'
|
|
7
7
|
import type { Theme } from './type'
|
|
8
8
|
|
|
9
9
|
type RecursivePartial<T> = {
|
|
@@ -111,9 +111,87 @@ export class UITheme {
|
|
|
111
111
|
|
|
112
112
|
private renderBGColorAlpha(theme: Theme): string {
|
|
113
113
|
const { color } = theme.bg
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
const alphas = [70]
|
|
115
|
+
const lines: string[] = Object.keys(color).map(
|
|
116
|
+
(type) => `--u-bg-color-${type}-alpha: ${color[type as keyof typeof color]}aa`
|
|
117
|
+
)
|
|
118
|
+
for (const type of Object.keys(color)) {
|
|
119
|
+
const hex = color[type as keyof typeof color] as `#${string}`
|
|
120
|
+
for (const a of alphas) {
|
|
121
|
+
lines.push(`--u-bg-color-${type}-a-${a}: ${hexWithAlpha(hex, a)}`)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return lines.join(';')
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** 语义色 / 边框 / 阴影 / 文本色的 alpha token(替代 color-mix + transparent) */
|
|
128
|
+
private renderAlphaTokens(theme: Theme): string {
|
|
129
|
+
const alphas = [4, 5, 8, 10, 11, 16, 22, 28, 35, 40, 50, 52, 60, 70, 86]
|
|
130
|
+
const lines: string[] = []
|
|
131
|
+
|
|
132
|
+
for (const type of Object.keys(theme.color)) {
|
|
133
|
+
const hex = theme.color[type as keyof typeof theme.color] as `#${string}`
|
|
134
|
+
for (const a of alphas) {
|
|
135
|
+
lines.push(`--u-color-${type}-a-${a}: ${hexWithAlpha(hex, a)}`)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const borderHex = theme.border.color as `#${string}`
|
|
140
|
+
for (const a of alphas) {
|
|
141
|
+
lines.push(`--u-border-color-a-${a}: ${hexWithAlpha(borderHex, a)}`)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const shadowHex = hexRgbOnly(theme.shadow.color)
|
|
145
|
+
for (const a of alphas) {
|
|
146
|
+
lines.push(`--u-shadow-color-a-${a}: ${hexWithAlpha(shadowHex, a)}`)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const textColor = theme['text-color']
|
|
150
|
+
for (const type of Object.keys(textColor)) {
|
|
151
|
+
const value = textColor[type as keyof typeof textColor]
|
|
152
|
+
if (typeof value === 'string' && /^#[0-9a-fA-F]{6}$/.test(value)) {
|
|
153
|
+
const hex = value as `#${string}`
|
|
154
|
+
for (const a of alphas) {
|
|
155
|
+
lines.push(`--u-text-color-${type}-a-${a}: ${hexWithAlpha(hex, a)}`)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return lines.join(';')
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** 组件级两色混合 token(collapse / batch-edit / kbd) */
|
|
164
|
+
private renderComponentMixTokens(theme: Theme): string {
|
|
165
|
+
const hover = theme.bg.color.hover as `#${string}`
|
|
166
|
+
const top = theme.bg.color.top as `#${string}`
|
|
167
|
+
const black = theme.bg.color.black as `#${string}`
|
|
168
|
+
const primary = theme.color.primary as `#${string}`
|
|
169
|
+
const borderColor = theme.border.color as `#${string}`
|
|
170
|
+
const shadowColor = hexRgbOnly(theme.shadow.color)
|
|
171
|
+
|
|
172
|
+
const itemBg = mixColor(hover, top, 0.28)
|
|
173
|
+
const itemHoverBg = mixColor(primary, itemBg as `#${string}`, 0.96)
|
|
174
|
+
const itemActiveBg = mixColor(primary, itemBg as `#${string}`, 0.95)
|
|
175
|
+
const headerActiveBg = mixColor(primary, itemBg as `#${string}`, 0.92)
|
|
176
|
+
const contentBg = mixColor(itemActiveBg as `#${string}`, top, 0.58)
|
|
177
|
+
|
|
178
|
+
const formHeaderBg = mixColor(top, hover, 0.04)
|
|
179
|
+
|
|
180
|
+
const kbdInset = mixColor(top, black, 0.25)
|
|
181
|
+
|
|
182
|
+
const lines = [
|
|
183
|
+
`--u-collapse-item-bg: ${itemBg}`,
|
|
184
|
+
`--u-collapse-item-hover-bg: ${itemHoverBg}`,
|
|
185
|
+
`--u-collapse-item-active-bg: ${itemActiveBg}`,
|
|
186
|
+
`--u-collapse-header-active-bg: ${headerActiveBg}`,
|
|
187
|
+
`--u-collapse-content-bg: ${contentBg}`,
|
|
188
|
+
`--u-batch-edit-form-header-bg: ${formHeaderBg}`,
|
|
189
|
+
`--u-kbd-inset-shadow: ${kbdInset}`,
|
|
190
|
+
`--u-kbd-border-shadow: ${hexWithAlpha(borderColor, 50)}`,
|
|
191
|
+
`--u-kbd-drop-shadow: ${hexWithAlpha(shadowColor, 60)}`
|
|
192
|
+
]
|
|
193
|
+
|
|
194
|
+
return lines.join(';')
|
|
117
195
|
}
|
|
118
196
|
|
|
119
197
|
private renderBGFilter(theme: Theme): string {
|
|
@@ -130,6 +208,8 @@ export class UITheme {
|
|
|
130
208
|
this.renderTypeColor(theme),
|
|
131
209
|
this.renderShadow(theme),
|
|
132
210
|
this.renderBGColorAlpha(theme),
|
|
211
|
+
this.renderAlphaTokens(theme),
|
|
212
|
+
this.renderComponentMixTokens(theme),
|
|
133
213
|
this.renderBGFilter(theme),
|
|
134
214
|
this.renderBorder(theme)
|
|
135
215
|
]
|