feat: implement theme toggling and enhance Button styles with new color variables
							parent
							
								
									428fdfc182
								
							
						
					
					
						commit
						d7ca354b87
					
				|  | @ -1,3 +1,15 @@ | |||
| <template> | ||||
|   <RouterView></RouterView> | ||||
|   <button @click="toggleTheme" class="fixed top-2 right-2">toggle {{ appearance }}</button> | ||||
|   <a-theme :appearance="appearance"> | ||||
|     <RouterView /> | ||||
|   </a-theme> | ||||
| </template> | ||||
| <script setup lang="ts"> | ||||
| import { ref } from 'vue' | ||||
| 
 | ||||
| const appearance = ref('light') | ||||
| 
 | ||||
| const toggleTheme = () => { | ||||
|   appearance.value = appearance.value === 'light' ? 'dark' : 'light' | ||||
| } | ||||
| </script> | ||||
|  |  | |||
|  | @ -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) => { | ||||
|  |  | |||
|  | @ -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) { | ||||
|  |  | |||
|  | @ -5,8 +5,30 @@ | |||
| <script setup lang="ts"> | ||||
| import { themeProps } from './meta' | ||||
| import { useThemeProvide } from './hook' | ||||
| import { getCssVarColor } from '@/utils/colorAlgorithm' | ||||
| import { watchEffect } from 'vue' | ||||
| 
 | ||||
| const props = defineProps(themeProps) | ||||
| 
 | ||||
| useThemeProvide(props) | ||||
| 
 | ||||
| const style = document.createElement('style') | ||||
| watchEffect(() => { | ||||
|   const cssVars = getCssVarColor(props.primaryColor, { | ||||
|     appearance: props.appearance, | ||||
|     backgroundColor: props.backgroundColor, | ||||
|   }) | ||||
|   document.documentElement.classList.remove('light-theme', 'dark-theme') | ||||
|   document.documentElement.classList.add(`${props.appearance}-theme`) | ||||
|   style.textContent = `:root.${props.appearance}-theme { | ||||
|     ${Object.entries(cssVars) | ||||
|       .map(([key, value]) => `${key}: ${value};`) | ||||
|       .join('\n')} | ||||
|   }` | ||||
|   document.head.appendChild(style) | ||||
| 
 | ||||
|   return () => { | ||||
|     document.head.removeChild(style) | ||||
|   } | ||||
| }) | ||||
| </script> | ||||
|  |  | |||
|  | @ -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<ThemeType> = Symbol('theme') | ||||
| const ThemeSymbol: InjectionKey<ThemeProps> = 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) | ||||
| } | ||||
|  |  | |||
|  | @ -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 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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; | ||||
| } | ||||
|  |  | |||
|  | @ -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<string, Record<string, string>>() | ||||
| 
 | ||||
| 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 | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 tangjinzhou
						tangjinzhou