@veltra/styles 1.6.6 → 1.7.1
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/README.md +22 -0
- package/dist/_mixins.scss +1 -7
- package/dist/theme/component-css-vars.d.ts +24 -0
- package/dist/theme/component-css-vars.js +74 -14
- package/dist/theme/component-css-vars.js.map +1 -1
- package/dist/theme/index.d.ts +11 -7
- package/dist/theme/index.js +10 -6
- package/dist/theme/load-theme.d.ts +3 -4
- package/dist/theme/load-theme.js +15 -22
- package/dist/theme/load-theme.js.map +1 -1
- package/dist/theme/presets/ancient.d.ts +3 -2
- package/dist/theme/presets/ancient.js +6 -3
- package/dist/theme/presets/ancient.js.map +1 -1
- package/dist/theme/presets/dark.js +1 -1
- package/dist/theme/presets/dark.js.map +1 -1
- package/dist/theme/presets/glass.d.ts +7 -3
- package/dist/theme/presets/glass.js +18 -47
- package/dist/theme/presets/glass.js.map +1 -1
- package/dist/theme/presets/hero.d.ts +3 -3
- package/dist/theme/presets/hero.js +5 -41
- package/dist/theme/presets/hero.js.map +1 -1
- package/dist/theme/presets/midnight.d.ts +8 -0
- package/dist/theme/presets/midnight.js +55 -0
- package/dist/theme/presets/midnight.js.map +1 -0
- package/dist/theme/presets/neon.d.ts +8 -0
- package/dist/theme/presets/neon.js +55 -0
- package/dist/theme/presets/neon.js.map +1 -0
- package/dist/theme/presets/ocean.d.ts +8 -0
- package/dist/theme/presets/ocean.js +57 -0
- package/dist/theme/presets/ocean.js.map +1 -0
- package/dist/theme/presets/sakura.d.ts +8 -0
- package/dist/theme/presets/sakura.js +59 -0
- package/dist/theme/presets/sakura.js.map +1 -0
- package/dist/theme/type.d.ts +10 -0
- package/dist/theme/ui-theme.d.ts +21 -8
- package/dist/theme/ui-theme.js +30 -27
- package/dist/theme/ui-theme.js.map +1 -1
- package/package.json +3 -3
- package/src/_mixins.scss +1 -7
- package/src/theme/__test__/ui-theme.test.ts +84 -9
- package/src/theme/component-css-vars.ts +90 -13
- package/src/theme/index.ts +2 -0
- package/src/theme/load-theme.ts +12 -27
- package/src/theme/presets/ancient.ts +9 -2
- package/src/theme/presets/dark.ts +43 -40
- package/src/theme/presets/glass.ts +56 -67
- package/src/theme/presets/hero.ts +5 -36
- package/src/theme/presets/index.ts +7 -4
- package/src/theme/presets/midnight.ts +45 -0
- package/src/theme/presets/neon.ts +45 -0
- package/src/theme/presets/ocean.ts +40 -0
- package/src/theme/presets/sakura.ts +42 -0
- package/src/theme/type.ts +10 -0
- package/src/theme/ui-theme.ts +54 -43
- package/dist/theme/presets/shadcn.d.ts +0 -8
- package/dist/theme/presets/shadcn.js +0 -100
- package/dist/theme/presets/shadcn.js.map +0 -1
- package/src/theme/presets/shadcn.ts +0 -84
|
@@ -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 { hexRgbOnly, hexWithAlpha, isHexColor, 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[] = []\n for (const type of Object.keys(color)) {\n const value = color[type as keyof typeof color]\n // 非 hex 颜色(如 glass 主题的 rgba())无法推导 alpha token,跳过避免生成 NaN 声明\n if (typeof value !== 'string' || !isHexColor(value)) continue\n const hex = value as `#${string}`\n lines.push(`--u-bg-color-${type}-alpha: ${hex}aa`)\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 value = theme.color[type as keyof typeof theme.color]\n if (typeof value !== 'string' || !isHexColor(value)) continue\n const hex = value 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 if (isHexColor(borderHex)) {\n for (const a of alphas) {\n lines.push(`--u-border-color-a-${a}: ${hexWithAlpha(borderHex, a)}`)\n }\n }\n\n const shadowHex = hexRgbOnly(theme.shadow.color)\n // 阴影色非 hex(如 rgba())时无法推导 alpha token,跳过避免生成 NaN 声明\n if (isHexColor(shadowHex)) {\n for (const a of alphas) {\n lines.push(`--u-shadow-color-a-${a}: ${hexWithAlpha(shadowHex, a)}`)\n }\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' && isHexColor(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(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 borderColor = theme.border.color as `#${string}`\n const shadowColor = hexRgbOnly(theme.shadow.color)\n\n // 依赖色含非 hex 值(如 glass 主题的 rgba())时整体跳过,避免生成 NaN 声明;\n // 组件侧 var() fallback 会接管,行为等同于此前的无效声明\n if (![hover, top, black, borderColor].every(isHexColor)) return ''\n\n const formHeaderBg = mixColor(top, hover, 0.04)\n\n const kbdInset = mixColor(top, black, 0.25)\n const kbdDropShadow = isHexColor(shadowColor)\n ? hexWithAlpha(shadowColor, 60)\n : 'rgba(0, 0, 0, 0.6)'\n\n const lines = [\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: ${kbdDropShadow}`\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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"}
|
|
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 {\n componentCssVarsDarkDecls,\n componentCssVarsLightDecls,\n resolveNavSidebarDecls,\n type NavSidebarVariant\n} from './component-css-vars'\nimport { hexRgbOnly, hexWithAlpha, isHexColor, 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\n/** 主题系列:浅色或深色。决定组件级 token 与 html[data-theme] */\nexport type ThemeSeries = 'light' | 'dark'\n\nexport interface UIThemeOptions {\n /** 主题变更时是否自动重渲染,默认 true */\n reactive?: boolean\n /** 主题所属系列,默认 'light' */\n series?: ThemeSeries\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 /** 主题所属系列:浅色 / 深色 */\n series: ThemeSeries\n\n private readonly reactiveEnabled: boolean\n\n constructor(theme: Theme, options?: UIThemeOptions) {\n this.reactiveEnabled = options?.reactive !== false\n this.series = options?.series ?? 'light'\n this.theme = reactive(theme) as Theme\n if (this.reactiveEnabled) {\n watch(this.theme, () => this.render(), { deep: true })\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 // nav 由 navSidebarDecls 按 variant + 覆盖项单独注入(须晚于组件级 token 才生效)\n if (parentKey === '--u' && key === 'nav') return\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[] = []\n for (const type of Object.keys(color)) {\n const value = color[type as keyof typeof color]\n // 非 hex 颜色(如 glass 主题的 rgba())无法推导 alpha token,跳过避免生成 NaN 声明\n if (typeof value !== 'string' || !isHexColor(value)) continue\n const hex = value as `#${string}`\n lines.push(`--u-bg-color-${type}-alpha: ${hex}aa`)\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 value = theme.color[type as keyof typeof theme.color]\n if (typeof value !== 'string' || !isHexColor(value)) continue\n const hex = value 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 if (isHexColor(borderHex)) {\n for (const a of alphas) {\n lines.push(`--u-border-color-a-${a}: ${hexWithAlpha(borderHex, a)}`)\n }\n }\n\n const shadowHex = hexRgbOnly(theme.shadow.color)\n // 阴影色非 hex(如 rgba())时无法推导 alpha token,跳过避免生成 NaN 声明\n if (isHexColor(shadowHex)) {\n for (const a of alphas) {\n lines.push(`--u-shadow-color-a-${a}: ${hexWithAlpha(shadowHex, a)}`)\n }\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' && isHexColor(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(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 borderColor = theme.border.color as `#${string}`\n const shadowColor = hexRgbOnly(theme.shadow.color)\n\n // 依赖色含非 hex 值(如 glass 主题的 rgba())时整体跳过,避免生成 NaN 声明;\n // 组件侧 var() fallback 会接管,行为等同于此前的无效声明\n if (![hover, top, black, borderColor].every(isHexColor)) return ''\n\n const formHeaderBg = mixColor(top, hover, 0.04)\n\n const kbdInset = mixColor(top, black, 0.25)\n const kbdDropShadow = isHexColor(shadowColor)\n ? hexWithAlpha(shadowColor, 60)\n : 'rgba(0, 0, 0, 0.6)'\n\n const lines = [\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: ${kbdDropShadow}`\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 /**\n * nav 侧栏外观声明:按主题 `nav.variant`(默认 dark)取内置深/浅侧栏 token,\n * 主题 `nav` 的其余键作为 `--u-nav-*` 覆盖追加在最后(同规则后者生效)。\n */\n navSidebarDecls(theme: Theme): string[] {\n const nav = theme.nav ?? {}\n const variant: NavSidebarVariant = nav.variant === 'light' ? 'light' : 'dark'\n const decls = resolveNavSidebarDecls(this.series, variant)\n const overrides = Object.keys(nav)\n .filter((key) => key !== 'variant' && typeof nav[key] === 'string' && nav[key])\n .map((key) => `--u-nav-${str(key).kebabCase()}: ${nav[key]}`)\n return [...decls, ...overrides]\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 /** 注入主题:全局 token + 与主题系列匹配的组件级 token + nav 侧栏外观,并写入 html[data-theme] */\n render(): void {\n const componentDecls =\n this.series === 'dark' ? componentCssVarsDarkDecls : componentCssVarsLightDecls\n const raw = toRaw(this.theme)\n const decls = [\n ...this.themeToDeclarationList(raw),\n ...componentDecls,\n ...this.navSidebarDecls(raw)\n ]\n const block = UITheme.declarationBlock(decls)\n const css = `html { ${block} }`\n UITheme.applyGlobalCSS(css)\n if (typeof document !== 'undefined') {\n document.documentElement.dataset.theme = this.series\n }\n }\n\n new(customTheme: RecursivePartial<Theme> = {}, options?: { series?: ThemeSeries }): 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, {\n reactive: this.reactiveEnabled,\n series: options?.series ?? this.series\n })\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.
|
|
3
|
+
"version": "1.7.1",
|
|
4
4
|
"homepage": "https://github.com/cabinet-fe/ultra-ui#readme",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/cabinet-fe/ultra-ui/issues"
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"@cat-kit/core": ">=1.1.8",
|
|
54
|
-
"@veltra/compositions": "^1.
|
|
55
|
-
"@veltra/utils": "^1.
|
|
54
|
+
"@veltra/compositions": "^1.7.1",
|
|
55
|
+
"@veltra/utils": "^1.7.1",
|
|
56
56
|
"vue": ">=3.5.41"
|
|
57
57
|
}
|
|
58
58
|
}
|
package/src/_mixins.scss
CHANGED
|
@@ -146,17 +146,11 @@ $namespace: 'u' !default;
|
|
|
146
146
|
@return var(--#{$namespace}-breakpoint-#{$point});
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
/**
|
|
149
|
+
/** 暗色:`UITheme.render()` 按主题系列写入 html[data-theme] */
|
|
150
150
|
@mixin dark {
|
|
151
151
|
html[data-theme='dark'] & {
|
|
152
152
|
@content;
|
|
153
153
|
}
|
|
154
|
-
|
|
155
|
-
@media (prefers-color-scheme: dark) {
|
|
156
|
-
html:not([data-theme='light']) & {
|
|
157
|
-
@content;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
154
|
}
|
|
161
155
|
|
|
162
156
|
@mixin xs {
|
|
@@ -2,11 +2,15 @@ import { describe, expect, it } from 'vitest'
|
|
|
2
2
|
|
|
3
3
|
import { componentCssVarsDark, componentCssVarsLight } from '../component-css-vars'
|
|
4
4
|
import {
|
|
5
|
+
ancientTheme,
|
|
5
6
|
darkTheme,
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
glassTheme,
|
|
8
|
+
heroTheme,
|
|
8
9
|
lightTheme,
|
|
9
|
-
|
|
10
|
+
midnightTheme,
|
|
11
|
+
neonTheme,
|
|
12
|
+
oceanTheme,
|
|
13
|
+
sakuraTheme
|
|
10
14
|
} from '../presets'
|
|
11
15
|
import { UITheme } from '../ui-theme'
|
|
12
16
|
|
|
@@ -21,15 +25,26 @@ describe('UITheme', () => {
|
|
|
21
25
|
}
|
|
22
26
|
})
|
|
23
27
|
|
|
24
|
-
it('
|
|
28
|
+
it('render writes data-theme from theme series', () => {
|
|
25
29
|
if (typeof document === 'undefined') return
|
|
26
30
|
const el = document.documentElement
|
|
27
|
-
|
|
31
|
+
darkTheme.render()
|
|
28
32
|
expect(el.dataset.theme).toBe('dark')
|
|
29
|
-
|
|
33
|
+
lightTheme.render()
|
|
30
34
|
expect(el.dataset.theme).toBe('light')
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('derived themes inherit series unless overridden', () => {
|
|
38
|
+
expect(darkTheme.series).toBe('dark')
|
|
39
|
+
expect(glassTheme.series).toBe('dark')
|
|
40
|
+
expect(heroTheme.series).toBe('light')
|
|
41
|
+
expect(ancientTheme.series).toBe('light')
|
|
42
|
+
expect(sakuraTheme.series).toBe('light')
|
|
43
|
+
expect(oceanTheme.series).toBe('light')
|
|
44
|
+
expect(midnightTheme.series).toBe('dark')
|
|
45
|
+
expect(neonTheme.series).toBe('dark')
|
|
46
|
+
expect(lightTheme.new({}).series).toBe('light')
|
|
47
|
+
expect(lightTheme.new({}, { series: 'dark' }).series).toBe('dark')
|
|
33
48
|
})
|
|
34
49
|
|
|
35
50
|
it('renderAlphaTokens emits semantic color alpha vars', () => {
|
|
@@ -46,6 +61,14 @@ describe('UITheme', () => {
|
|
|
46
61
|
expect(decls.some((d) => d.startsWith('--u-collapse-item-bg:'))).toBe(false)
|
|
47
62
|
})
|
|
48
63
|
|
|
64
|
+
it('glass declares kbd and batch-edit vars explicitly (non-hex bg skips mix tokens)', () => {
|
|
65
|
+
const decls = glassTheme.themeToDeclarationList(glassTheme.theme)
|
|
66
|
+
expect(decls.some((d) => d.startsWith('--u-kbd-inset-shadow:'))).toBe(true)
|
|
67
|
+
expect(decls.some((d) => d.startsWith('--u-kbd-border-shadow:'))).toBe(true)
|
|
68
|
+
expect(decls.some((d) => d.startsWith('--u-kbd-drop-shadow:'))).toBe(true)
|
|
69
|
+
expect(decls.some((d) => d.startsWith('--u-batch-edit-form-header-bg:'))).toBe(true)
|
|
70
|
+
})
|
|
71
|
+
|
|
49
72
|
it('emits elevation and motion tokens', () => {
|
|
50
73
|
const decls = lightTheme.themeToDeclarationList(lightTheme.theme)
|
|
51
74
|
expect(decls.some((d) => d.startsWith('--u-shadow-sm:'))).toBe(true)
|
|
@@ -59,8 +82,60 @@ describe('UITheme', () => {
|
|
|
59
82
|
expect(componentCssVarsDark['--u-focus-ring']).toContain('--u-color-primary-a-')
|
|
60
83
|
})
|
|
61
84
|
|
|
85
|
+
it('nav sidebar defaults to dark variant, resolved per series', () => {
|
|
86
|
+
// light 系列:深底(text-color.title)+ 浅前景,与浅色内容区拉开区分度
|
|
87
|
+
const lightDecls = lightTheme.navSidebarDecls(lightTheme.theme)
|
|
88
|
+
expect(lightDecls).toContain('--u-nav-bg-color: var(--u-text-color-title)')
|
|
89
|
+
expect(lightDecls).toContain('--u-nav-color: rgba(255, 255, 255, 0.72)')
|
|
90
|
+
// dark 系列:侧栏比内容区 top 深一档
|
|
91
|
+
const darkDecls = darkTheme.navSidebarDecls(darkTheme.theme)
|
|
92
|
+
expect(darkDecls).toContain('--u-nav-bg-color: var(--u-bg-color-middle)')
|
|
93
|
+
expect(darkDecls).toContain('--u-nav-color: var(--u-text-color-main)')
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('nav variant light switches to light sidebar tokens', () => {
|
|
97
|
+
const t = lightTheme.new({ nav: { variant: 'light' } })
|
|
98
|
+
const decls = t.navSidebarDecls(t.theme)
|
|
99
|
+
expect(decls).toContain('--u-nav-bg-color: var(--u-bg-color-middle)')
|
|
100
|
+
expect(decls).toContain('--u-nav-color: var(--u-text-color-title)')
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('hero preset defaults to light sidebar variant', () => {
|
|
104
|
+
const decls = heroTheme.navSidebarDecls(heroTheme.theme)
|
|
105
|
+
expect(decls).toContain('--u-nav-bg-color: var(--u-bg-color-middle)')
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('theme.nav overrides are appended after variant defaults and win', () => {
|
|
109
|
+
const t = lightTheme.new({ nav: { 'bg-color': '#123456' } })
|
|
110
|
+
const decls = t.navSidebarDecls(t.theme)
|
|
111
|
+
const bgDecls = decls.filter((d) => d.startsWith('--u-nav-bg-color:'))
|
|
112
|
+
expect(bgDecls.length).toBe(2)
|
|
113
|
+
expect(bgDecls[1]).toBe('--u-nav-bg-color: #123456')
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('renderBase skips the nav subtree (handled by navSidebarDecls)', () => {
|
|
117
|
+
const decls = sakuraTheme.themeToDeclarationList(sakuraTheme.theme)
|
|
118
|
+
expect(decls.some((d) => d.startsWith('--u-nav-'))).toBe(false)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('sakura / ocean / ancient have themed dark sidebar colors', () => {
|
|
122
|
+
expect(sakuraTheme.navSidebarDecls(sakuraTheme.theme)).toContain('--u-nav-bg-color: #662e40')
|
|
123
|
+
expect(oceanTheme.navSidebarDecls(oceanTheme.theme)).toContain('--u-nav-bg-color: #134944')
|
|
124
|
+
expect(ancientTheme.navSidebarDecls(ancientTheme.theme)).toContain('--u-nav-bg-color: #303c34')
|
|
125
|
+
})
|
|
126
|
+
|
|
62
127
|
it('no preset emits invalid NaN declarations', () => {
|
|
63
|
-
const themes = [
|
|
128
|
+
const themes = [
|
|
129
|
+
lightTheme,
|
|
130
|
+
darkTheme,
|
|
131
|
+
heroTheme,
|
|
132
|
+
glassTheme,
|
|
133
|
+
ancientTheme,
|
|
134
|
+
sakuraTheme,
|
|
135
|
+
oceanTheme,
|
|
136
|
+
midnightTheme,
|
|
137
|
+
neonTheme
|
|
138
|
+
]
|
|
64
139
|
for (const t of themes) {
|
|
65
140
|
const decls = t.themeToDeclarationList(t.theme)
|
|
66
141
|
for (const d of decls) {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 桌面组件级 `--u-*` token
|
|
2
|
+
* 桌面组件级 `--u-*` token,随 `UITheme.render()` 按主题系列(light/dark)注入 `html`。
|
|
3
3
|
* 修改此处即可在 TS 侧统一调整组件外观,无需在各组件 `style.scss` 重复声明。
|
|
4
|
+
* nav 系列:外观(底色/前景)不在此列表,由 `navSidebarCssVars` 按主题 `nav.variant`
|
|
5
|
+
* (dark 深底浅字 / light 浅底深字,默认 dark)与 `nav` 覆盖项注入,见 `ui-theme.ts`。
|
|
4
6
|
*/
|
|
5
7
|
|
|
6
8
|
/** 与 SCSS `fn.use-var($basename, $nodes...)` 输出一致 */
|
|
@@ -25,15 +27,10 @@ export const componentCssVarsLight: Record<string, string> = {
|
|
|
25
27
|
'--u-table-current-color': 'inherit',
|
|
26
28
|
'--u-table-checked-bg': T('color', 'primary', 'light-9'),
|
|
27
29
|
'--u-table-checked-color': 'inherit',
|
|
28
|
-
|
|
29
|
-
'--u-nav-hover-bg': T('text-color', 'title', 'a', '8'),
|
|
30
|
-
'--u-nav-hover-color': T('text-color', 'title'),
|
|
31
|
-
'--u-nav-active-bg': T('color', 'primary', 'a', '10'),
|
|
32
|
-
'--u-nav-active-color': T('color', 'primary', 'dark', '1'),
|
|
30
|
+
// ─── Nav 系列(nav / dual-nav / group-nav)尺寸与资源类;外观见文件底部 navSidebarCssVars ───
|
|
33
31
|
'--u-nav-height-small': '32px',
|
|
34
32
|
'--u-nav-height-default': '36px',
|
|
35
33
|
'--u-nav-height-large': '40px',
|
|
36
|
-
'--u-nav-bg-color': T('bg-color', 'top'),
|
|
37
34
|
'--u-nav-bg-image': 'none',
|
|
38
35
|
'--u-nav-rail-width': '56px',
|
|
39
36
|
'--u-nav-rail-labeled-width': '72px',
|
|
@@ -128,12 +125,6 @@ export const componentCssVarsDark: Record<string, string> = {
|
|
|
128
125
|
'--u-table-current-color': 'inherit',
|
|
129
126
|
'--u-table-checked-bg': T('color', 'primary', 'dark', '1'),
|
|
130
127
|
'--u-table-checked-color': 'inherit',
|
|
131
|
-
'--u-nav-color': T('text-color', 'main'),
|
|
132
|
-
'--u-nav-hover-bg': T('text-color', 'title', 'a', '8'),
|
|
133
|
-
'--u-nav-hover-color': T('text-color', 'title'),
|
|
134
|
-
'--u-nav-active-bg': T('color', 'primary', 'a', '16'),
|
|
135
|
-
'--u-nav-active-color': T('text-color', 'white'),
|
|
136
|
-
'--u-nav-bg-color': T('bg-color', 'middle'),
|
|
137
128
|
'--u-radio-border': '#595959',
|
|
138
129
|
'--u-checkbox-border': '#595959',
|
|
139
130
|
|
|
@@ -201,3 +192,89 @@ function recordToDeclList(record: Record<string, string>): string[] {
|
|
|
201
192
|
export const componentCssVarsLightDecls: string[] = recordToDeclList(componentCssVarsLight)
|
|
202
193
|
|
|
203
194
|
export const componentCssVarsDarkDecls: string[] = recordToDeclList(componentCssVarsDark)
|
|
195
|
+
|
|
196
|
+
// ─── Nav 侧栏外观:深/浅变体 × 明/暗主题系列 ───
|
|
197
|
+
// 由 UITheme 按主题 `nav.variant`(默认 dark)选择注入,主题 `nav` 的其余键可逐项覆盖。
|
|
198
|
+
|
|
199
|
+
/** 侧栏外观变体:dark 深底浅字 / light 浅底深字 */
|
|
200
|
+
export type NavSidebarVariant = 'dark' | 'light'
|
|
201
|
+
|
|
202
|
+
/** 深侧栏 · 浅色主题下:深底(text-color.title)+ 浅前景,与浅色内容区拉开区分度 */
|
|
203
|
+
const navSidebarDarkInLight: Record<string, string> = {
|
|
204
|
+
'--u-nav-bg-color': T('text-color', 'title'),
|
|
205
|
+
'--u-nav-color': 'rgba(255, 255, 255, 0.72)',
|
|
206
|
+
'--u-nav-hover-bg': 'rgba(255, 255, 255, 0.08)',
|
|
207
|
+
'--u-nav-hover-color': T('text-color', 'white'),
|
|
208
|
+
'--u-nav-active-bg': T('color', 'primary', 'a', '22'),
|
|
209
|
+
'--u-nav-active-color': T('text-color', 'white'),
|
|
210
|
+
'--u-nav-strong-color': T('text-color', 'white'),
|
|
211
|
+
'--u-nav-second-color': 'rgba(255, 255, 255, 0.45)',
|
|
212
|
+
'--u-nav-sub-border-color': 'rgba(255, 255, 255, 0.15)',
|
|
213
|
+
'--u-nav-rail-bg': 'rgba(0, 0, 0, 0.2)'
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** 浅侧栏 · 浅色主题下:比内容区 top 低一档的浅底 + 深前景 */
|
|
217
|
+
const navSidebarLightInLight: Record<string, string> = {
|
|
218
|
+
'--u-nav-bg-color': T('bg-color', 'middle'),
|
|
219
|
+
'--u-nav-color': T('text-color', 'title'),
|
|
220
|
+
'--u-nav-hover-bg': T('text-color', 'title', 'a', '8'),
|
|
221
|
+
'--u-nav-hover-color': T('text-color', 'title'),
|
|
222
|
+
'--u-nav-active-bg': T('color', 'primary', 'a', '10'),
|
|
223
|
+
'--u-nav-active-color': T('color', 'primary', 'dark', '1'),
|
|
224
|
+
'--u-nav-strong-color': T('text-color', 'title'),
|
|
225
|
+
'--u-nav-second-color': T('text-color', 'second'),
|
|
226
|
+
'--u-nav-sub-border-color': T('border', 'color', 'a', '52'),
|
|
227
|
+
'--u-nav-rail-bg': T('bg-color', 'bottom')
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** 深侧栏 · 深色主题下:比内容区 top 深一档 */
|
|
231
|
+
const navSidebarDarkInDark: Record<string, string> = {
|
|
232
|
+
'--u-nav-bg-color': T('bg-color', 'middle'),
|
|
233
|
+
'--u-nav-color': T('text-color', 'main'),
|
|
234
|
+
'--u-nav-hover-bg': T('text-color', 'title', 'a', '8'),
|
|
235
|
+
'--u-nav-hover-color': T('text-color', 'title'),
|
|
236
|
+
'--u-nav-active-bg': T('color', 'primary', 'a', '16'),
|
|
237
|
+
'--u-nav-active-color': T('text-color', 'white'),
|
|
238
|
+
'--u-nav-strong-color': T('text-color', 'title'),
|
|
239
|
+
'--u-nav-second-color': T('text-color', 'second'),
|
|
240
|
+
'--u-nav-sub-border-color': T('border', 'color', 'a', '52'),
|
|
241
|
+
'--u-nav-rail-bg': T('bg-color', 'bottom')
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** 浅侧栏 · 深色主题下:提亮侧栏(与深色内容区反向对比) */
|
|
245
|
+
const navSidebarLightInDark: Record<string, string> = {
|
|
246
|
+
'--u-nav-bg-color': T('bg-color', 'top'),
|
|
247
|
+
'--u-nav-color': T('text-color', 'title'),
|
|
248
|
+
'--u-nav-hover-bg': T('text-color', 'title', 'a', '8'),
|
|
249
|
+
'--u-nav-hover-color': T('text-color', 'title'),
|
|
250
|
+
'--u-nav-active-bg': T('color', 'primary', 'a', '22'),
|
|
251
|
+
'--u-nav-active-color': T('color', 'primary'),
|
|
252
|
+
'--u-nav-strong-color': T('text-color', 'title'),
|
|
253
|
+
'--u-nav-second-color': T('text-color', 'second'),
|
|
254
|
+
'--u-nav-sub-border-color': T('border', 'color', 'a', '52'),
|
|
255
|
+
'--u-nav-rail-bg': T('bg-color', 'middle')
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const navSidebarCssVars: Record<
|
|
259
|
+
'light' | 'dark',
|
|
260
|
+
Record<NavSidebarVariant, Record<string, string>>
|
|
261
|
+
> = {
|
|
262
|
+
light: { dark: navSidebarDarkInLight, light: navSidebarLightInLight },
|
|
263
|
+
dark: { dark: navSidebarDarkInDark, light: navSidebarLightInDark }
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** 某主题系列 + 侧栏变体下的 nav 外观 token(键为 `--u-nav-*`) */
|
|
267
|
+
export function navSidebarTokens(
|
|
268
|
+
series: 'light' | 'dark',
|
|
269
|
+
variant: NavSidebarVariant
|
|
270
|
+
): Record<string, string> {
|
|
271
|
+
return { ...navSidebarCssVars[series][variant] }
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** 解析某主题系列 + 侧栏变体下的 nav 外观 token 声明列表 */
|
|
275
|
+
export function resolveNavSidebarDecls(
|
|
276
|
+
series: 'light' | 'dark',
|
|
277
|
+
variant: NavSidebarVariant
|
|
278
|
+
): string[] {
|
|
279
|
+
return recordToDeclList(navSidebarCssVars[series][variant])
|
|
280
|
+
}
|
package/src/theme/index.ts
CHANGED
package/src/theme/load-theme.ts
CHANGED
|
@@ -1,45 +1,30 @@
|
|
|
1
1
|
import { useConfig } from '@veltra/compositions'
|
|
2
2
|
import { shallowRef, type ShallowRef } from 'vue'
|
|
3
3
|
|
|
4
|
-
import { lightTheme
|
|
5
|
-
import { UITheme } from './ui-theme'
|
|
4
|
+
import { lightTheme } from './presets'
|
|
5
|
+
import type { UITheme } from './ui-theme'
|
|
6
6
|
|
|
7
7
|
export const currentTheme: ShallowRef<UITheme | undefined> = shallowRef<UITheme>()
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
UITheme.setTheme(mode)
|
|
11
|
-
}
|
|
9
|
+
const SIZES = ['small', 'default', 'large'] as const
|
|
12
10
|
|
|
13
11
|
/**
|
|
14
12
|
* @description 加载主题, 如果你是 SSR 环境,
|
|
15
13
|
* 请在 `onMounted` 中调用,否则你可以在
|
|
16
14
|
* 项目的入口环境(通常是 main.ts)或其他全局环境中调用。
|
|
17
15
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
16
|
+
* 主题自带系列(`theme.series`),应用时会注入全局 token 与
|
|
17
|
+
* 同系列的组件级 token,并把 `html[data-theme]` 置为对应系列。
|
|
20
18
|
*/
|
|
21
|
-
export function loadTheme(theme
|
|
22
|
-
currentTheme.value = theme
|
|
23
|
-
|
|
24
|
-
const { config } = useConfig()
|
|
25
|
-
if (typeof document !== 'undefined') {
|
|
26
|
-
document.documentElement.classList.add(config.size)
|
|
27
|
-
}
|
|
19
|
+
export function loadTheme(theme: UITheme = lightTheme): void {
|
|
20
|
+
currentTheme.value = theme
|
|
28
21
|
|
|
29
22
|
if (typeof document === 'undefined') return
|
|
30
23
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
currentTheme.value = theme
|
|
24
|
+
const { config } = useConfig()
|
|
25
|
+
const classList = document.documentElement.classList
|
|
26
|
+
classList.remove(...SIZES.filter((s) => s !== config.size))
|
|
27
|
+
classList.add(config.size)
|
|
38
28
|
|
|
39
|
-
|
|
40
|
-
UITheme.injectBuiltInThemes(lightTheme, darkTheme)
|
|
41
|
-
UITheme.setTheme(theme === darkTheme ? 'dark' : 'light')
|
|
42
|
-
} else {
|
|
43
|
-
theme.render()
|
|
44
|
-
}
|
|
29
|
+
theme.render()
|
|
45
30
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { mixColor } from '../helper'
|
|
1
2
|
import { lightTheme } from './light'
|
|
2
|
-
|
|
3
|
+
|
|
4
|
+
/** 古风(松烟绿 + 宣纸底),天生适合浅色 */
|
|
5
|
+
export const ancientTheme = lightTheme.new({
|
|
3
6
|
color: {
|
|
4
7
|
primary: '#3d6b58' // 松烟绿
|
|
5
8
|
},
|
|
@@ -20,5 +23,9 @@ export const ancientLightTheme = lightTheme.new({
|
|
|
20
23
|
},
|
|
21
24
|
border: { width: 1, color: '#e4dcc8', mutedColor: '#d2c8ac' },
|
|
22
25
|
radius: { small: 4, default: 8, large: 12 },
|
|
23
|
-
shadow: { color: 'rgba(64, 54, 32, 0.14)', x: 0, y: 4, blur: 16, spread: -2 }
|
|
26
|
+
shadow: { color: 'rgba(64, 54, 32, 0.14)', x: 0, y: 4, blur: 16, spread: -2 },
|
|
27
|
+
nav: {
|
|
28
|
+
// 松烟墨侧栏:黛墨 + 松烟绿调和
|
|
29
|
+
'bg-color': mixColor('#2b2a26', '#3d6b58', 0.28)
|
|
30
|
+
}
|
|
24
31
|
})
|
|
@@ -1,47 +1,50 @@
|
|
|
1
1
|
import { lightTheme } from './light'
|
|
2
2
|
|
|
3
|
-
export const darkTheme = lightTheme.new(
|
|
4
|
-
|
|
5
|
-
primary: '#3b82f6',
|
|
6
|
-
success: '#22c55e',
|
|
7
|
-
warning: '#f59e0b',
|
|
8
|
-
danger: '#ef4444',
|
|
9
|
-
info: '#06b6d4',
|
|
10
|
-
disabled: '#27272a',
|
|
11
|
-
default: '#27272a'
|
|
12
|
-
},
|
|
13
|
-
|
|
14
|
-
bg: {
|
|
3
|
+
export const darkTheme = lightTheme.new(
|
|
4
|
+
{
|
|
15
5
|
color: {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
6
|
+
primary: '#3b82f6',
|
|
7
|
+
success: '#22c55e',
|
|
8
|
+
warning: '#f59e0b',
|
|
9
|
+
danger: '#ef4444',
|
|
10
|
+
info: '#06b6d4',
|
|
11
|
+
disabled: '#27272a',
|
|
12
|
+
default: '#27272a'
|
|
21
13
|
},
|
|
22
|
-
filter: { blur: 'none', saturate: 'none' }
|
|
23
|
-
},
|
|
24
14
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
15
|
+
bg: {
|
|
16
|
+
color: {
|
|
17
|
+
bottom: '#0c0c0e',
|
|
18
|
+
middle: '#141417',
|
|
19
|
+
top: '#1c1c21',
|
|
20
|
+
hover: '#26262c',
|
|
21
|
+
black: '#000000'
|
|
22
|
+
},
|
|
23
|
+
filter: { blur: 'none', saturate: 'none' }
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
'text-color': {
|
|
27
|
+
title: '#f4f4f5',
|
|
28
|
+
main: '#d4d4d8',
|
|
29
|
+
second: '#a1a1aa',
|
|
30
|
+
placeholder: '#71717a',
|
|
31
|
+
assist: '#52525b',
|
|
32
|
+
disabled: '#3f3f46',
|
|
33
|
+
white: '#ffffff'
|
|
34
|
+
},
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
border: { color: '#37373f', mutedColor: '#42424c' },
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
38
|
+
shadow: {
|
|
39
|
+
color: '#00000066',
|
|
40
|
+
x: 0,
|
|
41
|
+
y: 1,
|
|
42
|
+
blur: 3,
|
|
43
|
+
spread: 0,
|
|
44
|
+
emboss: 'none',
|
|
45
|
+
sm: '0 1px 2px 0 rgba(0, 0, 0, 0.4)',
|
|
46
|
+
lg: '0 8px 24px 0 rgba(0, 0, 0, 0.5), 0 2px 6px 0 rgba(0, 0, 0, 0.35)'
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
{ series: 'dark' }
|
|
50
|
+
)
|