mirror of
https://gitee.com/ant-design/ant-design.git
synced 2024-11-30 02:59:04 +08:00
chore: improve ThemeSwitcher implementation (#39398)
* docs: theme should be in searchParams * chore: use locale text * chore: fix searchParams usage * fix: compact can use with dark/light theme * chore: fix lint
This commit is contained in:
parent
33deda5e38
commit
cf476d9477
@ -3,7 +3,7 @@ import JsonML from 'jsonml.js/lib/utils';
|
||||
import toReactComponent from 'jsonml-to-react-element';
|
||||
import Prism from 'prismjs';
|
||||
import 'prismjs/components/prism-typescript';
|
||||
import { useLocation, useIntl, type IPreviewerProps } from 'dumi';
|
||||
import { useLocation, useSearchParams, useIntl, type IPreviewerProps } from 'dumi';
|
||||
import { ping } from '../../utils';
|
||||
|
||||
let pingDeferrer: PromiseLike<boolean>;
|
||||
@ -36,6 +36,7 @@ export default function fromDumiProps<P extends object>(
|
||||
const hoc = function DumiPropsAntdPreviewer(props: IPreviewerProps) {
|
||||
const showRiddleButton = useShowRiddleButton();
|
||||
const location = useLocation();
|
||||
const [searchParams] = useSearchParams();
|
||||
const { asset, children, demoUrl, expand, description = '', ...meta } = props;
|
||||
const intl = useIntl();
|
||||
const entryCode = asset.dependencies['index.tsx'].value;
|
||||
@ -88,8 +89,7 @@ export default function fromDumiProps<P extends object>(
|
||||
expand,
|
||||
// FIXME: confirm is there has any case?
|
||||
highlightedStyle: '',
|
||||
// FIXME: dumi support usePrefersColor
|
||||
theme: 'light',
|
||||
theme: searchParams.get('theme'),
|
||||
} as P;
|
||||
|
||||
return <WrappedComponent {...transformedProps} />;
|
||||
|
@ -4,7 +4,7 @@ import Icon from '@ant-design/icons';
|
||||
const ThemeIcon: React.FC<{ className?: string }> = (props) => {
|
||||
const SVGIcon = React.useCallback(
|
||||
() => (
|
||||
<svg width={21} height={21} viewBox="0 0 21 21" fill="currentColor" {...props}>
|
||||
<svg width={20} height={20} viewBox="0 0 24 24" fill="currentColor" {...props}>
|
||||
<g fillRule="evenodd">
|
||||
<g fillRule="nonzero">
|
||||
<path d="M7.02 3.635l12.518 12.518a1.863 1.863 0 010 2.635l-1.317 1.318a1.863 1.863 0 01-2.635 0L3.068 7.588A2.795 2.795 0 117.02 3.635zm2.09 14.428a.932.932 0 110 1.864.932.932 0 010-1.864zm-.043-9.747L7.75 9.635l9.154 9.153 1.318-1.317-9.154-9.155zM3.52 12.473c.514 0 .931.417.931.931v.932h.932a.932.932 0 110 1.864h-.932v.931a.932.932 0 01-1.863 0l-.001-.931h-.93a.932.932 0 010-1.864h.93v-.932c0-.514.418-.931.933-.931zm15.374-3.727a1.398 1.398 0 110 2.795 1.398 1.398 0 010-2.795zM4.385 4.953a.932.932 0 000 1.317l2.046 2.047L7.75 7 5.703 4.953a.932.932 0 00-1.318 0zM14.701.36a.932.932 0 01.931.932v.931h.932a.932.932 0 010 1.864h-.933l.001.932a.932.932 0 11-1.863 0l-.001-.932h-.93a.932.932 0 110-1.864h.93v-.931a.932.932 0 01.933-.932z" />
|
||||
|
@ -1,68 +1,51 @@
|
||||
import React from 'react';
|
||||
import { FloatButton, theme } from 'antd';
|
||||
import { FloatButton } from 'antd';
|
||||
import { FormattedMessage } from 'dumi';
|
||||
import { DarkTheme, Light, CompactTheme } from 'antd-token-previewer/es/icons';
|
||||
import ThemeIcon from './ThemeIcon';
|
||||
|
||||
const { defaultAlgorithm, darkAlgorithm, compactAlgorithm } = theme;
|
||||
export type ThemeName = 'light' | 'dark' | 'compact';
|
||||
|
||||
export type ThemeSwitchProps = {
|
||||
value: typeof defaultAlgorithm[];
|
||||
onChange: (value: typeof defaultAlgorithm[]) => void;
|
||||
value?: ThemeName[];
|
||||
onChange: (value: ThemeName[]) => void;
|
||||
};
|
||||
|
||||
const ThemeSwitch: React.FC<ThemeSwitchProps> = ({ value, onChange }) => {
|
||||
const handleLightSwitch = () => {
|
||||
let newValue = [...value];
|
||||
if (value.includes(darkAlgorithm)) {
|
||||
newValue = newValue.filter((item) => item !== darkAlgorithm);
|
||||
}
|
||||
if (!value.includes(defaultAlgorithm)) {
|
||||
newValue.unshift(defaultAlgorithm);
|
||||
}
|
||||
onChange(newValue);
|
||||
};
|
||||
|
||||
const handleDarkSwitch = () => {
|
||||
let newValue = [...value];
|
||||
if (value.includes(defaultAlgorithm)) {
|
||||
newValue = newValue.filter((item) => item !== defaultAlgorithm);
|
||||
}
|
||||
if (!value.includes(darkAlgorithm)) {
|
||||
newValue.push(darkAlgorithm);
|
||||
}
|
||||
onChange(newValue);
|
||||
};
|
||||
|
||||
const handleCompactSwitch = () => {
|
||||
if (value.includes(compactAlgorithm)) {
|
||||
onChange(value.filter((item) => item !== compactAlgorithm));
|
||||
} else {
|
||||
onChange([...value, compactAlgorithm]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
const ThemeSwitch: React.FC<ThemeSwitchProps> = ({ value, onChange }) => (
|
||||
<FloatButton.Group trigger="click" icon={<ThemeIcon />}>
|
||||
<FloatButton
|
||||
icon={<Light />}
|
||||
type={value.includes(defaultAlgorithm) ? 'primary' : 'default'}
|
||||
onClick={handleLightSwitch}
|
||||
tooltip="Light"
|
||||
type={!value.includes('dark') ? 'primary' : 'default'}
|
||||
onClick={() => {
|
||||
if (value.includes('dark')) {
|
||||
onChange(value.filter((theme) => theme !== 'dark'));
|
||||
}
|
||||
}}
|
||||
tooltip={<FormattedMessage id="app.theme.switch.default" />}
|
||||
/>
|
||||
<FloatButton
|
||||
icon={<DarkTheme />}
|
||||
type={value.includes(darkAlgorithm) ? 'primary' : 'default'}
|
||||
onClick={handleDarkSwitch}
|
||||
tooltip="Dark"
|
||||
type={value.includes('dark') ? 'primary' : 'default'}
|
||||
onClick={() => {
|
||||
if (!value.includes('dark')) {
|
||||
onChange([...value, 'dark']);
|
||||
}
|
||||
}}
|
||||
tooltip={<FormattedMessage id="app.theme.switch.dark" />}
|
||||
/>
|
||||
<FloatButton
|
||||
icon={<CompactTheme />}
|
||||
type={value.includes(compactAlgorithm) ? 'primary' : 'default'}
|
||||
onClick={handleCompactSwitch}
|
||||
tooltip="Compact"
|
||||
type={value.includes('compact') ? 'primary' : 'default'}
|
||||
onClick={() => {
|
||||
if (value.includes('compact')) {
|
||||
onChange(value.filter((theme) => theme !== 'compact'));
|
||||
} else {
|
||||
onChange([...value, 'compact']);
|
||||
}
|
||||
}}
|
||||
tooltip={<FormattedMessage id="app.theme.switch.compact" />}
|
||||
/>
|
||||
</FloatButton.Group>
|
||||
);
|
||||
};
|
||||
);
|
||||
|
||||
export default ThemeSwitch;
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React, { startTransition, useLayoutEffect } from 'react';
|
||||
import { useOutlet } from 'dumi';
|
||||
import React from 'react';
|
||||
import { useOutlet, useSearchParams } from 'dumi';
|
||||
import { ConfigProvider, theme as antdTheme } from 'antd';
|
||||
import type { ThemeConfig } from 'antd/es/config-provider/context';
|
||||
import { createCache, StyleProvider } from '@ant-design/cssinjs';
|
||||
import ThemeSwitch from '../common/ThemeSwitch';
|
||||
import type { ThemeName } from '../common/ThemeSwitch';
|
||||
import useLocation from '../../hooks/useLocation';
|
||||
|
||||
const styleCache = createCache();
|
||||
@ -11,9 +11,8 @@ if (typeof global !== 'undefined') {
|
||||
(global as any).styleCache = styleCache;
|
||||
}
|
||||
|
||||
const ANT_DESIGN_SITE_THEME = 'antd-site-theme';
|
||||
|
||||
const getAlgorithm = (theme: string) => {
|
||||
const getAlgorithm = (themes: ThemeName[]) =>
|
||||
(themes || []).map((theme) => {
|
||||
if (theme === 'dark') {
|
||||
return antdTheme.darkAlgorithm;
|
||||
}
|
||||
@ -21,67 +20,30 @@ const getAlgorithm = (theme: string) => {
|
||||
return antdTheme.compactAlgorithm;
|
||||
}
|
||||
return antdTheme.defaultAlgorithm;
|
||||
};
|
||||
|
||||
const getThemeString = (algorithm: typeof antdTheme.defaultAlgorithm) => {
|
||||
if (algorithm === antdTheme.darkAlgorithm) {
|
||||
return 'dark';
|
||||
}
|
||||
if (algorithm === antdTheme.compactAlgorithm) {
|
||||
return 'compact';
|
||||
}
|
||||
return 'light';
|
||||
};
|
||||
});
|
||||
|
||||
const GlobalLayout: React.FC = () => {
|
||||
const outlet = useOutlet();
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const [theme, setTheme] = React.useState<ThemeConfig>({
|
||||
algorithm: [antdTheme.defaultAlgorithm],
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const theme = searchParams.getAll('theme') as unknown as ThemeName[];
|
||||
const handleThemeChange = (value: ThemeName[]) => {
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
theme: value,
|
||||
});
|
||||
|
||||
const handleThemeChange = (newTheme: ThemeConfig, ignoreAlgorithm: boolean = true) => {
|
||||
const nextTheme = { ...newTheme };
|
||||
if (ignoreAlgorithm) {
|
||||
nextTheme.algorithm = theme.algorithm;
|
||||
}
|
||||
setTheme(nextTheme);
|
||||
localStorage.setItem(
|
||||
ANT_DESIGN_SITE_THEME,
|
||||
JSON.stringify(nextTheme, (key, value) => {
|
||||
if (key === 'algorithm') {
|
||||
return Array.isArray(value) ? value.map((item) => getThemeString(item)) : ['light'];
|
||||
}
|
||||
return value;
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const localTheme = localStorage.getItem(ANT_DESIGN_SITE_THEME);
|
||||
if (localTheme) {
|
||||
const themeConfig = JSON.parse(localTheme);
|
||||
if (themeConfig.algorithm) {
|
||||
themeConfig.algorithm = themeConfig.algorithm.map((item: string) => getAlgorithm(item));
|
||||
} else {
|
||||
themeConfig.algorithm = [antdTheme.defaultAlgorithm];
|
||||
}
|
||||
startTransition(() => {
|
||||
setTheme(themeConfig);
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<StyleProvider cache={styleCache}>
|
||||
<ConfigProvider theme={theme}>
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
algorithm: getAlgorithm(theme),
|
||||
}}
|
||||
>
|
||||
{outlet}
|
||||
{!pathname.startsWith('/~demos') && (
|
||||
<ThemeSwitch
|
||||
value={theme.algorithm as []}
|
||||
onChange={(value) => handleThemeChange({ ...theme, algorithm: value }, false)}
|
||||
/>
|
||||
<ThemeSwitch value={theme} onChange={handleThemeChange} />
|
||||
)}
|
||||
</ConfigProvider>
|
||||
</StyleProvider>
|
||||
|
Loading…
Reference in New Issue
Block a user