From d7ca354b870542885896ef9a3b32e6ce3fce28a9 Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Wed, 30 Jul 2025 23:02:30 +0800 Subject: [PATCH] feat: implement theme toggling and enhance Button styles with new color variables --- apps/playground/src/App.vue | 14 +- packages/ui/src/components/button/Button.vue | 10 +- .../ui/src/components/button/style/index.css | 28 ++-- packages/ui/src/components/theme/Theme.vue | 22 ++++ packages/ui/src/components/theme/hook.ts | 16 +-- packages/ui/src/components/theme/meta.ts | 11 +- packages/ui/src/style/base.css | 54 ++++---- packages/ui/src/utils/colorAlgorithm.ts | 121 +++++++----------- 8 files changed, 144 insertions(+), 132 deletions(-) diff --git a/apps/playground/src/App.vue b/apps/playground/src/App.vue index 4a9e23dab..4d4ebc37c 100644 --- a/apps/playground/src/App.vue +++ b/apps/playground/src/App.vue @@ -1,3 +1,15 @@ + diff --git a/packages/ui/src/components/button/Button.vue b/packages/ui/src/components/button/Button.vue index dc55b3a0e..42b0c5e2f 100644 --- a/packages/ui/src/components/button/Button.vue +++ b/packages/ui/src/components/button/Button.vue @@ -14,6 +14,7 @@ import { buttonProps, buttonEmits, ButtonSlots } from './meta' import { getCssVarColor } from '@/utils/colorAlgorithm' import { useThemeInject } from '../theme/hook' import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined' +import { defaultColor } from '../theme/meta' const props = defineProps(buttonProps) @@ -28,7 +29,7 @@ const color = computed(() => { } if (props.danger) { - return theme.dangerColor + return 'red' } return theme.primaryColor @@ -46,7 +47,12 @@ const rootClass = computed(() => { } }) const cssVars = computed(() => { - return getCssVarColor(color.value) + return color.value.toLowerCase() !== defaultColor.toLowerCase() + ? getCssVarColor(color.value, { + appearance: theme.appearance, + backgroundColor: theme.backgroundColor, + }) + : {} }) const handleClick = (event: MouseEvent) => { diff --git a/packages/ui/src/components/button/style/index.css b/packages/ui/src/components/button/style/index.css index 749fe6199..3c948b2f0 100644 --- a/packages/ui/src/components/button/style/index.css +++ b/packages/ui/src/components/button/style/index.css @@ -11,38 +11,40 @@ } &:where(.ant-btn-solid:not(:disabled)) { - @apply border-none bg-[var(--accent-color)] text-[var(--accent-color-content)]; - @apply hover:bg-[var(--accent-color-hover)] active:bg-[var(--accent-color-active)]; + @apply bg-accent text-accent-content border-none; + @apply hover:bg-accent-hover active:bg-accent-active; } &:where(.ant-btn-outlined:not(:disabled)), &:where(.ant-btn-dashed:not(:disabled)) { - @apply border-[var(--accent-color)] bg-transparent text-[var(--accent-color)]; - @apply hover:text-[var(--accent-color-hover)] active:border-[var(--accent-color-active)] active:text-[var(--accent-color-active)]; - @apply border-[var(--accent-color-active)] hover:border-[var(--accent-color-hover)]; + @apply border-accent text-accent bg-transparent; + @apply hover:text-accent-hover active:border-accent-active active:text-accent-active; + @apply border-accent-active hover:border-accent-hover; } &:where(.ant-btn-text:not(.ant-btn-custom-color):not(:disabled)) { - @apply border-none bg-transparent text-[var(--neutral-color)]; - @apply hover:bg-[var(--neutral-disabled-bg)]; + @apply text-neutral border-none bg-transparent; + @apply hover:bg-neutral-disabled-bg; } &:where(.ant-btn-text.ant-btn-custom-color:not(:disabled)) { - @apply border-none bg-transparent text-[var(--accent-color)]; - @apply hover:bg-[var(--accent-color-1)] hover:text-[var(--accent-color-hover)]; + @apply text-accent border-none bg-transparent; + @apply hover:bg-accent-1 hover:text-accent-hover; } &:where(.ant-btn-link:not(:disabled)) { - @apply border-none bg-transparent text-[var(--accent-color)] hover:text-[var(--accent-color-hover)]; + @apply text-accent border-none bg-transparent; + @apply hover:text-accent-hover; } &:where(.ant-btn-dashed) { @apply border-dashed; } &:where(.ant-btn-filled:not(:disabled)) { - @apply border-none bg-[var(--accent-color-1)] text-[var(--accent-color)] hover:text-[var(--accent-color-hover)]; - @apply hover:bg-[var(--accent-color-2)] active:bg-[var(--accent-color-3)] active:text-[var(--accent-color-active)]; + @apply text-accent bg-accent-1 border-none; + @apply hover:bg-accent-2 active:bg-accent-3 active:text-accent-active; + @apply hover:text-accent-hover; } &:where(.ant-btn-disabled) { @apply cursor-not-allowed; - @apply border-[var(--neutral-border)] bg-[var(--neutral-disabled-bg)] text-[var(--neutral-disabled)]; + @apply text-neutral-disabled bg-neutral-disabled-bg border-neutral-border; } &:where(.ant-btn-disabled.ant-btn-text), &:where(.ant-btn-disabled.ant-btn-link) { diff --git a/packages/ui/src/components/theme/Theme.vue b/packages/ui/src/components/theme/Theme.vue index 16219aeb1..d19b3f650 100644 --- a/packages/ui/src/components/theme/Theme.vue +++ b/packages/ui/src/components/theme/Theme.vue @@ -5,8 +5,30 @@ diff --git a/packages/ui/src/components/theme/hook.ts b/packages/ui/src/components/theme/hook.ts index 040cdaaae..3941e5850 100644 --- a/packages/ui/src/components/theme/hook.ts +++ b/packages/ui/src/components/theme/hook.ts @@ -1,21 +1,17 @@ -import { inject, InjectionKey, provide, Reactive } from 'vue' +import { inject, InjectionKey, provide } from 'vue' +import { ThemeProps } from './meta' -type ThemeType = Reactive<{ - appearance: 'light' | 'dark' - primaryColor: string - dangerColor: string -}> - -const ThemeSymbol: InjectionKey = Symbol('theme') +const ThemeSymbol: InjectionKey = Symbol('theme') export const useThemeInject = () => { return inject(ThemeSymbol, { appearance: 'light', primaryColor: '#1677ff', dangerColor: '#ff4d4f', - }) + darkBackgroundColor: '#141414', + } as ThemeProps) } -export const useThemeProvide = (theme: ThemeType) => { +export const useThemeProvide = (theme: ThemeProps) => { provide(ThemeSymbol, theme) } diff --git a/packages/ui/src/components/theme/meta.ts b/packages/ui/src/components/theme/meta.ts index 8ae12f91f..afff1aeda 100644 --- a/packages/ui/src/components/theme/meta.ts +++ b/packages/ui/src/components/theme/meta.ts @@ -1,5 +1,6 @@ import { PropType, ExtractPublicPropTypes } from 'vue' +export const defaultColor = '#1677FF' // Theme Props export const themeProps = { /** @@ -16,15 +17,15 @@ export const themeProps = { */ primaryColor: { type: String, - default: '#1677FF', + default: defaultColor, }, /** - * Specifies the danger color of the component - * @default '#ff4d4f' + * Specifies the background color of the component, only used in dark mode + * @default '#141414' */ - dangerColor: { + backgroundColor: { type: String, - default: '#ff4d4f', + default: '#141414', }, } as const diff --git a/packages/ui/src/style/base.css b/packages/ui/src/style/base.css index 510d18aec..e8032ac81 100644 --- a/packages/ui/src/style/base.css +++ b/packages/ui/src/style/base.css @@ -1,29 +1,33 @@ @theme static { - --color-base-100: #ffffff; - --color-base-200: #f7f7f7; - --color-base-300: #ededed; - --color-base-content: #222222; - --color-primary: #151415; - --color-primary-content: #ffffff; - --color-secondary: #0d58fc; - --color-secondary-content: #ffffff; - --color-accent: #0289ff; + --color-accent-1: #e6f4ff; + --color-accent-2: #bae0ff; + --color-accent-3: #91caff; + --color-accent-4: #69b1ff; + --color-accent-5: #4096ff; + --color-accent-6: #1677ff; + --color-accent-7: #0958d9; + --color-accent-8: #003eb3; + --color-accent-9: #002c8c; + --color-accent-10: #001d66; + --color-accent: #1677ff; + --color-accent-hover: #4096ff; + --color-accent-active: #0958d9; --color-accent-content: #ffffff; - --color-neutral: #666666; - --color-neutral-content: #ffffff; - --color-info: #0d58fc; - --color-info-content: #ffffff; - --color-success: #00c573; - --color-success-content: #ffffff; - --color-warning: #ff9900; - --color-warning-content: #ffffff; - --color-error: #ff3333; - --color-error-content: #ffffff; - --neutral-color: #000000e0; - --neutral-secondary: #000000a6; - --neutral-disabled: #00000040; - --neutral-border: #d9d9d9; - --neutral-separator: #0505050f; - --neutral-bg: #f5f5f5; + --color-neutral: #000000e0; + --color-neutral-secondary: #000000a6; + --color-neutral-disabled: #00000040; + --color-neutral-disabled-bg: #0000000a; + --color-neutral-border: #d9d9d9; + --color-neutral-separator: #0505050f; + --color-neutral-bg: #f5f5f5; + + --color-error: #ff4d4f; + --color-warning: #faad14; + --color-success: #52c41a; + --color-info: #1677ff; +} + +.dark-theme { + background-color: #141414; } diff --git a/packages/ui/src/utils/colorAlgorithm.ts b/packages/ui/src/utils/colorAlgorithm.ts index 08eb72480..7c7b4af49 100644 --- a/packages/ui/src/utils/colorAlgorithm.ts +++ b/packages/ui/src/utils/colorAlgorithm.ts @@ -1,51 +1,41 @@ import { TinyColor } from '@ctrl/tinycolor' import { generate, presetPalettes, presetDarkPalettes } from '@ant-design/colors' -export const getAlphaColor = (baseColor: string, alpha: number) => - new TinyColor(baseColor).setAlpha(alpha).toRgbString() - -export const getSolidColor = (baseColor: string, brightness: number) => { - const instance = new TinyColor(baseColor) - return instance.darken(brightness).toHexString() -} - -export const getTintColor = (baseColor: string, tintNumber: number) => { - return new TinyColor(baseColor).tint(tintNumber).toString() -} - -export const getShadeColor = (baseColor: string, shadeNumber: number) => { - return new TinyColor(baseColor).shade(shadeNumber).toString() -} - export const getLightNeutralColor = () => { return { - '--neutral-color': '#000000e0', - '--neutral-secondary': '#000000a6', - '--neutral-disabled': '#00000040', - '--neutral-disabled-bg': '#0000000a', - '--neutral-border': '#d9d9d9', - '--neutral-separator': '#0505050f', - '--neutral-bg': '#f5f5f5', + '--color-neutral': '#000000e0', + '--color-neutral-secondary': '#000000a6', + '--color-neutral-disabled': '#00000040', + '--color-neutral-disabled-bg': '#0000000a', + '--color-neutral-border': '#d9d9d9', + '--color-neutral-separator': '#0505050f', + '--color-neutral-bg': '#f5f5f5', } } export const getDarkNeutralColor = () => { return { - '--neutral-color': '#FFFFFFD9', - '--neutral-secondary': '#FFFFFFA6', - '--neutral-disabled': '#FFFFFF40', - '--neutral-disabled-bg': 'rgba(255, 255, 255, 0.08)', - '--neutral-border': '#424242', - '--neutral-separator': '#FDFDFD1F', - '--neutral-bg': '#000000', + '--color-neutral': '#FFFFFFD9', + '--color-neutral-secondary': '#FFFFFFA6', + '--color-neutral-disabled': '#FFFFFF40', + '--color-neutral-disabled-bg': 'rgba(255, 255, 255, 0.08)', + '--color-neutral-border': '#424242', + '--color-neutral-separator': '#FDFDFD1F', + '--color-neutral-bg': '#000000', } } +const cacheColors = new Map>() + export const getCssVarColor = ( baseColor: string, - opts?: { appearance: 'light' | 'dark'; backgroundColor: string }, + opts: { appearance: 'light' | 'dark'; backgroundColor: string }, ) => { - const { appearance = 'light', backgroundColor = '#141414' } = opts || {} + const { appearance = 'light', backgroundColor = '#141414' } = opts + const cacheKey = `${baseColor}-${appearance}-${backgroundColor}` + if (cacheColors.has(cacheKey)) { + return cacheColors.get(cacheKey) + } const color = new TinyColor(baseColor) const preset = appearance === 'dark' ? presetDarkPalettes : presetPalettes const colors = @@ -55,50 +45,29 @@ export const getCssVarColor = ( appearance === 'dark' ? { theme: appearance, backgroundColor } : undefined, ) const accentColor = colors[5] - return { - '--accent-color-1': colors[0], - '--accent-color-2': colors[1], - '--accent-color-3': colors[2], - '--accent-color-4': colors[3], - '--accent-color-5': colors[4], - '--accent-color-6': colors[5], - '--accent-color-7': colors[6], - '--accent-color-8': colors[7], - '--accent-color-9': colors[8], - '--accent-color-10': colors[9], - '--accent-color': accentColor, - '--accent-color-hover': colors[4], - '--accent-color-active': colors[5], - '--accent-color-content': '#ffffff', - ...(appearance === 'dark' ? getDarkNeutralColor() : getLightNeutralColor()), - '--bg-color': baseColor, - '--bg-color-hover': getTintColor(baseColor, 10), - '--bg-color-active': getTintColor(baseColor, 20), - '--bg-color-content': '#ffffff', + const cssVars = { + '--color-accent-1': colors[0], + '--color-accent-2': colors[1], + '--color-accent-3': colors[2], + '--color-accent-4': colors[3], + '--color-accent-5': colors[4], + '--color-accent-6': colors[5], + '--color-accent-7': colors[6], + '--color-accent-8': colors[7], + '--color-accent-9': colors[8], + '--color-accent-10': colors[9], + '--color-accent': accentColor, + '--color-accent-hover': colors[4], + '--color-accent-active': colors[6], + '--color-accent-content': '#ffffff', - '--border-color': baseColor, - '--border-color-hover': getTintColor(baseColor, 10), - '--border-color-active': getTintColor(baseColor, 20), - '--border-color-tint-10': getTintColor(baseColor, 10), - '--border-color-tint-20': getTintColor(baseColor, 20), - '--border-color-tint-30': getTintColor(baseColor, 30), - '--border-color-tint-40': getTintColor(baseColor, 40), - '--border-color-tint-50': getTintColor(baseColor, 50), - '--border-color-tint-60': getTintColor(baseColor, 60), - '--border-color-tint-70': getTintColor(baseColor, 70), - '--border-color-tint-80': getTintColor(baseColor, 80), - '--border-color-tint-90': getTintColor(baseColor, 90), - '--bg-color-tint-10': getTintColor(baseColor, 10), - '--bg-color-tint-20': getTintColor(baseColor, 20), - '--bg-color-tint-30': getTintColor(baseColor, 30), - '--bg-color-tint-40': getTintColor(baseColor, 40), - '--bg-color-tint-50': getTintColor(baseColor, 50), - '--bg-color-tint-60': getTintColor(baseColor, 60), - '--bg-color-tint-70': getTintColor(baseColor, 70), - '--bg-color-tint-80': getTintColor(baseColor, 80), - '--bg-color-tint-90': getTintColor(baseColor, 90), - '--text-color': baseColor, - '--text-color-hover': getTintColor(baseColor, 10), - '--text-color-active': getTintColor(baseColor, 20), + '--color-error': preset.red[4], + '--color-warning': preset.yellow[4], + '--color-success': preset.green[4], + '--color-info': preset.blue[4], + + ...(appearance === 'dark' ? getDarkNeutralColor() : getLightNeutralColor()), } + cacheColors.set(cacheKey, cssVars) + return cssVars }