mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-31 04:12:13 +03:00
fix(frontend): preserve theme body classes (#6157)
* fix(storybook): preserve preview body classes * fix(frontend): retain theme body classes * fix(storybook): mirror panel theme attributes * test(storybook): cover theme switches * test(storybook): strengthen theme DOM coverage * fix(frontend): preserve message container classes --------- Co-authored-by: PathGao <gaoyanbo@gaoyanbodeMacBook-Air.local>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useLayoutEffect } from 'react';
|
||||
import type { Decorator, Preview } from '@storybook/react-vite';
|
||||
import { ConfigProvider } from 'antd';
|
||||
import i18next from 'i18next';
|
||||
@@ -17,11 +17,12 @@ if (!i18next.isInitialized) {
|
||||
});
|
||||
}
|
||||
|
||||
const withTheme: Decorator = (Story, context) => {
|
||||
export const withTheme: Decorator = (Story, context) => {
|
||||
const dark = context.globals.theme === 'dark';
|
||||
useEffect(() => {
|
||||
document.body.setAttribute('class', dark ? 'dark' : 'light');
|
||||
document.documentElement.setAttribute('data-theme', dark ? 'dark' : 'light');
|
||||
useLayoutEffect(() => {
|
||||
document.body.classList.remove('dark', 'light');
|
||||
document.body.classList.add(dark ? 'dark' : 'light');
|
||||
document.documentElement.removeAttribute('data-theme');
|
||||
}, [dark]);
|
||||
return (
|
||||
<ConfigProvider theme={buildAntdThemeConfig(dark, false)}>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { createContext, useCallback, useContext, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { theme as antdTheme } from 'antd';
|
||||
import type { ThemeConfig } from 'antd';
|
||||
@@ -13,14 +13,18 @@ function readBool(key: string, fallback: boolean): boolean {
|
||||
}
|
||||
|
||||
function applyDom(isDark: boolean, isUltra: boolean) {
|
||||
document.body.setAttribute('class', isDark ? 'dark' : 'light');
|
||||
document.body.classList.remove('dark', 'light');
|
||||
document.body.classList.add(isDark ? 'dark' : 'light');
|
||||
if (isUltra) {
|
||||
document.documentElement.setAttribute('data-theme', 'ultra-dark');
|
||||
} else {
|
||||
document.documentElement.removeAttribute('data-theme');
|
||||
}
|
||||
const msg = document.getElementById('message');
|
||||
if (msg) msg.className = isDark ? 'dark' : 'light';
|
||||
if (msg) {
|
||||
msg.classList.remove('dark', 'light');
|
||||
msg.classList.add(isDark ? 'dark' : 'light');
|
||||
}
|
||||
}
|
||||
|
||||
// module load so the document is in the right theme before React mounts.
|
||||
@@ -158,7 +162,7 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
|
||||
const [isDark, setIsDark] = useState<boolean>(initialDark);
|
||||
const [isUltra, setIsUltra] = useState<boolean>(initialUltra);
|
||||
|
||||
useEffect(() => {
|
||||
useLayoutEffect(() => {
|
||||
applyDom(isDark, isUltra);
|
||||
localStorage.setItem(STORAGE_DARK, String(isDark));
|
||||
localStorage.setItem(STORAGE_ULTRA, String(isUltra));
|
||||
|
||||
51
frontend/src/test/storybook-theme.test.tsx
Normal file
51
frontend/src/test/storybook-theme.test.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { afterEach, expect, test } from 'vitest';
|
||||
|
||||
import { withTheme } from '../../.storybook/preview';
|
||||
import { ThemeProvider } from '@/hooks/useTheme';
|
||||
|
||||
function Story() {
|
||||
return <div>Story</div>;
|
||||
}
|
||||
|
||||
function StorybookTheme({ theme }: { theme: 'light' | 'dark' }) {
|
||||
return withTheme(Story, { globals: { theme } } as Partial<Parameters<typeof withTheme>[1]> as Parameters<typeof withTheme>[1]);
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
document.body.className = '';
|
||||
document.documentElement.removeAttribute('data-theme');
|
||||
});
|
||||
|
||||
test('preserves unrelated body classes when applying the Storybook theme', () => {
|
||||
document.body.className = 'storybook-fixture dark';
|
||||
document.documentElement.setAttribute('data-theme', 'ultra-dark');
|
||||
const { rerender } = render(<StorybookTheme theme="light" />);
|
||||
|
||||
expect(document.body.classList.contains('storybook-fixture')).toBe(true);
|
||||
expect(document.body.classList.contains('light')).toBe(true);
|
||||
expect(document.body.classList.contains('dark')).toBe(false);
|
||||
expect(document.documentElement.hasAttribute('data-theme')).toBe(false);
|
||||
|
||||
rerender(<StorybookTheme theme="dark" />);
|
||||
|
||||
expect(document.body.classList.contains('storybook-fixture')).toBe(true);
|
||||
expect(document.body.classList.contains('dark')).toBe(true);
|
||||
expect(document.body.classList.contains('light')).toBe(false);
|
||||
});
|
||||
|
||||
test('preserves unrelated body classes when applying the panel theme', () => {
|
||||
document.body.className = 'panel-fixture';
|
||||
const message = document.createElement('div');
|
||||
message.id = 'message';
|
||||
message.className = 'message-fixture';
|
||||
document.body.append(message);
|
||||
|
||||
render(<ThemeProvider><div>Panel</div></ThemeProvider>);
|
||||
|
||||
expect(document.body.classList.contains('panel-fixture')).toBe(true);
|
||||
expect(document.body.classList.contains('dark')).toBe(true);
|
||||
expect(document.body.classList.contains('light')).toBe(false);
|
||||
expect(message.classList.contains('message-fixture')).toBe(true);
|
||||
expect(message.classList.contains('dark')).toBe(true);
|
||||
});
|
||||
Reference in New Issue
Block a user