Skip to content

Commit a4deca9

Browse files
authored
fix(xuhome): make palette colors live (#4410)
1 parent 4200b3a commit a4deca9

7 files changed

Lines changed: 212 additions & 48 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* @jest-environment node
3+
*/
4+
import { getThemeSwitchMeta } from '@/conf/themeSwitch.manifest'
5+
import CONFIG from '@/themes/xuhome/config'
6+
7+
describe('XuHome theme console colors', () => {
8+
it('exposes every color config in the palette instead of text settings', () => {
9+
const meta = getThemeSwitchMeta('xuhome')
10+
const colorKeys = Object.keys(CONFIG).filter(key =>
11+
/_COLOR(?:_|$)|_THEME_COLOR(?:_|$)/.test(key)
12+
)
13+
const paletteKeys = meta.palette.map(item => item.key)
14+
const settingKeys = meta.settings.map(item => item.key)
15+
16+
expect(paletteKeys).toEqual(expect.arrayContaining(colorKeys))
17+
expect(settingKeys).not.toEqual(expect.arrayContaining(colorKeys))
18+
expect(new Set(paletteKeys).size).toBe(paletteKeys.length)
19+
})
20+
21+
it('uses dedicated live CSS variables for the Hero colors', () => {
22+
const paletteByKey = Object.fromEntries(
23+
getThemeSwitchMeta('xuhome').palette.map(item => [item.key, item])
24+
)
25+
26+
expect(paletteByKey.XUHOME_HERO_TITLE_COLOR.cssVar).toBe(
27+
'--xuhome-hero-title-color'
28+
)
29+
expect(paletteByKey.XUHOME_HERO_BIO_COLOR.cssVar).toBe(
30+
'--xuhome-hero-bio-color'
31+
)
32+
})
33+
})

conf/themeSwitch.manifest.js

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -333,17 +333,22 @@ export const THEME_SWITCH_MANIFEST = {
333333
summary: '新粗野主义博客主题,粗边框、偏移阴影、Hero 打字机与响应式导航。',
334334
tier: 'free',
335335
palette: [
336-
{ key: 'XUHOME_COLOR_PRIMARY', cssVar: '--xuhome-primary', label: '主色', defaultValue: '#0284c7' },
337-
{ key: 'XUHOME_COLOR_PRIMARY_HOVER', cssVar: '--xuhome-primary-hover', label: '主色 hover', defaultValue: '#0ea5e9' },
338-
{ key: 'XUHOME_COLOR_ACCENT', cssVar: '--xuhome-accent', label: '强调色', defaultValue: '#fde68a' },
339-
{ key: 'XUHOME_COLOR_BG', cssVar: '--xuhome-bg-light', label: '页面背景', defaultValue: '#faf8f5' },
340-
{ key: 'XUHOME_COLOR_SURFACE', cssVar: '--xuhome-surface-light', label: '卡片背景', defaultValue: '#ffffff' },
341-
{ key: 'XUHOME_COLOR_TEXT', cssVar: '--xuhome-text-light', label: '主文字', defaultValue: '#0f172a' },
342-
{ key: 'XUHOME_COLOR_MUTED', cssVar: '--xuhome-muted-light', label: '次级文字', defaultValue: '#475569' },
343-
{ key: 'XUHOME_COLOR_BG_DARK', cssVar: '--xuhome-bg-dark', label: '深色模式:页面背景', defaultValue: '#0f172a' },
344-
{ key: 'XUHOME_COLOR_SURFACE_DARK', cssVar: '--xuhome-surface-dark', label: '深色模式:卡片背景', defaultValue: '#1e293b' },
345-
{ key: 'XUHOME_COLOR_TEXT_DARK', cssVar: '--xuhome-text-dark', label: '深色模式:主文字', defaultValue: '#f1f5f9' },
346-
{ key: 'XUHOME_COLOR_MUTED_DARK', cssVar: '--xuhome-muted-dark', label: '深色模式:次级文字', defaultValue: '#94a3b8' }
336+
{ key: 'XUHOME_COLOR_PRIMARY', cssVar: '--xuhome-color-primary', label: '主色', defaultValue: '#0284c7' },
337+
{ key: 'XUHOME_COLOR_PRIMARY_HOVER', cssVar: '--xuhome-color-primary-hover', label: '主色 hover', defaultValue: '#0ea5e9' },
338+
{ key: 'XUHOME_COLOR_ACCENT', cssVar: '--xuhome-color-accent', label: '强调色', defaultValue: '#fde68a' },
339+
{ key: 'XUHOME_COLOR_BG', cssVar: '--xuhome-color-bg', label: '页面背景', defaultValue: '#faf8f5' },
340+
{ key: 'XUHOME_COLOR_CARD', cssVar: '--xuhome-color-card', label: '卡片背景', defaultValue: '#ffffff' },
341+
{ key: 'XUHOME_COLOR_TEXT', cssVar: '--xuhome-color-text', label: '主文字', defaultValue: '#0f172a' },
342+
{ key: 'XUHOME_COLOR_TEXT_SECONDARY', cssVar: '--xuhome-color-text-secondary', label: '次级文字', defaultValue: '#475569' },
343+
{ key: 'XUHOME_COLOR_BORDER', cssVar: '--xuhome-color-border', label: '边框', defaultValue: '#0284c7' },
344+
{ key: 'XUHOME_HERO_TITLE_COLOR', cssVar: '--xuhome-hero-title-color', label: '首屏标题颜色', defaultValue: '#0284c7' },
345+
{ key: 'XUHOME_HERO_BIO_COLOR', cssVar: '--xuhome-hero-bio-color', label: '首屏 Bio 颜色', defaultValue: '#475569' },
346+
{ key: 'XUHOME_COLOR_PRIMARY_DARK', cssVar: '--xuhome-color-primary-dark', label: '深色模式:主色', defaultValue: '#38bdf8' },
347+
{ key: 'XUHOME_COLOR_BG_DARK', cssVar: '--xuhome-color-bg-dark', label: '深色模式:页面背景', defaultValue: '#0f172a' },
348+
{ key: 'XUHOME_COLOR_CARD_DARK', cssVar: '--xuhome-color-card-dark', label: '深色模式:卡片背景', defaultValue: '#1e293b' },
349+
{ key: 'XUHOME_COLOR_TEXT_DARK', cssVar: '--xuhome-color-text-dark', label: '深色模式:主文字', defaultValue: '#f1f5f9' },
350+
{ key: 'XUHOME_COLOR_TEXT_SECONDARY_DARK', cssVar: '--xuhome-color-text-secondary-dark', label: '深色模式:次级文字', defaultValue: '#94a3b8' },
351+
{ key: 'XUHOME_COLOR_BORDER_DARK', cssVar: '--xuhome-color-border-dark', label: '深色模式:边框', defaultValue: '#38bdf8' }
347352
]
348353
}
349354
}
@@ -384,7 +389,7 @@ function inferThemeSettings(themeId, manualSettings = []) {
384389
return Object.entries(config)
385390
.filter(([key, value]) => {
386391
if (manualKeys.has(key)) return false
387-
if (/_COLOR_|_THEME_COLOR/.test(key)) return false
392+
if (/_COLOR(?:_|$)|_THEME_COLOR(?:_|$)/.test(key)) return false
388393
return ['boolean', 'string', 'number'].includes(typeof value)
389394
})
390395
.map(([key, value]) => normalizeSetting({

docs/user-guide/themes/xuhome.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,16 @@ Hello|Welcome|Enjoy reading
9090
| `XUHOME_COLOR_PRIMARY_HOVER` | 见 config.js |
9191
| `XUHOME_COLOR_ACCENT` | 见 config.js |
9292
| `XUHOME_COLOR_BG` | 见 config.js |
93-
| `XUHOME_COLOR_SURFACE` | 见 config.js |
93+
| `XUHOME_COLOR_CARD` | 卡片背景 |
9494
| `XUHOME_COLOR_TEXT` | 见 config.js |
95-
| `XUHOME_COLOR_MUTED` | 见 config.js |
95+
| `XUHOME_COLOR_TEXT_SECONDARY` | 次级文字 |
96+
| `XUHOME_COLOR_BORDER` | 边框与阴影 |
97+
| `XUHOME_COLOR_PRIMARY_DARK` | 深色模式主色 |
9698
| `XUHOME_COLOR_BG_DARK` | 见 config.js |
97-
| `XUHOME_COLOR_SURFACE_DARK` | 见 config.js |
99+
| `XUHOME_COLOR_CARD_DARK` | 深色模式卡片背景 |
98100
| `XUHOME_COLOR_TEXT_DARK` | 见 config.js |
99-
| `XUHOME_COLOR_MUTED_DARK` | 见 config.js |
101+
| `XUHOME_COLOR_TEXT_SECONDARY_DARK` | 深色模式次级文字 |
102+
| `XUHOME_COLOR_BORDER_DARK` | 深色模式边框与阴影 |
100103

101104
<!-- /theme-config-table -->
102105

themes/xuhome/components/HeroSection.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,13 @@ export default function HeroSection(props) {
1818
const typeSpeed = siteConfig('XUHOME_HERO_TYPE_SPEED', 80, CONFIG)
1919
const deleteSpeed = siteConfig('XUHOME_HERO_DELETE_SPEED', 40, CONFIG)
2020
const typePause = siteConfig('XUHOME_HERO_TYPE_PAUSE', 2000, CONFIG)
21-
const titleColor = siteConfig('XUHOME_HERO_TITLE_COLOR', '#0284c7', CONFIG)
22-
const bioColor = siteConfig('XUHOME_HERO_BIO_COLOR', '#475569', CONFIG)
23-
2421
const texts = heroTexts.length > 0 ? heroTexts : [heroTitle]
2522

2623
return (
2724
<div className='mb-8'>
2825
<div
2926
className='text-3xl font-black uppercase tracking-tight mb-2 min-h-[2.5rem]'
30-
style={{ color: titleColor }}
27+
style={{ color: 'var(--xuhome-hero-title-color)' }}
3128
>
3229
{texts.length > 1 ? (
3330
<Typewriter
@@ -45,15 +42,15 @@ export default function HeroSection(props) {
4542
{heroBio && (
4643
<p
4744
className='text-base font-semibold leading-relaxed max-w-2xl'
48-
style={{ color: bioColor }}
45+
style={{ color: 'var(--xuhome-hero-bio-color)' }}
4946
>
5047
{heroBio}
5148
</p>
5249
)}
5350

5451
<div
5552
className='mt-6 border-b-[3px]'
56-
style={{ borderColor: titleColor }}
53+
style={{ borderColor: 'var(--xuhome-hero-title-color)' }}
5754
/>
5855
</div>
5956
)

themes/xuhome/config.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,15 @@ const CONFIG = {
2323
XUHOME_COLOR_PRIMARY_HOVER: '#0ea5e9',
2424
XUHOME_COLOR_ACCENT: '#fde68a',
2525
XUHOME_COLOR_BG: '#faf8f5',
26-
XUHOME_COLOR_SURFACE: '#ffffff',
26+
XUHOME_COLOR_CARD: '#ffffff',
2727
XUHOME_COLOR_TEXT: '#0f172a',
28-
XUHOME_COLOR_MUTED: '#475569',
28+
XUHOME_COLOR_TEXT_SECONDARY: '#475569',
29+
XUHOME_COLOR_BORDER: '#0284c7',
30+
XUHOME_COLOR_PRIMARY_DARK: '#38bdf8',
2931
XUHOME_COLOR_BG_DARK: '#0f172a',
30-
XUHOME_COLOR_SURFACE_DARK: '#1e293b',
32+
XUHOME_COLOR_CARD_DARK: '#1e293b',
3133
XUHOME_COLOR_TEXT_DARK: '#f1f5f9',
32-
XUHOME_COLOR_MUTED_DARK: '#94a3b8'
34+
XUHOME_COLOR_TEXT_SECONDARY_DARK: '#94a3b8',
35+
XUHOME_COLOR_BORDER_DARK: '#38bdf8'
3336
}
3437
export default CONFIG

themes/xuhome/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ const Layout404 = props => {
247247
<div className='text-center'>
248248
<div
249249
className='text-8xl font-black text-[#0284c7] mb-4'
250-
style={{ textShadow: '4px 4px 0 #fde68a' }}
250+
style={{ textShadow: '4px 4px 0 var(--xuhome-accent)' }}
251251
>
252252
404
253253
</div>

themes/xuhome/style.js

Lines changed: 144 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,151 @@
11
/* eslint-disable react/no-unknown-property */
22
import { themeConsoleStyle } from '@/lib/themeConsoleStyle'
3+
import { siteConfig } from '@/lib/config'
34
import CONFIG from './config'
45

56
const Style = () => {
67
return (
78
<style jsx global>{`
89
#theme-xuhome {
9-
--xuhome-primary: #0284c7;
10-
--xuhome-primary-hover: #0ea5e9;
11-
--xuhome-accent: #fde68a;
12-
--xuhome-bg-light: #faf8f5;
13-
--xuhome-surface-light: #ffffff;
14-
--xuhome-text-light: #0f172a;
15-
--xuhome-muted-light: #475569;
16-
--xuhome-bg-dark: #0f172a;
17-
--xuhome-surface-dark: #1e293b;
18-
--xuhome-text-dark: #f1f5f9;
19-
--xuhome-muted-dark: #94a3b8;
20-
background-color: var(--xuhome-bg-light);
21-
color: var(--xuhome-text-light);
10+
--xuhome-color-primary-hover: ${siteConfig('XUHOME_COLOR_PRIMARY_HOVER', CONFIG.XUHOME_COLOR_PRIMARY_HOVER, CONFIG)};
11+
--xuhome-color-accent: ${siteConfig('XUHOME_COLOR_ACCENT', CONFIG.XUHOME_COLOR_ACCENT, CONFIG)};
12+
--xuhome-hero-title-color: ${siteConfig('XUHOME_HERO_TITLE_COLOR', CONFIG.XUHOME_HERO_TITLE_COLOR, CONFIG)};
13+
--xuhome-hero-bio-color: ${siteConfig('XUHOME_HERO_BIO_COLOR', CONFIG.XUHOME_HERO_BIO_COLOR, CONFIG)};
14+
--xuhome-primary: var(--xuhome-console-primary);
15+
--xuhome-primary-hover: var(--xuhome-console-primary-hover);
16+
--xuhome-accent: var(
17+
--xuhome-console-accent,
18+
var(--xuhome-color-accent)
19+
);
20+
--xuhome-bg-light: var(--xuhome-console-bg);
21+
--xuhome-surface-light: var(--xuhome-console-card);
22+
--xuhome-text-light: var(--xuhome-console-text);
23+
--xuhome-muted-light: var(--xuhome-console-text-secondary);
24+
--xuhome-bg-dark: var(--xuhome-console-bg);
25+
--xuhome-surface-dark: var(--xuhome-console-card);
26+
--xuhome-text-dark: var(--xuhome-console-text);
27+
--xuhome-muted-dark: var(--xuhome-console-text-secondary);
28+
background-color: var(--xuhome-console-bg);
29+
color: var(--xuhome-console-text);
2230
}
2331
.dark #theme-xuhome {
24-
background-color: var(--xuhome-bg-dark);
25-
color: var(--xuhome-text-dark);
32+
background-color: var(--xuhome-console-bg);
33+
color: var(--xuhome-console-text);
34+
}
35+
36+
/* XuHome uses arbitrary Tailwind colors extensively. Map those legacy
37+
utility tokens to the live theme-console variables. */
38+
#theme-xuhome [class~='border-[#0284c7]'] {
39+
border-color: var(--xuhome-console-border) !important;
40+
}
41+
#theme-xuhome [class~='border-[#fde68a]'] {
42+
border-color: var(--xuhome-accent) !important;
43+
}
44+
#theme-xuhome [class~='text-[#0284c7]'] {
45+
color: var(--xuhome-console-primary) !important;
46+
}
47+
#theme-xuhome [class~='text-[#0284c7]/60'] {
48+
color: color-mix(
49+
in srgb,
50+
var(--xuhome-console-primary) 60%,
51+
transparent
52+
) !important;
53+
}
54+
#theme-xuhome [class~='bg-[#0284c7]'] {
55+
background-color: var(--xuhome-console-primary) !important;
56+
}
57+
#theme-xuhome [class~='bg-[#fde68a]'] {
58+
background-color: var(--xuhome-accent) !important;
59+
}
60+
#theme-xuhome [class~='bg-[#ffffff]'],
61+
#theme-xuhome [class~='bg-white'] {
62+
background-color: var(--xuhome-console-card) !important;
63+
}
64+
#theme-xuhome [class~='bg-[#faf8f5]'] {
65+
background-color: var(--xuhome-console-bg) !important;
66+
}
67+
#theme-xuhome [class~='text-slate-900'],
68+
#theme-xuhome [class~='text-slate-800'],
69+
#theme-xuhome [class~='dark:text-slate-100']:is(.dark *),
70+
#theme-xuhome [class~='dark:text-slate-200']:is(.dark *) {
71+
color: var(--xuhome-console-text) !important;
72+
}
73+
#theme-xuhome [class~='text-slate-700'],
74+
#theme-xuhome [class~='text-slate-600'],
75+
#theme-xuhome [class~='text-slate-500'],
76+
#theme-xuhome [class~='text-slate-400'],
77+
#theme-xuhome [class~='text-slate-300'],
78+
#theme-xuhome [class~='dark:text-slate-300']:is(.dark *),
79+
#theme-xuhome [class~='dark:text-slate-400']:is(.dark *),
80+
#theme-xuhome [class~='dark:text-slate-500']:is(.dark *),
81+
#theme-xuhome [class~='dark:text-slate-600']:is(.dark *) {
82+
color: var(--xuhome-console-text-secondary) !important;
83+
}
84+
.dark #theme-xuhome [class~='dark:bg-slate-600'],
85+
.dark #theme-xuhome [class~='dark:bg-slate-700'],
86+
.dark #theme-xuhome [class~='dark:bg-slate-800'] {
87+
background-color: var(--xuhome-console-card) !important;
88+
}
89+
.dark #theme-xuhome [class~='dark:bg-slate-900/85'] {
90+
background-color: color-mix(
91+
in srgb,
92+
var(--xuhome-console-bg) 85%,
93+
transparent
94+
) !important;
95+
}
96+
#theme-xuhome [class~='bg-[#faf8f5]/55'] {
97+
background-color: color-mix(
98+
in srgb,
99+
var(--xuhome-console-bg) 55%,
100+
transparent
101+
) !important;
102+
}
103+
#theme-xuhome [class~='hover:bg-[#0284c7]']:hover {
104+
background-color: var(--xuhome-console-primary) !important;
105+
}
106+
#theme-xuhome [class~='hover:bg-[#0ea5e9]']:hover {
107+
background-color: var(--xuhome-console-primary-hover) !important;
108+
}
109+
#theme-xuhome [class~='hover:bg-[#fde68a]']:hover {
110+
background-color: var(--xuhome-accent) !important;
111+
}
112+
#theme-xuhome [class~='hover:text-[#0284c7]']:hover,
113+
#theme-xuhome .group:hover [class~='group-hover:text-[#0284c7]'] {
114+
color: var(--xuhome-console-primary) !important;
115+
}
116+
#theme-xuhome .group:hover [class~='group-hover:text-[#0ea5e9]'] {
117+
color: var(--xuhome-console-primary-hover) !important;
118+
}
119+
#theme-xuhome [class~='shadow-[1px_1px_0px_0px_#0284c7]'] {
120+
--tw-shadow: 1px 1px 0 0 var(--xuhome-console-border);
121+
}
122+
#theme-xuhome [class~='shadow-[2px_2px_0px_0px_#0284c7]'] {
123+
--tw-shadow: 2px 2px 0 0 var(--xuhome-console-border);
124+
}
125+
#theme-xuhome [class~='shadow-[3px_3px_0px_0px_#0284c7]'] {
126+
--tw-shadow: 3px 3px 0 0 var(--xuhome-console-border);
127+
}
128+
#theme-xuhome [class~='shadow-[4px_4px_0px_0px_#0284c7]'] {
129+
--tw-shadow: 4px 4px 0 0 var(--xuhome-console-border);
130+
}
131+
#theme-xuhome [class~='hover:shadow-[4px_4px_0px_0px_#0284c7]']:hover {
132+
--tw-shadow: 4px 4px 0 0 var(--xuhome-console-border);
133+
}
134+
#theme-xuhome [class~='hover:shadow-[6px_6px_0px_0px_#0284c7]']:hover {
135+
--tw-shadow: 6px 6px 0 0 var(--xuhome-console-border);
136+
}
137+
#theme-xuhome [class~='active:shadow-[1px_1px_0px_0px_#0284c7]']:active {
138+
--tw-shadow: 1px 1px 0 0 var(--xuhome-console-border);
139+
}
140+
#theme-xuhome [class~='active:shadow-[2px_2px_0px_0px_#0284c7]']:active {
141+
--tw-shadow: 2px 2px 0 0 var(--xuhome-console-border);
142+
}
143+
#theme-xuhome [class~='shadow-[0px_4px_0px_0px_#0284c7]'] {
144+
--tw-shadow: 0 4px 0 0 var(--xuhome-console-border);
145+
}
146+
#theme-xuhome [class~='shadow-[0px_4px_0px_0px_rgba(2,132,199,0.2)]'] {
147+
--tw-shadow: 0 4px 0 0
148+
color-mix(in srgb, var(--xuhome-console-border) 20%, transparent);
26149
}
27150
28151
#theme-xuhome ::selection {
@@ -76,15 +199,15 @@ const Style = () => {
76199
ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
77200
}
78201
.dark #theme-xuhome #notion-article code {
79-
background: #334155;
202+
background: var(--xuhome-console-card);
80203
color: var(--xuhome-accent);
81204
}
82205
83206
#theme-xuhome #notion-article pre {
84207
border-radius: 0.125rem;
85208
padding: 16px;
86209
overflow-x: auto;
87-
background: #f8fafc;
210+
background: var(--xuhome-console-card);
88211
border: 2px solid var(--xuhome-primary);
89212
font-size: 0.8125rem;
90213
line-height: 1.5;
@@ -97,11 +220,11 @@ const Style = () => {
97220
border-left: 4px solid var(--xuhome-accent);
98221
padding: 0.5em 1em;
99222
margin: 1em 0;
100-
background: rgba(253, 230, 138, 0.1);
223+
background: color-mix(in srgb, var(--xuhome-accent) 10%, transparent);
101224
font-weight: 500;
102225
}
103226
.dark #theme-xuhome #notion-article blockquote {
104-
background: rgba(253, 230, 138, 0.05);
227+
background: color-mix(in srgb, var(--xuhome-accent) 5%, transparent);
105228
}
106229
107230
#theme-xuhome #notion-article hr {
@@ -128,7 +251,7 @@ const Style = () => {
128251
letter-spacing: 0.05em;
129252
}
130253
.dark #theme-xuhome #notion-article th {
131-
background: #334155;
254+
background: var(--xuhome-console-card);
132255
color: var(--xuhome-accent);
133256
border-color: var(--xuhome-primary);
134257
}
@@ -157,7 +280,7 @@ const Style = () => {
157280
height: 8px;
158281
}
159282
#theme-xuhome ::-webkit-scrollbar-track {
160-
background: #f1f5f9;
283+
background: var(--xuhome-console-card);
161284
}
162285
#theme-xuhome ::-webkit-scrollbar-thumb {
163286
background: var(--xuhome-primary);

0 commit comments

Comments
 (0)