feat: enhance background layer handling and uploader functionality
- Introduced automatic overlay opacity and gradient based on layer ID in BackgroundLayer component. - Added disabled state to BackgroundUploader, preventing actions when disabled. - Updated component CSS editor to handle disabled state, preventing changes when disabled. - Modified Header and Layout components to manage background inheritance from the page layer. - Improved Sidebar and Card components to respect background inheritance and layering. - Refactored theme management to include default accent color and normalization functions. - Enhanced AppearanceTab to manage accent color changes with debouncing and validation. - Added UI feedback for inherited background layers in AppearanceTab.
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
* 统一管理所有前端 localStorage 设置
|
||||
*/
|
||||
|
||||
import { DEFAULT_ACCENT_COLOR_HSL } from './theme/palette'
|
||||
|
||||
// 所有设置的 key 定义
|
||||
export const STORAGE_KEYS = {
|
||||
// 外观设置
|
||||
@@ -32,7 +34,7 @@ export const STORAGE_KEYS = {
|
||||
export const DEFAULT_SETTINGS = {
|
||||
// 外观
|
||||
theme: 'system' as 'light' | 'dark' | 'system',
|
||||
accentColor: 'blue',
|
||||
accentColor: DEFAULT_ACCENT_COLOR_HSL,
|
||||
enableAnimations: true,
|
||||
enableWavesBackground: true,
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@ type HSL = {
|
||||
l: number
|
||||
}
|
||||
|
||||
export const DEFAULT_ACCENT_COLOR_HSL = '188.5 100% 45.5%'
|
||||
export const DEFAULT_ACCENT_COLOR_HEX = '#00c7e8'
|
||||
|
||||
const clamp = (value: number, min: number, max: number): number => {
|
||||
if (value < min) return min
|
||||
if (value > max) return max
|
||||
@@ -45,6 +48,11 @@ export const formatHSL = (h: number, s: number, l: number): string => {
|
||||
return `${safeH} ${safeS}% ${safeL}%`
|
||||
}
|
||||
|
||||
export const isValidHSLString = (value: string): boolean => {
|
||||
const cleaned = value.trim()
|
||||
return /^-?\d+(?:\.\d+)?\s+-?\d+(?:\.\d+)?%\s+-?\d+(?:\.\d+)?%$/i.test(cleaned)
|
||||
}
|
||||
|
||||
export const hexToHSL = (hex: string): string => {
|
||||
let cleaned = hex.trim().replace('#', '')
|
||||
if (cleaned.length === 3) {
|
||||
@@ -91,6 +99,25 @@ export const hexToHSL = (hex: string): string => {
|
||||
return formatHSL(h, s * 100, l * 100)
|
||||
}
|
||||
|
||||
export const normalizeAccentColor = (accentColor?: string | null): string => {
|
||||
const trimmed = accentColor?.trim()
|
||||
|
||||
if (!trimmed) {
|
||||
return DEFAULT_ACCENT_COLOR_HSL
|
||||
}
|
||||
|
||||
if (trimmed.startsWith('#')) {
|
||||
return hexToHSL(trimmed)
|
||||
}
|
||||
|
||||
if (isValidHSLString(trimmed)) {
|
||||
const { h, s, l } = parseHSL(trimmed)
|
||||
return formatHSL(h, s, l)
|
||||
}
|
||||
|
||||
return DEFAULT_ACCENT_COLOR_HSL
|
||||
}
|
||||
|
||||
export const adjustLightness = (hsl: string, amount: number): string => {
|
||||
const { h, s, l } = parseHSL(hsl)
|
||||
return formatHSL(h, s, l + amount)
|
||||
@@ -170,8 +197,13 @@ export const generatePalette = (accentHSL: string, isDark: boolean): ColorTokens
|
||||
const chartSteps = [0, 72, 144, 216, 288]
|
||||
const charts = chartSteps.map((step) => rotateHue(chartBase, step))
|
||||
|
||||
const card = adjustLightness(background, isDark ? 2 : -1)
|
||||
const popover = adjustLightness(background, isDark ? 3 : -0.5)
|
||||
const surfaceSaturation = clamp(accent.s * (isDark ? 0.18 : 0.14), isDark ? 10 : 6, isDark ? 24 : 16)
|
||||
const card = formatHSL(accent.h, surfaceSaturation, isDark ? 8.8 : 98.6)
|
||||
const popover = formatHSL(
|
||||
accent.h,
|
||||
clamp(surfaceSaturation + (isDark ? 3 : 2), 0, 100),
|
||||
isDark ? 10.5 : 99.3,
|
||||
)
|
||||
|
||||
return {
|
||||
primary,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* 统一处理主题相关的存储操作,包括加载、保存、导出、导入和迁移旧 key
|
||||
*/
|
||||
|
||||
import { DEFAULT_ACCENT_COLOR_HSL, normalizeAccentColor } from './palette'
|
||||
import type { BackgroundConfigMap, UserThemeConfig } from './tokens'
|
||||
|
||||
/**
|
||||
@@ -23,7 +24,7 @@ export const THEME_STORAGE_KEYS = {
|
||||
*/
|
||||
const DEFAULT_THEME_CONFIG: UserThemeConfig = {
|
||||
selectedPreset: 'light',
|
||||
accentColor: 'blue',
|
||||
accentColor: DEFAULT_ACCENT_COLOR_HSL,
|
||||
tokenOverrides: {},
|
||||
customCSS: '',
|
||||
backgroundConfig: {} as BackgroundConfigMap,
|
||||
@@ -65,7 +66,7 @@ export function loadThemeConfig(): UserThemeConfig {
|
||||
|
||||
return {
|
||||
selectedPreset: preset || DEFAULT_THEME_CONFIG.selectedPreset,
|
||||
accentColor: accent || DEFAULT_THEME_CONFIG.accentColor,
|
||||
accentColor: normalizeAccentColor(accent),
|
||||
tokenOverrides,
|
||||
customCSS: customCSS || DEFAULT_THEME_CONFIG.customCSS,
|
||||
backgroundConfig,
|
||||
@@ -79,7 +80,7 @@ export function loadThemeConfig(): UserThemeConfig {
|
||||
*/
|
||||
export function saveThemeConfig(config: UserThemeConfig): void {
|
||||
localStorage.setItem(THEME_STORAGE_KEYS.PRESET, config.selectedPreset)
|
||||
localStorage.setItem(THEME_STORAGE_KEYS.ACCENT, config.accentColor)
|
||||
localStorage.setItem(THEME_STORAGE_KEYS.ACCENT, normalizeAccentColor(config.accentColor))
|
||||
localStorage.setItem(THEME_STORAGE_KEYS.OVERRIDES, JSON.stringify(config.tokenOverrides))
|
||||
localStorage.setItem(THEME_STORAGE_KEYS.CUSTOM_CSS, config.customCSS)
|
||||
if (config.backgroundConfig) {
|
||||
@@ -215,7 +216,7 @@ export function migrateOldKeys(): void {
|
||||
const newAccent = localStorage.getItem(THEME_STORAGE_KEYS.ACCENT)
|
||||
|
||||
if (accentColor && !newAccent) {
|
||||
localStorage.setItem(THEME_STORAGE_KEYS.ACCENT, accentColor)
|
||||
localStorage.setItem(THEME_STORAGE_KEYS.ACCENT, normalizeAccentColor(accentColor))
|
||||
}
|
||||
|
||||
// 删除旧 key
|
||||
|
||||
@@ -154,27 +154,27 @@ export type UserThemeConfig = {
|
||||
|
||||
export const defaultLightTokens: ThemeTokens = {
|
||||
color: {
|
||||
primary: '221.2 83.2% 53.3%',
|
||||
primary: '188.5 100% 45.5%',
|
||||
'primary-foreground': '210 40% 98%',
|
||||
'primary-gradient': 'none',
|
||||
secondary: '210 40% 96.1%',
|
||||
secondary: '188.5 35% 96%',
|
||||
'secondary-foreground': '222.2 47.4% 11.2%',
|
||||
muted: '210 40% 96.1%',
|
||||
'muted-foreground': '215.4 16.3% 46.9%',
|
||||
accent: '210 40% 96.1%',
|
||||
muted: '188.5 12% 96%',
|
||||
'muted-foreground': '188.5 20% 46.9%',
|
||||
accent: '223.5 60% 50.4%',
|
||||
'accent-foreground': '222.2 47.4% 11.2%',
|
||||
destructive: '0 84.2% 60.2%',
|
||||
'destructive-foreground': '210 40% 98%',
|
||||
background: '0 0% 100%',
|
||||
foreground: '222.2 84% 4.9%',
|
||||
card: '0 0% 100%',
|
||||
card: '188.5 14% 98.6%',
|
||||
'card-foreground': '222.2 84% 4.9%',
|
||||
popover: '0 0% 100%',
|
||||
popover: '188.5 16% 99.3%',
|
||||
'popover-foreground': '222.2 84% 4.9%',
|
||||
border: '214.3 31.8% 91.4%',
|
||||
input: '214.3 31.8% 91.4%',
|
||||
ring: '221.2 83.2% 53.3%',
|
||||
'chart-1': '221.2 83.2% 53.3%',
|
||||
border: '188.5 20% 91.4%',
|
||||
input: '188.5 20% 91.4%',
|
||||
ring: '188.5 100% 45.5%',
|
||||
'chart-1': '188.5 100% 45.5%',
|
||||
'chart-2': '160 60% 45%',
|
||||
'chart-3': '30 80% 55%',
|
||||
'chart-4': '280 65% 60%',
|
||||
@@ -249,27 +249,27 @@ export const defaultLightTokens: ThemeTokens = {
|
||||
|
||||
export const defaultDarkTokens: ThemeTokens = {
|
||||
color: {
|
||||
primary: '217.2 91.2% 59.8%',
|
||||
primary: '188.5 100% 45.5%',
|
||||
'primary-foreground': '210 40% 98%',
|
||||
'primary-gradient': 'none',
|
||||
secondary: '217.2 32.6% 17.5%',
|
||||
secondary: '188.5 35% 17.5%',
|
||||
'secondary-foreground': '210 40% 98%',
|
||||
muted: '217.2 32.6% 17.5%',
|
||||
'muted-foreground': '215 20.2% 65.1%',
|
||||
accent: '217.2 32.6% 17.5%',
|
||||
muted: '188.5 12% 17.5%',
|
||||
'muted-foreground': '188.5 20% 65.1%',
|
||||
accent: '223.5 60% 35.3%',
|
||||
'accent-foreground': '210 40% 98%',
|
||||
destructive: '0 62.8% 30.6%',
|
||||
'destructive-foreground': '210 40% 98%',
|
||||
background: '222.2 84% 4.9%',
|
||||
foreground: '210 40% 98%',
|
||||
card: '222.2 84% 4.9%',
|
||||
card: '188.5 18% 8.8%',
|
||||
'card-foreground': '210 40% 98%',
|
||||
popover: '222.2 84% 4.9%',
|
||||
popover: '188.5 21% 10.5%',
|
||||
'popover-foreground': '210 40% 98%',
|
||||
border: '217.2 32.6% 17.5%',
|
||||
input: '217.2 32.6% 17.5%',
|
||||
ring: '224.3 76.3% 48%',
|
||||
'chart-1': '217.2 91.2% 59.8%',
|
||||
border: '188.5 20% 17.5%',
|
||||
input: '188.5 20% 17.5%',
|
||||
ring: '188.5 100% 45.5%',
|
||||
'chart-1': '188.5 100% 45.5%',
|
||||
'chart-2': '160 60% 50%',
|
||||
'chart-3': '30 80% 60%',
|
||||
'chart-4': '280 65% 65%',
|
||||
|
||||
Reference in New Issue
Block a user