mirror of
https://github.com/iflytek/skillhub.git
synced 2026-09-07 08:26:00 +00:00
fix(theme): polish dark header and toggle
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
This commit is contained in:
parent
fa13dd54ee
commit
0d48945fd2
6 changed files with 55 additions and 41 deletions
|
|
@ -59,9 +59,13 @@ test.describe('Light and dark theme', () => {
|
|||
|
||||
await page.goto('/')
|
||||
await expect(page.locator('html')).not.toHaveClass(/dark/)
|
||||
const header = page.locator('header')
|
||||
const lightHeaderBackground = await header.evaluate((element) => getComputedStyle(element).backgroundColor)
|
||||
|
||||
await page.getByRole('button', { name: 'Switch to dark theme' }).click()
|
||||
await expect(page.locator('html')).toHaveClass(/dark/)
|
||||
await expect.poll(() => header.evaluate((element) => getComputedStyle(element).backgroundColor))
|
||||
.not.toBe(lightHeaderBackground)
|
||||
await expect.poll(() => page.evaluate(() => window.localStorage.getItem('skillhub-theme'))).toBe('dark')
|
||||
|
||||
await page.reload()
|
||||
|
|
@ -73,7 +77,7 @@ test.describe('Light and dark theme', () => {
|
|||
).__themeAtFirstReactContent)).toBe(true)
|
||||
|
||||
await page.getByRole('link', { name: 'Search', exact: true }).first().click()
|
||||
await expect(page).toHaveURL(/\/search$/)
|
||||
await expect(page).toHaveURL(/\/search(?:\?|$)/)
|
||||
await expect(page.locator('html')).toHaveClass(/dark/)
|
||||
await page.screenshot({ path: testInfo.outputPath('dark-desktop.png'), fullPage: true })
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,11 @@ import { APP_HEADER_ELEVATED_CLASS_NAME, getAppHeaderClassName } from './layout-
|
|||
|
||||
describe('getAppHeaderClassName', () => {
|
||||
it('keeps the header flat before the page starts scrolling', () => {
|
||||
expect(getAppHeaderClassName(false)).not.toContain(APP_HEADER_ELEVATED_CLASS_NAME)
|
||||
const className = getAppHeaderClassName(false)
|
||||
|
||||
expect(className).not.toContain(APP_HEADER_ELEVATED_CLASS_NAME)
|
||||
expect(className).toContain('bg-background/90')
|
||||
expect(className).not.toContain('bg-white')
|
||||
})
|
||||
|
||||
it('adds a subtle drop shadow after the header becomes sticky', () => {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { cn } from '@/shared/lib/utils'
|
||||
|
||||
export const APP_HEADER_BASE_CLASS_NAME =
|
||||
'sticky top-0 z-50 flex items-center justify-between border-b bg-white px-6 py-4 transition-shadow duration-200 md:px-12'
|
||||
'sticky top-0 z-50 flex items-center justify-between border-b border-border/70 bg-background/90 px-6 py-4 backdrop-blur-xl transition-[background-color,border-color,box-shadow] duration-200 supports-[backdrop-filter]:bg-background/80 md:px-12'
|
||||
|
||||
export const APP_HEADER_ELEVATED_CLASS_NAME = 'shadow-[0_10px_24px_-20px_rgba(15,23,42,0.32)]'
|
||||
export const APP_HEADER_ELEVATED_CLASS_NAME =
|
||||
'shadow-[0_12px_30px_-24px_hsl(var(--foreground)/0.45)]'
|
||||
|
||||
export function getAppHeaderClassName(isElevated: boolean): string {
|
||||
return cn(APP_HEADER_BASE_CLASS_NAME, isElevated && APP_HEADER_ELEVATED_CLASS_NAME)
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ export function Layout() {
|
|||
<div
|
||||
className="absolute top-0 right-0 w-[600px] h-[500px] rounded-full opacity-90"
|
||||
style={{
|
||||
background: 'radial-gradient(ellipse at 70% 20%, rgba(184,94,255,0.25) 0%, rgba(106,109,255,0.15) 40%, transparent 70%)',
|
||||
background: 'radial-gradient(ellipse at 70% 20%, hsl(var(--glow-accent) / 0.12) 0%, hsl(var(--glow-primary) / 0.07) 40%, transparent 70%)',
|
||||
filter: 'blur(60px)',
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -51,33 +51,33 @@
|
|||
}
|
||||
|
||||
.dark {
|
||||
/* Dark theme (preserved, not default) */
|
||||
--background: 222 47% 6%;
|
||||
--foreground: 210 40% 96%;
|
||||
--card: 222 40% 9%;
|
||||
--card-foreground: 210 40% 96%;
|
||||
--popover: 222 40% 9%;
|
||||
--popover-foreground: 210 40% 96%;
|
||||
--primary: 239 100% 75%;
|
||||
--primary-foreground: 222 47% 6%;
|
||||
--secondary: 222 30% 13%;
|
||||
--secondary-foreground: 210 30% 85%;
|
||||
--muted: 222 25% 15%;
|
||||
--muted-foreground: 215 20% 55%;
|
||||
--accent: 271 100% 72%;
|
||||
/* Cool ink surfaces: distinct depth without near-black dead zones. */
|
||||
--background: 222 30% 10%;
|
||||
--foreground: 216 28% 93%;
|
||||
--card: 222 25% 14%;
|
||||
--card-foreground: 216 28% 93%;
|
||||
--popover: 222 24% 15%;
|
||||
--popover-foreground: 216 28% 93%;
|
||||
--primary: 241 92% 74%;
|
||||
--primary-foreground: 222 30% 10%;
|
||||
--secondary: 222 20% 18%;
|
||||
--secondary-foreground: 216 24% 88%;
|
||||
--muted: 222 18% 19%;
|
||||
--muted-foreground: 216 14% 66%;
|
||||
--accent: 272 86% 72%;
|
||||
--accent-foreground: 0 0% 100%;
|
||||
--destructive: 0 72% 55%;
|
||||
--destructive: 0 72% 61%;
|
||||
--destructive-foreground: 0 0% 100%;
|
||||
--border: 222 20% 18%;
|
||||
--input: 222 20% 18%;
|
||||
--ring: 239 100% 75%;
|
||||
--surface-glass: 222 35% 11%;
|
||||
--glow-primary: 239 100% 75%;
|
||||
--glow-accent: 271 100% 72%;
|
||||
--text-secondary: 215 20% 72%;
|
||||
--text-muted: 215 16% 63%;
|
||||
--text-placeholder: 215 14% 48%;
|
||||
--border-card: 222 20% 18%;
|
||||
--border: 222 16% 25%;
|
||||
--input: 222 16% 25%;
|
||||
--ring: 241 92% 74%;
|
||||
--surface-glass: 222 24% 14%;
|
||||
--glow-primary: 241 92% 70%;
|
||||
--glow-accent: 272 80% 68%;
|
||||
--text-secondary: 216 17% 76%;
|
||||
--text-muted: 216 14% 66%;
|
||||
--text-placeholder: 216 12% 54%;
|
||||
--border-card: 222 16% 24%;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,24 +21,29 @@ export function ThemeToggle({ className }: ThemeToggleProps) {
|
|||
title={label}
|
||||
onClick={toggleTheme}
|
||||
className={cn(
|
||||
'group relative inline-flex h-11 w-11 shrink-0 items-center justify-center overflow-hidden rounded-full border border-border/70 bg-card/80 text-muted-foreground shadow-sm transition-colors duration-200 hover:border-primary/35 hover:bg-secondary hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background',
|
||||
'group relative inline-flex h-10 w-[4.5rem] shrink-0 items-center rounded-full border border-border bg-muted/70 p-1 text-muted-foreground shadow-sm transition-[background-color,border-color] duration-200 hover:border-primary/40 hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<Sun
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={cn(
|
||||
'absolute h-[18px] w-[18px] transition-[opacity,transform] duration-200 motion-reduce:transition-none',
|
||||
isDark ? 'rotate-90 scale-75 opacity-0' : 'rotate-0 scale-100 opacity-100',
|
||||
)}
|
||||
/>
|
||||
<Moon
|
||||
aria-hidden="true"
|
||||
className={cn(
|
||||
'absolute h-[18px] w-[18px] transition-[opacity,transform] duration-200 motion-reduce:transition-none',
|
||||
isDark ? 'rotate-0 scale-100 opacity-100' : '-rotate-90 scale-75 opacity-0',
|
||||
'absolute left-1 top-1 h-8 w-8 rounded-full border border-border/80 bg-card shadow-[0_3px_10px_-4px_hsl(var(--foreground)/0.45)] transition-transform duration-200 ease-out motion-reduce:transition-none',
|
||||
isDark && 'translate-x-8',
|
||||
)}
|
||||
/>
|
||||
<span className="relative z-10 inline-flex h-8 w-8 items-center justify-center">
|
||||
<Sun
|
||||
aria-hidden="true"
|
||||
className={cn('h-4 w-4 transition-colors duration-200', !isDark && 'text-foreground')}
|
||||
/>
|
||||
</span>
|
||||
<span className="relative z-10 inline-flex h-8 w-8 items-center justify-center">
|
||||
<Moon
|
||||
aria-hidden="true"
|
||||
className={cn('h-4 w-4 transition-colors duration-200', isDark && 'text-foreground')}
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue