Browse Source

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
pull/39404/head
afc163 2 years ago
committed by GitHub
parent
commit
cf476d9477
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      .dumi/theme/builtins/Previewer/fromDumiProps.tsx
  2. 2
      .dumi/theme/common/ThemeSwitch/ThemeIcon.tsx
  3. 99
      .dumi/theme/common/ThemeSwitch/index.tsx
  4. 90
      .dumi/theme/layouts/GlobalLayout.tsx

6
.dumi/theme/builtins/Previewer/fromDumiProps.tsx

@ -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} />;

2
.dumi/theme/common/ThemeSwitch/ThemeIcon.tsx

@ -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" />

99
.dumi/theme/common/ThemeSwitch/index.tsx

@ -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 (
<FloatButton.Group trigger="click" icon={<ThemeIcon />}>
<FloatButton
icon={<Light />}
type={value.includes(defaultAlgorithm) ? 'primary' : 'default'}
onClick={handleLightSwitch}
tooltip="Light"
/>
<FloatButton
icon={<DarkTheme />}
type={value.includes(darkAlgorithm) ? 'primary' : 'default'}
onClick={handleDarkSwitch}
tooltip="Dark"
/>
<FloatButton
icon={<CompactTheme />}
type={value.includes(compactAlgorithm) ? 'primary' : 'default'}
onClick={handleCompactSwitch}
tooltip="Compact"
/>
</FloatButton.Group>
);
};
const ThemeSwitch: React.FC<ThemeSwitchProps> = ({ value, onChange }) => (
<FloatButton.Group trigger="click" icon={<ThemeIcon />}>
<FloatButton
icon={<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('dark') ? 'primary' : 'default'}
onClick={() => {
if (!value.includes('dark')) {
onChange([...value, 'dark']);
}
}}
tooltip={<FormattedMessage id="app.theme.switch.dark" />}
/>
<FloatButton
icon={<CompactTheme />}
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;

90
.dumi/theme/layouts/GlobalLayout.tsx

@ -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,77 +11,39 @@ if (typeof global !== 'undefined') {
(global as any).styleCache = styleCache;
}
const ANT_DESIGN_SITE_THEME = 'antd-site-theme';
const getAlgorithm = (theme: string) => {
if (theme === 'dark') {
return antdTheme.darkAlgorithm;
}
if (theme === 'compact') {
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 getAlgorithm = (themes: ThemeName[]) =>
(themes || []).map((theme) => {
if (theme === 'dark') {
return antdTheme.darkAlgorithm;
}
if (theme === 'compact') {
return antdTheme.compactAlgorithm;
}
return antdTheme.defaultAlgorithm;
});
const GlobalLayout: React.FC = () => {
const outlet = useOutlet();
const { pathname } = useLocation();
const [theme, setTheme] = React.useState<ThemeConfig>({
algorithm: [antdTheme.defaultAlgorithm],
});
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;
}),
);
const [searchParams, setSearchParams] = useSearchParams();
const theme = searchParams.getAll('theme') as unknown as ThemeName[];
const handleThemeChange = (value: ThemeName[]) => {
setSearchParams({
...searchParams,
theme: 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…
Cancel
Save