feat: add useTDesignDesignTokens
parent
7fd4b1de04
commit
0eb72ca4fd
|
|
@ -3,6 +3,7 @@ import type { GlobalConfigProvider } from 'tdesign-vue-next';
|
|||
|
||||
import { watch } from 'vue';
|
||||
|
||||
import { useTDesignDesignTokens } from '@vben/hooks';
|
||||
import { usePreferences } from '@vben/preferences';
|
||||
|
||||
import { merge } from 'es-toolkit/compat';
|
||||
|
|
@ -12,10 +13,16 @@ import zhConfig from 'tdesign-vue-next/es/locale/zh_CN';
|
|||
defineOptions({ name: 'App' });
|
||||
const { isDark } = usePreferences();
|
||||
|
||||
// 将 Vben 设计系统的 CSS 变量适配到 TDesign 的设计变量上
|
||||
useTDesignDesignTokens();
|
||||
|
||||
watch(
|
||||
() => isDark.value,
|
||||
(dark) => {
|
||||
document.documentElement.setAttribute('theme-mode', dark ? 'dark' : '');
|
||||
document.documentElement.setAttribute(
|
||||
'theme-mode',
|
||||
dark ? 'dark' : 'light',
|
||||
);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ import { registerLoadingDirective } from '@vben/common-ui/es/loading';
|
|||
import { preferences } from '@vben/preferences';
|
||||
import { initStores } from '@vben/stores';
|
||||
import '@vben/styles';
|
||||
// import '@vben/styles/antd';
|
||||
// 引入组件库的少量全局样式变量
|
||||
|
||||
import { useTitle } from '@vueuse/core';
|
||||
|
||||
|
|
@ -17,6 +15,7 @@ import { initSetupVbenForm } from './adapter/form';
|
|||
import App from './app.vue';
|
||||
import { router } from './router';
|
||||
|
||||
// 引入组件库的少量全局样式变量
|
||||
import 'tdesign-vue-next/es/style/index.css';
|
||||
|
||||
async function bootstrap(namespace: string) {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export async function node(): Promise<Linter.Config[]> {
|
|||
'error',
|
||||
{
|
||||
ignores: [],
|
||||
version: '>=20.12.0',
|
||||
version: '>=22.18.0',
|
||||
},
|
||||
],
|
||||
'n/prefer-global/buffer': ['error', 'never'],
|
||||
|
|
|
|||
|
|
@ -106,5 +106,5 @@
|
|||
"node": "^22.18.0 || ^24.0.0",
|
||||
"pnpm": ">=11.0.0"
|
||||
},
|
||||
"packageManager": "pnpm@11.4.0"
|
||||
"packageManager": "pnpm@11.5.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,3 +28,21 @@ it('updateCSSVariables should update CSS variables in :root selector', () => {
|
|||
updatedStyleContent?.includes('fontSize: 16px;'),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('updateCSSVariables should support a custom selector', () => {
|
||||
document.head.innerHTML = `<style id="tdesign-styles"></style>`;
|
||||
|
||||
// 使用自定义选择器(如 TDesign 的 theme-mode 选择器)更新 CSS 变量
|
||||
updateCSSVariables(
|
||||
{ '--td-brand-color': 'rgb(0, 82, 217)' },
|
||||
'tdesign-styles',
|
||||
":root[theme-mode='dark']",
|
||||
);
|
||||
|
||||
const styleElement = document.querySelector('#tdesign-styles');
|
||||
const content = styleElement?.textContent ?? '';
|
||||
|
||||
// 选择器与变量都应正确写入
|
||||
expect(content.startsWith(":root[theme-mode='dark'] {")).toBe(true);
|
||||
expect(content.includes('--td-brand-color: rgb(0, 82, 217);')).toBe(true);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
/**
|
||||
* 更新 CSS 变量的函数
|
||||
* @param variables 要更新的 CSS 变量与其新值的映射
|
||||
* @param id 内联样式表的 id,便于复用与覆盖
|
||||
* @param selector CSS 变量挂载的选择器,默认 `:root`。
|
||||
* 对于像 TDesign 这种将变量定义在 `:root[theme-mode='dark']` 等更高优先级选择器下的组件库,
|
||||
* 需要传入相同(或更高)优先级的选择器才能正确覆盖。
|
||||
*/
|
||||
function updateCSSVariables(
|
||||
variables: { [key: string]: string },
|
||||
id = '__vben-styles__',
|
||||
selector = ':root',
|
||||
): void {
|
||||
// 获取或创建内联样式表元素
|
||||
const styleElement =
|
||||
|
|
@ -13,7 +18,7 @@ function updateCSSVariables(
|
|||
styleElement.id = id;
|
||||
|
||||
// 构建要更新的 CSS 变量的样式文本
|
||||
let cssText = ':root {';
|
||||
let cssText = `${selector} {`;
|
||||
for (const key in variables) {
|
||||
if (Object.prototype.hasOwnProperty.call(variables, key)) {
|
||||
cssText += `${key}: ${variables[key]};`;
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ import SubMenu from './sub-menu.vue';
|
|||
|
||||
interface Props extends MenuProps {}
|
||||
|
||||
defineOptions({ name: 'Menu' });
|
||||
defineOptions({ name: 'MenuUI' });
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
accordion: true,
|
||||
|
|
|
|||
|
|
@ -319,3 +319,103 @@ export function useElementPlusDesignTokens() {
|
|||
{ immediate: true },
|
||||
);
|
||||
}
|
||||
|
||||
export function useTDesignDesignTokens() {
|
||||
const { isDark } = usePreferences();
|
||||
const rootStyles = getComputedStyle(document.documentElement);
|
||||
|
||||
const getCssVariableValue = (variable: string, isColor: boolean = true) => {
|
||||
const value = rootStyles.getPropertyValue(variable);
|
||||
return isColor ? convertToRgb(`hsl(${value})`) : value;
|
||||
};
|
||||
|
||||
/**
|
||||
* 生成某个语义色对应的 TDesign 变量(语义变体 + 1~10 色阶)。
|
||||
* TDesign 的色阶在亮色模式下 1 最浅、10 最深,暗色模式下方向相反,
|
||||
* 这里根据当前模式把 Vben 的 50~900 色板映射到对应方向。
|
||||
* @param tdName TDesign 颜色名,如 `brand`、`error`
|
||||
* @param vbenName Vben 颜色名,如 `primary`、`destructive`
|
||||
*/
|
||||
const getColorTokens = (tdName: string, vbenName: string) => {
|
||||
const dark = isDark.value;
|
||||
const getColor = (level?: number) =>
|
||||
getCssVariableValue(
|
||||
level === undefined ? `--${vbenName}` : `--${vbenName}-${level}`,
|
||||
);
|
||||
|
||||
// 亮色模式 1 最浅、10 最深;暗色模式相反
|
||||
const lightScale = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900];
|
||||
const scaleLevels = dark ? [...lightScale].toReversed() : lightScale;
|
||||
const scale: Record<string, string> = {};
|
||||
scaleLevels.forEach((level, index) => {
|
||||
scale[`--td-${tdName}-color-${index + 1}`] = getColor(level);
|
||||
});
|
||||
|
||||
return {
|
||||
[`--td-${tdName}-color`]: getColor(),
|
||||
[`--td-${tdName}-color-active`]: getColor(dark ? 300 : 700),
|
||||
[`--td-${tdName}-color-disabled`]: getColor(dark ? 800 : 200),
|
||||
[`--td-${tdName}-color-focus`]: getColor(dark ? 900 : 100),
|
||||
[`--td-${tdName}-color-hover`]: getColor(dark ? 400 : 600),
|
||||
[`--td-${tdName}-color-light`]: getColor(dark ? 950 : 50),
|
||||
[`--td-${tdName}-color-light-hover`]: getColor(dark ? 900 : 100),
|
||||
...scale,
|
||||
};
|
||||
};
|
||||
|
||||
watch(
|
||||
() => preferences.theme,
|
||||
() => {
|
||||
const variables: Record<string, string> = {
|
||||
// 品牌色(主题色)、功能色
|
||||
...getColorTokens('brand', 'primary'),
|
||||
...getColorTokens('error', 'destructive'),
|
||||
...getColorTokens('warning', 'warning'),
|
||||
...getColorTokens('success', 'success'),
|
||||
|
||||
// 文字颜色
|
||||
'--td-text-color-anti': getCssVariableValue('--primary-foreground'),
|
||||
'--td-text-color-brand': getCssVariableValue('--primary'),
|
||||
'--td-text-color-disabled': getCssVariableValue('--muted-foreground'),
|
||||
'--td-text-color-link': getCssVariableValue('--primary'),
|
||||
'--td-text-color-placeholder': getCssVariableValue(
|
||||
'--input-placeholder',
|
||||
),
|
||||
'--td-text-color-primary': getCssVariableValue('--foreground'),
|
||||
'--td-text-color-secondary': getCssVariableValue('--muted-foreground'),
|
||||
|
||||
// 背景颜色
|
||||
'--td-bg-color-component': getCssVariableValue('--accent'),
|
||||
'--td-bg-color-component-active':
|
||||
getCssVariableValue('--accent-darker'),
|
||||
'--td-bg-color-component-disabled': getCssVariableValue('--muted'),
|
||||
'--td-bg-color-component-hover': getCssVariableValue('--accent-hover'),
|
||||
'--td-bg-color-container': getCssVariableValue('--background'),
|
||||
'--td-bg-color-container-active':
|
||||
getCssVariableValue('--accent-darker'),
|
||||
'--td-bg-color-container-hover': getCssVariableValue('--accent'),
|
||||
'--td-bg-color-container-select': getCssVariableValue('--accent'),
|
||||
'--td-bg-color-page': getCssVariableValue('--background-deep'),
|
||||
'--td-bg-color-secondarycontainer': getCssVariableValue('--card'),
|
||||
|
||||
// 边框颜色
|
||||
'--td-border-level-1-color': getCssVariableValue('--border'),
|
||||
'--td-border-level-2-color': getCssVariableValue('--border'),
|
||||
'--td-component-border': getCssVariableValue('--border'),
|
||||
'--td-component-stroke': getCssVariableValue('--border'),
|
||||
|
||||
// 圆角
|
||||
'--td-radius-default': getCssVariableValue('--radius', false),
|
||||
};
|
||||
|
||||
// TDesign 将亮/暗变量定义在 `:root[theme-mode='light'|'dark']` 下,
|
||||
// 需要写入相同优先级的选择器才能覆盖默认值
|
||||
const selector = isDark.value
|
||||
? `:root[theme-mode='dark']`
|
||||
: `:root[theme-mode='light']`;
|
||||
|
||||
updateCSSVariables(variables, `__vben_design_tdesign_styles__`, selector);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
}
|
||||
|
|
|
|||
3255
pnpm-lock.yaml
3255
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -41,20 +41,20 @@ catalog:
|
|||
'@changesets/changelog-github': ^0.7.0
|
||||
'@changesets/cli': ^2.31.0
|
||||
'@changesets/git': ^3.0.4
|
||||
'@clack/prompts': ^1.4.0
|
||||
'@commitlint/cli': ^21.0.1
|
||||
'@commitlint/config-conventional': ^21.0.1
|
||||
'@clack/prompts': ^1.5.0
|
||||
'@commitlint/cli': ^21.0.2
|
||||
'@commitlint/config-conventional': ^21.0.2
|
||||
'@ctrl/tinycolor': ^4.2.0
|
||||
'@eslint-community/eslint-plugin-eslint-comments': ^4.7.2
|
||||
'@eslint/js': ^10.0.1
|
||||
'@faker-js/faker': ^10.4.0
|
||||
'@iconify/json': ^2.2.479
|
||||
'@iconify/json': ^2.2.481
|
||||
'@iconify/tailwind4': ^1.2.3
|
||||
'@iconify/vue': ^5.0.1
|
||||
'@intlify/core-base': ^11.4.4
|
||||
'@intlify/unplugin-vue-i18n': ^11.2.3
|
||||
'@jspm/generator': ^2.16.1
|
||||
'@lucide/vue': ^1.16.0
|
||||
'@lucide/vue': ^1.17.0
|
||||
'@manypkg/get-packages': ^3.1.0
|
||||
'@nolebase/vitepress-plugin-git-changelog': ^2.18.2
|
||||
'@playwright/test': ^1.60.0
|
||||
|
|
@ -64,19 +64,19 @@ catalog:
|
|||
'@tailwindcss/vite': ^4.3.0
|
||||
'@tanstack/vue-query': ^5.100.14
|
||||
'@tanstack/vue-store': ^0.11.0
|
||||
'@tiptap/core': ^3.23.6
|
||||
'@tiptap/extension-document': ^3.23.6
|
||||
'@tiptap/extension-highlight': ^3.23.6
|
||||
'@tiptap/extension-image': ^3.23.6
|
||||
'@tiptap/extension-link': ^3.23.6
|
||||
'@tiptap/extension-placeholder': ^3.23.6
|
||||
'@tiptap/extension-text-align': ^3.23.6
|
||||
'@tiptap/extension-text-style': ^3.23.6
|
||||
'@tiptap/extension-underline': ^3.23.6
|
||||
'@tiptap/pm': ^3.23.6
|
||||
'@tiptap/starter-kit': ^3.23.6
|
||||
'@tiptap/vue-3': ^3.23.6
|
||||
'@tsdown/css': ^0.22.0
|
||||
'@tiptap/core': ^3.24.0
|
||||
'@tiptap/extension-document': ^3.24.0
|
||||
'@tiptap/extension-highlight': ^3.24.0
|
||||
'@tiptap/extension-image': ^3.24.0
|
||||
'@tiptap/extension-link': ^3.24.0
|
||||
'@tiptap/extension-placeholder': ^3.24.0
|
||||
'@tiptap/extension-text-align': ^3.24.0
|
||||
'@tiptap/extension-text-style': ^3.24.0
|
||||
'@tiptap/extension-underline': ^3.24.0
|
||||
'@tiptap/pm': ^3.24.0
|
||||
'@tiptap/starter-kit': ^3.24.0
|
||||
'@tiptap/vue-3': ^3.24.0
|
||||
'@tsdown/css': ^0.22.1
|
||||
'@types/archiver': ^7.0.0
|
||||
'@types/html-minifier-terser': ^7.0.2
|
||||
'@types/json-bigint': ^1.0.4
|
||||
|
|
@ -87,8 +87,8 @@ catalog:
|
|||
'@types/qrcode': ^1.5.6
|
||||
'@types/qs': ^6.15.1
|
||||
'@types/sortablejs': ^1.15.9
|
||||
'@typescript-eslint/eslint-plugin': ^8.60.0
|
||||
'@typescript-eslint/parser': ^8.60.0
|
||||
'@typescript-eslint/eslint-plugin': ^8.60.1
|
||||
'@typescript-eslint/parser': ^8.60.1
|
||||
'@vee-validate/zod': ^4.15.1
|
||||
'@vite-pwa/vitepress': ^1.1.0
|
||||
'@vitejs/plugin-vue': ^6.0.7
|
||||
|
|
@ -112,26 +112,26 @@ catalog:
|
|||
commitlint-plugin-function-rules: ^5.0.1
|
||||
consola: ^3.4.2
|
||||
cross-env: ^10.1.0
|
||||
cspell: ^10.0.0
|
||||
cspell: ^10.0.1
|
||||
cz-git: ^1.13.1
|
||||
czg: ^1.13.1
|
||||
dayjs: ^1.11.21
|
||||
defu: ^6.1.7
|
||||
dotenv: ^17.4.2
|
||||
echarts: ^6.1.0
|
||||
element-plus: ^2.14.0
|
||||
element-plus: ^2.14.1
|
||||
es-toolkit: ^1.47.0
|
||||
eslint: ^10.4.0
|
||||
eslint: ^10.4.1
|
||||
eslint-plugin-better-tailwindcss: ^4.5.0
|
||||
eslint-plugin-command: ^3.5.2
|
||||
eslint-plugin-jsonc: ^3.1.2
|
||||
eslint-plugin-jsonc: ^3.2.0
|
||||
eslint-plugin-n: ^18.0.1
|
||||
eslint-plugin-perfectionist: ^5.9.0
|
||||
eslint-plugin-pnpm: ^1.6.1
|
||||
eslint-plugin-unicorn: ^64.0.0
|
||||
eslint-plugin-unused-imports: ^4.4.1
|
||||
eslint-plugin-vue: ^10.9.1
|
||||
eslint-plugin-yml: ^3.3.2
|
||||
eslint-plugin-yml: ^3.4.0
|
||||
execa: ^9.6.1
|
||||
find-up: ^8.0.0
|
||||
get-port: ^7.2.0
|
||||
|
|
@ -142,16 +142,16 @@ catalog:
|
|||
is-ci: ^4.1.0
|
||||
json-bigint: ^1.0.0
|
||||
jsonwebtoken: ^9.0.3
|
||||
knip: ^6.14.2
|
||||
lefthook: ^2.1.8
|
||||
knip: ^6.15.0
|
||||
lefthook: ^2.1.9
|
||||
lodash.clonedeep: ^4.5.0
|
||||
medium-zoom: ^1.1.0
|
||||
naive-ui: ^2.44.1
|
||||
nitropack: ^2.13.4
|
||||
nprogress: ^0.2.0
|
||||
ora: ^9.4.0
|
||||
oxfmt: ^0.52.0
|
||||
oxlint: ^1.67.0
|
||||
oxfmt: ^0.53.0
|
||||
oxlint: ^1.68.0
|
||||
oxlint-tsgolint: ^0.23.0
|
||||
pinia: ^3.0.4
|
||||
pinia-plugin-persistedstate: ^4.7.1
|
||||
|
|
@ -184,31 +184,31 @@ catalog:
|
|||
tdesign-vue-next: ^1.20.0
|
||||
theme-colors: ^0.1.0
|
||||
tippy.js: ^6.3.7
|
||||
tsdown: ^0.22.0
|
||||
turbo: ^2.9.15
|
||||
tsdown: ^0.22.1
|
||||
turbo: ^2.9.16
|
||||
tw-animate-css: ^1.4.0
|
||||
typescript: ^6.0.3
|
||||
unplugin-dts: ^1.0.1
|
||||
unplugin-dts: ^1.0.2
|
||||
unplugin-element-plus: ^0.11.2
|
||||
unplugin-vue: ^7.2.0
|
||||
vee-validate: ^4.15.1
|
||||
vite: 8.0.10
|
||||
vite: ^8.0.16
|
||||
vite-plugin-compression: ^0.5.1
|
||||
vite-plugin-lazy-import: ^1.0.7
|
||||
vite-plugin-pwa: ^1.3.0
|
||||
vite-plugin-vue-devtools: ^8.1.2
|
||||
vitepress: ^2.0.0-alpha.17
|
||||
vitepress-plugin-group-icons: ^1.7.5
|
||||
vitest: ^4.1.7
|
||||
vitest: ^4.1.8
|
||||
vue: ^3.5.35
|
||||
vue-eslint-parser: ^10.4.0
|
||||
vue-i18n: ^11.4.4
|
||||
vue-json-pretty: ^2.6.0
|
||||
vue-router: ^5.0.7
|
||||
vue-router: ^5.1.0
|
||||
vue-tippy: ^6.7.1
|
||||
vue-tsc: ^3.3.2
|
||||
vxe-pc-ui: ^4.14.24
|
||||
vxe-table: ^4.19.3
|
||||
vue-tsc: ^3.3.3
|
||||
vxe-pc-ui: ^4.14.26
|
||||
vxe-table: ^4.19.6
|
||||
watermark-js-plus: ^1.6.3
|
||||
yaml-eslint-parser: ^2.0.0
|
||||
zod: ^3.25.76
|
||||
|
|
|
|||
Loading…
Reference in New Issue